21
21
22
22
import json
23
23
import re
24
- from datetime import datetime
24
+ from datetime import datetime , timezone
25
25
from enum import Enum
26
26
from typing import Any , Callable , Dict , Optional , Tuple , cast
27
27
from urllib .parse import urlparse
33
33
)
34
34
from packages .valory .protocols .http .message import HttpMessage
35
35
from packages .valory .protocols .ipfs import IpfsMessage
36
+ from packages .valory .skills .abstract_round_abci .base import RoundSequence
36
37
from packages .valory .skills .abstract_round_abci .handlers import (
37
38
ABCIRoundHandler as BaseABCIRoundHandler ,
38
39
)
70
71
TendermintHandler = BaseTendermintHandler
71
72
72
73
74
+ FSM_REPR_MAX_DEPTH = 25
75
+
76
+
73
77
class IpfsHandler (AbstractResponseHandler ):
74
78
"""IPFS message handler."""
75
79
@@ -104,6 +108,7 @@ def handle(self, message: IpfsMessage) -> None:
104
108
OK_CODE = 200
105
109
NOT_FOUND_CODE = 404
106
110
BAD_REQUEST_CODE = 400
111
+ TOO_EARLY_CODE = 425
107
112
AVERAGE_PERIOD_SECONDS = 10
108
113
109
114
@@ -156,12 +161,15 @@ def setup(self) -> None:
156
161
157
162
self .rounds_info = load_rounds_info_with_transitions ()
158
163
164
+ @property
165
+ def round_sequence (self ) -> RoundSequence :
166
+ """Return the round sequence."""
167
+ return self .context .state .round_sequence
168
+
159
169
@property
160
170
def synchronized_data (self ) -> SynchronizedData :
161
171
"""Return the synchronized data."""
162
- return SynchronizedData (
163
- db = self .context .state .round_sequence .latest_synchronized_data .db
164
- )
172
+ return SynchronizedData (db = self .round_sequence .latest_synchronized_data .db )
165
173
166
174
def _get_handler (self , url : str , method : str ) -> Tuple [Optional [Callable ], Dict ]:
167
175
"""Check if an url is meant to be handled in this handler
@@ -176,7 +184,7 @@ def _get_handler(self, url: str, method: str) -> Tuple[Optional[Callable], Dict]
176
184
# Check base url
177
185
if not re .match (self .handler_url_regex , url ):
178
186
self .context .logger .info (
179
- f"The url { url } does not match the DynamicNFT HttpHandler's pattern"
187
+ f"The url { url } does not match the HttpHandler's pattern"
180
188
)
181
189
return None , {}
182
190
@@ -193,7 +201,7 @@ def _get_handler(self, url: str, method: str) -> Tuple[Optional[Callable], Dict]
193
201
194
202
# No route found
195
203
self .context .logger .info (
196
- f"The message [{ method } ] { url } is intended for the DynamicNFT HttpHandler but did not match any valid pattern"
204
+ f"The message [{ method } ] { url } is intended for the HttpHandler but did not match any valid pattern"
197
205
)
198
206
return self ._handle_bad_request , {}
199
207
@@ -265,6 +273,36 @@ def _handle_bad_request(
265
273
self .context .logger .info ("Responding with: {}" .format (http_response ))
266
274
self .context .outbox .put_message (message = http_response )
267
275
276
+ def _has_transitioned (self ) -> bool :
277
+ """Check if the agent has transitioned."""
278
+ try :
279
+ return bool (self .round_sequence .last_round_transition_height )
280
+ except ValueError :
281
+ return False
282
+
283
+ def _handle_too_early (
284
+ self , http_msg : HttpMessage , http_dialogue : HttpDialogue
285
+ ) -> None :
286
+ """
287
+ Handle a request when the FSM's loop has not started yet.
288
+
289
+ :param http_msg: the http message
290
+ :param http_dialogue: the http dialogue
291
+ """
292
+ http_response = http_dialogue .reply (
293
+ performative = HttpMessage .Performative .RESPONSE ,
294
+ target_message = http_msg ,
295
+ version = http_msg .version ,
296
+ status_code = TOO_EARLY_CODE ,
297
+ status_text = "The state machine has not started yet! Please try again later..." ,
298
+ headers = http_msg .headers ,
299
+ body = b"" ,
300
+ )
301
+
302
+ # Send response
303
+ self .context .logger .info ("Responding with: {}" .format (http_response ))
304
+ self .context .outbox .put_message (message = http_response )
305
+
268
306
def _handle_get_health (
269
307
self , http_msg : HttpMessage , http_dialogue : HttpDialogue
270
308
) -> None :
@@ -274,40 +312,41 @@ def _handle_get_health(
274
312
:param http_msg: the http message
275
313
:param http_dialogue: the http dialogue
276
314
"""
277
- seconds_since_last_transition = None
278
- is_tm_unhealthy = None
279
- is_transitioning_fast = None
280
- current_round = None
281
- rounds = None
315
+ if not self ._has_transitioned ():
316
+ self ._handle_too_early (http_msg , http_dialogue )
317
+ return
318
+
282
319
has_required_funds = self ._check_required_funds ()
283
320
is_receiving_mech_responses = self ._check_is_receiving_mech_responses ()
284
321
is_staking_kpi_met = self .synchronized_data .is_staking_kpi_met
285
322
staking_status = self .synchronized_data .service_staking_state .name .lower ()
286
323
287
- round_sequence = cast (SharedState , self .context .state ).round_sequence
324
+ round_sequence = self .round_sequence
325
+ is_tm_unhealthy = round_sequence .block_stall_deadline_expired
288
326
289
- if round_sequence ._last_round_transition_timestamp :
290
- is_tm_unhealthy = cast (
291
- SharedState , self .context .state
292
- ).round_sequence .block_stall_deadline_expired
293
-
294
- current_time = datetime .now ().timestamp ()
295
- seconds_since_last_transition = current_time - datetime .timestamp (
296
- round_sequence ._last_round_transition_timestamp
297
- )
327
+ current_time = datetime .now ().timestamp ()
328
+ seconds_since_last_transition = current_time - datetime .timestamp (
329
+ round_sequence .last_round_transition_timestamp
330
+ )
298
331
299
- is_transitioning_fast = (
300
- not is_tm_unhealthy
301
- and seconds_since_last_transition
302
- < 2 * self .context .params .reset_pause_duration
303
- )
332
+ abci_app = self .round_sequence .abci_app
333
+ previous_rounds = abci_app ._previous_rounds
334
+ previous_round_cls = type (previous_rounds [- 1 ])
335
+ previous_round_events = abci_app .transition_function .get (
336
+ previous_round_cls , {}
337
+ ).keys ()
338
+ previous_round_timeouts = {
339
+ abci_app .event_to_timeout .get (event , - 1 ) for event in previous_round_events
340
+ }
341
+ last_round_timeout = max (previous_round_timeouts )
342
+ is_transitioning_fast = (
343
+ not is_tm_unhealthy
344
+ and seconds_since_last_transition < 2 * last_round_timeout
345
+ )
304
346
305
- if round_sequence ._abci_app :
306
- current_round = round_sequence ._abci_app .current_round .round_id
307
- rounds = [
308
- r .round_id for r in round_sequence ._abci_app ._previous_rounds [- 25 :]
309
- ]
310
- rounds .append (current_round )
347
+ rounds = [r .round_id for r in previous_rounds [- FSM_REPR_MAX_DEPTH :]] + [
348
+ round_sequence .current_round_id
349
+ ]
311
350
312
351
data = {
313
352
"seconds_since_last_transition" : seconds_since_last_transition ,
@@ -348,7 +387,7 @@ def _send_ok_response(
348
387
def _send_not_found_response (
349
388
self , http_msg : HttpMessage , http_dialogue : HttpDialogue
350
389
) -> None :
351
- """Send an not found response"""
390
+ """Send a not found response"""
352
391
http_response = http_dialogue .reply (
353
392
performative = HttpMessage .Performative .RESPONSE ,
354
393
target_message = http_msg ,
@@ -375,6 +414,6 @@ def _check_is_receiving_mech_responses(self) -> bool:
375
414
# (an on chain transaction)
376
415
return (
377
416
self .synchronized_data .decision_receive_timestamp
378
- < int (datetime .utcnow ( ).timestamp ())
417
+ < int (datetime .now ( timezone . utc ).timestamp ())
379
418
- self .context .params .expected_mech_response_time
380
419
)
0 commit comments