diff --git a/packages/valory/skills/reset_pause_abci/README.md b/packages/valory/skills/reset_pause_abci/README.md new file mode 100644 index 00000000..99ca7988 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/README.md @@ -0,0 +1,23 @@ +# Reset and pause abci + +## Description + +This module contains the ABCI reset and pause skill for an AEA. It implements an ABCI +application. + +## Behaviours + +* `ResetAndPauseBehaviour` + + Reset state. + +* `ResetPauseABCIConsensusBehaviour` + + This behaviour manages the consensus stages for the reset and pause abci app. + +## Handlers + +* `ResetPauseABCIHandler` +* `HttpHandler` +* `SigningHandler` + diff --git a/packages/valory/skills/reset_pause_abci/__init__.py b/packages/valory/skills/reset_pause_abci/__init__.py new file mode 100644 index 00000000..850ef394 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2022 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the Reset & Pause skill for an AEA.""" + +from aea.configurations.base import PublicId + + +PUBLIC_ID = PublicId.from_str("valory/reset_pause_abci:0.1.0") diff --git a/packages/valory/skills/reset_pause_abci/behaviours.py b/packages/valory/skills/reset_pause_abci/behaviours.py new file mode 100644 index 00000000..9ee30a1e --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/behaviours.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the behaviours for the 'reset_pause_abci' skill.""" + +from abc import ABC +from typing import Generator, Set, Type, cast + +from packages.valory.skills.abstract_round_abci.base import BaseSynchronizedData +from packages.valory.skills.abstract_round_abci.behaviours import ( + AbstractRoundBehaviour, + BaseBehaviour, +) +from packages.valory.skills.reset_pause_abci.models import Params, SharedState +from packages.valory.skills.reset_pause_abci.payloads import ResetPausePayload +from packages.valory.skills.reset_pause_abci.rounds import ( + ResetAndPauseRound, + ResetPauseAbciApp, +) + + +class ResetAndPauseBaseBehaviour(BaseBehaviour, ABC): + """Reset behaviour.""" + + @property + def synchronized_data(self) -> BaseSynchronizedData: + """Return the synchronized data.""" + return cast( + BaseSynchronizedData, + cast(SharedState, self.context.state).synchronized_data, + ) + + @property + def params(self) -> Params: + """Return the params.""" + return cast(Params, self.context.params) + + +class ResetAndPauseBehaviour(ResetAndPauseBaseBehaviour): + """Reset and pause behaviour.""" + + matching_round = ResetAndPauseRound + + def async_act(self) -> Generator: + """ + Do the action. + + Steps: + - Trivially log the behaviour. + - Sleep for configured interval. + - Build a registration transaction. + - Send the transaction and wait for it to be mined. + - Wait until ABCI application transitions to the next round. + - Go to the next behaviour (set done event). + """ + # + 1 because `period_count` starts from 0 + n_periods_done = self.synchronized_data.period_count + 1 + reset_tm_nodes = n_periods_done % self.params.reset_tendermint_after == 0 + if reset_tm_nodes: + tendermint_reset = yield from self.reset_tendermint_with_wait() + if not tendermint_reset: + return + else: + yield from self.wait_from_last_timestamp(self.params.reset_pause_duration) + self.context.logger.info("Period end.") + self.context.benchmark_tool.save(self.synchronized_data.period_count) + + payload = ResetPausePayload( + self.context.agent_address, self.synchronized_data.period_count + ) + yield from self.send_a2a_transaction(payload, reset_tm_nodes) + yield from self.wait_until_round_end() + self.set_done() + + +class ResetPauseABCIConsensusBehaviour(AbstractRoundBehaviour): + """This behaviour manages the consensus stages for the reset_pause_abci app.""" + + initial_behaviour_cls = ResetAndPauseBehaviour + abci_app_cls = ResetPauseAbciApp + behaviours: Set[Type[BaseBehaviour]] = { + ResetAndPauseBehaviour, # type: ignore + } diff --git a/packages/valory/skills/reset_pause_abci/dialogues.py b/packages/valory/skills/reset_pause_abci/dialogues.py new file mode 100644 index 00000000..e244bde0 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/dialogues.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the classes required for dialogue management.""" + +from packages.valory.skills.abstract_round_abci.dialogues import ( + AbciDialogue as BaseAbciDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + AbciDialogues as BaseAbciDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + ContractApiDialogue as BaseContractApiDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + ContractApiDialogues as BaseContractApiDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + HttpDialogue as BaseHttpDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + HttpDialogues as BaseHttpDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + IpfsDialogue as BaseIpfsDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + IpfsDialogues as BaseIpfsDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + LedgerApiDialogue as BaseLedgerApiDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + LedgerApiDialogues as BaseLedgerApiDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + SigningDialogue as BaseSigningDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + SigningDialogues as BaseSigningDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + TendermintDialogue as BaseTendermintDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + TendermintDialogues as BaseTendermintDialogues, +) + + +AbciDialogue = BaseAbciDialogue +AbciDialogues = BaseAbciDialogues + + +HttpDialogue = BaseHttpDialogue +HttpDialogues = BaseHttpDialogues + + +SigningDialogue = BaseSigningDialogue +SigningDialogues = BaseSigningDialogues + + +LedgerApiDialogue = BaseLedgerApiDialogue +LedgerApiDialogues = BaseLedgerApiDialogues + + +ContractApiDialogue = BaseContractApiDialogue +ContractApiDialogues = BaseContractApiDialogues + + +TendermintDialogue = BaseTendermintDialogue +TendermintDialogues = BaseTendermintDialogues + + +IpfsDialogue = BaseIpfsDialogue +IpfsDialogues = BaseIpfsDialogues diff --git a/packages/valory/skills/reset_pause_abci/fsm_specification.yaml b/packages/valory/skills/reset_pause_abci/fsm_specification.yaml new file mode 100644 index 00000000..b4882ef0 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/fsm_specification.yaml @@ -0,0 +1,19 @@ +alphabet_in: +- DONE +- NO_MAJORITY +- RESET_AND_PAUSE_TIMEOUT +default_start_state: ResetAndPauseRound +final_states: +- FinishedResetAndPauseErrorRound +- FinishedResetAndPauseRound +label: ResetPauseAbciApp +start_states: +- ResetAndPauseRound +states: +- FinishedResetAndPauseErrorRound +- FinishedResetAndPauseRound +- ResetAndPauseRound +transition_func: + (ResetAndPauseRound, DONE): FinishedResetAndPauseRound + (ResetAndPauseRound, NO_MAJORITY): FinishedResetAndPauseErrorRound + (ResetAndPauseRound, RESET_AND_PAUSE_TIMEOUT): FinishedResetAndPauseErrorRound diff --git a/packages/valory/skills/reset_pause_abci/handlers.py b/packages/valory/skills/reset_pause_abci/handlers.py new file mode 100644 index 00000000..d6c6335d --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/handlers.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the handler for the 'reset_pause_abci' skill.""" + +from packages.valory.skills.abstract_round_abci.handlers import ( + ABCIRoundHandler as BaseABCIRoundHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + ContractApiHandler as BaseContractApiHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + HttpHandler as BaseHttpHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + IpfsHandler as BaseIpfsHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + LedgerApiHandler as BaseLedgerApiHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + SigningHandler as BaseSigningHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + TendermintHandler as BaseTendermintHandler, +) + + +ABCIHandler = BaseABCIRoundHandler +HttpHandler = BaseHttpHandler +SigningHandler = BaseSigningHandler +LedgerApiHandler = BaseLedgerApiHandler +ContractApiHandler = BaseContractApiHandler +TendermintHandler = BaseTendermintHandler +IpfsHandler = BaseIpfsHandler diff --git a/packages/valory/skills/reset_pause_abci/models.py b/packages/valory/skills/reset_pause_abci/models.py new file mode 100644 index 00000000..89d44fc9 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/models.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the shared state for the 'reset_pause_abci' application.""" + +from packages.valory.skills.abstract_round_abci.models import BaseParams +from packages.valory.skills.abstract_round_abci.models import ( + BenchmarkTool as BaseBenchmarkTool, +) +from packages.valory.skills.abstract_round_abci.models import Requests as BaseRequests +from packages.valory.skills.abstract_round_abci.models import ( + SharedState as BaseSharedState, +) +from packages.valory.skills.reset_pause_abci.rounds import Event, ResetPauseAbciApp + + +MARGIN = 5 + +Requests = BaseRequests +BenchmarkTool = BaseBenchmarkTool + + +class SharedState(BaseSharedState): + """Keep the current shared state of the skill.""" + + abci_app_cls = ResetPauseAbciApp + + def setup(self) -> None: + """Set up.""" + super().setup() + ResetPauseAbciApp.event_to_timeout[ + Event.ROUND_TIMEOUT + ] = self.context.params.round_timeout_seconds + ResetPauseAbciApp.event_to_timeout[Event.RESET_AND_PAUSE_TIMEOUT] = ( + self.context.params.reset_pause_duration + MARGIN + ) + + +Params = BaseParams diff --git a/packages/valory/skills/reset_pause_abci/payloads.py b/packages/valory/skills/reset_pause_abci/payloads.py new file mode 100644 index 00000000..d5c00586 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/payloads.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the transaction payloads for the reset_pause_abci app.""" + +from dataclasses import dataclass + +from packages.valory.skills.abstract_round_abci.base import BaseTxPayload + + +@dataclass(frozen=True) +class ResetPausePayload(BaseTxPayload): + """Represent a transaction payload of type 'reset'.""" + + period_count: int diff --git a/packages/valory/skills/reset_pause_abci/rounds.py b/packages/valory/skills/reset_pause_abci/rounds.py new file mode 100644 index 00000000..1fd666ac --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/rounds.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the data classes for the reset_pause_abci application.""" + +from enum import Enum +from typing import Dict, Optional, Set, Tuple + +from packages.valory.skills.abstract_round_abci.base import ( + AbciApp, + AbciAppTransitionFunction, + AppState, + BaseSynchronizedData, + CollectSameUntilThresholdRound, + DegenerateRound, +) +from packages.valory.skills.reset_pause_abci.payloads import ResetPausePayload + + +class Event(Enum): + """Event enumeration for the reset_pause_abci app.""" + + DONE = "done" + ROUND_TIMEOUT = "round_timeout" + NO_MAJORITY = "no_majority" + RESET_AND_PAUSE_TIMEOUT = "reset_and_pause_timeout" + + +class ResetAndPauseRound(CollectSameUntilThresholdRound): + """A round that represents that consensus is reached (the final round)""" + + payload_class = ResetPausePayload + _allow_rejoin_payloads = True + synchronized_data_class = BaseSynchronizedData + + def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Event]]: + """Process the end of the block.""" + if self.threshold_reached: + return self.synchronized_data.create(), Event.DONE + if not self.is_majority_possible( + self.collection, self.synchronized_data.nb_participants + ): + return self.synchronized_data, Event.NO_MAJORITY + return None + + +class FinishedResetAndPauseRound(DegenerateRound): + """A round that represents reset and pause has finished""" + + +class FinishedResetAndPauseErrorRound(DegenerateRound): + """A round that represents reset and pause has finished with errors""" + + +class ResetPauseAbciApp(AbciApp[Event]): + """ResetPauseAbciApp + + Initial round: ResetAndPauseRound + + Initial states: {ResetAndPauseRound} + + Transition states: + 0. ResetAndPauseRound + - done: 1. + - reset and pause timeout: 2. + - no majority: 2. + 1. FinishedResetAndPauseRound + 2. FinishedResetAndPauseErrorRound + + Final states: {FinishedResetAndPauseErrorRound, FinishedResetAndPauseRound} + + Timeouts: + round timeout: 30.0 + reset and pause timeout: 30.0 + """ + + initial_round_cls: AppState = ResetAndPauseRound + transition_function: AbciAppTransitionFunction = { + ResetAndPauseRound: { + Event.DONE: FinishedResetAndPauseRound, + Event.RESET_AND_PAUSE_TIMEOUT: FinishedResetAndPauseErrorRound, + Event.NO_MAJORITY: FinishedResetAndPauseErrorRound, + }, + FinishedResetAndPauseRound: {}, + FinishedResetAndPauseErrorRound: {}, + } + final_states: Set[AppState] = { + FinishedResetAndPauseRound, + FinishedResetAndPauseErrorRound, + } + event_to_timeout: Dict[Event, float] = { + Event.ROUND_TIMEOUT: 30.0, + Event.RESET_AND_PAUSE_TIMEOUT: 30.0, + } + db_pre_conditions: Dict[AppState, Set[str]] = {ResetAndPauseRound: set()} + db_post_conditions: Dict[AppState, Set[str]] = { + FinishedResetAndPauseRound: set(), + FinishedResetAndPauseErrorRound: set(), + } diff --git a/packages/valory/skills/reset_pause_abci/skill.yaml b/packages/valory/skills/reset_pause_abci/skill.yaml new file mode 100644 index 00000000..c4ae62da --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/skill.yaml @@ -0,0 +1,140 @@ +name: reset_pause_abci +author: valory +version: 0.1.0 +type: skill +description: ABCI application for resetting and pausing app executions. +license: Apache-2.0 +aea_version: '>=1.0.0, <2.0.0' +fingerprint: + README.md: bafybeigyx3zutnbq2sqlgeo2hi2vjgpmnlspnkyh4wemjfrqkrpel27bwi + __init__.py: bafybeicx55fcmu5t2lrrs4wqi6bdvsmoq2csfqebyzwy6oh4olmhnvmelu + behaviours.py: bafybeich7tmipn2zsuqsmhtbrmmqys3mpvn3jctx6g3kz2atet2atl3j6q + dialogues.py: bafybeigabhaykiyzbluu4mk6bbrmqhzld2kyp32pg24bvjmzrrb74einwm + fsm_specification.yaml: bafybeietrxvm2odv3si3ecep3by6rftsirzzazxpmeh73yvtsis2mfaali + handlers.py: bafybeie22h45jr2opf2waszr3qt5km2fppcaahalcavhzutgb6pyyywqxq + models.py: bafybeiagj2e73wvzfqti6chbgkxh5tawzdjwqnxlo2bcfa5lyzy6ogzh2u + payloads.py: bafybeihychpsosovpyq7bh6aih2cyjkxr23j7becd5apetrqivvnolzm7i + rounds.py: bafybeifi2gpj2piilxtqcvv6lxhwpnbl7xs3a3trh3wvlv2wihowoon4tm + tests/__init__.py: bafybeiclijinxvycj7agcagt2deuuyh7zxyp7k2s55la6lh3jghzqvfux4 + tests/test_behaviours.py: bafybeigblrmkjci6at74yetaevgw5zszhivpednbok7fby4tqn7zt2vemy + tests/test_dialogues.py: bafybeif7pe7v34cfznzv4htyuevx733ersmk4bqjcgajn2535jmuujdmzm + tests/test_handlers.py: bafybeiggog2k65ijtvqwkvjvmaoo6khwgfkeodddzl6u76gcvvongwjawy + tests/test_payloads.py: bafybeifj343tlaiasebfgahfxehn4oi74omgah3ju2pze2fefoouid2zdq + tests/test_rounds.py: bafybeifz67lfay4pkz5ipblpfpadl4zmd5riajkv6sdsiby22z24gp3cxa +fingerprint_ignore_patterns: [] +connections: [] +contracts: [] +protocols: [] +skills: +- valory/abstract_round_abci:0.1.0:bafybeif75fef5csbnc6xthpgtnwvd4ojj5zmbeadt4jxmkgap2eo24qixa +behaviours: + main: + args: {} + class_name: ResetPauseABCIConsensusBehaviour +handlers: + abci: + args: {} + class_name: ABCIHandler + contract_api: + args: {} + class_name: ContractApiHandler + http: + args: {} + class_name: HttpHandler + ipfs: + args: {} + class_name: IpfsHandler + ledger_api: + args: {} + class_name: LedgerApiHandler + signing: + args: {} + class_name: SigningHandler + tendermint: + args: {} + class_name: TendermintHandler +models: + abci_dialogues: + args: {} + class_name: AbciDialogues + benchmark_tool: + args: + log_dir: /logs + class_name: BenchmarkTool + contract_api_dialogues: + args: {} + class_name: ContractApiDialogues + http_dialogues: + args: {} + class_name: HttpDialogues + ipfs_dialogues: + args: {} + class_name: IpfsDialogues + ledger_api_dialogues: + args: {} + class_name: LedgerApiDialogues + params: + args: + cleanup_history_depth: 1 + cleanup_history_depth_current: null + drand_public_key: 868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31 + genesis_config: + genesis_time: '2022-05-20T16:00:21.735122717Z' + chain_id: chain-c4daS1 + consensus_params: + block: + max_bytes: '22020096' + max_gas: '-1' + time_iota_ms: '1000' + evidence: + max_age_num_blocks: '100000' + max_age_duration: '172800000000000' + max_bytes: '1048576' + validator: + pub_key_types: + - ed25519 + version: {} + voting_power: '10' + keeper_timeout: 30.0 + max_attempts: 10 + max_healthcheck: 120 + on_chain_service_id: null + request_retry_delay: 1.0 + request_timeout: 10.0 + reset_pause_duration: 10 + reset_tendermint_after: 2 + retry_attempts: 400 + retry_timeout: 3 + round_timeout_seconds: 30.0 + service_id: reset_pause_abci + service_registry_address: null + setup: {} + share_tm_config_on_startup: false + sleep_time: 1 + tendermint_check_sleep_delay: 3 + tendermint_com_url: http://localhost:8080 + tendermint_max_retries: 5 + tendermint_p2p_url: localhost:26656 + tendermint_url: http://localhost:26657 + tx_timeout: 10.0 + use_termination: false + use_slashing: false + slash_cooldown_hours: 3 + slash_threshold_amount: 10000000000000000 + light_slash_unit_amount: 5000000000000000 + serious_slash_unit_amount: 8000000000000000 + class_name: Params + requests: + args: {} + class_name: Requests + signing_dialogues: + args: {} + class_name: SigningDialogues + state: + args: {} + class_name: SharedState + tendermint_dialogues: + args: {} + class_name: TendermintDialogues +dependencies: {} +is_abstract: true diff --git a/packages/valory/skills/reset_pause_abci/tests/__init__.py b/packages/valory/skills/reset_pause_abci/tests/__init__.py new file mode 100644 index 00000000..db0918fb --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/tests/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2022 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Tests for valory/reset_pause_abci skill.""" diff --git a/packages/valory/skills/reset_pause_abci/tests/test_behaviours.py b/packages/valory/skills/reset_pause_abci/tests/test_behaviours.py new file mode 100644 index 00000000..54356764 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/tests/test_behaviours.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Tests for valory/reset_pause_abci skill's behaviours.""" + +# pylint: skip-file + +from pathlib import Path +from typing import Callable, Generator, Optional +from unittest import mock +from unittest.mock import MagicMock + +import pytest + +from packages.valory.skills.abstract_round_abci.base import AbciAppDB +from packages.valory.skills.abstract_round_abci.base import ( + BaseSynchronizedData as ResetSynchronizedSata, +) +from packages.valory.skills.abstract_round_abci.behaviour_utils import ( + make_degenerate_behaviour, +) +from packages.valory.skills.abstract_round_abci.test_tools.base import ( + FSMBehaviourBaseCase, +) +from packages.valory.skills.reset_pause_abci import PUBLIC_ID +from packages.valory.skills.reset_pause_abci.behaviours import ResetAndPauseBehaviour +from packages.valory.skills.reset_pause_abci.rounds import Event as ResetEvent +from packages.valory.skills.reset_pause_abci.rounds import FinishedResetAndPauseRound + + +PACKAGE_DIR = Path(__file__).parent.parent + + +def test_skill_public_id() -> None: + """Test skill module public ID""" + + assert PUBLIC_ID.name == Path(__file__).parents[1].name + assert PUBLIC_ID.author == Path(__file__).parents[3].name + + +class ResetPauseAbciFSMBehaviourBaseCase(FSMBehaviourBaseCase): + """Base case for testing PauseReset FSMBehaviour.""" + + path_to_skill = PACKAGE_DIR + + +def dummy_reset_tendermint_with_wait_wrapper( + reset_successfully: Optional[bool], +) -> Callable[[], Generator[None, None, Optional[bool]]]: + """Wrapper for a Dummy `reset_tendermint_with_wait` method.""" + + def dummy_reset_tendermint_with_wait() -> Generator[None, None, Optional[bool]]: + """Dummy `reset_tendermint_with_wait` method.""" + yield + return reset_successfully + + return dummy_reset_tendermint_with_wait + + +class TestResetAndPauseBehaviour(ResetPauseAbciFSMBehaviourBaseCase): + """Test ResetBehaviour.""" + + behaviour_class = ResetAndPauseBehaviour + next_behaviour_class = make_degenerate_behaviour(FinishedResetAndPauseRound) + + @pytest.mark.parametrize("tendermint_reset_status", (None, True, False)) + def test_reset_behaviour( + self, + tendermint_reset_status: Optional[bool], + ) -> None: + """Test reset behaviour.""" + dummy_participants = [[i for i in range(4)]] + + self.fast_forward_to_behaviour( + behaviour=self.behaviour, + behaviour_id=self.behaviour_class.auto_behaviour_id(), + synchronized_data=ResetSynchronizedSata( + AbciAppDB( + setup_data=dict( + all_participants=dummy_participants, + participants=dummy_participants, + safe_contract_address=[""], + consensus_threshold=[3], + most_voted_estimate=[0.1], + tx_hashes_history=[["68656c6c6f776f726c64"]], + ), + ) + ), + ) + + assert self.behaviour.current_behaviour is not None + assert ( + self.behaviour.current_behaviour.behaviour_id + == self.behaviour_class.auto_behaviour_id() + ) + + with mock.patch.object( + self.behaviour.current_behaviour, + "send_a2a_transaction", + side_effect=self.behaviour.current_behaviour.send_a2a_transaction, + ), mock.patch.object( + self.behaviour.current_behaviour, + "reset_tendermint_with_wait", + side_effect=dummy_reset_tendermint_with_wait_wrapper( + tendermint_reset_status + ), + ) as mock_reset_tendermint_with_wait, mock.patch.object( + self.behaviour.current_behaviour, + "wait_from_last_timestamp", + side_effect=lambda _: (yield), + ): + if tendermint_reset_status is not None: + # Increase the period_count to force the call to reset_tendermint_with_wait() + self.behaviour.current_behaviour.synchronized_data.create() + + self.behaviour.act_wrapper() + self.behaviour.act_wrapper() + + # now if the first reset attempt has been simulated to fail, let's simulate the second attempt to succeed. + if tendermint_reset_status is not None and not tendermint_reset_status: + mock_reset_tendermint_with_wait.side_effect = ( + dummy_reset_tendermint_with_wait_wrapper(True) + ) + self.behaviour.act_wrapper() + # make sure that the behaviour does not send any txs to other agents when Tendermint reset fails + assert isinstance( + self.behaviour.current_behaviour.send_a2a_transaction, MagicMock + ) + self.behaviour.current_behaviour.send_a2a_transaction.assert_not_called() + self.behaviour.act_wrapper() + + self.mock_a2a_transaction() + self._test_done_flag_set() + self.end_round(ResetEvent.DONE) + assert ( + self.behaviour.current_behaviour.behaviour_id + == self.next_behaviour_class.auto_behaviour_id() + ) diff --git a/packages/valory/skills/reset_pause_abci/tests/test_dialogues.py b/packages/valory/skills/reset_pause_abci/tests/test_dialogues.py new file mode 100644 index 00000000..d9b2904c --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/tests/test_dialogues.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2022 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test the dialogues.py module of the skill.""" + +# pylint: skip-file + +import packages.valory.skills.reset_pause_abci.dialogues # noqa + + +def test_import() -> None: + """Test that the 'dialogues.py' Python module can be imported.""" diff --git a/packages/valory/skills/reset_pause_abci/tests/test_handlers.py b/packages/valory/skills/reset_pause_abci/tests/test_handlers.py new file mode 100644 index 00000000..347f9e9f --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/tests/test_handlers.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2022 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test the dialogues.py module of the skill.""" + +# pylint: skip-file + +import packages.valory.skills.reset_pause_abci.handlers # noqa + + +def test_import() -> None: + """Test that the 'handlers.py' Python module can be imported.""" diff --git a/packages/valory/skills/reset_pause_abci/tests/test_payloads.py b/packages/valory/skills/reset_pause_abci/tests/test_payloads.py new file mode 100644 index 00000000..15b67fae --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/tests/test_payloads.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test the payloads.py module of the skill.""" + +# pylint: skip-file + +from packages.valory.skills.reset_pause_abci.payloads import ResetPausePayload + + +def test_reset_pause_payload() -> None: + """Test `ResetPausePayload`.""" + + payload = ResetPausePayload(sender="sender", period_count=1) + + assert payload.period_count == 1 + assert payload.data == {"period_count": 1} + assert ResetPausePayload.from_json(payload.json) == payload diff --git a/packages/valory/skills/reset_pause_abci/tests/test_rounds.py b/packages/valory/skills/reset_pause_abci/tests/test_rounds.py new file mode 100644 index 00000000..adadb778 --- /dev/null +++ b/packages/valory/skills/reset_pause_abci/tests/test_rounds.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2021-2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test the rounds of the skill.""" + +# pylint: skip-file + +import hashlib +import logging # noqa: F401 +from typing import Dict, FrozenSet +from unittest.mock import MagicMock + +from packages.valory.skills.abstract_round_abci.base import ( + BaseSynchronizedData as ResetSynchronizedSata, +) +from packages.valory.skills.abstract_round_abci.test_tools.rounds import ( + BaseCollectSameUntilThresholdRoundTest, +) +from packages.valory.skills.reset_pause_abci.payloads import ResetPausePayload +from packages.valory.skills.reset_pause_abci.rounds import Event as ResetEvent +from packages.valory.skills.reset_pause_abci.rounds import ResetAndPauseRound + + +MAX_PARTICIPANTS: int = 4 +DUMMY_RANDOMNESS = hashlib.sha256("hash".encode() + str(0).encode()).hexdigest() + + +def get_participant_to_period_count( + participants: FrozenSet[str], period_count: int +) -> Dict[str, ResetPausePayload]: + """participant_to_selection""" + return { + participant: ResetPausePayload(sender=participant, period_count=period_count) + for participant in participants + } + + +class TestResetAndPauseRound(BaseCollectSameUntilThresholdRoundTest): + """Test ResetRound.""" + + _synchronized_data_class = ResetSynchronizedSata + _event_class = ResetEvent + + def test_runs( + self, + ) -> None: + """Runs tests.""" + + synchronized_data = self.synchronized_data.update( + most_voted_randomness=DUMMY_RANDOMNESS, consensus_threshold=3 + ) + synchronized_data._db._cross_period_persisted_keys = frozenset( + {"most_voted_randomness"} + ) + test_round = ResetAndPauseRound( + synchronized_data=synchronized_data, + context=MagicMock(), + ) + next_period_count = 1 + self._complete_run( + self._test_round( + test_round=test_round, + round_payloads=get_participant_to_period_count( + self.participants, next_period_count + ), + synchronized_data_update_fn=lambda _synchronized_data, _: _synchronized_data.create(), + synchronized_data_attr_checks=[], # [lambda _synchronized_data: _synchronized_data.participants], + most_voted_payload=next_period_count, + exit_event=self._event_class.DONE, + ) + ) + + def test_accepting_payloads_from(self) -> None: + """Test accepting payloads from""" + + alice, *others = self.participants + participants = list(others) + all_participants = participants + [alice] + + synchronized_data = self.synchronized_data.update( + participants=participants, all_participants=all_participants + ) + + test_round = ResetAndPauseRound( + synchronized_data=synchronized_data, + context=MagicMock(), + ) + + assert test_round.accepting_payloads_from != participants + assert test_round.accepting_payloads_from == frozenset(all_participants)