Oracle Module
The Oracle Module
Below is a description of how the oracle module sources information from Ojo's nodes, and ensures that they are all reporting correct prices.
The Oracle Module was audited by Runtime Verification.
Voting Procedure
During each VotePeriod
, the Oracle module obtains consensus on the exchange rate of multiple denominations against USD specified in AcceptList
by requiring all members of the validator set to submit a vote for exchange rates before the end of the interval.
Validators must first pre-commit to a set of exchange rates, then in the subsequent VotePeriod
submit and reveal their exchange rates alongside a proof that they had pre-commited at those prices. This scheme forces the voter to commit to a submission before knowing the votes of others and thereby reduces centralization and free-rider risk in the Oracle.
Prevote and Vote
Let
P_t
be the current time interval of duration defined byVotePeriod
during which validators must submit two messages:A
MsgAggregateExchangeRatePrevote
, containing the SHA256 hash of the exchange rates of multiple denominations. A prevote must be submitted for all different denominations specified inAcceptList
.A
MsgAggregateExchangeRateVote
, containing the salt used to create the hash for the aggregate prevote submitted in the previous intervalP_t-1
.
Vote Tally
At the end of
P_t
, the submitted votes are tallied.The submitted salt of each vote is used to verify consistency with the prevote submitted by the validator in
P_t-1
. If the validator has not submitted a prevote, or the SHA256 resulting from the salt does not match the hash from the prevote, the vote is dropped.For each exchange rate the median of the votes is recorded on-chain as the effective rate for that denomination against USD for the following
VotePeriod
P_t+1
.Exchange rates receiving fewer than
VoteThreshold
total voting power have their exchange rates deleted from the store.Ballot Rewards
After the votes are tallied, the winners of the ballots are determined with
tally()
.Voters that have managed to vote within a narrow band around the median are rewarded with a portion of the collected seigniorage.
The set of validators that can earn rewards is updated every
SlashWindow
by taking all the active validators at the start of aSlashWindow
and updating theValidatorRewardSet
. Any validators that join in the middle of aSlashWindow
will not be able to earn ballot rewards until the nextSlashWindow
.The reward portion is determined by the MissCounter amount for the voted on exchange rates accrued by each validator in the
SlashWindow
, where the validator with smallest MissCounter collects the most reward and the rest are rewarded logarithimically favoring fewer miss counts. Seek.RewardBallotWinners()
for more details.
Reward Band
Each asset has a unique RewardBand when it's being added to the Oracle Parameters. For some assets this needs to be smaller or larger in order to account for expected price flux / stability.
Let M
be the median, 𝜎
be the standard deviation of the votes in the ballot, and R
be the RewardBand for a given asset. The band around the median is set to be 𝜀 = max(𝜎, R/2)
. All valid (i.e. bonded and non-jailed) validators that submitted an exchange rate vote in the interval [M - 𝜀, M + 𝜀]
should be included in the set of winners.
Reward Pool
The Oracle module's reward pool is composed of any tokens present in its module account. If there are no tokens present in the Oracle module reward pool during a reward period, no tokens are distributed for that period.
This reward pool will be filled initially by token inflation, then in a future upgrade will be funded by payment for the Ojo Validator services.
The reward pool is not distributed all at once, but instead over a period of time, determined by the param RewardDistributionWindow
, currently set to 5256000
.
Slashing
Be sure to read this section carefully as it concerns potential loss of funds.
A VotePeriod
during which either of the following events occur is considered a "miss":
The validator fails to submits a vote for each and every exchange rate specified in
AcceptList
.The validator fails to vote within the
reward band
around the median for one or more denominations.
During every SlashWindow
, participating validators must maintain a valid vote rate of at least MinValidPerWindow
(5%), lest they get their stake slashed (currently set to 0.01%). The slashed validator is automatically temporarily "jailed" by the protocol (to protect the funds of delegators), and the operator is expected to fix the discrepancy promptly to resume validator participation.
Abstaining from Voting
In Terra's implementation, validators have the option of abstaining from voting. To quote Terra's documentation :
A validator may abstain from voting by submitting a non-positive integer for the
ExchangeRate
field inMsgExchangeRateVote
. Doing so will absolve them of any penalties for missingVotePeriod
s, but also disqualify them from receiving Oracle seigniorage rewards for faithful reporting.
In order to ensure that we have the most accurate exchange rates, we have removed this feature. Non-positive exchange rates in MsgAggregateExchangeRateVote
are instead dropped.
The control flow for vote-tallying, exchange rate updates, ballot rewards and slashing happens at the end of every VotePeriod
, and is found at the end-block ABCI function rather than inside message handlers.
State
ExchangeRate
An math.LegacyDec
that stores an exchange rate against USD.
ExchangeRate:
0x01 | byte(denom) -> math.LegacyDec
FeederDelegation
An sdk.AccAddress
(ojo-
account) address for operator
price feeder rewards.
FeederDelegation:
0x02 | byte(valAddress length) | byte(valAddress) -> sdk.AccAddress
MissCounter
An int64
representing the number of VotePeriods
that validator operator
missed during the current SlashWindow
.
MissCounter:
0x03 | byte(valAddress length) | byte(valAddress) -> ProtocolBuffer(uint64)
AggregateExchangeRatePrevote
AggregateExchangeRatePrevote
containing a validator's aggregated prevote for all denoms for the current VotePeriod
.
AggregateExchangeRatePrevote:
0x04 | byte(valAddress length) | byte(valAddress) -> ProtocolBuffer(AggregateExchangeRatePrevote)
AggregateExchangeRateVote
AggregateExchangeRateVote
containing a validator's aggregate vote for all denoms for the current VotePeriod
.
AggregateExchangeRateVote:
0x05 | byte(valAddress length) | byte(valAddress) -> ProtocolBuffer(AggregateExchangeRateVote)
End Block
Tally Exchange Rate Votes
At the end of every block, the Oracle
module checks whether it's the last block of the VotePeriod
. If it is, it runs the Voting Procedure:
All current active exchange rates are purged from the store
Received votes are organized into ballots by denomination. Votes by inactive or jailed validators are ignored.
Exchange rates not meeting the following requirements will be dropped:
Must appear in the permitted denominations in
AcceptList
Ballot for rate must have at least
VoteThreshold
total vote power
For each remaining
denom
with a passing ballot:Tally up votes and find the median exchange rate and winners with
tally()
Iterate through winners of the ballot and add their weight to their running total
Set the exchange rate on the blockchain for that
denom
withk.SetExchangeRate()
Emit an
exchange_rate_update
event
Count up the validators who missed the Oracle vote and increase the appropriate miss counters
If at the end of a
SlashWindow
, penalize validators who have missed more than the penalty threshold (submitted fewer valid votes thanMinValidPerWindow
)Distribute rewards to ballot winners with
k.RewardBallotWinners()
Clear all prevotes (except ones for the next
VotePeriod
) and votes from the store
Messages
See oracle tx proto for list of supported messages.
Events
See oracle events proto for list of supported events.
Params
See oracle events proto for list of module parameters.
Last updated