├── config.toml ├── vue ├── .env ├── .browserslistrc ├── public │ ├── favicon.ico │ └── index.html ├── vue.config.js ├── babel.config.js ├── src │ ├── store │ │ └── index.js │ ├── main.js │ ├── App.vue │ ├── router │ │ └── index.js │ └── views │ │ └── Index.vue ├── .gitignore ├── Dockerfile ├── README.md └── package.json ├── x ├── atom │ ├── types │ │ ├── query.go │ │ ├── types.go │ │ ├── errors.go │ │ ├── codec.go │ │ ├── genesis.go │ │ ├── expected_keepers.go │ │ ├── keys.go │ │ ├── atomUsd.go │ │ └── query.pb.gw.go │ ├── client │ │ ├── rest │ │ │ └── rest.go │ │ └── cli │ │ │ ├── tx.go │ │ │ └── query.go │ ├── handler.go │ ├── genesis.go │ ├── keeper │ │ ├── grpc_query.go │ │ ├── query.go │ │ ├── keeper.go │ │ └── atom.go │ └── module.go └── oracle │ ├── types │ ├── types.go │ ├── events.go │ ├── errors.go │ ├── Vote.go │ ├── query.go │ ├── expected_keepers.go │ ├── RoundResult.go │ ├── MsgVote_test.go │ ├── MsgPrevote.go │ ├── TestClaim.go │ ├── MsgDelegate.go │ ├── codec.go │ ├── keys.go │ ├── params.go │ ├── MsgVote.go │ ├── genesis.go │ └── genesis_test.go │ ├── atlas │ └── config.toml │ ├── keeper │ ├── claim_test.go │ ├── params.go │ ├── keeper.go │ ├── msg_server_test.go │ ├── keeper_test.go │ ├── delegation.go │ ├── claim.go │ ├── vote_test.go │ ├── grpc_query_test.go │ ├── round.go │ ├── vote.go │ └── msg_server.go │ ├── handler.go │ ├── spec │ ├── 04_events.md │ ├── 05_params.md │ ├── README.md │ ├── 03_messages.md │ ├── 02_state.md │ └── 01_concepts.md │ ├── exported │ └── claim.go │ ├── client │ ├── cli │ │ ├── tx.go │ │ ├── queryClaim.go │ │ ├── query_test.go │ │ ├── delegation_test.go │ │ ├── workerHelper.go │ │ └── worker.go │ └── rest │ │ └── query_grpc_test.go │ ├── testoracle │ └── helper.go │ ├── genesis.go │ └── module.go ├── docs ├── post.sh ├── .vuepress │ ├── styles │ │ └── index.styl │ ├── public │ │ ├── logo.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-256x256.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── browserconfig.xml │ │ ├── site.webmanifest │ │ ├── safari-pinned-tab.svg │ │ ├── favicon-svg.svg │ │ └── relevantlogo.svg │ └── config.js ├── pre.sh ├── package.json └── README.md ├── Makefile ├── scripts ├── intiChain.sh ├── setparams.js └── init.sh ├── .gitignore ├── .pi ├── Dockerfile └── build.sh ├── cmd └── oracled │ ├── main.go │ └── cmd │ └── worker │ └── handlers.go ├── proto ├── atom │ ├── genesis.proto │ ├── atom.proto │ └── query.proto └── oracle │ ├── params.proto │ ├── oracle.proto │ ├── genesis.proto │ ├── tx.proto │ └── query.proto ├── app ├── params │ ├── encoding.go │ └── proto.go ├── encoding.go ├── prefix.go ├── genesis.go ├── types.go └── export.go ├── Dockerfile ├── config.yml ├── go.mod ├── .github └── workflows │ ├── build.yml │ ├── pages.yml │ ├── docker.yml │ └── pi.yml ├── docker-compose.yaml └── readme.md /config.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_ADDRESS_PREFIX=cosmos -------------------------------------------------------------------------------- /x/atom/types/query.go: -------------------------------------------------------------------------------- 1 | package types 2 | -------------------------------------------------------------------------------- /x/atom/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | -------------------------------------------------------------------------------- /x/oracle/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | -------------------------------------------------------------------------------- /docs/post.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf modules 4 | -------------------------------------------------------------------------------- /vue/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | :root 2 | --color-primary #5064fb 3 | --color-link #5064fb -------------------------------------------------------------------------------- /vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/vue/public/favicon.ico -------------------------------------------------------------------------------- /vue/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | disableHostCheck: true, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/logo.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for the "oraclenode" docker image. 2 | 3 | all: 4 | docker build --tag relevant/oracle oraclenode 5 | 6 | .PHONY: all -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/favicon-16x16.png -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/.vuepress/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/mstile-150x150.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/.vuepress/public/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/android-chrome-256x256.png -------------------------------------------------------------------------------- /docs/.vuepress/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevant-community/oracle/HEAD/docs/.vuepress/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /scripts/intiChain.sh: -------------------------------------------------------------------------------- 1 | node ./scripts/setparams.js 2 | 3 | echo "Collecting genesis txs..." 4 | oracled collect-gentxs 5 | 6 | echo "Validating genesis file..." 7 | oracled validate-genesis -------------------------------------------------------------------------------- /vue/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import cosmos from "@tendermint/vue/src/store/cosmos.js"; 4 | 5 | Vue.use(Vuex); 6 | 7 | export default new Vuex.Store({ 8 | modules: { cosmos }, 9 | }); 10 | -------------------------------------------------------------------------------- /x/atom/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | ) 8 | 9 | // x/atom module sentinel errors 10 | var ( 11 | ErrSample = sdkerrors.Register(ModuleName, 1100, "sample error") 12 | ) 13 | -------------------------------------------------------------------------------- /vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | 6 | Vue.config.productionTip = false; 7 | 8 | new Vue({ 9 | router, 10 | store, 11 | render: (h) => h(App), 12 | }).$mount("#app"); 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vue/node_modules 2 | vue/dist 3 | secret.yml 4 | 5 | .idea 6 | .vscode 7 | .DS_Store 8 | 9 | # Build 10 | vendor 11 | build 12 | docs/_build 13 | docs/tutorial 14 | docs/node_modules 15 | docs/modules 16 | dist 17 | tools-stamp 18 | buf-stamp 19 | artifacts 20 | 21 | coverage.out 22 | -------------------------------------------------------------------------------- /docs/.vuepress/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.pi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM faddat/sos-lite 2 | 3 | # Add seeds and such after doing gaia. 4 | # I will prototype the rest of this on gaia. 5 | RUN echo oracle > /etc/hostname && \ 6 | pacman -Syyu --noconfirm docker-compose zerotier-one 7 | # echo "docker run relevant-community/oracle" >> /usr/local/bin/firstboot.sh 8 | # TODO: docker-compose 9 | -------------------------------------------------------------------------------- /vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /vue/Dockerfile: -------------------------------------------------------------------------------- 1 | # This image is not yet distroless because distroless images expect thatthe entrypoint will be a .js file and ours is vue-cli-service 2 | # We use lopsided/archlinux for comptability 3 | 4 | FROM lopsided/archlinux 5 | COPY . . 6 | 7 | RUN pacman -Syyu --noconfirm npm 8 | RUN npm install 9 | RUN npm run build 10 | 11 | EXPOSE 8080 12 | CMD ["/usr/bin/npm","run","serve"] -------------------------------------------------------------------------------- /cmd/oracled/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 7 | "github.com/relevant-community/oracle/app" 8 | "github.com/relevant-community/oracle/cmd/oracled/cmd" 9 | ) 10 | 11 | func main() { 12 | rootCmd, _ := cmd.NewRootCmd() 13 | if err := svrcmd.Execute(rootCmd, app.DefaultNodeHome); err != nil { 14 | os.Exit(1) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vue/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import Index from "../views/Index.vue"; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: "/", 10 | component: Index, 11 | }, 12 | ]; 13 | 14 | const router = new VueRouter({ 15 | mode: "history", 16 | base: process.env.BASE_URL, 17 | routes, 18 | }); 19 | 20 | export default router; 21 | -------------------------------------------------------------------------------- /proto/atom/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relevantcommunity.oracle.atom; 3 | 4 | // this line is used by starport scaffolding # genesis/proto/import 5 | 6 | option go_package = "github.com/relevant-community/oracle/x/atom/types"; 7 | 8 | // GenesisState defines the atom module's genesis state. 9 | message GenesisState { 10 | // this line is used by starport scaffolding # genesis/proto/state 11 | } 12 | -------------------------------------------------------------------------------- /vue/src/views/Index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | -------------------------------------------------------------------------------- /x/oracle/types/events.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // claim module events 4 | const ( 5 | AttributeValueCategory = "oracle" 6 | AttributeKeyClaimHash = "claim_hash" 7 | 8 | EventTypeDelegate = "delegate" 9 | AttributeKeyDelegate = "delegate" 10 | AttributeKeyValidator = "validator" 11 | 12 | EventTypeVote = "vote" 13 | EventTypePrevote = "prevote" 14 | AttributeKeyPrevoteHash = "prevote_hash" 15 | ) 16 | -------------------------------------------------------------------------------- /proto/atom/atom.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "gogoproto/gogo.proto"; 4 | option go_package = "github.com/relevant-community/oracle/x/atom/types"; 5 | 6 | message AtomUsd { 7 | string price = 1 [ 8 | (gogoproto.jsontag) = "price", 9 | (gogoproto.moretags) = "yaml:\"price\"", 10 | (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", 11 | (gogoproto.nullable) = false 12 | ]; 13 | int64 blockHeight = 2; 14 | } -------------------------------------------------------------------------------- /scripts/setparams.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = '/Users/slavab/.oracle/config/genesis.json' 3 | const genesis = require(path) 4 | genesis.app_state.oracle.params.claim_params.AtomClaim = { 5 | claim_type: 'AtomClaim', 6 | prevote: true, 7 | vote_period: 3, 8 | vote_threshold: '0.5', 9 | } 10 | fs.writeFile(path, JSON.stringify(genesis), (err) => { 11 | if (err) { 12 | console.error(err) 13 | return 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm -rf ~/.oracle 3 | 4 | oracled init test --chain-id=oracle 5 | 6 | name=$1 7 | token=$2 8 | stake=$3 9 | 10 | # oracled config output json 11 | # oracled config indent true 12 | # oracled config trust-node true 13 | # oracled config chain-id oracle 14 | # oracled config keyring-backend test 15 | 16 | oracled keys add $name 17 | oracled add-genesis-account $(oracled keys show "$name" -a) $token,$stake 18 | oracled gentx $name $stake --chain-id oracle 19 | 20 | -------------------------------------------------------------------------------- /docs/pre.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p modules 4 | 5 | for D in ../x/*; do 6 | echo $D 7 | echo $(echo $D | awk -F/ '{print $NF}') 8 | if [ -d "${D}" ]; then 9 | rm -rf "modules/$(echo $D | awk -F/ '{print $NF}')" 10 | mkdir -p "modules/$(echo $D | awk -F/ '{print $NF}')" && cp -r $D/spec/* "$_" 11 | fi 12 | done 13 | 14 | cat ../x/README.md | sed 's/\.\/x/\/modules/g' | sed 's/spec\/README.md//g' | sed 's/\.\.\/docs\/building-modules\/README\.md/\/building-modules\/intro\.html/g' > ./modules/README.md 15 | -------------------------------------------------------------------------------- /vue/README.md: -------------------------------------------------------------------------------- 1 | # oracle UI 2 | 3 | This is the [vuejs](https://vuejs.org/) user inteface to oracle. 4 | 5 | It provides UI components including login, and a template for you to do additonal development. 6 | 7 | ## Project setup 8 | 9 | ``` 10 | npm install 11 | ``` 12 | 13 | ### Compiles and hot-reloads for development 14 | 15 | ``` 16 | npm run serve 17 | ``` 18 | 19 | ### Compiles and minifies for production 20 | 21 | ``` 22 | npm run build 23 | ``` 24 | 25 | ### Customize configuration 26 | 27 | See [Configuration Reference](https://cli.vuejs.org/config/). 28 | -------------------------------------------------------------------------------- /app/params/encoding.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/client" 5 | "github.com/cosmos/cosmos-sdk/codec" 6 | "github.com/cosmos/cosmos-sdk/codec/types" 7 | ) 8 | 9 | // EncodingConfig specifies the concrete encoding types to use for a given app. 10 | // This is provided for compatibility between protobuf and amino implementations. 11 | type EncodingConfig struct { 12 | InterfaceRegistry types.InterfaceRegistry 13 | Marshaler codec.Marshaler 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /docs/.vuepress/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cosmos SDK Documentation", 3 | "short_name": "Cosmos SDK", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /app/encoding.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/std" 5 | "github.com/relevant-community/oracle/app/params" 6 | ) 7 | 8 | // MakeEncodingConfig creates an EncodingConfig for testing 9 | func MakeEncodingConfig() params.EncodingConfig { 10 | encodingConfig := params.MakeEncodingConfig() 11 | std.RegisterLegacyAminoCodec(encodingConfig.Amino) 12 | std.RegisterInterfaces(encodingConfig.InterfaceRegistry) 13 | ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) 14 | ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) 15 | return encodingConfig 16 | } 17 | -------------------------------------------------------------------------------- /x/atom/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 6 | exportedOracle "github.com/relevant-community/oracle/x/oracle/exported" 7 | ) 8 | 9 | func RegisterCodec(cdc *codec.LegacyAmino) { 10 | } 11 | 12 | func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { 13 | registry.RegisterInterface( 14 | "relevantcommunity.oracle.oracle.Claim", 15 | (*exportedOracle.Claim)(nil), 16 | &AtomUsd{}, 17 | ) 18 | } 19 | 20 | var ( 21 | amino = codec.NewLegacyAmino() 22 | ModuleCdc = codec.NewAminoCodec(amino) 23 | ) 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Compile 2 | FROM golang:alpine AS builder 3 | WORKDIR /src/app/ 4 | COPY go.mod go.sum* ./ 5 | RUN go mod download 6 | COPY . . 7 | # COPY ./scripts/* /home/nonroot/scripts 8 | RUN for bin in cmd/*; do CGO_ENABLED=0 go build -o=/usr/local/bin/$(basename $bin) ./cmd/$(basename $bin); done 9 | # RUN apk add --no-cache bash 10 | 11 | # Add to a distroless container 12 | FROM gcr.io/distroless/base:debug 13 | COPY --from=builder /usr/local/bin /usr/local/bin 14 | COPY --chown=nonroot ./scripts/* /home/nonroot/scripts/ 15 | USER nonroot:nonroot 16 | # RUN sh /home/nonroot/scripts/init.sh val 400token 4000000stake 17 | CMD ["oracled"] 18 | -------------------------------------------------------------------------------- /vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "npm install && vue-cli-service serve", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build" 9 | }, 10 | "dependencies": { 11 | "@tendermint/vue": "0.1.12", 12 | "core-js": "^3.6.5", 13 | "vue": "^2.6.11", 14 | "vue-router": "^3.2.0", 15 | "vuex": "^3.4.0" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^4.4.0", 19 | "@vue/cli-plugin-router": "^4.4.0", 20 | "@vue/cli-plugin-vuex": "^4.4.6", 21 | "@vue/cli-service": "^4.4.0", 22 | "vue-template-compiler": "^2.6.11" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /x/atom/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // this line is used by starport scaffolding # genesis/types/import 4 | 5 | // DefaultIndex is the default capability global index 6 | const DefaultIndex uint64 = 1 7 | 8 | // DefaultGenesis returns the default Capability genesis state 9 | func DefaultGenesis() *GenesisState { 10 | return &GenesisState{ 11 | // this line is used by starport scaffolding # genesis/types/default 12 | } 13 | } 14 | 15 | // Validate performs basic genesis state validation returning an error upon any 16 | // failure. 17 | func (gs GenesisState) Validate() error { 18 | // this line is used by starport scaffolding # genesis/types/validate 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /x/oracle/types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // DONTCOVER 4 | 5 | import ( 6 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 7 | ) 8 | 9 | // x/oracle module errors 10 | var ( 11 | ErrInvalidClaim = sdkerrors.Register(ModuleName, 2, "invalid claim") 12 | ErrNoClaimExists = sdkerrors.Register(ModuleName, 3, "no claim exits") 13 | ErrNoClaimTypeExists = sdkerrors.Register(ModuleName, 4, "claim type is not registered as part of the oracle params") 14 | ErrNoPrevote = sdkerrors.Register(ModuleName, 5, "no prevote exists for this claim") 15 | ErrIncorrectClaimRound = sdkerrors.Register(ModuleName, 6, "claim must be submitted after the prevote round is over") 16 | ) 17 | -------------------------------------------------------------------------------- /x/atom/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | oracle "github.com/relevant-community/oracle/x/oracle/exported" 6 | oracletypes "github.com/relevant-community/oracle/x/oracle/types" 7 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 8 | ) 9 | 10 | type ( 11 | // OracleKeeper interface 12 | OracleKeeper interface { 13 | FinalizeRound(ctx sdk.Context, claimType string, roundID uint64) 14 | GetPendingRounds(ctx sdk.Context, roundType string) (rounds []uint64) 15 | TallyVotes(ctx sdk.Context, claimType string, roundID uint64) *oracletypes.RoundResult 16 | GetClaim(ctx sdk.Context, hash tmbytes.HexBytes) oracle.Claim 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /x/oracle/atlas/config.toml: -------------------------------------------------------------------------------- 1 | [module] 2 | description = "The Oracle module allows validators to run arbitrary off-chain worker processes and report the results for inclusion in the on-chain state" 3 | homepage = "https://relevant-community.github.io/oracle/" 4 | keywords = [ 5 | "oracle", 6 | "offchain worker", 7 | "relevant", 8 | ] 9 | 10 | name = "x/oracle" 11 | 12 | [bug_tracker] 13 | url = "https://github.com/relevant-community/oracle/issues" 14 | 15 | [[authors]] 16 | name = "balasan" 17 | 18 | [version] 19 | documentation = "https://raw.githubusercontent.com/relevant-community/oracle/v0.1.1/readme.md" 20 | repo = "https://github.com/relevant-community/oracle" 21 | sdk_compat = "v0.41.0" 22 | version = "v0.1.2" -------------------------------------------------------------------------------- /x/atom/client/rest/rest.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | // this line is used by starport scaffolding # 1 8 | ) 9 | 10 | const ( 11 | MethodGet = "GET" 12 | ) 13 | 14 | // RegisterRoutes registers atom-related REST handlers to a router 15 | func RegisterRoutes(clientCtx client.Context, r *mux.Router) { 16 | // this line is used by starport scaffolding # 2 17 | } 18 | 19 | func registerQueryRoutes(clientCtx client.Context, r *mux.Router) { 20 | // this line is used by starport scaffolding # 3 21 | } 22 | 23 | func registerTxHandlers(clientCtx client.Context, r *mux.Router) { 24 | // this line is used by starport scaffolding # 4 25 | } 26 | -------------------------------------------------------------------------------- /app/params/proto.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/types" 6 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 7 | ) 8 | 9 | // MakeEncodingConfig creates an EncodingConfig for an amino based test configuration. 10 | func MakeEncodingConfig() EncodingConfig { 11 | amino := codec.NewLegacyAmino() 12 | interfaceRegistry := types.NewInterfaceRegistry() 13 | marshaler := codec.NewProtoCodec(interfaceRegistry) 14 | txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) 15 | 16 | return EncodingConfig{ 17 | InterfaceRegistry: interfaceRegistry, 18 | Marshaler: marshaler, 19 | TxConfig: txCfg, 20 | Amino: amino, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | accounts: 2 | - name: alice 3 | coins: ['1000token', '100000000stake'] 4 | - name: bob 5 | coins: ['500token'] 6 | - name: val2 7 | coins: ['400token'] 8 | - name: val3 9 | coins: ['500token'] 10 | validator: 11 | name: alice 12 | staked: '100000000stake' 13 | genesis: 14 | app_state: 15 | oracle: 16 | params: 17 | claim_params: 18 | AtomClaim: # This is our oracle 'type' 19 | vote_period: 3 # Voting round duration in blocks 20 | claim_type: 'AtomClaim' # Oracle type again 21 | prevote: true # Require commit-reveal type voting to avoid free-rider problem 22 | vote_threshold: '0.5' # Validator power threshold required to reach consensus 23 | -------------------------------------------------------------------------------- /x/atom/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | // ModuleName defines the module name 5 | ModuleName = "atom" 6 | 7 | // StoreKey defines the primary module store key 8 | StoreKey = ModuleName 9 | 10 | // RouterKey is the message route for slashing 11 | RouterKey = ModuleName 12 | 13 | // QuerierRoute defines the module's query routing key 14 | QuerierRoute = ModuleName 15 | 16 | // MemStoreKey defines the in-memory store key 17 | MemStoreKey = "mem_capability" 18 | 19 | // Atom Price Claim 20 | AtomClaim = "AtomClaim" 21 | ) 22 | 23 | // Keys for oracle store, with -> 24 | var ( 25 | // 0x00 -> Dec 26 | AtomUsdKey = []byte{0x00} 27 | ) 28 | 29 | func KeyPrefix(p string) []byte { 30 | return []byte(p) 31 | } 32 | -------------------------------------------------------------------------------- /x/atom/client/cli/tx.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/cosmos/cosmos-sdk/client" 9 | // "github.com/cosmos/cosmos-sdk/client/flags" 10 | "github.com/relevant-community/oracle/x/atom/types" 11 | ) 12 | 13 | // GetTxCmd returns the transaction commands for this module 14 | func GetTxCmd() *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: types.ModuleName, 17 | Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), 18 | DisableFlagParsing: true, 19 | SuggestionsMinimumDistance: 2, 20 | RunE: client.ValidateCmd, 21 | } 22 | 23 | // this line is used by starport scaffolding # 1 24 | 25 | return cmd 26 | } 27 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "description": "Cosmos IBC Account Documentation", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "vuepress dev", 8 | "preserve": "./pre.sh", 9 | "serve": "trap 'exit 0' SIGINT; vuepress dev --no-cache", 10 | "postserve": "./post.sh", 11 | "prebuild": "./pre.sh", 12 | "build": "trap 'exit 0' SIGINT; vuepress build --no-cache", 13 | "postbuild": "./post.sh", 14 | "deploy": "yarn prebuild && yarn build && yarn gh-pages --dist '.vuepress/dist'" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "gh-pages": "^3.1.0", 20 | "markdown-it": "^12.0.4", 21 | "vuepress": "^1.8.2", 22 | "vuepress-theme-cosmos": "^1.0.179" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /x/atom/handler.go: -------------------------------------------------------------------------------- 1 | package atom 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | "github.com/relevant-community/oracle/x/atom/keeper" 9 | "github.com/relevant-community/oracle/x/atom/types" 10 | ) 11 | 12 | // NewHandler ... 13 | func NewHandler(k keeper.Keeper) sdk.Handler { 14 | return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { 15 | ctx = ctx.WithEventManager(sdk.NewEventManager()) 16 | 17 | switch msg := msg.(type) { 18 | // this line is used by starport scaffolding # 1 19 | default: 20 | errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) 21 | return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /x/atom/genesis.go: -------------------------------------------------------------------------------- 1 | package atom 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/relevant-community/oracle/x/atom/keeper" 6 | "github.com/relevant-community/oracle/x/atom/types" 7 | ) 8 | 9 | // InitGenesis initializes the capability module's state from a provided genesis 10 | // state. 11 | func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { 12 | // this line is used by starport scaffolding # genesis/module/init 13 | } 14 | 15 | // ExportGenesis returns the capability module's exported genesis. 16 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 17 | genesis := types.DefaultGenesis() 18 | 19 | // this line is used by starport scaffolding # genesis/module/export 20 | 21 | return genesis 22 | } 23 | -------------------------------------------------------------------------------- /x/atom/keeper/grpc_query.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/relevant-community/oracle/x/atom/types" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | var _ types.QueryServer = Keeper{} 13 | 14 | // AtomUsd implements the Query/AtomUsd gRPC method 15 | func (k Keeper) AtomUsd(c context.Context, req *types.QueryAtomUsdRequest) (*types.QueryAtomUsdResponse, error) { 16 | if req == nil { 17 | return nil, status.Errorf(codes.InvalidArgument, "empty request") 18 | } 19 | 20 | ctx := sdk.UnwrapSDKContext(c) 21 | atomUsd := k.GetAtomUsd(ctx) 22 | if atomUsd == nil { 23 | return nil, status.Errorf(codes.NotFound, "No results") 24 | } 25 | 26 | return &types.QueryAtomUsdResponse{AtomUsd: atomUsd}, nil 27 | } 28 | -------------------------------------------------------------------------------- /x/oracle/keeper/claim_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "github.com/relevant-community/oracle/x/oracle/types" 5 | ) 6 | 7 | func (suite *KeeperTestSuite) TestCreateClaim() { 8 | ctx := suite.ctx.WithIsCheckTx(false) 9 | 10 | claim := types.NewTestClaim(1, "test", "test") 11 | 12 | suite.k.CreateClaim(ctx, claim) 13 | 14 | res := suite.k.GetClaim(ctx, claim.Hash()) 15 | suite.NotNil(res) 16 | suite.Equal(claim, res) 17 | 18 | suite.k.DeleteClaim(ctx, claim.Hash()) 19 | 20 | deleted := suite.k.GetClaim(ctx, claim.Hash()) 21 | suite.Nil(deleted) 22 | } 23 | 24 | func (suite *KeeperTestSuite) TestIterateClaims() { 25 | ctx := suite.ctx.WithIsCheckTx(false) 26 | numClaims := 20 27 | suite.populateClaims(ctx, numClaims) 28 | 29 | claims := suite.k.GetAllClaims(ctx) 30 | suite.Len(claims, numClaims) 31 | } 32 | -------------------------------------------------------------------------------- /app/prefix.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | ) 6 | 7 | const ( 8 | AccountAddressPrefix = "cosmos" 9 | ) 10 | 11 | var ( 12 | AccountPubKeyPrefix = AccountAddressPrefix + "pub" 13 | ValidatorAddressPrefix = AccountAddressPrefix + "valoper" 14 | ValidatorPubKeyPrefix = AccountAddressPrefix + "valoperpub" 15 | ConsNodeAddressPrefix = AccountAddressPrefix + "valcons" 16 | ConsNodePubKeyPrefix = AccountAddressPrefix + "valconspub" 17 | ) 18 | 19 | func SetConfig() { 20 | config := sdk.GetConfig() 21 | config.SetBech32PrefixForAccount(AccountAddressPrefix, AccountPubKeyPrefix) 22 | config.SetBech32PrefixForValidator(ValidatorAddressPrefix, ValidatorPubKeyPrefix) 23 | config.SetBech32PrefixForConsensusNode(ConsNodeAddressPrefix, ConsNodePubKeyPrefix) 24 | config.Seal() 25 | } 26 | -------------------------------------------------------------------------------- /app/genesis.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | ) 8 | 9 | // The genesis state of the blockchain is represented here as a map of raw json 10 | // messages key'd by a identifier string. 11 | // The identifier is used to determine which module genesis information belongs 12 | // to so it may be appropriately routed during init chain. 13 | // Within this application default genesis information is retrieved from 14 | // the ModuleBasicManager which populates json from each BasicModule 15 | // object provided to it during init. 16 | type GenesisState map[string]json.RawMessage 17 | 18 | // NewDefaultGenesisState generates the default state for the application. 19 | func NewDefaultGenesisState(cdc codec.JSONMarshaler) GenesisState { 20 | return ModuleBasics.DefaultGenesis(cdc) 21 | } 22 | -------------------------------------------------------------------------------- /x/oracle/types/Vote.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "crypto/sha256" 5 | fmt "fmt" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/relevant-community/oracle/x/oracle/exported" 9 | ) 10 | 11 | // NewVote creates a MsgVote instance 12 | func NewVote(roundID uint64, claim exported.Claim, validator sdk.ValAddress, claimType string) *Vote { 13 | return &Vote{ 14 | RoundId: roundID, 15 | ClaimHash: claim.Hash(), 16 | ConsensusId: claim.GetConcensusKey(), 17 | Validator: validator, 18 | ClaimType: claimType, 19 | } 20 | } 21 | 22 | // VoteHash returns the hash for a precommit given the proper args 23 | func VoteHash(salt string, claimHash string, signer sdk.AccAddress) []byte { 24 | h := sha256.New() 25 | h.Write([]byte(fmt.Sprintf("%s:%s:%s", salt, claimHash, signer.String()))) 26 | return h.Sum(nil) 27 | } 28 | -------------------------------------------------------------------------------- /x/atom/keeper/query.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | // this line is used by starport scaffolding # 1 5 | "github.com/cosmos/cosmos-sdk/codec" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | "github.com/relevant-community/oracle/x/atom/types" 9 | 10 | abci "github.com/tendermint/tendermint/abci/types" 11 | ) 12 | 13 | func NewQuerier(k Keeper, legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { 14 | return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { 15 | var ( 16 | res []byte 17 | err error 18 | ) 19 | 20 | switch path[0] { 21 | // this line is used by starport scaffolding # 1 22 | default: 23 | err = sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint: %s", types.ModuleName, path[0]) 24 | } 25 | 26 | return res, err 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /x/atom/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tendermint/tendermint/libs/log" 7 | 8 | "github.com/cosmos/cosmos-sdk/codec" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | "github.com/relevant-community/oracle/x/atom/types" 11 | ) 12 | 13 | type ( 14 | Keeper struct { 15 | cdc codec.Marshaler 16 | storeKey sdk.StoreKey 17 | memKey sdk.StoreKey 18 | oracleKeeper types.OracleKeeper 19 | } 20 | ) 21 | 22 | func NewKeeper(cdc codec.Marshaler, storeKey, memKey sdk.StoreKey, oracleKeeper types.OracleKeeper) *Keeper { 23 | return &Keeper{ 24 | cdc: cdc, 25 | storeKey: storeKey, 26 | memKey: memKey, 27 | oracleKeeper: oracleKeeper, 28 | } 29 | } 30 | 31 | func (k Keeper) Logger(ctx sdk.Context) log.Logger { 32 | return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) 33 | } 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/relevant-community/oracle 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/cosmos/cosmos-sdk v0.41.0 7 | github.com/gogo/protobuf v1.3.3 8 | github.com/golang/protobuf v1.4.3 9 | github.com/gorilla/mux v1.8.0 10 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 11 | github.com/regen-network/cosmos-proto v0.3.1 12 | github.com/spf13/cast v1.3.1 13 | github.com/spf13/cobra v1.1.1 14 | github.com/spf13/pflag v1.0.5 15 | github.com/stretchr/testify v1.7.0 16 | github.com/tendermint/tendermint v0.34.3 17 | github.com/tendermint/tm-db v0.6.3 18 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 19 | golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 // indirect 20 | google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f 21 | google.golang.org/grpc v1.35.0 22 | ) 23 | 24 | replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 25 | -------------------------------------------------------------------------------- /proto/atom/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relevantcommunity.oracle.atom; 3 | 4 | import "google/api/annotations.proto"; 5 | import "cosmos/base/query/v1beta1/pagination.proto"; 6 | 7 | import "cosmos_proto/cosmos.proto"; 8 | import "atom/atom.proto"; 9 | 10 | option go_package = "github.com/relevant-community/oracle/x/atom/types"; 11 | 12 | // Query defines the gRPC querier service. 13 | service Query { 14 | // AtomUsd queries the AtomUSD price 15 | rpc AtomUsd(QueryAtomUsdRequest) returns (QueryAtomUsdResponse) { 16 | option (google.api.http).get = "/relevantcommunity/oracle/atome/atomusd"; 17 | } 18 | } 19 | 20 | 21 | // QueryAtomUsdRequest is the request type for the Query/AtomUsd RPC method 22 | message QueryAtomUsdRequest { 23 | } 24 | 25 | // QueryAtomUsdResponse is the response type for the Query/AtomUsd RPC method 26 | message QueryAtomUsdResponse { 27 | AtomUsd atomUsd = 1; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /x/oracle/types/query.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | query "github.com/cosmos/cosmos-sdk/types/query" 5 | ) 6 | 7 | // QueryAllClaimsParams querey params struct 8 | type QueryAllClaimsParams struct { 9 | Page int `json:"page" yaml:"page"` 10 | Limit int `json:"limit" yaml:"limit"` 11 | } 12 | 13 | // NewQueryAllClaimsParams creates a new instance of QueryAllClaimsParams. 14 | func NewQueryAllClaimsParams(page, limit int) QueryAllClaimsParams { 15 | return QueryAllClaimsParams{Page: page, Limit: limit} 16 | } 17 | 18 | // NewQueryClaimRequest creates a new instance of QueryClaimRequest. 19 | func NewQueryClaimRequest(hash string) *QueryClaimRequest { 20 | return &QueryClaimRequest{ClaimHash: hash} 21 | } 22 | 23 | // NewQueryAllClaimsRequest creates a new instance of QueryAllClaimRequest. 24 | func NewQueryAllClaimsRequest(pageReq *query.PageRequest) *QueryAllClaimsRequest { 25 | return &QueryAllClaimsRequest{Pagination: pageReq} 26 | } 27 | -------------------------------------------------------------------------------- /x/oracle/keeper/params.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/relevant-community/oracle/x/oracle/types" 6 | ) 7 | 8 | // ClaimParams returns all claim params. 9 | func (k Keeper) ClaimParams(ctx sdk.Context) (res map[string](types.ClaimParams)) { 10 | k.paramspace.Get(ctx, types.KeyClaimParams, &res) 11 | return 12 | } 13 | 14 | // ClaimParamsForType returns claim params for a given claimType. 15 | func (k Keeper) ClaimParamsForType(ctx sdk.Context, claimType string) (res types.ClaimParams) { 16 | res = k.ClaimParams(ctx)[claimType] 17 | return 18 | } 19 | 20 | // GetParams returns the total set of oracle parameters. 21 | func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { 22 | k.paramspace.GetParamSet(ctx, ¶ms) 23 | return params 24 | } 25 | 26 | // SetParams sets the total set of oracle parameters. 27 | func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { 28 | k.paramspace.SetParamSet(ctx, ¶ms) 29 | } 30 | -------------------------------------------------------------------------------- /proto/oracle/params.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relevantcommunity.oracle.oracle; 3 | 4 | option go_package = "github.com/relevant-community/oracle/x/oracle/types"; 5 | option (gogoproto.equal_all) = true; 6 | 7 | import "gogoproto/gogo.proto"; 8 | 9 | // Params represents the parameters used for by the slashing module. 10 | message Params { 11 | map claim_params = 1 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"claim_params\""]; 12 | } 13 | 14 | // ClaimParams is the parameters set for each oracle claim type 15 | message ClaimParams { 16 | uint64 vote_period = 1 [(gogoproto.moretags) = "yaml:\"vote_period\""]; 17 | string claim_type = 2 [(gogoproto.moretags) = "yaml:\"claim_type\""]; 18 | bool prevote = 3 [(gogoproto.moretags) = "yaml:\"prevote\""]; 19 | bytes vote_threshold = 4 [ 20 | (gogoproto.moretags) = "yaml:\"vote_threshold\"", 21 | (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", 22 | (gogoproto.nullable) = false 23 | ]; 24 | } 25 | -------------------------------------------------------------------------------- /proto/oracle/oracle.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relevantcommunity.oracle.oracle; 3 | 4 | option go_package = "github.com/relevant-community/oracle/x/oracle/types"; 5 | import "gogoproto/gogo.proto"; 6 | 7 | // Vote is a vote for a given claim by a validator 8 | message Vote { 9 | uint64 roundId = 1; // use int in case we need to enforce sequential ordering 10 | bytes claimHash = 2 [(gogoproto.casttype) = "github.com/tendermint/tendermint/libs/bytes.HexBytes"]; 11 | string consensusId = 3; // used to compare claims when tallying votes 12 | string claimType = 4; 13 | bytes validator = 5 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress"]; 14 | } 15 | 16 | // Round contains all claim votes for a given round 17 | message Round { 18 | uint64 roundId = 1; 19 | string claimType = 2; // namespace so we can have multiple oracles 20 | repeated Vote votes = 3 [(gogoproto.nullable) = false]; 21 | } 22 | 23 | // TestClaim is a concrete Claim type we use for testing 24 | message TestClaim { 25 | int64 blockHeight = 1; 26 | string claimType = 2; 27 | string content = 3; 28 | } -------------------------------------------------------------------------------- /x/oracle/types/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" 6 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 7 | ) 8 | 9 | type ( 10 | // StakingKeeper defines the staking module interface contract needed by the 11 | // claim module. 12 | StakingKeeper interface { 13 | // ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) stakingexported.ValidatorI 14 | Validator(ctx sdk.Context, address sdk.ValAddress) stakingtypes.ValidatorI // get validator by operator address; nil when validator not found 15 | 16 | TotalBondedTokens(sdk.Context) sdk.Int // total bonded tokens within the validator set 17 | } 18 | 19 | // ParamSubspace defines the expected Subspace interfacace 20 | ParamSubspace interface { 21 | HasKeyTable() bool 22 | WithKeyTable(table paramtypes.KeyTable) paramtypes.Subspace 23 | Get(ctx sdk.Context, key []byte, ptr interface{}) 24 | GetParamSet(ctx sdk.Context, ps paramtypes.ParamSet) 25 | SetParamSet(ctx sdk.Context, ps paramtypes.ParamSet) 26 | } 27 | ) 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow that is manually triggered 2 | 3 | name: oracle 4 | 5 | # Controls when the action will run. Workflow runs when manually triggered using the UI 6 | # or API. 7 | on: [push, pull_request] 8 | 9 | # This workflow makes x86_64 binaries for mac, windows, and linux. 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | arch: [amd64, arm64] 17 | targetos: [windows, darwin, linux] 18 | name: oracle ${{ matrix.arch }} for ${{ matrix.targetos }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Setup go 22 | uses: actions/setup-go@v1 23 | with: 24 | go-version: 1.15 25 | env: 26 | GOOS: ${{ matrix.targetos }} 27 | GOARCH: ${{ matrix.arch }} 28 | 29 | - name: Compile 30 | run: | 31 | go mod download 32 | cd cmd/oracled 33 | go build . 34 | 35 | - uses: actions/upload-artifact@v2 36 | with: 37 | name: oracled ${{ matrix.targetos }} ${{ matrix.arch }} 38 | path: cmd/oracled/oracled 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: [push] 3 | jobs: 4 | build-and-deploy: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 🛎️ 8 | uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 9 | with: 10 | persist-credentials: false 11 | 12 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 13 | run: | 14 | cd docs 15 | npm install 16 | npm run prebuild 17 | npm run build 18 | 19 | - name: Deploy 🚀 20 | uses: JamesIves/github-pages-deploy-action@3.7.1 21 | with: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | BRANCH: gh-pages # The branch the action should deploy to. 24 | FOLDER: docs/.vuepress/dist # The folder the action should deploy. 25 | CLEAN: true # Automatically remove deleted files from the deploy branch 26 | -------------------------------------------------------------------------------- /x/oracle/types/RoundResult.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import tmbytes "github.com/tendermint/tendermint/libs/bytes" 4 | 5 | // ClaimVoteResult is a record of votes for each claim 6 | type ClaimVoteResult struct { 7 | ClaimHash tmbytes.HexBytes 8 | VotePower int64 9 | } 10 | 11 | // RoundResult is is a record of vote tallies for a given round 12 | type RoundResult struct { 13 | VotePower int64 14 | TotalPower int64 15 | ClaimType string 16 | Claims []*ClaimVoteResult 17 | } 18 | 19 | // UpsertClaim upserts a claim 20 | func (r *RoundResult) UpsertClaim(claimHash tmbytes.HexBytes, votePower int64) { 21 | var existingClaim *ClaimVoteResult 22 | for _, claim := range r.Claims { 23 | if claim.ClaimHash.String() == claimHash.String() { 24 | existingClaim = claim 25 | } 26 | } 27 | 28 | if existingClaim != nil { 29 | existingClaim.VotePower += votePower 30 | return 31 | } 32 | newClaim := newClaimVoteResult(claimHash, votePower) 33 | r.Claims = append(r.Claims, newClaim) 34 | return 35 | } 36 | 37 | func newClaimVoteResult(claimHash tmbytes.HexBytes, votePower int64) *ClaimVoteResult { 38 | return &ClaimVoteResult{ 39 | ClaimHash: claimHash, 40 | VotePower: votePower, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /proto/oracle/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relevantcommunity.oracle.oracle; 3 | 4 | option go_package = "github.com/relevant-community/oracle/x/oracle/types"; 5 | 6 | import "gogoproto/gogo.proto"; 7 | import "google/protobuf/any.proto"; 8 | 9 | import "oracle/params.proto"; 10 | import "oracle/oracle.proto"; 11 | import "oracle/tx.proto"; 12 | 13 | 14 | // GenesisState defines the oracle module's genesis state. 15 | message GenesisState { 16 | option (gogoproto.equal) = false; 17 | option (gogoproto.goproto_getters) = false; 18 | 19 | // params defines all the paramaters of related to deposit. 20 | Params params = 1 [(gogoproto.nullable) = false]; 21 | repeated google.protobuf.Any claims =2; 22 | repeated Round rounds = 3 [(gogoproto.nullable) = false]; 23 | message ListOfUint { 24 | repeated uint64 pending = 1 [(gogoproto.nullable) = false]; 25 | } 26 | map pending = 4 [(gogoproto.nullable) = false]; 27 | repeated MsgDelegate delegations =5 [ 28 | (gogoproto.nullable) = false 29 | ]; 30 | repeated bytes prevotes = 6 [(gogoproto.nullable) = false]; 31 | map finalizedRounds = 7 [(gogoproto.nullable) = false]; 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /x/oracle/handler.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | "github.com/relevant-community/oracle/x/oracle/keeper" 9 | "github.com/relevant-community/oracle/x/oracle/types" 10 | ) 11 | 12 | // NewHandler ... 13 | func NewHandler(k keeper.Keeper) sdk.Handler { 14 | msgServer := keeper.NewMsgServerImpl(k) 15 | 16 | return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { 17 | ctx = ctx.WithEventManager(sdk.NewEventManager()) 18 | 19 | switch msg := msg.(type) { 20 | // this line is used by starport scaffolding # 1 21 | case *types.MsgDelegate: 22 | res, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), msg) 23 | return sdk.WrapServiceResult(ctx, res, err) 24 | case *types.MsgPrevote: 25 | res, err := msgServer.Prevote(sdk.WrapSDKContext(ctx), msg) 26 | return sdk.WrapServiceResult(ctx, res, err) 27 | case *types.MsgVote: 28 | res, err := msgServer.Vote(sdk.WrapSDKContext(ctx), msg) 29 | return sdk.WrapServiceResult(ctx, res, err) 30 | default: 31 | errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) 32 | return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /x/oracle/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tendermint/tendermint/libs/log" 7 | 8 | "github.com/cosmos/cosmos-sdk/codec" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | "github.com/relevant-community/oracle/x/oracle/types" 11 | ) 12 | 13 | type ( 14 | // Keeper of the oracle store 15 | Keeper struct { 16 | cdc codec.Marshaler 17 | storeKey sdk.StoreKey 18 | memKey sdk.StoreKey 19 | StakingKeeper types.StakingKeeper 20 | paramspace types.ParamSubspace 21 | } 22 | ) 23 | 24 | // NewKeeper instatiates the oracle keeper 25 | func NewKeeper(cdc codec.Marshaler, storeKey, memKey sdk.StoreKey, stakingKeeper types.StakingKeeper, paramspace types.ParamSubspace) *Keeper { 26 | 27 | // set KeyTable if it has not already been set 28 | if !paramspace.HasKeyTable() { 29 | paramspace = paramspace.WithKeyTable(types.ParamKeyTable()) 30 | } 31 | 32 | return &Keeper{ 33 | cdc: cdc, 34 | storeKey: storeKey, 35 | memKey: memKey, 36 | StakingKeeper: stakingKeeper, 37 | paramspace: paramspace, 38 | } 39 | } 40 | 41 | // Logger returns a module-specific logger. 42 | func (k Keeper) Logger(ctx sdk.Context) log.Logger { 43 | return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) 44 | } 45 | -------------------------------------------------------------------------------- /x/oracle/spec/04_events.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Events 6 | 7 | The oracle module emits the following events: 8 | 9 | ### MsgDelegate 10 | 11 | | Type | Attribute Key | Attribute Value | 12 | | -------- | ------------- | ------------------ | 13 | | delegate | validator | {validatorAddress} | 14 | | delegate | delegate | {delegateAddress} | 15 | | message | module | oracle | 16 | | message | action | delegate | 17 | | message | sender | {senderAddress} | 18 | 19 | ### MsgPrevote 20 | 21 | | Type | Attribute Key | Attribute Value | 22 | | ------- | ------------- | ------------------ | 23 | | prevote | prevote_hash | {prevoteHash} | 24 | | prevote | validator | {validatorAddress} | 25 | | message | module | oracle | 26 | | message | action | prevote | 27 | | message | sender | {senderAddress} | 28 | 29 | ### MsgVote 30 | 31 | | Type | Attribute Key | Attribute Value | 32 | | ------- | ------------- | ------------------ | 33 | | vote | claim_hash | {claimHash} | 34 | | vote | validator | {validatorAddress} | 35 | | message | module | oracle | 36 | | message | action | vote | 37 | | message | sender | {senderAddress} | 38 | -------------------------------------------------------------------------------- /x/oracle/exported/claim.go: -------------------------------------------------------------------------------- 1 | // Package exported defines a Claim interface that all oracle claims must implement 2 | package exported 3 | 4 | import ( 5 | "github.com/gogo/protobuf/proto" 6 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 7 | 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | // Claim defines the methods that all oracle claims must implement 12 | type Claim interface { 13 | proto.Message 14 | 15 | Type() string 16 | Hash() tmbytes.HexBytes 17 | GetRoundID() uint64 18 | // GetConcensusKey returns a key the oracle will use fore voting consensus 19 | // for deterministic results it should be the the hash of the content 20 | // (this is because we expect all validators to submit claims with the same exact content) 21 | // however for nondeterministic content it should be a constant because voting doesn't 22 | // depend on the content of the claim (developers will need to use the results of the voting 23 | // to reconcile the various claims) 24 | GetConcensusKey() string 25 | ValidateBasic() error 26 | } 27 | 28 | // MsgVoteI defines the specific interface a concrete message must 29 | // implement in order to process an oracle claim. The concrete MsgSubmitClaim 30 | // must be defined at the application-level. 31 | type MsgVoteI interface { 32 | sdk.Msg 33 | 34 | GetClaim() Claim 35 | GetSigner() sdk.AccAddress 36 | } 37 | -------------------------------------------------------------------------------- /app/types.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | abci "github.com/tendermint/tendermint/abci/types" 5 | 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | "github.com/cosmos/cosmos-sdk/server/types" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | ) 10 | 11 | // App implements the common methods for a Cosmos SDK-based application 12 | // specific blockchain. 13 | type CosmosApp interface { 14 | // The assigned name of the app. 15 | Name() string 16 | 17 | // The application types codec. 18 | // NOTE: This shoult be sealed before being returned. 19 | LegacyAmino() *codec.LegacyAmino 20 | 21 | // Application updates every begin block. 22 | BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock 23 | 24 | // Application updates every end block. 25 | EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock 26 | 27 | // Application update at chain (i.e app) initialization. 28 | InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain 29 | 30 | // Loads the app at a given height. 31 | LoadHeight(height int64) error 32 | 33 | // Exports the state of the application for a genesis file. 34 | ExportAppStateAndValidators( 35 | forZeroHeight bool, jailAllowedAddrs []string, 36 | ) (types.ExportedApp, error) 37 | 38 | // All the registered module account addreses. 39 | ModuleAccountAddrs() map[string]bool 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | amd64: 6 | name: oracle Docker 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Set up QEMU 13 | run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes 14 | 15 | - name: Set up Docker Buildx 16 | id: buildx 17 | uses: docker/setup-buildx-action@v1 18 | with: 19 | version: latest 20 | 21 | - name: Login to DockerHub 22 | uses: docker/login-action@v1 23 | with: 24 | username: ${{ secrets.DOCKERHUB_USERNAME }} 25 | password: ${{ secrets.DOCKERHUB_TOKEN }} 26 | 27 | - name: Build and push blockchain 28 | run: docker buildx build --tag ${{ secrets.DOCKERHUB_USERNAME }}/oracle --file Dockerfile --platform linux/amd64,linux/arm64 --cache-from ${{ secrets.DOCKERHUB_USERNAME }}/oracle:cache --cache-to ${{ secrets.DOCKERHUB_USERNAME }}/oracle:cache --push --progress tty . 29 | 30 | - name: Build and push UI 31 | run: docker buildx build --tag ${{ secrets.DOCKERHUB_USERNAME }}/oracle-ui --file vue/Dockerfile --platform linux/amd64,linux/arm64 --cache-from ${{ secrets.DOCKERHUB_USERNAME }}/oracle-ui:cache --cache-to ${{ secrets.DOCKERHUB_USERNAME }}/oracle-ui:cache --push --progress tty ./vue 32 | -------------------------------------------------------------------------------- /docs/.vuepress/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | gaiadnode0: 5 | container_name: oracle0 6 | image: 'relevant/oracle' 7 | ports: 8 | - '26656-26657:26656-26657' 9 | environment: 10 | - ID=0 11 | - LOG=${LOG:-oracled.log} 12 | volumes: 13 | - ./build:/oracled:Z 14 | networks: 15 | localnet: 16 | ipv4_address: 192.168.10.2 17 | 18 | gaiadnode1: 19 | container_name: oracle1 20 | image: 'relevant/oracle' 21 | ports: 22 | - '26659-26660:26656-26657' 23 | environment: 24 | - ID=1 25 | - LOG=${LOG:-oracled.log} 26 | volumes: 27 | - ./build:/oracled:Z 28 | networks: 29 | localnet: 30 | ipv4_address: 192.168.10.3 31 | 32 | gaiadnode2: 33 | container_name: oracle2 34 | image: 'relevant/oracle' 35 | environment: 36 | - ID=2 37 | - LOG=${LOG:-oracled.log} 38 | ports: 39 | - '26661-26662:26656-26657' 40 | volumes: 41 | - ./build:/oracled:Z 42 | networks: 43 | localnet: 44 | ipv4_address: 192.168.10.4 45 | 46 | gaiadnode3: 47 | container_name: oracle3 48 | image: 'relevant/oracle' 49 | environment: 50 | - ID=3 51 | - LOG=${LOG:-oracled.log} 52 | ports: 53 | - '26663-26664:26656-26657' 54 | volumes: 55 | - ./build:/oracled:Z 56 | networks: 57 | localnet: 58 | ipv4_address: 192.168.10.5 59 | 60 | networks: 61 | localnet: 62 | driver: bridge 63 | ipam: 64 | driver: default 65 | config: 66 | - subnet: 192.168.10.0/16 67 | -------------------------------------------------------------------------------- /x/oracle/types/MsgVote_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/relevant-community/oracle/x/oracle/exported" 8 | "github.com/relevant-community/oracle/x/oracle/types" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func testMsgCreateClaim(t *testing.T, c exported.Claim, s sdk.AccAddress) exported.MsgVoteI { 13 | msg, err := types.NewMsgVote(s, c, "") 14 | require.NoError(t, err) 15 | return msg 16 | } 17 | 18 | func TestMsgCreateClaim(t *testing.T) { 19 | submitter := sdk.AccAddress("test________________") 20 | 21 | testCases := []struct { 22 | msg sdk.Msg 23 | submitter sdk.AccAddress 24 | expectErr bool 25 | }{ 26 | { 27 | testMsgCreateClaim(t, &types.TestClaim{ 28 | BlockHeight: 0, 29 | Content: "test", 30 | ClaimType: "test", 31 | }, submitter), 32 | submitter, 33 | true, 34 | }, 35 | { 36 | testMsgCreateClaim(t, &types.TestClaim{ 37 | BlockHeight: 10, 38 | Content: "test", 39 | ClaimType: "test", 40 | }, submitter), 41 | submitter, 42 | false, 43 | }, 44 | } 45 | 46 | for i, tc := range testCases { 47 | require.Equal(t, tc.msg.Route(), types.RouterKey, "unexpected result for tc #%d", i) 48 | require.Equal(t, tc.msg.Type(), types.TypeMsgVote, "unexpected result for tc #%d", i) 49 | require.Equal(t, tc.expectErr, tc.msg.ValidateBasic() != nil, "unexpected result for tc #%d", i) 50 | 51 | if !tc.expectErr { 52 | require.Equal(t, tc.msg.GetSigners(), []sdk.AccAddress{tc.submitter}, "unexpected result for tc #%d", i) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /x/atom/types/atomUsd.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | fmt "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/tendermint/tendermint/crypto/tmhash" 8 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 9 | ) 10 | 11 | // AtomUsd creates a new AtomUsd claim 12 | func NewAtomUsd(blockHeight int64, price string) *AtomUsd { 13 | decPrice, err := sdk.NewDecFromStr(price) 14 | if err != nil { 15 | fmt.Println(err) 16 | return nil 17 | } 18 | return &AtomUsd{ 19 | Price: decPrice, 20 | BlockHeight: blockHeight, 21 | } 22 | } 23 | 24 | // Claim methods that implement the abstract Claim interface of the oracle module 25 | 26 | // ValidateBasic performs basic validation of the claim 27 | func (c *AtomUsd) ValidateBasic() error { 28 | if c.BlockHeight < 1 { 29 | return fmt.Errorf("invalid claim height: %d", c.BlockHeight) 30 | } 31 | return nil 32 | } 33 | 34 | // Type return type of oracle Claim 35 | func (c *AtomUsd) Type() string { 36 | return AtomClaim 37 | } 38 | 39 | // Hash returns the hash of an Claim Content. 40 | func (c *AtomUsd) Hash() tmbytes.HexBytes { 41 | bz, err := c.Marshal() 42 | if err != nil { 43 | panic(err) 44 | } 45 | return tmhash.Sum(bz) 46 | } 47 | 48 | // GetRoundID returns the block height for when the data was used. 49 | func (c *AtomUsd) GetRoundID() uint64 { 50 | return uint64(c.BlockHeight) 51 | } 52 | 53 | // GetConcensusKey returns a key the oracle will use of vote consensus 54 | // for deterministic results it should be the same as the hash of the content 55 | // for nondeterministic content it should be a constant 56 | func (c *AtomUsd) GetConcensusKey() string { 57 | return "" 58 | } 59 | -------------------------------------------------------------------------------- /x/oracle/types/MsgPrevote.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | fmt "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 8 | ) 9 | 10 | // Message types for the oracle module 11 | const ( 12 | TypeMsgPrevote = "prevote" 13 | ) 14 | 15 | var ( 16 | _ sdk.Msg = &MsgPrevote{} 17 | ) 18 | 19 | // NewMsgPrevote returns a new MsgPrevotePrevote with a signer. 20 | func NewMsgPrevote(s sdk.AccAddress, hash []byte) *MsgPrevote { 21 | return &MsgPrevote{Signer: s.String(), Hash: hash} 22 | } 23 | 24 | // Route get msg route 25 | func (msg *MsgPrevote) Route() string { 26 | return RouterKey 27 | } 28 | 29 | // Type get msg type 30 | func (msg *MsgPrevote) Type() string { 31 | return TypeMsgPrevote 32 | } 33 | 34 | // GetSigners implements sdk.Msg 35 | func (msg *MsgPrevote) GetSigners() []sdk.AccAddress { 36 | return []sdk.AccAddress{msg.MustGetSigner()} 37 | } 38 | 39 | // GetSignBytes get msg get getsingbytes 40 | func (msg *MsgPrevote) GetSignBytes() []byte { 41 | bz := ModuleCdc.MustMarshalJSON(msg) 42 | return sdk.MustSortJSON(bz) 43 | } 44 | 45 | // ValidateBasic validation 46 | func (msg *MsgPrevote) ValidateBasic() error { 47 | if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { 48 | return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) 49 | } 50 | 51 | if len(msg.Hash) == 0 { 52 | return fmt.Errorf("prevote hash cannot be empty") 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // MustGetSigner returns submitter 59 | func (msg MsgPrevote) MustGetSigner() sdk.AccAddress { 60 | accAddr, err := sdk.AccAddressFromBech32(msg.Signer) 61 | if err != nil { 62 | panic(err) 63 | } 64 | return accAddr 65 | } 66 | -------------------------------------------------------------------------------- /x/oracle/spec/05_params.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Parameters 6 | 7 | Each `claimType` your application needs to process, must be registered under in the Oracle Module params. 8 | `ClaimParams` is a map that contains the parameters for each `claimType`. 9 | 10 | ```protobuf 11 | // Params represents the parameters used by the oracle module. 12 | message Params { 13 | map claim_params = 1 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"claim_params\""]; 14 | } 15 | 16 | // ClaimParams is the parameters set for each oracle claim type 17 | message ClaimParams { 18 | uint64 vote_period = 1 [(gogoproto.moretags) = "yaml:\"vote_period\""]; 19 | string claim_type = 2 [(gogoproto.moretags) = "yaml:\"claim_type\""]; 20 | bool prevote = 3 [(gogoproto.moretags) = "yaml:\"prevote\""]; 21 | bytes vote_threshold = 2 [ 22 | (gogoproto.moretags) = "yaml:\"vote_threshold\"", 23 | (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", 24 | (gogoproto.nullable) = false 25 | ]; 26 | } 27 | 28 | ``` 29 | 30 | Each `OracleType` supports the following parameters: 31 | 32 | | Key | Type | Example | Description | 33 | | -------------- | ------------- | ---------------------- | --------------------------------- | 34 | | vote_period | string (int) | "5" | duration of voting round | 35 | | vote_threshold | string (dec) | "0.500000000000000000" | threshold necessary for consensus | 36 | | prevote | boolean (dec) | true | requires prevote round | 37 | | claim_type | string | "myOracleClaimType" | claim type | 38 | -------------------------------------------------------------------------------- /x/oracle/client/cli/tx.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | "github.com/cosmos/cosmos-sdk/client/flags" 8 | "github.com/cosmos/cosmos-sdk/client/tx" 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | "github.com/relevant-community/oracle/x/oracle/types" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | // GetTxCmd returns the transaction commands for this module 15 | func GetTxCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: types.ModuleName, 18 | Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), 19 | DisableFlagParsing: true, 20 | SuggestionsMinimumDistance: 2, 21 | RunE: client.ValidateCmd, 22 | } 23 | 24 | // this line is used by starport scaffolding # 1 25 | cmd.AddCommand(StartWorkerCmd()) 26 | cmd.AddCommand(TxDelegate()) 27 | 28 | return cmd 29 | } 30 | 31 | // TxDelegate delegates permission 32 | func TxDelegate() *cobra.Command { 33 | cmd := &cobra.Command{ 34 | Use: "delegate-feeder [address]", 35 | Args: cobra.ExactArgs(1), 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | ctx, err := client.GetClientTxContext(cmd) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | del, err := sdk.AccAddressFromBech32(args[0]) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | msg := types.NewMsgDelegate(ctx.GetFromAddress(), del) 48 | if err = msg.ValidateBasic(); err != nil { 49 | return fmt.Errorf("message validation failed: %w", err) 50 | } 51 | 52 | return tx.GenerateOrBroadcastTxCLI(ctx, cmd.Flags(), msg) 53 | 54 | }, 55 | } 56 | flags.AddTxFlagsToCmd(cmd) 57 | return cmd 58 | } 59 | -------------------------------------------------------------------------------- /x/oracle/types/TestClaim.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | fmt "fmt" 5 | 6 | "github.com/tendermint/tendermint/crypto/tmhash" 7 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 8 | ) 9 | 10 | // NewTestClaim creates a new TestClaim object 11 | func NewTestClaim(blockHeight int64, content string, claimType string) *TestClaim { 12 | return &TestClaim{ 13 | BlockHeight: blockHeight, 14 | Content: content, 15 | ClaimType: claimType, 16 | } 17 | } 18 | 19 | // Claim types needed for oracle 20 | 21 | // ValidateBasic performs basic validation of the claim 22 | func (c *TestClaim) ValidateBasic() error { 23 | if len(c.ClaimType) == 0 { 24 | return fmt.Errorf("claim type should not be empty") 25 | } 26 | if len(c.Content) == 0 { 27 | return fmt.Errorf("claim content should not be empty") 28 | } 29 | if c.BlockHeight < 1 { 30 | return fmt.Errorf("invalid claim height: %d", c.BlockHeight) 31 | } 32 | return nil 33 | } 34 | 35 | // Type return type of oracle Claim 36 | func (c *TestClaim) Type() string { 37 | return c.ClaimType 38 | } 39 | 40 | // Hash returns the hash of an Claim Content. 41 | func (c *TestClaim) Hash() tmbytes.HexBytes { 42 | bz, err := c.Marshal() 43 | if err != nil { 44 | panic(err) 45 | } 46 | return tmhash.Sum(bz) 47 | } 48 | 49 | // GetRoundID returns the block height for when the data was used. 50 | func (c *TestClaim) GetRoundID() uint64 { 51 | return uint64(c.BlockHeight) 52 | } 53 | 54 | // GetConcensusKey returns a key the oracle will use of vote consensus 55 | // for deterministic results it should be the same as the hash of the content 56 | // for nondeterministic content it should be a constant 57 | func (c *TestClaim) GetConcensusKey() string { 58 | return c.Hash().String() 59 | } 60 | -------------------------------------------------------------------------------- /x/oracle/spec/README.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Oracle & Offchain Workers 9 | 10 | ## Abstract 11 | 12 | This document specifies the Oracle module for the Cosmos SDK. 13 | 14 | The Oracle module accepts and stores arbitrary `Claims` submitted by the chain validators. Multiple oracly types can run simultaniously. 15 | 16 | The module includes helper methods to design off-chain workers that supply data to the Oracle module. 17 | 18 | The Oracle modules assists with deciding how a consensus is reached on oracle results, however it is the responsibility of external modules to implement this logic as needed. 19 | 20 | ## Contents 21 | 22 | 1. **[Concepts](01_concepts.md)** 23 | - [Worker](01_concepts.md#Worker) 24 | - [Claim](01_concepts.md#Claim) 25 | - [Vote](01_concepts.md#Vote) 26 | - [Prevote](01_concepts.md#Prevote) 27 | - [Round](01_concepts.md#Round) 28 | - [PendingRounds](01_concepts.md#PendingRounds) 29 | - [Tallying Votes](01_concepts.md#Tallying-Votes) 30 | - [HouseKeeping](01_concepts.md#HouseKeeping) 31 | - [Rewards and Punishments](01_concepts.md#Rewards-and-Punishments) 32 | - [Validator Delegation](01_concepts.md#Validator-Delegation) 33 | 34 | 2) **[State](02_state.md)** 35 | - [Round](02_state.md#Round) 36 | - [PendingRounds](02_state.md#PendingRounds) 37 | - [Prevote](02_state.md#Prevote) 38 | - [FeedDelegateKey](02_state.md#FeedDelegateKey) 39 | 3) **[Messages](03_messages.md)** 40 | - [MsgDelegate](03_messages.md#MsgDelegate) 41 | - [MsgPrevote](03_messages.md#MsgPrevote) 42 | - [MsgVote](03_messages.md#MsgVote) 43 | 4) **[Events](04_events.md)** 44 | - [MsgDelegate](04_events.md#MsgDelegate) 45 | - [MsgPrevote](04_events.md#MsgPrevote) 46 | - [MsgVote](04_events.md#MsgVote) 47 | 5) **[Params](05_params.md)** 48 | -------------------------------------------------------------------------------- /x/oracle/client/cli/queryClaim.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cosmos/cosmos-sdk/client" 7 | "github.com/cosmos/cosmos-sdk/client/flags" 8 | "github.com/relevant-community/oracle/x/oracle/types" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // CmdClaim queries a claim by hash 13 | func CmdClaim() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "claim", 16 | Short: "query claim by hash", 17 | Args: cobra.ExactArgs(1), 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | clientCtx, err := client.GetClientQueryContext(cmd) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | queryClient := types.NewQueryClient(clientCtx) 25 | 26 | hash := args[0] 27 | params := &types.QueryClaimRequest{ClaimHash: hash} 28 | 29 | res, err := queryClient.Claim(context.Background(), params) 30 | if err != nil { 31 | return err 32 | } 33 | return clientCtx.PrintProto(res) 34 | }, 35 | } 36 | 37 | flags.AddQueryFlagsToCmd(cmd) 38 | 39 | return cmd 40 | } 41 | 42 | // CmdAllClaims queries all clams 43 | func CmdAllClaims() *cobra.Command { 44 | cmd := &cobra.Command{ 45 | Use: "all-claims", 46 | Short: "query all claims", 47 | RunE: func(cmd *cobra.Command, args []string) error { 48 | clientCtx, err := client.GetClientQueryContext(cmd) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | pageReq, err := client.ReadPageRequest(cmd.Flags()) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | queryClient := types.NewQueryClient(clientCtx) 59 | 60 | params := &types.QueryAllClaimsRequest{ 61 | Pagination: pageReq, 62 | } 63 | 64 | res, err := queryClient.AllClaims(context.Background(), params) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | return clientCtx.PrintProto(res) 70 | }, 71 | } 72 | 73 | flags.AddQueryFlagsToCmd(cmd) 74 | 75 | return cmd 76 | } 77 | -------------------------------------------------------------------------------- /x/atom/client/cli/query.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | // "strings" 9 | 10 | "github.com/spf13/cobra" 11 | 12 | "github.com/cosmos/cosmos-sdk/client" 13 | "github.com/cosmos/cosmos-sdk/client/flags" 14 | 15 | // "github.com/cosmos/cosmos-sdk/client/flags" 16 | // sdk "github.com/cosmos/cosmos-sdk/types" 17 | 18 | "github.com/relevant-community/oracle/x/atom/types" 19 | ) 20 | 21 | // GetQueryCmd returns the cli query commands for this module 22 | func GetQueryCmd(queryRoute string) *cobra.Command { 23 | // Group atom queries under a subcommand 24 | cmd := &cobra.Command{ 25 | Use: types.ModuleName, 26 | Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), 27 | DisableFlagParsing: true, 28 | SuggestionsMinimumDistance: 2, 29 | RunE: client.ValidateCmd, 30 | } 31 | 32 | // this line is used by starport scaffolding # 1 33 | cmd.AddCommand(CmdAtomUsd()) 34 | return cmd 35 | } 36 | 37 | // CmdAtomUsd implements a command to fetch the AtomUsd price. 38 | func CmdAtomUsd() *cobra.Command { 39 | cmd := &cobra.Command{ 40 | Use: "atomUsd", 41 | Short: "query the Atom/USD price", 42 | Args: cobra.NoArgs, 43 | Long: strings.TrimSpace(`Query genesis parameters for the oracle module: 44 | 45 | $ query oracle params 46 | `), 47 | RunE: func(cmd *cobra.Command, args []string) error { 48 | clientCtx, err := client.GetClientQueryContext(cmd) 49 | if err != nil { 50 | return err 51 | } 52 | queryClient := types.NewQueryClient(clientCtx) 53 | 54 | params := &types.QueryAtomUsdRequest{} 55 | res, err := queryClient.AtomUsd(context.Background(), params) 56 | 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return clientCtx.PrintProto(res.AtomUsd) 62 | }, 63 | } 64 | 65 | flags.AddQueryFlagsToCmd(cmd) 66 | 67 | return cmd 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/pi.yml: -------------------------------------------------------------------------------- 1 | # This workflow makes a 64 bit Raspberry Pi Arch Linux Image. 2 | # It does not have the security issues mentioned here: https://github.com/tendermint/tendermint/blob/master/docs/tendermint-core/running-in-production.md#validator-signing-on-32-bit-architectures-or-arm 3 | # Later, more devices will be supported, as well. 4 | # The "base" is built by: https://github.com/faddat/sos 5 | # The base image is located at: https://hub.docker.com/r/faddat/spos 6 | # TODO: Replace this with a system that fetches SOS-light, loop-mounts the image, modifies hostname, adds docker compose run, and exits. 7 | 8 | name: Rpi 9 | on: [push, pull_request] 10 | 11 | jobs: 12 | pi: 13 | name: oracle Pi 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up QEMU 20 | run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes 21 | 22 | - name: Set up Docker Buildx 23 | id: buildx 24 | uses: docker/setup-buildx-action@v1 25 | with: 26 | version: latest 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v1 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USERNAME }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | 34 | - name: Build Image in Docker 35 | run: docker buildx build --tag oracle --file .pi/Dockerfile --platform linux/arm64 --cache-from ${{ secrets.DOCKERHUB_USERNAME }}/oracle:picache --cache-to ${{ secrets.DOCKERHUB_USERNAME }}/oracle:picache --load --progress tty . 36 | 37 | - name: Build Image 38 | run: bash .pi/build.sh 39 | 40 | - name: Compress 41 | run: xz -T $(nproc) images/oracle.img 42 | 43 | - name: Upload image 44 | uses: actions/upload-artifact@v2 45 | with: 46 | name: Starport Pi 47 | path: images/oracle.img.xz 48 | -------------------------------------------------------------------------------- /x/oracle/types/MsgDelegate.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 6 | ) 7 | 8 | var _ sdk.Msg = &MsgDelegate{} 9 | 10 | // Message types for the oracle module 11 | const ( 12 | TypeMsgDelegate = "delegate" 13 | ) 14 | 15 | // NewMsgDelegate returns a new MsgDelegateFeedConsent 16 | func NewMsgDelegate(val, del sdk.AccAddress) *MsgDelegate { 17 | return &MsgDelegate{ 18 | Validator: val.String(), 19 | Delegate: del.String(), 20 | } 21 | } 22 | 23 | // Route implements sdk.Msg 24 | func (m *MsgDelegate) Route() string { return ModuleName } 25 | 26 | // Type implements sdk.Msg 27 | func (m *MsgDelegate) Type() string { return TypeMsgDelegate } 28 | 29 | // ValidateBasic implements sdk.Msg 30 | func (m *MsgDelegate) ValidateBasic() error { 31 | if _, err := sdk.AccAddressFromBech32(m.Validator); err != nil { 32 | return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) 33 | } 34 | if _, err := sdk.AccAddressFromBech32(m.Delegate); err != nil { 35 | return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) 36 | } 37 | return nil 38 | } 39 | 40 | // GetSignBytes implements sdk.Msg 41 | func (m *MsgDelegate) GetSignBytes() []byte { 42 | panic("amino support disabled") 43 | } 44 | 45 | // GetSigners implements sdk.Msg 46 | func (m *MsgDelegate) GetSigners() []sdk.AccAddress { 47 | return []sdk.AccAddress{m.MustGetValidator()} 48 | } 49 | 50 | // MustGetValidator returns the sdk.AccAddress for the validator 51 | func (m *MsgDelegate) MustGetValidator() sdk.AccAddress { 52 | val, err := sdk.AccAddressFromBech32(m.Validator) 53 | if err != nil { 54 | panic(err) 55 | } 56 | return val 57 | } 58 | 59 | // MustGetDelegate returns the sdk.AccAddress for the delegate 60 | func (m *MsgDelegate) MustGetDelegate() sdk.AccAddress { 61 | val, err := sdk.AccAddressFromBech32(m.Delegate) 62 | if err != nil { 63 | panic(err) 64 | } 65 | return val 66 | } 67 | -------------------------------------------------------------------------------- /x/oracle/types/codec.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 6 | cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/msgservice" 9 | "github.com/relevant-community/oracle/x/oracle/exported" 10 | ) 11 | 12 | // RegisterLegacyAminoCodec registers all the necessary types and interfaces for the 13 | // evidence module. 14 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 15 | cdc.RegisterInterface((*exported.Claim)(nil), nil) 16 | cdc.RegisterConcrete(&MsgVote{}, "oracle/MsgVote", nil) 17 | cdc.RegisterConcrete(&MsgPrevote{}, "oracle/MsgPrevote", nil) 18 | cdc.RegisterConcrete(&MsgDelegate{}, "oracle/MsgDelegateFeedConsent", nil) 19 | cdc.RegisterConcrete(&TestClaim{}, "oracle/TestClaim", nil) 20 | } 21 | 22 | // RegisterInterfaces registers interfaces 23 | func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { 24 | // this line is used by starport scaffolding # 3 25 | registry.RegisterImplementations( 26 | (*sdk.Msg)(nil), 27 | &MsgVote{}, 28 | &MsgPrevote{}, 29 | &MsgDelegate{}, 30 | ) 31 | registry.RegisterInterface( 32 | "relevantcommunity.oracle.oracle.Claim", 33 | (*exported.Claim)(nil), 34 | &TestClaim{}, 35 | ) 36 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 37 | } 38 | 39 | var ( 40 | amino = codec.NewLegacyAmino() 41 | 42 | // ModuleCdc references the global x/oracle module codec. Note, the codec should 43 | // ONLY be used in certain instances of tests and for JSON encoding as Amino is 44 | // still used for that purpose. 45 | // 46 | // The actual codec used for serialization should be provided to x/oracle and 47 | // defined at the application level. 48 | ModuleCdc = codec.NewAminoCodec(amino) 49 | ) 50 | 51 | func init() { 52 | RegisterLegacyAminoCodec(amino) 53 | cryptocodec.RegisterCrypto(amino) 54 | amino.Seal() 55 | } 56 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Relevant Oracle Module 3 | description: The documentation of the Relevant Oracle Module for the Cosmso SDK. 4 | footer: 5 | newsletter: false 6 | aside: true 7 | --- 8 | 9 | # Relevant Oracle Module 10 | 11 | The Oracle module allows validators to run arbitrary off-chain worker processes and report the results for inclusion in the on-chain state. 12 | 13 | Unlike onchain smart contracts, offchain workers are able to run non-deterministic code, like fetching exchange price data via an api call, and long-running computations, i.e. AI alogrithms or graph analysis. All of the validators are expected to run the workers and come to a consensus on the results. Exactly how consensus is reached can be decided by the app developer. 14 | 15 | The module is inspired by the [Terra Oracle Module](https://docs.terra.money/dev/spec-oracle.html#concepts) as well as a more recent iteration of it by [Sommelier](https://github.com/PeggyJV/sommelier/tree/main/x/oracle). 16 | 17 | ## Getting Started 18 | 19 | - **[The Tutoral](./tutorial)** goes through the steps required to incorporate the Oracle module into your codebase. As an example, we fetch the Atom/USDC price data from Binance and write it on-chain. 20 | 21 | - **[Module Documentation](./modules/oracle)**: Describes the high-level architecture and module apis. 22 | 23 | ## Learn More About Oracles 24 | 25 | - This [Terra Blog Post](https://medium.com/stakewithus/terra-oracle-voter-by-stakewith-us-d54a1321beb9) has a nice general overview of core concepts. 26 | - [Terra Oracle Module Docs](https://docs.terra.money/dev/spec-oracle.html#concepts) are also very insightful 27 | - A useful [talk about oracles by Chainlink's Sergey Nazarov](https://youtu.be/UAP6--JTAlU) 28 | - [Sommelier Oracle Module](https://github.com/PeggyJV/sommelier/tree/b2f81e9007db479ac5c88bf4d6edbc17a27120fc/x/oracle) 29 | 30 | ## Other Resources 31 | 32 | - **[Cosmos SDK API Reference](https://godoc.org/github.com/cosmos/cosmos-sdk)**: Godocs of the Cosmos SDK. 33 | -------------------------------------------------------------------------------- /x/oracle/keeper/msg_server_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "github.com/relevant-community/oracle/x/oracle/exported" 5 | "github.com/relevant-community/oracle/x/oracle/types" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func testMsgSubmitClaim(r *require.Assertions, claim exported.Claim, sender sdk.AccAddress) exported.MsgVoteI { 12 | msg, err := types.NewMsgVote(sender, claim, "") 13 | r.NoError(err) 14 | return msg 15 | } 16 | 17 | func (suite *KeeperTestSuite) TestMsgSubmitClaim() { 18 | nonValidator := suite.addrs[3] 19 | validator := suite.validators[0] 20 | val1 := suite.validators[1] 21 | delegator := suite.addrs[4] 22 | 23 | suite.k.SetValidatorDelegateAddress(suite.ctx, sdk.AccAddress(val1), delegator) 24 | 25 | testCases := []struct { 26 | msg sdk.Msg 27 | expectErr bool 28 | }{ 29 | { 30 | testMsgSubmitClaim( 31 | suite.Require(), 32 | types.NewTestClaim(10, "test", "test"), 33 | sdk.AccAddress(validator), 34 | ), 35 | false, 36 | }, 37 | { 38 | testMsgSubmitClaim( 39 | suite.Require(), 40 | types.NewTestClaim(11, "test", "test"), 41 | sdk.AccAddress(delegator), 42 | ), 43 | false, 44 | }, 45 | { 46 | testMsgSubmitClaim( 47 | suite.Require(), 48 | types.NewTestClaim(12, "test", "test"), 49 | nonValidator, 50 | ), 51 | true, 52 | }, 53 | } 54 | 55 | for i, tc := range testCases { 56 | ctx := suite.ctx 57 | res, err := suite.handler(ctx, tc.msg) 58 | if tc.expectErr { 59 | suite.Require().Error(err, "expected error; tc #%d", i) 60 | } else { 61 | suite.Require().NoError(err, "unexpected error; tc #%d", i) 62 | suite.Require().NotNil(res, "expected non-nil result; tc #%d", i) 63 | 64 | msg := tc.msg.(exported.MsgVoteI) 65 | 66 | var resultData types.MsgVoteResponse 67 | suite.app.AppCodec().UnmarshalBinaryBare(res.Data, &resultData) 68 | suite.Require().Equal(msg.GetClaim().Hash().Bytes(), resultData.Hash, "invalid hash; tc #%d", i) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /proto/oracle/tx.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relevantcommunity.oracle.oracle; 3 | 4 | option go_package = "github.com/relevant-community/oracle/x/oracle/types"; 5 | option (gogoproto.equal_all) = true; 6 | 7 | import "gogoproto/gogo.proto"; 8 | import "google/protobuf/any.proto"; 9 | import "cosmos_proto/cosmos.proto"; 10 | 11 | // Msg defines the clim Msg service. 12 | service Msg { 13 | // Vote submits an arbitrary oracle Claim 14 | rpc Vote(MsgVote) returns (MsgVoteResponse); 15 | 16 | // Prevote submits a prevote for a Claim 17 | rpc Prevote (MsgPrevote) returns (MsgPrevoteResponse); 18 | 19 | // Delegate delegates oracle claim submission permission from the validator to 20 | // an arbitrary address 21 | rpc Delegate (MsgDelegate) returns (MsgDelegateResponse); 22 | } 23 | 24 | 25 | // MsgVote represents a message that supports submitting a vote for 26 | // an arbitrary oracle Claim. 27 | message MsgVote { 28 | option (gogoproto.equal) = false; 29 | option (gogoproto.goproto_getters) = false; 30 | 31 | string signer = 1; 32 | google.protobuf.Any claim = 2 [(cosmos_proto.accepts_interface) = "Claim"]; 33 | string salt = 3; 34 | } 35 | 36 | // MsgVoteResponse defines the Msg/SubmitEvidence response type. 37 | message MsgVoteResponse { 38 | // hash defines the hash of the evidence. 39 | bytes hash = 4; 40 | } 41 | 42 | // MsgPrevote - sdk.Msg for prevoting on an array of oracle claim types. 43 | // The purpose of the prevote is to hide vote for data with hashes formatted as hex string: 44 | // SHA256("{salt}:{data_cannonical_json}:{voter}") 45 | message MsgPrevote { 46 | bytes hash = 1; 47 | string signer = 2; 48 | } 49 | 50 | message MsgPrevoteResponse {} 51 | 52 | 53 | // MsgDelegate - sdk.Msg for delegating oracle voting rights from a validator 54 | // to another address, must be signed by an active validator 55 | message MsgDelegate { 56 | string delegate = 1; 57 | string validator = 2; 58 | } 59 | 60 | // MsgDelegateResponse delegation response 61 | message MsgDelegateResponse {} -------------------------------------------------------------------------------- /cmd/oracled/cmd/worker/handlers.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/relevant-community/oracle/x/atom/types" 9 | "github.com/relevant-community/oracle/x/oracle/client/cli" 10 | oracletypes "github.com/relevant-community/oracle/x/oracle/types" 11 | rpctypes "github.com/tendermint/tendermint/rpc/core/types" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | type AtomData struct { 17 | Symbol string `json:"symbol"` 18 | Price string `json:"price"` 19 | } 20 | 21 | // HandleTx is our custom tx handler 22 | func HandleTx(cmd *cobra.Command, txEvent rpctypes.ResultEvent) error { 23 | return nil 24 | } 25 | 26 | // HandleBlock is our custom block handler 27 | func HandleBlock(cmd *cobra.Command, blockEvent rpctypes.ResultEvent) error { 28 | helper, err := cli.NewWorkerHelper(cmd, blockEvent) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | // for each claim type in the array, run the approriate handler 34 | for _, param := range helper.OracleParams.ClaimParams { 35 | switch param.ClaimType { 36 | case types.AtomClaim: 37 | getAtomPrice(cmd, helper, param) 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func getAtomPrice(cmd *cobra.Command, helper *cli.WorkerHelper, param oracletypes.ClaimParams) error { 45 | 46 | // use VotePeriod to check if its time to submit a claim 47 | if helper.IsRoundStart(param.ClaimType) == false { 48 | return nil 49 | } 50 | 51 | // fetch ATOM/USDC pair price from Binance 52 | var atomData = AtomData{} 53 | r, err := http.Get("https://api.binance.com/api/v3/ticker/price?symbol=ATOMUSDC") 54 | if err != nil { 55 | fmt.Println("Error fetching ATOM price") 56 | return err 57 | } 58 | defer r.Body.Close() 59 | json.NewDecoder(r.Body).Decode(&atomData) 60 | 61 | // create a Claim about AtomUSD price 62 | atomUsd := types.NewAtomUsd(helper.BlockHeight, atomData.Price) 63 | 64 | // run process for the given claimType 65 | if atomUsd == nil { 66 | fmt.Println("Error creating claim") 67 | return nil 68 | } 69 | 70 | // submit our claim 71 | helper.SubmitWorkerTx(atomUsd) 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /x/atom/keeper/atom.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/relevant-community/oracle/x/atom/types" 9 | ) 10 | 11 | func (k Keeper) SetAtomUsd(ctx sdk.Context, atomUsd types.AtomUsd) { 12 | store := ctx.KVStore(k.storeKey) 13 | b := k.cdc.MustMarshalBinaryBare(&atomUsd) 14 | store.Set(types.AtomUsdKey, b) 15 | } 16 | 17 | func (k Keeper) GetAtomUsd(ctx sdk.Context) *types.AtomUsd { 18 | store := ctx.KVStore(k.storeKey) 19 | bz := store.Get(types.AtomUsdKey) 20 | if len(bz) == 0 { 21 | return nil 22 | } 23 | 24 | var atomUsd types.AtomUsd 25 | k.cdc.MustUnmarshalBinaryBare(bz, &atomUsd) 26 | return &atomUsd 27 | } 28 | 29 | func (k Keeper) UpdateAtomUsd(ctx sdk.Context) { 30 | claimType := types.AtomClaim 31 | pending := k.oracleKeeper.GetPendingRounds(ctx, claimType) 32 | 33 | // sort pending rounds in ascending order 34 | sort.SliceStable(pending, func(i, j int) bool { return pending[i] < pending[j] }) 35 | 36 | for _, roundID := range pending { 37 | result := k.oracleKeeper.TallyVotes(ctx, claimType, roundID) 38 | 39 | if result == nil || result.Claims[0] == nil { 40 | continue 41 | } 42 | 43 | // take an average of all claims and commit to chain 44 | avgAtomUsd := sdk.NewDec(0) 45 | var blockHeight int64 46 | var totalVotePower int64 47 | for _, claimResult := range result.Claims { 48 | claimHash := claimResult.ClaimHash 49 | atomClaim, ok := k.oracleKeeper.GetClaim(ctx, claimHash).(*types.AtomUsd) 50 | 51 | if ok == false { 52 | fmt.Printf("Error retrieving claim") 53 | continue 54 | } 55 | weightedAvg := avgAtomUsd.Mul(sdk.NewDec(totalVotePower)) 56 | weightedVote := atomClaim.Price.Mul(sdk.NewDec(result.VotePower)) 57 | totalVotePower += result.VotePower 58 | avgAtomUsd = weightedAvg.Add(weightedVote).Quo(sdk.NewDec(totalVotePower)) 59 | blockHeight = atomClaim.BlockHeight 60 | } 61 | 62 | atomUsd := &types.AtomUsd{ 63 | Price: avgAtomUsd, 64 | BlockHeight: blockHeight, 65 | } 66 | k.SetAtomUsd(ctx, *atomUsd) 67 | 68 | // TODO delete the any earlier pending rounds 69 | k.oracleKeeper.FinalizeRound(ctx, claimType, roundID) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /x/oracle/testoracle/helper.go: -------------------------------------------------------------------------------- 1 | package testoracle 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cosmos/cosmos-sdk/simapp" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/x/staking" 9 | stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" 10 | "github.com/cosmos/cosmos-sdk/x/staking/teststaking" 11 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 12 | oracleapp "github.com/relevant-community/oracle/app" 13 | "github.com/relevant-community/oracle/x/oracle/keeper" 14 | "github.com/relevant-community/oracle/x/oracle/types" 15 | ) 16 | 17 | // AddClaimType Registers claimType as an orcale params 18 | func AddClaimType(ctx sdk.Context, k keeper.Keeper, claimType string) { 19 | params := types.DefaultParams() 20 | params.ClaimParams = map[string](types.ClaimParams){ 21 | claimType: { 22 | ClaimType: claimType, 23 | }, 24 | } 25 | k.SetParams(ctx, params) 26 | } 27 | 28 | // CreateTestInput Returns a simapp with custom OracleKeeper 29 | // to avoid messing with the hooks. 30 | func CreateTestInput() (*oracleapp.App, sdk.Context) { 31 | return oracleapp.CreateTestInput() 32 | } 33 | 34 | // CreateValidators intializes validators 35 | func CreateValidators(t *testing.T, ctx sdk.Context, app *oracleapp.App, powers []int64) ([]sdk.AccAddress, []sdk.ValAddress, []stakingtypes.ValidatorI) { 36 | addrs := oracleapp.AddTestAddrsIncremental(app, ctx, 5, sdk.NewInt(30000000)) 37 | valAddrs := simapp.ConvertAddrsToValAddrs(addrs) 38 | pks := simapp.CreateTestPubKeys(5) 39 | 40 | stakingHelper := teststaking.NewHelper(t, ctx, app.StakingKeeper) 41 | 42 | appCodec, _ := simapp.MakeCodecs() 43 | 44 | app.StakingKeeper = stakingkeeper.NewKeeper( 45 | appCodec, 46 | app.GetKey(stakingtypes.StoreKey), 47 | app.AccountKeeper, 48 | app.BankKeeper, 49 | app.GetSubspace(stakingtypes.ModuleName), 50 | ) 51 | 52 | var vals []stakingtypes.ValidatorI 53 | for i, power := range powers { 54 | stakingHelper.CreateValidatorWithValPower(valAddrs[i], pks[i], power, true) 55 | val := app.StakingKeeper.Validator(ctx, valAddrs[i]) 56 | vals = append(vals, val) 57 | } 58 | 59 | _ = staking.EndBlocker(ctx, app.StakingKeeper) 60 | return addrs, valAddrs, vals 61 | } 62 | -------------------------------------------------------------------------------- /x/oracle/keeper/keeper_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/relevant-community/oracle/app" 7 | "github.com/relevant-community/oracle/x/oracle" 8 | "github.com/relevant-community/oracle/x/oracle/exported" 9 | "github.com/relevant-community/oracle/x/oracle/keeper" 10 | "github.com/relevant-community/oracle/x/oracle/testoracle" 11 | "github.com/relevant-community/oracle/x/oracle/types" 12 | 13 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 14 | 15 | "github.com/cosmos/cosmos-sdk/baseapp" 16 | sdk "github.com/cosmos/cosmos-sdk/types" 17 | "github.com/stretchr/testify/suite" 18 | ) 19 | 20 | type KeeperTestSuite struct { 21 | suite.Suite 22 | 23 | ctx sdk.Context 24 | app *app.App 25 | 26 | queryClient types.QueryClient 27 | querier sdk.Querier 28 | 29 | validators []sdk.ValAddress 30 | pow []int64 31 | k keeper.Keeper 32 | handler sdk.Handler 33 | addrs []sdk.AccAddress 34 | } 35 | 36 | func (suite *KeeperTestSuite) SetupTest() { 37 | checkTx := false 38 | app, ctx := testoracle.CreateTestInput() 39 | // cdc := app.LegacyAmino() 40 | 41 | powers := []int64{10, 10, 10} 42 | addrs, validators, _ := testoracle.CreateValidators(suite.T(), ctx, app, powers) 43 | 44 | suite.addrs = addrs 45 | suite.validators = validators 46 | suite.pow = powers 47 | suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1}) 48 | suite.k = app.OracleKeeper 49 | 50 | suite.app = app 51 | 52 | querier := keeper.Querier{Keeper: app.OracleKeeper} 53 | queryHelper := baseapp.NewQueryServerTestHelper(ctx, app.InterfaceRegistry()) 54 | types.RegisterQueryServer(queryHelper, querier) 55 | 56 | suite.queryClient = types.NewQueryClient(queryHelper) 57 | suite.handler = oracle.NewHandler(app.OracleKeeper) 58 | 59 | } 60 | 61 | func (suite *KeeperTestSuite) populateClaims(ctx sdk.Context, numClaims int) []exported.Claim { 62 | claims := make([]exported.Claim, numClaims) 63 | for i := 0; i < numClaims; i++ { 64 | claims[i] = types.NewTestClaim(int64(i), "test", "test") 65 | suite.k.CreateClaim(ctx, claims[i]) 66 | } 67 | return claims 68 | } 69 | 70 | func TestKeeperTestSuite(t *testing.T) { 71 | suite.Run(t, new(KeeperTestSuite)) 72 | } 73 | -------------------------------------------------------------------------------- /x/oracle/client/cli/query_test.go: -------------------------------------------------------------------------------- 1 | package cli_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/relevant-community/oracle/app" 8 | "github.com/relevant-community/oracle/x/oracle/client/cli" 9 | "github.com/spf13/cobra" 10 | "github.com/stretchr/testify/suite" 11 | 12 | clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" 13 | testnet "github.com/cosmos/cosmos-sdk/testutil/network" 14 | ) 15 | 16 | type IntegrationTestSuite struct { 17 | suite.Suite 18 | 19 | cfg testnet.Config 20 | network *testnet.Network 21 | } 22 | 23 | func (s *IntegrationTestSuite) SetupSuite() { 24 | s.T().Log("setting up integration test suite") 25 | 26 | cfg := app.DefaultConfig() 27 | cfg.NumValidators = 1 28 | 29 | s.cfg = cfg 30 | s.network = testnet.New(s.T(), cfg) 31 | 32 | _, err := s.network.WaitForHeight(1) 33 | s.Require().NoError(err) 34 | } 35 | 36 | func (s *IntegrationTestSuite) TearDownSuite() { 37 | s.T().Log("tearing down integration test suite") 38 | s.network.Cleanup() 39 | } 40 | 41 | func TestIntegrationTestSuite(t *testing.T) { 42 | suite.Run(t, new(IntegrationTestSuite)) 43 | } 44 | 45 | func (s *IntegrationTestSuite) TestGetQueryCmd() { 46 | val := s.network.Validators[0] 47 | 48 | testCases := map[string]struct { 49 | cmd *cobra.Command 50 | args []string 51 | expectedOutput string 52 | expectErr bool 53 | }{ 54 | "non-existent claim": { 55 | cli.CmdClaim(), 56 | []string{"DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"}, 57 | "claim DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660 not found", 58 | true, 59 | }, 60 | "list-claim (default pagination)": { 61 | cli.CmdAllClaims(), 62 | []string{}, 63 | "claims: []\npagination:\n next_key: null\n total: \"0\"", 64 | false, 65 | }, 66 | } 67 | 68 | for name, tc := range testCases { 69 | tc := tc 70 | 71 | s.Run(name, func() { 72 | clientCtx := val.ClientCtx 73 | 74 | out, err := clitestutil.ExecTestCLICmd(clientCtx, tc.cmd, tc.args) 75 | if tc.expectErr { 76 | s.Require().Error(err) 77 | } else { 78 | s.Require().NoError(err) 79 | } 80 | 81 | s.Require().Contains(strings.TrimSpace(out.String()), tc.expectedOutput) 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /x/oracle/genesis.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/relevant-community/oracle/x/oracle/exported" 8 | "github.com/relevant-community/oracle/x/oracle/keeper" 9 | "github.com/relevant-community/oracle/x/oracle/types" 10 | ) 11 | 12 | // InitGenesis initializes the capability module's state from a provided genesis 13 | // state. 14 | func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { 15 | if err := genState.Validate(); err != nil { 16 | panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err)) 17 | } 18 | 19 | k.SetParams(ctx, genState.Params) 20 | 21 | for _, round := range genState.Rounds { 22 | k.CreateRound(ctx, round) 23 | } 24 | 25 | for _, c := range genState.Claims { 26 | claim, ok := c.GetCachedValue().(exported.Claim) 27 | if !ok { 28 | panic("expected claim") 29 | } 30 | if cliamExists := k.GetClaim(ctx, claim.Hash()); cliamExists != nil { 31 | panic(fmt.Sprintf("claim with hash %s already exists", claim.Hash())) 32 | } 33 | 34 | k.CreateClaim(ctx, claim) 35 | } 36 | 37 | for claimType, pendingRounds := range genState.Pending { 38 | for _, roundID := range pendingRounds.Pending { 39 | k.AddPendingRound(ctx, claimType, roundID) 40 | } 41 | } 42 | 43 | for _, delegation := range genState.Delegations { 44 | k.SetValidatorDelegateAddress(ctx, delegation.MustGetValidator(), delegation.MustGetDelegate()) 45 | } 46 | 47 | for _, prevote := range genState.Prevotes { 48 | k.CreatePrevote(ctx, prevote) 49 | } 50 | 51 | for claimType, roundID := range genState.FinalizedRounds { 52 | k.SetLastFinalizedRound(ctx, claimType, roundID) 53 | } 54 | } 55 | 56 | // ExportGenesis returns the capability module's exported genesis. 57 | func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { 58 | params := k.GetParams(ctx) 59 | rounds := k.GetAllRounds(ctx) 60 | claims := k.GetAllClaims(ctx) 61 | pending := k.GetAllPendingRounds(ctx) 62 | delegations := k.GetAllDelegations(ctx) 63 | prevotes := k.GetAllPrevotes(ctx) 64 | finalizedRounds := k.GetAllFinalizedRounds(ctx) 65 | 66 | return types.NewGenesisState(params, rounds, claims, pending, delegations, prevotes, finalizedRounds) 67 | } 68 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon-svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /x/oracle/types/keys.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "strconv" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 8 | ) 9 | 10 | // Keys for oracle store, with -> 11 | const ( 12 | // ModuleName defines the module name 13 | ModuleName = "oracle" 14 | 15 | // StoreKey defines the primary module store key 16 | StoreKey = ModuleName 17 | 18 | // RouterKey is the message route for slashing 19 | RouterKey = ModuleName 20 | 21 | // QuerierRoute defines the module's query routing key 22 | QuerierRoute = ModuleName 23 | 24 | // MemStoreKey defines the in-memory store key 25 | MemStoreKey = "mem_capability" 26 | ) 27 | 28 | // Keys for oracle store, with -> 29 | var ( 30 | // 0x00 | claim_hash -> claim 31 | ClaimKey = []byte{0x00} 32 | 33 | // 0x01 | claimType | roundId -> round 34 | RoundKey = []byte{0x01} 35 | 36 | // 0x02 | claimType | roundId -> roundId 37 | PendingRoundKey = []byte{0x02} 38 | 39 | // - 0x03 | prevote_hash -> prevote_hash 40 | PrevoteKey = []byte{0x03} 41 | 42 | // - 0x04 | del_address -> val_address 43 | DelValKey = []byte{0x05} // key for validator feed delegation 44 | 45 | // - 0x05 | val_address -> del_address 46 | ValDelKey = []byte{0x06} // key for validator feed delegation 47 | 48 | // - 0x06 | claimType -> roundId 49 | FinalizedRoundKey = []byte{0x07} // key for validator feed delegation 50 | ) 51 | 52 | // KeyPrefix helper 53 | func KeyPrefix(p string) []byte { 54 | return []byte(p) 55 | } 56 | 57 | // GetDelValKey returns the validator for a given delegate key 58 | func GetDelValKey(del sdk.AccAddress) []byte { 59 | return append(DelValKey, del.Bytes()...) 60 | } 61 | 62 | // GetValDelKey returns the validator for a given delegate key 63 | func GetValDelKey(val sdk.AccAddress) []byte { 64 | return append(ValDelKey, val.Bytes()...) 65 | } 66 | 67 | // GetClaimPrevoteKey returns the key for a validators prevote 68 | func GetClaimPrevoteKey(hash tmbytes.HexBytes) []byte { 69 | return append(PrevoteKey, hash...) 70 | } 71 | 72 | // GetRoundKey helper 73 | func GetRoundKey(claimType string, roundID uint64) string { 74 | return claimType + strconv.FormatUint(roundID, 10) 75 | } 76 | 77 | // RoundPrefix helper 78 | func RoundPrefix(claimType string, roundID uint64) []byte { 79 | return KeyPrefix(GetRoundKey(claimType, roundID)) 80 | } 81 | -------------------------------------------------------------------------------- /x/oracle/spec/03_messages.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Messages 6 | 7 | ## MsgDelegate 8 | 9 | Validators may also elect to delegate voting rights to another key as to not require the validator operator key to be kept online. To do so, validators must submit a `MsgDelegateFeedConsent`, delegating their oracle voting rights to a `Delegate` that can sign `MsgExchangeRatePrevote` and `MsgExchangeRateVote` on behalf of the validator. 10 | 11 | The `Validator` field contains the operator address of the validator (prefixed `cosmos1...`). The `Delegate` field is the account address (prefixed `cosmos1...`) of the delegate account that will be submitting exchange rate related votes and prevotes on behalf of the `Operator`. 12 | 13 | ```protobuf 14 | // MsgDelegate - sdk.Msg for delegating oracle voting rights from a validator 15 | // to another address, must be signed by an active validator 16 | message MsgDelegate { 17 | string delegate = 1; 18 | string validator = 2; 19 | } 20 | ``` 21 | 22 | ## MsgPrevote 23 | 24 | `hashe` is the SHA256 hash of a string of the format `{salt}:{claim_hash}:{signer}`. This is a commitment to the actual `MsgVote` a validator will submit in the subsequent `VotePeriod`. You can use the `oracletypes.DataHash(salt string, jsn string, signer sdk.AccAddress)` function to help encode it. Note that since in the subsequent `MsgVote`, the salt for the prevote will have to be revealed. Salt used must be regenerated for each prevote submission. 25 | 26 | ```protobuf 27 | // MsgPrevote - sdk.Msg for prevoting on an array of oracle data types. 28 | // The purpose of the prevote is to hide vote for data with hashes formatted as hex string: 29 | // SHA256("{salt}:{claim_hash}:{voter}") 30 | message MsgPrevote { 31 | bytes hash = 1; 32 | string signer = 2; 33 | } 34 | ``` 35 | 36 | ## MsgVote 37 | 38 | The `MsgVote` contains a concrete `Claim`. The `salt` parameter must match the salt used to create the prevote, otherwise the vote cannot be processed. 39 | 40 | ```protobuf 41 | // MsgVote represents a message that supports submitting a vote for 42 | // an arbitrary oracle Claim. 43 | message MsgVote { 44 | option (gogoproto.equal) = false; 45 | option (gogoproto.goproto_getters) = false; 46 | 47 | string signer = 1; 48 | google.protobuf.Any claim = 2 [(cosmos_proto.accepts_interface) = "Claim"]; 49 | string salt = 3; 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /x/oracle/types/params.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" 8 | ) 9 | 10 | // Parameter keys 11 | var ( 12 | KeyClaimParams = []byte("claimParams") 13 | ) 14 | 15 | // Default params for testing 16 | var ( 17 | TestClaimType = "test" 18 | TestPrevoteClaimType = "prevoteTest" 19 | TestVotePeriod uint64 = 3 20 | ) 21 | 22 | // Default parameter values 23 | var ( 24 | DefaultVoteThreshold = sdk.NewDecWithPrec(50, 2) // 50% 25 | DefaultClaimParams = map[string](ClaimParams){ 26 | TestClaimType: { 27 | ClaimType: TestClaimType, 28 | VoteThreshold: DefaultVoteThreshold, 29 | VotePeriod: 1, 30 | }, 31 | TestPrevoteClaimType: { 32 | ClaimType: TestPrevoteClaimType, 33 | Prevote: true, 34 | VotePeriod: TestVotePeriod, 35 | VoteThreshold: DefaultVoteThreshold, 36 | }, 37 | } 38 | ) 39 | 40 | var _ paramtypes.ParamSet = (*Params)(nil) 41 | 42 | // ParamKeyTable for oracle module 43 | func ParamKeyTable() paramtypes.KeyTable { 44 | return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) 45 | } 46 | 47 | // DefaultParams creates default oracle module parameters 48 | func DefaultParams() Params { 49 | return Params{ 50 | ClaimParams: DefaultClaimParams, 51 | } 52 | } 53 | 54 | // ParamSetPairs returns the parameter set pairs. 55 | func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { 56 | return paramtypes.ParamSetPairs{ 57 | paramtypes.NewParamSetPair(KeyClaimParams, &p.ClaimParams, validateClaimParams), 58 | } 59 | } 60 | 61 | // ValidateBasic performs basic validation on oracle parameters. 62 | func (p Params) ValidateBasic() error { 63 | return nil 64 | } 65 | 66 | func validateClaimParams(i interface{}) error { 67 | claimParams, ok := i.(map[string](ClaimParams)) 68 | if !ok { 69 | return fmt.Errorf("invalid parameter type: %T", i) 70 | } 71 | 72 | for _, param := range claimParams { 73 | if param.VotePeriod <= 0 { 74 | return fmt.Errorf("vote period must be greater than 0: %d", param.VotePeriod) 75 | } 76 | if param.VoteThreshold.LTE(sdk.NewDecWithPrec(33, 2)) { 77 | return fmt.Errorf("oracle parameter VoteTheshold must be greater than 33 percent") 78 | } 79 | if param.VoteThreshold.GT(sdk.OneDec()) { 80 | return fmt.Errorf("vote threshold too large: %s", param.VoteThreshold) 81 | } 82 | } 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Cosmos Oracle 2 | 3 | The Oracle module allows validators to run arbitrary off-chain worker processes and report the results for inclusion in the on-chain state. 4 | 5 | Unlike onchain smart contracts, offchain workers are able to run non-deterministic code, like fetching exchange price data via an api call, and long-running computations, i.e. AI alogrithms or graph analysis. All of the validators are expected to run the workers and come to a consensus on the results. Exactly how consensus is reached can be decided by the app developer. 6 | 7 | The module supports running an arbitrary amount of different oracles simultaniously. 8 | 9 | The module is inspired by the [Terra Oracle Module](https://docs.terra.money/dev/spec-oracle.html#concepts) as well as a more recent iteration of it by [Sommelier](https://github.com/PeggyJV/sommelier/tree/main/x/oracle). 10 | 11 | **oracle** is a blockchain built using Cosmos SDK and Tendermint and created with [Starport](https://github.com/tendermint/starport). 12 | 13 | ## Getting Started 14 | 15 | - Tutorial: https://relevant-community.github.io/oracle/tutorial/ 16 | - Documentation: https://relevant-community.github.io/oracle/ 17 | 18 | Run the demo app that fetches ATOM/USD price: 19 | 20 | Clone the repo into your local folder and run: 21 | 22 | ``` 23 | $ starport serve 24 | ``` 25 | 26 | In a new browser window, star the oracle worker process: 27 | 28 | ``` 29 | $ oracled tx oracle start-worker --from alice -o text -y 30 | ``` 31 | 32 | After some time, check the on-chain Atom-USD price: 33 | 34 | ``` 35 | $ oracled query atom atomUsd 36 | ``` 37 | 38 | You should see the latest Atom/USD price and the blockHeight at which it was captured. 39 | 40 | --- 41 | 42 | ## Starport Docs: 43 | 44 | ``` 45 | starport serve 46 | ``` 47 | 48 | `serve` command installs dependencies, builds, initializes and starts your blockchain in development. 49 | 50 | ## Configure 51 | 52 | Your blockchain in development can be configured with `config.yml`. To learn more see the [reference](https://github.com/tendermint/starport#documentation). 53 | 54 | ## Launch 55 | 56 | To launch your blockchain live on mutliple nodes use `starport network` commands. Learn more about [Starport Network](https://github.com/tendermint/spn). 57 | 58 | ## Learn more 59 | 60 | - [Starport](https://github.com/tendermint/starport) 61 | - [Cosmos SDK documentation](https://docs.cosmos.network) 62 | - [Cosmos SDK Tutorials](https://tutorials.cosmos.network) 63 | - [Discord](https://discord.gg/W8trcGV) 64 | -------------------------------------------------------------------------------- /x/oracle/keeper/delegation.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "bytes" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/relevant-community/oracle/x/oracle/types" 8 | ) 9 | 10 | // SetValidatorDelegateAddress sets a new address that will have the power to send data on behalf of the validator 11 | // we store the mapping in both directions for easy lookup 12 | func (k Keeper) SetValidatorDelegateAddress(ctx sdk.Context, val, del sdk.AccAddress) { 13 | if val.String() == del.String() { 14 | ctx.KVStore(k.storeKey).Delete(types.GetDelValKey(del)) 15 | ctx.KVStore(k.storeKey).Delete(types.GetValDelKey(val)) 16 | } else { 17 | ctx.KVStore(k.storeKey).Set(types.GetDelValKey(del), val.Bytes()) 18 | ctx.KVStore(k.storeKey).Set(types.GetValDelKey(val), del.Bytes()) 19 | } 20 | } 21 | 22 | // GetValidatorAddressFromDelegate returns the delegate address for a given validator 23 | func (k Keeper) GetValidatorAddressFromDelegate(ctx sdk.Context, del sdk.AccAddress) sdk.AccAddress { 24 | return sdk.AccAddress(ctx.KVStore(k.storeKey).Get(types.GetDelValKey(del))) 25 | } 26 | 27 | // GetDelegateAddressFromValidator returns the validator address for a given delegate 28 | func (k Keeper) GetDelegateAddressFromValidator(ctx sdk.Context, val sdk.AccAddress) sdk.AccAddress { 29 | return sdk.AccAddress(ctx.KVStore(k.storeKey).Get(types.GetValDelKey(val))) 30 | } 31 | 32 | // IsDelegateAddress returns true if the validator has delegated their feed to an address 33 | func (k Keeper) IsDelegateAddress(ctx sdk.Context, del sdk.AccAddress) bool { 34 | return ctx.KVStore(k.storeKey).Has(types.GetDelValKey(del)) 35 | } 36 | 37 | // GetAllDelegations return all delegations 38 | func (k Keeper) GetAllDelegations(ctx sdk.Context) []types.MsgDelegate { 39 | delegations := []types.MsgDelegate{} 40 | k.IterateDelegations(ctx, func(del sdk.AccAddress, val sdk.AccAddress) (stop bool) { 41 | delegations = append(delegations, *types.NewMsgDelegate(val, del)) 42 | return 43 | }) 44 | return delegations 45 | } 46 | 47 | // IterateDelegations iterates over all delegate address pairs in the store 48 | func (k Keeper) IterateDelegations(ctx sdk.Context, handler func(del, val sdk.AccAddress) (stop bool)) { 49 | store := ctx.KVStore(k.storeKey) 50 | iter := sdk.KVStorePrefixIterator(store, types.DelValKey) 51 | defer iter.Close() 52 | for ; iter.Valid(); iter.Next() { 53 | del := sdk.AccAddress(bytes.TrimPrefix(iter.Key(), types.DelValKey)) 54 | val := sdk.AccAddress(iter.Value()) 55 | if handler(del, val) { 56 | break 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /x/oracle/spec/02_state.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # State 6 | 7 | ## Claim 8 | 9 | A `Claim` is an abstract type that the result of an off-chain worker process must implement. A `Claim` must have a `Type`. You must specify a list of `ClaimTypes` and their associate the module must proceess via the oracle module's `ClaimParams` param map. 10 | 11 | - `0x00 | claimHash -> ProtocolBuffer(claim)` 12 | 13 | ```go 14 | // Claim defines the methods that all oracle claims must implement 15 | type Claim interface { 16 | proto.Message 17 | 18 | Type() string 19 | Hash() tmbytes.HexBytes 20 | GetRoundID() uint64 21 | // GetConcensusKey returns a key the oracle will use fore voting consensus 22 | // for deterministic results it should be the the hash of the content 23 | // (this is because we expect all validators to submit claims with the same exact content) 24 | // however for nondeterministic content it should be a constant because voting doesn't 25 | // depend on the content of the claim (developers will need to use the results of the voting 26 | // to reconcile the various claims) 27 | GetConcensusKey() string 28 | ValidateBasic() error 29 | } 30 | ``` 31 | 32 | ## Round 33 | 34 | A round contains all the validator votes for the given round and claim type. 35 | 36 | - `0x01 | claimType | roundID -> round` 37 | 38 | ```go 39 | // Round contains all claim votes for a given round 40 | message Round { 41 | uint64 roundId = 1; 42 | string type = 2; // namespace so we can have multiple oracles 43 | repeated Vote votes = 3 [(gogoproto.nullable) = false]; 44 | } 45 | 46 | // Vote is a vote for a given claim by a validator 47 | message Vote { 48 | uint64 roundId = 1; // use int in case we need to enforce sequential ordering 49 | bytes claimHash = 2 [(gogoproto.casttype) = "github.com/tendermint/tendermint/libs/bytes.HexBytes"]; 50 | string consensusId = 3; // used to compare claims when tallying votes 51 | string type = 4; 52 | bytes validator = 5 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress"]; 53 | } 54 | ``` 55 | 56 | ## PendingRounds 57 | 58 | An array of rounds that have not yet reached consensus 59 | 60 | - `0x02 | claimType | roundId -> roundID` 61 | - `0x02 | claimType -> []roundID` 62 | 63 | ## Prevote 64 | 65 | A hash of the prevote 66 | 67 | - `0x03 | prevote_hash -> prevote_hash` 68 | 69 | ## Delegate 70 | 71 | Delegation mapping 72 | 73 | - `0x04 | validator_address -> delegate_address` 74 | - `0x05 | delegate_address -> validator_address` 75 | 76 | ## LastFinalizedRound 77 | 78 | The most recent round that has reached consensus. Votes with RoundID lower than LastFinalizedRound are not processed 79 | 80 | - `0x06 | claimType -> roundID` 81 | -------------------------------------------------------------------------------- /x/oracle/client/cli/delegation_test.go: -------------------------------------------------------------------------------- 1 | package cli_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmos/cosmos-sdk/client/flags" 7 | clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/relevant-community/oracle/x/oracle/client/cli" 10 | "github.com/relevant-community/oracle/x/oracle/types" 11 | ) 12 | 13 | func (s *IntegrationTestSuite) TestDelegationCmd() { 14 | val := s.network.Validators[0] 15 | 16 | del, err := sdk.AccAddressFromBech32("cosmos1cxlt8kznps92fwu3j6npahx4mjfutydyene2qw") 17 | s.Require().NoError(err) 18 | 19 | clientCtx := val.ClientCtx.WithNodeURI(val.RPCAddress) 20 | clientCtx.OutputFormat = "json" 21 | 22 | args := []string{ 23 | del.String(), 24 | fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), 25 | fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), 26 | fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), 27 | fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), 28 | } 29 | 30 | out, err := clitestutil.ExecTestCLICmd(clientCtx, cli.TxDelegate(), args) 31 | s.Require().NoError(err) 32 | 33 | txRes := &sdk.TxResponse{} 34 | s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), txRes), out.String()) 35 | s.Require().Equal(uint32(0), txRes.Code) 36 | 37 | args = []string{ 38 | fmt.Sprintf("%s", val.Address.String()), 39 | } 40 | 41 | out, err = clitestutil.ExecTestCLICmd(clientCtx, cli.CmdDelegeateAddress(), args) 42 | delRes := &types.QueryDelegeateAddressResponse{} 43 | s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), delRes), out.String()) 44 | s.Require().Equal(del.String(), delRes.Delegate) 45 | 46 | args = []string{ 47 | fmt.Sprintf("%s", delRes.Delegate), 48 | } 49 | 50 | out, err = clitestutil.ExecTestCLICmd(clientCtx, cli.CmdValidatorAddress(), args) 51 | valRes := &types.QueryValidatorAddressResponse{} 52 | s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), valRes), out.String()) 53 | 54 | s.Require().Equal(val.Address.String(), valRes.Validator) 55 | 56 | // undo delegation 57 | args = []string{ 58 | val.Address.String(), 59 | fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), 60 | fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), 61 | fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), 62 | fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), 63 | } 64 | 65 | out, err = clitestutil.ExecTestCLICmd(clientCtx, cli.TxDelegate(), args) 66 | s.Require().NoError(err) 67 | 68 | txRes = &sdk.TxResponse{} 69 | s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), txRes), out.String()) 70 | s.Require().Equal(uint32(0), txRes.Code) 71 | 72 | // test undo delegation 73 | args = []string{ 74 | fmt.Sprintf("%s", val.Address.String()), 75 | } 76 | out, err = clitestutil.ExecTestCLICmd(clientCtx, cli.CmdDelegeateAddress(), args) 77 | s.Require().Contains(out.String(), "NotFound") 78 | } 79 | -------------------------------------------------------------------------------- /x/oracle/types/MsgVote.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | fmt "fmt" 5 | 6 | types "github.com/cosmos/cosmos-sdk/codec/types" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | proto "github.com/gogo/protobuf/proto" 10 | "github.com/relevant-community/oracle/x/oracle/exported" 11 | ) 12 | 13 | // Message types for the oracle module 14 | const ( 15 | TypeMsgVote = "vote" 16 | ) 17 | 18 | var ( 19 | _ sdk.Msg = &MsgVote{} 20 | _ types.UnpackInterfacesMessage = MsgVote{} 21 | _ exported.MsgVoteI = &MsgVote{} 22 | ) 23 | 24 | // NewMsgVote returns a new MsgVote with a signer/submitter. 25 | func NewMsgVote(s sdk.AccAddress, claim exported.Claim, salt string) (*MsgVote, error) { 26 | msg, ok := claim.(proto.Message) 27 | if !ok { 28 | return nil, fmt.Errorf("cannot proto marshal %T", claim) 29 | } 30 | any, err := types.NewAnyWithValue(msg) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return &MsgVote{Signer: s.String(), Claim: any, Salt: salt}, nil 35 | } 36 | 37 | // Route get msg route 38 | func (msg *MsgVote) Route() string { 39 | return RouterKey 40 | } 41 | 42 | // Type get msg type 43 | func (msg *MsgVote) Type() string { 44 | return TypeMsgVote 45 | } 46 | 47 | // GetSigners get msg signers 48 | func (msg *MsgVote) GetSigners() []sdk.AccAddress { 49 | accAddr, err := sdk.AccAddressFromBech32(msg.Signer) 50 | if err != nil { 51 | return nil 52 | } 53 | 54 | return []sdk.AccAddress{accAddr} 55 | } 56 | 57 | // GetSignBytes get msg get getsingbytes 58 | func (msg *MsgVote) GetSignBytes() []byte { 59 | bz := ModuleCdc.MustMarshalJSON(msg) 60 | return sdk.MustSortJSON(bz) 61 | } 62 | 63 | // ValidateBasic validation 64 | func (msg *MsgVote) ValidateBasic() error { 65 | if msg.Signer == "" { 66 | return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "creator can't be empty") 67 | } 68 | claim := msg.GetClaim() 69 | if claim == nil { 70 | return sdkerrors.Wrap(ErrInvalidClaim, "missing claim") 71 | } 72 | if err := claim.ValidateBasic(); err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | 79 | // GetClaim get the claim 80 | func (msg MsgVote) GetClaim() exported.Claim { 81 | claim, ok := msg.Claim.GetCachedValue().(exported.Claim) 82 | if !ok { 83 | return nil 84 | } 85 | return claim 86 | } 87 | 88 | // GetSigner get the submitter 89 | func (msg MsgVote) GetSigner() sdk.AccAddress { 90 | accAddr, err := sdk.AccAddressFromBech32(msg.Signer) 91 | if err != nil { 92 | return nil 93 | } 94 | return accAddr 95 | } 96 | 97 | // MustGetSigner returns submitter 98 | func (msg MsgVote) MustGetSigner() sdk.AccAddress { 99 | accAddr, err := sdk.AccAddressFromBech32(msg.Signer) 100 | if err != nil { 101 | panic(err) 102 | } 103 | return accAddr 104 | } 105 | 106 | // UnpackInterfaces unpack 107 | func (msg MsgVote) UnpackInterfaces(ctx types.AnyUnpacker) error { 108 | var claim exported.Claim 109 | return ctx.UnpackAny(msg.Claim, &claim) 110 | } 111 | -------------------------------------------------------------------------------- /x/oracle/keeper/claim.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmos/cosmos-sdk/store/prefix" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/relevant-community/oracle/x/oracle/exported" 9 | "github.com/relevant-community/oracle/x/oracle/types" 10 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 11 | ) 12 | 13 | // CreateClaim sets Claim by hash in the module's KVStore. 14 | func (k Keeper) CreateClaim(ctx sdk.Context, claim exported.Claim) { 15 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ClaimKey) 16 | store.Set(claim.Hash(), k.MustMarshalClaim(claim)) 17 | } 18 | 19 | // GetClaim retrieves Claim by hash if it exists. If no Claim exists for 20 | // the given hash, (nil, false) is returned. 21 | func (k Keeper) GetClaim(ctx sdk.Context, hash tmbytes.HexBytes) exported.Claim { 22 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ClaimKey) 23 | 24 | bz := store.Get(hash) 25 | if len(bz) == 0 { 26 | return nil 27 | } 28 | 29 | return k.MustUnmarshalClaim(bz) 30 | } 31 | 32 | // GetAllClaims returns all claims 33 | func (k Keeper) GetAllClaims(ctx sdk.Context) (msgs []exported.Claim) { 34 | store := ctx.KVStore(k.storeKey) 35 | iterator := sdk.KVStorePrefixIterator(store, types.ClaimKey) 36 | 37 | defer iterator.Close() 38 | 39 | for ; iterator.Valid(); iterator.Next() { 40 | claim := k.MustUnmarshalClaim(iterator.Value()) 41 | msgs = append(msgs, claim) 42 | } 43 | 44 | return 45 | } 46 | 47 | // DeleteClaim deletes claim by hash 48 | func (k Keeper) DeleteClaim(ctx sdk.Context, hash tmbytes.HexBytes) { 49 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ClaimKey) 50 | store.Delete(hash) 51 | } 52 | 53 | // MustUnmarshalClaim attempts to decode and return an Claim object from 54 | // raw encoded bytes. It panics on error. 55 | func (k Keeper) MustUnmarshalClaim(bz []byte) exported.Claim { 56 | Claim, err := k.UnmarshalClaim(bz) 57 | if err != nil { 58 | panic(fmt.Errorf("failed to decode Claim: %w", err)) 59 | } 60 | 61 | return Claim 62 | } 63 | 64 | // MustMarshalClaim attempts to encode an Claim object and returns the 65 | // raw encoded bytes. It panics on error. 66 | func (k Keeper) MustMarshalClaim(Claim exported.Claim) []byte { 67 | bz, err := k.MarshalClaim(Claim) 68 | if err != nil { 69 | panic(fmt.Errorf("failed to encode Claim: %w", err)) 70 | } 71 | 72 | return bz 73 | } 74 | 75 | // MarshalClaim marshals a Claim interface. If the given type implements 76 | // the Marshaler interface, it is treated as a Proto-defined message and 77 | // serialized that way. Otherwise, it falls back on the internal Amino codec. 78 | func (k Keeper) MarshalClaim(claimI exported.Claim) ([]byte, error) { 79 | return k.cdc.MarshalInterface(claimI) 80 | } 81 | 82 | // UnmarshalClaim returns a Claim interface from raw encoded Claim 83 | // bytes of a Proto-based Claim type. An error is returned upon decoding 84 | // failure. 85 | func (k Keeper) UnmarshalClaim(bz []byte) (exported.Claim, error) { 86 | var claim exported.Claim 87 | if err := k.cdc.UnmarshalInterface(bz, &claim); err != nil { 88 | return nil, err 89 | } 90 | 91 | return claim, nil 92 | } 93 | -------------------------------------------------------------------------------- /x/oracle/spec/01_concepts.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Concepts 6 | 7 | ## Worker 8 | 9 | A worker is an off-chain process that is triggered by a Tendermint event. The module includes a `start-worker` tx command that will listen for Tendermint events and trigger user-defined event handlers. Before use, the worker must be initialized with the necessary event handlers. This should be done in the `/cmd/d/cmd/root.go` file. 10 | 11 | ### WorkerHelper 12 | 13 | The Oracle module provides some useful utility functions for running worker handlers and submitting results (see `/x/oracle/client/cli/workerHleper.go`). 14 | 15 | ## Claim 16 | 17 | A Claim is an abstract type that represents the result of an off-chain worker process. All claims must have a `Type`. You must specify a list of `ClaimTypes` and their settings in the oracle module's `ClaimParams` parameter. 18 | 19 | ## Vote 20 | 21 | A `Vote` is a `Claim` submitted by a validator. Votes are tallied using the validator power to determine consensus. 22 | 23 | ## Prevote 24 | 25 | ClaimType parameters can be configured to require a `Prevote`. This is highly recomended because it reduces centralization and free-rider risk in the Oracle. Whith prevote enabled, before submitting the actual claim data, the validator must submit a hash of a random salt + cliam hash + validator address. After the prevote round is over, validators submit their `Vote` wich includes the actual `Claim`. 26 | 27 | ## Round 28 | 29 | All Claims must belong to a Round. Round duration can be set via `ClaimParams`. It can be as short as 1 block, or longer. 30 | 31 | ### PendingRounds 32 | 33 | When a new Vote is cast, the associate `Round` will be added to the pending que if its not already there. 34 | 35 | ### LatestFinalizedRound 36 | 37 | Once a round is finalized (consensus is reached), `LatestFinalizedRound` is updated with the roundId. Claims for with a `RoundId` lower than the `LatestFinalizedRound` are not accepted. 38 | 39 | ## Tallying Votes 40 | 41 | Apps relying on the Oracle will most likely want to tally the oracle votes. This can be done in some module's `EndBlock` method. The Oracle Module provides a helper method for tallying the votes that returns the `RoundResult` struct once the required vote threshold has been met. 42 | 43 | ## HouseKeeping 44 | 45 | It is the responsibility of the app using the oracle module to remove stale oracle data. 46 | 47 | - Once it is determined that a Claim has reached consensus, the app should let the Oracle know this by calling `FinalizeRound`. This will remove the `Round` from the pending que and updated `LatestFinalizedRound` and remove all votes and claims associated with the round. 48 | 49 | ## Rewards and Punishments 50 | 51 | Rewards and Slashing logic is outside of the scope of the Oracle module. Modules relying on oracle data may implement their own logic. We reccomend taking a look at the (https://docs.terra.money/dev/spec-oracle.html#concepts)[Terra Oracle] and the (https://github.com/PeggyJV/sommelier/tree/main/x/oracle)[Sommelier Oracle] for examples. 52 | 53 | ## Validator Delegation 54 | 55 | Validators are able to delegate oracle responsibilities to a cosmos address of their choice. 56 | -------------------------------------------------------------------------------- /x/oracle/types/genesis.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | fmt "fmt" 5 | 6 | types "github.com/cosmos/cosmos-sdk/codec/types" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | proto "github.com/gogo/protobuf/proto" 9 | "github.com/relevant-community/oracle/x/oracle/exported" 10 | ) 11 | 12 | var _ types.UnpackInterfacesMessage = GenesisState{} 13 | 14 | // DefaultIndex is the default capability global index 15 | const DefaultIndex uint64 = 1 16 | 17 | // NewGenesisState creates a new GenesisState object 18 | func NewGenesisState( 19 | params Params, 20 | rounds []Round, 21 | _claims []exported.Claim, 22 | pending map[string]([]uint64), 23 | delegations []MsgDelegate, 24 | prevotes [][]byte, 25 | finalizedRounds map[string](uint64), 26 | ) *GenesisState { 27 | 28 | claims := make([]*types.Any, len(_claims)) 29 | for i, claim := range _claims { 30 | msg, ok := claim.(proto.Message) 31 | if !ok { 32 | panic(fmt.Errorf("cannot proto marshal %T", claim)) 33 | } 34 | any, err := types.NewAnyWithValue(msg) 35 | if err != nil { 36 | panic(err) 37 | } 38 | claims[i] = any 39 | } 40 | 41 | genPending := map[string]GenesisState_ListOfUint{} 42 | for i, p := range pending { 43 | genPending[i] = GenesisState_ListOfUint{p} 44 | } 45 | 46 | return &GenesisState{ 47 | Params: params, 48 | Rounds: rounds, 49 | Claims: claims, 50 | Pending: genPending, 51 | Delegations: delegations, 52 | Prevotes: prevotes, 53 | FinalizedRounds: finalizedRounds, 54 | } 55 | } 56 | 57 | // DefaultGenesis returns the default Capability genesis state 58 | func DefaultGenesis() *GenesisState { 59 | return &GenesisState{ 60 | Params: DefaultParams(), 61 | Claims: []*types.Any{}, 62 | Rounds: []Round{}, 63 | Pending: map[string]GenesisState_ListOfUint{}, 64 | FinalizedRounds: map[string](uint64){}, 65 | } 66 | } 67 | 68 | // Validate performs basic genesis state validation returning an error upon any 69 | // failure. 70 | func (gs GenesisState) Validate() error { 71 | 72 | for i, delegation := range gs.Delegations { 73 | if _, err := sdk.AccAddressFromBech32(delegation.Delegate); err != nil { 74 | return fmt.Errorf("invalid feeder at index %d: %w", i, err) 75 | } 76 | if _, err := sdk.AccAddressFromBech32(delegation.Validator); err != nil { 77 | return fmt.Errorf("invalid feeder at index %d: %w", i, err) 78 | } 79 | } 80 | 81 | for _, c := range gs.Claims { 82 | claim, ok := c.GetCachedValue().(exported.Claim) 83 | if !ok { 84 | return fmt.Errorf("expected claim") 85 | } 86 | if err := claim.ValidateBasic(); err != nil { 87 | return err 88 | } 89 | } 90 | if err := gs.Params.ValidateBasic(); err != nil { 91 | return err 92 | } 93 | return nil 94 | } 95 | 96 | // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces 97 | func (gs GenesisState) UnpackInterfaces(unpacker types.AnyUnpacker) error { 98 | for _, any := range gs.Claims { 99 | var claim exported.Claim 100 | err := unpacker.UnpackAny(any, &claim) 101 | if err != nil { 102 | return err 103 | } 104 | } 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: 'cosmos', 3 | title: 'Relevant Oracle', 4 | locales: { 5 | '/': { 6 | lang: 'en-US', 7 | }, 8 | kr: { 9 | lang: 'kr', 10 | }, 11 | cn: { 12 | lang: 'cn', 13 | }, 14 | ru: { 15 | lang: 'ru', 16 | }, 17 | }, 18 | base: '/oracle/', 19 | head: [ 20 | [ 21 | 'link', 22 | { 23 | rel: 'apple-touch-icon', 24 | sizes: '180x180', 25 | href: '/apple-touch-icon.png', 26 | }, 27 | ], 28 | [ 29 | 'link', 30 | { 31 | rel: 'icon', 32 | type: 'image/png', 33 | sizes: '32x32', 34 | href: '/favicon-32x32.png', 35 | }, 36 | ], 37 | [ 38 | 'link', 39 | { 40 | rel: 'icon', 41 | type: 'image/png', 42 | sizes: '16x16', 43 | href: '/favicon-16x16.png', 44 | }, 45 | ], 46 | ['link', { rel: 'manifest', href: '/site.webmanifest' }], 47 | ['meta', { name: 'msapplication-TileColor', content: '#2e3148' }], 48 | ['meta', { name: 'theme-color', content: '#ffffff' }], 49 | ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon-svg.svg' }], 50 | [ 51 | 'link', 52 | { 53 | rel: 'apple-touch-icon-precomposed', 54 | href: '/apple-touch-icon-precomposed.png', 55 | }, 56 | ], 57 | ], 58 | themeConfig: { 59 | repo: 'relevant-community/oracle', 60 | docsRepo: 'relevant-community/oracle', 61 | docsDir: 'docs', 62 | // label: 'sdk', 63 | custom: true, 64 | topbar: { 65 | banner: false, 66 | }, 67 | sidebar: { 68 | auto: false, 69 | nav: [ 70 | { 71 | title: 'Documentation', 72 | children: [ 73 | { 74 | title: 'Atom/USD Tutorial', 75 | directory: true, 76 | path: '/tutorial', 77 | }, 78 | { 79 | title: 'Oracle Module Docs', 80 | directory: true, 81 | path: '/modules/oracle', 82 | }, 83 | ], 84 | }, 85 | ], 86 | }, 87 | 88 | footer: { 89 | logo: '/oracle/relevantlogo.svg', 90 | textLink: { 91 | text: 'relevant.community', 92 | url: 'https://relevant.community', 93 | }, 94 | services: [ 95 | { 96 | service: 'twitter', 97 | url: 'https://twitter.com/relevantfeed', 98 | }, 99 | { 100 | service: 'github', 101 | url: 'https://github.com/relevant-community/oracle', 102 | }, 103 | ], 104 | links: [ 105 | { 106 | title: 'Contributing', 107 | children: [ 108 | { 109 | title: 'Source code on GitHub', 110 | url: 'https://github.com/relevant-community/oracle', 111 | }, 112 | ], 113 | }, 114 | { 115 | title: 'Related Docs', 116 | children: [ 117 | { 118 | title: 'Cosmos SDK', 119 | url: 'https://cosmos.network/docs', 120 | }, 121 | ], 122 | }, 123 | ], 124 | }, 125 | }, 126 | } 127 | -------------------------------------------------------------------------------- /.pi/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ======================================================================= 3 | # Starport Development Environment Build System 4 | # ======================================================================= 5 | 6 | 7 | # This process uses tools and a design pattern first developed by the pikvm team for their pi-builder and os tools. 8 | # the biggest differences between this process and theirs are: 9 | # * we use docker buildx so we don't need to deal with qemu directly. 10 | # * we are not offering as many choices to users and are designing around automation. 11 | # Later we can make this work for more devices and platforms with nearly the same technique. 12 | # Reasonable build targets include: https://archlinuxarm.org/platforms/armv8 13 | # For example, the Odroid-N2 is the same software-wise as our Router! 14 | 15 | # Fail on error 16 | set -exo pipefail 17 | 18 | # Print each command 19 | set -o xtrace 20 | 21 | # EXTRACT IMAGE 22 | # Make a temporary directory 23 | rm -rf .tmp || true 24 | mkdir .tmp 25 | 26 | # UNCOMMENT and add username WHEN NOT USING GITHUB ACTIONS 27 | # docker buildx build --tag oracle --file .pi/Dockerfile --platform linux/arm64 --cache-from oracle:cache --cache-to oracle:cache --load --progress tty . 28 | 29 | # save the image to result-rootfs.tar 30 | docker save --output ./.tmp/result-rootfs.tar oracle 31 | 32 | # Extract the image using docker-extract 33 | docker run --rm --tty --volume $(pwd)/./.tmp:/root/./.tmp --workdir /root/./.tmp/.. faddat/toolbox /tools/docker-extract --root ./.tmp/result-rootfs ./.tmp/result-rootfs.tar 34 | 35 | # get rid of result-rootfs.tar to save space 36 | rm ./.tmp/result-rootfs.tar 37 | 38 | # Set hostname while the image is just in the filesystem. 39 | sudo bash -c "echo oracle > ./.tmp/result-rootfs/etc/hostname" 40 | 41 | 42 | # =================================================================================== 43 | # IMAGE: Make a .img file and compress it. 44 | # Uses Techniques from Disconnected Systems: 45 | # https://disconnected.systems/blog/raspberry-pi-archlinuxarm-setup/ 46 | # =================================================================================== 47 | 48 | 49 | # Unmount anything on the loop device 50 | sudo umount /dev/loop0p2 || true 51 | sudo umount /dev/loop0p1 || true 52 | 53 | # Detach from the loop device 54 | sudo losetup -d /dev/loop0 || true 55 | 56 | # Create a folder for images 57 | rm -rf images || true 58 | mkdir -p images 59 | 60 | # Make the image file 61 | fallocate -l 4G "images/oracle.img" 62 | 63 | # loop-mount the image file so it becomes a disk 64 | sudo losetup --find --show images/oracle.img 65 | 66 | # partition the loop-mounted disk 67 | sudo parted --script /dev/loop0 mklabel msdos 68 | sudo parted --script /dev/loop0 mkpart primary fat32 0% 200M 69 | sudo parted --script /dev/loop0 mkpart primary ext4 200M 100% 70 | 71 | # format the newly partitioned loop-mounted disk 72 | sudo mkfs.vfat -F32 /dev/loop0p1 73 | sudo mkfs.ext4 -F /dev/loop0p2 74 | 75 | # Use the toolbox to copy the rootfs into the filesystem we formatted above. 76 | # * mount the disk's /boot and / partitions 77 | # * use rsync to copy files into the filesystem 78 | # make a folder so we can mount the boot partition 79 | # soon will not use toolbox 80 | 81 | sudo mkdir -p mnt/boot mnt/rootfs 82 | sudo mount /dev/loop0p1 mnt/boot 83 | sudo mount /dev/loop0p2 mnt/rootfs 84 | sudo rsync -a ./.tmp/result-rootfs/boot/* mnt/boot 85 | sudo rsync -a ./.tmp/result-rootfs/* mnt/rootfs --exclude boot 86 | sudo mkdir mnt/rootfs/boot 87 | sudo umount mnt/boot mnt/rootfs || true 88 | 89 | # Drop the loop mount 90 | sudo losetup -d /dev/loop0 91 | -------------------------------------------------------------------------------- /docs/.vuepress/public/relevantlogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 39 | 40 | 43 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /x/oracle/keeper/vote_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | "github.com/relevant-community/oracle/x/oracle/types" 6 | ) 7 | 8 | const claimType = "test" 9 | 10 | func (suite *KeeperTestSuite) TestCastVote() { 11 | ctx := suite.ctx.WithIsCheckTx(false) 12 | claim := types.NewTestClaim(99, "test", claimType) 13 | val0 := suite.validators[0] 14 | val1 := suite.validators[1] 15 | 16 | suite.k.CreateVote(ctx, claim, val0) 17 | 18 | savedClaim := suite.k.GetClaim(ctx, claim.Hash()) 19 | suite.NotNil(savedClaim) 20 | 21 | pending := suite.k.GetPendingRounds(ctx, claimType) 22 | suite.NotNil(pending) 23 | 24 | roundID := pending[len(pending)-1] 25 | suite.NotNil(roundID) 26 | suite.Equal(roundID, claim.GetRoundID()) 27 | 28 | round := suite.k.GetRound(ctx, claimType, roundID) 29 | suite.NotNil(round) 30 | 31 | vote := round.Votes[len(round.Votes)-1] 32 | suite.NotNil(vote) 33 | 34 | suite.Equal(vote.Validator, val0) 35 | suite.Equal(vote.ClaimHash, claim.Hash()) 36 | suite.Equal(vote.ClaimType, claim.ClaimType) 37 | suite.Equal(vote.ConsensusId, claim.GetConcensusKey()) 38 | suite.Equal(vote.RoundId, roundID) 39 | 40 | // Add second vote 41 | suite.k.CreateVote(ctx, claim, val1) 42 | round = suite.k.GetRound(ctx, claimType, roundID) 43 | suite.NotNil(round) 44 | suite.Equal(len(round.Votes), 2) 45 | 46 | // Test cleanup 47 | 48 | // Remove pending round 49 | suite.k.FinalizeRound(ctx, claimType, roundID) 50 | 51 | pending = suite.k.GetPendingRounds(ctx, claimType) 52 | suite.Equal(len(pending), 0) 53 | 54 | // Remove votes + claims 55 | suite.k.DeleteVotesForRound(ctx, claimType, roundID) 56 | round = suite.k.GetRound(ctx, claimType, roundID) 57 | suite.Nil(round) 58 | 59 | savedClaim = suite.k.GetClaim(ctx, claim.Hash()) 60 | suite.Nil(savedClaim) 61 | } 62 | 63 | func (suite *KeeperTestSuite) TestVoteTally() { 64 | ctx := suite.ctx.WithIsCheckTx(false) 65 | claim := types.NewTestClaim(99, "test", claimType) 66 | roundID := claim.GetRoundID() 67 | 68 | val0 := suite.validators[0] 69 | val1 := suite.validators[1] 70 | 71 | suite.k.CreateVote(ctx, claim, val0) 72 | 73 | // Haven't reached threshold 74 | roundResult := suite.k.TallyVotes(ctx, claimType, roundID) 75 | suite.Nil(roundResult) 76 | 77 | // Haven't reached threshold (50%) 78 | suite.k.CreateVote(ctx, claim, val1) 79 | roundResult = suite.k.TallyVotes(ctx, claimType, roundID) 80 | suite.NotNil(roundResult) 81 | 82 | totalBondedPower := sdk.TokensToConsensusPower(suite.k.StakingKeeper.TotalBondedTokens(ctx)) 83 | suite.Equal(roundResult.TotalPower, totalBondedPower) 84 | 85 | suite.Equal(roundResult.VotePower, suite.pow[0]+suite.pow[1]) 86 | 87 | suite.Equal(len(roundResult.Claims), 1) 88 | suite.Equal(roundResult.Claims[0].ClaimHash, claim.Hash()) 89 | } 90 | 91 | // TEST Getters & Setters used in Genesis 92 | 93 | func (suite *KeeperTestSuite) TestIterateVotes() { 94 | ctx := suite.ctx.WithIsCheckTx(false) 95 | num := 20 96 | suite.populateVotes(ctx, num) 97 | 98 | votes := suite.k.GetAllRounds(ctx) 99 | suite.Len(votes, num) 100 | } 101 | 102 | func (suite *KeeperTestSuite) populateVotes(ctx sdk.Context, num int) []types.Round { 103 | rounds := make([]types.Round, num) 104 | 105 | for i := 0; i < num; i++ { 106 | roundID := uint64(i) 107 | 108 | claim := types.NewTestClaim(int64(roundID), "test", claimType) 109 | vote := types.NewVote(roundID, claim, suite.validators[0], claimType) 110 | round := types.Round{ 111 | Votes: []types.Vote{*vote}, 112 | RoundId: roundID, 113 | ClaimType: claimType, 114 | } 115 | rounds[i] = round 116 | suite.k.CreateRound(ctx, round) 117 | } 118 | return rounds 119 | } 120 | -------------------------------------------------------------------------------- /x/oracle/client/rest/query_grpc_test.go: -------------------------------------------------------------------------------- 1 | package rest_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/cosmos/cosmos-sdk/testutil" 8 | grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" 9 | "github.com/cosmos/cosmos-sdk/types/query" 10 | proto "github.com/gogo/protobuf/proto" 11 | "github.com/relevant-community/oracle/app" 12 | "github.com/relevant-community/oracle/x/oracle/types" 13 | "github.com/stretchr/testify/suite" 14 | 15 | testnet "github.com/cosmos/cosmos-sdk/testutil/network" 16 | ) 17 | 18 | type IntegrationTestSuite struct { 19 | suite.Suite 20 | 21 | cfg testnet.Config 22 | network *testnet.Network 23 | } 24 | 25 | func (s *IntegrationTestSuite) SetupSuite() { 26 | s.T().Log("setting up integration test suite") 27 | 28 | cfg := app.DefaultConfig() 29 | cfg.NumValidators = 1 30 | 31 | s.cfg = cfg 32 | s.network = testnet.New(s.T(), cfg) 33 | 34 | _, err := s.network.WaitForHeight(1) 35 | s.Require().NoError(err) 36 | } 37 | 38 | func (s *IntegrationTestSuite) TearDownSuite() { 39 | s.T().Log("tearing down integration test suite") 40 | s.network.Cleanup() 41 | } 42 | 43 | func TestIntegrationTestSuite(t *testing.T) { 44 | suite.Run(t, new(IntegrationTestSuite)) 45 | } 46 | 47 | func (s *IntegrationTestSuite) TestGRPCQueries() { 48 | val := s.network.Validators[0] 49 | baseURL := val.APIAddress 50 | 51 | testHash := types.NewTestClaim(1, "test", types.TestClaimType).Hash() 52 | 53 | testCases := []struct { 54 | name string 55 | url string 56 | headers map[string]string 57 | expErr bool 58 | respType proto.Message 59 | expected proto.Message 60 | errMsg string 61 | }{ 62 | { 63 | "Get Params", 64 | fmt.Sprintf("%s/relevantcommunity/oracle/oracle/params", baseURL), 65 | map[string]string{ 66 | grpctypes.GRPCBlockHeightHeader: "1", 67 | }, 68 | false, 69 | &types.QueryParamsResponse{}, 70 | &types.QueryParamsResponse{ 71 | Params: types.DefaultParams(), 72 | }, 73 | "", 74 | }, 75 | { 76 | "Get Claim", 77 | fmt.Sprintf("%s/relevantcommunity/oracle/oracle/claim/%s", baseURL, testHash.String()), 78 | map[string]string{ 79 | grpctypes.GRPCBlockHeightHeader: "1", 80 | }, 81 | true, 82 | &types.QueryClaimResponse{}, 83 | &types.QueryClaimResponse{}, 84 | fmt.Sprintf("claim %s not found:", testHash.String()), 85 | }, 86 | { 87 | "Get all claims", 88 | fmt.Sprintf("%s/relevantcommunity/oracle/oracle/allclaims", baseURL), 89 | map[string]string{ 90 | grpctypes.GRPCBlockHeightHeader: "1", 91 | }, 92 | false, 93 | &types.QueryAllClaimsResponse{}, 94 | &types.QueryAllClaimsResponse{ 95 | Pagination: &query.PageResponse{Total: 0}, 96 | }, 97 | "", 98 | }, 99 | { 100 | "Get pending rounds", 101 | fmt.Sprintf("%s/relevantcommunity/oracle/oracle/pending_rounds/test", baseURL), 102 | map[string]string{ 103 | grpctypes.GRPCBlockHeightHeader: "1", 104 | }, 105 | false, 106 | &types.QueryPendingRoundsResponse{}, 107 | &types.QueryPendingRoundsResponse{}, 108 | "", 109 | }, 110 | { 111 | "Get round", 112 | fmt.Sprintf("%s/relevantcommunity/oracle/oracle/round/test/1", baseURL), 113 | map[string]string{ 114 | grpctypes.GRPCBlockHeightHeader: "1", 115 | }, 116 | true, 117 | &types.QueryRoundResponse{}, 118 | &types.QueryRoundResponse{}, 119 | fmt.Sprintf("round %s not found:", "1"), 120 | }, 121 | { 122 | "Get all rounds", 123 | fmt.Sprintf("%s/relevantcommunity/oracle/oracle/all_rounds", baseURL), 124 | map[string]string{ 125 | grpctypes.GRPCBlockHeightHeader: "1", 126 | }, 127 | false, 128 | &types.QueryAllRoundsResponse{}, 129 | &types.QueryAllRoundsResponse{ 130 | Pagination: &query.PageResponse{Total: 0}, 131 | }, 132 | "", 133 | }, 134 | { 135 | "Get finlized round", 136 | fmt.Sprintf("%s/relevantcommunity/oracle/oracle/finalized_round/test", baseURL), 137 | map[string]string{ 138 | grpctypes.GRPCBlockHeightHeader: "1", 139 | }, 140 | false, 141 | &types.QueryLastFinalizedRoundResponse{}, 142 | &types.QueryLastFinalizedRoundResponse{ 143 | LastFinalizedRound: 0, 144 | }, 145 | "", 146 | }, 147 | } 148 | 149 | for _, tc := range testCases { 150 | tc := tc 151 | 152 | s.Run(tc.name, func() { 153 | resp, err := testutil.GetRequestWithHeaders(tc.url, tc.headers) 154 | s.Require().NoError(err) 155 | 156 | err = val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, tc.respType) 157 | 158 | if tc.expErr { 159 | s.Require().Error(err) 160 | s.Require().Contains(string(resp), tc.errMsg) 161 | } else { 162 | s.Require().NoError(err) 163 | s.Require().Equal(tc.expected.String(), tc.respType.String()) 164 | } 165 | }) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /x/oracle/keeper/grpc_query_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/query" 8 | "github.com/relevant-community/oracle/x/oracle/exported" 9 | "github.com/relevant-community/oracle/x/oracle/types" 10 | 11 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 12 | ) 13 | 14 | func (suite *KeeperTestSuite) TestQueryParam() { 15 | var ( 16 | req *types.QueryParamsRequest 17 | ) 18 | 19 | testCases := []struct { 20 | msg string 21 | malleate func() 22 | expPass bool 23 | posttests func(res *types.QueryParamsResponse) 24 | }{ 25 | { 26 | "success", 27 | func() { 28 | req = &types.QueryParamsRequest{} 29 | }, 30 | true, 31 | func(res *types.QueryParamsResponse) { 32 | suite.Require().NotNil(res) 33 | suite.Require().Equal(res.Params, types.DefaultParams()) 34 | }, 35 | }, 36 | } 37 | 38 | for _, tc := range testCases { 39 | suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { 40 | suite.SetupTest() 41 | 42 | tc.malleate() 43 | ctx := sdk.WrapSDKContext(suite.ctx) 44 | 45 | res, err := suite.queryClient.Params(ctx, req) 46 | 47 | if tc.expPass { 48 | suite.Require().NoError(err) 49 | suite.Require().NotNil(res) 50 | } else { 51 | suite.Require().Error(err) 52 | suite.Require().Nil(res) 53 | } 54 | 55 | tc.posttests(res) 56 | }) 57 | } 58 | } 59 | 60 | func (suite *KeeperTestSuite) TestQueryClaim() { 61 | var ( 62 | req *types.QueryClaimRequest 63 | claims []exported.Claim 64 | ) 65 | 66 | testCases := []struct { 67 | msg string 68 | malleate func() 69 | expPass bool 70 | posttests func(res *types.QueryClaimResponse) 71 | }{ 72 | { 73 | "empty request", 74 | func() { 75 | req = &types.QueryClaimRequest{} 76 | }, 77 | false, 78 | func(res *types.QueryClaimResponse) {}, 79 | }, 80 | { 81 | "invalid request with empty claim hash", 82 | func() { 83 | req = &types.QueryClaimRequest{ClaimHash: tmbytes.HexBytes{}.String()} 84 | }, 85 | false, 86 | func(res *types.QueryClaimResponse) {}, 87 | }, 88 | { 89 | "success", 90 | func() { 91 | num := 1 92 | claims = suite.populateClaims(suite.ctx, num) 93 | req = types.NewQueryClaimRequest(claims[0].Hash().String()) 94 | }, 95 | true, 96 | func(res *types.QueryClaimResponse) { 97 | var c exported.Claim 98 | err := suite.app.InterfaceRegistry().UnpackAny(res.Claim, &c) 99 | suite.Require().NoError(err) 100 | suite.Require().NotNil(c) 101 | suite.Require().Equal(c, claims[0]) 102 | }, 103 | }, 104 | } 105 | 106 | for _, tc := range testCases { 107 | suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { 108 | suite.SetupTest() 109 | 110 | tc.malleate() 111 | ctx := sdk.WrapSDKContext(suite.ctx) 112 | 113 | res, err := suite.queryClient.Claim(ctx, req) 114 | 115 | if tc.expPass { 116 | suite.Require().NoError(err) 117 | suite.Require().NotNil(res) 118 | } else { 119 | suite.Require().Error(err) 120 | suite.Require().Nil(res) 121 | } 122 | 123 | tc.posttests(res) 124 | }) 125 | } 126 | } 127 | 128 | func (suite *KeeperTestSuite) TestQueryAllClaims() { 129 | var ( 130 | req *types.QueryAllClaimsRequest 131 | ) 132 | 133 | testCases := []struct { 134 | msg string 135 | malleate func() 136 | expPass bool 137 | posttests func(res *types.QueryAllClaimsResponse) 138 | }{ 139 | { 140 | "success without claim", 141 | func() { 142 | req = &types.QueryAllClaimsRequest{} 143 | }, 144 | true, 145 | func(res *types.QueryAllClaimsResponse) { 146 | suite.Require().Empty(res.Claims) 147 | }, 148 | }, 149 | { 150 | "success", 151 | func() { 152 | num := 100 153 | _ = suite.populateClaims(suite.ctx, num) 154 | pageReq := &query.PageRequest{ 155 | Key: nil, 156 | Limit: 50, 157 | CountTotal: false, 158 | } 159 | req = types.NewQueryAllClaimsRequest(pageReq) 160 | }, 161 | true, 162 | func(res *types.QueryAllClaimsResponse) { 163 | suite.Equal(len(res.Claims), 50) 164 | suite.NotNil(res.Pagination.NextKey) 165 | }, 166 | }, 167 | } 168 | 169 | for _, tc := range testCases { 170 | suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { 171 | suite.SetupTest() 172 | 173 | tc.malleate() 174 | ctx := sdk.WrapSDKContext(suite.ctx) 175 | 176 | res, err := suite.queryClient.AllClaims(ctx, req) 177 | 178 | if tc.expPass { 179 | suite.Require().NoError(err) 180 | suite.Require().NotNil(res) 181 | } else { 182 | suite.Require().Error(err) 183 | suite.Require().Nil(res) 184 | } 185 | 186 | tc.posttests(res) 187 | }) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /x/oracle/types/genesis_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | fmt "fmt" 5 | "testing" 6 | 7 | "github.com/cosmos/cosmos-sdk/codec" 8 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 9 | "github.com/relevant-community/oracle/x/oracle/exported" 10 | "github.com/relevant-community/oracle/x/oracle/types" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestDefaultGenesisState(t *testing.T) { 16 | gs := types.DefaultGenesis() 17 | require.NotNil(t, gs.Claims) 18 | require.Len(t, gs.Claims, 0) 19 | 20 | require.NotNil(t, gs.Rounds) 21 | require.Len(t, gs.Rounds, 0) 22 | 23 | require.NotNil(t, gs.Params) 24 | require.Equal(t, gs.Params, types.DefaultParams()) 25 | } 26 | 27 | func TestNewGenesisState(t *testing.T) { 28 | var ( 29 | claims []exported.Claim 30 | rounds []types.Round 31 | pending map[string]([]uint64) 32 | delegations []types.MsgDelegate 33 | prevotes [][]byte 34 | finalizedRounds map[string](uint64) 35 | ) 36 | 37 | testCases := []struct { 38 | msg string 39 | malleate func() 40 | expPass bool 41 | }{ 42 | { 43 | "can proto marshal", 44 | func() { 45 | claims = []exported.Claim{&types.TestClaim{}} 46 | rounds = []types.Round{} 47 | pending = map[string][]uint64{ 48 | "test": {1}, 49 | } 50 | }, 51 | true, 52 | }, 53 | } 54 | 55 | for _, tc := range testCases { 56 | t.Run(fmt.Sprintf("Case %s", tc.msg), func(t *testing.T) { 57 | tc.malleate() 58 | 59 | if tc.expPass { 60 | require.NotPanics(t, func() { 61 | types.NewGenesisState( 62 | types.DefaultParams(), 63 | rounds, 64 | claims, 65 | pending, 66 | delegations, 67 | prevotes, 68 | finalizedRounds, 69 | ) 70 | }) 71 | } else { 72 | require.Panics(t, func() { 73 | types.NewGenesisState( 74 | types.DefaultParams(), 75 | rounds, 76 | claims, 77 | pending, 78 | delegations, 79 | prevotes, 80 | finalizedRounds, 81 | ) 82 | }) 83 | } 84 | }) 85 | } 86 | } 87 | 88 | func TestGenesisStateValidate(t *testing.T) { 89 | var ( 90 | genesisState *types.GenesisState 91 | testClaim []exported.Claim 92 | pending map[string]([]uint64) 93 | delegations []types.MsgDelegate 94 | prevotes [][]byte 95 | finalizedRounds map[string](uint64) 96 | ) 97 | round := []types.Round{} 98 | params := types.DefaultParams() 99 | 100 | testCases := []struct { 101 | msg string 102 | malleate func() 103 | expPass bool 104 | }{ 105 | { 106 | "valid", 107 | func() { 108 | testClaim = make([]exported.Claim, 100) 109 | for i := 0; i < 100; i++ { 110 | testClaim[i] = &types.TestClaim{ 111 | BlockHeight: int64(i + 1), 112 | Content: "test", 113 | ClaimType: "test", 114 | } 115 | } 116 | genesisState = types.NewGenesisState( 117 | params, round, testClaim, pending, delegations, prevotes, finalizedRounds, 118 | ) 119 | }, 120 | true, 121 | }, 122 | { 123 | "invalid", 124 | func() { 125 | testClaim = make([]exported.Claim, 100) 126 | for i := 0; i < 100; i++ { 127 | testClaim[i] = &types.TestClaim{ 128 | BlockHeight: int64(i), 129 | Content: "test", 130 | ClaimType: "test", 131 | } 132 | } 133 | genesisState = types.NewGenesisState( 134 | params, round, testClaim, pending, delegations, prevotes, finalizedRounds, 135 | ) 136 | }, 137 | false, 138 | }, 139 | { 140 | "expected claim", 141 | func() { 142 | genesisState = &types.GenesisState{ 143 | Claims: []*codectypes.Any{{}}, 144 | } 145 | }, 146 | false, 147 | }, 148 | } 149 | 150 | for _, tc := range testCases { 151 | t.Run(fmt.Sprintf("Case %s", tc.msg), func(t *testing.T) { 152 | tc.malleate() 153 | 154 | if tc.expPass { 155 | require.NoError(t, genesisState.Validate()) 156 | } else { 157 | require.Error(t, genesisState.Validate()) 158 | } 159 | }) 160 | } 161 | } 162 | 163 | func TestUnpackInterfaces(t *testing.T) { 164 | var gs = types.GenesisState{ 165 | Claims: []*codectypes.Any{{}}, 166 | } 167 | 168 | testCases := []struct { 169 | msg string 170 | unpacker codectypes.AnyUnpacker 171 | expPass bool 172 | }{ 173 | { 174 | "success", 175 | codectypes.NewInterfaceRegistry(), 176 | true, 177 | }, 178 | { 179 | "error", 180 | codec.NewLegacyAmino(), 181 | false, 182 | }, 183 | } 184 | 185 | for _, tc := range testCases { 186 | t.Run(fmt.Sprintf("Case %s", tc.msg), func(t *testing.T) { 187 | 188 | if tc.expPass { 189 | require.NoError(t, gs.UnpackInterfaces(tc.unpacker)) 190 | } else { 191 | require.Error(t, gs.UnpackInterfaces(tc.unpacker)) 192 | } 193 | }) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /x/oracle/client/cli/workerHelper.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | 9 | "github.com/cosmos/cosmos-sdk/client" 10 | "github.com/cosmos/cosmos-sdk/client/tx" 11 | "github.com/relevant-community/oracle/x/oracle/exported" 12 | "github.com/relevant-community/oracle/x/oracle/types" 13 | "github.com/spf13/cobra" 14 | ctypes "github.com/tendermint/tendermint/rpc/core/types" 15 | tendermint "github.com/tendermint/tendermint/types" 16 | ) 17 | 18 | // store claim and salt of previous round to submit after prevote 19 | // TODO: a non-ephemeral storage option? 20 | type prevClaim struct { 21 | claim exported.Claim 22 | salt string 23 | } 24 | 25 | var prevVotes = map[string](*prevClaim){} 26 | 27 | // WorkerHelper helps with oracle queries and tx 28 | type WorkerHelper struct { 29 | cmd *cobra.Command 30 | OracleParams types.Params 31 | QueryClient types.QueryClient 32 | BlockHeight int64 33 | } 34 | 35 | // NewWorkerHelper is a worker helper util 36 | func NewWorkerHelper(cmd *cobra.Command, blockEvent ctypes.ResultEvent) (*WorkerHelper, error) { 37 | blockHeight := 38 | blockEvent.Data.(tendermint.EventDataNewBlock).Block.Header.Height 39 | 40 | clientCtx, err := client.GetClientTxContext(cmd) 41 | if err != nil { 42 | return nil, err 43 | } 44 | // Make sure we querey the data from the right height 45 | clientCtx.Height = blockHeight 46 | queryClient := types.NewQueryClient(clientCtx) 47 | 48 | params := &types.QueryParamsRequest{} 49 | 50 | // we query the array of claim parameters for different claims 51 | res, err := queryClient.Params(context.Background(), params) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return &WorkerHelper{ 57 | cmd: cmd, 58 | OracleParams: res.Params, 59 | QueryClient: queryClient, 60 | BlockHeight: blockHeight, 61 | }, nil 62 | } 63 | 64 | // SubmitWorkerTx submits the correct transactions based on oracle params 65 | func (h WorkerHelper) SubmitWorkerTx(claim exported.Claim) { 66 | 67 | /////////////// 68 | /// NO PREVOTE 69 | /////////////// 70 | claimType := claim.Type() 71 | usePrevote := h.OracleParams.ClaimParams[claimType].Prevote 72 | 73 | // use the most recent data and no salt = "" 74 | if usePrevote == false { 75 | err := h.SubmitVote(claim, "") 76 | if err != nil { 77 | fmt.Println("Error submitting claim vote", err) 78 | } 79 | return 80 | } 81 | 82 | /////////////// 83 | /// YES PREVOTE 84 | /////////////// 85 | 86 | // 1. submit data for prev round 87 | // 2. submit prevote for current round 88 | prev := prevVotes[claimType] 89 | if prev != nil { 90 | err := h.SubmitVote(prev.claim, prev.salt) 91 | if err != nil { 92 | fmt.Println("Error submitting claim vote", err) 93 | } 94 | // clear the submitted claim 95 | prevVotes[claimType] = nil 96 | } 97 | err := h.SubmitPrevote(claim) 98 | if err != nil { 99 | fmt.Println("Error submitting prevote", err) 100 | } 101 | } 102 | 103 | // SubmitPrevote submits prevote of current claim 104 | func (h WorkerHelper) SubmitPrevote(claim exported.Claim) error { 105 | clientCtx, err := client.GetClientTxContext(h.cmd) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | salt := genrandstr(9) 111 | prevoteHash := types.VoteHash(salt, claim.Hash().String(), clientCtx.FromAddress) 112 | prevoteMsg := types.NewMsgPrevote(clientCtx.GetFromAddress(), prevoteHash) 113 | if err := prevoteMsg.ValidateBasic(); err != nil { 114 | return err 115 | } 116 | err = tx.GenerateOrBroadcastTxCLI(clientCtx, h.cmd.Flags(), prevoteMsg) 117 | if err != nil { 118 | return err 119 | } 120 | prevVotes[claim.Type()] = &prevClaim{ 121 | claim: claim, 122 | salt: salt, 123 | } 124 | return nil 125 | } 126 | 127 | // SubmitVote submits a vote of the given claim + salt 128 | func (h WorkerHelper) SubmitVote(claim exported.Claim, salt string) error { 129 | clientCtx, err := client.GetClientTxContext(h.cmd) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | voteMsg, err := types.NewMsgVote(clientCtx.GetFromAddress(), claim, salt) 135 | if err != nil { 136 | return err 137 | } 138 | if err := voteMsg.ValidateBasic(); err != nil { 139 | return err 140 | } 141 | 142 | // Submit Claim from previous round 143 | err = tx.GenerateOrBroadcastTxCLI(clientCtx, h.cmd.Flags(), voteMsg) 144 | 145 | // TODO retry if the TX fails 146 | if err != nil { 147 | return err 148 | } 149 | return nil 150 | } 151 | 152 | // IsRoundStart determins if the current blockHeight is the start of a round 153 | func (h WorkerHelper) IsRoundStart(claimType string) bool { 154 | votePeriod := h.OracleParams.ClaimParams[claimType].VotePeriod 155 | // default is every round 156 | if votePeriod == 0 { 157 | return true 158 | } 159 | return h.BlockHeight%int64(h.OracleParams.ClaimParams[claimType].VotePeriod) == 0 160 | } 161 | 162 | func genrandstr(s int) string { 163 | b := make([]byte, s) 164 | _, _ = rand.Read(b) 165 | return base64.URLEncoding.EncodeToString(b) 166 | } 167 | -------------------------------------------------------------------------------- /x/oracle/keeper/round.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/cosmos/cosmos-sdk/store/prefix" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/relevant-community/oracle/x/oracle/types" 9 | ) 10 | 11 | // CreateRound creates a Round (used in genesis file) 12 | func (k Keeper) CreateRound(ctx sdk.Context, round types.Round) { 13 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.RoundKey) 14 | store.Set(types.RoundPrefix(round.ClaimType, round.RoundId), k.cdc.MustMarshalBinaryBare(&round)) 15 | } 16 | 17 | // GetRound retrieves a Round that contains all Votes for a claimType and roundID 18 | func (k Keeper) GetRound(ctx sdk.Context, claimType string, roundID uint64) *types.Round { 19 | roundKey := types.GetRoundKey(claimType, roundID) 20 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.RoundKey) 21 | var round types.Round 22 | bz := store.Get(types.KeyPrefix(roundKey)) 23 | if len(bz) == 0 { 24 | return nil 25 | } 26 | k.cdc.MustUnmarshalBinaryBare(bz, &round) 27 | return &round 28 | } 29 | 30 | // GetAllRounds retrieves all the Rounds (used in genesis) 31 | func (k Keeper) GetAllRounds(ctx sdk.Context) (rounds []types.Round) { 32 | store := ctx.KVStore(k.storeKey) 33 | iterator := sdk.KVStorePrefixIterator(store, types.RoundKey) 34 | defer iterator.Close() 35 | 36 | rounds = []types.Round{} 37 | for ; iterator.Valid(); iterator.Next() { 38 | var round types.Round 39 | k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &round) 40 | rounds = append(rounds, round) 41 | } 42 | return 43 | } 44 | 45 | // PENDING 46 | 47 | // AddPendingRound adds the roundId to the pending que 48 | func (k Keeper) AddPendingRound(ctx sdk.Context, claimType string, roundID uint64) { 49 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PendingRoundKey) 50 | bz := []byte(strconv.FormatUint(roundID, 10)) 51 | store.Set(types.RoundPrefix(claimType, roundID), bz) 52 | } 53 | 54 | // GetPendingRounds returns an array of pending rounds for a given claimType 55 | func (k Keeper) GetPendingRounds(ctx sdk.Context, claimType string) (rounds []uint64) { 56 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PendingRoundKey) 57 | iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefix(claimType)) 58 | 59 | defer iterator.Close() 60 | 61 | for ; iterator.Valid(); iterator.Next() { 62 | round, err := strconv.ParseUint(string(iterator.Value()), 10, 64) 63 | if err != nil { 64 | // Panic because the count should be always formattable to uint64 65 | panic("cannot decode count") 66 | } 67 | rounds = append(rounds, round) 68 | } 69 | return 70 | } 71 | 72 | // GetAllPendingRounds returns all pending rounds 73 | func (k Keeper) GetAllPendingRounds(ctx sdk.Context) (allPendingRounds map[string]([]uint64)) { 74 | store := ctx.KVStore(k.storeKey) 75 | iterator := sdk.KVStorePrefixIterator(store, types.PendingRoundKey) 76 | 77 | defer iterator.Close() 78 | params := k.GetParams(ctx) 79 | 80 | allPendingRounds = map[string][]uint64{} 81 | for _, param := range params.ClaimParams { 82 | pending := k.GetPendingRounds(ctx, param.ClaimType) 83 | allPendingRounds[param.ClaimType] = pending 84 | } 85 | 86 | return allPendingRounds 87 | } 88 | 89 | // FinalizeRound deletes the roundKey from the store and updates the LastFinalizedRound 90 | func (k Keeper) FinalizeRound(ctx sdk.Context, claimType string, roundID uint64) { 91 | roundKey := types.GetRoundKey(claimType, roundID) 92 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PendingRoundKey) 93 | store.Delete(types.KeyPrefix(roundKey)) 94 | k.SetLastFinalizedRound(ctx, claimType, roundID) 95 | k.DeleteVotesForRound(ctx, claimType, roundID) 96 | } 97 | 98 | // SetLastFinalizedRound sets the most recent finalized round 99 | func (k Keeper) SetLastFinalizedRound(ctx sdk.Context, claimType string, roundID uint64) { 100 | currentFinalRound := k.GetLastFinalizedRound(ctx, claimType) 101 | // only increment 102 | if currentFinalRound >= roundID { 103 | return 104 | } 105 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FinalizedRoundKey) 106 | bz := []byte(strconv.FormatUint(roundID, 10)) 107 | store.Set(types.KeyPrefix(claimType), bz) 108 | } 109 | 110 | // GetLastFinalizedRound sets the most recent finalized round 111 | func (k Keeper) GetLastFinalizedRound(ctx sdk.Context, claimType string) uint64 { 112 | store := ctx.KVStore(k.storeKey) 113 | bz := store.Get(types.FinalizedRoundKey) 114 | if len(bz) == 0 { 115 | return 0 116 | } 117 | roundID, err := strconv.ParseUint(string(bz), 10, 64) 118 | if err != nil { 119 | // Panic because the roundID should be always formattable to uint64 120 | panic("cannot decode roundID") 121 | } 122 | return roundID 123 | } 124 | 125 | // GetAllFinalizedRounds returns all pending rounds 126 | func (k Keeper) GetAllFinalizedRounds(ctx sdk.Context) map[string](uint64) { 127 | store := ctx.KVStore(k.storeKey) 128 | iterator := sdk.KVStorePrefixIterator(store, types.PendingRoundKey) 129 | 130 | defer iterator.Close() 131 | params := k.GetParams(ctx) 132 | 133 | finalRounds := map[string]uint64{} 134 | for _, param := range params.ClaimParams { 135 | finalRound := k.GetLastFinalizedRound(ctx, param.ClaimType) 136 | finalRounds[param.ClaimType] = finalRound 137 | } 138 | 139 | return finalRounds 140 | } 141 | 142 | // GetCurrentRound returns the current vote round 143 | func (k Keeper) GetCurrentRound(ctx sdk.Context, claimType string) uint64 { 144 | claimParams := k.ClaimParamsForType(ctx, claimType) 145 | block := ctx.BlockHeight() 146 | return uint64(block) - uint64(block)%claimParams.VotePeriod 147 | } 148 | -------------------------------------------------------------------------------- /x/oracle/keeper/vote.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/cosmos/cosmos-sdk/store/prefix" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/relevant-community/oracle/x/oracle/exported" 9 | "github.com/relevant-community/oracle/x/oracle/types" 10 | tmbytes "github.com/tendermint/tendermint/libs/bytes" 11 | ) 12 | 13 | //////////////////// 14 | /// VOTE 15 | //////////////////// 16 | 17 | // CreateVote casts a vote for a given claim. 18 | func (k Keeper) CreateVote(ctx sdk.Context, claim exported.Claim, validator sdk.ValAddress) { 19 | k.CreateClaim(ctx, claim) 20 | roundID := claim.GetRoundID() 21 | claimType := claim.Type() 22 | vote := types.NewVote(roundID, claim, validator, claimType) 23 | 24 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.RoundKey) 25 | 26 | var votes types.Round 27 | bz := store.Get(types.RoundPrefix(claimType, roundID)) 28 | if len(bz) == 0 { 29 | votes = types.Round{ 30 | Votes: []types.Vote{*vote}, 31 | RoundId: roundID, 32 | ClaimType: claimType, 33 | } 34 | } else { 35 | k.cdc.MustUnmarshalBinaryBare(bz, &votes) 36 | votes.Votes = append(votes.Votes, *vote) 37 | } 38 | 39 | k.AddPendingRound(ctx, vote.ClaimType, vote.RoundId) 40 | store.Set(types.RoundPrefix(claimType, roundID), k.cdc.MustMarshalBinaryBare(&votes)) 41 | } 42 | 43 | // DeleteVotesForRound deletes all votes and claims for a given round and claimType 44 | func (k Keeper) DeleteVotesForRound(ctx sdk.Context, claimType string, roundID uint64) { 45 | store := prefix.NewStore(ctx.KVStore(k.storeKey), types.RoundKey) 46 | roundKey := types.GetRoundKey(claimType, roundID) 47 | 48 | round := k.GetRound(ctx, claimType, roundID) 49 | if round == nil { 50 | return 51 | } 52 | 53 | for _, vote := range round.Votes { 54 | k.DeleteClaim(ctx, []byte(vote.ClaimHash)) 55 | } 56 | store.Delete(types.KeyPrefix(roundKey)) 57 | } 58 | 59 | // TallyVotes tallies up the votes for a given Claim and returns the result with the maximum claim 60 | // vote.ClaimHash 61 | func (k Keeper) TallyVotes(ctx sdk.Context, claimType string, roundID uint64) *types.RoundResult { 62 | votes := k.GetRound(ctx, claimType, roundID) 63 | 64 | voteMap := make(map[string]*types.RoundResult) 65 | var maxVotePower int64 66 | var maxVoteKey string 67 | for _, vote := range votes.Votes { 68 | validator := k.StakingKeeper.Validator(ctx, vote.Validator) 69 | if validator == nil || !validator.IsBonded() || validator.IsJailed() { 70 | continue 71 | } 72 | weight := validator.GetConsensusPower() 73 | key := vote.GetConsensusId() 74 | 75 | resultObject := voteMap[key] 76 | if resultObject == nil { 77 | resultObject = &types.RoundResult{ 78 | ClaimType: vote.GetClaimType(), 79 | } 80 | } 81 | 82 | resultObject.UpsertClaim(vote.ClaimHash, weight) 83 | resultObject.VotePower += weight 84 | 85 | voteMap[key] = resultObject 86 | 87 | if resultObject.VotePower > maxVotePower { 88 | maxVoteKey = key 89 | } 90 | } 91 | 92 | result := voteMap[maxVoteKey] 93 | if result == nil { 94 | return nil 95 | } 96 | 97 | passesThreshold, totalBondedPower := k.VotePassedThreshold(ctx, result) 98 | 99 | if passesThreshold { 100 | result.TotalPower = totalBondedPower 101 | return result 102 | } 103 | return nil 104 | } 105 | 106 | // VotePassedThreshold checks if a given claim has cleared the vote threshold 107 | func (k Keeper) VotePassedThreshold(ctx sdk.Context, roundResult *types.RoundResult) (bool, int64) { 108 | totalBondedPower := sdk.TokensToConsensusPower(k.StakingKeeper.TotalBondedTokens(ctx)) 109 | claimParams := k.ClaimParamsForType(ctx, roundResult.ClaimType) 110 | voteThreshold := claimParams.VoteThreshold 111 | thresholdVotes := voteThreshold.MulInt64(totalBondedPower).RoundInt() 112 | votePower := sdk.NewInt(roundResult.VotePower) 113 | return !votePower.IsZero() && votePower.GT(thresholdVotes), totalBondedPower 114 | } 115 | 116 | //////////////////// 117 | /// PRE-VOTE 118 | //////////////////// 119 | 120 | // CreatePrevote sets the prevote for a given validator 121 | func (k Keeper) CreatePrevote(ctx sdk.Context, hash []byte) { 122 | ctx.KVStore(k.storeKey).Set(types.GetClaimPrevoteKey(hash), hash) 123 | } 124 | 125 | // GetPrevote gets the prevote for a given validator 126 | func (k Keeper) GetPrevote(ctx sdk.Context, hash tmbytes.HexBytes) tmbytes.HexBytes { 127 | return ctx.KVStore(k.storeKey).Get(hash) 128 | } 129 | 130 | // DeletePrevote deletes the prevote for a given validator 131 | func (k Keeper) DeletePrevote(ctx sdk.Context, hash tmbytes.HexBytes) { 132 | ctx.KVStore(k.storeKey).Delete(types.GetClaimPrevoteKey(hash)) 133 | } 134 | 135 | // HasPrevote gets the prevote for a given hash 136 | func (k Keeper) HasPrevote(ctx sdk.Context, hash tmbytes.HexBytes) bool { 137 | return ctx.KVStore(k.storeKey).Has(types.GetClaimPrevoteKey(hash)) 138 | } 139 | 140 | // GetAllPrevotes returns all prevotes 141 | func (k Keeper) GetAllPrevotes(ctx sdk.Context) [][]byte { 142 | prevotes := [][]byte{} 143 | k.IteratePrevotes(ctx, func(hash []byte) (stop bool) { 144 | prevotes = append(prevotes, hash) 145 | return 146 | }) 147 | return prevotes 148 | } 149 | 150 | // IteratePrevotes iterates over all prevotes in the store 151 | func (k Keeper) IteratePrevotes(ctx sdk.Context, handler func(hash []byte) (stop bool)) { 152 | store := ctx.KVStore(k.storeKey) 153 | iter := sdk.KVStorePrefixIterator(store, types.PrevoteKey) 154 | defer iter.Close() 155 | for ; iter.Valid(); iter.Next() { 156 | hash := bytes.TrimPrefix(iter.Key(), types.PrevoteKey) 157 | if handler(hash) { 158 | break 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /proto/oracle/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relevantcommunity.oracle.oracle; 3 | 4 | import "cosmos/base/query/v1beta1/pagination.proto"; 5 | import "gogoproto/gogo.proto"; 6 | import "google/protobuf/any.proto"; 7 | import "google/api/annotations.proto"; 8 | import "oracle/params.proto"; 9 | import "oracle/oracle.proto"; 10 | import "cosmos_proto/cosmos.proto"; 11 | 12 | // this line is used by starport scaffolding # 1 13 | 14 | option go_package = "github.com/relevant-community/oracle/x/oracle/types"; 15 | 16 | // Query defines the gRPC querier service. 17 | service Query { 18 | // Params queries the parameters of othe racle module 19 | rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { 20 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/params"; 21 | } 22 | 23 | // PendingRounds queries the pending Rounds of the oracle module 24 | rpc PendingRounds(QueryPendingRoundsRequest) returns (QueryPendingRoundsResponse) { 25 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/pending_rounds/{claim_type}"; 26 | } 27 | 28 | // LastFinalizedRound queries the pending Rounds of the oracle module 29 | rpc LastFinalizedRound(QueryLastFinalizedRoundRequest) returns (QueryLastFinalizedRoundResponse) { 30 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/finalized_round/{claim_type}"; 31 | } 32 | 33 | // Vote queries the parameters of the oracle module 34 | rpc Round(QueryRoundRequest) returns (QueryRoundResponse) { 35 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/round/{claimType}/{roundId}"; 36 | } 37 | 38 | // Votes queries the parameters of the oracle module 39 | rpc AllRounds(QueryAllRoundsRequest) returns (QueryAllRoundsResponse) { 40 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/all_rounds"; 41 | } 42 | 43 | // AllClaims queries all claims with pagination. 44 | rpc AllClaims(QueryAllClaimsRequest) returns (QueryAllClaimsResponse){ 45 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/allclaims"; 46 | }; 47 | 48 | // Claim queries claims based on claim hash. 49 | rpc Claim(QueryClaimRequest) returns (QueryClaimResponse){ 50 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/claim/{claim_hash}"; 51 | }; 52 | 53 | rpc QueryDelegeateAddress(QueryDelegeateAddressRequest) returns (QueryDelegeateAddressResponse) { 54 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/delegate/{validator}"; 55 | } 56 | rpc QueryValidatorAddress(QueryValidatorAddressRequest) returns (QueryValidatorAddressResponse) { 57 | option (google.api.http).get = "/relevantcommunity/oracle/oracle/validator/{delegate}"; 58 | } 59 | } 60 | 61 | // QueryRoundResponse is the request type for the Query/Params RPC method 62 | message QueryRoundRequest { 63 | string claimType = 1; 64 | uint64 roundId = 2; 65 | } 66 | 67 | // QueryRoundResponse is the response type for the Query/Params RPC method 68 | message QueryRoundResponse { 69 | Round round = 1 [(gogoproto.nullable) = false]; 70 | } 71 | 72 | 73 | 74 | // QueryAllRoundsRequest is the request type for the Query/Params RPC method 75 | message QueryAllRoundsRequest { 76 | cosmos.base.query.v1beta1.PageRequest pagination = 1; 77 | } 78 | 79 | // QueryAllRoundsResponse is the response type for the Query/Params RPC method 80 | message QueryAllRoundsResponse { 81 | repeated Round rounds = 1 [(gogoproto.nullable) = false]; 82 | cosmos.base.query.v1beta1.PageResponse pagination = 2; 83 | } 84 | 85 | 86 | 87 | // QueryPendingRoundsRequest is the request type for the Query/Params RPC method 88 | message QueryPendingRoundsRequest { 89 | string claim_type = 1; 90 | } 91 | 92 | // QueryParamsResponse is the response type for the Query/Params RPC method 93 | message QueryPendingRoundsResponse { 94 | repeated uint64 pending_rounds = 1 [(gogoproto.nullable) = false]; 95 | } 96 | 97 | 98 | 99 | // LastFinalizedRround is the request type for the Query/Params RPC method 100 | message QueryLastFinalizedRoundRequest { 101 | string claim_type = 1; 102 | } 103 | 104 | // LastFinalizedRround is the response type for the Query/Params RPC method 105 | message QueryLastFinalizedRoundResponse { 106 | uint64 lastFinalizedRound = 1; 107 | } 108 | 109 | 110 | 111 | // QueryParamsRequest is the request type for the Query/Params RPC method 112 | message QueryParamsRequest {} 113 | 114 | // QueryParamsResponse is the response type for the Query/Params RPC method 115 | message QueryParamsResponse { 116 | Params params = 1 [(gogoproto.nullable) = false]; 117 | } 118 | 119 | 120 | 121 | // QueryClaimRequest is the request type for the Query/Claim RPC method 122 | message QueryClaimRequest { 123 | string claim_hash = 1; 124 | } 125 | 126 | // QueryClaimResponse is the response type for the Query/Claim RPC method. 127 | message QueryClaimResponse { 128 | // claim returns the requested claim. 129 | google.protobuf.Any claim = 1 [(cosmos_proto.accepts_interface) = "Claim"]; 130 | } 131 | 132 | 133 | 134 | // this line is used by starport scaffolding # 3 135 | 136 | // QueryAllClaimsRequest is the request type for the Query/AllClaims RPC method 137 | message QueryAllClaimsRequest { 138 | cosmos.base.query.v1beta1.PageRequest pagination = 1; 139 | } 140 | 141 | // QueryAllClaimsResponse is the response type for the Query/Claims RPC method. 142 | message QueryAllClaimsResponse { 143 | repeated google.protobuf.Any claims = 1; 144 | cosmos.base.query.v1beta1.PageResponse pagination = 2; 145 | } 146 | 147 | message QueryValidatorAddressRequest { 148 | string delegate = 1; 149 | } 150 | message QueryValidatorAddressResponse { 151 | string validator = 1; 152 | } 153 | 154 | message QueryDelegeateAddressRequest { 155 | string validator = 1; 156 | } 157 | message QueryDelegeateAddressResponse { 158 | string delegate = 2; 159 | } -------------------------------------------------------------------------------- /x/oracle/client/cli/worker.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "strconv" 9 | "sync" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/cosmos/cosmos-sdk/client" 14 | "github.com/cosmos/cosmos-sdk/client/flags" 15 | "github.com/spf13/cobra" 16 | tmcli "github.com/tendermint/tendermint/libs/cli" 17 | "github.com/tendermint/tendermint/rpc/client/http" 18 | ctypes "github.com/tendermint/tendermint/rpc/core/types" 19 | tendermint "github.com/tendermint/tendermint/types" 20 | "golang.org/x/sync/errgroup" 21 | ) 22 | 23 | var ( 24 | txQuery = "tm.event='Tx'" 25 | blockQuery = "tm.event='NewBlock'" 26 | once sync.Once 27 | ) 28 | 29 | // BlockHandler is the code that the worker must run every block 30 | type BlockHandler func(*cobra.Command, ctypes.ResultEvent) error 31 | 32 | // TxHandler is the code that the worker runs on every tx event 33 | type TxHandler func(*cobra.Command, ctypes.ResultEvent) error 34 | 35 | // Worker is a singleton type 36 | type Worker struct { 37 | handleBlock BlockHandler 38 | handleTx TxHandler 39 | } 40 | 41 | var instance *Worker 42 | 43 | // InitializeWorker intializes the singleton instance and sets the worker process 44 | // This method should be called from the app's cmd/root.go file 45 | func InitializeWorker(blockHandler BlockHandler, txHandler TxHandler) *Worker { 46 | instance = &Worker{ 47 | handleBlock: blockHandler, 48 | handleTx: txHandler, 49 | } 50 | return instance 51 | } 52 | 53 | // StartWorkerCmd starts the off-chain worker process 54 | func StartWorkerCmd() *cobra.Command { 55 | cmd := &cobra.Command{ 56 | Use: "start-worker [stopAtHeight]", 57 | Short: "Starts offchain worker", 58 | RunE: func(cmd *cobra.Command, args []string) error { 59 | 60 | if instance == nil { 61 | return fmt.Errorf("Worker process is not intialized, did you forget to initialize the worker and set handlers in root.go?") 62 | } 63 | 64 | // the process after stopAtHeight (used for testing) 65 | stopAtHeight := int64(0) 66 | var err error 67 | if len(args) > 0 { 68 | stopAtHeight, err = strconv.ParseInt(args[0], 10, 64) 69 | if err != nil { 70 | stopAtHeight = 0 71 | } 72 | } 73 | 74 | goctx, cancel := context.WithCancel(context.Background()) 75 | defer cancel() 76 | eg, goctx := errgroup.WithContext(goctx) 77 | 78 | eg.Go(func() error { 79 | return loop(goctx, cancel, cmd, stopAtHeight) 80 | }) 81 | eg.Go(func() error { 82 | return stopLoop(goctx, cancel) 83 | }) 84 | if err := eg.Wait(); err != nil { 85 | return err 86 | } 87 | return nil 88 | }, 89 | } 90 | 91 | flags.AddTxFlagsToCmd(cmd) 92 | cmd.Flags().StringP(tmcli.OutputFlag, "o", "text", "Output format (text|json)") 93 | return cmd 94 | } 95 | 96 | func loop(goctx context.Context, cancel context.CancelFunc, cmd *cobra.Command, stopAtHeight int64) error { 97 | var ( 98 | blockEvent <-chan ctypes.ResultEvent 99 | blockCancel context.CancelFunc 100 | 101 | txEvent <-chan ctypes.ResultEvent 102 | txCancel context.CancelFunc 103 | ) 104 | defer cancel() 105 | 106 | clientCtx, err := client.GetClientTxContext(cmd) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | if clientCtx.NodeURI == "" { 112 | return fmt.Errorf("Missing Tendermint Node URI") 113 | } 114 | 115 | rpcClient, _ := http.New(clientCtx.NodeURI, "/websocket") 116 | err = rpcClient.Start() 117 | 118 | // Retry if we don't have a connection 119 | for err != nil { 120 | fmt.Println("Could not connect to Tendermint node, retrying in 1 second.") 121 | time.Sleep(1 * time.Second) 122 | err = rpcClient.Start() 123 | } 124 | defer rpcClient.Stop() 125 | 126 | // subscribe to block events 127 | blockEvent, blockCancel = subscribe(goctx, rpcClient, blockQuery) 128 | defer blockCancel() 129 | 130 | // subscribe to tx events 131 | txEvent, txCancel = subscribe(goctx, rpcClient, txQuery) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | defer txCancel() 137 | 138 | // Loop on received messages. 139 | for { 140 | select { 141 | case block := <-blockEvent: 142 | // run the custom block handler 143 | if err = instance.handleBlock(cmd, block); err != nil { 144 | fmt.Println("There was an error handling a new block", err) 145 | } 146 | 147 | // cancel loop if we reach endBlockHeight (for testing) 148 | if blockHeight := 149 | block.Data.(tendermint.EventDataNewBlock).Block.Header.Height; stopAtHeight != 0 && stopAtHeight <= blockHeight { 150 | cancel() 151 | } 152 | 153 | case txEvent := <-txEvent: 154 | // run the custom tx handler 155 | if err = instance.handleTx(cmd, txEvent); err != nil { 156 | fmt.Println("There was an error handling a tx event", err) 157 | // return err 158 | } 159 | case <-goctx.Done(): 160 | return nil 161 | } 162 | } 163 | } 164 | 165 | // subscribe returns channel of events from the oracle chain given a query (TX or BLOCK) 166 | func subscribe(ctx context.Context, clnt *http.HTTP, query string) (<-chan ctypes.ResultEvent, context.CancelFunc) { 167 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 168 | ch, err := clnt.Subscribe(ctx, fmt.Sprintf("oracle-feeder"), query) 169 | 170 | for err != nil { 171 | fmt.Println("Could not subscribe to Tendermint event, retrying in 1 second.") 172 | time.Sleep(1 * time.Second) 173 | ch, err = clnt.Subscribe(ctx, fmt.Sprintf("oracle-feeder"), query) 174 | } 175 | 176 | return ch, cancel 177 | } 178 | 179 | // stopLoop waits for a SIGINT or SIGTERM and returns an error 180 | func stopLoop(ctx context.Context, cancel context.CancelFunc) error { 181 | sigCh := make(chan os.Signal, 1) 182 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 183 | for { 184 | select { 185 | case sig := <-sigCh: 186 | close(sigCh) 187 | cancel() 188 | fmt.Printf("Exiting feeder loop, recieved stop signal(%s)", sig.String()) 189 | return nil 190 | case <-ctx.Done(): 191 | close(sigCh) 192 | return nil 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /x/oracle/keeper/msg_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | staking "github.com/cosmos/cosmos-sdk/x/staking/types" 10 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 11 | "github.com/relevant-community/oracle/x/oracle/types" 12 | ) 13 | 14 | type msgServer struct { 15 | Keeper 16 | } 17 | 18 | // NewMsgServerImpl returns an implementation of the oracle MsgServer interface 19 | // for the provided Keeper. 20 | func NewMsgServerImpl(keeper Keeper) types.MsgServer { 21 | return &msgServer{Keeper: keeper} 22 | } 23 | 24 | var _ types.MsgServer = msgServer{} 25 | 26 | func (k msgServer) Vote(goCtx context.Context, msg *types.MsgVote) (*types.MsgVoteResponse, error) { 27 | ctx := sdk.UnwrapSDKContext(goCtx) 28 | 29 | claim := msg.GetClaim() 30 | signer := msg.MustGetSigner() 31 | valAddr := getValidatorAddr(ctx, k, signer) 32 | 33 | // make sure this message is submitted by a validator 34 | val := k.StakingKeeper.Validator(ctx, valAddr) 35 | if val == nil { 36 | return nil, sdkerrors.Wrap(staking.ErrNoValidatorFound, valAddr.String()) 37 | } 38 | 39 | prevoteHash, err := k.isCorrectRound(ctx, msg, signer) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | // store the validator vote 45 | k.CreateVote(ctx, claim, valAddr) 46 | 47 | ctx.EventManager().EmitEvent( 48 | sdk.NewEvent( 49 | sdk.EventTypeMessage, 50 | sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), 51 | sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypeVote), 52 | sdk.NewAttribute(sdk.AttributeKeySender, signer.String()), 53 | sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()), 54 | sdk.NewAttribute(types.AttributeKeyClaimHash, claim.Hash().String()), 55 | ), 56 | ) 57 | 58 | if len(prevoteHash) != 0 { 59 | k.DeletePrevote(ctx, prevoteHash) 60 | } 61 | 62 | return &types.MsgVoteResponse{ 63 | Hash: claim.Hash(), 64 | }, nil 65 | } 66 | 67 | // Delegate implements types.MsgServer 68 | func (k msgServer) Delegate(c context.Context, msg *types.MsgDelegate) (*types.MsgDelegateResponse, error) { 69 | ctx := sdk.UnwrapSDKContext(c) 70 | 71 | val, del := msg.MustGetValidator(), msg.MustGetDelegate() 72 | 73 | if k.Keeper.StakingKeeper.Validator(ctx, sdk.ValAddress(val)) == nil { 74 | return nil, sdkerrors.Wrap(stakingtypes.ErrNoValidatorFound, val.String()) 75 | } 76 | 77 | k.SetValidatorDelegateAddress(ctx, val, del) 78 | 79 | ctx.EventManager().EmitEvent( 80 | sdk.NewEvent( 81 | sdk.EventTypeMessage, 82 | sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), 83 | sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypeDelegate), 84 | sdk.NewAttribute(sdk.AttributeKeySender, msg.Validator), 85 | sdk.NewAttribute(types.AttributeKeyValidator, msg.Validator), 86 | sdk.NewAttribute(types.AttributeKeyDelegate, msg.Delegate), 87 | ), 88 | ) 89 | 90 | return &types.MsgDelegateResponse{}, nil 91 | } 92 | 93 | func (k msgServer) Prevote(goCtx context.Context, msg *types.MsgPrevote) (*types.MsgPrevoteResponse, error) { 94 | ctx := sdk.UnwrapSDKContext(goCtx) 95 | signer := msg.MustGetSigner() 96 | 97 | valAddr := getValidatorAddr(ctx, k, signer) 98 | 99 | // make sure this message is submitted by a validator 100 | val := k.StakingKeeper.Validator(ctx, valAddr) 101 | if val == nil { 102 | return nil, sdkerrors.Wrap(staking.ErrNoValidatorFound, valAddr.String()) 103 | } 104 | 105 | k.CreatePrevote(ctx, msg.Hash) 106 | 107 | ctx.EventManager().EmitEvent( 108 | sdk.NewEvent( 109 | sdk.EventTypeMessage, 110 | sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), 111 | sdk.NewAttribute(sdk.AttributeKeyAction, types.EventTypePrevote), 112 | sdk.NewAttribute(sdk.AttributeKeySender, signer.String()), 113 | sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()), 114 | sdk.NewAttribute(types.AttributeKeyPrevoteHash, fmt.Sprintf("%x", msg.Hash)), 115 | ), 116 | ) 117 | 118 | return &types.MsgPrevoteResponse{}, nil 119 | } 120 | 121 | // HELPERS 122 | 123 | func (k msgServer) isCorrectRound(ctx sdk.Context, msg *types.MsgVote, signer sdk.AccAddress) ([]byte, error) { 124 | claim := msg.GetClaim() 125 | claimType := claim.Type() 126 | 127 | // will return empty struct if doesn't exist 128 | claimParams := k.ClaimParamsForType(ctx, claimType) 129 | if claimParams.ClaimType != claimType { 130 | return nil, sdkerrors.Wrap(types.ErrNoClaimTypeExists, claimType) 131 | } 132 | 133 | claimRoundID := claim.GetRoundID() 134 | lastFinalizedRound := k.GetLastFinalizedRound(ctx, claimType) 135 | 136 | // RoundID should be greater than the LastFinalizedRound 137 | if claimRoundID <= lastFinalizedRound { 138 | return []byte{}, sdkerrors.Wrap( 139 | types.ErrIncorrectClaimRound, 140 | fmt.Sprintf("expected current round %d, to be greater than last finalized round %d", claimRoundID, lastFinalizedRound), 141 | ) 142 | } 143 | 144 | // if no prevote we are done 145 | if claimParams.Prevote != true { 146 | return []byte{}, nil 147 | } 148 | 149 | // when using prevote claims must be submited only after the prevote round 150 | // claim.RoundID + VotePeriod >= currentRound 151 | currentRound := k.GetCurrentRound(ctx, claimType) 152 | if claimRoundID+claimParams.VotePeriod < currentRound { 153 | return []byte{}, sdkerrors.Wrap(types.ErrIncorrectClaimRound, fmt.Sprintf("expected %d, got %d", currentRound-claimParams.VotePeriod, claimRoundID)) 154 | } 155 | 156 | prevoteHash := types.VoteHash(msg.Salt, claim.Hash().String(), signer) 157 | hasPrevote := k.HasPrevote(ctx, prevoteHash) 158 | if hasPrevote == false { 159 | return []byte{}, sdkerrors.Wrap(types.ErrNoPrevote, claim.Hash().String()) 160 | } 161 | 162 | return prevoteHash, nil 163 | } 164 | 165 | func getValidatorAddr(ctx sdk.Context, k msgServer, signer sdk.AccAddress) sdk.ValAddress { 166 | // get delegator's validator 167 | valAddr := sdk.ValAddress(k.GetValidatorAddressFromDelegate(ctx, signer)) 168 | 169 | // if there is no delegation it must be the validator 170 | if valAddr == nil { 171 | valAddr = sdk.ValAddress(signer) 172 | } 173 | 174 | return valAddr 175 | } 176 | -------------------------------------------------------------------------------- /x/oracle/module.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | // this line is used by starport scaffolding # 1 9 | 10 | "github.com/gorilla/mux" 11 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 12 | "github.com/spf13/cobra" 13 | 14 | abci "github.com/tendermint/tendermint/abci/types" 15 | 16 | "github.com/cosmos/cosmos-sdk/client" 17 | "github.com/cosmos/cosmos-sdk/codec" 18 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 19 | sdk "github.com/cosmos/cosmos-sdk/types" 20 | "github.com/cosmos/cosmos-sdk/types/module" 21 | "github.com/relevant-community/oracle/x/oracle/client/cli" 22 | "github.com/relevant-community/oracle/x/oracle/keeper" 23 | "github.com/relevant-community/oracle/x/oracle/types" 24 | ) 25 | 26 | var ( 27 | _ module.AppModule = AppModule{} 28 | _ module.AppModuleBasic = AppModuleBasic{} 29 | ) 30 | 31 | // ---------------------------------------------------------------------------- 32 | // AppModuleBasic 33 | // ---------------------------------------------------------------------------- 34 | 35 | // AppModuleBasic implements the AppModuleBasic interface for the capability module. 36 | type AppModuleBasic struct { 37 | cdc codec.Marshaler 38 | } 39 | 40 | func NewAppModuleBasic(cdc codec.Marshaler) AppModuleBasic { 41 | return AppModuleBasic{cdc: cdc} 42 | } 43 | 44 | // Name returns the capability module's name. 45 | func (AppModuleBasic) Name() string { 46 | return types.ModuleName 47 | } 48 | 49 | func (AppModuleBasic) RegisterCodec(cdc *codec.LegacyAmino) { 50 | types.RegisterLegacyAminoCodec(cdc) 51 | } 52 | 53 | func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 54 | types.RegisterLegacyAminoCodec(cdc) 55 | } 56 | 57 | // RegisterInterfaces registers the module's interface types 58 | func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { 59 | types.RegisterInterfaces(reg) 60 | } 61 | 62 | // DefaultGenesis returns the capability module's default genesis state. 63 | func (AppModuleBasic) DefaultGenesis(cdc codec.JSONMarshaler) json.RawMessage { 64 | return cdc.MustMarshalJSON(types.DefaultGenesis()) 65 | } 66 | 67 | // ValidateGenesis performs genesis state validation for the capability module. 68 | func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxEncodingConfig, bz json.RawMessage) error { 69 | var genState types.GenesisState 70 | if err := cdc.UnmarshalJSON(bz, &genState); err != nil { 71 | return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) 72 | } 73 | return genState.Validate() 74 | } 75 | 76 | // RegisterRESTRoutes registers the capability module's REST service handlers. 77 | func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {} 78 | 79 | // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. 80 | func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { 81 | types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) 82 | } 83 | 84 | // GetTxCmd returns the capability module's root tx command. 85 | func (a AppModuleBasic) GetTxCmd() *cobra.Command { 86 | return cli.GetTxCmd() 87 | } 88 | 89 | // GetQueryCmd returns the capability module's root query command. 90 | func (AppModuleBasic) GetQueryCmd() *cobra.Command { 91 | return cli.GetQueryCmd(types.StoreKey) 92 | } 93 | 94 | // ---------------------------------------------------------------------------- 95 | // AppModule 96 | // ---------------------------------------------------------------------------- 97 | 98 | // AppModule implements the AppModule interface for the capability module. 99 | type AppModule struct { 100 | AppModuleBasic 101 | 102 | keeper keeper.Keeper 103 | } 104 | 105 | func NewAppModule(cdc codec.Marshaler, keeper keeper.Keeper) AppModule { 106 | return AppModule{ 107 | AppModuleBasic: NewAppModuleBasic(cdc), 108 | keeper: keeper, 109 | } 110 | } 111 | 112 | // Name returns the capability module's name. 113 | func (am AppModule) Name() string { 114 | return am.AppModuleBasic.Name() 115 | } 116 | 117 | // Route returns the capability module's message routing key. 118 | func (am AppModule) Route() sdk.Route { 119 | return sdk.NewRoute(types.RouterKey, NewHandler(am.keeper)) 120 | } 121 | 122 | // QuerierRoute returns the capability module's query routing key. 123 | func (AppModule) QuerierRoute() string { return types.QuerierRoute } 124 | 125 | // LegacyQuerierHandler returns the capability module's Querier. 126 | func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { 127 | return nil 128 | } 129 | 130 | // RegisterServices registers a GRPC query service to respond to the 131 | // module-specific GRPC queries. 132 | func (am AppModule) RegisterServices(cfg module.Configurator) { 133 | types.RegisterQueryServer(cfg.QueryServer(), am.keeper) 134 | } 135 | 136 | // RegisterInvariants registers the capability module's invariants. 137 | func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} 138 | 139 | // InitGenesis performs the capability module's genesis initialization It returns 140 | // no validator updates. 141 | func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, gs json.RawMessage) []abci.ValidatorUpdate { 142 | var genState types.GenesisState 143 | // Initialize global index to index in genesis state 144 | cdc.MustUnmarshalJSON(gs, &genState) 145 | 146 | InitGenesis(ctx, am.keeper, genState) 147 | 148 | return []abci.ValidatorUpdate{} 149 | } 150 | 151 | // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. 152 | func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage { 153 | genState := ExportGenesis(ctx, am.keeper) 154 | return cdc.MustMarshalJSON(genState) 155 | } 156 | 157 | // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. 158 | func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} 159 | 160 | // EndBlock executes all ABCI EndBlock logic respective to the capability module. It 161 | // returns no validator updates. 162 | func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { 163 | return []abci.ValidatorUpdate{} 164 | } 165 | -------------------------------------------------------------------------------- /x/atom/types/query.pb.gw.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. 2 | // source: atom/query.proto 3 | 4 | /* 5 | Package types is a reverse proxy. 6 | 7 | It translates gRPC into RESTful JSON APIs. 8 | */ 9 | package types 10 | 11 | import ( 12 | "context" 13 | "io" 14 | "net/http" 15 | 16 | "github.com/golang/protobuf/descriptor" 17 | "github.com/golang/protobuf/proto" 18 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 19 | "github.com/grpc-ecosystem/grpc-gateway/utilities" 20 | "google.golang.org/grpc" 21 | "google.golang.org/grpc/codes" 22 | "google.golang.org/grpc/grpclog" 23 | "google.golang.org/grpc/metadata" 24 | "google.golang.org/grpc/status" 25 | ) 26 | 27 | // Suppress "imported and not used" errors 28 | var _ codes.Code 29 | var _ io.Reader 30 | var _ status.Status 31 | var _ = runtime.String 32 | var _ = utilities.NewDoubleArray 33 | var _ = descriptor.ForMessage 34 | var _ = metadata.Join 35 | 36 | func request_Query_AtomUsd_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { 37 | var protoReq QueryAtomUsdRequest 38 | var metadata runtime.ServerMetadata 39 | 40 | msg, err := client.AtomUsd(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) 41 | return msg, metadata, err 42 | 43 | } 44 | 45 | func local_request_Query_AtomUsd_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { 46 | var protoReq QueryAtomUsdRequest 47 | var metadata runtime.ServerMetadata 48 | 49 | msg, err := server.AtomUsd(ctx, &protoReq) 50 | return msg, metadata, err 51 | 52 | } 53 | 54 | // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". 55 | // UnaryRPC :call QueryServer directly. 56 | // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. 57 | // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. 58 | func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { 59 | 60 | mux.Handle("GET", pattern_Query_AtomUsd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { 61 | ctx, cancel := context.WithCancel(req.Context()) 62 | defer cancel() 63 | var stream runtime.ServerTransportStream 64 | ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) 65 | inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) 66 | rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) 67 | if err != nil { 68 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 69 | return 70 | } 71 | resp, md, err := local_request_Query_AtomUsd_0(rctx, inboundMarshaler, server, req, pathParams) 72 | md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) 73 | ctx = runtime.NewServerMetadataContext(ctx, md) 74 | if err != nil { 75 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 76 | return 77 | } 78 | 79 | forward_Query_AtomUsd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) 80 | 81 | }) 82 | 83 | return nil 84 | } 85 | 86 | // RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but 87 | // automatically dials to "endpoint" and closes the connection when "ctx" gets done. 88 | func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { 89 | conn, err := grpc.Dial(endpoint, opts...) 90 | if err != nil { 91 | return err 92 | } 93 | defer func() { 94 | if err != nil { 95 | if cerr := conn.Close(); cerr != nil { 96 | grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) 97 | } 98 | return 99 | } 100 | go func() { 101 | <-ctx.Done() 102 | if cerr := conn.Close(); cerr != nil { 103 | grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) 104 | } 105 | }() 106 | }() 107 | 108 | return RegisterQueryHandler(ctx, mux, conn) 109 | } 110 | 111 | // RegisterQueryHandler registers the http handlers for service Query to "mux". 112 | // The handlers forward requests to the grpc endpoint over "conn". 113 | func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { 114 | return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) 115 | } 116 | 117 | // RegisterQueryHandlerClient registers the http handlers for service Query 118 | // to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". 119 | // Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" 120 | // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in 121 | // "QueryClient" to call the correct interceptors. 122 | func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { 123 | 124 | mux.Handle("GET", pattern_Query_AtomUsd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { 125 | ctx, cancel := context.WithCancel(req.Context()) 126 | defer cancel() 127 | inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) 128 | rctx, err := runtime.AnnotateContext(ctx, mux, req) 129 | if err != nil { 130 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 131 | return 132 | } 133 | resp, md, err := request_Query_AtomUsd_0(rctx, inboundMarshaler, client, req, pathParams) 134 | ctx = runtime.NewServerMetadataContext(ctx, md) 135 | if err != nil { 136 | runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) 137 | return 138 | } 139 | 140 | forward_Query_AtomUsd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) 141 | 142 | }) 143 | 144 | return nil 145 | } 146 | 147 | var ( 148 | pattern_Query_AtomUsd_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"relevantcommunity", "oracle", "atome", "atomusd"}, "", runtime.AssumeColonVerbOpt(true))) 149 | ) 150 | 151 | var ( 152 | forward_Query_AtomUsd_0 = runtime.ForwardResponseMessage 153 | ) 154 | -------------------------------------------------------------------------------- /x/atom/module.go: -------------------------------------------------------------------------------- 1 | package atom 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | // this line is used by starport scaffolding # 1 8 | 9 | "github.com/gorilla/mux" 10 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 11 | "github.com/spf13/cobra" 12 | 13 | abci "github.com/tendermint/tendermint/abci/types" 14 | 15 | "github.com/cosmos/cosmos-sdk/client" 16 | "github.com/cosmos/cosmos-sdk/codec" 17 | cdctypes "github.com/cosmos/cosmos-sdk/codec/types" 18 | sdk "github.com/cosmos/cosmos-sdk/types" 19 | "github.com/cosmos/cosmos-sdk/types/module" 20 | "github.com/relevant-community/oracle/x/atom/client/cli" 21 | "github.com/relevant-community/oracle/x/atom/client/rest" 22 | "github.com/relevant-community/oracle/x/atom/keeper" 23 | "github.com/relevant-community/oracle/x/atom/types" 24 | ) 25 | 26 | var ( 27 | _ module.AppModule = AppModule{} 28 | _ module.AppModuleBasic = AppModuleBasic{} 29 | ) 30 | 31 | // ---------------------------------------------------------------------------- 32 | // AppModuleBasic 33 | // ---------------------------------------------------------------------------- 34 | 35 | // AppModuleBasic implements the AppModuleBasic interface for the capability module. 36 | type AppModuleBasic struct { 37 | cdc codec.Marshaler 38 | } 39 | 40 | func NewAppModuleBasic(cdc codec.Marshaler) AppModuleBasic { 41 | return AppModuleBasic{cdc: cdc} 42 | } 43 | 44 | // Name returns the capability module's name. 45 | func (AppModuleBasic) Name() string { 46 | return types.ModuleName 47 | } 48 | 49 | func (AppModuleBasic) RegisterCodec(cdc *codec.LegacyAmino) { 50 | types.RegisterCodec(cdc) 51 | } 52 | 53 | func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 54 | types.RegisterCodec(cdc) 55 | } 56 | 57 | // RegisterInterfaces registers the module's interface types 58 | func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { 59 | types.RegisterInterfaces(reg) 60 | } 61 | 62 | // DefaultGenesis returns the capability module's default genesis state. 63 | func (AppModuleBasic) DefaultGenesis(cdc codec.JSONMarshaler) json.RawMessage { 64 | return cdc.MustMarshalJSON(types.DefaultGenesis()) 65 | } 66 | 67 | // ValidateGenesis performs genesis state validation for the capability module. 68 | func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, config client.TxEncodingConfig, bz json.RawMessage) error { 69 | var genState types.GenesisState 70 | if err := cdc.UnmarshalJSON(bz, &genState); err != nil { 71 | return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) 72 | } 73 | return genState.Validate() 74 | } 75 | 76 | // RegisterRESTRoutes registers the capability module's REST service handlers. 77 | func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { 78 | rest.RegisterRoutes(clientCtx, rtr) 79 | } 80 | 81 | // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. 82 | func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { 83 | // this line is used by starport scaffolding # 2 84 | } 85 | 86 | // GetTxCmd returns the capability module's root tx command. 87 | func (a AppModuleBasic) GetTxCmd() *cobra.Command { 88 | return cli.GetTxCmd() 89 | } 90 | 91 | // GetQueryCmd returns the capability module's root query command. 92 | func (AppModuleBasic) GetQueryCmd() *cobra.Command { 93 | return cli.GetQueryCmd(types.StoreKey) 94 | } 95 | 96 | // ---------------------------------------------------------------------------- 97 | // AppModule 98 | // ---------------------------------------------------------------------------- 99 | 100 | // AppModule implements the AppModule interface for the capability module. 101 | type AppModule struct { 102 | AppModuleBasic 103 | 104 | keeper keeper.Keeper 105 | } 106 | 107 | func NewAppModule(cdc codec.Marshaler, keeper keeper.Keeper) AppModule { 108 | return AppModule{ 109 | AppModuleBasic: NewAppModuleBasic(cdc), 110 | keeper: keeper, 111 | } 112 | } 113 | 114 | // Name returns the capability module's name. 115 | func (am AppModule) Name() string { 116 | return am.AppModuleBasic.Name() 117 | } 118 | 119 | // Route returns the capability module's message routing key. 120 | func (am AppModule) Route() sdk.Route { 121 | return sdk.NewRoute(types.RouterKey, NewHandler(am.keeper)) 122 | } 123 | 124 | // QuerierRoute returns the capability module's query routing key. 125 | func (AppModule) QuerierRoute() string { return types.QuerierRoute } 126 | 127 | // LegacyQuerierHandler returns the capability module's Querier. 128 | func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { 129 | return keeper.NewQuerier(am.keeper, legacyQuerierCdc) 130 | } 131 | 132 | // RegisterServices registers a GRPC query service to respond to the 133 | // module-specific GRPC queries. 134 | func (am AppModule) RegisterServices(cfg module.Configurator) { 135 | types.RegisterQueryServer(cfg.QueryServer(), am.keeper) 136 | } 137 | 138 | // RegisterInvariants registers the capability module's invariants. 139 | func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} 140 | 141 | // InitGenesis performs the capability module's genesis initialization It returns 142 | // no validator updates. 143 | func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, gs json.RawMessage) []abci.ValidatorUpdate { 144 | var genState types.GenesisState 145 | // Initialize global index to index in genesis state 146 | cdc.MustUnmarshalJSON(gs, &genState) 147 | 148 | InitGenesis(ctx, am.keeper, genState) 149 | 150 | return []abci.ValidatorUpdate{} 151 | } 152 | 153 | // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. 154 | func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage { 155 | genState := ExportGenesis(ctx, am.keeper) 156 | return cdc.MustMarshalJSON(genState) 157 | } 158 | 159 | // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. 160 | func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} 161 | 162 | // EndBlock executes all ABCI EndBlock logic respective to the capability module. It 163 | // returns no validator updates. 164 | func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { 165 | am.keeper.UpdateAtomUsd(ctx) 166 | return []abci.ValidatorUpdate{} 167 | } 168 | -------------------------------------------------------------------------------- /app/export.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | 7 | tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 8 | 9 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 10 | sdk "github.com/cosmos/cosmos-sdk/types" 11 | slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" 12 | "github.com/cosmos/cosmos-sdk/x/staking" 13 | stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" 14 | ) 15 | 16 | // ExportAppStateAndValidators exports the state of the application for a genesis 17 | // file. 18 | func (app *App) ExportAppStateAndValidators( 19 | forZeroHeight bool, jailAllowedAddrs []string, 20 | ) (servertypes.ExportedApp, error) { 21 | 22 | // as if they could withdraw from the start of the next block 23 | ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) 24 | 25 | // We export at last height + 1, because that's the height at which 26 | // Tendermint will start InitChain. 27 | height := app.LastBlockHeight() + 1 28 | if forZeroHeight { 29 | height = 0 30 | app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) 31 | } 32 | 33 | genState := app.mm.ExportGenesis(ctx, app.appCodec) 34 | appState, err := json.MarshalIndent(genState, "", " ") 35 | if err != nil { 36 | return servertypes.ExportedApp{}, err 37 | } 38 | 39 | validators, err := staking.WriteValidators(ctx, app.StakingKeeper) 40 | if err != nil { 41 | return servertypes.ExportedApp{}, err 42 | } 43 | return servertypes.ExportedApp{ 44 | AppState: appState, 45 | Validators: validators, 46 | Height: height, 47 | ConsensusParams: app.BaseApp.GetConsensusParams(ctx), 48 | }, nil 49 | } 50 | 51 | // prepare for fresh start at zero height 52 | // NOTE zero height genesis is a temporary feature which will be deprecated 53 | // in favour of export at a block height 54 | func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { 55 | applyAllowedAddrs := false 56 | 57 | // check if there is a allowed address list 58 | if len(jailAllowedAddrs) > 0 { 59 | applyAllowedAddrs = true 60 | } 61 | 62 | allowedAddrsMap := make(map[string]bool) 63 | 64 | for _, addr := range jailAllowedAddrs { 65 | _, err := sdk.ValAddressFromBech32(addr) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | allowedAddrsMap[addr] = true 70 | } 71 | 72 | /* Just to be safe, assert the invariants on current state. */ 73 | app.CrisisKeeper.AssertInvariants(ctx) 74 | 75 | /* Handle fee distribution state. */ 76 | 77 | // withdraw all validator commission 78 | app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 79 | _, err := app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) 80 | if err != nil { 81 | panic(err) 82 | } 83 | return false 84 | }) 85 | 86 | // withdraw all delegator rewards 87 | dels := app.StakingKeeper.GetAllDelegations(ctx) 88 | for _, delegation := range dels { 89 | _, err := app.DistrKeeper.WithdrawDelegationRewards(ctx, delegation.GetDelegatorAddr(), delegation.GetValidatorAddr()) 90 | if err != nil { 91 | panic(err) 92 | } 93 | } 94 | 95 | // clear validator slash events 96 | app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) 97 | 98 | // clear validator historical rewards 99 | app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) 100 | 101 | // set context height to zero 102 | height := ctx.BlockHeight() 103 | ctx = ctx.WithBlockHeight(0) 104 | 105 | // reinitialize all validators 106 | app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { 107 | // donate any unwithdrawn outstanding reward fraction tokens to the community pool 108 | scraps := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator()) 109 | feePool := app.DistrKeeper.GetFeePool(ctx) 110 | feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) 111 | app.DistrKeeper.SetFeePool(ctx, feePool) 112 | 113 | app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) 114 | return false 115 | }) 116 | 117 | // reinitialize all delegations 118 | for _, del := range dels { 119 | app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, del.GetDelegatorAddr(), del.GetValidatorAddr()) 120 | app.DistrKeeper.Hooks().AfterDelegationModified(ctx, del.GetDelegatorAddr(), del.GetValidatorAddr()) 121 | } 122 | 123 | // reset context height 124 | ctx = ctx.WithBlockHeight(height) 125 | 126 | /* Handle staking state. */ 127 | 128 | // iterate through redelegations, reset creation height 129 | app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { 130 | for i := range red.Entries { 131 | red.Entries[i].CreationHeight = 0 132 | } 133 | app.StakingKeeper.SetRedelegation(ctx, red) 134 | return false 135 | }) 136 | 137 | // iterate through unbonding delegations, reset creation height 138 | app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { 139 | for i := range ubd.Entries { 140 | ubd.Entries[i].CreationHeight = 0 141 | } 142 | app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) 143 | return false 144 | }) 145 | 146 | // Iterate through validators by power descending, reset bond heights, and 147 | // update bond intra-tx counters. 148 | store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) 149 | iter := sdk.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) 150 | counter := int16(0) 151 | 152 | for ; iter.Valid(); iter.Next() { 153 | addr := sdk.ValAddress(iter.Key()[1:]) 154 | validator, found := app.StakingKeeper.GetValidator(ctx, addr) 155 | if !found { 156 | panic("expected validator, not found") 157 | } 158 | 159 | validator.UnbondingHeight = 0 160 | if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { 161 | validator.Jailed = true 162 | } 163 | 164 | app.StakingKeeper.SetValidator(ctx, validator) 165 | counter++ 166 | } 167 | 168 | iter.Close() 169 | 170 | if _, err := app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx); err != nil { 171 | panic(err) 172 | } 173 | 174 | /* Handle slashing state. */ 175 | 176 | // reset start height on signing infos 177 | app.SlashingKeeper.IterateValidatorSigningInfos( 178 | ctx, 179 | func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { 180 | info.StartHeight = 0 181 | app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) 182 | return false 183 | }, 184 | ) 185 | } 186 | --------------------------------------------------------------------------------