├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── lint.yml │ ├── md-link-checker.yml │ └── misspell.yml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── banner.png ├── go.work.example ├── scripts └── go-lint-all.bash └── tutorials ├── base ├── Makefile ├── app │ ├── app.go │ ├── app.yaml │ ├── export.go │ └── params │ │ ├── config.go │ │ └── encoding.go ├── cmd │ └── exampled │ │ ├── cmd │ │ ├── commands.go │ │ └── root.go │ │ └── main.go ├── go.mod ├── go.sum └── scripts │ └── init.sh ├── nameservice ├── base │ ├── Makefile │ ├── app │ │ ├── app.go │ │ ├── app.yaml │ │ ├── export.go │ │ ├── genesis.go │ │ └── params │ │ │ ├── config.go │ │ │ └── encoding.go │ ├── cmd │ │ └── exampled │ │ │ ├── cmd │ │ │ ├── cmd.go │ │ │ └── commands.go │ │ │ └── main.go │ ├── go.mod │ ├── go.sum │ ├── scripts │ │ └── init.sh │ └── x │ │ └── auction │ │ ├── Makefile │ │ ├── abci │ │ ├── proposal.go │ │ ├── types.go │ │ └── vote_ext.go │ │ ├── api │ │ ├── module │ │ │ └── module.pulsar.go │ │ └── v1 │ │ │ ├── event.pulsar.go │ │ │ ├── genesis.pulsar.go │ │ │ ├── query.pulsar.go │ │ │ ├── query_grpc.pb.go │ │ │ ├── tx.pulsar.go │ │ │ ├── tx_grpc.pb.go │ │ │ └── types.pulsar.go │ │ ├── codec.go │ │ ├── errors.go │ │ ├── event.pb.go │ │ ├── expected_keepers.go │ │ ├── flags.go │ │ ├── genesis.go │ │ ├── genesis.pb.go │ │ ├── keeper │ │ ├── genesis.go │ │ ├── genesis_test.go │ │ ├── keeper.go │ │ ├── migrator.go │ │ ├── msg_server.go │ │ ├── msg_server_test.go │ │ ├── nameservice.go │ │ ├── query_server.go │ │ └── query_server_test.go │ │ ├── keys.go │ │ ├── mempool │ │ ├── mempool.go │ │ └── testutil.go │ │ ├── module │ │ ├── autocli.go │ │ ├── cli.go │ │ ├── depinject.go │ │ └── module.go │ │ ├── proto │ │ ├── buf.gen.gogo.yaml │ │ ├── buf.gen.pulsar.yaml │ │ ├── buf.lock │ │ ├── buf.yaml │ │ └── cosmos │ │ │ └── auction │ │ │ ├── module │ │ │ └── module.proto │ │ │ └── v1 │ │ │ ├── event.proto │ │ │ ├── genesis.proto │ │ │ ├── query.proto │ │ │ ├── tx.proto │ │ │ └── types.proto │ │ ├── provider │ │ └── provider.go │ │ ├── query.pb.go │ │ ├── query.pb.gw.go │ │ ├── scripts │ │ ├── configure.sh │ │ ├── list-beacon-keys.sh │ │ ├── protocgen.sh │ │ ├── query-beacon-status.sh │ │ ├── reserve.sh │ │ ├── single_node │ │ │ ├── frontrun.sh │ │ │ └── setup.sh │ │ ├── stop.sh │ │ ├── vars.sh │ │ └── whois.sh │ │ ├── testutils │ │ └── encoding.go │ │ ├── tx.pb.go │ │ └── types.pb.go └── docs │ ├── 00-getting-started.md │ ├── 01-understanding-frontrunning.md │ ├── 02-mitigating-front-running-with-vote-extesions.md │ └── 03-demo-of-mitigating-front-running.md └── oracle ├── base ├── Makefile ├── app │ ├── app.go │ ├── app.yaml │ ├── export.go │ └── params │ │ ├── config.go │ │ └── encoding.go ├── cmd │ └── exampled │ │ ├── cmd │ │ ├── commands.go │ │ └── root.go │ │ └── main.go ├── go.mod ├── go.sum ├── scripts │ └── init.sh └── x │ └── oracle │ ├── Makefile │ ├── abci │ ├── proposal.go │ ├── provider_aggregator.go │ └── vote_extensions.go │ ├── api │ ├── module │ │ └── v1 │ │ │ └── module.pulsar.go │ └── v1 │ │ ├── query.pulsar.go │ │ ├── query_grpc.pb.go │ │ ├── tx.pulsar.go │ │ ├── tx_grpc.pb.go │ │ └── types.pulsar.go │ ├── codec.go │ ├── errors.go │ ├── genesis.go │ ├── integration │ ├── go.mod │ ├── go.sum │ └── integration_test.go │ ├── keeper │ ├── genesis.go │ ├── genesis_test.go │ ├── keeper.go │ ├── keeper_test.go │ ├── migrator.go │ ├── msg_server.go │ ├── msg_server_test.go │ ├── query_server.go │ └── query_server_test.go │ ├── keys.go │ ├── mockprovider │ └── provider.go │ ├── module │ ├── autocli.go │ ├── depinject.go │ └── module.go │ ├── proto │ ├── buf.gen.gogo.yaml │ ├── buf.gen.pulsar.yaml │ ├── buf.lock │ ├── buf.yaml │ └── cosmos │ │ └── oracle │ │ ├── module │ │ └── v1 │ │ │ └── module.proto │ │ └── v1 │ │ ├── query.proto │ │ ├── tx.proto │ │ └── types.proto │ ├── query.pb.go │ ├── query.pb.gw.go │ ├── scripts │ └── protocgen.sh │ ├── tx.pb.go │ └── types.pb.go └── docs ├── 00-getting-started.md ├── 01-what-is-an-oracle.md ├── 02-implementing-vote-extensions.md └── 03-testing-oracle.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS: 2 | 3 | @samricotta 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Closes: #XXXX 4 | 5 | 7 | 8 | --- 9 | 10 | ## Author Checklist 11 | 12 | *All items are required. Please add a note to the item if the item is not applicable and 13 | please add links to any relevant follow up issues.* 14 | 15 | I have... 16 | 17 | * [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title 18 | * [ ] confirmed `!` in the type prefix if API or client breaking change 19 | * [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting)) 20 | * [ ] provided a link to the relevant issue or specification 21 | * [ ] reviewed "Files changed" and left comments if necessary 22 | * [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing) 23 | * [ ] added a changelog entry to `CHANGELOG.md` 24 | * [ ] updated the relevant documentation or specification, including comments for [documenting Go code](https://blog.golang.org/godoc) 25 | * [ ] confirmed all CI checks have passed 26 | 27 | ## Reviewers Checklist 28 | 29 | *All items are required. Please add a note if the item is not applicable and please add 30 | your handle next to the items reviewed if you only reviewed selected items.* 31 | 32 | I have... 33 | 34 | * [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title 35 | * [ ] confirmed all author checklist items have been addressed 36 | * [ ] reviewed state machine logic, API design and naming, documentation is accurate, tests and test coverage 37 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - release/** 7 | pull_request: 8 | merge_group: 9 | permissions: 10 | contents: read 11 | jobs: 12 | golangci: 13 | name: golangci-lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: DeterminateSystems/nix-installer-action@main 18 | - uses: DeterminateSystems/magic-nix-cache-action@main 19 | - uses: actions/setup-go@v5 20 | with: 21 | go-version: "1.21" 22 | check-latest: true 23 | - name: run linting (long) 24 | if: env.GIT_DIFF 25 | id: lint_long 26 | run: | 27 | make lint 28 | -------------------------------------------------------------------------------- /.github/workflows/md-link-checker.yml: -------------------------------------------------------------------------------- 1 | name: Check Markdown links 2 | on: 3 | pull_request: 4 | jobs: 5 | markdown-link-check: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - uses: gaurav-nelson/github-action-markdown-link-check@1.0.15 10 | -------------------------------------------------------------------------------- /.github/workflows/misspell.yml: -------------------------------------------------------------------------------- 1 | name: Fix typos nightly 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Run codespell 12 | continue-on-error: true 13 | run: | 14 | sudo apt-get install codespell -y 15 | codespell -w --skip="*.pulsar.go,*.pb.go,*.pb.gw.go,*.cosmos_orm.go,*.json,*.git,*.js,crypto/keys,fuzz,*.h,proto/tendermint,*.bin" --ignore-words=.github/.codespellignore 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/exampled 2 | build/ 3 | data/ 4 | config/ 5 | Library/ 6 | go.work 7 | go.work.sum 8 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | timeout: 15m 4 | allow-parallel-runners: true 5 | skip-files: 6 | - ".*\\.pb\\.go$" 7 | - ".*\\.pb\\.gw\\.go$" 8 | - ".*\\.pulsar\\.go$" 9 | 10 | linters: 11 | disable-all: true 12 | enable: 13 | - dogsled 14 | - errcheck 15 | - exportloopref 16 | - gci 17 | - goconst 18 | - gocritic 19 | - gofumpt 20 | - gosec 21 | - gosimple 22 | - govet 23 | - ineffassign 24 | - misspell 25 | - nakedret 26 | - nolintlint 27 | - revive 28 | - staticcheck 29 | - stylecheck 30 | - thelper 31 | - typecheck 32 | - unconvert 33 | - unused 34 | 35 | issues: 36 | exclude-rules: 37 | - text: "Use of weak random number generator" 38 | linters: 39 | - gosec 40 | - text: "ST1003:" 41 | linters: 42 | - stylecheck 43 | # FIXME: Disabled until golangci-lint updates stylecheck with this fix: 44 | # https://github.com/dominikh/go-tools/issues/389 45 | - text: "ST1016:" 46 | linters: 47 | - stylecheck 48 | - text: "leading space" 49 | linters: 50 | - nolintlint 51 | max-issues-per-linter: 10000 52 | max-same-issues: 10000 53 | 54 | linters-settings: 55 | gci: 56 | custom-order: true 57 | sections: 58 | - standard # Standard section: captures all standard packages. 59 | - default # Default section: contains all imports that could not be matched to another section type. 60 | - prefix(cosmossdk.io github.com/cosmos/cosmos-sdk) 61 | - prefix(github.com/cosmos/sdk-tutorials) 62 | revive: 63 | rules: 64 | - name: redefines-builtin-id 65 | disabled: true 66 | 67 | gosec: 68 | # To select a subset of rules to run. 69 | # Available rules: https://github.com/securego/gosec#available-rules 70 | # Default: [] - means include all rules 71 | includes: 72 | # - G101 # Look for hard coded credentials 73 | - G102 # Bind to all interfaces 74 | - G103 # Audit the use of unsafe block 75 | - G104 # Audit errors not checked 76 | - G106 # Audit the use of ssh.InsecureIgnoreHostKey 77 | - G107 # Url provided to HTTP request as taint input 78 | - G108 # Profiling endpoint automatically exposed on /debug/pprof 79 | - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 80 | - G110 # Potential DoS vulnerability via decompression bomb 81 | - G111 # Potential directory traversal 82 | - G112 # Potential slowloris attack 83 | - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) 84 | - G114 # Use of net/http serve function that has no support for setting timeouts 85 | - G201 # SQL query construction using format string 86 | - G202 # SQL query construction using string concatenation 87 | - G203 # Use of unescaped data in HTML templates 88 | - G204 # Audit use of command execution 89 | - G301 # Poor file permissions used when creating a directory 90 | - G302 # Poor file permissions used with chmod 91 | - G303 # Creating tempfile using a predictable path 92 | - G304 # File path provided as taint input 93 | - G305 # File traversal when extracting zip/tar archive 94 | - G306 # Poor file permissions used when writing to a new file 95 | - G307 # Deferring a method which returns an error 96 | - G401 # Detect the usage of DES, RC4, MD5 or SHA1 97 | - G402 # Look for bad TLS connection settings 98 | - G403 # Ensure minimum RSA key length of 2048 bits 99 | - G404 # Insecure random number source (rand) 100 | - G501 # Import blocklist: crypto/md5 101 | - G502 # Import blocklist: crypto/des 102 | - G503 # Import blocklist: crypto/rc4 103 | - G504 # Import blocklist: net/http/cgi 104 | - G505 # Import blocklist: crypto/sha1 105 | - G601 # Implicit memory aliasing of items from a range statement 106 | misspell: 107 | locale: US 108 | gofumpt: 109 | extra-rules: true 110 | dogsled: 111 | max-blank-identifiers: 6 112 | maligned: 113 | suggest-new: true 114 | nolintlint: 115 | allow-unused: false 116 | require-explanation: true 117 | require-specific: false 118 | gosimple: 119 | checks: ["all"] 120 | gocritic: 121 | disabled-checks: 122 | - regexpMust 123 | - appendAssign 124 | - ifElseChain 125 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Cosmos SDK Tutorials repo 2 | 3 | This repository contains the code and content for the published along side the [Cosmos SDK documentation](https://docs.cosmos.network/). 4 | 5 | ## Most Tutorials are Version-Specific 6 | 7 | If the tutorial instructs you to download a specific version, that means that the code and the docs for each tutorial are tested, supported, and based on a specific version of the software. Be sure to download and use the right version. 8 | 9 | ## Contributing 10 | 11 | Thank you for helping us to create and maintain this awesome developer portal. 12 | 13 | ### Language and Style 14 | 15 | We welcome contributions to the tutorials. Our technical content follows the Google developer documentation style guide: 16 | 17 | * [Google developer documentation style guide](https://developers.google.com/style) 18 | * [Highlights](https://developers.google.com/style/highlights) 19 | * [Word list](https://developers.google.com/style/word-list) 20 | * [Style and tone](https://developers.google.com/style/tone) 21 | * [Writing for a global audience](https://developers.google.com/style/translation) 22 | * [Cross-references](https://developers.google.com/style/cross-references) 23 | * [Present tense](https://developers.google.com/style/tense) 24 | 25 | The Google guidelines include more material than is listed here, and are used as a guide that enables easy decision making about proposed content changes. 26 | 27 | Other useful resources: 28 | 29 | * [Google Technical Writing Courses](https://developers.google.com/tech-writing) 30 | * [GitHub guides on mastering markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) 31 | 32 | ### Pull Request 33 | 34 | When you submit your PR, please use the default [Pull Request template](./.github/PULL_REQUEST_TEMPLATE.md). 35 | 36 | ### Folder structure 37 | 38 | The published content currently lives in a few separate folders: 39 | 40 | * `tutorials` contains specific tutorials on different topics 41 | 42 | ## Images 43 | 44 | **Please note that images _must_ be linked using an absolute path!** 45 | 46 | ## Who works on the tutorials? 47 | 48 | Meet the people [behind the Cosmos SDK and contributors](https://github.com/cosmos/sdk-tutorials/graphs/contributors). 49 | 50 | ## Viewing Example Builds 51 | 52 | There are two ways to see what your changes will look like in production before the updated pages are published: 53 | 54 | * When a PR is ready for review, you can see a preview deployment. 55 | * While a PR is in DRAFT mode, you can preview a local build. 56 | 57 | ### Pull Requests 58 | 59 | After the PR is created CI will kick off and run. All of ci must pass in order to merge the PR. Once the PR is merged, the changes will be deployed to the production site. 60 | 61 | ```sh 62 | npm run serve 63 | ``` 64 | 65 | to start the local server. 66 | 67 | A successful client compile looks like: `> VuePress dev server listening at http://localhost:8080/ ✔ Client Compiled successfully in 280.71ms success [12:06:28] Build 03d41f finished in 283 ms! ( http://localhost:8080/ )` 68 | 69 | You can now view the tutorial build on a local web browser at `http://localhost:8080/`. 70 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ### Linting ### 3 | ############################################################################### 4 | 5 | golangci_version=v1.55.0 6 | 7 | lint-install: 8 | @echo "--> Installing golangci-lint $(golangci_version)" 9 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 10 | 11 | lint: 12 | @echo "--> Running linter" 13 | $(MAKE) lint-install 14 | @./scripts/go-lint-all.bash --timeout=15m 15 | 16 | lint-fix: 17 | @echo "--> Running linter" 18 | $(MAKE) lint-install 19 | @./scripts/go-lint-all.bash --fix 20 | 21 | .PHONY: lint lint-fix 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cosmos SDK Tutorials - Application Development 2 | 3 | ![banner](./banner.png) 4 | 5 | Welcome to Cosmos SDK Tutorails, your go-to resource for learning and mastering the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk). This repository contains a series of comprehensive tutorials designed to help you understand and effectively use the Cosmos SDK. 6 | 7 | ## About the Tutorials 8 | 9 | These tutorials are crafted to cater to learners at various levels, from beginners to advanced users. Each tutorial covers specific aspects of the Cosmos SDK, ensuring a thorough understanding of its principles and applications. 10 | 11 | ## Prerequisites 12 | 13 | Before you begin, ensure you have the following installed: 14 | 15 | - [Install Golang](https://go.dev/doc/install) 16 | 17 | 26 | 27 | ## How to Use This Repository 28 | 29 | Navigate to the specific tutorial you are interested in, and follow the instructions provided within each tutorial's directory. 30 | 31 | ## Contributing 32 | 33 | We welcome contributions to this repository! Please read our [Contributing Guidelines](./CONTRIBUTING.md) for more information on how you can contribute. 34 | 35 | ## Feedback 36 | 37 | If you have any feedback or suggestions, please open an issue in this repository. 38 | 39 | ## License 40 | 41 | This project is licensed under the Apache license - see the [LICENSE.md](./LICENSE) file for details. 42 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmos/sdk-tutorials/34fe852e441259fff969b98bf1134716f2fa3ccb/banner.png -------------------------------------------------------------------------------- /go.work.example: -------------------------------------------------------------------------------- 1 | go 1.21 2 | 3 | toolchain go1.21 4 | 5 | use ( 6 | ./tutorials/base 7 | ) 8 | -------------------------------------------------------------------------------- /scripts/go-lint-all.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )" 6 | export REPO_ROOT 7 | 8 | LINT_TAGS="e2e,ledger,test_ledger_mock" 9 | if [[ ! -z "${NIX:-}" ]]; then 10 | LINT_TAGS+=",rocksdb" 11 | fi 12 | export LINT_TAGS 13 | 14 | lint_module() { 15 | local root="$1" 16 | shift 17 | if [ -f $root ]; then 18 | cd "$(dirname "$root")" 19 | else 20 | cd "$REPO_ROOT/$root" 21 | fi 22 | echo "linting $(grep "^module" go.mod) [$(date -Iseconds -u)]" 23 | golangci-lint run ./... -c "${REPO_ROOT}/.golangci.yml" "$@" --build-tags=${LINT_TAGS} 24 | 25 | # always lint simapp with app_v1 build tag, otherwise it never gets linted 26 | if [[ "$(grep "^module" go.mod)" == "module cosmossdk.io/simapp" ]]; then 27 | golangci-lint run ./... -c "${REPO_ROOT}/.golangci.yml" "$@" --build-tags=app_v1 28 | fi 29 | } 30 | export -f lint_module 31 | 32 | # if LINT_DIFF env is set, only lint the files in the current commit otherwise lint all files 33 | if [[ -z "${LINT_DIFF:-}" ]]; then 34 | find "${REPO_ROOT}" -type f -name go.mod -print0 | xargs -0 -I{} bash -c 'lint_module "$@"' _ {} "$@" 35 | else 36 | if [[ -z $GIT_DIFF ]]; then 37 | GIT_DIFF=$(git diff --name-only) || true 38 | fi 39 | 40 | if [[ -z "$GIT_DIFF" ]]; then 41 | echo "no files to lint" 42 | exit 0 43 | fi 44 | 45 | GIT_DIFF=$(echo $GIT_DIFF | tr -d "'" | tr ' ' '\n' | grep '\.go$' | grep -v '\.pb\.go$' | grep -Eo '^[^/]+\/[^/]+' | uniq) 46 | 47 | lint_sdk=false 48 | for dir in ${GIT_DIFF[@]}; do 49 | if [[ ! -f "$REPO_ROOT/$dir/go.mod" ]]; then 50 | lint_sdk=true 51 | else 52 | lint_module $dir "$@" 53 | fi 54 | done 55 | 56 | if [[ $lint_sdk ]]; then 57 | cd "$REPO_ROOT" 58 | echo "linting github.com/cosmos/cosmos-sdk [$(date -Iseconds -u)]" 59 | golangci-lint run ./... -c "${REPO_ROOT}/.golangci.yml" "$@" --build-tags=${LINT_TAGS} 60 | fi 61 | fi 62 | -------------------------------------------------------------------------------- /tutorials/base/Makefile: -------------------------------------------------------------------------------- 1 | BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 2 | COMMIT := $(shell git log -1 --format='%H') 3 | 4 | # don't override user values 5 | ifeq (,$(VERSION)) 6 | VERSION := $(shell git describe --exact-match 2>/dev/null) 7 | # if VERSION is empty, then populate it with branch's name and raw commit hash 8 | ifeq (,$(VERSION)) 9 | VERSION := $(BRANCH)-$(COMMIT) 10 | endif 11 | endif 12 | 13 | # Update the ldflags with the app, client & server names 14 | ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=example \ 15 | -X github.com/cosmos/cosmos-sdk/version.AppName=exampled \ 16 | -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ 17 | -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) 18 | 19 | BUILD_FLAGS := -ldflags '$(ldflags)' 20 | 21 | ########### 22 | # Install # 23 | ########### 24 | 25 | all: install 26 | 27 | install: 28 | @echo "--> ensure dependencies have not been modified" 29 | @go mod verify 30 | @echo "--> installing exampled" 31 | @go install $(BUILD_FLAGS) -mod=readonly ./cmd/exampled 32 | 33 | init: 34 | ./scripts/init.sh -------------------------------------------------------------------------------- /tutorials/base/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | _ "embed" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | 9 | _ "cosmossdk.io/api/cosmos/tx/config/v1" // import for side-effects 10 | "cosmossdk.io/core/appconfig" 11 | "cosmossdk.io/depinject" 12 | "cosmossdk.io/log" 13 | storetypes "cosmossdk.io/store/types" 14 | dbm "github.com/cosmos/cosmos-db" 15 | "github.com/cosmos/cosmos-sdk/baseapp" 16 | "github.com/cosmos/cosmos-sdk/client" 17 | "github.com/cosmos/cosmos-sdk/codec" 18 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 19 | "github.com/cosmos/cosmos-sdk/runtime" 20 | "github.com/cosmos/cosmos-sdk/server" 21 | "github.com/cosmos/cosmos-sdk/server/api" 22 | "github.com/cosmos/cosmos-sdk/server/config" 23 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 24 | "github.com/cosmos/cosmos-sdk/types/module" 25 | _ "github.com/cosmos/cosmos-sdk/x/auth" // import for side-effects 26 | authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" 27 | _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import for side-effects 28 | _ "github.com/cosmos/cosmos-sdk/x/bank" // import for side-effects 29 | bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" 30 | _ "github.com/cosmos/cosmos-sdk/x/consensus" // import for side-effects 31 | consensuskeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" 32 | _ "github.com/cosmos/cosmos-sdk/x/distribution" // import for side-effects 33 | distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" 34 | "github.com/cosmos/cosmos-sdk/x/genutil" 35 | genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" 36 | _ "github.com/cosmos/cosmos-sdk/x/mint" // import for side-effects 37 | _ "github.com/cosmos/cosmos-sdk/x/staking" // import for side-effects 38 | stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" 39 | ) 40 | 41 | // DefaultNodeHome default home directories for the application daemon 42 | var DefaultNodeHome string 43 | 44 | //go:embed app.yaml 45 | var AppConfigYAML []byte 46 | 47 | var ( 48 | _ runtime.AppI = (*ExampleApp)(nil) 49 | _ servertypes.Application = (*ExampleApp)(nil) 50 | ) 51 | 52 | // ExampleApp extends an ABCI application, but with most of its parameters exported. 53 | // They are exported for convenience in creating helper functions, as object 54 | // capabilities aren't needed for testing. 55 | type ExampleApp struct { 56 | *runtime.App 57 | legacyAmino *codec.LegacyAmino 58 | appCodec codec.Codec 59 | txConfig client.TxConfig 60 | interfaceRegistry codectypes.InterfaceRegistry 61 | 62 | // keepers 63 | AccountKeeper authkeeper.AccountKeeper 64 | BankKeeper bankkeeper.Keeper 65 | StakingKeeper *stakingkeeper.Keeper 66 | DistrKeeper distrkeeper.Keeper 67 | ConsensusParamsKeeper consensuskeeper.Keeper 68 | 69 | // simulation manager 70 | sm *module.SimulationManager 71 | } 72 | 73 | func init() { 74 | userHomeDir, err := os.UserHomeDir() 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | DefaultNodeHome = filepath.Join(userHomeDir, ".exampled") 80 | } 81 | 82 | // AppConfig returns the default app config. 83 | func AppConfig() depinject.Config { 84 | return depinject.Configs( 85 | appconfig.LoadYAML(AppConfigYAML), 86 | depinject.Supply( 87 | // supply custom module basics 88 | map[string]module.AppModuleBasic{ 89 | genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), 90 | }, 91 | ), 92 | ) 93 | } 94 | 95 | // NewExampleApp returns a reference to an initialized ExampleApp. 96 | func NewExampleApp( 97 | logger log.Logger, 98 | db dbm.DB, 99 | traceStore io.Writer, 100 | loadLatest bool, 101 | appOpts servertypes.AppOptions, 102 | baseAppOptions ...func(*baseapp.BaseApp), 103 | ) (*ExampleApp, error) { 104 | var ( 105 | app = &ExampleApp{} 106 | appBuilder *runtime.AppBuilder 107 | ) 108 | 109 | if err := depinject.Inject( 110 | depinject.Configs( 111 | AppConfig(), 112 | depinject.Supply( 113 | logger, 114 | appOpts, 115 | ), 116 | ), 117 | &appBuilder, 118 | &app.appCodec, 119 | &app.legacyAmino, 120 | &app.txConfig, 121 | &app.interfaceRegistry, 122 | &app.AccountKeeper, 123 | &app.BankKeeper, 124 | &app.StakingKeeper, 125 | &app.DistrKeeper, 126 | &app.ConsensusParamsKeeper, 127 | ); err != nil { 128 | return nil, err 129 | } 130 | 131 | app.App = appBuilder.Build(db, traceStore, baseAppOptions...) 132 | 133 | // register streaming services 134 | if err := app.RegisterStreamingServices(appOpts, app.kvStoreKeys()); err != nil { 135 | return nil, err 136 | } 137 | 138 | /**** Module Options ****/ 139 | 140 | // create the simulation manager and define the order of the modules for detertutorialstic simulations 141 | // NOTE: this is not required apps that don't use the simulator for fuzz testing transactions 142 | app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, make(map[string]module.AppModuleSimulation, 0)) 143 | app.sm.RegisterStoreDecoders() 144 | 145 | if err := app.Load(loadLatest); err != nil { 146 | return nil, err 147 | } 148 | 149 | return app, nil 150 | } 151 | 152 | // LegacyAmino returns ExampleApp's amino codec. 153 | func (app *ExampleApp) LegacyAmino() *codec.LegacyAmino { 154 | return app.legacyAmino 155 | } 156 | 157 | // GetKey returns the KVStoreKey for the provided store key. 158 | func (app *ExampleApp) GetKey(storeKey string) *storetypes.KVStoreKey { 159 | sk := app.UnsafeFindStoreKey(storeKey) 160 | kvStoreKey, ok := sk.(*storetypes.KVStoreKey) 161 | if !ok { 162 | return nil 163 | } 164 | return kvStoreKey 165 | } 166 | 167 | func (app *ExampleApp) kvStoreKeys() map[string]*storetypes.KVStoreKey { 168 | keys := make(map[string]*storetypes.KVStoreKey) 169 | for _, k := range app.GetStoreKeys() { 170 | if kv, ok := k.(*storetypes.KVStoreKey); ok { 171 | keys[kv.Name()] = kv 172 | } 173 | } 174 | 175 | return keys 176 | } 177 | 178 | // SimulationManager implements the SimulationApp interface 179 | func (app *ExampleApp) SimulationManager() *module.SimulationManager { 180 | return app.sm 181 | } 182 | 183 | // RegisterAPIRoutes registers all application module routes with the provided 184 | // API server. 185 | func (app *ExampleApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { 186 | app.App.RegisterAPIRoutes(apiSvr, apiConfig) 187 | // register swagger API in app.go so that other applications can override easily 188 | if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil { 189 | panic(err) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /tutorials/base/app/app.yaml: -------------------------------------------------------------------------------- 1 | modules: 2 | - name: runtime 3 | config: 4 | "@type": cosmos.app.runtime.v1alpha1.Module 5 | app_name: ExampleApp 6 | # During begin block slashing happens after distr.BeginBlocker so that 7 | # there is nothing left over in the validator fee pool, so as to keep the CanWithdrawInvariant invariant. 8 | # NOTE: staking module is required if HistoricalEntries param > 0 9 | begin_blockers: [distribution, staking] 10 | end_blockers: [staking] 11 | # NOTE: The genutils module must occur after staking so that pools are properly initialized with tokens from genesis accounts. 12 | # NOTE: The genutils module must also occur after auth so that it can access the params from auth. 13 | init_genesis: [auth, bank, distribution, staking, genutil] 14 | override_store_keys: 15 | - module_name: auth 16 | kv_store_key: acc 17 | - name: auth 18 | config: 19 | "@type": cosmos.auth.module.v1.Module 20 | bech32_prefix: tutorial 21 | module_account_permissions: 22 | - account: fee_collector 23 | - account: distribution 24 | - account: bonded_tokens_pool 25 | permissions: [burner, staking] 26 | - account: not_bonded_tokens_pool 27 | permissions: [burner, staking] 28 | - name: bank 29 | config: 30 | "@type": cosmos.bank.module.v1.Module 31 | blocked_module_accounts_override: 32 | [auth, distribution, bonded_tokens_pool, not_bonded_tokens_pool] 33 | - name: staking 34 | config: 35 | "@type": cosmos.staking.module.v1.Module 36 | - name: distribution 37 | config: 38 | "@type": cosmos.distribution.module.v1.Module 39 | - name: consensus 40 | config: 41 | "@type": cosmos.consensus.module.v1.Module 42 | - name: genutil 43 | config: 44 | "@type": cosmos.genutil.module.v1.Module 45 | - name: tx 46 | config: 47 | "@type": cosmos.tx.config.v1.Config 48 | -------------------------------------------------------------------------------- /tutorials/base/app/params/config.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "cosmossdk.io/errors" 5 | "cosmossdk.io/math" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/address" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | ) 10 | 11 | const ( 12 | CoinUnit = "example" 13 | 14 | DefaultBondDenom = CoinUnit 15 | 16 | // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address. 17 | Bech32PrefixAccAddr = "example" 18 | ) 19 | 20 | var ( 21 | // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. 22 | Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" 23 | // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address. 24 | Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" 25 | // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key. 26 | Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" 27 | // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address. 28 | Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" 29 | // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key. 30 | Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" 31 | ) 32 | 33 | func init() { 34 | SetAddressPrefixes() 35 | RegisterDenoms() 36 | } 37 | 38 | func RegisterDenoms() { 39 | err := sdk.RegisterDenom(CoinUnit, math.LegacyOneDec()) 40 | if err != nil { 41 | panic(err) 42 | } 43 | } 44 | 45 | func SetAddressPrefixes() { 46 | config := sdk.GetConfig() 47 | config.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) 48 | config.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) 49 | config.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) 50 | 51 | // This is copied from the cosmos sdk v0.43.0-beta1 52 | // source: https://github.com/cosmos/cosmos-sdk/blob/v0.43.0-beta1/types/address.go#L141 53 | config.SetAddressVerifier(func(bytes []byte) error { 54 | if len(bytes) == 0 { 55 | return errors.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty") 56 | } 57 | 58 | if len(bytes) > address.MaxAddrLen { 59 | return errors.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bytes)) 60 | } 61 | 62 | if len(bytes) != 20 && len(bytes) != 32 { 63 | return errors.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes)) 64 | } 65 | 66 | return nil 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /tutorials/base/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 | Codec codec.Codec 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /tutorials/base/cmd/exampled/cmd/commands.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | "cosmossdk.io/log" 8 | confixcmd "cosmossdk.io/tools/confix/cmd" 9 | dbm "github.com/cosmos/cosmos-db" 10 | "github.com/cosmos/cosmos-sdk/client" 11 | "github.com/cosmos/cosmos-sdk/client/debug" 12 | "github.com/cosmos/cosmos-sdk/client/flags" 13 | "github.com/cosmos/cosmos-sdk/client/keys" 14 | "github.com/cosmos/cosmos-sdk/client/pruning" 15 | "github.com/cosmos/cosmos-sdk/client/rpc" 16 | "github.com/cosmos/cosmos-sdk/client/snapshot" 17 | "github.com/cosmos/cosmos-sdk/server" 18 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 19 | sdk "github.com/cosmos/cosmos-sdk/types" 20 | "github.com/cosmos/cosmos-sdk/types/module" 21 | authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" 22 | genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/viper" 25 | 26 | "github.com/cosmos/sdk-tutorials/tutorials/base/app" 27 | ) 28 | 29 | func initRootCmd(rootCmd *cobra.Command, txConfig client.TxConfig, basicManager module.BasicManager) { 30 | cfg := sdk.GetConfig() 31 | cfg.Seal() 32 | 33 | rootCmd.AddCommand( 34 | genutilcli.InitCmd(basicManager, app.DefaultNodeHome), 35 | debug.Cmd(), 36 | confixcmd.ConfigCommand(), 37 | pruning.Cmd(newApp, app.DefaultNodeHome), 38 | snapshot.Cmd(newApp), 39 | ) 40 | 41 | server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, appExport, func(startCmd *cobra.Command) {}) 42 | 43 | // add keybase, auxiliary RPC, query, genesis, and tx child commands 44 | rootCmd.AddCommand( 45 | server.StatusCommand(), 46 | genutilcli.Commands(txConfig, basicManager, app.DefaultNodeHome), 47 | queryCommand(), 48 | txCommand(), 49 | keys.Commands(), 50 | ) 51 | } 52 | 53 | func queryCommand() *cobra.Command { 54 | cmd := &cobra.Command{ 55 | Use: "query", 56 | Aliases: []string{"q"}, 57 | Short: "Querying subcommands", 58 | DisableFlagParsing: false, 59 | SuggestionsMinimumDistance: 2, 60 | RunE: client.ValidateCmd, 61 | } 62 | 63 | cmd.AddCommand( 64 | rpc.ValidatorCommand(), 65 | server.QueryBlockCmd(), 66 | authcmd.QueryTxsByEventsCmd(), 67 | server.QueryBlocksCmd(), 68 | authcmd.QueryTxCmd(), 69 | server.QueryBlockResultsCmd(), 70 | ) 71 | 72 | return cmd 73 | } 74 | 75 | func txCommand() *cobra.Command { 76 | cmd := &cobra.Command{ 77 | Use: "tx", 78 | Short: "Transactions subcommands", 79 | DisableFlagParsing: false, 80 | SuggestionsMinimumDistance: 2, 81 | RunE: client.ValidateCmd, 82 | } 83 | 84 | cmd.AddCommand( 85 | authcmd.GetSignCommand(), 86 | authcmd.GetSignBatchCommand(), 87 | authcmd.GetMultiSignCommand(), 88 | authcmd.GetMultiSignBatchCmd(), 89 | authcmd.GetValidateSignaturesCommand(), 90 | authcmd.GetBroadcastCommand(), 91 | authcmd.GetEncodeCommand(), 92 | authcmd.GetDecodeCommand(), 93 | authcmd.GetSimulateCmd(), 94 | ) 95 | 96 | return cmd 97 | } 98 | 99 | // newApp is an appCreator 100 | func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { 101 | baseappOptions := server.DefaultBaseappOptions(appOpts) 102 | app, err := app.NewExampleApp(logger, db, traceStore, true, appOpts, baseappOptions...) 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | return app 108 | } 109 | 110 | // appExport creates a new app (optionally at a given height) and exports state. 111 | func appExport( 112 | logger log.Logger, 113 | db dbm.DB, 114 | traceStore io.Writer, 115 | height int64, 116 | forZeroHeight bool, 117 | jailAllowedAddrs []string, 118 | appOpts servertypes.AppOptions, 119 | modulesToExport []string, 120 | ) (servertypes.ExportedApp, error) { 121 | var ( 122 | ExampleApp *app.ExampleApp 123 | err error 124 | ) 125 | 126 | // this check is necessary as we use the flag in x/upgrade. 127 | // we can exit more gracefully by checking the flag here. 128 | homePath, ok := appOpts.Get(flags.FlagHome).(string) 129 | if !ok || homePath == "" { 130 | return servertypes.ExportedApp{}, errors.New("application home not set") 131 | } 132 | 133 | viperAppOpts, ok := appOpts.(*viper.Viper) 134 | if !ok { 135 | return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper") 136 | } 137 | 138 | // overwrite the FlagInvCheckPeriod 139 | viperAppOpts.Set(server.FlagInvCheckPeriod, 1) 140 | appOpts = viperAppOpts 141 | 142 | if height != -1 { 143 | ExampleApp, err = app.NewExampleApp(logger, db, traceStore, false, appOpts) 144 | if err != nil { 145 | return servertypes.ExportedApp{}, err 146 | } 147 | 148 | if err := ExampleApp.LoadHeight(height); err != nil { 149 | return servertypes.ExportedApp{}, err 150 | } 151 | } else { 152 | ExampleApp, err = app.NewExampleApp(logger, db, traceStore, true, appOpts) 153 | if err != nil { 154 | return servertypes.ExportedApp{}, err 155 | } 156 | } 157 | 158 | return ExampleApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) 159 | } 160 | -------------------------------------------------------------------------------- /tutorials/base/cmd/exampled/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "cosmossdk.io/client/v2/autocli" 8 | clientv2keyring "cosmossdk.io/client/v2/autocli/keyring" 9 | "cosmossdk.io/core/address" 10 | "cosmossdk.io/depinject" 11 | "cosmossdk.io/log" 12 | cmtcfg "github.com/cometbft/cometbft/config" 13 | "github.com/cosmos/cosmos-sdk/client" 14 | "github.com/cosmos/cosmos-sdk/client/config" 15 | "github.com/cosmos/cosmos-sdk/codec" 16 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 17 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 18 | "github.com/cosmos/cosmos-sdk/server" 19 | serverconfig "github.com/cosmos/cosmos-sdk/server/config" 20 | "github.com/cosmos/cosmos-sdk/types/module" 21 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 22 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 23 | txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" 24 | "github.com/cosmos/cosmos-sdk/x/auth/types" 25 | "github.com/spf13/cobra" 26 | 27 | "github.com/cosmos/sdk-tutorials/tutorials/base/app" 28 | ) 29 | 30 | // NewRootCmd creates a new root command for exampled. It is called once in the 31 | // main function. 32 | func NewRootCmd() *cobra.Command { 33 | var ( 34 | // It can be used to set options like the signer, fee granularity, and other transaction-related configurations. 35 | txConfigOpts tx.ConfigOptions 36 | // It can include options for modules, address codecs, and other CLI-related configurations. 37 | autoCliOpts autocli.AppOptions 38 | // This includes things like genesis data, default genesis data, verifying genesis data, and the module's name. 39 | moduleBasicManager module.BasicManager 40 | // This can include things like the client's home directory, the client's input/output, and the client's trust node. 41 | clientCtx client.Context 42 | ) 43 | 44 | if err := depinject.Inject( 45 | depinject.Configs(app.AppConfig(), 46 | depinject.Supply( 47 | log.NewNopLogger(), 48 | ), 49 | depinject.Provide( 50 | ProvideClientContext, 51 | ProvideKeyring, 52 | ), 53 | ), 54 | &txConfigOpts, 55 | &autoCliOpts, 56 | &moduleBasicManager, 57 | &clientCtx, 58 | ); err != nil { 59 | panic(err) 60 | } 61 | 62 | rootCmd := &cobra.Command{ 63 | Use: "exampled", 64 | Short: "exampled - the example chain app", 65 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { 66 | // set the default command outputs 67 | cmd.SetOut(cmd.OutOrStdout()) 68 | cmd.SetErr(cmd.ErrOrStderr()) 69 | 70 | clientCtx = clientCtx.WithCmdContext(cmd.Context()) 71 | clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags()) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | clientCtx, err = config.ReadFromClientConfig(clientCtx) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | // sign mode textual is only available in online mode 82 | if !clientCtx.Offline { 83 | // This needs to go after ReadFromClientConfig, as that function ets the RPC client needed for SIGN_MODE_TEXTUAL. 84 | txConfigOpts.EnabledSignModes = append(txConfigOpts.EnabledSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) 85 | txConfigOpts.TextualCoinMetadataQueryFn = txmodule.NewGRPCCoinMetadataQueryFn(clientCtx) 86 | txConfigWithTextual, err := tx.NewTxConfigWithOptions(codec.NewProtoCodec(clientCtx.InterfaceRegistry), txConfigOpts) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | clientCtx = clientCtx.WithTxConfig(txConfigWithTextual) 92 | } 93 | 94 | if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { 95 | return err 96 | } 97 | 98 | if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { 99 | return err 100 | } 101 | 102 | // overwrite the minimum gas price from the app configuration 103 | srvCfg := serverconfig.DefaultConfig() 104 | srvCfg.MinGasPrices = "0example" 105 | 106 | // overwrite the block timeout 107 | cmtCfg := cmtcfg.DefaultConfig() 108 | cmtCfg.Consensus.TimeoutCommit = 3 * time.Second 109 | cmtCfg.LogLevel = "*:error,p2p:info,state:info" // better default logging 110 | 111 | return server.InterceptConfigsPreRunHandler(cmd, serverconfig.DefaultConfigTemplate, srvCfg, cmtCfg) 112 | }, 113 | } 114 | 115 | initRootCmd(rootCmd, clientCtx.TxConfig, moduleBasicManager) 116 | 117 | if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { 118 | panic(err) 119 | } 120 | 121 | return rootCmd 122 | } 123 | 124 | func ProvideClientContext( 125 | appCodec codec.Codec, 126 | interfaceRegistry codectypes.InterfaceRegistry, 127 | txConfig client.TxConfig, 128 | legacyAmino *codec.LegacyAmino, 129 | ) client.Context { 130 | clientCtx := client.Context{}. 131 | WithCodec(appCodec). 132 | WithInterfaceRegistry(interfaceRegistry). 133 | WithTxConfig(txConfig). 134 | WithLegacyAmino(legacyAmino). 135 | WithInput(os.Stdin). 136 | WithAccountRetriever(types.AccountRetriever{}). 137 | WithHomeDir(app.DefaultNodeHome). 138 | WithViper("TUTORIAL") // env variable prefix 139 | 140 | // Read the config again to overwrite the default values with the values from the config file 141 | clientCtx, _ = config.ReadFromClientConfig(clientCtx) 142 | 143 | return clientCtx 144 | } 145 | 146 | func ProvideKeyring(clientCtx client.Context, addressCodec address.Codec) (clientv2keyring.Keyring, error) { 147 | kb, err := client.NewKeyringFromBackend(clientCtx, clientCtx.Keyring.Backend()) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | return keyring.NewAutoCLIKeyring(kb) 153 | } 154 | -------------------------------------------------------------------------------- /tutorials/base/cmd/exampled/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 8 | 9 | "github.com/cosmos/sdk-tutorials/tutorials/base/app" 10 | "github.com/cosmos/sdk-tutorials/tutorials/base/app/params" 11 | "github.com/cosmos/sdk-tutorials/tutorials/base/cmd/exampled/cmd" 12 | ) 13 | 14 | func main() { 15 | params.SetAddressPrefixes() 16 | 17 | rootCmd := cmd.NewRootCmd() 18 | if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { 19 | fmt.Fprintln(rootCmd.OutOrStderr(), err) 20 | os.Exit(1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tutorials/base/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -r ~/.exampled || true 4 | exampled_BIN=$(which exampled) 5 | # configure exampled 6 | $exampled_BIN config set client chain-id demo 7 | $exampled_BIN config set client keyring-backend test 8 | $exampled_BIN keys add alice 9 | $exampled_BIN keys add bob 10 | $exampled_BIN init test --chain-id demo --default-denom example 11 | # update genesis 12 | $exampled_BIN genesis add-genesis-account alice 10000000example --keyring-backend test 13 | $exampled_BIN genesis add-genesis-account bob 1000example --keyring-backend test 14 | # create default validator 15 | $exampled_BIN genesis gentx alice 1000000example --chain-id demo 16 | $exampled_BIN genesis collect-gentxs 17 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') 4 | COMMIT := $(shell git log -1 --format='%H') 5 | DOCKER := $(shell which docker) 6 | BUILDDIR ?= $(CURDIR)/build 7 | LEDGER_ENABLED ?= true 8 | 9 | # ********** Golang configs ********** 10 | 11 | export GO111MODULE = on 12 | 13 | GO_MAJOR_VERSION = $(shell go version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1) 14 | GO_MINOR_VERSION = $(shell go version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) 15 | 16 | # ********** process build tags ********** 17 | 18 | build_tags = netgo 19 | ifeq ($(LEDGER_ENABLED),true) 20 | ifeq ($(OS),Windows_NT) 21 | GCCEXE = $(shell where gcc.exe 2> NUL) 22 | ifeq ($(GCCEXE),) 23 | $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) 24 | else 25 | build_tags += ledger 26 | endif 27 | else 28 | UNAME_S = $(shell uname -s) 29 | ifeq ($(UNAME_S),OpenBSD) 30 | $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) 31 | else 32 | GCC = $(shell command -v gcc 2> /dev/null) 33 | ifeq ($(GCC),) 34 | $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) 35 | else 36 | build_tags += ledger 37 | endif 38 | endif 39 | endif 40 | endif 41 | 42 | ifeq (cleveldb,$(findstring cleveldb,$(TUTORA_BUILD_OPTIONS))) 43 | build_tags += gcc cleveldb 44 | else ifeq (rocksdb,$(findstring rocksdb,$(TUTORA_BUILD_OPTIONS))) 45 | build_tags += gcc rocksdb 46 | endif 47 | build_tags += $(BUILD_TAGS) 48 | build_tags := $(strip $(build_tags)) 49 | 50 | whitespace := 51 | whitespace := $(whitespace) $(whitespace) 52 | comma := , 53 | build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags)) 54 | 55 | # ********** process linker flags ********** 56 | 57 | ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=example \ 58 | -X github.com/cosmos/cosmos-sdk/version.AppName=exampled \ 59 | -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ 60 | -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ 61 | -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" 62 | 63 | ifeq (cleveldb,$(findstring cleveldb,$(TUTORA_BUILD_OPTIONS))) 64 | ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=cleveldb 65 | else ifeq (rocksdb,$(findstring rocksdb,$(TUTORA_BUILD_OPTIONS))) 66 | ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=rocksdb 67 | endif 68 | ifeq (,$(findstring nostrip,$(TUTORA_BUILD_OPTIONS))) 69 | ldflags += -w -s 70 | endif 71 | ifeq ($(LINK_STATICALLY),true) 72 | ldflags += -linkmode=external -extldflags "-Wl,-z,muldefs -static" 73 | endif 74 | ldflags += $(LDFLAGS) 75 | ldflags := $(strip $(ldflags)) 76 | 77 | BUILD_FLAGS := -tags '$(build_tags)' -ldflags '$(ldflags)' 78 | # check for nostrip option 79 | ifeq (,$(findstring nostrip,$(TUTORA_BUILD_OPTIONS))) 80 | BUILD_FLAGS += -trimpath 81 | endif 82 | 83 | all: lint test install 84 | 85 | 86 | ############################################################################### 87 | ### Build ### 88 | ############################################################################### 89 | 90 | install: enforce-go-version 91 | @echo "Installing exampled..." 92 | go install -mod=readonly $(BUILD_FLAGS) ./cmd/exampled 93 | 94 | build: enforce-go-version 95 | @echo "Building exampled..." 96 | go build $(BUILD_FLAGS) -o $(BUILDDIR)/ ./cmd/exampled 97 | 98 | enforce-go-version: 99 | @echo "Go version: $(GO_MAJOR_VERSION).$(GO_MINOR_VERSION)" 100 | ifneq ($(GO_MINOR_VERSION),21) 101 | @echo "Go version 1.20 is required" 102 | @exit 1 103 | endif 104 | 105 | clean: 106 | rm -rf $(CURDIR)/artifacts/ 107 | 108 | distclean: clean 109 | rm -rf vendor/ 110 | 111 | 112 | ############################################################################### 113 | ### Linting ### 114 | ############################################################################### 115 | golangci_lint_cmd=golangci-lint 116 | golangci_version=v1.52.2 117 | 118 | lint: 119 | @echo "--> Running linter" 120 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 121 | @$(golangci_lint_cmd) run --timeout=10m 122 | 123 | lint-fix: 124 | @echo "--> Running linter" 125 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 126 | @$(golangci_lint_cmd) run --fix --out-format=tab --issues-exit-code=0 127 | 128 | format: 129 | @go install mvdan.cc/gofumpt@latest 130 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 131 | find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name "*.pb.go" -not -name "*.pb.gw.go" -not -name "*.pulsar.go" -not -path "./crypto/keys/secp256k1/*" | xargs gofumpt -w -l 132 | $(golangci_lint_cmd) run --fix 133 | .PHONY: format 134 | 135 | ############################################################################### 136 | ### Localnet ### 137 | ############################################################################### 138 | 139 | start-localnet: build 140 | rm -rf ~/.exampled 141 | ./build/exampled init liveness --chain-id cosmos-1 --default-denom uatom 142 | ./build/exampled config set client chain-id cosmos-1 143 | ./build/exampled config set client keyring-backend test 144 | ./build/exampled keys add val1 145 | ./build/exampled keys add alice 146 | ./build/exampled keys add bob 147 | ./build/exampled genesis add-genesis-account val1 10000000000000000000000000uatom 148 | ./build/exampled genesis add-genesis-account alice 1000000000000000000uatom 149 | ./build/exampled genesis add-genesis-account bob 1000000000000000000uatom 150 | ./build/exampled genesis gentx val1 1000000000uatom --chain-id cosmos-1 151 | ./build/exampled genesis collect-gentxs 152 | sed -i.bak'' 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025uatom"/' ~/.exampled/config/app.toml 153 | ./build/exampled start --val-key val1 154 | ############################################################################### 155 | ### Tests & Simulation ### 156 | ############################################################################### 157 | PACKAGES_UNIT=$(shell go list ./... | grep -v -e '/tests/e2e') 158 | PACKAGES_E2E=$(shell cd tests/e2e && go list ./... | grep '/e2e') 159 | TEST_PACKAGES=./... 160 | TEST_TARGETS := test-unit test-e2e 161 | 162 | test-unit: ARGS=-timeout=5m -tags='norace' 163 | test-unit: TEST_PACKAGES=$(PACKAGES_UNIT) 164 | test-e2e: ARGS=-timeout=25m -v 165 | test-e2e: TEST_PACKAGES=$(PACKAGES_E2E) 166 | $(TEST_TARGETS): run-tests 167 | 168 | run-tests: 169 | ifneq (,$(shell which tparse 2>/dev/null)) 170 | @echo "--> Running tests" 171 | @go test -mod=readonly -json $(ARGS) $(TEST_PACKAGES) | tparse 172 | else 173 | @echo "--> Running tests" 174 | @go test -mod=readonly $(ARGS) $(TEST_PACKAGES) 175 | endif -------------------------------------------------------------------------------- /tutorials/nameservice/base/app/app.yaml: -------------------------------------------------------------------------------- 1 | modules: 2 | - name: runtime 3 | config: 4 | "@type": cosmos.app.runtime.v1alpha1.Module 5 | app_name: ExampleApp 6 | # During begin block slashing happens after distr.BeginBlocker so that 7 | # there is nothing left over in the validator fee pool, so as to keep the CanWithdrawInvariant invariant. 8 | # NOTE: staking module is required if HistoricalEntries param > 0 9 | begin_blockers: [distribution, staking] 10 | end_blockers: [staking] 11 | # NOTE: The genutils module must occur after staking so that pools are properly initialized with tokens from genesis accounts. 12 | # NOTE: The genutils module must also occur after auth so that it can access the params from auth. 13 | init_genesis: [auth, bank, distribution, staking, genutil] 14 | override_store_keys: 15 | - module_name: auth 16 | kv_store_key: acc 17 | - name: auth 18 | config: 19 | "@type": cosmos.auth.module.v1.Module 20 | bech32_prefix: tutorial 21 | module_account_permissions: 22 | - account: fee_collector 23 | - account: distribution 24 | - account: bonded_tokens_pool 25 | permissions: [burner, staking] 26 | - account: not_bonded_tokens_pool 27 | permissions: [burner, staking] 28 | - name: bank 29 | config: 30 | "@type": cosmos.bank.module.v1.Module 31 | blocked_module_accounts_override: 32 | [auth, distribution, bonded_tokens_pool, not_bonded_tokens_pool] 33 | - name: staking 34 | config: 35 | "@type": cosmos.staking.module.v1.Module 36 | - name: distribution 37 | config: 38 | "@type": cosmos.distribution.module.v1.Module 39 | - name: consensus 40 | config: 41 | "@type": cosmos.consensus.module.v1.Module 42 | - name: genutil 43 | config: 44 | "@type": cosmos.genutil.module.v1.Module 45 | - name: tx 46 | config: 47 | "@type": cosmos.tx.config.v1.Config 48 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/app/genesis.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // The genesis state of the blockchain is represented here as a map of raw json 8 | // messages key'd by a identifier string. 9 | // The identifier is used to determine which module genesis information belongs 10 | // to so it may be appropriately routed during init chain. 11 | // Within this application default genesis information is retrieved from 12 | // the ModuleBasicManager which populates json from each BasicModule 13 | // object provided to it during init. 14 | type GenesisState map[string]json.RawMessage 15 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/app/params/config.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "cosmossdk.io/errors" 5 | "cosmossdk.io/math" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/address" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | ) 10 | 11 | const ( 12 | CoinUnit = "uatom" 13 | 14 | DefaultBondDenom = CoinUnit 15 | 16 | // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address. 17 | Bech32PrefixAccAddr = "cosmos" 18 | ) 19 | 20 | var ( 21 | // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. 22 | Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" 23 | // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address. 24 | Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" 25 | // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key. 26 | Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" 27 | // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address. 28 | Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" 29 | // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key. 30 | Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" 31 | ) 32 | 33 | func init() { 34 | SetAddressPrefixes() 35 | RegisterDenoms() 36 | } 37 | 38 | func RegisterDenoms() { 39 | err := sdk.RegisterDenom(CoinUnit, math.LegacyOneDec()) 40 | if err != nil { 41 | panic(err) 42 | } 43 | } 44 | 45 | func SetAddressPrefixes() { 46 | config := sdk.GetConfig() 47 | config.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) 48 | config.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) 49 | config.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) 50 | 51 | // This is copied from the cosmos sdk v0.43.0-beta1 52 | // source: https://github.com/cosmos/cosmos-sdk/blob/v0.43.0-beta1/types/address.go#L141 53 | config.SetAddressVerifier(func(bytes []byte) error { 54 | if len(bytes) == 0 { 55 | return errors.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty") 56 | } 57 | 58 | if len(bytes) > address.MaxAddrLen { 59 | return errors.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bytes)) 60 | } 61 | 62 | if len(bytes) != 20 && len(bytes) != 32 { 63 | return errors.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes)) 64 | } 65 | 66 | return nil 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/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 | Codec codec.Codec 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/cmd/exampled/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "cosmossdk.io/log" 7 | dbm "github.com/cosmos/cosmos-db" 8 | "github.com/cosmos/cosmos-sdk/client" 9 | "github.com/cosmos/cosmos-sdk/client/config" 10 | "github.com/cosmos/cosmos-sdk/codec" 11 | "github.com/cosmos/cosmos-sdk/server" 12 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 13 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 14 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 15 | txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" 16 | "github.com/cosmos/cosmos-sdk/x/auth/types" 17 | "github.com/spf13/cobra" 18 | 19 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/app" 20 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/testutils" 21 | ) 22 | 23 | func NewRootCmd() (*cobra.Command, testutils.EncodingConfig) { 24 | tmpApp := app.NewExampleApp( 25 | log.NewNopLogger(), 26 | dbm.NewMemDB(), 27 | nil, 28 | true, 29 | map[int64]bool{}, 30 | "val", 31 | simtestutil.NewAppOptionsWithFlagHome(tempDir()), 32 | ) 33 | 34 | encodingConfig := testutils.EncodingConfig{ 35 | InterfaceRegistry: tmpApp.InterfaceRegistry(), 36 | Marshaler: tmpApp.AppCodec(), 37 | TxConfig: tmpApp.GetTxConfig(), 38 | Amino: tmpApp.LegacyAmino(), 39 | } 40 | 41 | initClientCtx := client.Context{}. 42 | WithCodec(encodingConfig.Marshaler). 43 | WithInterfaceRegistry(encodingConfig.InterfaceRegistry). 44 | WithTxConfig(encodingConfig.TxConfig). 45 | WithLegacyAmino(encodingConfig.Amino). 46 | WithInput(os.Stdin). 47 | WithAccountRetriever(types.AccountRetriever{}). 48 | WithHomeDir(app.DefaultNodeHome). 49 | WithViper("") 50 | 51 | rootCmd := &cobra.Command{ 52 | Use: app.AppName + "d", 53 | Short: "Cosmos App", 54 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { 55 | cmd.SetOut(cmd.OutOrStdout()) 56 | cmd.SetErr(cmd.ErrOrStderr()) 57 | 58 | initClientCtx = initClientCtx.WithCmdContext(cmd.Context()) 59 | initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | initClientCtx, err = config.ReadFromClientConfig(initClientCtx) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | // This needs to go after ReadFromClientConfig, as that function 70 | // sets the RPC client needed for SIGN_MODE_TEXTUAL. 71 | enabledSignModes := append(tx.DefaultSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) 72 | txConfigOpts := tx.ConfigOptions{ 73 | EnabledSignModes: enabledSignModes, 74 | TextualCoinMetadataQueryFn: txmodule.NewGRPCCoinMetadataQueryFn(initClientCtx), 75 | } 76 | txConfigWithTextual, err := tx.NewTxConfigWithOptions( 77 | codec.NewProtoCodec(encodingConfig.InterfaceRegistry), 78 | txConfigOpts, 79 | ) 80 | if err != nil { 81 | return err 82 | } 83 | initClientCtx = initClientCtx.WithTxConfig(txConfigWithTextual) 84 | 85 | if err = client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { 86 | return err 87 | } 88 | 89 | customAppTemplate, customAppConfig := initAppConfig() 90 | customTMConfig := initTendermintConfig() 91 | 92 | return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customTMConfig) 93 | }, 94 | } 95 | 96 | initRootCmd(rootCmd, encodingConfig, tmpApp.BasicManager, app.DefaultNodeHome) 97 | 98 | // // // add keyring to autocli opts 99 | // autoCliOpts := tmpApp.AutoCliOpts() 100 | // initClientCtx, _ = config.ReadFromClientConfig(initClientCtx) 101 | // // autoCliOpts.Keyring = initClientCtx.Keyring 102 | 103 | // if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { 104 | // panic(err) 105 | // } 106 | 107 | return rootCmd, encodingConfig 108 | } 109 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/cmd/exampled/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 8 | 9 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/app" 10 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/cmd/exampled/cmd" 11 | ) 12 | 13 | func main() { 14 | rootCmd, _ := cmd.NewRootCmd() 15 | if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { 16 | fmt.Fprintln(rootCmd.OutOrStderr(), err) 17 | os.Exit(1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -r ~/.exampled || true 4 | exampled_BIN=$(which exampled) 5 | # configure exampled 6 | $exampled_BIN config set client chain-id demo 7 | $exampled_BIN config set client keyring-backend test 8 | $exampled_BIN keys add alice 9 | $exampled_BIN keys add bob 10 | $exampled_BIN init test --chain-id demo --default-denom example 11 | # update genesis 12 | $exampled_BIN genesis add-genesis-account alice 10000000example --keyring-backend test 13 | $exampled_BIN genesis add-genesis-account bob 1000example --keyring-backend test 14 | # create default validator 15 | $exampled_BIN genesis gentx alice 1000000example --chain-id demo 16 | $exampled_BIN genesis collect-gentxs 17 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DOCKER := $(shell which docker) 4 | 5 | ################## 6 | ### Build #### 7 | ################## 8 | 9 | test: 10 | @echo "--> Running tests" 11 | go test -v ./... 12 | 13 | test-integration: 14 | @echo "--> Running integration tests" 15 | cd integration; go test -v ./... 16 | 17 | .PHONY: test test-integration 18 | 19 | ################### 20 | ### Protobuf #### 21 | ################### 22 | 23 | protoVer=0.13.2 24 | protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) 25 | protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) 26 | 27 | proto-all: proto-format proto-lint proto-gen 28 | 29 | proto-gen: 30 | @echo "Generating protobuf files..." 31 | @$(protoImage) sh ./scripts/protocgen.sh 32 | @go mod tidy 33 | 34 | proto-format: 35 | @$(protoImage) find ./ -name "*.proto" -exec clang-format -i {} \; 36 | 37 | proto-lint: 38 | @$(protoImage) buf lint 39 | 40 | .PHONY: proto-all proto-gen proto-format proto-lintll proto-gen proto-gen-any proto-swagger-gen proto-format proto-lint proto-check-breaking proto-update-deps mocks 41 | 42 | ################## 43 | ### Linting #### 44 | ################## 45 | 46 | golangci_lint_cmd=golangci-lint 47 | golangci_version=v1.51.2 48 | 49 | lint: 50 | @echo "--> Running linter" 51 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 52 | @$(golangci_lint_cmd) run ./... --timeout 15m 53 | 54 | lint-fix: 55 | @echo "--> Running linter and fixing issues" 56 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 57 | @$(golangci_lint_cmd) run ./... --fix --timeout 15m 58 | 59 | .PHONY: lint lint-fix -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/abci/types.go: -------------------------------------------------------------------------------- 1 | package abci 2 | 3 | import ( 4 | "cosmossdk.io/log" 5 | "github.com/cosmos/cosmos-sdk/client" 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | 8 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/mempool" 9 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/provider" 10 | ) 11 | 12 | type PrepareProposalHandler struct { 13 | logger log.Logger 14 | txConfig client.TxConfig 15 | cdc codec.Codec 16 | mempool *mempool.ThresholdMempool 17 | txProvider provider.TxProvider 18 | runProvider bool 19 | } 20 | 21 | type ProcessProposalHandler struct { 22 | TxConfig client.TxConfig 23 | Codec codec.Codec 24 | Logger log.Logger 25 | } 26 | 27 | type VoteExtHandler struct { 28 | logger log.Logger 29 | mempool *mempool.ThresholdMempool 30 | cdc codec.Codec 31 | } 32 | 33 | type InjectedVoteExt struct { 34 | VoteExtSigner []byte 35 | Bids [][]byte 36 | } 37 | 38 | type InjectedVotes struct { 39 | Votes []InjectedVoteExt 40 | } 41 | 42 | type AppVoteExtension struct { 43 | Height int64 44 | Bids [][]byte 45 | } 46 | 47 | type SpecialTransaction struct { 48 | Height int 49 | Bids [][]byte 50 | } 51 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/abci/vote_ext.go: -------------------------------------------------------------------------------- 1 | package abci 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "cosmossdk.io/log" 9 | abci "github.com/cometbft/cometbft/abci/types" 10 | "github.com/cosmos/cosmos-sdk/codec" 11 | sdk "github.com/cosmos/cosmos-sdk/types" 12 | 13 | auctiontypes "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 14 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/mempool" 15 | ) 16 | 17 | func NewVoteExtensionHandler(lg log.Logger, mp *mempool.ThresholdMempool, cdc codec.Codec) *VoteExtHandler { 18 | return &VoteExtHandler{ 19 | logger: lg, 20 | mempool: mp, 21 | cdc: cdc, 22 | } 23 | } 24 | 25 | func (h *VoteExtHandler) ExtendVoteHandler() sdk.ExtendVoteHandler { 26 | return func(ctx sdk.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) { 27 | h.logger.Info(fmt.Sprintf("Extending votes at block height : %v", req.Height)) 28 | 29 | voteExtBids := [][]byte{} 30 | 31 | // Get mempool txs 32 | itr := h.mempool.SelectPending(context.Background(), nil) 33 | 34 | for itr != nil { 35 | tmptx := itr.Tx() 36 | sdkMsgs := tmptx.GetMsgs() 37 | 38 | // Iterate through msgs, check for any bids 39 | for _, msg := range sdkMsgs { 40 | switch msg := msg.(type) { 41 | case *auctiontypes.MsgBid: 42 | // Marshal sdk bids to []byte 43 | bz, err := h.cdc.Marshal(msg) 44 | if err != nil { 45 | h.logger.Error(fmt.Sprintf("Error marshaling VE Bid : %v", err)) 46 | break 47 | } 48 | voteExtBids = append(voteExtBids, bz) 49 | default: 50 | } 51 | } 52 | 53 | // Move tx to ready pool 54 | err := h.mempool.Update(context.Background(), tmptx) 55 | //// Remove tx from app side mempool 56 | // err = h.mempool.Remove(tmptx) 57 | if err != nil { 58 | h.logger.Info(fmt.Sprintf("Unable to update mempool tx: %v", err)) 59 | } 60 | 61 | itr = itr.Next() 62 | } 63 | 64 | // Create vote extension 65 | voteExt := AppVoteExtension{ 66 | Height: req.Height, 67 | Bids: voteExtBids, 68 | } 69 | 70 | // Encode Vote Extension 71 | bz, err := json.Marshal(voteExt) 72 | if err != nil { 73 | return nil, fmt.Errorf("error marshaling VE: %w", err) 74 | } 75 | 76 | return &abci.ResponseExtendVote{VoteExtension: bz}, nil 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/api/v1/query_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc (unknown) 5 | // source: cosmos/auction/v1/query.proto 6 | 7 | package auctionv1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Query_Name_FullMethodName = "/cosmos.auction.v1.Query/Name" 23 | ) 24 | 25 | // QueryClient is the client API for Query service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type QueryClient interface { 29 | // Name is a method that takes a QueryNameRequest and returns a QueryNameResponse. 30 | Name(ctx context.Context, in *QueryNameRequest, opts ...grpc.CallOption) (*QueryNameResponse, error) 31 | } 32 | 33 | type queryClient struct { 34 | cc grpc.ClientConnInterface 35 | } 36 | 37 | func NewQueryClient(cc grpc.ClientConnInterface) QueryClient { 38 | return &queryClient{cc} 39 | } 40 | 41 | func (c *queryClient) Name(ctx context.Context, in *QueryNameRequest, opts ...grpc.CallOption) (*QueryNameResponse, error) { 42 | out := new(QueryNameResponse) 43 | err := c.cc.Invoke(ctx, Query_Name_FullMethodName, in, out, opts...) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return out, nil 48 | } 49 | 50 | // QueryServer is the server API for Query service. 51 | // All implementations must embed UnimplementedQueryServer 52 | // for forward compatibility 53 | type QueryServer interface { 54 | // Name is a method that takes a QueryNameRequest and returns a QueryNameResponse. 55 | Name(context.Context, *QueryNameRequest) (*QueryNameResponse, error) 56 | mustEmbedUnimplementedQueryServer() 57 | } 58 | 59 | // UnimplementedQueryServer must be embedded to have forward compatible implementations. 60 | type UnimplementedQueryServer struct { 61 | } 62 | 63 | func (UnimplementedQueryServer) Name(context.Context, *QueryNameRequest) (*QueryNameResponse, error) { 64 | return nil, status.Errorf(codes.Unimplemented, "method Name not implemented") 65 | } 66 | func (UnimplementedQueryServer) mustEmbedUnimplementedQueryServer() {} 67 | 68 | // UnsafeQueryServer may be embedded to opt out of forward compatibility for this service. 69 | // Use of this interface is not recommended, as added methods to QueryServer will 70 | // result in compilation errors. 71 | type UnsafeQueryServer interface { 72 | mustEmbedUnimplementedQueryServer() 73 | } 74 | 75 | func RegisterQueryServer(s grpc.ServiceRegistrar, srv QueryServer) { 76 | s.RegisterService(&Query_ServiceDesc, srv) 77 | } 78 | 79 | func _Query_Name_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 80 | in := new(QueryNameRequest) 81 | if err := dec(in); err != nil { 82 | return nil, err 83 | } 84 | if interceptor == nil { 85 | return srv.(QueryServer).Name(ctx, in) 86 | } 87 | info := &grpc.UnaryServerInfo{ 88 | Server: srv, 89 | FullMethod: Query_Name_FullMethodName, 90 | } 91 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 92 | return srv.(QueryServer).Name(ctx, req.(*QueryNameRequest)) 93 | } 94 | return interceptor(ctx, in, info, handler) 95 | } 96 | 97 | // Query_ServiceDesc is the grpc.ServiceDesc for Query service. 98 | // It's only intended for direct use with grpc.RegisterService, 99 | // and not to be introspected or modified (even as a copy) 100 | var Query_ServiceDesc = grpc.ServiceDesc{ 101 | ServiceName: "cosmos.auction.v1.Query", 102 | HandlerType: (*QueryServer)(nil), 103 | Methods: []grpc.MethodDesc{ 104 | { 105 | MethodName: "Name", 106 | Handler: _Query_Name_Handler, 107 | }, 108 | }, 109 | Streams: []grpc.StreamDesc{}, 110 | Metadata: "cosmos/auction/v1/query.proto", 111 | } 112 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/api/v1/tx_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc (unknown) 5 | // source: cosmos/auction/v1/tx.proto 6 | 7 | package auctionv1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Msg_Bid_FullMethodName = "/cosmos.auction.v1.Msg/Bid" 23 | ) 24 | 25 | // MsgClient is the client API for Msg service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type MsgClient interface { 29 | // Reserve defines a method to buy a nameservice with an associated bech32 30 | // address to resolve to. 31 | Bid(ctx context.Context, in *MsgBid, opts ...grpc.CallOption) (*MsgBidResponse, error) 32 | } 33 | 34 | type msgClient struct { 35 | cc grpc.ClientConnInterface 36 | } 37 | 38 | func NewMsgClient(cc grpc.ClientConnInterface) MsgClient { 39 | return &msgClient{cc} 40 | } 41 | 42 | func (c *msgClient) Bid(ctx context.Context, in *MsgBid, opts ...grpc.CallOption) (*MsgBidResponse, error) { 43 | out := new(MsgBidResponse) 44 | err := c.cc.Invoke(ctx, Msg_Bid_FullMethodName, in, out, opts...) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return out, nil 49 | } 50 | 51 | // MsgServer is the server API for Msg service. 52 | // All implementations must embed UnimplementedMsgServer 53 | // for forward compatibility 54 | type MsgServer interface { 55 | // Reserve defines a method to buy a nameservice with an associated bech32 56 | // address to resolve to. 57 | Bid(context.Context, *MsgBid) (*MsgBidResponse, error) 58 | mustEmbedUnimplementedMsgServer() 59 | } 60 | 61 | // UnimplementedMsgServer must be embedded to have forward compatible implementations. 62 | type UnimplementedMsgServer struct { 63 | } 64 | 65 | func (UnimplementedMsgServer) Bid(context.Context, *MsgBid) (*MsgBidResponse, error) { 66 | return nil, status.Errorf(codes.Unimplemented, "method Bid not implemented") 67 | } 68 | func (UnimplementedMsgServer) mustEmbedUnimplementedMsgServer() {} 69 | 70 | // UnsafeMsgServer may be embedded to opt out of forward compatibility for this service. 71 | // Use of this interface is not recommended, as added methods to MsgServer will 72 | // result in compilation errors. 73 | type UnsafeMsgServer interface { 74 | mustEmbedUnimplementedMsgServer() 75 | } 76 | 77 | func RegisterMsgServer(s grpc.ServiceRegistrar, srv MsgServer) { 78 | s.RegisterService(&Msg_ServiceDesc, srv) 79 | } 80 | 81 | func _Msg_Bid_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 82 | in := new(MsgBid) 83 | if err := dec(in); err != nil { 84 | return nil, err 85 | } 86 | if interceptor == nil { 87 | return srv.(MsgServer).Bid(ctx, in) 88 | } 89 | info := &grpc.UnaryServerInfo{ 90 | Server: srv, 91 | FullMethod: Msg_Bid_FullMethodName, 92 | } 93 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 94 | return srv.(MsgServer).Bid(ctx, req.(*MsgBid)) 95 | } 96 | return interceptor(ctx, in, info, handler) 97 | } 98 | 99 | // Msg_ServiceDesc is the grpc.ServiceDesc for Msg service. 100 | // It's only intended for direct use with grpc.RegisterService, 101 | // and not to be introspected or modified (even as a copy) 102 | var Msg_ServiceDesc = grpc.ServiceDesc{ 103 | ServiceName: "cosmos.auction.v1.Msg", 104 | HandlerType: (*MsgServer)(nil), 105 | Methods: []grpc.MethodDesc{ 106 | { 107 | MethodName: "Bid", 108 | Handler: _Msg_Bid_Handler, 109 | }, 110 | }, 111 | Streams: []grpc.StreamDesc{}, 112 | Metadata: "cosmos/auction/v1/tx.proto", 113 | } 114 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/codec.go: -------------------------------------------------------------------------------- 1 | package auction 2 | 3 | import ( 4 | "github.com/cosmos/cosmos-sdk/codec" 5 | "github.com/cosmos/cosmos-sdk/codec/legacy" 6 | types "github.com/cosmos/cosmos-sdk/codec/types" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | "github.com/cosmos/cosmos-sdk/types/msgservice" 9 | ) 10 | 11 | // RegisterLegacyAminoCodec registers the necessary interfaces and concrete types 12 | // on the provided LegacyAmino codec. These types are used for Amino JSON serialization. 13 | func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 14 | legacy.RegisterAminoMsg(cdc, &MsgBid{}, "namespace/MsgReserve") 15 | } 16 | 17 | // RegisterInterfaces registers the interfaces types with the interface registry. 18 | func RegisterInterfaces(registry types.InterfaceRegistry) { 19 | registry.RegisterImplementations((*sdk.Msg)(nil), 20 | &MsgBid{}, 21 | ) 22 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 23 | } 24 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/errors.go: -------------------------------------------------------------------------------- 1 | package auction 2 | 3 | import ( 4 | "cosmossdk.io/errors" 5 | ) 6 | 7 | var ( 8 | ErrNameReserved = errors.Register(ModuleName, 1, "name already reserved") 9 | ErrEmptyName = errors.Register(ModuleName, 2, "name cannot be empty") 10 | ) 11 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/expected_keepers.go: -------------------------------------------------------------------------------- 1 | package auction 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | ) 8 | 9 | // BankKeeper defines the contract needed to be fulfilled for banking and supply 10 | // dependencies. 11 | type BankKeeper interface { 12 | GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin 13 | 14 | SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins 15 | 16 | SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error 17 | } 18 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/flags.go: -------------------------------------------------------------------------------- 1 | package auction 2 | 3 | var ( 4 | FlagValKey = "val-key" 5 | FlagRunProvider = "run-provider" 6 | ) 7 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/genesis.go: -------------------------------------------------------------------------------- 1 | package auction 2 | 3 | func NewGenesisState() *GenesisState { 4 | return &GenesisState{} 5 | } 6 | 7 | func (gs *GenesisState) Validate() error { 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/genesis.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 7 | ) 8 | 9 | func (k *Keeper) InitGenesis(ctx context.Context, data *auction.GenesisState) error { 10 | // TODO: Implement 11 | return nil 12 | } 13 | 14 | func (k *Keeper) ExportGenesis(ctx context.Context) (*auction.GenesisState, error) { 15 | // TODO: Implement 16 | return &auction.GenesisState{}, nil 17 | } 18 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/genesis_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 7 | 8 | ) 9 | 10 | func TestInitGenesis(t *testing.T) { 11 | fixture := initFixture(t) 12 | 13 | data := &auction.GenesisState{} 14 | err := fixture.k.InitGenesis(fixture.ctx, data) 15 | if err != nil { 16 | t.Errorf("InitGenesis returned an error: %v", err) 17 | } 18 | } 19 | 20 | func TestExportGenesis(t *testing.T) { 21 | fixture := initFixture(t) 22 | 23 | _, err := fixture.k.ExportGenesis(fixture.ctx) 24 | if err != nil { 25 | t.Errorf("InitGenesis returned an error: %v", err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "fmt" 5 | 6 | "cosmossdk.io/collections" 7 | "cosmossdk.io/core/address" 8 | storetypes "cosmossdk.io/core/store" 9 | "cosmossdk.io/log" 10 | "github.com/cosmos/cosmos-sdk/codec" 11 | sdk "github.com/cosmos/cosmos-sdk/types" 12 | 13 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 14 | ) 15 | 16 | type Keeper struct { 17 | cdc codec.BinaryCodec 18 | addressCodec address.Codec 19 | 20 | authority string 21 | 22 | bk auction.BankKeeper 23 | defaultDenom string 24 | 25 | // state management 26 | Schema collections.Schema 27 | Names collections.Map[string, auction.Name] 28 | } 29 | 30 | func NewKeeper(cdc codec.BinaryCodec, addressCodec address.Codec, storeService storetypes.KVStoreService, authority string, bk auction.BankKeeper, denom string) Keeper { 31 | if _, err := addressCodec.StringToBytes(authority); err != nil { 32 | panic(fmt.Errorf("invalid authority address: %w", err)) 33 | } 34 | 35 | sb := collections.NewSchemaBuilder(storeService) 36 | k := Keeper{ 37 | cdc: cdc, 38 | addressCodec: addressCodec, 39 | authority: authority, 40 | bk: bk, 41 | defaultDenom: denom, 42 | Names: collections.NewMap(sb, auction.NamesKey, "names", collections.StringKey, codec.CollValue[auction.Name](cdc)), 43 | } 44 | 45 | schema, err := sb.Build() 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | k.Schema = schema 51 | 52 | return k 53 | } 54 | 55 | func (k Keeper) GetAuthority() string { 56 | return k.authority 57 | } 58 | 59 | // Logger returns a module-specific logger. 60 | func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { 61 | return ctx.Logger().With("module", "x/"+auction.ModuleName) 62 | } 63 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/migrator.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | type Migrator struct { 4 | keeper Keeper 5 | } 6 | 7 | func NewMigrator(k Keeper) Migrator { 8 | return Migrator{ 9 | keeper: k, 10 | } 11 | } 12 | 13 | // Migrate1to2 migrates the module state from version 1 to version 2. 14 | // func (m Migrator) Migrate1to2(ctx context.Context) error { 15 | // return v2.Migrate(/* migrations arguments */) 16 | // } 17 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/msg_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | 8 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 9 | ) 10 | 11 | type msgServer struct { 12 | k Keeper 13 | } 14 | 15 | var _ auction.MsgServer = msgServer{} 16 | 17 | func NewMsgServerImpl(keeper Keeper) auction.MsgServer { 18 | return &msgServer{k: keeper} 19 | } 20 | 21 | func (ms msgServer) Bid(goCtx context.Context, msg *auction.MsgBid) (*auction.MsgBidResponse, error) { 22 | ctx := sdk.UnwrapSDKContext(goCtx) 23 | 24 | if err := ms.k.checkAvailability(ctx, msg.Name); err != nil { 25 | return nil, err 26 | } 27 | 28 | nameReservation, err := ms.k.validateAndFormat(ctx, msg) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | if err = ms.k.reserveName(ctx, nameReservation); err != nil { 34 | return nil, err 35 | } 36 | 37 | return &auction.MsgBidResponse{}, nil 38 | } 39 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/msg_server_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestMsg(t *testing.T) { 12 | f := initFixture(t) 13 | require := require.New(t) 14 | 15 | testCases := []struct { 16 | name string 17 | request *auction.MsgBid 18 | expectErrMsg string 19 | }{ 20 | { 21 | name: "Success", 22 | request: &auction.MsgBid{}, 23 | expectErrMsg: "", 24 | }, 25 | } 26 | 27 | for _, tc := range testCases { 28 | tc := tc 29 | t.Run(tc.name, func(t *testing.T) { 30 | _, err := f.msgServer.Bid(f.ctx, tc.request) 31 | if tc.expectErrMsg != "" { 32 | require.Error(err) 33 | require.ErrorContains(err, tc.expectErrMsg) 34 | } else { 35 | require.NoError(err) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/nameservice.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | 9 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 10 | ) 11 | 12 | func (k Keeper) GetNameRecord(ctx context.Context, name string) (auction.Name, error) { 13 | nameRecord, err := k.Names.Get(ctx, name) 14 | if err != nil { 15 | return auction.Name{}, err 16 | } 17 | 18 | return nameRecord, nil 19 | } 20 | 21 | func (k Keeper) reserveName(ctx sdk.Context, nr auction.Name) error { 22 | owner, _ := sdk.AccAddressFromBech32(nr.Owner) 23 | if err := k.executeTransfer(ctx, owner, nr.Amount); err != nil { 24 | return err 25 | } 26 | if err := k.Names.Set(ctx, nr.Name, nr); err != nil { 27 | // TODO: Custom error 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (k Keeper) checkAvailability(ctx sdk.Context, name string) error { 35 | hasName, err := k.Names.Has(ctx, name) 36 | if hasName { 37 | return fmt.Errorf("%s is already reserved", name) 38 | } 39 | if err != nil { 40 | return fmt.Errorf("unexpected error validating reservation :: %w", err) 41 | } 42 | return nil 43 | } 44 | 45 | func (k Keeper) validateAndFormat(ctx sdk.Context, msg *auction.MsgBid) (auction.Name, error) { 46 | nr := auction.Name{} 47 | 48 | sender, err := sdk.AccAddressFromBech32(msg.Owner) 49 | if err != nil { 50 | return nr, err 51 | } 52 | 53 | resolveAddr, err := sdk.AccAddressFromBech32(msg.ResolveAddress) 54 | if err != nil { 55 | return nr, fmt.Errorf("invalid resolve address %s", msg.ResolveAddress) 56 | } 57 | 58 | if err := k.checkSufficientBalance(ctx, sender, msg.Amount); err != nil { 59 | return nr, err 60 | } 61 | 62 | return auction.Name{Name: msg.Name, Owner: sender.String(), ResolveAddress: resolveAddr.String(), Amount: msg.Amount}, nil 63 | } 64 | 65 | func (k Keeper) checkSufficientBalance(ctx sdk.Context, senderAddr sdk.AccAddress, amount sdk.Coins) error { 66 | found, payment := amount.Find(k.defaultDenom) 67 | if !found { 68 | return fmt.Errorf("invalid coin denom - %s, default denom: %s", amount, k.defaultDenom) 69 | } 70 | 71 | balance := k.bk.GetBalance(ctx, senderAddr, payment.Denom) 72 | _, insufficientBalance := sdk.Coins{balance}.SafeSub(payment) 73 | if insufficientBalance { 74 | return fmt.Errorf("price for name exceeds account balance: %s < %s", balance, payment) 75 | } 76 | return nil 77 | } 78 | 79 | func (k Keeper) executeTransfer(ctx sdk.Context, senderAddr sdk.AccAddress, amount sdk.Coins) error { 80 | err := k.bk.SendCoinsFromAccountToModule(ctx, senderAddr, auction.ModuleName, amount) 81 | if err != nil { 82 | return err 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/query_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 8 | ) 9 | 10 | var _ auction.QueryServer = queryServer{} 11 | 12 | // NewQueryServerImpl returns an implementation of the module QueryServer. 13 | func NewQueryServerImpl(k Keeper) auction.QueryServer { 14 | return queryServer{k} 15 | } 16 | 17 | type queryServer struct { 18 | k Keeper 19 | } 20 | 21 | func (qs queryServer) Name(ctx context.Context, r *auction.QueryNameRequest) (*auction.QueryNameResponse, error) { 22 | if r == nil { 23 | return nil, errors.New("empty request") 24 | } 25 | if len(r.Name) == 0 { 26 | return nil, auction.ErrEmptyName 27 | } 28 | 29 | record, err := qs.k.GetNameRecord(ctx, r.Name) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return &auction.QueryNameResponse{ 35 | Name: &record, 36 | }, nil 37 | } 38 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keeper/query_server_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestQueryName(t *testing.T) { 13 | f := initFixture(t) 14 | require := require.New(t) 15 | 16 | nameRes := &auction.MsgBid{ 17 | Name: "bob.cosmos", 18 | ResolveAddress: f.addrs[0].String(), 19 | Owner: f.addrs[1].String(), 20 | Amount: sdk.NewCoins(sdk.NewInt64Coin("stake", 150)), 21 | } 22 | 23 | nameReq := &auction.QueryNameRequest{ 24 | Name: "bob.cosmos", 25 | } 26 | 27 | _, err := f.msgServer.Bid(f.ctx, nameRes) 28 | require.NoError(err) 29 | resp, err := f.queryServer.Name(f.ctx, nameReq) 30 | require.NoError(err) 31 | require.Equal(nameReq, resp) 32 | } 33 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/keys.go: -------------------------------------------------------------------------------- 1 | package auction 2 | 3 | import "cosmossdk.io/collections" 4 | 5 | const ( 6 | ModuleName = "auction" 7 | StoreKey = ModuleName 8 | ) 9 | 10 | var ( 11 | NamesKey = collections.NewPrefix(0) 12 | OwnersKey = collections.NewPrefix(1) 13 | ) 14 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/mempool/mempool.go: -------------------------------------------------------------------------------- 1 | package mempool 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "cosmossdk.io/log" 8 | sdk "github.com/cosmos/cosmos-sdk/types" 9 | "github.com/cosmos/cosmos-sdk/types/mempool" 10 | "github.com/cosmos/cosmos-sdk/x/auth/signing" 11 | ) 12 | 13 | var _ mempool.Mempool = (*ThresholdMempool)(nil) 14 | 15 | type ThresholdMempool struct { 16 | logger log.Logger 17 | pendingPool thTxs 18 | pool thTxs 19 | } 20 | 21 | func NewThresholdMempool(logger log.Logger) *ThresholdMempool { 22 | return &ThresholdMempool{ 23 | logger: logger.With("module", "threshold-mempool"), 24 | } 25 | } 26 | 27 | func (t *ThresholdMempool) Insert(ctx context.Context, tx sdk.Tx) error { 28 | sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() 29 | if err != nil { 30 | t.logger.Error("Error unable to retrieve tx signatures") 31 | return err 32 | } 33 | // Guarantee there is at least 1 signer 34 | if len(sigs) == 0 { 35 | t.logger.Error("Error missing tx signatures") 36 | return fmt.Errorf("transaction must be signed") 37 | } 38 | 39 | sig := sigs[0] 40 | sender := sdk.AccAddress(sig.PubKey.Address()).String() 41 | t.logger.Info(fmt.Sprintf("This is the sender account address :: %v", sender)) 42 | 43 | // Set default 0 priority 44 | priority := int64(0) 45 | appTx := thTx{ 46 | sender, 47 | priority, 48 | tx, 49 | } 50 | 51 | t.logger.Info(fmt.Sprintf("Inserting transaction from %v with priority %v", sender, priority)) 52 | 53 | t.pendingPool.txs = append(t.pendingPool.txs, appTx) 54 | leng := len(t.pendingPool.txs) 55 | t.logger.Info(fmt.Sprintf("Transactions length %v", leng)) 56 | 57 | return nil 58 | } 59 | 60 | func (t *ThresholdMempool) Select(ctx context.Context, i [][]byte) mempool.Iterator { 61 | if len(t.pool.txs) == 0 { 62 | return nil 63 | } 64 | 65 | return &t.pool 66 | } 67 | 68 | func (t *ThresholdMempool) SelectPending(ctx context.Context, i [][]byte) mempool.Iterator { 69 | if len(t.pendingPool.txs) == 0 { 70 | return nil 71 | } 72 | 73 | return &t.pendingPool 74 | } 75 | 76 | func (t *ThresholdMempool) Update(ctx context.Context, tx sdk.Tx) error { 77 | sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() 78 | if err != nil { 79 | return err 80 | } 81 | if len(sigs) == 0 { 82 | return fmt.Errorf("tx must have at least one signer") 83 | } 84 | 85 | sig := sigs[0] 86 | sender := sdk.AccAddress(sig.PubKey.Address()).String() 87 | 88 | txToUpdate := thTx{ 89 | sender, 90 | 1, 91 | tx, 92 | } 93 | 94 | for idx, ttx := range t.pendingPool.txs { 95 | if ttx.Equal(txToUpdate) { 96 | t.pendingPool.txs = removeAtIndex(t.pendingPool.txs, idx) 97 | t.pool.txs = append(t.pool.txs, txToUpdate) 98 | return nil 99 | } 100 | } 101 | // remove from pendingPool, add to 102 | 103 | return mempool.ErrTxNotFound 104 | } 105 | 106 | func (t *ThresholdMempool) CountTx() int { 107 | return len(t.pendingPool.txs) 108 | } 109 | 110 | func (t *ThresholdMempool) Remove(tx sdk.Tx) error { 111 | sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() 112 | if err != nil { 113 | return err 114 | } 115 | if len(sigs) == 0 { 116 | return fmt.Errorf("tx must have at least one signer") 117 | } 118 | 119 | sig := sigs[0] 120 | sender := sdk.AccAddress(sig.PubKey.Address()).String() 121 | 122 | txToRemove := thTx{ 123 | sender, 124 | 1, 125 | tx, 126 | } 127 | 128 | for idx, ttx := range t.pool.txs { 129 | if ttx.Equal(txToRemove) { 130 | t.pool.txs = removeAtIndex(t.pool.txs, idx) 131 | return nil 132 | } 133 | } 134 | 135 | return mempool.ErrTxNotFound 136 | } 137 | 138 | var _ mempool.Iterator = &thTxs{} 139 | 140 | type thTxs struct { 141 | idx int 142 | txs []thTx 143 | } 144 | 145 | func (t *thTxs) Next() mempool.Iterator { 146 | if len(t.txs) == 0 { 147 | return nil 148 | } 149 | 150 | if len(t.txs) == t.idx+1 { 151 | return nil 152 | } 153 | 154 | t.idx++ 155 | return t 156 | } 157 | 158 | func (t *thTxs) Tx() sdk.Tx { 159 | if t.idx >= len(t.txs) { 160 | panic(fmt.Sprintf("index out of bound: %d, Txs: %v", t.idx, t)) 161 | } 162 | 163 | return t.txs[t.idx].tx 164 | } 165 | 166 | type thTx struct { 167 | address string 168 | priority int64 169 | tx sdk.Tx 170 | } 171 | 172 | func (tx thTx) Equal(other thTx) bool { 173 | if tx.address != other.address { 174 | return false 175 | } 176 | 177 | if len(tx.tx.GetMsgs()) != len(other.tx.GetMsgs()) { 178 | return false 179 | } 180 | 181 | for i, msg := range tx.tx.GetMsgs() { 182 | if msg.String() != other.tx.GetMsgs()[i].String() { 183 | return false 184 | } 185 | } 186 | 187 | return true 188 | } 189 | 190 | func removeAtIndex[T any](slice []T, index int) []T { 191 | return append(slice[:index], slice[index+1:]...) 192 | } 193 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/mempool/testutil.go: -------------------------------------------------------------------------------- 1 | package mempool 2 | 3 | import ( 4 | "fmt" 5 | 6 | cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" 9 | "github.com/cosmos/cosmos-sdk/x/auth/signing" 10 | "google.golang.org/protobuf/proto" 11 | ) 12 | 13 | // testPubKey is a dummy implementation of PubKey used for testing. 14 | type testPubKey struct { 15 | address sdk.AccAddress 16 | } 17 | 18 | func (t testPubKey) Reset() { panic("not implemented") } 19 | 20 | func (t testPubKey) String() string { panic("not implemented") } 21 | 22 | func (t testPubKey) ProtoMessage() { panic("not implemented") } 23 | 24 | func (t testPubKey) Address() cryptotypes.Address { return t.address.Bytes() } 25 | 26 | func (t testPubKey) Bytes() []byte { panic("not implemented") } 27 | 28 | func (t testPubKey) VerifySignature(msg, sig []byte) bool { panic("not implemented") } 29 | 30 | func (t testPubKey) Equals(key cryptotypes.PubKey) bool { panic("not implemented") } 31 | 32 | func (t testPubKey) Type() string { panic("not implemented") } 33 | 34 | // testTx is a dummy implementation of Tx used for testing. 35 | type testTx struct { 36 | address sdk.AccAddress 37 | nonce uint64 38 | } 39 | 40 | func (tx testTx) GetMsgsV2() ([]proto.Message, error) { 41 | panic("implement me") 42 | } 43 | 44 | func (tx testTx) GetSigners() ([][]byte, error) { panic("not implemented") } 45 | 46 | func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { panic("not implemented") } 47 | 48 | func (tx testTx) GetSignaturesV2() ([]txsigning.SignatureV2, error) { 49 | return []txsigning.SignatureV2{{ 50 | PubKey: testPubKey{address: tx.address}, 51 | Data: nil, 52 | Sequence: tx.nonce, 53 | }}, nil 54 | } 55 | 56 | func (tx testTx) GetGas() uint64 { 57 | return 10 58 | } 59 | 60 | func (tx testTx) GetFee() sdk.Coins { 61 | return sdk.NewCoins(sdk.NewInt64Coin("stake", int64(5))) 62 | } 63 | 64 | func (tx testTx) FeePayer() sdk.AccAddress { 65 | return tx.address 66 | } 67 | 68 | func (tx testTx) FeeGranter() sdk.AccAddress { 69 | return tx.address 70 | } 71 | 72 | var ( 73 | _ sdk.Tx = (*testTx)(nil) 74 | _ signing.SigVerifiableTx = (*testTx)(nil) 75 | _ cryptotypes.PubKey = (*testPubKey)(nil) 76 | ) 77 | 78 | func (tx testTx) GetMsgs() []sdk.Msg { return nil } 79 | 80 | func (tx testTx) ValidateBasic() error { return nil } 81 | 82 | func (tx testTx) String() string { 83 | return fmt.Sprintf("tx a: %s, n: %d", tx.address, tx.nonce) 84 | } 85 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/module/autocli.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" 5 | 6 | nsv1 "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/api/v1" 7 | ) 8 | 9 | // AutoCLIOptions implements the autocli.HasAutoCLIConfig interface. 10 | func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { 11 | return &autocliv1.ModuleOptions{ 12 | Query: &autocliv1.ServiceCommandDescriptor{ 13 | Service: nsv1.Query_ServiceDesc.ServiceName, 14 | RpcCommandOptions: []*autocliv1.RpcCommandOptions{ 15 | { 16 | RpcMethod: "Name", 17 | Use: "whois [name]", 18 | Short: "Get the resolve address and owner for a name record", 19 | PositionalArgs: []*autocliv1.PositionalArgDescriptor{ 20 | {ProtoField: "name"}, 21 | }, 22 | }, 23 | }, 24 | }, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/module/cli.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/cosmos/cosmos-sdk/client" 8 | "github.com/cosmos/cosmos-sdk/client/flags" 9 | "github.com/cosmos/cosmos-sdk/client/tx" 10 | sdk "github.com/cosmos/cosmos-sdk/types" 11 | "github.com/spf13/cobra" 12 | 13 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 14 | ) 15 | 16 | func (AppModule) GetTxCmd() *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Use: auction.ModuleName, 19 | Args: cobra.ExactArgs(1), 20 | RunE: client.ValidateCmd, 21 | } 22 | 23 | cmd.AddCommand( 24 | ReserveCmd(), 25 | ) 26 | return cmd 27 | } 28 | 29 | func ReserveCmd() *cobra.Command { 30 | cmd := &cobra.Command{ 31 | Use: "reserve [name] [resolve_address] [amount]", 32 | Short: "Create a new game", 33 | Args: cobra.ExactArgs(3), 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | clientCtx, err := client.GetClientTxContext(cmd) 36 | if err != nil { 37 | return err 38 | } 39 | cmd.Println(fmt.Sprintf("This is the name: %s", args[0])) 40 | cmd.Println(fmt.Sprintf("This is the resolve: %s", args[1])) 41 | cmd.Println(fmt.Sprintf("This is the amount: %s", args[2])) 42 | 43 | sender := clientCtx.GetFromAddress() 44 | 45 | amt, err := sdk.ParseCoinsNormalized(args[2]) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | msg := &auction.MsgBid{ 51 | Name: args[0], 52 | ResolveAddress: args[1], 53 | Owner: sender.String(), 54 | Amount: amt, 55 | } 56 | 57 | cmd.Printf("Reserve Name: %s\n", msg) 58 | 59 | return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) 60 | }, 61 | } 62 | 63 | flags.AddTxFlagsToCmd(cmd) 64 | return cmd 65 | } 66 | 67 | func QueryWhoisCmd() *cobra.Command { 68 | cmd := &cobra.Command{ 69 | Use: "whois [name]", 70 | Short: "Get the resolve address and owner for a name record", 71 | Args: cobra.ExactArgs(1), 72 | RunE: func(cmd *cobra.Command, args []string) error { 73 | clientCtx, err := client.GetClientTxContext(cmd) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | queryClient := auction.NewQueryClient(clientCtx) 79 | params := &auction.QueryNameRequest{Name: args[0]} 80 | res, err := queryClient.Name(context.Background(), params) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | cmd.Println(fmt.Sprintf("This is the name: %s", args[0])) 86 | cmd.Println(fmt.Sprintf("This is the resolve: %s", res.Name)) 87 | cmd.Println(fmt.Sprintf("This is the owner: %s", res.Name.Owner)) 88 | 89 | return nil 90 | }, 91 | } 92 | 93 | flags.AddQueryFlagsToCmd(cmd) 94 | return cmd 95 | } 96 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/module/depinject.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "cosmossdk.io/core/address" 5 | "cosmossdk.io/core/appmodule" 6 | "cosmossdk.io/core/store" 7 | "cosmossdk.io/depinject" 8 | "github.com/cosmos/cosmos-sdk/codec" 9 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 10 | 11 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 12 | modulev1 "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/api/module" 13 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/keeper" 14 | ) 15 | 16 | var _ appmodule.AppModule = AppModule{} 17 | 18 | // IsOnePerModuleType implements the depinject.OnePerModuleType interface. 19 | func (am AppModule) IsOnePerModuleType() {} 20 | 21 | // IsAppModule implements the appmodule.AppModule interface. 22 | func (am AppModule) IsAppModule() {} 23 | 24 | func init() { 25 | appmodule.Register( 26 | &modulev1.Module{}, 27 | appmodule.Provide(ProvideModule), 28 | ) 29 | } 30 | 31 | type ModuleInputs struct { 32 | depinject.In 33 | 34 | Cdc codec.Codec 35 | StoreService store.KVStoreService 36 | AddressCodec address.Codec 37 | 38 | BankKeeper auction.BankKeeper 39 | 40 | Config *modulev1.Module 41 | } 42 | 43 | type ModuleOutputs struct { 44 | depinject.Out 45 | 46 | Module appmodule.AppModule 47 | Keeper keeper.Keeper 48 | } 49 | 50 | func ProvideModule(in ModuleInputs) ModuleOutputs { 51 | // default to governance as authority if not provided 52 | authority := authtypes.NewModuleAddress("gov") 53 | if in.Config.Authority != "" { 54 | authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority) 55 | } 56 | 57 | k := keeper.NewKeeper(in.Cdc, in.AddressCodec, in.StoreService, authority.String(), in.BankKeeper, in.Config.DefaultDenom) 58 | m := NewAppModule(in.Cdc, k) 59 | 60 | return ModuleOutputs{Module: m, Keeper: k} 61 | } 62 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | abci "github.com/cometbft/cometbft/abci/types" 9 | "github.com/cosmos/cosmos-sdk/client" 10 | "github.com/cosmos/cosmos-sdk/codec" 11 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | "github.com/cosmos/cosmos-sdk/types/module" 14 | gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" 15 | 16 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 17 | "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/keeper" 18 | ) 19 | 20 | const ConsensusVersion = 1 21 | 22 | type AppModule struct { 23 | cdc codec.Codec 24 | keeper keeper.Keeper 25 | } 26 | 27 | func NewAppModule(cdc codec.Codec, keeper keeper.Keeper) AppModule { 28 | return AppModule{ 29 | cdc: cdc, 30 | keeper: keeper, 31 | } 32 | } 33 | 34 | func NewAppModuleBasic(m AppModule) module.AppModuleBasic { 35 | return module.CoreAppModuleBasicAdaptor(m.Name(), m) 36 | } 37 | 38 | func (AppModule) Name() string { return auction.ModuleName } 39 | 40 | func (AppModule) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { 41 | auction.RegisterLegacyAminoCodec(cdc) 42 | } 43 | 44 | func (AppModule) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *gwruntime.ServeMux) { 45 | if err := auction.RegisterQueryHandlerClient(context.Background(), mux, auction.NewQueryClient(clientCtx)); err != nil { 46 | panic(err) 47 | } 48 | } 49 | 50 | func (AppModule) RegisterInterfaces(registry codectypes.InterfaceRegistry) { 51 | auction.RegisterInterfaces(registry) 52 | } 53 | 54 | func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } 55 | 56 | func (am AppModule) RegisterServices(cfg module.Configurator) { 57 | auction.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) 58 | auction.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper)) 59 | 60 | // Register in place module state migration migrations 61 | // m := keeper.NewMigrator(am.keeper) 62 | // if err := cfg.RegisterMigration(ns.ModuleName, 1, m.Migrate1to2); err != nil { 63 | // panic(fmt.Sprintf("failed to migrate x/%s from version 1 to 2: %v", ns.ModuleName, err)) 64 | // } 65 | } 66 | 67 | func (AppModule) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { 68 | return cdc.MustMarshalJSON(auction.NewGenesisState()) 69 | } 70 | 71 | func (AppModule) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { 72 | var data auction.GenesisState 73 | if err := cdc.UnmarshalJSON(bz, &data); err != nil { 74 | return fmt.Errorf("failed to unmarshal %s genesis state: %w", auction.ModuleName, err) 75 | } 76 | 77 | return data.Validate() 78 | } 79 | 80 | func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { 81 | var genesisState auction.GenesisState 82 | cdc.MustUnmarshalJSON(data, &genesisState) 83 | 84 | if err := am.keeper.InitGenesis(ctx, &genesisState); err != nil { 85 | panic(fmt.Sprintf("failed to initialize %s genesis state: %v", auction.ModuleName, err)) 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { 92 | gs, err := am.keeper.ExportGenesis(ctx) 93 | if err != nil { 94 | panic(fmt.Sprintf("failed to export %s genesis state: %v", auction.ModuleName, err)) 95 | } 96 | 97 | return cdc.MustMarshalJSON(gs) 98 | } 99 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/buf.gen.gogo.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: gocosmos 4 | out: .. 5 | opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types,Mcosmos/orm/v1/orm.proto=cosmossdk.io/orm 6 | - name: grpc-gateway 7 | out: .. 8 | opt: logtostderr=true,allow_colon_final_segments=true 9 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/buf.gen.pulsar.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | managed: 3 | enabled: true 4 | go_package_prefix: 5 | default: github.com/tutorials/nameservice/base/x/auction/api 6 | except: 7 | - buf.build/googleapis/googleapis 8 | - buf.build/cosmos/gogo-proto 9 | - buf.build/cosmos/cosmos-proto 10 | - buf.build/cosmos/cosmos-sdk 11 | plugins: 12 | - name: go-pulsar 13 | out: .. 14 | opt: paths=source_relative,Mcosmos/app/v1alpha1/module.proto=cosmossdk.io/api/cosmos/app/v1alpha1,Mcosmos/base/v1beta1/coin.proto=cosmossdk.io/api/cosmos/base/v1beta1 15 | - name: go-grpc 16 | out: .. 17 | opt: paths=source_relative,Mcosmos/app/v1alpha1/module.proto=cosmossdk.io/api/cosmos/app/v1alpha1 18 | - name: go-cosmos-orm 19 | out: .. 20 | opt: paths=source_relative,Mcosmos/app/v1alpha1/module.proto=cosmossdk.io/api/cosmos/app/v1alpha1 21 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: cosmos 6 | repository: cosmos-proto 7 | commit: 1935555c206d4afb9e94615dfd0fad31 8 | - remote: buf.build 9 | owner: cosmos 10 | repository: cosmos-sdk 11 | commit: d5661b4f6ef64bb1b5beb6cb7bd705b7 12 | - remote: buf.build 13 | owner: cosmos 14 | repository: gogo-proto 15 | commit: 5e5b9fdd01804356895f8f79a6f1ddc1 16 | - remote: buf.build 17 | owner: googleapis 18 | repository: googleapis 19 | commit: cc916c31859748a68fd229a3c8d7a2e8 20 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | deps: 3 | - buf.build/cosmos/cosmos-sdk # pin the Cosmos SDK version 4 | - buf.build/cosmos/cosmos-proto 5 | - buf.build/cosmos/gogo-proto 6 | - buf.build/googleapis/googleapis 7 | lint: 8 | use: 9 | - DEFAULT 10 | - COMMENTS 11 | - FILE_LOWER_SNAKE_CASE 12 | except: 13 | - UNARY_RPC 14 | - COMMENT_FIELD 15 | - SERVICE_SUFFIX 16 | - PACKAGE_VERSION_SUFFIX 17 | - RPC_REQUEST_STANDARD_NAME 18 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/cosmos/auction/module/module.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cosmos.auction.module; 4 | 5 | import "cosmos/app/v1alpha1/module.proto"; 6 | 7 | // Module represents the main structure for the auction module. 8 | message Module { 9 | option (cosmos.app.v1alpha1.module) = { 10 | go_import : "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/types" 11 | }; 12 | 13 | string authority = 1; 14 | 15 | string default_denom = 2; 16 | } 17 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/cosmos/auction/v1/event.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.auction.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction"; 5 | 6 | import "cosmos_proto/cosmos.proto"; 7 | import "cosmos/msg/v1/msg.proto"; 8 | import "gogoproto/gogo.proto"; 9 | import "cosmos/base/v1beta1/coin.proto"; 10 | import "amino/amino.proto"; 11 | 12 | // EventReserve is emitted on Msg/Reserve 13 | message EventReserve { 14 | // name is the human readable reserved address 15 | string name = 1; 16 | 17 | // owner is the bech32 address of the name's owner 18 | string owner = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; 19 | 20 | // resolve is the bech32 address the name resolves to 21 | string resolve = 3 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; 22 | 23 | // receiver is the receiver address of nft 24 | repeated cosmos.base.v1beta1.Coin amount = 4 [ 25 | (gogoproto.nullable) = false, 26 | (amino.dont_omitempty) = true, 27 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" 28 | ]; 29 | } -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/cosmos/auction/v1/genesis.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.auction.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction"; 5 | 6 | import "cosmos_proto/cosmos.proto"; 7 | import "cosmos/auction/v1/types.proto"; 8 | 9 | // GenesisState defines the nameservice module's genesis state. 10 | message GenesisState { 11 | // listing defines all listings. 12 | // repeated nameservice.v1.NameCollection name_collection = 1; 13 | } 14 | 15 | // Entry define all names owned by an account 16 | message NameCollection { 17 | // owner is the owner address of the following nft 18 | string owner = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; 19 | 20 | // names is a all names of the same owner 21 | repeated Name names = 2; 22 | } -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/cosmos/auction/v1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.auction.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction"; 5 | 6 | import "cosmos/auction/v1/types.proto"; 7 | import "google/api/annotations.proto"; 8 | import "cosmos/base/query/v1beta1/pagination.proto"; 9 | import "cosmos/query/v1/query.proto"; 10 | 11 | // Query service defines the queries for the auction module 12 | service Query { 13 | // Name is a method that takes a QueryNameRequest and returns a QueryNameResponse. 14 | rpc Name(QueryNameRequest) returns (QueryNameResponse) { 15 | option (google.api.http).get = "/cosmos/ns/name/{name}"; 16 | } 17 | } 18 | 19 | // QueryNameResponse is the response type for the Query/Names RPC method 20 | message QueryNameResponse { Name name = 1; } 21 | 22 | // QueryNameRequest is the request type for the Query/Names RPC method 23 | message QueryNameRequest { string name = 1; } -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/cosmos/auction/v1/tx.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.auction.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction"; 5 | 6 | import "cosmos/msg/v1/msg.proto"; 7 | import "gogoproto/gogo.proto"; 8 | import "cosmos/base/v1beta1/coin.proto"; 9 | import "amino/amino.proto"; 10 | import "cosmos/auction/v1/types.proto"; 11 | import "cosmos_proto/cosmos.proto"; 12 | 13 | // Msg defines the nameservice Msg service. 14 | service Msg { 15 | option (cosmos.msg.v1.service) = true; 16 | 17 | // Reserve defines a method to buy a nameservice with an associated bech32 18 | // address to resolve to. 19 | rpc Bid(MsgBid) returns (MsgBidResponse); 20 | } 21 | 22 | // MsgReserve represents a message to purchase a nameservice. 23 | message MsgBid { 24 | option (cosmos.msg.v1.signer) = "owner"; 25 | 26 | // name defines the human readable address 27 | string name = 1; 28 | 29 | // resolveAddress defines the bech32 address to resolve to 30 | string resolve_address = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; 31 | 32 | // owner is the address of the owner of listing 33 | string owner = 3 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; 34 | 35 | // price is the last price paid for listing 36 | repeated cosmos.base.v1beta1.Coin amount = 4 [ 37 | (gogoproto.nullable) = false, 38 | (amino.dont_omitempty) = true, 39 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" 40 | ]; 41 | } 42 | 43 | // MsgSendResponse defines the Msg/Reserve response type. 44 | message MsgBidResponse {} 45 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/proto/cosmos/auction/v1/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.auction.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction"; 5 | 6 | import "cosmos_proto/cosmos.proto"; 7 | import "gogoproto/gogo.proto"; 8 | import "amino/amino.proto"; 9 | import "cosmos/base/v1beta1/coin.proto"; 10 | 11 | // Name defines the properties of a name in the name service auction system. 12 | message Name { 13 | string name = 1; 14 | string owner = 2; 15 | // Bech32 Address to Resolve 16 | string resolve_address = 3 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; 17 | 18 | repeated cosmos.base.v1beta1.Coin amount = 4 [ 19 | (gogoproto.nullable) = false, 20 | (amino.dont_omitempty) = true, 21 | (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" 22 | ]; 23 | } 24 | 25 | // Owner defines the properties of an owner in the name service auction system. 26 | message Owner { 27 | string name = 1; // Name of the owner 28 | // Bech32 Address of Owner 29 | string owner_address = 2; 30 | } -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | 6 | "cosmossdk.io/log" 7 | "cosmossdk.io/math" 8 | "github.com/cosmos/cosmos-sdk/client" 9 | "github.com/cosmos/cosmos-sdk/client/tx" 10 | "github.com/cosmos/cosmos-sdk/codec" 11 | "github.com/cosmos/cosmos-sdk/codec/address" 12 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 13 | sdk "github.com/cosmos/cosmos-sdk/types" 14 | authclient "github.com/cosmos/cosmos-sdk/x/auth/client" 15 | authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" 16 | 17 | auction "github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction" 18 | ) 19 | 20 | /* 21 | This implementation is for demo purposes only and does not reflect all limitations and 22 | constraints of a live distributed network. 23 | 24 | Transaction Provider is an embedded solution to demonstrate an interface an application could 25 | leverage to extract MEV when building and proposing a block. In this example, the 26 | application is building and signing transactions locally for the sake of a simplicity. 27 | Alternatively, another implementation could instead take transactions submitted directly 28 | via RPC to its app side mempool, and could even implement a separate custom mempool for 29 | special transactions of this nature. 30 | */ 31 | type TxProvider interface { 32 | BuildProposal(ctx sdk.Context, proposalTxs []sdk.Tx) ([]sdk.Tx, error) 33 | getMatchingBid(ctx sdk.Context, bid *auction.MsgBid) sdk.Tx 34 | } 35 | 36 | type LocalSigner struct { 37 | KeyName string 38 | KeyringDir string 39 | codec codec.Codec 40 | txConfig client.TxConfig 41 | kb keyring.Keyring 42 | lg log.Logger 43 | } 44 | 45 | type LocalTxProvider struct { 46 | Logger log.Logger 47 | Codec codec.Codec 48 | Signer LocalSigner 49 | TxConfig client.TxConfig 50 | AcctKeeper authkeeper.AccountKeeper 51 | } 52 | 53 | func (bp *LocalTxProvider) Init() error { 54 | return bp.Signer.Init(bp.TxConfig, bp.Codec, bp.Logger) 55 | } 56 | 57 | func (ls *LocalSigner) Init(txCfg client.TxConfig, cdc codec.Codec, logger log.Logger) error { 58 | if len(ls.KeyName) == 0 { 59 | return fmt.Errorf("keyName must be set") 60 | } 61 | 62 | if len(ls.KeyringDir) == 0 { 63 | return fmt.Errorf("keyDir must be set") 64 | } 65 | 66 | ls.txConfig = txCfg 67 | ls.codec = cdc 68 | ls.lg = logger 69 | 70 | kb, err := keyring.New("cosmos", keyring.BackendTest, ls.KeyringDir, nil, ls.codec) 71 | if err != nil { 72 | return err 73 | } 74 | ls.kb = kb 75 | return nil 76 | } 77 | 78 | func (ls *LocalSigner) RetreiveSigner(ctx sdk.Context, actKeeper authkeeper.AccountKeeper) (sdk.AccountI, error) { 79 | lg := ls.lg 80 | 81 | info, err := ls.kb.Key(ls.KeyName) 82 | if err != nil { 83 | lg.Error(fmt.Sprintf("Error retrieving key info: %v", err)) 84 | return nil, err 85 | } 86 | 87 | addrBz, err := info.GetAddress() 88 | if err != nil { 89 | lg.Error(fmt.Sprintf("Error retrieving address by key name: %v", err)) 90 | return nil, err 91 | } 92 | 93 | addCodec := address.Bech32Codec{ 94 | Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), 95 | } 96 | 97 | addrStr, err := addCodec.BytesToString(addrBz) 98 | if err != nil { 99 | lg.Error(fmt.Sprintf("Error converting address bytes to str: %v", err)) 100 | return nil, err 101 | } 102 | 103 | sdkAddr, err := sdk.AccAddressFromBech32(addrStr) 104 | if err != nil { 105 | lg.Error(fmt.Sprintf("Error getting acct address from addr string: %v", err)) 106 | return nil, err 107 | } 108 | 109 | acct := actKeeper.GetAccount(ctx, sdkAddr) 110 | return acct, nil 111 | } 112 | 113 | func (ls *LocalSigner) BuildAndSignTx(ctx sdk.Context, acct sdk.AccountI, msg auction.MsgBid) sdk.Tx { 114 | factory := tx.Factory{}. 115 | WithTxConfig(ls.txConfig). 116 | WithKeybase(ls.kb). 117 | WithChainID(ctx.ChainID()). 118 | WithAccountNumber(acct.GetAccountNumber()). 119 | WithSequence(acct.GetSequence()). 120 | WithFees("50uatom") 121 | 122 | txBuilder, err := factory.BuildUnsignedTx(&msg) 123 | if err != nil { 124 | ls.lg.Error(fmt.Sprintf("Error building unsigned tx: %v", err)) 125 | 126 | return nil 127 | } 128 | clientCtx := client.Context{} 129 | 130 | err = authclient.SignTx(factory, clientCtx, ls.KeyName, txBuilder, true, true) 131 | if err != nil { 132 | ls.lg.Error(fmt.Sprintf("Error signing tx: %v", err)) 133 | 134 | return nil 135 | } 136 | return txBuilder.GetTx() 137 | } 138 | 139 | func (b *LocalTxProvider) getMatchingBid(ctx sdk.Context, bid *auction.MsgBid) sdk.Tx { 140 | acct, err := b.Signer.RetreiveSigner(ctx, b.AcctKeeper) 141 | if err != nil { 142 | b.Logger.Error(fmt.Sprintf("Error retrieving signer: %v", err)) 143 | return nil 144 | } 145 | b.Logger.Info("💨 :: Created new bid") 146 | 147 | msg := auction.MsgBid{ 148 | Name: bid.Name, 149 | Owner: acct.GetAddress().String(), 150 | ResolveAddress: acct.GetAddress().String(), 151 | Amount: bid.Amount.MulInt(math.NewInt(2)), 152 | } 153 | 154 | newTx := b.Signer.BuildAndSignTx(ctx, acct, msg) 155 | return newTx 156 | } 157 | 158 | func (b *LocalTxProvider) BuildProposal(ctx sdk.Context, proposalTxs []sdk.Tx) ([]sdk.Tx, error) { 159 | b.Logger.Info("💨 :: Building Proposal") 160 | 161 | var newProposal []sdk.Tx 162 | for _, tx := range proposalTxs { 163 | sdkMsgs := tx.GetMsgs() 164 | for _, msg := range sdkMsgs { 165 | switch msg := msg.(type) { 166 | case *auction.MsgBid: 167 | b.Logger.Info("💨 :: Found a Bid to Snipe") 168 | 169 | // Get matching bid from matching engine 170 | newTx := b.getMatchingBid(ctx, msg) 171 | 172 | // First append sniped Bid 173 | newProposal = append(newProposal, newTx) 174 | newProposal = append(newProposal, tx) 175 | default: 176 | // Append all other transactions 177 | newProposal = append(newProposal, tx) 178 | } 179 | } 180 | } 181 | 182 | return newProposal, nil 183 | } 184 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | 4 | source ./vars.sh 5 | 6 | pkill -f exampled &> /dev/null || true 7 | sleep 1 8 | rm -rf ${NODES_ROOT_DIR} 9 | 10 | # Beacon creates genesis 11 | BEACON_DIR=${NODES_ROOT_DIR}/${BEACON_MONIKER} 12 | BEACON_DIR_KEY=${BEACON_MONIKER}-key 13 | BEACON_LISTEN_ADDR=tcp://${NODE_IP}:${RPC_LADDR_BASEPORT} 14 | 15 | # Create Account Balances 16 | for index in "${!MONIKERS[@]}" 17 | do 18 | MONIKER=${MONIKERS[$index]} 19 | # validator key 20 | KEY=${MONIKER}-key 21 | 22 | # home directory of this validator 23 | NODE_DIR=${NODES_ROOT_DIR}/${MONIKER} 24 | 25 | # Build genesis file and node directory structure 26 | $BINARY init $MONIKER --chain-id $CHAIN_ID --home ${NODE_DIR} --default-denom uatom 27 | jq ".app_state.gov.voting_params.voting_period = \"10s\" | .app_state.staking.params.unbonding_time = \"86400s\"" \ 28 | ${NODE_DIR}/config/genesis.json > \ 29 | ${NODE_DIR}/edited_genesis.json && mv ${NODE_DIR}/edited_genesis.json ${NODE_DIR}/config/genesis.json 30 | 31 | 32 | sleep 1 33 | 34 | # Create account keypair 35 | # echo "Adding key to ${NODE_DIR}" 36 | # echo ${KEY} 37 | $BINARY keys add $KEY --home ${NODE_DIR} --keyring-backend test --output json > ${NODE_DIR}/${KEY}.json 2>&1 38 | sleep 1 39 | 40 | if [ $MONIKER == $BEACON_MONIKER ]; then 41 | for USER in "${USERS[@]}" 42 | do 43 | USERKEY=${USER} 44 | $BINARY keys add $USERKEY --home ${NODE_DIR} --keyring-backend test --output json > ${NODE_DIR}/${USERKEY}.json 2>&1 45 | sleep 1 46 | 47 | ACC_ADDR=$(jq -r '.address' ${NODE_DIR}/${USERKEY}.json) 48 | $BINARY genesis add-genesis-account $ACC_ADDR $USER_COINS --home ${NODE_DIR} --keyring-backend test 49 | done 50 | fi 51 | 52 | # copy genesis in, unless this validator is the beacon 53 | if [ $MONIKER != $BEACON_MONIKER ]; then 54 | cp ${BEACON_DIR}/config/genesis.json ${NODE_DIR}/config/genesis.json 55 | fi 56 | 57 | # Add stake to user 58 | ACCOUNT_ADDR=$(jq -r '.address' ${NODE_DIR}/${KEY}.json) 59 | $BINARY genesis add-genesis-account $ACCOUNT_ADDR $USER_COINS --home ${NODE_DIR} --keyring-backend test 60 | sleep 1 61 | 62 | # copy genesis out, unless this validator is the beacon 63 | if [ $MONIKER != $BEACON_MONIKER ]; then 64 | cp ${NODE_DIR}/config/genesis.json ${BEACON_DIR}/config/genesis.json 65 | fi 66 | 67 | PPROF_LADDR=${NODE_IP}:$(($PPROF_LADDR_BASEPORT + $index)) 68 | P2P_LADDR_PORT=$(($P2P_LADDR_BASEPORT + $index)) 69 | 70 | # adjust configs of this node 71 | sed -i -r 's/timeout_commit = "5s"/timeout_commit = "3s"/g' ${NODE_DIR}/config/config.toml 72 | sed -i -r 's/timeout_propose = "3s"/timeout_propose = "1s"/g' ${NODE_DIR}/config/config.toml 73 | 74 | # make address book non-strict. necessary for this setup 75 | sed -i -r 's/addr_book_strict = true/addr_book_strict = false/g' ${NODE_DIR}/config/config.toml 76 | 77 | # avoid port double binding 78 | sed -i -r "s/pprof_laddr = \"localhost:6060\"/pprof_laddr = \"${PPROF_LADDR}\"/g" ${NODE_DIR}/config/config.toml 79 | 80 | # allow duplicate IP addresses (all nodes are on the same machine) 81 | sed -i -r 's/allow_duplicate_ip = false/allow_duplicate_ip = true/g' ${NODE_DIR}/config/config.toml 82 | done 83 | 84 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 85 | 86 | # Create Validators 87 | for MONIKER in "${MONIKERS[@]}" 88 | do 89 | # validator key 90 | KEY=${MONIKER}-key 91 | 92 | # home directory of this validator 93 | NODE_DIR=${NODES_ROOT_DIR}/${MONIKER} 94 | 95 | # copy genesis in, unless this validator is the beacon 96 | if [ $MONIKER != $BEACON_MONIKER ]; then 97 | cp ${BEACON_DIR}/config/genesis.json* ${NODE_DIR}/config/genesis.json 98 | fi 99 | 100 | # Stake 1/1000 user's coins 101 | $BINARY genesis gentx $KEY $STAKE --chain-id $CHAIN_ID --home ${NODE_DIR} --keyring-backend test --moniker $MONIKER 102 | sleep 1 103 | 104 | # Copy gentxs to the beacon for possible future collection. 105 | # Obviously we don't need to copy the first validator's gentx to itself 106 | if [ $MONIKER != $BEACON_MONIKER ]; then 107 | cp ${NODE_DIR}/config/gentx/* ${BEACON_DIR}/config/gentx/ 108 | fi 109 | done 110 | 111 | # Collect genesis transactions with beacon 112 | $BINARY genesis collect-gentxs --home ${BEACON_DIR} --gentx-dir ${BEACON_DIR}/config/gentx/ 113 | 114 | sleep 1 115 | 116 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 117 | 118 | # Create node config 119 | 120 | for index in "${!MONIKERS[@]}" 121 | do 122 | MONIKER=${MONIKERS[$index]} 123 | 124 | PERSISTENT_PEERS="" 125 | 126 | for peer_index in "${!MONIKERS[@]}" 127 | do 128 | if [ $index == $peer_index ]; then 129 | continue 130 | fi 131 | PEER_MONIKER=${MONIKERS[$peer_index]} 132 | 133 | PEER_NODE_DIR=${NODES_ROOT_DIR}/${PEER_MONIKER} 134 | 135 | PEER_NODE_ID=$($BINARY comet show-node-id --home ${PEER_NODE_DIR}) 136 | 137 | PEER_P2P_LADDR_PORT=$(($P2P_LADDR_BASEPORT + $peer_index)) 138 | PERSISTENT_PEERS="$PERSISTENT_PEERS,$PEER_NODE_ID@${NODE_IP}:${PEER_P2P_LADDR_PORT}" 139 | done 140 | 141 | # remove trailing comma from persistent peers 142 | PERSISTENT_PEERS=${PERSISTENT_PEERS:1} 143 | 144 | # validator key 145 | KEY=${MONIKER}-key 146 | 147 | # home directory of this validator on provider 148 | NODE_DIR=${NODES_ROOT_DIR}/${MONIKER} 149 | 150 | # Set min gas prices in app config 151 | sed -i.bak'' 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025uatom"/' ${NODE_DIR}/config/app.toml 152 | 153 | # Enable Vote Extensions for H-1 154 | jq '.consensus.params.abci.vote_extensions_enable_height = "2"' ${NODE_DIR}/config/genesis.json > output.json && mv output.json ${NODE_DIR}/config/genesis.json 155 | 156 | # copy genesis in, unless this validator is already the lead validator and thus it already has its genesis 157 | if [ $MONIKER != $BEACON_MONIKER ]; then 158 | cp ${BEACON_DIR}/config/genesis.json ${NODE_DIR}/config/genesis.json 159 | fi 160 | 161 | RPC_LADDR_PORT=$(($RPC_LADDR_BASEPORT + $index)) 162 | P2P_LADDR_PORT=$(($P2P_LADDR_BASEPORT + $index)) 163 | GRPC_LADDR_PORT=$(($GRPC_LADDR_BASEPORT + $index)) 164 | NODE_ADDRESS_PORT=$(($NODE_ADDRESS_BASEPORT + $index)) 165 | 166 | RUN_PROV=false 167 | if [ $MONIKER == $BEACON_MONIKER ]; then 168 | RUN_PROV=true 169 | fi 170 | 171 | # Start nodes 172 | $BINARY start \ 173 | --home ${NODE_DIR} \ 174 | --val-key ${KEY} \ 175 | --run-provider ${RUN_PROV} \ 176 | --p2p.persistent_peers ${PERSISTENT_PEERS} \ 177 | --rpc.laddr tcp://${NODE_IP}:${RPC_LADDR_PORT} \ 178 | --grpc.address ${NODE_IP}:${GRPC_LADDR_PORT} \ 179 | --address tcp://${NODE_IP}:${NODE_ADDRESS_PORT} \ 180 | --p2p.laddr tcp://${NODE_IP}:${P2P_LADDR_PORT} \ 181 | --grpc-web.enable=false &> ${NODE_DIR}/logs & 182 | 183 | sleep 5 184 | done 185 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/list-beacon-keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./vars.sh &> /dev/null 4 | 5 | $BINARY keys list --home $HOME/cosmos/nodes/beacon --keyring-backend test --output json | jq -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo "Generating gogo proto code" 6 | cd proto 7 | proto_dirs=$(find . -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 8 | for dir in $proto_dirs; do 9 | for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do 10 | # this regex checks if a proto file has its go_package set to github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/api/... 11 | # gogo proto files SHOULD ONLY be generated if this is false 12 | # we don't want gogo proto to run for proto files which are natively built for google.golang.org/protobuf 13 | if grep -q "option go_package" "$file" && grep -H -o -c 'option go_package.*github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/api' "$file" | grep -q ':0$'; then 14 | buf generate --template buf.gen.gogo.yaml $file 15 | fi 16 | done 17 | done 18 | 19 | echo "Generating pulsar proto code" 20 | buf generate --template buf.gen.pulsar.yaml 21 | 22 | cd .. 23 | 24 | cp -r github.com/cosmos/sdk-tutorials/tutorials/nameservice/base/x/auction/* ./ 25 | rm -rf api && mkdir api 26 | mv cosmos/auction/* ./api 27 | rm -rf github.com cosmos -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/query-beacon-status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./vars.sh &> /dev/null 4 | 5 | $BINARY q consensus comet node-info --node "tcp://127.0.0.1:29170" -o json | jq '.default_node_info' -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/reserve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./vars.sh &> /dev/null 4 | 5 | if [ $# -eq 0 ]; then 6 | echo "Must provide a name to reserve" 7 | exit 1 8 | fi 9 | 10 | find_project_root() { 11 | local dir=$PWD 12 | while [ "$dir" != "" ] && [ ! -d "$dir/.git" ]; do 13 | dir=$(dirname "$dir") 14 | done 15 | echo "$dir" 16 | } 17 | 18 | PROJECT_ROOT=$(find_project_root) 19 | BINARY=$PROJECT_ROOT/tutorials/nameservice/base/build/exampled 20 | 21 | name="$1" 22 | 23 | $BINARY keys show alice -a --home $HOME/cosmos/nodes/beacon --keyring-backend test 24 | $BINARY keys show barbara -a --home $HOME/cosmos/nodes/beacon --keyring-backend test 25 | 26 | $BINARY tx reserve "${name}" $($BINARY keys show alice -a --home $HOME/cosmos/nodes/beacon --keyring-backend test) 1000uatom --from $($BINARY keys show barbara -a --home $HOME/cosmos/nodes/beacon --keyring-backend test) --home $HOME/cosmos/nodes/beacon --keyring-backend test --node "tcp://127.0.0.1:29170" -y -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/single_node/frontrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | find_project_root() { 3 | local dir=$PWD 4 | while [ "$dir" != "" ] && [ ! -d "$dir/.git" ]; do 5 | dir=$(dirname "$dir") 6 | done 7 | echo "$dir" 8 | } 9 | 10 | PROJECT_ROOT=$(find_project_root) 11 | HOME=$HOME/.exampled 12 | BINARY=$PROJECT_ROOT/tutorials/auction/base/build/exampled 13 | 14 | echo $HOME 15 | $BINARY tx reserve "bob.cosmos" $($BINARY keys show alice -a --home $HOME --keyring-backend test) 1000uatom --from $($BINARY keys show bob -a --home $HOME --keyring-backend test) --home $HOME -y 16 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/single_node/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | find_project_root() { 3 | local dir=$PWD 4 | while [ "$dir" != "" ] && [ ! -d "$dir/.git" ]; do 5 | dir=$(dirname "$dir") 6 | done 7 | echo "$dir" 8 | } 9 | 10 | PROJECT_ROOT=$(find_project_root) 11 | HOME=$HOME/.exampled 12 | BINARY=$PROJECT_ROOT/tutorials/auction/base/build/exampled 13 | 14 | echo $PROJECT_ROOT 15 | echo $HOME 16 | 17 | rm -rf $HOME 18 | 19 | make build 20 | 21 | $BINARY init liveness --chain-id cosmos-1 --default-denom uatom --home $HOME 22 | $BINARY config set client chain-id cosmos-1 --home $HOME 23 | $BINARY config set client keyring-backend test --home $HOME 24 | $BINARY keys add val1 --home $HOME 25 | $BINARY keys add alice --home $HOME 26 | $BINARY keys add bob --home $HOME 27 | $BINARY genesis add-genesis-account val1 10000000000000000000000000uatom --home $HOME 28 | $BINARY genesis add-genesis-account alice 1000000000000000000uatom --home $HOME 29 | $BINARY genesis add-genesis-account bob 1000000000000000000uatom --home $HOME 30 | $BINARY genesis gentx val1 1000000000uatom --chain-id cosmos-1 --home $HOME 31 | $BINARY genesis collect-gentxs --home $HOME 32 | sed -i.bak'' 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025uatom"/' $HOME/config/app.toml 33 | $BINARY start --val-key val1 --run-provider true --home $HOME 34 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | 4 | pkill -f exampled &> /dev/null || true 5 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | find_project_root() { 4 | local dir=$PWD 5 | while [ "$dir" != "" ] && [ ! -d "$dir/.git" ]; do 6 | dir=$(dirname "$dir") 7 | done 8 | echo "$dir" 9 | } 10 | 11 | PROJECT_ROOT=$(find_project_root) 12 | export HOME=$HOME/.exampled 13 | echo $PROJECT_ROOT 14 | BINARY=$PROJECT_ROOT/tutorials/nameservice/base/build/exampled 15 | export CHAIN_ID=test 16 | export KEYRING=test 17 | # User balance of stake tokens 18 | export USER_COINS="100000000000uatom" 19 | # Amount of stake tokens staked 20 | export STAKE="100000000uatom" 21 | # Node IP address 22 | export NODE_IP="127.0.0.1" 23 | 24 | export ALICE="alice" 25 | export BARBARA="barbara" 26 | export CINDY="cindy" 27 | 28 | # Home directory 29 | export HOME_DIR=$HOME 30 | 31 | export USERS=($ALICE $BARBARA $CINDY) 32 | 33 | # Validator moniker 34 | export MONIKERS=("beacon" "val1" "val2") 35 | export BEACON_MONIKER="beacon" 36 | 37 | export NODES_ROOT_DIR=${HOME_DIR}/cosmos/nodes 38 | 39 | # Base port. Ports assigned after these ports sequentially by nodes. 40 | export RPC_LADDR_BASEPORT=29170 41 | export P2P_LADDR_BASEPORT=29180 42 | export GRPC_LADDR_BASEPORT=29190 43 | export NODE_ADDRESS_BASEPORT=29200 44 | export PPROF_LADDR_BASEPORT=29210 45 | export CLIENT_BASEPORT=29220 46 | 47 | export VALIDATOR_2=val2 48 | export ALICE=alice 49 | export BARBARA=barb 50 | export CINDY=cindy 51 | export DON=don 52 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/scripts/whois.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./vars.sh &> /dev/null 4 | 5 | if [ $# -eq 0 ]; then 6 | echo "Must provide an name to query" 7 | exit 1 8 | fi 9 | 10 | name="$1" 11 | 12 | $BINARY q whois "${name}" --node "tcp://127.0.0.1:29170" -o json 13 | -------------------------------------------------------------------------------- /tutorials/nameservice/base/x/auction/testutils/encoding.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "cosmossdk.io/x/tx/signing" 5 | "github.com/cosmos/cosmos-sdk/client" 6 | "github.com/cosmos/cosmos-sdk/codec" 7 | "github.com/cosmos/cosmos-sdk/codec/address" 8 | "github.com/cosmos/cosmos-sdk/codec/testutil" 9 | "github.com/cosmos/cosmos-sdk/codec/types" 10 | "github.com/cosmos/cosmos-sdk/std" 11 | sdk "github.com/cosmos/cosmos-sdk/types" 12 | "github.com/cosmos/cosmos-sdk/types/module" 13 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 14 | "github.com/cosmos/gogoproto/proto" 15 | ) 16 | 17 | // EncodingConfig specifies the concrete encoding types to use for a given app. 18 | // This is provided for compatibility between protobuf and amino implementations. 19 | type EncodingConfig struct { 20 | InterfaceRegistry types.InterfaceRegistry 21 | Marshaler codec.Codec 22 | TxConfig client.TxConfig 23 | Amino *codec.LegacyAmino 24 | } 25 | 26 | // MakeEncodingConfig creates an EncodingConfig for an amino based test configuration. 27 | func MakeEncodingConfig() EncodingConfig { 28 | interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ 29 | ProtoFiles: proto.HybridResolver, 30 | SigningOptions: signing.Options{ 31 | AddressCodec: address.Bech32Codec{ 32 | Bech32Prefix: sdk.Bech32MainPrefix, 33 | }, 34 | ValidatorAddressCodec: address.Bech32Codec{ 35 | Bech32Prefix: sdk.Bech32MainPrefix, 36 | }, 37 | }, 38 | }) 39 | 40 | appCodec := codec.NewProtoCodec(interfaceRegistry) 41 | legacyAmino := codec.NewLegacyAmino() 42 | txConfig := tx.NewTxConfig(appCodec, tx.DefaultSignModes) 43 | 44 | // amino := codec.NewLegacyAmino() 45 | // interfaceRegistry := codectypes.NewInterfaceRegistry() 46 | // cdc := codec.NewProtoCodec(interfaceRegistry) 47 | // txCfg := tx.NewTxConfig(cdc, tx.DefaultSignModes) 48 | return EncodingConfig{ 49 | InterfaceRegistry: interfaceRegistry, 50 | Marshaler: appCodec, 51 | TxConfig: txConfig, 52 | Amino: legacyAmino, 53 | } 54 | } 55 | 56 | func MakeTestEncodingConfig(modules ...module.AppModuleBasic) EncodingConfig { 57 | aminoCodec := codec.NewLegacyAmino() 58 | interfaceRegistry := testutil.CodecOptions{}.NewInterfaceRegistry() 59 | codec := codec.NewProtoCodec(interfaceRegistry) 60 | 61 | encCfg := EncodingConfig{ 62 | InterfaceRegistry: interfaceRegistry, 63 | Marshaler: codec, 64 | TxConfig: tx.NewTxConfig(codec, tx.DefaultSignModes), 65 | Amino: aminoCodec, 66 | } 67 | 68 | mb := module.NewBasicManager(modules...) 69 | 70 | std.RegisterLegacyAminoCodec(encCfg.Amino) 71 | std.RegisterInterfaces(encCfg.InterfaceRegistry) 72 | mb.RegisterLegacyAminoCodec(encCfg.Amino) 73 | mb.RegisterInterfaces(encCfg.InterfaceRegistry) 74 | 75 | return encCfg 76 | } 77 | 78 | func MakeTestTxConfig() client.TxConfig { 79 | interfaceRegistry := testutil.CodecOptions{}.NewInterfaceRegistry() 80 | cdc := codec.NewProtoCodec(interfaceRegistry) 81 | return tx.NewTxConfig(cdc, tx.DefaultSignModes) 82 | } 83 | -------------------------------------------------------------------------------- /tutorials/nameservice/docs/00-getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Table of Contents 4 | 5 | - [Getting Started](#overview-of-the-project) 6 | - [Understanding Front-Running](./01-understanding-frontrunning.md) 7 | - [Mitigating Front-running with Vote Extensions](./02-mitigating-front-running-with-vote-extesions.md) 8 | - [Demo of Mitigating Front-Running](./03-demo-of-mitigating-front-running.md) 9 | 10 | ## Getting Started 11 | 12 | ### Overview of the Project 13 | 14 | This tutorial outlines the development of a module designed to mitigate front-running in nameservice auctions. The following functions are central to this module: 15 | 16 | * `ExtendVote`: Gathers bids from the mempool and includes them in the vote extension to ensure a fair and transparent auction process. 17 | * `PrepareProposal`: Processes the vote extensions from the previous block, creating a special transaction that encapsulates bids to be included in the current proposal. 18 | * `ProcessProposal`: Validates that the first transaction in the proposal is the special transaction containing the vote extensions and ensures the integrity of the bids. 19 | 20 | In this advanced tutorial, we will be working with an example application that facilitates the auctioning of nameservices. To see what frontrunning and nameservices are [here](./01-understanding-frontrunning.md) This application provides a practical use case to explore the prevention of auction front-running, also known as "bid sniping", where a validator takes advantage of seeing a bid in the mempool to place their own higher bid before the original bid is processed. 21 | 22 | The tutorial will guide you through using the Cosmos SDK to mitigate front-running using vote extensions. The module will be built on top of the base blockchain provided in the `tutorials/base` directory and will use the `auction` module as a foundation. By the end of this tutorial, you will have a better understanding of how to prevent front-running in blockchain auctions, specifically in the context of nameservice auctioning. 23 | 24 | ## What are Vote extensions? 25 | 26 | Vote extensions is arbitrary information which can be inserted into a block. This feature is part of ABCI 2.0, which is available for use in the SDK 0.50 release and part of the 0.38 CometBFT release. 27 | 28 | More information about vote extensions can be seen [here](https://docs.cosmos.network/main/build/abci/vote-extensions). 29 | 30 | ## Requirements and Setup 31 | 32 | Before diving into the advanced tutorial on auction front-running simulation, ensure you meet the following requirements: 33 | 34 | * [Golang >1.21.5](https://golang.org/doc/install) installed 35 | * Familiarity with the concepts of front-running and MEV, as detailed in [Understanding Front-Running](./01-understanding-frontrunning.md) 36 | * Understanding of Vote Extensions as described [here](https://docs.cosmos.network/main/build/abci/vote-extensions) 37 | 38 | You will also need a foundational blockchain to build upon coupled with your own module. The `tutorials/base` directory has the necessary blockchain code to start your custom project with the Cosmos SDK. For the module, you can use the `auction` module provided in the `tutorials/auction/x/auction` directory as a reference but please be aware that all of the code needed to implement vote extensions is already implemented in this module. 39 | 40 | This will set up a strong base for your blockchain, enabling the integration of advanced features such as auction front-running simulation. 41 | -------------------------------------------------------------------------------- /tutorials/nameservice/docs/01-understanding-frontrunning.md: -------------------------------------------------------------------------------- 1 | # Understanding Front-Running and more 2 | 3 | ## Introduction 4 | 5 | Blockchain technology is vulnerable to practices that can affect the fairness and security of the network. Two such practices are front-running and Maximal Extractable Value (MEV), which are important for blockchain participants to understand. 6 | 7 | ## What is Front-Running? 8 | 9 | Front-running is when someone, such as a validator, uses their ability to see pending transactions to execute their own transactions first, benefiting from the knowledge of upcoming transactions. In nameservice auctions, a front-runner might place a higher bid before the original bid is confirmed, unfairly winning the auction. 10 | 11 | ## Nameservices and Nameservice Auctions 12 | 13 | Nameservices are human-readable identifiers on a blockchain, akin to internet domain names, that correspond to specific addresses or resources. They simplify interactions with typically long and complex blockchain addresses, allowing users to have a memorable and unique identifier for their blockchain address or smart contract. 14 | 15 | Nameservice auctions are the process by which these identifiers are bid on and acquired. To combat front-running—where someone might use knowledge of pending bids to place a higher bid first—mechanisms such as commit-reveal schemes, auction extensions, and fair sequencing are implemented. These strategies ensure a transparent and fair bidding process, reducing the potential for Maximal Extractable Value (MEV) exploitation. 16 | 17 | ## What is Maximal Extractable Value (MEV)? 18 | 19 | MEV is the highest value that can be extracted by manipulating the order of transactions within a block, beyond the standard block rewards and fees. This has become more prominent with the growth of decentralised finance (DeFi), where transaction order can greatly affect profits. 20 | 21 | ## Implications of MEV 22 | 23 | MEV can lead to: 24 | 25 | - **Network Security**: Potential centralisation, as those with more computational power might dominate the process, increasing the risk of attacks. 26 | - **Market Fairness**: An uneven playing field where only a few can gain at the expense of the majority. 27 | - **User Experience**: Higher fees and network congestion due to the competition for MEV. 28 | 29 | ## Mitigating MEV and Front-Running 30 | 31 | Some solutions being developed to mitigate MEV and front-running, including: 32 | 33 | - **Time-delayed Transactions**: Random delays to make transaction timing unpredictable. 34 | - **Private Transaction Pools**: Concealing transactions until they are mined. 35 | - **Fair Sequencing Services**: Processing transactions in the order they are received. 36 | 37 | For this tutorial, we will be exploring the last solution, fair sequencing services, in the context of nameservice auctions. 38 | 39 | ## Conclusion 40 | 41 | MEV and front-running are challenges to blockchain integrity and fairness. Ongoing innovation and implementation of mitigation strategies are crucial for the ecosystem's health and success. 42 | -------------------------------------------------------------------------------- /tutorials/nameservice/docs/03-demo-of-mitigating-front-running.md: -------------------------------------------------------------------------------- 1 | # Demo of Mitigating Front-Running with Vote Extensions 2 | 3 | The purpose of this demo is to test the implementation of the `VoteExtensionHandler` and `PrepareProposalHandler` that we have just added to the codebase. These handlers are designed to mitigate front-running by ensuring that all validators have a consistent view of the mempool when preparing proposals. 4 | 5 | In this demo, we are using a 3 validator network. The Beacon validator is special because it has a custom transaction provider enabled. This means that it can potentially manipulate the order of transactions in a proposal to its advantage (i.e., front-running). 6 | 7 | 1. Bootstrap the validator network: This sets up a network with 3 validators. The script `./scripts/configure.sh is used to configure the network and the validators. 8 | 9 | ```shell 10 | cd scripts 11 | configure.sh 12 | ``` 13 | 14 | If this doesn't work please ensure you have run `make build` in the `tutorials/nameservice/base` directory. 15 | 16 | 17 | 2. Have alice attempt to reserve `bob.cosmos`: This is a normal transaction that alice wants to execute. The script ``./scripts/reserve.sh "bob.cosmos"` is used to send this transaction. 18 | 19 | ```shell 20 | reserve.sh "bob.cosmos" 21 | ``` 22 | 23 | 3. Query to verify the name has been reserved: This is to check the result of the transaction. The script `./scripts/whois.sh "bob.cosmos"` is used to query the state of the blockchain. 24 | 25 | ```shell 26 | whois.sh "bob.cosmos" 27 | ``` 28 | 29 | It should return: 30 | 31 | ```{ 32 | "name": { 33 | "name": "bob.cosmos", 34 | "owner": "cosmos1nq9wuvuju4jdmpmzvxmg8zhhu2ma2y2l2pnu6w", 35 | "resolve_address": "cosmos1h6zy2kn9efxtw5z22rc5k9qu7twl70z24kr3ht", 36 | "amount": [ 37 | { 38 | "denom": "uatom", 39 | "amount": "1000" 40 | } 41 | ] 42 | } 43 | } 44 | ``` 45 | 46 | To detect front-running attempts by the beacon, scrutinise the logs during the `ProcessProposal` stage. Open the logs for each validator, including the beacon, `val1`, and `val2`, to observe the following behavior. Open the log file of the validator node. The location of this file can vary depending on your setup, but it's typically located in a directory like `$HOME/cosmos/nodes/#{validator}/logs`. The directory in this case will be under the validator so, `beacon` `val1` or `val2`. Run the following to tail the logs of the validator or beacon: 47 | 48 | ```shell 49 | tail -f $HOME/cosmos/nodes/#{validator}/logs 50 | ``` 51 | 52 | ```shell 53 | 2:47PM ERR ❌️:: Detected invalid proposal bid :: name:"bob.cosmos" resolveAddress:"cosmos1wmuwv38pdur63zw04t0c78r2a8dyt08hf9tpvd" owner:"cosmos1wmuwv38pdur63zw04t0c78r2a8dyt08hf9tpvd" amount: module=server 54 | 2:47PM ERR ❌️:: Unable to validate bids in Process Proposal :: module=server 55 | 2:47PM ERR prevote step: state machine rejected a proposed block; this should not happen:the proposer may be misbehaving; prevoting nil err=null height=142 module=consensus round=0 56 | ``` 57 | 58 | 59 | 4. List the Beacon's keys: This is to verify the addresses of the validators. The script `./scripts/list-beacon-keys.sh` is used to list the keys. 60 | 61 | ```shell 62 | list-beacon-keys.sh 63 | ``` 64 | 65 | We should receive something similar to the following: 66 | 67 | ```shell 68 | [ 69 | { 70 | "name": "alice", 71 | "type": "local", 72 | "address": "cosmos1h6zy2kn9efxtw5z22rc5k9qu7twl70z24kr3ht", 73 | "pubkey": "{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A32cvBUkNJz+h2vld4A5BxvU5Rd+HyqpR3aGtvEhlm4C\"}" 74 | }, 75 | { 76 | "name": "barbara", 77 | "type": "local", 78 | "address": "cosmos1nq9wuvuju4jdmpmzvxmg8zhhu2ma2y2l2pnu6w", 79 | "pubkey": "{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"Ag9PFsNyTQPoJdbyCWia5rZH9CrvSrjMsk7Oz4L3rXQ5\"}" 80 | }, 81 | { 82 | "name": "beacon-key", 83 | "type": "local", 84 | "address": "cosmos1ez9a6x7lz4gvn27zr368muw8jeyas7sv84lfup", 85 | "pubkey": "{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AlzJZMWyN7lass710TnAhyuFKAFIaANJyw5ad5P2kpcH\"}" 86 | }, 87 | { 88 | "name": "cindy", 89 | "type": "local", 90 | "address": "cosmos1m5j6za9w4qc2c5ljzxmm2v7a63mhjeag34pa3g", 91 | "pubkey": "{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A6F1/3yot5OpyXoSkBbkyl+3rqBkxzRVSJfvSpm/AvW5\"}" 92 | } 93 | ] 94 | ``` 95 | 96 | This allows us to match up the addresses and see that the bid was not front run by the beacon due as the resolve address is Alice's address and not the beacons address. 97 | 98 | By running this demo, we can verify that the `VoteExtensionHandler` and `PrepareProposalHandler` are working as expected and that they are able to prevent front-running. 99 | 100 | ## Conclusion 101 | 102 | In this tutorial, we've tackled front-running and MEV, focusing on nameservice auctions' vulnerability to these issues. We've explored vote extensions, a key feature of ABCI 2.0, and integrated them into a Cosmos SDK application. 103 | 104 | Through practical exercises, you've implemented vote extensions, and tested their effectiveness in creating a fair auction system. You've gained practical insights by configuring a validator network and analysing blockchain logs. 105 | 106 | Keep experimenting with these concepts, engage with the community, and stay updated on new advancements. The knowledge you've acquired here is crucial for developing secure and fair blockchain applications. 107 | -------------------------------------------------------------------------------- /tutorials/oracle/base/Makefile: -------------------------------------------------------------------------------- 1 | BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 2 | COMMIT := $(shell git log -1 --format='%H') 3 | 4 | # don't override user values 5 | ifeq (,$(VERSION)) 6 | VERSION := $(shell git describe --exact-match 2>/dev/null) 7 | # if VERSION is empty, then populate it with branch's name and raw commit hash 8 | ifeq (,$(VERSION)) 9 | VERSION := $(BRANCH)-$(COMMIT) 10 | endif 11 | endif 12 | 13 | # Update the ldflags with the app, client & server names 14 | ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=example \ 15 | -X github.com/cosmos/cosmos-sdk/version.AppName=exampled \ 16 | -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ 17 | -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) 18 | 19 | BUILD_FLAGS := -ldflags '$(ldflags)' 20 | 21 | ########### 22 | # Install # 23 | ########### 24 | 25 | all: install 26 | 27 | install: 28 | @echo "--> ensure dependencies have not been modified" 29 | @go mod verify 30 | @echo "--> installing exampled" 31 | @go install $(BUILD_FLAGS) -mod=readonly ./cmd/exampled 32 | 33 | init: 34 | ./scripts/init.sh -------------------------------------------------------------------------------- /tutorials/oracle/base/app/app.yaml: -------------------------------------------------------------------------------- 1 | modules: 2 | - name: runtime 3 | config: 4 | "@type": cosmos.app.runtime.v1alpha1.Module 5 | app_name: ExampleApp 6 | # During begin block slashing happens after distr.BeginBlocker so that 7 | # there is nothing left over in the validator fee pool, so as to keep the CanWithdrawInvariant invariant. 8 | # NOTE: staking module is required if HistoricalEntries param > 0 9 | begin_blockers: [distribution, staking] 10 | end_blockers: [staking] 11 | # NOTE: The genutils module must occur after staking so that pools are properly initialized with tokens from genesis accounts. 12 | # NOTE: The genutils module must also occur after auth so that it can access the params from auth. 13 | init_genesis: [auth, bank, distribution, staking, genutil, oracle] 14 | override_store_keys: 15 | - module_name: auth 16 | kv_store_key: acc 17 | - name: auth 18 | config: 19 | "@type": cosmos.auth.module.v1.Module 20 | bech32_prefix: tutorial 21 | module_account_permissions: 22 | - account: fee_collector 23 | - account: distribution 24 | - account: bonded_tokens_pool 25 | permissions: [burner, staking] 26 | - account: not_bonded_tokens_pool 27 | permissions: [burner, staking] 28 | - name: bank 29 | config: 30 | "@type": cosmos.bank.module.v1.Module 31 | blocked_module_accounts_override: 32 | [auth, distribution, bonded_tokens_pool, not_bonded_tokens_pool] 33 | - name: staking 34 | config: 35 | "@type": cosmos.staking.module.v1.Module 36 | - name: distribution 37 | config: 38 | "@type": cosmos.distribution.module.v1.Module 39 | - name: consensus 40 | config: 41 | "@type": cosmos.consensus.module.v1.Module 42 | - name: genutil 43 | config: 44 | "@type": cosmos.genutil.module.v1.Module 45 | - name: tx 46 | config: 47 | "@type": cosmos.tx.config.v1.Config 48 | - name: oracle 49 | config: 50 | "@type": cosmos.oracle.module.v1.Module -------------------------------------------------------------------------------- /tutorials/oracle/base/app/params/config.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "cosmossdk.io/errors" 5 | "cosmossdk.io/math" 6 | sdk "github.com/cosmos/cosmos-sdk/types" 7 | "github.com/cosmos/cosmos-sdk/types/address" 8 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 9 | ) 10 | 11 | const ( 12 | CoinUnit = "tutorial" 13 | 14 | DefaultBondDenom = CoinUnit 15 | 16 | // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address. 17 | Bech32PrefixAccAddr = "tutorial" 18 | ) 19 | 20 | var ( 21 | // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. 22 | Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" 23 | // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address. 24 | Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" 25 | // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key. 26 | Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" 27 | // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address. 28 | Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" 29 | // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key. 30 | Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" 31 | ) 32 | 33 | func init() { 34 | SetAddressPrefixes() 35 | RegisterDenoms() 36 | } 37 | 38 | func RegisterDenoms() { 39 | err := sdk.RegisterDenom(CoinUnit, math.LegacyOneDec()) 40 | if err != nil { 41 | panic(err) 42 | } 43 | } 44 | 45 | func SetAddressPrefixes() { 46 | config := sdk.GetConfig() 47 | config.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) 48 | config.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) 49 | config.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) 50 | 51 | // This is copied from the cosmos sdk v0.43.0-beta1 52 | // source: https://github.com/cosmos/cosmos-sdk/blob/v0.43.0-beta1/types/address.go#L141 53 | config.SetAddressVerifier(func(bytes []byte) error { 54 | if len(bytes) == 0 { 55 | return errors.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty") 56 | } 57 | 58 | if len(bytes) > address.MaxAddrLen { 59 | return errors.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bytes)) 60 | } 61 | 62 | if len(bytes) != 20 && len(bytes) != 32 { 63 | return errors.Wrapf(sdkerrors.ErrUnknownAddress, "address length must be 20 or 32 bytes, got %d", len(bytes)) 64 | } 65 | 66 | return nil 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /tutorials/oracle/base/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 | Codec codec.Codec 14 | TxConfig client.TxConfig 15 | Amino *codec.LegacyAmino 16 | } 17 | -------------------------------------------------------------------------------- /tutorials/oracle/base/cmd/exampled/cmd/commands.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | "cosmossdk.io/log" 8 | confixcmd "cosmossdk.io/tools/confix/cmd" 9 | dbm "github.com/cosmos/cosmos-db" 10 | "github.com/cosmos/cosmos-sdk/client" 11 | "github.com/cosmos/cosmos-sdk/client/debug" 12 | "github.com/cosmos/cosmos-sdk/client/flags" 13 | "github.com/cosmos/cosmos-sdk/client/keys" 14 | "github.com/cosmos/cosmos-sdk/client/pruning" 15 | "github.com/cosmos/cosmos-sdk/client/rpc" 16 | "github.com/cosmos/cosmos-sdk/client/snapshot" 17 | "github.com/cosmos/cosmos-sdk/server" 18 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 19 | sdk "github.com/cosmos/cosmos-sdk/types" 20 | "github.com/cosmos/cosmos-sdk/types/module" 21 | authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" 22 | genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/viper" 25 | 26 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/app" 27 | ) 28 | 29 | func initRootCmd(rootCmd *cobra.Command, txConfig client.TxConfig, basicManager module.BasicManager) { 30 | cfg := sdk.GetConfig() 31 | cfg.Seal() 32 | 33 | rootCmd.AddCommand( 34 | genutilcli.InitCmd(basicManager, app.DefaultNodeHome), 35 | debug.Cmd(), 36 | confixcmd.ConfigCommand(), 37 | pruning.Cmd(newApp, app.DefaultNodeHome), 38 | snapshot.Cmd(newApp), 39 | ) 40 | 41 | server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, appExport, func(startCmd *cobra.Command) {}) 42 | 43 | // add keybase, auxiliary RPC, query, genesis, and tx child commands 44 | rootCmd.AddCommand( 45 | server.StatusCommand(), 46 | genutilcli.Commands(txConfig, basicManager, app.DefaultNodeHome), 47 | queryCommand(), 48 | txCommand(), 49 | keys.Commands(), 50 | ) 51 | } 52 | 53 | func queryCommand() *cobra.Command { 54 | cmd := &cobra.Command{ 55 | Use: "query", 56 | Aliases: []string{"q"}, 57 | Short: "Querying subcommands", 58 | DisableFlagParsing: false, 59 | SuggestionsMinimumDistance: 2, 60 | RunE: client.ValidateCmd, 61 | } 62 | 63 | cmd.AddCommand( 64 | rpc.ValidatorCommand(), 65 | server.QueryBlockCmd(), 66 | authcmd.QueryTxsByEventsCmd(), 67 | server.QueryBlocksCmd(), 68 | authcmd.QueryTxCmd(), 69 | server.QueryBlockResultsCmd(), 70 | ) 71 | 72 | return cmd 73 | } 74 | 75 | func txCommand() *cobra.Command { 76 | cmd := &cobra.Command{ 77 | Use: "tx", 78 | Short: "Transactions subcommands", 79 | DisableFlagParsing: false, 80 | SuggestionsMinimumDistance: 2, 81 | RunE: client.ValidateCmd, 82 | } 83 | 84 | cmd.AddCommand( 85 | authcmd.GetSignCommand(), 86 | authcmd.GetSignBatchCommand(), 87 | authcmd.GetMultiSignCommand(), 88 | authcmd.GetMultiSignBatchCmd(), 89 | authcmd.GetValidateSignaturesCommand(), 90 | authcmd.GetBroadcastCommand(), 91 | authcmd.GetEncodeCommand(), 92 | authcmd.GetDecodeCommand(), 93 | authcmd.GetSimulateCmd(), 94 | ) 95 | 96 | return cmd 97 | } 98 | 99 | // newApp is an appCreator 100 | func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { 101 | baseappOptions := server.DefaultBaseappOptions(appOpts) 102 | app, err := app.NewExampleApp(logger, db, traceStore, true, appOpts, baseappOptions...) 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | return app 108 | } 109 | 110 | // appExport creates a new app (optionally at a given height) and exports state. 111 | func appExport( 112 | logger log.Logger, 113 | db dbm.DB, 114 | traceStore io.Writer, 115 | height int64, 116 | forZeroHeight bool, 117 | jailAllowedAddrs []string, 118 | appOpts servertypes.AppOptions, 119 | modulesToExport []string, 120 | ) (servertypes.ExportedApp, error) { 121 | var ( 122 | ExampleApp *app.ExampleApp 123 | err error 124 | ) 125 | 126 | // this check is necessary as we use the flag in x/upgrade. 127 | // we can exit more gracefully by checking the flag here. 128 | homePath, ok := appOpts.Get(flags.FlagHome).(string) 129 | if !ok || homePath == "" { 130 | return servertypes.ExportedApp{}, errors.New("application home not set") 131 | } 132 | 133 | viperAppOpts, ok := appOpts.(*viper.Viper) 134 | if !ok { 135 | return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper") 136 | } 137 | 138 | // overwrite the FlagInvCheckPeriod 139 | viperAppOpts.Set(server.FlagInvCheckPeriod, 1) 140 | appOpts = viperAppOpts 141 | 142 | if height != -1 { 143 | ExampleApp, err = app.NewExampleApp(logger, db, traceStore, false, appOpts) 144 | if err != nil { 145 | return servertypes.ExportedApp{}, err 146 | } 147 | 148 | if err := ExampleApp.LoadHeight(height); err != nil { 149 | return servertypes.ExportedApp{}, err 150 | } 151 | } else { 152 | ExampleApp, err = app.NewExampleApp(logger, db, traceStore, true, appOpts) 153 | if err != nil { 154 | return servertypes.ExportedApp{}, err 155 | } 156 | } 157 | 158 | return ExampleApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) 159 | } 160 | -------------------------------------------------------------------------------- /tutorials/oracle/base/cmd/exampled/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "cosmossdk.io/client/v2/autocli" 8 | clientv2keyring "cosmossdk.io/client/v2/autocli/keyring" 9 | "cosmossdk.io/core/address" 10 | "cosmossdk.io/depinject" 11 | "cosmossdk.io/log" 12 | cmtcfg "github.com/cometbft/cometbft/config" 13 | "github.com/cosmos/cosmos-sdk/client" 14 | "github.com/cosmos/cosmos-sdk/client/config" 15 | "github.com/cosmos/cosmos-sdk/codec" 16 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 17 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 18 | "github.com/cosmos/cosmos-sdk/server" 19 | serverconfig "github.com/cosmos/cosmos-sdk/server/config" 20 | "github.com/cosmos/cosmos-sdk/types/module" 21 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 22 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 23 | txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" 24 | "github.com/cosmos/cosmos-sdk/x/auth/types" 25 | "github.com/spf13/cobra" 26 | 27 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/app" 28 | ) 29 | 30 | // NewRootCmd creates a new root command for exampled. It is called once in the 31 | // main function. 32 | func NewRootCmd() *cobra.Command { 33 | var ( 34 | // It can be used to set options like the signer, fee granularity, and other transaction-related configurations. 35 | txConfigOpts tx.ConfigOptions 36 | // It can include options for modules, address codecs, and other CLI-related configurations. 37 | autoCliOpts autocli.AppOptions 38 | // This includes things like genesis data, default genesis data, verifying genesis data, and the module's name. 39 | moduleBasicManager module.BasicManager 40 | // This can include things like the client's home directory, the client's input/output, and the client's trust node. 41 | clientCtx client.Context 42 | ) 43 | 44 | if err := depinject.Inject( 45 | depinject.Configs(app.AppConfig(), 46 | depinject.Supply( 47 | log.NewNopLogger(), 48 | ), 49 | depinject.Provide( 50 | ProvideClientContext, 51 | ProvideKeyring, 52 | ), 53 | ), 54 | &txConfigOpts, 55 | &autoCliOpts, 56 | &moduleBasicManager, 57 | &clientCtx, 58 | ); err != nil { 59 | panic(err) 60 | } 61 | 62 | rootCmd := &cobra.Command{ 63 | Use: "exampled", 64 | Short: "exampled - the example chain app", 65 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { 66 | // set the default command outputs 67 | cmd.SetOut(cmd.OutOrStdout()) 68 | cmd.SetErr(cmd.ErrOrStderr()) 69 | 70 | clientCtx = clientCtx.WithCmdContext(cmd.Context()) 71 | clientCtx, err := client.ReadPersistentCommandFlags(clientCtx, cmd.Flags()) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | clientCtx, err = config.ReadFromClientConfig(clientCtx) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | // sign mode textual is only available in online mode 82 | if !clientCtx.Offline { 83 | // This needs to go after ReadFromClientConfig, as that function ets the RPC client needed for SIGN_MODE_TEXTUAL. 84 | txConfigOpts.EnabledSignModes = append(txConfigOpts.EnabledSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) 85 | txConfigOpts.TextualCoinMetadataQueryFn = txmodule.NewGRPCCoinMetadataQueryFn(clientCtx) 86 | txConfigWithTextual, err := tx.NewTxConfigWithOptions(codec.NewProtoCodec(clientCtx.InterfaceRegistry), txConfigOpts) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | clientCtx = clientCtx.WithTxConfig(txConfigWithTextual) 92 | } 93 | 94 | if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { 95 | return err 96 | } 97 | 98 | if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { 99 | return err 100 | } 101 | 102 | // overwrite the minimum gas price from the app configuration 103 | srvCfg := serverconfig.DefaultConfig() 104 | srvCfg.MinGasPrices = "0example" 105 | 106 | // overwrite the block timeout 107 | cmtCfg := cmtcfg.DefaultConfig() 108 | cmtCfg.Consensus.TimeoutCommit = 3 * time.Second 109 | cmtCfg.LogLevel = "*:error,p2p:info,state:info" // better default logging 110 | 111 | // TODO: copy from nameservice/simapp 112 | return server.InterceptConfigsPreRunHandler(cmd, "", srvCfg, cmtCfg) 113 | }, 114 | } 115 | 116 | initRootCmd(rootCmd, clientCtx.TxConfig, moduleBasicManager) 117 | 118 | if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { 119 | panic(err) 120 | } 121 | 122 | return rootCmd 123 | } 124 | 125 | func ProvideClientContext( 126 | appCodec codec.Codec, 127 | interfaceRegistry codectypes.InterfaceRegistry, 128 | txConfig client.TxConfig, 129 | legacyAmino *codec.LegacyAmino, 130 | ) client.Context { 131 | clientCtx := client.Context{}. 132 | WithCodec(appCodec). 133 | WithInterfaceRegistry(interfaceRegistry). 134 | WithTxConfig(txConfig). 135 | WithLegacyAmino(legacyAmino). 136 | WithInput(os.Stdin). 137 | WithAccountRetriever(types.AccountRetriever{}). 138 | WithHomeDir(app.DefaultNodeHome). 139 | WithViper("TUTORIAL") // env variable prefix 140 | 141 | // Read the config again to overwrite the default values with the values from the config file 142 | clientCtx, _ = config.ReadFromClientConfig(clientCtx) 143 | 144 | return clientCtx 145 | } 146 | 147 | func ProvideKeyring(clientCtx client.Context, addressCodec address.Codec) (clientv2keyring.Keyring, error) { 148 | kb, err := client.NewKeyringFromBackend(clientCtx, clientCtx.Keyring.Backend()) 149 | if err != nil { 150 | return nil, err 151 | } 152 | 153 | return keyring.NewAutoCLIKeyring(kb) 154 | } 155 | -------------------------------------------------------------------------------- /tutorials/oracle/base/cmd/exampled/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 8 | 9 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/app" 10 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/app/params" 11 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/cmd/exampled/cmd" 12 | ) 13 | 14 | func main() { 15 | params.SetAddressPrefixes() 16 | 17 | rootCmd := cmd.NewRootCmd() 18 | if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { 19 | fmt.Fprintln(rootCmd.OutOrStderr(), err) 20 | os.Exit(1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tutorials/oracle/base/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -r ~/.exampled || true 4 | exampled_BIN=$(which exampled) 5 | # configure exampled 6 | $exampled_BIN config set client chain-id demo 7 | $exampled_BIN config set client keyring-backend test 8 | $exampled_BIN keys add alice 9 | $exampled_BIN keys add bob 10 | $exampled_BIN init test --chain-id demo --default-denom example 11 | # update genesis 12 | $exampled_BIN genesis add-genesis-account alice 10000000example --keyring-backend test 13 | $exampled_BIN genesis add-genesis-account bob 1000example --keyring-backend test 14 | # create default validator 15 | $exampled_BIN genesis gentx alice 1000000example --chain-id demo 16 | sed -i.bak'' 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025uatom"/' $HOME/.exampled/config/app.toml 17 | jq '.consensus.params.abci.vote_extensions_enable_height = "1"' ~/.exampled/config/genesis.json > output.json && mv output.json ~/.exampled/config/genesis.json 18 | $exampled_BIN genesis collect-gentxs 19 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/Makefile: -------------------------------------------------------------------------------- 1 | DOCKER := $(shell which docker) 2 | 3 | ################# 4 | ### Build ### 5 | ################# 6 | 7 | test: 8 | @echo "--> Running tests" 9 | go test -v ./... 10 | 11 | test-integration: 12 | @echo "--> Running integration tests" 13 | cd integration; go test -v ./... 14 | 15 | .PHONY: test test-integration 16 | 17 | ################## 18 | ### Protobuf ### 19 | ################## 20 | 21 | protoVer=0.14.0 22 | protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) 23 | protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) 24 | 25 | proto-all: proto-format proto-lint proto-gen 26 | 27 | proto-gen: 28 | @echo "Generating protobuf files..." 29 | @$(protoImage) sh ./scripts/protocgen.sh 30 | @go mod tidy 31 | 32 | proto-format: 33 | @$(protoImage) find ./ -name "*.proto" -exec clang-format -i {} \; 34 | 35 | proto-lint: 36 | @$(protoImage) buf lint proto/ --error-format=json 37 | 38 | .PHONY: proto-all proto-gen proto-format proto-lint 39 | 40 | ################# 41 | ### Linting ### 42 | ################# 43 | 44 | golangci_lint_cmd=golangci-lint 45 | golangci_version=v1.51.2 46 | 47 | lint: 48 | @echo "--> Running linter" 49 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 50 | @$(golangci_lint_cmd) run ./... --timeout 15m 51 | 52 | lint-fix: 53 | @echo "--> Running linter and fixing issues" 54 | @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version) 55 | @$(golangci_lint_cmd) run ./... --fix --timeout 15m 56 | 57 | .PHONY: lint lint-fix -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/abci/provider_aggregator.go: -------------------------------------------------------------------------------- 1 | package abci 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle/keeper" 7 | ) 8 | 9 | // Provider defines an interface for fetching prices and candles for a given set 10 | // of currency pairs. The provider is presumed to be a trusted source of prices. 11 | type Provider interface { 12 | GetTickerPrices(...keeper.CurrencyPair) (map[string]keeper.TickerPrice, error) 13 | } 14 | 15 | // ProviderAggregator is a simple aggregator for provider prices and candles. 16 | // It is thread-safe since it is assumed to be called concurrently in price 17 | // fetching goroutines, i.e. ExtendVote. 18 | type ProviderAggregator struct { 19 | mtx sync.Mutex 20 | 21 | providerPrices keeper.AggregatedProviderPrices 22 | } 23 | 24 | func NewProviderAggregator() *ProviderAggregator { 25 | return &ProviderAggregator{ 26 | providerPrices: make(keeper.AggregatedProviderPrices), 27 | } 28 | } 29 | 30 | func (p *ProviderAggregator) SetProviderTickerPricesAndCandles( 31 | providerName string, 32 | prices map[string]keeper.TickerPrice, 33 | pair keeper.CurrencyPair, 34 | ) bool { 35 | p.mtx.Lock() 36 | defer p.mtx.Unlock() 37 | 38 | // set prices and candles for this provider if we haven't seen it before 39 | if _, ok := p.providerPrices[providerName]; !ok { 40 | p.providerPrices[providerName] = make(map[string]keeper.TickerPrice) 41 | } 42 | 43 | // set price for provider/base (e.g. Binance -> ATOM -> 11.98) 44 | tp, pricesOk := prices[pair.String()] 45 | if pricesOk { 46 | p.providerPrices[providerName][pair.Base] = tp 47 | } 48 | 49 | // return true if we set at least one price 50 | return pricesOk 51 | } 52 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/api/v1/query_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc (unknown) 5 | // source: cosmos/oracle/v1/query.proto 6 | 7 | package oraclev1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Query_Counter_FullMethodName = "/cosmos.oracle.v1.Query/Counter" 23 | Query_Prices_FullMethodName = "/cosmos.oracle.v1.Query/Prices" 24 | ) 25 | 26 | // QueryClient is the client API for Query service. 27 | // 28 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 29 | type QueryClient interface { 30 | // Counter returns the current counter value. 31 | Counter(ctx context.Context, in *QueryCounterRequest, opts ...grpc.CallOption) (*QueryCounterResponse, error) 32 | // Prices returns the current prices. 33 | Prices(ctx context.Context, in *QueryPricesRequest, opts ...grpc.CallOption) (*QueryPricesResponse, error) 34 | } 35 | 36 | type queryClient struct { 37 | cc grpc.ClientConnInterface 38 | } 39 | 40 | func NewQueryClient(cc grpc.ClientConnInterface) QueryClient { 41 | return &queryClient{cc} 42 | } 43 | 44 | func (c *queryClient) Counter(ctx context.Context, in *QueryCounterRequest, opts ...grpc.CallOption) (*QueryCounterResponse, error) { 45 | out := new(QueryCounterResponse) 46 | err := c.cc.Invoke(ctx, Query_Counter_FullMethodName, in, out, opts...) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return out, nil 51 | } 52 | 53 | func (c *queryClient) Prices(ctx context.Context, in *QueryPricesRequest, opts ...grpc.CallOption) (*QueryPricesResponse, error) { 54 | out := new(QueryPricesResponse) 55 | err := c.cc.Invoke(ctx, Query_Prices_FullMethodName, in, out, opts...) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return out, nil 60 | } 61 | 62 | // QueryServer is the server API for Query service. 63 | // All implementations must embed UnimplementedQueryServer 64 | // for forward compatibility 65 | type QueryServer interface { 66 | // Counter returns the current counter value. 67 | Counter(context.Context, *QueryCounterRequest) (*QueryCounterResponse, error) 68 | // Prices returns the current prices. 69 | Prices(context.Context, *QueryPricesRequest) (*QueryPricesResponse, error) 70 | mustEmbedUnimplementedQueryServer() 71 | } 72 | 73 | // UnimplementedQueryServer must be embedded to have forward compatible implementations. 74 | type UnimplementedQueryServer struct { 75 | } 76 | 77 | func (UnimplementedQueryServer) Counter(context.Context, *QueryCounterRequest) (*QueryCounterResponse, error) { 78 | return nil, status.Errorf(codes.Unimplemented, "method Counter not implemented") 79 | } 80 | func (UnimplementedQueryServer) Prices(context.Context, *QueryPricesRequest) (*QueryPricesResponse, error) { 81 | return nil, status.Errorf(codes.Unimplemented, "method Prices not implemented") 82 | } 83 | func (UnimplementedQueryServer) mustEmbedUnimplementedQueryServer() {} 84 | 85 | // UnsafeQueryServer may be embedded to opt out of forward compatibility for this service. 86 | // Use of this interface is not recommended, as added methods to QueryServer will 87 | // result in compilation errors. 88 | type UnsafeQueryServer interface { 89 | mustEmbedUnimplementedQueryServer() 90 | } 91 | 92 | func RegisterQueryServer(s grpc.ServiceRegistrar, srv QueryServer) { 93 | s.RegisterService(&Query_ServiceDesc, srv) 94 | } 95 | 96 | func _Query_Counter_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 97 | in := new(QueryCounterRequest) 98 | if err := dec(in); err != nil { 99 | return nil, err 100 | } 101 | if interceptor == nil { 102 | return srv.(QueryServer).Counter(ctx, in) 103 | } 104 | info := &grpc.UnaryServerInfo{ 105 | Server: srv, 106 | FullMethod: Query_Counter_FullMethodName, 107 | } 108 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 109 | return srv.(QueryServer).Counter(ctx, req.(*QueryCounterRequest)) 110 | } 111 | return interceptor(ctx, in, info, handler) 112 | } 113 | 114 | func _Query_Prices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 115 | in := new(QueryPricesRequest) 116 | if err := dec(in); err != nil { 117 | return nil, err 118 | } 119 | if interceptor == nil { 120 | return srv.(QueryServer).Prices(ctx, in) 121 | } 122 | info := &grpc.UnaryServerInfo{ 123 | Server: srv, 124 | FullMethod: Query_Prices_FullMethodName, 125 | } 126 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 127 | return srv.(QueryServer).Prices(ctx, req.(*QueryPricesRequest)) 128 | } 129 | return interceptor(ctx, in, info, handler) 130 | } 131 | 132 | // Query_ServiceDesc is the grpc.ServiceDesc for Query service. 133 | // It's only intended for direct use with grpc.RegisterService, 134 | // and not to be introspected or modified (even as a copy) 135 | var Query_ServiceDesc = grpc.ServiceDesc{ 136 | ServiceName: "cosmos.oracle.v1.Query", 137 | HandlerType: (*QueryServer)(nil), 138 | Methods: []grpc.MethodDesc{ 139 | { 140 | MethodName: "Counter", 141 | Handler: _Query_Counter_Handler, 142 | }, 143 | { 144 | MethodName: "Prices", 145 | Handler: _Query_Prices_Handler, 146 | }, 147 | }, 148 | Streams: []grpc.StreamDesc{}, 149 | Metadata: "cosmos/oracle/v1/query.proto", 150 | } 151 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/api/v1/tx_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.3.0 4 | // - protoc (unknown) 5 | // source: cosmos/oracle/v1/tx.proto 6 | 7 | package oraclev1 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.32.0 or later. 19 | const _ = grpc.SupportPackageIsVersion7 20 | 21 | const ( 22 | Msg_IncrementCounter_FullMethodName = "/cosmos.oracle.v1.Msg/IncrementCounter" 23 | ) 24 | 25 | // MsgClient is the client API for Msg service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type MsgClient interface { 29 | // IncrementCounter increments the counter. 30 | IncrementCounter(ctx context.Context, in *MsgIncrementCounter, opts ...grpc.CallOption) (*MsgIncrementCounterResponse, error) 31 | } 32 | 33 | type msgClient struct { 34 | cc grpc.ClientConnInterface 35 | } 36 | 37 | func NewMsgClient(cc grpc.ClientConnInterface) MsgClient { 38 | return &msgClient{cc} 39 | } 40 | 41 | func (c *msgClient) IncrementCounter(ctx context.Context, in *MsgIncrementCounter, opts ...grpc.CallOption) (*MsgIncrementCounterResponse, error) { 42 | out := new(MsgIncrementCounterResponse) 43 | err := c.cc.Invoke(ctx, Msg_IncrementCounter_FullMethodName, in, out, opts...) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return out, nil 48 | } 49 | 50 | // MsgServer is the server API for Msg service. 51 | // All implementations must embed UnimplementedMsgServer 52 | // for forward compatibility 53 | type MsgServer interface { 54 | // IncrementCounter increments the counter. 55 | IncrementCounter(context.Context, *MsgIncrementCounter) (*MsgIncrementCounterResponse, error) 56 | mustEmbedUnimplementedMsgServer() 57 | } 58 | 59 | // UnimplementedMsgServer must be embedded to have forward compatible implementations. 60 | type UnimplementedMsgServer struct { 61 | } 62 | 63 | func (UnimplementedMsgServer) IncrementCounter(context.Context, *MsgIncrementCounter) (*MsgIncrementCounterResponse, error) { 64 | return nil, status.Errorf(codes.Unimplemented, "method IncrementCounter not implemented") 65 | } 66 | func (UnimplementedMsgServer) mustEmbedUnimplementedMsgServer() {} 67 | 68 | // UnsafeMsgServer may be embedded to opt out of forward compatibility for this service. 69 | // Use of this interface is not recommended, as added methods to MsgServer will 70 | // result in compilation errors. 71 | type UnsafeMsgServer interface { 72 | mustEmbedUnimplementedMsgServer() 73 | } 74 | 75 | func RegisterMsgServer(s grpc.ServiceRegistrar, srv MsgServer) { 76 | s.RegisterService(&Msg_ServiceDesc, srv) 77 | } 78 | 79 | func _Msg_IncrementCounter_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 80 | in := new(MsgIncrementCounter) 81 | if err := dec(in); err != nil { 82 | return nil, err 83 | } 84 | if interceptor == nil { 85 | return srv.(MsgServer).IncrementCounter(ctx, in) 86 | } 87 | info := &grpc.UnaryServerInfo{ 88 | Server: srv, 89 | FullMethod: Msg_IncrementCounter_FullMethodName, 90 | } 91 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 92 | return srv.(MsgServer).IncrementCounter(ctx, req.(*MsgIncrementCounter)) 93 | } 94 | return interceptor(ctx, in, info, handler) 95 | } 96 | 97 | // Msg_ServiceDesc is the grpc.ServiceDesc for Msg service. 98 | // It's only intended for direct use with grpc.RegisterService, 99 | // and not to be introspected or modified (even as a copy) 100 | var Msg_ServiceDesc = grpc.ServiceDesc{ 101 | ServiceName: "cosmos.oracle.v1.Msg", 102 | HandlerType: (*MsgServer)(nil), 103 | Methods: []grpc.MethodDesc{ 104 | { 105 | MethodName: "IncrementCounter", 106 | Handler: _Msg_IncrementCounter_Handler, 107 | }, 108 | }, 109 | Streams: []grpc.StreamDesc{}, 110 | Metadata: "cosmos/oracle/v1/tx.proto", 111 | } 112 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/codec.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | import ( 4 | types "github.com/cosmos/cosmos-sdk/codec/types" 5 | sdk "github.com/cosmos/cosmos-sdk/types" 6 | "github.com/cosmos/cosmos-sdk/types/msgservice" 7 | ) 8 | 9 | // RegisterInterfaces registers the interfaces types with the interface registry. 10 | func RegisterInterfaces(registry types.InterfaceRegistry) { 11 | registry.RegisterImplementations((*sdk.Msg)(nil), 12 | &MsgIncrementCounter{}, 13 | ) 14 | msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) 15 | } 16 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/errors.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | import "cosmossdk.io/errors" 4 | 5 | // ErrDuplicateAddress error if there is a duplicate address 6 | var ErrDuplicateAddress = errors.Register(ModuleName, 2, "duplicate address") 7 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/genesis.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | // NewGenesisState creates a new genesis state with default values. 4 | func NewGenesisState() *GenesisState { 5 | return &GenesisState{ 6 | } 7 | } 8 | 9 | // Validate performs basic genesis state validation returning an error upon any 10 | func (gs *GenesisState) Validate() error { 11 | uniq := make(map[string]bool) 12 | for _, counter := range gs.Counters { 13 | if _, ok := uniq[counter.Address]; ok { 14 | return ErrDuplicateAddress 15 | } 16 | 17 | uniq[counter.Address] = true 18 | } 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/integration/integration_test.go: -------------------------------------------------------------------------------- 1 | package integration_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | // blank import for app wiring registration 9 | _ "github.com/cosmos/cosmos-sdk/x/auth" 10 | _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" 11 | _ "github.com/cosmos/cosmos-sdk/x/bank" 12 | _ "github.com/cosmos/cosmos-sdk/x/consensus" 13 | _ "github.com/cosmos/cosmos-sdk/x/genutil" 14 | _ "github.com/cosmos/cosmos-sdk/x/mint" 15 | _ "github.com/cosmos/sdk-tutorials/x/staking" 16 | _ "github.com/cosmos/sdk-tutorials/x/oracle" 17 | 18 | appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" 19 | "cosmossdk.io/core/appconfig" 20 | "cosmossdk.io/depinject" 21 | "cosmossdk.io/log" 22 | "github.com/cosmos/cosmos-sdk/testutil/configurator" 23 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 24 | 25 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle" 26 | oraclemodulev1 "github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle/api/module/v1" 27 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle/keeper" 28 | ) 29 | 30 | // ExampleModule is a configurator.ModuleOption that add the oracle module to the app config. 31 | var ExampleModule = func() configurator.ModuleOption { 32 | return func(config *configurator.Config) { 33 | config.ModuleConfigs[oracle.ModuleName] = &appv1alpha1.ModuleConfig{ 34 | Name: oracle.ModuleName, 35 | Config: appconfig.WrapAny(&oraclemodulev1.Module{}), 36 | } 37 | } 38 | } 39 | 40 | func TestIntegration(t *testing.T) { 41 | t.Parallel() 42 | 43 | logger := log.NewTestLogger(t) 44 | appConfig := depinject.Configs( 45 | configurator.NewAppConfig( 46 | configurator.AuthModule(), 47 | configurator.BankModule(), 48 | configurator.StakingModule(), 49 | configurator.TxModule(), 50 | configurator.ConsensusModule(), 51 | configurator.GenutilModule(), 52 | configurator.MintModule(), 53 | ExampleModule(), 54 | configurator.WithCustomInitGenesisOrder( 55 | "auth", 56 | "bank", 57 | "staking", 58 | "mint", 59 | "genutil", 60 | "consensus", 61 | oracle.ModuleName, 62 | ), 63 | ), 64 | depinject.Supply(logger)) 65 | 66 | var keeper keeper.Keeper 67 | app, err := simtestutil.Setup(appConfig, &keeper) 68 | require.NoError(t, err) 69 | require.NotNil(t, app) // use the app or the keeper for running integration tests 70 | } 71 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/genesis.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 7 | ) 8 | 9 | // InitGenesis initializes the module state from a genesis state. 10 | func (k *Keeper) InitGenesis(ctx context.Context, data *oracle.GenesisState) error { 11 | for _, counter := range data.Counters { 12 | if err := k.Counter.Set(ctx, counter.Address, counter.Count); err != nil { 13 | return err 14 | } 15 | } 16 | 17 | return nil 18 | } 19 | 20 | // ExportGenesis exports the module state to a genesis state. 21 | func (k *Keeper) ExportGenesis(ctx context.Context) (*oracle.GenesisState, error) { 22 | var counters []oracle.Counter 23 | if err := k.Counter.Walk(ctx, nil, func(address string, count uint64) (bool, error) { 24 | counters = append(counters, oracle.Counter{ 25 | Address: address, 26 | Count: count, 27 | }) 28 | 29 | return false, nil 30 | }); err != nil { 31 | return nil, err 32 | } 33 | 34 | return &oracle.GenesisState{ 35 | Counters: counters, 36 | }, nil 37 | } 38 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/genesis_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 9 | ) 10 | 11 | func TestInitGenesis(t *testing.T) { 12 | fixture := initFixture(t) 13 | 14 | data := &oracle.GenesisState{ 15 | Counters: []oracle.Counter{ 16 | { 17 | Address: fixture.addrs[0].String(), 18 | Count: 5, 19 | }, 20 | }, 21 | } 22 | err := fixture.k.InitGenesis(fixture.ctx, data) 23 | require.NoError(t, err) 24 | 25 | count, err := fixture.k.Counter.Get(fixture.ctx, fixture.addrs[0].String()) 26 | require.NoError(t, err) 27 | require.Equal(t, uint64(5), count) 28 | } 29 | 30 | func TestExportGenesis(t *testing.T) { 31 | fixture := initFixture(t) 32 | 33 | _, err := fixture.msgServer.IncrementCounter(fixture.ctx, &oracle.MsgIncrementCounter{ 34 | Sender: fixture.addrs[0].String(), 35 | }) 36 | require.NoError(t, err) 37 | 38 | out, err := fixture.k.ExportGenesis(fixture.ctx) 39 | require.NoError(t, err) 40 | 41 | require.Equal(t, uint64(1), out.Counters[0].Count) 42 | } 43 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/keeper.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "cosmossdk.io/collections" 8 | "cosmossdk.io/core/address" 9 | storetypes "cosmossdk.io/core/store" 10 | "cosmossdk.io/math" 11 | "github.com/cosmos/cosmos-sdk/codec" 12 | 13 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 14 | ) 15 | 16 | type Keeper struct { 17 | cdc codec.BinaryCodec 18 | addressCodec address.Codec 19 | 20 | // authority is the address capable of executing a MsgUpdateParams and other authority-gated message. 21 | // typically, this should be the x/gov module account. 22 | authority string 23 | 24 | // state management 25 | Schema collections.Schema 26 | Counter collections.Map[string, uint64] 27 | Prices collections.Map[string, []byte] 28 | } 29 | 30 | // NewKeeper creates a new Keeper instance 31 | func NewKeeper(cdc codec.BinaryCodec, addressCodec address.Codec, storeService storetypes.KVStoreService, authority string) Keeper { 32 | if _, err := addressCodec.StringToBytes(authority); err != nil { 33 | panic(fmt.Errorf("invalid authority address: %w", err)) 34 | } 35 | 36 | sb := collections.NewSchemaBuilder(storeService) 37 | k := Keeper{ 38 | cdc: cdc, 39 | addressCodec: addressCodec, 40 | authority: authority, 41 | Counter: collections.NewMap(sb, oracle.CounterKey, "counter", collections.StringKey, collections.Uint64Value), 42 | Prices: collections.NewMap(sb, oracle.PricesKey, "prices", collections.StringKey, collections.BytesValue), 43 | } 44 | 45 | schema, err := sb.Build() 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | k.Schema = schema 51 | 52 | return k 53 | } 54 | 55 | // GetAuthority returns the module's authority. 56 | func (k Keeper) GetAuthority() string { 57 | return k.authority 58 | } 59 | 60 | func (k Keeper) GetSupportedPairs(_ context.Context) []CurrencyPair { 61 | return []CurrencyPair{ 62 | {Base: "ATOM", Quote: "USD"}, 63 | {Base: "OSMO", Quote: "USD"}, 64 | } 65 | } 66 | 67 | func (k Keeper) SetOraclePrices(ctx context.Context, prices map[string]math.LegacyDec) error { 68 | for b, q := range prices { 69 | bz, err := q.Marshal() 70 | if err != nil { 71 | return err 72 | } 73 | 74 | err = k.Prices.Set(ctx, b, bz) 75 | if err != nil { 76 | return err 77 | } 78 | } 79 | return nil 80 | } 81 | 82 | func (k Keeper) GetOraclePrices(ctx context.Context) (map[string]math.LegacyDec, error) { 83 | prices := make(map[string]math.LegacyDec) 84 | err := k.Prices.Walk(ctx, nil, func(key string, value []byte) (bool, error) { 85 | var q math.LegacyDec 86 | if err := q.Unmarshal(value); err != nil { 87 | return true, err 88 | } 89 | prices[key] = q 90 | return false, nil 91 | }) 92 | if err != nil { 93 | return nil, err 94 | } 95 | return prices, nil 96 | } 97 | 98 | type ( 99 | CurrencyPair struct { 100 | Base string 101 | Quote string 102 | } 103 | 104 | TickerPrice struct { 105 | Price math.LegacyDec // last trade price 106 | Volume math.LegacyDec // 24h volume 107 | } 108 | 109 | // AggregatedProviderPrices defines a type alias for a map of 110 | // provider -> asset -> TickerPrice (e.g. Binance -> ATOM/USD -> 11.98) 111 | AggregatedProviderPrices map[string]map[string]TickerPrice 112 | ) 113 | 114 | func (cp CurrencyPair) String() string { 115 | return cp.Base + cp.Quote 116 | } 117 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/keeper_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | storetypes "cosmossdk.io/store/types" 7 | addresscodec "github.com/cosmos/cosmos-sdk/codec/address" 8 | "github.com/cosmos/cosmos-sdk/runtime" 9 | "github.com/cosmos/cosmos-sdk/testutil" 10 | simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 11 | sdk "github.com/cosmos/cosmos-sdk/types" 12 | moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" 13 | 14 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 15 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle/keeper" 16 | ) 17 | 18 | type testFixture struct { 19 | ctx sdk.Context 20 | k keeper.Keeper 21 | msgServer oracle.MsgServer 22 | queryServer oracle.QueryServer 23 | 24 | addrs []sdk.AccAddress 25 | } 26 | 27 | func initFixture(t *testing.T) *testFixture { 28 | t.Helper() 29 | encCfg := moduletestutil.MakeTestEncodingConfig() 30 | key := storetypes.NewKVStoreKey(oracle.ModuleName) 31 | testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) 32 | storeService := runtime.NewKVStoreService(key) 33 | addrs := simtestutil.CreateIncrementalAccounts(3) 34 | 35 | k := keeper.NewKeeper(encCfg.Codec, addresscodec.NewBech32Codec("cosmos"), storeService, addrs[0].String()) 36 | err := k.InitGenesis(testCtx.Ctx, oracle.NewGenesisState()) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | return &testFixture{ 42 | ctx: testCtx.Ctx, 43 | k: k, 44 | msgServer: keeper.NewMsgServerImpl(k), 45 | queryServer: keeper.NewQueryServerImpl(k), 46 | addrs: addrs, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/migrator.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | // Migrator is a struct for handling in-place state migrations. 4 | type Migrator struct { 5 | keeper Keeper 6 | } 7 | 8 | // NewMigrator returns Migrator instance for the state migration. 9 | func NewMigrator(k Keeper) Migrator { 10 | return Migrator{ 11 | keeper: k, 12 | } 13 | } 14 | 15 | // Migrate1to2 migrates the module state from version 1 to version 2. 16 | // func (m Migrator) Migrate1to2(ctx context.Context) error { 17 | // return v2.Migrate(/* migrations arguments */) 18 | // } 19 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/msg_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "cosmossdk.io/collections" 9 | 10 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 11 | ) 12 | 13 | type msgServer struct { 14 | k Keeper 15 | } 16 | 17 | var _ oracle.MsgServer = msgServer{} 18 | 19 | // NewMsgServerImpl returns an implementation of the module MsgServer interface. 20 | func NewMsgServerImpl(keeper Keeper) oracle.MsgServer { 21 | return &msgServer{k: keeper} 22 | } 23 | 24 | // IncrementCounter defines the handler for the MsgIncrementCounter message. 25 | func (ms msgServer) IncrementCounter(ctx context.Context, msg *oracle.MsgIncrementCounter) (*oracle.MsgIncrementCounterResponse, error) { 26 | if _, err := ms.k.addressCodec.StringToBytes(msg.Sender); err != nil { 27 | return nil, fmt.Errorf("invalid sender address: %w", err) 28 | } 29 | 30 | counter, err := ms.k.Counter.Get(ctx, msg.Sender) 31 | if err != nil && !errors.Is(err, collections.ErrNotFound) { 32 | return nil, err 33 | } 34 | 35 | counter++ 36 | 37 | if err := ms.k.Counter.Set(ctx, msg.Sender, counter); err != nil { 38 | return nil, err 39 | } 40 | 41 | return &oracle.MsgIncrementCounterResponse{}, nil 42 | } 43 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/msg_server_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 9 | ) 10 | 11 | func TestIncrementCounter(t *testing.T) { 12 | f := initFixture(t) 13 | require := require.New(t) 14 | 15 | testCases := []struct { 16 | name string 17 | request *oracle.MsgIncrementCounter 18 | expectErrMsg string 19 | expectedCounter uint64 20 | }{ 21 | { 22 | name: "set invalid sender (not an address)", 23 | request: &oracle.MsgIncrementCounter{ 24 | Sender: "foo", 25 | }, 26 | expectErrMsg: "invalid sender address", 27 | }, 28 | { 29 | name: "set valid sender", 30 | request: &oracle.MsgIncrementCounter{ 31 | Sender: "cosmos139f7kncmglres2nf3h4hc4tade85ekfr8sulz5", 32 | }, 33 | expectErrMsg: "", 34 | expectedCounter: 1, 35 | }, 36 | } 37 | 38 | for _, tc := range testCases { 39 | tc := tc 40 | t.Run(tc.name, func(t *testing.T) { 41 | _, err := f.msgServer.IncrementCounter(f.ctx, tc.request) 42 | if tc.expectErrMsg != "" { 43 | require.Error(err) 44 | require.ErrorContains(err, tc.expectErrMsg) 45 | } else { 46 | require.NoError(err) 47 | 48 | counter, err := f.k.Counter.Get(f.ctx, tc.request.Sender) 49 | require.NoError(err) 50 | require.Equal(tc.expectedCounter, counter) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/query_server.go: -------------------------------------------------------------------------------- 1 | package keeper 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "cosmossdk.io/collections" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | 12 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 13 | ) 14 | 15 | var _ oracle.QueryServer = queryServer{} 16 | 17 | // NewQueryServerImpl returns an implementation of the module QueryServer. 18 | func NewQueryServerImpl(k Keeper) oracle.QueryServer { 19 | return queryServer{k} 20 | } 21 | 22 | type queryServer struct { 23 | k Keeper 24 | } 25 | 26 | // Counter defines the handler for the Query/Counter RPC method. 27 | func (qs queryServer) Counter(ctx context.Context, req *oracle.QueryCounterRequest) (*oracle.QueryCounterResponse, error) { 28 | if _, err := qs.k.addressCodec.StringToBytes(req.Address); err != nil { 29 | return nil, fmt.Errorf("invalid sender address: %w", err) 30 | } 31 | 32 | counter, err := qs.k.Counter.Get(ctx, req.Address) 33 | if err != nil { 34 | if errors.Is(err, collections.ErrNotFound) { 35 | return &oracle.QueryCounterResponse{Counter: 0}, nil 36 | } 37 | 38 | return nil, status.Error(codes.Internal, err.Error()) 39 | } 40 | 41 | return &oracle.QueryCounterResponse{Counter: counter}, nil 42 | } 43 | 44 | func (qs queryServer) Prices(ctx context.Context, req *oracle.QueryPricesRequest) (*oracle.QueryPricesResponse, error) { 45 | var prices []*oracle.Price 46 | 47 | p, err := qs.k.GetOraclePrices(ctx) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | for symbol, price := range p { 53 | prices = append(prices, &oracle.Price{ 54 | Symbol: symbol, 55 | Price: price.String(), 56 | }) 57 | } 58 | 59 | return &oracle.QueryPricesResponse{Prices: prices}, nil 60 | } 61 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keeper/query_server_test.go: -------------------------------------------------------------------------------- 1 | package keeper_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 9 | ) 10 | 11 | func TestQueryCounter(t *testing.T) { 12 | f := initFixture(t) 13 | require := require.New(t) 14 | 15 | resp, err := f.queryServer.Counter(f.ctx, &oracle.QueryCounterRequest{Address: f.addrs[0].String()}) 16 | require.NoError(err) 17 | require.Equal(uint64(0), resp.Counter) 18 | 19 | _, err = f.msgServer.IncrementCounter(f.ctx, &oracle.MsgIncrementCounter{Sender: f.addrs[0].String()}) 20 | require.NoError(err) 21 | 22 | resp, err = f.queryServer.Counter(f.ctx, &oracle.QueryCounterRequest{Address: f.addrs[0].String()}) 23 | require.NoError(err) 24 | require.Equal(uint64(1), resp.Counter) 25 | } 26 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/keys.go: -------------------------------------------------------------------------------- 1 | package oracle 2 | 3 | import "cosmossdk.io/collections" 4 | 5 | const ModuleName = "oracle" 6 | 7 | var ( 8 | ParamsKey = collections.NewPrefix(0) 9 | CounterKey = collections.NewPrefix(1) 10 | PricesKey = collections.NewPrefix(2) 11 | ) 12 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/mockprovider/provider.go: -------------------------------------------------------------------------------- 1 | package mockprovider 2 | 3 | import ( 4 | "cosmossdk.io/math" 5 | 6 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle/keeper" 7 | ) 8 | 9 | type Provider interface { 10 | GetTickerPrices(...keeper.CurrencyPair) (map[string]keeper.TickerPrice, error) 11 | } 12 | 13 | var _ Provider = MockProvider{} 14 | 15 | type MockProvider struct{} 16 | 17 | func NewMockProvider() MockProvider { 18 | return MockProvider{} 19 | } 20 | 21 | func (p MockProvider) GetTickerPrices(pairs ...keeper.CurrencyPair) (map[string]keeper.TickerPrice, error) { 22 | tickers := map[string]keeper.TickerPrice{} 23 | for _, p := range pairs { 24 | 25 | pair := p.String() 26 | price := math.LegacyNewDec(0) 27 | switch pair { 28 | case "ATOMUSD": 29 | price = math.LegacyNewDec(10) 30 | case "OSMOUSD": 31 | price = math.LegacyNewDec(2) 32 | } 33 | 34 | tickers[pair] = keeper.TickerPrice{ 35 | Price: price, 36 | Volume: math.LegacyNewDec(10000000), 37 | } 38 | } 39 | 40 | return tickers, nil 41 | } 42 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/module/autocli.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" 5 | 6 | oraclev1 "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle/api/v1" 7 | ) 8 | 9 | // AutoCLIOptions implements the autocli.HasAutoCLIConfig interface. 10 | func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { 11 | return &autocliv1.ModuleOptions{ 12 | Query: &autocliv1.ServiceCommandDescriptor{ 13 | Service: oraclev1.Query_ServiceDesc.ServiceName, 14 | RpcCommandOptions: []*autocliv1.RpcCommandOptions{ 15 | { 16 | RpcMethod: "Counter", 17 | Use: "counter [address]", 18 | Short: "Get the current value of the counter for an address", 19 | PositionalArgs: []*autocliv1.PositionalArgDescriptor{ 20 | {ProtoField: "address"}, 21 | }, 22 | }, 23 | { 24 | RpcMethod: "Prices", 25 | Use: "prices", 26 | Short: "Get the current prices", 27 | }, 28 | }, 29 | }, 30 | Tx: &autocliv1.ServiceCommandDescriptor{ 31 | Service: oraclev1.Msg_ServiceDesc.ServiceName, 32 | RpcCommandOptions: []*autocliv1.RpcCommandOptions{ 33 | { 34 | RpcMethod: "IncrementCounter", 35 | Use: "counter [sender]", 36 | Short: "Increments the counter by 1 for the sender", 37 | PositionalArgs: []*autocliv1.PositionalArgDescriptor{ 38 | {ProtoField: "sender"}, 39 | }, 40 | }, 41 | // The UpdateParams tx is purposely left empty, the MsgUpdateParams is gov gated. 42 | }, 43 | }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/module/depinject.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "cosmossdk.io/core/address" 5 | "cosmossdk.io/core/appmodule" 6 | "cosmossdk.io/core/store" 7 | "cosmossdk.io/depinject" 8 | "github.com/cosmos/cosmos-sdk/codec" 9 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 10 | 11 | modulev1 "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle/api/module/v1" 12 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle/keeper" 13 | ) 14 | 15 | var _ appmodule.AppModule = AppModule{} 16 | 17 | // IsOnePerModuleType implements the depinject.OnePerModuleType interface. 18 | func (am AppModule) IsOnePerModuleType() {} 19 | 20 | // IsAppModule implements the appmodule.AppModule interface. 21 | func (am AppModule) IsAppModule() {} 22 | 23 | func init() { 24 | appmodule.Register( 25 | &modulev1.Module{}, 26 | appmodule.Provide(ProvideModule), 27 | ) 28 | } 29 | 30 | type ModuleInputs struct { 31 | depinject.In 32 | 33 | Cdc codec.Codec 34 | StoreService store.KVStoreService 35 | AddressCodec address.Codec 36 | 37 | Config *modulev1.Module 38 | } 39 | 40 | type ModuleOutputs struct { 41 | depinject.Out 42 | 43 | Module appmodule.AppModule 44 | Keeper keeper.Keeper 45 | } 46 | 47 | func ProvideModule(in ModuleInputs) ModuleOutputs { 48 | // default to governance as authority if not provided 49 | authority := authtypes.NewModuleAddress("gov") 50 | if in.Config.Authority != "" { 51 | authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority) 52 | } 53 | 54 | k := keeper.NewKeeper(in.Cdc, in.AddressCodec, in.StoreService, authority.String()) 55 | m := NewAppModule(in.Cdc, k) 56 | 57 | return ModuleOutputs{Module: m, Keeper: k} 58 | } 59 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "cosmossdk.io/core/appmodule" 9 | "github.com/cosmos/cosmos-sdk/client" 10 | "github.com/cosmos/cosmos-sdk/codec" 11 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 12 | sdk "github.com/cosmos/cosmos-sdk/types" 13 | "github.com/cosmos/cosmos-sdk/types/module" 14 | gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" 15 | 16 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle" 17 | "github.com/cosmos/sdk-tutorials/tutorials/oracle/base/x/oracle/keeper" 18 | ) 19 | 20 | var ( 21 | _ module.AppModuleBasic = AppModule{} 22 | _ module.HasGenesis = AppModule{} 23 | _ appmodule.AppModule = AppModule{} 24 | ) 25 | 26 | // ConsensusVersion defines the current module consensus version. 27 | const ConsensusVersion = 1 28 | 29 | type AppModule struct { 30 | cdc codec.Codec 31 | keeper keeper.Keeper 32 | } 33 | 34 | // NewAppModule creates a new AppModule object 35 | func NewAppModule(cdc codec.Codec, keeper keeper.Keeper) AppModule { 36 | return AppModule{ 37 | cdc: cdc, 38 | keeper: keeper, 39 | } 40 | } 41 | 42 | func NewAppModuleBasic(m AppModule) module.AppModuleBasic { 43 | return module.CoreAppModuleBasicAdaptor(m.Name(), m) 44 | } 45 | 46 | // Name returns the oracle module's name. 47 | func (AppModule) Name() string { return oracle.ModuleName } 48 | 49 | // RegisterLegacyAminoCodec registers the oracle module's types on the LegacyAmino codec. 50 | // New modules do not need to support Amino. 51 | func (AppModule) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {} 52 | 53 | // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the oracle module. 54 | func (AppModule) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *gwruntime.ServeMux) { 55 | if err := oracle.RegisterQueryHandlerClient(context.Background(), mux, oracle.NewQueryClient(clientCtx)); err != nil { 56 | panic(err) 57 | } 58 | } 59 | 60 | // RegisterInterfaces registers interfaces and implementations of the oracle module. 61 | func (AppModule) RegisterInterfaces(registry codectypes.InterfaceRegistry) { 62 | oracle.RegisterInterfaces(registry) 63 | } 64 | 65 | // ConsensusVersion implements AppModule/ConsensusVersion. 66 | func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } 67 | 68 | // RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries. 69 | func (am AppModule) RegisterServices(cfg module.Configurator) { 70 | oracle.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) 71 | oracle.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper)) 72 | 73 | // Register in place module state migration migrations 74 | // m := keeper.NewMigrator(am.keeper) 75 | // if err := cfg.RegisterMigration(oracle.ModuleName, 1, m.Migrate1to2); err != nil { 76 | // panic(fmt.Sprintf("failed to migrate x/%s from version 1 to 2: %v", oracle.ModuleName, err)) 77 | // } 78 | } 79 | 80 | // DefaultGenesis returns default genesis state as raw bytes for the module. 81 | func (AppModule) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { 82 | return cdc.MustMarshalJSON(oracle.NewGenesisState()) 83 | } 84 | 85 | // ValidateGenesis performs genesis state validation for the circuit module. 86 | func (AppModule) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { 87 | var data oracle.GenesisState 88 | if err := cdc.UnmarshalJSON(bz, &data); err != nil { 89 | return fmt.Errorf("failed to unmarshal %s genesis state: %w", oracle.ModuleName, err) 90 | } 91 | 92 | return data.Validate() 93 | } 94 | 95 | // InitGenesis performs genesis initialization for the oracle module. 96 | // It returns no validator updates. 97 | func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) { 98 | var genesisState oracle.GenesisState 99 | cdc.MustUnmarshalJSON(data, &genesisState) 100 | 101 | if err := am.keeper.InitGenesis(ctx, &genesisState); err != nil { 102 | panic(fmt.Sprintf("failed to initialize %s genesis state: %v", oracle.ModuleName, err)) 103 | } 104 | } 105 | 106 | // ExportGenesis returns the exported genesis state as raw bytes for the circuit 107 | // module. 108 | func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { 109 | gs, err := am.keeper.ExportGenesis(ctx) 110 | if err != nil { 111 | panic(fmt.Sprintf("failed to export %s genesis state: %v", oracle.ModuleName, err)) 112 | } 113 | 114 | return cdc.MustMarshalJSON(gs) 115 | } 116 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/buf.gen.gogo.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: gocosmos 4 | out: .. 5 | opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types,Mcosmos/orm/v1/orm.proto=cosmossdk.io/orm 6 | - name: grpc-gateway 7 | out: .. 8 | opt: logtostderr=true,allow_colon_final_segments=true 9 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/buf.gen.pulsar.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | managed: 3 | enabled: true 4 | go_package_prefix: 5 | default: github.com/cosmos/sdk-tutorial/x/oracle/api 6 | except: 7 | - buf.build/googleapis/googleapis 8 | - buf.build/cosmos/gogo-proto 9 | - buf.build/cosmos/cosmos-proto 10 | - buf.build/cosmos/cosmos-sdk 11 | plugins: 12 | - name: go-pulsar 13 | out: .. 14 | opt: paths=source_relative,Mcosmos/app/v1alpha1/module.proto=cosmossdk.io/api/cosmos/app/v1alpha1,Mcosmos/base/v1beta1/coin.proto=cosmossdk.io/api/cosmos/base/v1beta1 15 | - name: go-grpc 16 | out: .. 17 | opt: paths=source_relative,Mcosmos/app/v1alpha1/module.proto=cosmossdk.io/api/cosmos/app/v1alpha1 18 | - name: go-cosmos-orm 19 | out: .. 20 | opt: paths=source_relative,Mcosmos/app/v1alpha1/module.proto=cosmossdk.io/api/cosmos/app/v1alpha1 21 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: cosmos 6 | repository: cosmos-proto 7 | commit: 1935555c206d4afb9e94615dfd0fad31 8 | digest: shake256:c74d91a3ac7ae07d579e90eee33abf9b29664047ac8816500cf22c081fec0d72d62c89ce0bebafc1f6fec7aa5315be72606717740ca95007248425102c365377 9 | - remote: buf.build 10 | owner: cosmos 11 | repository: cosmos-sdk 12 | commit: cf13c7d232dd405180c2af616fa8a075 13 | digest: shake256:769a38e306a98339b549bc96991c97fae8bd3ceb1a7646c7bfe9a74e406ab068372970fbc5abda1891e2f3c36527cf2d3a25f631739d36900787226e564bb612 14 | - remote: buf.build 15 | owner: cosmos 16 | repository: gogo-proto 17 | commit: 5e5b9fdd01804356895f8f79a6f1ddc1 18 | digest: shake256:0b85da49e2e5f9ebc4806eae058e2f56096ff3b1c59d1fb7c190413dd15f45dd456f0b69ced9059341c80795d2b6c943de15b120a9e0308b499e43e4b5fc2952 19 | - remote: buf.build 20 | owner: googleapis 21 | repository: googleapis 22 | commit: 28151c0d0a1641bf938a7672c500e01d 23 | digest: shake256:49215edf8ef57f7863004539deff8834cfb2195113f0b890dd1f67815d9353e28e668019165b9d872395871eeafcbab3ccfdb2b5f11734d3cca95be9e8d139de 24 | - remote: buf.build 25 | owner: protocolbuffers 26 | repository: wellknowntypes 27 | commit: 657250e6a39648cbb169d079a60bd9ba 28 | digest: shake256:00de25001b8dd2e29d85fc4bcc3ede7aed886d76d67f5e0f7a9b320b90f871d3eb73507d50818d823a0512f3f8db77a11c043685528403e31ff3fef18323a9fb 29 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | deps: 3 | - buf.build/cosmos/cosmos-sdk # pin the Cosmos SDK version 4 | - buf.build/cosmos/cosmos-proto 5 | - buf.build/cosmos/gogo-proto 6 | - buf.build/googleapis/googleapis 7 | lint: 8 | use: 9 | - DEFAULT 10 | - COMMENTS 11 | - FILE_LOWER_SNAKE_CASE 12 | except: 13 | - UNARY_RPC 14 | - COMMENT_FIELD 15 | - SERVICE_SUFFIX 16 | - PACKAGE_VERSION_SUFFIX 17 | - RPC_REQUEST_STANDARD_NAME 18 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/cosmos/oracle/module/v1/module.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cosmos.oracle.module.v1; 4 | 5 | import "cosmos/app/v1alpha1/module.proto"; 6 | 7 | // Module is the app config object of the module. 8 | // Learn more: https://docs.cosmos.network/main/building-modules/depinject 9 | message Module { 10 | option (cosmos.app.v1alpha1.module) = { 11 | go_import : "github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle" 12 | }; 13 | 14 | // authority defines the custom module authority. 15 | // if not set, defaults to the governance module. 16 | string authority = 1; 17 | } 18 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/cosmos/oracle/v1/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.oracle.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle"; 5 | 6 | import "cosmos/oracle/v1/types.proto"; 7 | import "google/api/annotations.proto"; 8 | import "cosmos/query/v1/query.proto"; 9 | import "amino/amino.proto"; 10 | import "gogoproto/gogo.proto"; 11 | 12 | // Msg defines the module Msg service. 13 | service Query { 14 | // Counter returns the current counter value. 15 | rpc Counter(QueryCounterRequest) returns (QueryCounterResponse) { 16 | option (cosmos.query.v1.module_query_safe) = true; 17 | option (google.api.http).get = 18 | "/cosmos/sdk-tutorials/x/oracle/v1/counter/{address}"; 19 | } 20 | 21 | // Prices returns the current prices. 22 | rpc Prices(QueryPricesRequest) returns (QueryPricesResponse) { 23 | option (google.api.http).get = "/cosmos/oracle/v1/prices"; 24 | } 25 | } 26 | 27 | // QueryPricesRequest is the request type for the Query/Prices RPC method. 28 | message QueryPricesRequest {} 29 | 30 | // QueryPricesResponse is the response type for the Query/Prices RPC method. 31 | message QueryPricesResponse { 32 | repeated Price prices = 1; 33 | } 34 | 35 | // Price represents the price of a specific symbol. 36 | message Price { 37 | string symbol = 1; 38 | string price = 2; 39 | } 40 | 41 | // QueryCounterRequest is the request type for the Query/Counter RPC 42 | // method. 43 | message QueryCounterRequest { 44 | // address defines the address to query for the counter. 45 | string address = 1; 46 | } 47 | 48 | // QueryCounterResponse is the response type for the Query/Counter RPC 49 | // method. 50 | message QueryCounterResponse { 51 | // counter defines the current counter for the sender. 52 | uint64 counter = 1; 53 | } 54 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/cosmos/oracle/v1/tx.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.oracle.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle"; 5 | 6 | import "cosmos/msg/v1/msg.proto"; 7 | import "gogoproto/gogo.proto"; 8 | import "amino/amino.proto"; 9 | import "cosmos/oracle/v1/types.proto"; 10 | import "cosmos_proto/cosmos.proto"; 11 | 12 | // Msg defines the module Msg service. 13 | service Msg { 14 | option (cosmos.msg.v1.service) = true; 15 | 16 | // IncrementCounter increments the counter. 17 | rpc IncrementCounter(MsgIncrementCounter) 18 | returns (MsgIncrementCounterResponse); 19 | } 20 | 21 | // MsgIncrementCounter defines the Msg/IncrementCounter request type. 22 | message MsgIncrementCounter { 23 | option (cosmos.msg.v1.signer) = "sender"; 24 | option (amino.name) = "cosmos/sdk-tutorials/x/oracle/MsgIncrementCounter"; 25 | 26 | // sender is the message sender. 27 | string sender = 1; 28 | } 29 | 30 | // MsgIncrementCounterResponse defines the Msg/IncrementCounter response type. 31 | message MsgIncrementCounterResponse {} -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/proto/cosmos/oracle/v1/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cosmos.oracle.v1; 3 | 4 | option go_package = "github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle"; 5 | 6 | import "cosmos_proto/cosmos.proto"; 7 | import "gogoproto/gogo.proto"; 8 | import "amino/amino.proto"; 9 | 10 | 11 | // Counter defines a counter object. 12 | // It is used only for genesis purposes. Collections does not need to use it. 13 | message Counter { 14 | option (amino.name) = "cosmos/oracle/Counter"; 15 | 16 | // count defines the count of the counter. 17 | uint64 count = 1; 18 | 19 | // address defines the address that is associated with the count. 20 | string address = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; 21 | ; 22 | } 23 | 24 | // GenesisState is the state that must be provided at genesis. 25 | message GenesisState { 26 | // counter defines the counter object. 27 | repeated Counter counters = 1 28 | [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; 29 | } 30 | -------------------------------------------------------------------------------- /tutorials/oracle/base/x/oracle/scripts/protocgen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo "Generating gogo proto code" 6 | cd proto 7 | proto_dirs=$(find . -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) 8 | for dir in $proto_dirs; do 9 | for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do 10 | # this regex checks if a proto file has its go_package set to github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle/api/... 11 | # gogo proto files SHOULD ONLY be generated if this is false 12 | # we don't want gogo proto to run for proto files which are natively built for google.golang.org/protobuf 13 | if grep -q "option go_package" "$file" && grep -H -o -c 'option go_package.*github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle/api' "$file" | grep -q ':0$'; then 14 | buf generate --template buf.gen.gogo.yaml $file 15 | fi 16 | done 17 | done 18 | 19 | echo "Generating pulsar proto code" 20 | buf generate --template buf.gen.pulsar.yaml 21 | 22 | cd .. 23 | 24 | cp -r github.com/cosmos/sdk-tutorials/tutorials/oracle/x/oracle/* ./ 25 | rm -rf api && mkdir api 26 | mv cosmos/oracle/* ./api 27 | rm -rf github.com cosmos -------------------------------------------------------------------------------- /tutorials/oracle/docs/00-getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Table of Contents 4 | 5 | * [What is an Oracle?](./01-what-is-an-oracle.md) 6 | * [Implementing Vote Extensions](./02-implementing-vote-extensions.md) 7 | * [Testing the Oracle Module](./03-testing-oracle.md) 8 | 9 | ## Prerequisites 10 | 11 | Before you start with this tutorial, make sure you have: 12 | 13 | * A working chain project. This tutorial won't cover the steps of creating a new chain/module. 14 | * Familiarity with the Cosmos SDK. If you're not, we suggest you start with [Cosmos SDK Tutorials](https://tutorials.cosmos.network), as ABCI++ is considered an advanced topic. 15 | * Read and understood [What is an Oracle?](01-what-is-an-oracle.md). This provides necessary background information for understanding the Oracle module. 16 | * Basic understanding of Go programming language. 17 | 18 | ## What are Vote extensions? 19 | 20 | Vote extensions is arbitrary information which can be inserted into a block. This feature is part of ABCI 2.0, which is available for use in the SDK 0.50 release and part of the 0.38 CometBFT release. 21 | 22 | More information about vote extensions can be seen [here](https://docs.cosmos.network/main/build/abci/vote-extensions). 23 | 24 | ## Overview of the project 25 | 26 | We’ll go through the creation of a simple price oracle module focusing on the vote extensions implementation, ignoring the details inside the price oracle itself. 27 | 28 | We’ll go through the implementation of: 29 | 30 | * `ExtendVote` to get information from external price APIs. 31 | * `VerifyVoteExtension` to check that the format of the provided votes is correct. 32 | * `PrepareProposal` to process the vote extensions from the previous block and include them into the proposal as a transaction. 33 | * `ProcessProposal` to check that the first transaction in the proposal is actually a “special tx” that contains the price information. 34 | * `PreBlocker` to make price information available during FinalizeBlock. 35 | 36 | If you would like to see the complete working oracle module please see [here](https://github.com/cosmos/sdk-tutorials/blob/master/tutorials/oracle/base/x/oracle) 37 | -------------------------------------------------------------------------------- /tutorials/oracle/docs/01-what-is-an-oracle.md: -------------------------------------------------------------------------------- 1 | # What is an Oracle? 2 | 3 | An oracle in blockchain technology is a system that provides external data to a blockchain network. It acts as a source of information that is not natively accessible within the blockchain's closed environment. This can range from financial market prices to real-world event, making it crucial for decentralised applications. 4 | 5 | ## Oracle in the Cosmos SDK 6 | 7 | In the Cosmos SDK, an oracle module can be implemented to provide external data to the blockchain. This module can use features like vote extensions to submit additional data during the consensus process, which can then be used by the blockchain to update its state with information from the outside world. 8 | 9 | For instance, a price oracle module in the Cosmos SDK could supply timely and accurate asset price information, which is vital for various financial operations within the blockchain ecosystem. 10 | 11 | ## Conclusion 12 | 13 | Oracles are essential for blockchains to interact with external data, enabling them to respond to real-world information and events. Their implementation is key to the reliability and robustness of blockchain networks. 14 | -------------------------------------------------------------------------------- /tutorials/oracle/docs/03-testing-oracle.md: -------------------------------------------------------------------------------- 1 | # Testing the Oracle Module 2 | 3 | We will guide you through the process of testing the Oracle module in your application. The Oracle module uses vote extensions to provide current price data. If you would like to see the complete working oracle module please see [here](https://github.com/cosmos/sdk-tutorials/blob/master/tutorials/oracle/base/x/oracle). 4 | 5 | ## Step 1: Compile and Install the Application 6 | 7 | First, we need to compile and install the application. Please ensure you are in the `tutorials/oracle/base` directory. Run the following command in your terminal: 8 | 9 | ```shell 10 | make install 11 | ``` 12 | 13 | This command compiles the application and moves the resulting binary to a location in your system's PATH. 14 | 15 | ## Step 2: Initialise the Application 16 | 17 | Next, we need to initialise the application. Run the following command in your terminal: 18 | 19 | ```shell 20 | make init 21 | ``` 22 | 23 | This command runs the script `tutorials/oracle/base/scripts/init.sh`, which sets up the necessary configuration for your application to run. This includes creating the `app.toml` configuration file and initialising the blockchain with a genesis block. 24 | 25 | ## Step 3: Start the Application 26 | 27 | Now, we can start the application. Run the following command in your terminal: 28 | 29 | ```shell 30 | exampled start 31 | ``` 32 | 33 | This command starts your application, begins the blockchain node, and starts processing transactions. 34 | 35 | ## Step 4: Query the Oracle Prices 36 | 37 | Finally, we can query the current prices from the Oracle module. Run the following command in your terminal: 38 | 39 | ```shell 40 | exampled q oracle prices 41 | ``` 42 | 43 | This command queries the current prices from the Oracle module. The expected output shows that the vote extensions were successfully included in the block and the Oracle module was able to retrieve the price data. 44 | 45 | ## Understanding Vote Extensions in Oracle 46 | 47 | In the Oracle module, the `ExtendVoteHandler` function is responsible for creating the vote extensions. This function fetches the current prices from the provider, creates a `OracleVoteExtension` struct with these prices, and then marshals this struct into bytes. These bytes are then set as the vote extension. 48 | 49 | In the context of testing, the Oracle module uses a mock provider to simulate the behavior of a real price provider. This mock provider is defined in the mockprovider package and is used to return predefined prices for specific currency pairs. 50 | 51 | ## Conclusion 52 | 53 | In this tutorial, we've delved into the concept of Oracle's in blockchain technology, focusing on their role in providing external data to a blockchain network. We've explored vote extensions, a powerful feature of ABCI++, and integrated them into a Cosmos SDK application to create a price oracle module. 54 | 55 | Through hands-on exercises, you've implemented vote extensions, and tested their effectiveness in providing timely and accurate asset price information. You've gained practical insights by setting up a mock provider for testing and analysing the process of extending votes, verifying vote extensions, and preparing and processing proposals. 56 | 57 | Keep experimenting with these concepts, engage with the community, and stay updated on new advancements. The knowledge you've acquired here is crucial for developing robust and reliable blockchain applications that can interact with real-world data. 58 | --------------------------------------------------------------------------------