Price Oracle - Technical Details
Agent instances communicate directly to their local Tendermint node, whereas the AbciApp
is used to handle requests they receive (e.g., in response to their behaviour).
In addition to other general-use components (e.g., signing, HTTP client), the components of the AEAs related to the implementation of the agent blueprint are:
- Protocol
valory/abci:0.1.0: it allows representing ABCI request and response messages. - Connection
valory/abci:0.1.0: it accepts ABCI requests from a consensus engine module, e.g. the Tendermint node; - Skill
valory/abstract_abci:0.1.0: it provides a scaffold handler for ABCI requests. It is an abstract skill. - Skill
valory/abstract_round_abci:0.1.0: it implements an ABCI handler and provides useful code abstractions for creating round-based replicated state machines, based on the ABCI protocol (e.g.Period,AbstractRound). It is an abstract skill.
Moreover, it has the following specific components to implement the particular case of the price oracle agent blueprint:
valory/price_estimation_abci: it implements the round-based ABCI application for price estimation of a cryptocurrency, with a finalization step over an Ethereum chain.
The constituent FSM Apps
The ABCI-based replicated FSM (AbciApp) used for price estimation consists
of several smaller modules, each of which is an AbciApp of its own. Each of
them is a Skill, which implies that they operate independently of each other,
however they can be combined to create a larger AbciApp, provided that the
developer specifies the required transition mapping to connect these FSMs.
This modularity allows a developer to use a subset of these skills in different
contexts, potentially in combination with skills they themselves define, to
create another composite AbciApp that performs according to their particular
needs. Specifically, the PriceEstimationAbciApp is created by combining the
following parts:
AgentRegistrationAbciAppSafeDeploymentAbciAppOracleDeploymentAbciAppPriceAggregationAbciAppTransactionSubmissionAbciAppResetPauseAbciApp
The AgentRegistrationAbciApp
This AbciApp implements the registration of agent instances to partake in the behaviour
scheduled in subsequent rounds.
-
RegistrationStartupRound -
RegistrationRound
In this round registrations from AEAs to join the period are accepted, up to a configured maximum number of participants (in the demo, this limit is 4); once this threshold is hit ("registration threshold"), the round is finished. -
FinishedRegistrationRound
A round that signals agent instance registration was successful, but AI agent contracts have not been already deployed. -
FinishedRegistrationFFWRound
A round that signals agent instance registration was successful, and the AI agent contracts are already deployed.
The SafeDeploymentAbciApp
This AbciApp implements the deployments of a Gnosis safe contract, which is
a multisig smart contract wallet that requires a minimum number of people to
approve a transaction before it can occur. This assures that no single agent instance
can compromise the funds contained in it.
-
RandomnessSafeRound
Some randomness is retrieved to be used in a keeper agent instance selection. In particular, agent instances individually request the latest random number from DRAND, establish consensus on it and then use it as a seed for computations requiring randomness (e.g. keeper selection). -
SelectKeeperSafeRound
The agent instances agree on a new keeper that will be in charge of sending deploying the multisig wallet and settling transactions. -
DeploySafeRound
A designated sender among the participants of the current period deploys a Gnosis Safe contract with all the participants as owners and withceil((2n + 1) / 3)as threshold. If the safe deployment has not been completed after some time, a new keeper will be selected and the safe deployment will be re-run. -
ValidateSafeRound
All agent instances validate the previous deployment to ensure that the correct contract with the correct settings has been deployed. If the safe deployment could not be verified, the process will start again from the registration round. -
FinishedSafeRound
A round that signals the safe contract was deployed successfully.
The OracleDeploymentAbciApp
This AbciApp implements the deployments of an Oracle contract, a Gnosis safe
multisig smart contract wallet that was forked from Chainlink
and subsequently stripped from unnecessary data structures and behaviours.
-
RandomnessOracleRound
Similar as to theRandomnessSafeRound, randomness is retrieved here, this time for the selection of an agent instance to become the oracle keeper. -
SelectKeeperOracleRound
The agent instances select a new keeper that will be in charge of sending deploying the multisig wallet and settling transactions. -
DeployOracleRound
The designated keeper deploys a Gnosis safe contract. If a timeout occurs before oracle deployment was completed, a new keeper will be selected and the oracle deployment will be re-run. -
ValidateOracleRound
all agent instances verify that the Oracle contract has been deployed using the expected settings. If that's not the case, agent instances will restart the period. -
FinishedOracleRoundA round that signals the oracle contract was deployed successfully.
The PriceAggregationAbciApp
This AbciApp implements off-chain aggregation of observations by the agent instances.
Once the majority of agent instances has submitted their observation these are shared
with all agent instances as they move to the price estimation round. In this next round
each of the agent instances performs off-chain a computation on the data set, which could
be a simple summary statistic or an estimate derived from a complex model -
either way this is something that cannot be done on-chain. Once consensus is
reached on this estimate, the aggregate value is submitted and recorded
on-chain in the next block that is mined.
-
CollectObservationRound
Observational data is collected by the AEAs on the target quantity to estimate. Once the agent instances reach consensus over this data, that is to say at least 2/3rd of them agree on the set of single observations collected by agent instances (not on individual observations), the shared state gets updated and the agent instances enter the next round. -
EstimateConsensusRound
Based on the collected data the actual price of the asset is estimated, which could be a simple summary statistic such as the median or the geometric mean. Once the same estimate receives a number of votes greater or equal than 2/3 of the total voting power, consensus is reached and the period moves forward. -
TxHashRound
A designated sender composes the transaction and puts it on the temporary Tendermint-based chain. Signing of the transaction for the multisig smart contract requires consensus among the agent instances on the transaction hash to use. -
FinishedPriceAggregationRound
A round that signals price aggregation was completed successfully.
The TransactionSubmissionAbciApp
-
RandomnessTransactionSubmissionRound
Randomness is retrieved for keeper selection. -
SelectKeeperTransactionSubmissionARound
The agent instances select a keeper that will be in charge of sending the transaction. -
CollectSignatureRound
Agent instances sign the transaction to be submitted. -
FinalizationRound
The keeper sends off the transaction to be incorporated in the next block. A transaction hash is returned. This round takes care of submitting with the right amount of gas, i.e., if resubmitting, then the "tip" for the miners is also increased by 10%. -
ValidateTransactionRound
Agent instances validate whether the transaction has been incorporated in the blockchain. -
CheckTransactionHistoryRound
This round is triggered if theValidateTransactionRoundreturns with aNEGATIVEEvent, which means that the transaction has not been validated. During the round, the agent instances check the transaction history up to this point again in order to specify the cause of the problem, e.g., a transaction of a keeper was settled before another keeper managed to do so, a payload is invalid, etc. -
SelectKeeperTransactionSubmissionBRound
The agent instances select a keeper that will be in charge of sending the transaction, in case that the first keeper has failed. -
ResetRound
In case that a failure such as round timeout or no majority reached, the period is reset. -
ResetAndPauseRound
A round that updates theSynchronizedDatato allocate a new period before going to theFinishedTransactionSubmissionRound. -
FinishedTransactionSubmissionRound
A round that signals transaction submission was completed successfully. -
FailedRound
A round that signals transaction submission has failed. Occurs if theCheckTransactionHistoryRounddoes not manage to find a validated transaction in the history, or if a Reset round fails.
Implementation of the PriceEstimationAbciApp
In the final implementation the PriceEstimationAbciApp is then assembled from
its constituent parts. However, in order to combine the various FSMs previously
discussed, a transition mapping between states of these FSMs also needs to be
provided. In order to combine the different FSMs we need to connect them by
providing the necessary transition mapping. As per the code implemented in the
demo, the implementation looks as follows:
abci_app_transition_mapping: AbciAppTransitionMapping = {
FinishedRegistrationRound: RandomnessSafeRound,
FinishedSafeRound: RandomnessOracleRound,
FinishedOracleRound: CollectObservationRound,
FinishedRegistrationFFWRound: CollectObservationRound,
FinishedPriceAggregationRound: RandomnessTransactionSubmissionRound,
FailedRound: ResetAndPauseRound,
FinishedTransactionSubmissionRound: ResetAndPauseRound,
FinishedResetAndPauseRound: CollectObservationRound,
FinishedResetAndPauseErrorRound: RegistrationRound,
}
OracleAbciApp = chain(
(
AgentRegistrationAbciApp,
SafeDeploymentAbciApp,
OracleDeploymentAbciApp,
PriceAggregationAbciApp,
TransactionSubmissionAbciApp,
ResetPauseAbciApp,
),
abci_app_transition_mapping,
)
Find below a graphical representation of this composition showing how the constituent FSMs interconnect to achieve the functionality of the agent blueprint in the demo.
The AbstractRoundBehaviour schedules the state behaviour associated with the current round, ensuring that a transition to the new state cannot occur without first invoking the associated state Behaviour. Since it is composed of the Behaviours belonging to the constituent FSMs, we can reference them as depicted below.
class OracleAbciAppConsensusBehaviour(AbstractRoundBehaviour):
"""This behaviour manages the consensus stages for the price estimation."""
initial_behaviour_cls = RegistrationStartupBehaviour
abci_app_cls = OracleAbciApp # type: ignore
behaviours: Set[Type[BaseBehaviour]] = {
*OracleDeploymentRoundBehaviour.behaviours,
*AgentRegistrationRoundBehaviour.behaviours,
*SafeDeploymentRoundBehaviour.behaviours,
*TransactionSettlementRoundBehaviour.behaviours,
*ResetPauseABCIConsensusBehaviour.behaviours,
*ObserverRoundBehaviour.behaviours,
}
Have a look at the FSM diagram of the application in order to see what the encoded state transitions in the final composite FSM look like.
Warning
A sequence diagram that shows how AEAs communicate with their environment throughout the execution can be found here. However, it is not fully up-to-date with the implementation discussed here.
Known limitations
The TransactionSettlementSkill has a known limitation that concerns the revert reason lookup.
While checking the history in CheckTransactionHistoryRound, an exception may get raised:
ValueError: The given transaction has not been reverted!
This error arises because of the way that we check for the revert reason; We currently
replay
the tx locally. However, there is an important limitation with this method. The replayed transaction will be
executed in isolation. This means that transactions which occurred prior to the replayed transaction within
the same block will not be accounted for! Therefore, the replay will not raise a SolidityError in such case,
because both the transactions happened in the same block.
The exception is handled automatically and logged as an error, so it does not affect the execution. However, as a side effect, we may end the round with a failure status even though the transaction has settled, because we have not managed to detect it.