# Using the Oracle Data

In order to use the data provided by the Oracle module, we must make sure all validators come to a consensus about what is commeted to the blockchain state.

While the Oracle module provides some helper methods, the app developer is ultimately responsible for doing this.

# Reaching Consensus

Plan of action:

  • In our app's EndBlock, query the pending oracle rounds
  • If we have enough votes to reach consensus, compute the average result of all claims
  • Write the result on-chain.

First we need to make sure we add our app to the EndBlock handler. Add your app to SetOrderEndBlockers:

Copy app.mm.SetOrderEndBlockers(crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, <your_app_types>.ModuleName)

We will also need to import the Oracle keeper into our app's keeper.

add types/expected_keepers.go

Copy package types import ( sdk "github.com/cosmos/cosmos-sdk/types" oracle "github.com/relevant-community/oracle/x/oracle/exported" oracletypes "github.com/relevant-community/oracle/x/oracle/types" tmbytes "github.com/tendermint/tendermint/libs/bytes" ) type ( // OracleKeeper interface OracleKeeper interface { FinalizeRound(ctx sdk.Context, claimType string, roundID uint64) GetPendingRounds(ctx sdk.Context, roundType string) (rounds []uint64) TallyVotes(ctx sdk.Context, claimType string, roundID uint64) *oracletypes.RoundResult GetClaim(ctx sdk.Context, hash tmbytes.HexBytes) oracle.Claim } )

your keeper/keeper.go file should look like this:

Copy type ( Keeper struct { cdc codec.Marshaler storeKey sdk.StoreKey memKey sdk.StoreKey oracleKeeper types.OracleKeeper } ) func NewKeeper(cdc codec.Marshaler, storeKey, memKey sdk.StoreKey, oracleKeeper types.OracleKeeper) *Keeper { return &Keeper{ cdc: cdc, storeKey: storeKey, memKey: memKey, oracleKeeper: oracleKeeper, } }

Finallly, make sure to update your app's keeper constructor inside app.go file if needed.

# Tally the Votes

Copy func (k Keeper) UpdateAtomUsd(ctx sdk.Context) { claimType := types.AtomClaim pending := k.oracleKeeper.GetPendingRounds(ctx, claimType) // sort pending rounds in ascending order sort.SliceStable(pending, func(i, j int) bool { return pending[i] < pending[j] }) for _, roundID := range pending { result := k.oracleKeeper.TallyVotes(ctx, claimType, roundID) if result == nil || result.Claims[0] == nil { continue } // take an average of all claims and commit to chain avgAtomUsd := sdk.NewDec(0) var blockHeight int64 var totalVotePower int64 for _, claimResult := range result.Claims { claimHash := claimResult.ClaimHash atomClaim, ok := k.oracleKeeper.GetClaim(ctx, claimHash).(*types.AtomUsd) if ok == false { fmt.Printf("Error retrieving claim") continue } weightedAvg := avgAtomUsd.Mul(sdk.NewDec(totalVotePower)) weightedVote := atomClaim.Price.Mul(sdk.NewDec(result.VotePower)) totalVotePower += result.VotePower avgAtomUsd = weightedAvg.Add(weightedVote).Quo(sdk.NewDec(totalVotePower)) blockHeight = atomClaim.BlockHeight } atomUsd := &types.AtomUsd{ Price: avgAtomUsd, BlockHeight: blockHeight, } k.SetAtomUsd(ctx, *atomUsd) // TODO delete the any earlier pending rounds k.oracleKeeper.FinalizeRound(ctx, claimType, roundID) } }

Here is what happens in the code above:

  • We get all 'PendingRounds' from the oracleKeeper and sort them in ascending order so we can process the early claims first
  • For each pending round, we tally the votes using the oracleKeeper's TallyVotes method. If consensus threshold we specified in the params is reached, TallyVotes returns a RoundResult struct.
Copy type ClaimVoteResult struct { ClaimHash tmbytes.HexBytes VotePower int64 } // RoundResult is is a record of vote tallies for a given round type RoundResult struct { VotePower int64 TotalPower int64 ClaimType string Claims []*ClaimVoteResult }
  • We iterate through the round result's Claims field, fetch the Claim and weigh the price by the validators power. This gives us an average submission price weighted by validator power.
  • Write the final result on-chain along with the Claim's BlockHeight
  • Note in reality you may want to use weighted median value rather than a weighted average.

# Run UpdateAtomUsd in modules's EndBlock

Update your module's EndBlock method in module.go

Copy func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { am.keeper.UpdateAtomUsd(ctx) return []abci.ValidatorUpdate{} }

# Query the AtomUsd Price

As a final step we add the querier and cli methods to fetch the on-chain price. You consult the starport examples on how to do this, or simply view the final result here: https://github.com/relevant-community/oracle/tree/main/x/atom