Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/healthcheck #116

Merged
merged 6 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"contract/valory/fpmm_deterministic_factory/0.1.0": "bafybeigjfuahxhb2y4q5ueayry55boaojcryoamwr6fshgayf5s762vpl4",
"contract/valory/wxdai/0.1.0": "bafybeidalocwbhmbto6ii6adldtpcughtdt6j3v4tv36utevjk2wrdyqie",
"contract/valory/fpmm/0.1.0": "bafybeiai2ruj27nnglvn7yc5atojyojo3fkmofw6wrjgz2ybps2uwdizx4",
"skill/valory/market_creation_manager_abci/0.1.0": "bafybeigopmjddoomz57pkeafpjvssvvbemgtl6zitjvvjgj2y2dbyazf5e",
"skill/valory/market_maker_abci/0.1.0": "bafybeih7cs656swz6l33cdw43njf5dqro3lu2rbkr362jtkw3ixahll2ya",
"agent/valory/market_maker/0.1.0": "bafybeih4jazyeuw7w3npc36eujvkrdc4yk5yt33iep2jhj6rjzmpgyjj6e",
"service/valory/market_maker/0.1.0": "bafybeie3ibdzqzpiycrrbc6g6gx2hkhz57cb3tc2gz5toh7qovaotcektm"
"skill/valory/market_creation_manager_abci/0.1.0": "bafybeidgpctdcbxubjdlqzcrzr2qwtuocv236ngtjduabkmja7qvqr3fri",
"skill/valory/market_maker_abci/0.1.0": "bafybeietyfyi6qphajffab37bueo4seb2fdtptak6sneszk7ww7fjhwr5y",
"agent/valory/market_maker/0.1.0": "bafybeibbnx4azoeyuh2zsd6ebxvv2k2y3yvmuhfl4776p5pz4tzjb5uyuu",
"service/valory/market_maker/0.1.0": "bafybeiftf5j3vu7tab5uxfsidxrneayacfjbrgwejt5wk3qoyn4llgpkji"
},
"third_party": {
"protocol/valory/contract_api/1.0.0": "bafybeidgu7o5llh26xp3u3ebq3yluull5lupiyeu6iooi2xyymdrgnzq5i",
Expand Down Expand Up @@ -34,6 +34,7 @@
"connection/valory/p2p_libp2p_client/0.1.0": "bafybeid3xg5k2ol5adflqloy75ibgljmol6xsvzvezebsg7oudxeeolz7e",
"connection/valory/ledger/0.19.0": "bafybeig7woeog4srdby75hpjkmx4rhpkzncbf4h2pm5r6varsp26pf2uhu",
"connection/valory/http_client/0.23.0": "bafybeihi772xgzpqeipp3fhmvpct4y6e6tpjp4sogwqrnf3wqspgeilg4u",
"connection/valory/http_server/0.22.0": "bafybeihpgu56ovmq4npazdbh6y6ru5i7zuv6wvdglpxavsckyih56smu7m",
"skill/valory/abstract_abci/0.1.0": "bafybeieh4ei3qdelmacnm7vwq57phoewgumr3udvxt6pybmuggwc3yk65q",
"skill/valory/abstract_round_abci/0.1.0": "bafybeiar2yhzxacfe3qqamqhaihtlcimquwedffctw55sowx6rac3cm3ui",
"skill/valory/transaction_settlement_abci/0.1.0": "bafybeic3tccdjypuge2lewtlgprwkbb53lhgsgn7oiwzyrcrrptrbeyote",
Expand Down
12 changes: 10 additions & 2 deletions packages/valory/agents/market_maker/aea-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ fingerprint_ignore_patterns: []
connections:
- valory/abci:0.1.0:bafybeicksmavx23ralbdw3ajxv5fq5s4c3wzhbc3zdudefm4jqsgrg72ai
- valory/http_client:0.23.0:bafybeihi772xgzpqeipp3fhmvpct4y6e6tpjp4sogwqrnf3wqspgeilg4u
- valory/http_server:0.22.0:bafybeihpgu56ovmq4npazdbh6y6ru5i7zuv6wvdglpxavsckyih56smu7m
- valory/ledger:0.19.0:bafybeig7woeog4srdby75hpjkmx4rhpkzncbf4h2pm5r6varsp26pf2uhu
- valory/openai:0.1.0:bafybeigyehjbahya5mp7vyp5tjvn36rey4btvzskp3ql3mgxp3zu6gwq5a
- valory/p2p_libp2p_client:0.1.0:bafybeid3xg5k2ol5adflqloy75ibgljmol6xsvzvezebsg7oudxeeolz7e
Expand All @@ -37,9 +38,9 @@ protocols:
skills:
- valory/abstract_abci:0.1.0:bafybeieh4ei3qdelmacnm7vwq57phoewgumr3udvxt6pybmuggwc3yk65q
- valory/abstract_round_abci:0.1.0:bafybeiar2yhzxacfe3qqamqhaihtlcimquwedffctw55sowx6rac3cm3ui
- valory/market_maker_abci:0.1.0:bafybeih7cs656swz6l33cdw43njf5dqro3lu2rbkr362jtkw3ixahll2ya
- valory/market_maker_abci:0.1.0:bafybeietyfyi6qphajffab37bueo4seb2fdtptak6sneszk7ww7fjhwr5y
- valory/registration_abci:0.1.0:bafybeieu7vq3pyns4t5ty6u3sbmpkd7yznpg3rmqifoz3jhy7pmqyg3w6q
- valory/market_creation_manager_abci:0.1.0:bafybeigopmjddoomz57pkeafpjvssvvbemgtl6zitjvvjgj2y2dbyazf5e
- valory/market_creation_manager_abci:0.1.0:bafybeidgpctdcbxubjdlqzcrzr2qwtuocv236ngtjduabkmja7qvqr3fri
- valory/reset_pause_abci:0.1.0:bafybeiameewywqigpupy3u2iwnkfczeiiucue74x2l5lbge74rmw6bgaie
- valory/termination_abci:0.1.0:bafybeif2zim2de356eo3sipkmoev5emwadpqqzk3huwqarywh4tmqt3vzq
- valory/transaction_settlement_abci:0.1.0:bafybeic3tccdjypuge2lewtlgprwkbb53lhgsgn7oiwzyrcrrptrbeyote
Expand Down Expand Up @@ -220,6 +221,7 @@ models:
mech_contract_address: ${str:0x77af31de935740567cf4ff1986d04b2c964a786a}
mech_tool_resolve_market: ${str:resolve-market-reasoning-gpt-4}
answer_retry_intervals: ${list:[0, 86400, 259200, 604800, 1209600]}
service_endpoint_base: ${str:https://dummy_service.autonolas.tech/}
randomness_api:
args:
method: ${str:GET}
Expand All @@ -246,3 +248,9 @@ models:
retries: ${int:5}
url: ${str:https://api.thegraph.com/subgraphs/name/protofire/omen-xdai}
is_abstract: false
---
public_id: valory/http_server:0.22.0:bafybeicblltx7ha3ulthg7bzfccuqqyjmihhrvfeztlgrlcoxhr7kf6nbq
type: connection
config:
host: 0.0.0.0
target_skill_id: valory/market_maker_abci:0.1.0
3 changes: 2 additions & 1 deletion packages/valory/services/market_maker/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license: Apache-2.0
fingerprint:
README.md: bafybeibwz3af6326msp4h3kqehijvmyhaytvyfbo3o2npc2w4b6zrg6pfq
fingerprint_ignore_patterns: []
agent: valory/market_maker:0.1.0:bafybeih4jazyeuw7w3npc36eujvkrdc4yk5yt33iep2jhj6rjzmpgyjj6e
agent: valory/market_maker:0.1.0:bafybeibbnx4azoeyuh2zsd6ebxvv2k2y3yvmuhfl4776p5pz4tzjb5uyuu
number_of_agents: 1
deployment:
agent:
Expand Down Expand Up @@ -106,6 +106,7 @@ models:
google_api_key: ${GOOGLE_API_KEY:str:google_api_key}
google_engine_id: ${GOOGLE_ENGINE_ID:str:google_engine_id}
openai_api_key: ${OPENAI_API_KEY:str:openai_api_key}
service_endpoint_base: ${SERVICE_ENDPOINT_BASE:str:https://dummy_service.autonolas.tech/}
randomness_api:
args:
method: ${RANDOMNESS_API_METHOD:str:GET}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

"""This module contains the handlers for the skill of MarketCreationManagerAbciApp."""


from typing import Optional

from aea.configurations.data_types import PublicId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self.answer_retry_intervals = self._ensure(
key="answer_retry_intervals", kwargs=kwargs, type_=List[int]
)
self.service_endpoint_base = self._ensure("service_endpoint_base", kwargs, str)
super().__init__(*args, **kwargs)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ fingerprint:
behaviours.py: bafybeibaeqww3qnhiqck3pi7t276azxv7jt4tzscex25onaoobtrakqnoi
dialogues.py: bafybeicmaufkl7vdomnfciv7lw4536ssld7x4uemdapuhsyvfpd4ncibza
fsm_specification.yaml: bafybeiebwzp5jirrwo475r2hzwrirx45jlxtp5duo4275phypqfkueybtq
handlers.py: bafybeietxjfli2i57kb7heoy772rcq2znusl36gg7jjj5g3pddw7egny3q
models.py: bafybeic2w6gyk3hhn4ksea3nte4c6mron6f2zojxjgo6u5kyvkfnx2g6ie
handlers.py: bafybeicj2skzmias5pqvbctiidwgioreaabspsppxn5ytl6e356is4lgga
models.py: bafybeicfdblqzyj6ex3qvjmbejjdt6qwecblfsgjdrfj7rydqgo6h7xasm
payloads.py: bafybeibu7sptf43adazxpyzwtfpph7bgfhtwiotg5sdlcwjt6iw3idqn7a
rounds.py: bafybeifnezj3cw323fj5xnieuucfqarznzlpesaj75huylfkpeyuunqkvy
tests/__init__.py: bafybeihfxvqnyfly72tbxnnnglshcilm2kanihqnjiasvcz3ec3csw32ti
Expand Down Expand Up @@ -224,6 +224,7 @@ models:
- 259200
- 604800
- 1209600
service_endpoint_base: https://dummy_service.autonolas.tech/
class_name: MarketCreationManagerParams
randomness_api:
args:
Expand Down
257 changes: 256 additions & 1 deletion packages/valory/skills/market_maker_abci/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@
"""This module contains the handler for the 'price_estimation_abci' skill."""


import json
import re
from datetime import datetime
from enum import Enum
from typing import Callable, Dict, List, Optional, Tuple, Union, cast
from urllib.parse import urlparse

from aea.protocols.base import Message

from packages.valory.connections.http_server.connection import (
PUBLIC_ID as HTTP_SERVER_PUBLIC_ID,
)
from packages.valory.protocols.http import HttpMessage
from packages.valory.skills.abstract_round_abci.handlers import ABCIRoundHandler
from packages.valory.skills.abstract_round_abci.handlers import (
ContractApiHandler as BaseContractApiHandler,
Expand All @@ -39,16 +52,258 @@
from packages.valory.skills.abstract_round_abci.handlers import (
TendermintHandler as BaseTendermintHandler,
)
from packages.valory.skills.market_creation_manager_abci.dialogues import (
HttpDialogue,
HttpDialogues,
)
from packages.valory.skills.market_creation_manager_abci.handlers import (
LlmHandler as BaseLlmHandler,
)
from packages.valory.skills.market_creation_manager_abci.rounds import SynchronizedData
from packages.valory.skills.market_maker_abci.models import SharedState


MarketCreatorABCIRoundHandler = ABCIRoundHandler
HttpHandler = BaseHttpHandler
SigningHandler = BaseSigningHandler
LedgerApiHandler = BaseLedgerApiHandler
ContractApiHandler = BaseContractApiHandler
TendermintHandler = BaseTendermintHandler
IpfsHandler = BaseIpfsHandler
LlmHandler = BaseLlmHandler


class HttpCode(Enum):
"""Http codes"""

OK_CODE = 200
NOT_FOUND_CODE = 404
BAD_REQUEST_CODE = 400
NOT_READY = 503


class HttpMethod(Enum):
"""Http methods"""

GET = "get"
HEAD = "head"
POST = "post"


class HttpHandler(BaseHttpHandler):
"""This implements the HTTP handler."""

SUPPORTED_PROTOCOL = HttpMessage.protocol_id

def setup(self) -> None:
"""Implement the setup."""

# Custom hostname (set via params)
service_endpoint_base = urlparse(
self.context.params.service_endpoint_base
).hostname

# Propel hostname regex
propel_uri_base_hostname = (
r"https?:\/\/[a-zA-Z0-9]{16}.agent\.propel\.(staging\.)?autonolas\.tech"
)

# Route regexes
hostname_regex = rf".*({service_endpoint_base}|{propel_uri_base_hostname}|localhost|127.0.0.1|0.0.0.0)(:\d+)?"
self.handler_url_regex = rf"{hostname_regex}\/.*"
health_url_regex = rf"{hostname_regex}\/healthcheck"

# Routes
self.routes = {
(HttpMethod.POST.value,): [],
(HttpMethod.GET.value, HttpMethod.HEAD.value): [
(health_url_regex, self._handle_get_health),
],
}

self.json_content_header = "Content-Type: application/json\n"

@property
def synchronized_data(self) -> SynchronizedData:
"""Return the synchronized data."""
return SynchronizedData(
db=self.context.state.round_sequence.latest_synchronized_data.db
)

def _get_handler(self, url: str, method: str) -> Tuple[Optional[Callable], Dict]:
"""Check if an url is meant to be handled in this handler

We expect url to match the pattern {hostname}/.*,
where hostname is allowed to be localhost, 127.0.0.1 or the token_uri_base's hostname.
Examples:
localhost:8000/0
127.0.0.1:8000/100
https://pfp.staging.autonolas.tech/45
http://pfp.staging.autonolas.tech/120

:param url: the url to check
:param method: the http method
:returns: the handling method if the message is intended to be handled by this handler, None otherwise, and the regex captures
"""
# Check base url
if not re.match(self.handler_url_regex, url):
self.context.logger.info(
f"The url {url} does not match the HttpHandler's pattern"
)
return None, {}

# Check if there is a route for this request
for methods, routes in self.routes.items():
if method not in methods:
continue

for route in routes: # type: ignore
# Routes are tuples like (route_regex, handle_method)
m = re.match(route[0], url)
if m:
return route[1], m.groupdict()

# No route found
self.context.logger.info(
f"The message [{method}] {url} is intended for the HttpHandler but did not match any valid pattern"
)
return self._handle_bad_request, {}

def handle(self, message: Message) -> None:
"""
Implement the reaction to an envelope.

:param message: the message
"""
http_msg = cast(HttpMessage, message)

# Check if this is a request sent from the http_server skill
if (
http_msg.performative != HttpMessage.Performative.REQUEST
or message.sender != str(HTTP_SERVER_PUBLIC_ID.without_hash())
):
super().handle(message)
return

# Check if this message is for this skill. If not, send to super()
handler, kwargs = self._get_handler(http_msg.url, http_msg.method)
if not handler:
super().handle(message)
return

# Retrieve dialogues
http_dialogues = cast(HttpDialogues, self.context.http_dialogues)
http_dialogue = cast(HttpDialogue, http_dialogues.update(http_msg))

# Invalid message
if http_dialogue is None:
self.context.logger.info(
"Received invalid http message={}, unidentified dialogue.".format(
http_msg
)
)
return

# Handle message
self.context.logger.info(
"Received http request with method={}, url={} and body={!r}".format(
http_msg.method,
http_msg.url,
http_msg.body,
)
)
handler(http_msg, http_dialogue, **kwargs)

def _handle_bad_request(
self, http_msg: HttpMessage, http_dialogue: HttpDialogue
) -> None:
"""
Handle a Http bad request.

:param http_msg: the http message
:param http_dialogue: the http dialogue
"""
http_response = http_dialogue.reply(
performative=HttpMessage.Performative.RESPONSE,
target_message=http_msg,
version=http_msg.version,
status_code=HttpCode.BAD_REQUEST_CODE.value,
status_text="Bad request",
headers=http_msg.headers,
body=b"",
)

# Send response
self.context.logger.info("Responding with: {}".format(http_response))
self.context.outbox.put_message(message=http_response)

def _send_ok_response(
self,
http_msg: HttpMessage,
http_dialogue: HttpDialogue,
data: Union[Dict, List],
) -> None:
"""Send an OK response with the provided data"""
http_response = http_dialogue.reply(
performative=HttpMessage.Performative.RESPONSE,
target_message=http_msg,
version=http_msg.version,
status_code=HttpCode.OK_CODE.value,
status_text="Success",
headers=f"{self.json_content_header}{http_msg.headers}",
body=json.dumps(data).encode("utf-8"),
)

# Send response
self.context.logger.info("Responding with: {}".format(http_response))
self.context.outbox.put_message(message=http_response)

def _handle_get_health(
self, http_msg: HttpMessage, http_dialogue: HttpDialogue
) -> None:
"""
Handle a Http request of verb GET.

:param http_msg: the http message
:param http_dialogue: the http dialogue
"""
seconds_since_last_transition = None
is_tm_unhealthy = None
is_transitioning_fast = None
current_round = None
rounds = None

round_sequence = cast(SharedState, self.context.state).round_sequence

if round_sequence._last_round_transition_timestamp:
is_tm_unhealthy = cast(
SharedState, self.context.state
).round_sequence.block_stall_deadline_expired

current_time = datetime.now().timestamp()
seconds_since_last_transition = current_time - datetime.timestamp(
round_sequence._last_round_transition_timestamp
)

is_transitioning_fast = (
not is_tm_unhealthy
and seconds_since_last_transition
< 2 * self.context.params.reset_pause_duration
)

if round_sequence._abci_app:
current_round = round_sequence._abci_app.current_round.round_id
rounds = [
r.round_id for r in round_sequence._abci_app._previous_rounds[-25:]
]
rounds.append(current_round)

data = {
"seconds_since_last_transition": seconds_since_last_transition,
"is_tm_healthy": not is_tm_unhealthy,
"period": self.synchronized_data.period_count,
"reset_pause_duration": self.context.params.reset_pause_duration,
"rounds": rounds,
"is_transitioning_fast": is_transitioning_fast,
}

self._send_ok_response(http_msg, http_dialogue, data)
Loading
Loading