├── 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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------