# Cosmos Developer Documentation Source: https://docs.cosmos.network/index
Cosmos Cosmos
Cosmos Cosmos
Cosmos SDK Cosmos EVM IBC CometBFT
Talk to an expert
# Cosmos Developer Documentation

The most widely adopted, battle-tested Layer 1 blockchain stack, trusted by 200+ chains live in production.

How it works

The stack is modular. Leverage pre-built components or integrate custom features for your specific use case, from consensus mechanisms to governance and compliance.

Fast and Reliable

Performant, customizable, and EVM-compatible, the Cosmos stack offers engineers full control of their blockchain infrastructure and implementation. Its stable and secure codebase enables blockchains to achieve up to 10,000 transactions per second.

Maintainers and Contributors

Cosmos Labs maintains the core components of the stack: Cosmos SDK, CometBFT, IBC, Cosmos EVM, and various developer tools and frameworks. In addition to developing and maintaining the Cosmos Stack, Cosmos Labs provides advisory and engineering services for blockchain solutions.

 

Cosmos Labs is a wholly-owned subsidiary of the Interchain Foundation, the Swiss nonprofit responsible for treasury management, funding public goods, and supporting governance for Cosmos.

 

The Cosmos Stack is supported by a global community of open-source contributors.

Get in Touch with Cosmos Labs
# Changelog Source: https://docs.cosmos.network/sdk/latest/changelog/release-notes Release history and changelog for Cosmos SDK This page tracks releases and changes for v0.55.0. For the full release history, see the [CHANGELOG](https://github.com/cosmos/cosmos-sdk/blob/main/CHANGELOG.md) on GitHub. ## Breaking Changes * (mempool) [#25338](https://github.com/cosmos/cosmos-sdk/pull/25338) Respect gas wanted returned by the ante handler for block selection. Adds `InsertWithOption` to the `Mempool` interface (carries the ante-reported `GasWanted`) and changes the `SelectBy` callback to receive a `mempool.Tx` wrapper that exposes the stored value. * (tx) [#26456](https://github.com/cosmos/cosmos-sdk/pull/26456) Remove `SIGN_MODE_TEXTUAL` and all associated implementation (`x/tx/signing/textual`, `x/auth/tx/textual.go`, `TextualCoinMetadataQueryFn`). The proto enum value is reserved to prevent future reuse. ADR-050 is marked archived. * (modules) [#26421](https://github.com/cosmos/cosmos-sdk/pull/26421) Remove the `x/protocolpool` module and its API/proto surface from the SDK. Applications upgrading from v0.54 should include `protocolpool` in deleted store upgrades. * (genutils) [#26468](https://github.com/cosmos/cosmos-sdk/pull/26468) Consolidate ExportGenesisFileWithTime arguments to preserve consensus params. ## Features * (abci) [#25620](https://github.com/cosmos/cosmos-sdk/pull/25620) Add support for new application side mempool ABCI methods. * (abci) [#25969](https://github.com/cosmos/cosmos-sdk/pull/25969) Add support for new ABCI methods, `InsertTx` and `ReapTxs`. * (blockstm) [#26208](https://github.com/cosmos/cosmos-sdk/pull/26208) Add Block-STM configuration support: `block-executor`, `block-stm-workers` and `block-stm-pre-estimate`. * (blockstm) [#25909](https://github.com/cosmos/cosmos-sdk/pull/25909) Cache pre-state to optimize value-based validation. * (deps) [#26388](https://github.com/cosmos/cosmos-sdk/pull/26388) Bump CometBFT version to v0.39.3. * (staking) [#26440](https://github.com/cosmos/cosmos-sdk/pull/26440) Add basic key rotation for validator consensus keys. * (crypto) [#26436](https://github.com/cosmos/cosmos-sdk/pull/26436) Add ML-DSA-65 (FIPS 204) post-quantum validator consensus key type, with SDK key wrappers, Amino + interface-registry registration, multisig support, and a `hd.MlDsa65Type` constant. * (blockstm) [#26467](https://github.com/cosmos/cosmos-sdk/pull/26467) Track existence for `Has()` reads to reduce false conflicts. * (staking) [#26485](https://github.com/cosmos/cosmos-sdk/pull/26485) Add `key_rotation_fee` to `x/staking` params and register associated 5->6 migration. * (staking) [#26461](https://github.com/cosmos/cosmos-sdk/pull/26461) Wire `MsgRotateConsPubKey` into cli and add a happy path system test. * (staking) [#26471](https://github.com/cosmos/cosmos-sdk/pull/26471) Add genesis import/export support for validator consensus key rotation. * (crypto) [#26472](https://github.com/cosmos/cosmos-sdk/pull/26472) Add ML-DSA-65 (FIPS 204) support for user account keys: mnemonic-based keyring creation/recovery (`--algo ml_dsa_65`), transaction signing/verification, and an ante-handler signature-verification gas cost (`Params.SigVerifyCostMlDsa65`). * (enterprise/poa) [#26590](https://github.com/cosmos/cosmos-sdk/pull/26590) Add `MsgRotateConsPubKey` for POA validator consensus key rotation (operator self-service plus admin override). * (enterprise/poa) [#26614](https://github.com/cosmos/cosmos-sdk/pull/26614) Add ML-DSA-65 (mldsa65) validator key support to the PoA module via a `WithMlDsa65Support()` module option, raising `MaxPubKeyLength` to accommodate the larger keys. * (crypto) [#26615](https://github.com/cosmos/cosmos-sdk/pull/26615) Add `secp256k1eth` validator consensus key type. ## Improvements * (server/config) [#26572](https://github.com/cosmos/cosmos-sdk/pull/26572) Warn that `query-gas-limit = 0` (the default) is unbounded and exposes public RPC nodes to DoS via expensive queries. * (docs) [#25918](https://github.com/cosmos/cosmos-sdk/issues/25918) Regenerate Swagger API spec to reflect current proto state, including `authority` field on consensus params and removal of stale module-config definitions. * (baseapp) [#22368](https://github.com/cosmos/cosmos-sdk/issues/22368) Add `-race`-mode regression test (`TestABCI_Race_GRPC_Query_During_Commit`) covering concurrent `BaseApp.Query` and `FinalizeBlock`/`Commit`. Pins down the state-management mutex work added in #24655 and follow-ups so the data race reported against v0.50.x cannot regress silently. * (x/staking, x/slashing) [#26481](https://github.com/cosmos/cosmos-sdk/pull/26481) Resolve evidence against recently rotated consensus keys and migrate slashing signing state to the active consensus key. * (x/auth/tx) [#25221](https://github.com/cosmos/cosmos-sdk/issues/25221) Add `ConfigOptions.AminoJSONEncoder` so applications can configure a custom `aminojson.Encoder` (e.g. custom field encodings) for the `SIGN_MODE_LEGACY_AMINO_JSON` handler without replicating the SDK's `HandlerMap` construction. * chore(x/auth) [#26567](https://github.com/cosmos/cosmos-sdk/pull/26567): add a human-readable error * (blockstm) [#26592](https://github.com/cosmos/cosmos-sdk/pull/26592) Validate `ExecuteBlock` inputs (block size, store index mapping, and estimates) at the exported entry point so invalid input returns a descriptive error instead of an opaque "index out of range" panic. * (cli) [#26604](https://github.com/cosmos/cosmos-sdk/pull/26604) Add consensus key algo to init and testnet CLIs. * (crypto) [#26626](https://github.com/cosmos/cosmos-sdk/pull/26626) Update mldsa65 PubKey Address logic to validate length, remove unpacking. * (staking) [#26619](https://github.com/cosmos/cosmos-sdk/pull/26619) Emit `rotate_cons_pubkey` and `apply_cons_pubkey_rotation` events during key rotation. ## Bug Fixes * (codec) [#26587](https://github.com/cosmos/cosmos-sdk/pull/26587) Lower the nested `google.protobuf.Any` recursion depth cap in unknown-field validation from 10,000 to 64, reducing CPU-amplification DoS risk from deeply nested `Any` wrappers. No legitimate message nests `Any` anywhere near that deep. * (x/feegrant) [#26596](https://github.com/cosmos/cosmos-sdk/pull/26596) Honor the `PageRequest` offset and `count_total` in the `Allowances` and `AllowancesByGranter` gRPC queries, which previously collected grants inside the pagination predicate and so returned offset-skipped and beyond-limit results. * (x/authz) [#26588](https://github.com/cosmos/cosmos-sdk/pull/26588) Cap the number of expired grants pruned per `BeginBlocker` call to 200, matching `x/feegrant`'s existing pattern, so a block where many grants expire at once can't cause unbounded work. * (client) [#26524](https://github.com/cosmos/cosmos-sdk/pull/26524) Fix file handle leak in the `snapshot dump` command where chunk files were deferred-closed inside the loop, keeping every chunk's handle open until the command returned (follow-up to #25811). * (x/distribution) [#26518](https://github.com/cosmos/cosmos-sdk/pull/26518) Return an error from internal historical rewards reads when the record is absent, preventing recovered reference-count panics during BlockSTM speculative execution. * (x/auth) [#26515](https://github.com/cosmos/cosmos-sdk/pull/26515) Bound the pubkey and signature indices in `ConsumeMultisignatureVerificationGas` and `VerifyMultisignature` so a multisig signature with a bit array larger than the key set, or with more set bits than supplied signatures, returns an error instead of panicking with index out of range. * (x/distribution) [#26406](https://github.com/cosmos/cosmos-sdk/pull/26406) Add fallback paths (delegator/validator owner, then community pool) when withdrawing delegator rewards or validator commission to a blocked address during `Begin/EndBlockers`. user msg initiated paths still return `ErrUnauthorized` when withdrawing to blocked addresses. * (x/gov) [#26353](https://github.com/cosmos/cosmos-sdk/pull/26353) Fix leading comma in `proposal_messages` event attribute emitted by `SubmitProposal`. * (telemetry) [#26390](https://github.com/cosmos/cosmos-sdk/pull/26390) Fix env var for otel telemetry initialization. * (x/staking) [#26408](https://github.com/cosmos/cosmos-sdk/pull/26408) Fix `MsgBeginRedelegate` failure when redelegating all shares from an unbonded source validator that is removed after unbonding. * (x/auth/tx) [#26422](https://github.com/cosmos/cosmos-sdk/pull/26422) Reuse the signing context from the codec's `InterfaceRegistry` when `ConfigOptions.SigningOptions` is unset so that `CustomGetSigners` registered via `NewInterfaceRegistryWithOptions` are honored by `NewTxConfig` / `NewTxConfigWithOptions`. * (x/staking) [#26460](https://github.com/cosmos/cosmos-sdk/pull/26460) Coalesce key rotation power updates to not emit duplicates. * (x/staking) [#26483](https://github.com/cosmos/cosmos-sdk/pull/26483) Block `MsgCreateValidator` from creating validators with cons addrs locked by key rotations. * (blockstm) [#25893](https://github.com/cosmos/cosmos-sdk/pull/25893) Fix CancelAll cancellation by clearing blocker ESTIMATE marks before waking suspended executors. * (crypto) [#26529](https://github.com/cosmos/cosmos-sdk/pull/26529) Validate the SEC1 tag byte (`0x02`/`0x03`) when unmarshaling a `secp256k1.PubKey`, rejecting malformed compressed keys that previously passed the length-only check. * (x/auth/tx) [#26571](https://github.com/cosmos/cosmos-sdk/pull/26571) Avoid nil pointer panic in `GetSigningTxData` for multisig `ModeInfo` with a nil `Multi` or nil `Bitarray`. * (x/auth/tx) [#26527](https://github.com/cosmos/cosmos-sdk/pull/26527) Fix nil pointer panic in `GetSigningTxData` when a `SignerInfo` has a nil `PublicKey`. * (x/auth/tx) [#26517](https://github.com/cosmos/cosmos-sdk/pull/26517) Return a decode error instead of panicking when a transaction's `SignerInfos` and `Signatures` counts disagree in `GetSignaturesV2`, or a multisig's `ModeInfos` and sub-signature counts disagree in `ModeInfoAndSigToSignatureData`. * (x/auth/ante) [#26573](https://github.com/cosmos/cosmos-sdk/pull/26573) Reject tx with extra SignerInfos in SetPubKeyDecorator. * (block-stm) [#26583](https://github.com/cosmos/cosmos-sdk/pull/26583) Fix count validation tasks before advancing validationIdx to prevent lost updates. * (blockstm) [#26591](https://github.com/cosmos/cosmos-sdk/pull/26591) normalize non-positive worker count in `STMRunner.Run`. * (x/staking) [#26613](https://github.com/cosmos/cosmos-sdk/pull/26613) Require `key_rotation_fee` denom to equal `bond_denom` in `Params.Validate` and derive the default fee denom from the configured bond denom. * (x/staking) [#26611](https://github.com/cosmos/cosmos-sdk/pull/26611) Fix missing key rotation type tags on genesis import. * (blockstm) [#26627](https://github.com/cosmos/cosmos-sdk/pull/26627) Guard against block-stm estimate panic. * (x/staking) [#26616](https://github.com/cosmos/cosmos-sdk/pull/26616) Expire historical cons addr lookups only once equivocation evidence is no longer admissible. * (x/poa) [#26642](https://github.com/cosmos/cosmos-sdk/pull/26642) Always return error when migrating fees to an occupied key. * (x/staking) [#26641](https://github.com/cosmos/cosmos-sdk/pull/26641) Allow multiple history entires in genesis import, and fix labeling of historical entries. # Overview Source: https://docs.cosmos.network/sdk/latest/enterprise/overview Source-available Cosmos SDK modules for permissioned and enterprise blockchain networks. Cosmos Enterprise modules are production-ready modules for permissioned networks, institutional chains, and enterprise deployments that need features beyond a public blockchain architecture. They follow the same patterns as the core modules and integrate alongside them. The module source is published in the [`enterprise` directory of the Cosmos SDK repository](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/enterprise). ## Available modules A Proof of Authority (PoA) module enabling permissioned consensus for networks run by a known set of operators without staking or tokens. On-chain multisig accounts and collective decision-making with configurable, weighted voting policies. ## Licensing Cosmos Enterprise modules are published under the Source Available Evaluation License. For production use, please contact [sales@cosmoslabs.io](mailto:sales@cosmoslabs.io). # Overview Source: https://docs.cosmos.network/sdk/latest/enterprise/poa/overview Enterprise-Ready Network Security and Operations The Proof of Authority (PoA) permissioned consensus module is a Cosmos SDK module that enables permissioned consensus for networks requiring controlled participation. A designated administrative authority manages the validator set directly, ensuring that only approved operators participate in block production and governance. Unlike traditional Proof-of-Stake systems, validator membership is not determined by token staking. Validators are explicitly authorized, updated, and removed through on-chain administrative actions, enabling predictable operations and compliance-aligned governance. The PoA module is designed for networks that require: 1. **Permissioned Operators:** A configurable administrative authority defines validators and governance participants to meet organizational security, compliance, or consortium requirements. 2. **Instant Validator Updates:** Add, remove, or replace validators, adjust relative validator weights, and rotate keys in a single atomic on-chain action. 3. **Token-Free Operation:** Launch, operate, and govern a network without issuing or managing a native token. 4. **Future-Proof Architecture:** Seamlessly transition to Proof-of-Stake and introduce a token when needed. ## The best available option for Proof of Authority | Characteristic | Alternatives | Cosmos PoA Module | | --------------------------------------------- | ------------ | ----------------- | | Compatibility with Cosmos SDK v0.53+ | ✗ | ✓ | | Support for token-free operation | ✗ | ✓ | | Flexible governance authority | ✗ | ✓ | | Programmable penalties (jailing, slashing) | ✗ | ✓ | | Included in Cosmos bug bounty program | ✗ | ✓ | | Ongoing development by Cosmos core developers | ✗ | ✓ | ## Source Code The source code for the Proof of Authority module can be found [here](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/enterprise/poa). ## Available Documentation This directory contains detailed documentation for the Proof of Authority module. * **[API Reference](/sdk/latest/enterprise/poa/api)** - Complete API reference for gRPC queries and transactions * **[Architecture](/sdk/latest/enterprise/poa/architecture)** - System architecture and module integration details * **[Distribution](/sdk/latest/enterprise/poa/distribution)** - Fee distribution mechanics and algorithms * **[Governance](/sdk/latest/enterprise/poa/governance)** - Governance integration and power-based voting * **[Rotate a consensus key](/sdk/latest/keys/rotate-validator-key-poa)** - Rotate a PoA validator's consensus key as the operator or the admin ## Licensing The Proof of Authority module source is published under the [Source Available Evaluation License](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/LICENSE), which permits evaluation and testing in non-production environments only. Production or commercial use requires an Enterprise License from Cosmos Labs. To use the Proof of Authority module in production, contact [sales@cosmoslabs.io](mailto:sales@cosmoslabs.io). # Block-STM: Parallel Transaction Execution Source: https://docs.cosmos.network/sdk/latest/experimental/blockstm **Synopsis** Block-STM enables parallel execution of transactions during `FinalizeBlock`, using optimistic concurrency control to improve block processing throughput. **Prerequisite Readings** * [BaseApp](/sdk/latest/learn/concepts/baseapp) * [Transactions](/sdk/latest/learn/concepts/transactions) * [Store](/sdk/latest/learn/concepts/store) ## Background Block-STM is an algorithm originally published in the [Block-STM paper](https://arxiv.org/pdf/2203.06871) and implemented for the Aptos blockchain. The algorithm was then written for Cosmos SDK compatible chains in Go by developers for the Cronos blockchain in [go-block-stm](https://github.com/crypto-org-chain/go-block-stm). This library was forked and directly integrated into the Cosmos SDK with accompanying changes to the `baseapp` and `store` packages. Subsequent changes and improvements have been made on top of the original implementation to further optimize performance in both memory and time. ## Algorithm Overview Block-STM implements a form of optimistic concurrency control to enable parallel execution of transactions. It does this by implementing read and write set tracking on top of the SDK's IAVL storage layer. This, combined with the absolute ordering of transactions provided by the block proposal, is used in a validation phase which determines if any two executed transactions have conflicting storage access. In the case of conflicting storage access, the algorithm provides a means for re-execution and re-validation of the conflicting transactions based on the ordering in the proposal. Block-STM is currently only integrated into the `FinalizeBlock` phase of execution, meaning the code path is never accessed until the block is agreed upon in consensus. It is possible that the algorithm may be extended in the future to support different execution models, but as of right now it expects a complete block and returns its result after the entire block has been executed. For this reason, Block-STM is expected to produce identical results to serial execution. In other words, the `AppHash` produced by Block-STM's parallel execution should be equal to the `AppHash` produced by the default serial transaction runner. ## Safe Deployment Practices Given the Block-STM executor is a general purpose parallel execution engine, we recommend a phased rollout with extensive testing for each application individually. * *Phased Rollout* Since parallel execution is purely a performance optimization, applications should expect to calculate the same AppHash when using Block-STM as they would when serial execution is enabled via the default TxRunner. This allows teams to turn on parallel execution for a fraction of their nodes--API nodes instead of validators or on a portion of a distributed validator cluster for example. Running with a mixed fleet of parallel and serial execution for an extended time should minimize blast radius in the event that a failure occurs. * *Message Type Support* We have done testing on as many of the core SDK message types as possible, but given the Cosmos SDK allows arbitrary message creation it will be impossible to validate all message types that exist and all combinations of workflows. Each team integrating Block-STM in production should validate their own message types for both correctness and performance. NOTE: We specifically have **not** validated support for CosmWasm message types run using Block-STM. Run independent validation before enabling if your chain uses CosmWasm. * *Caching Risks* Block-STM works via dependency tracking within the SDK's `MultiStore` interface. Any data which could cause stateful changes to execution that lives outside the store poses the biggest risk for indeterminism. We recommend in general avoiding the use of cached data, in memory stores, or persisting any state outside the scope of a `Store`. ## App Integration Integration of parallel execution is abstracted into two interfaces: `DeliverTxFunc` and `TxRunner`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // DeliverTxFunc is the function called for each transaction in order to produce // a single ExecTxResult. `memTx` is an optional in-memory representation of // the transaction, which can be used to avoid decoding the transaction. type DeliverTxFunc func( tx []byte, memTx Tx, ms storetypes.MultiStore, txIndex int, incarnationCache map[string]any, ) *abci.ExecTxResult // TxRunner defines an interface for types which can be used to execute the // DeliverTxFunc. It should return an array of *abci.ExecTxResult corresponding // to the result of executing each transaction provided to the Run function. type TxRunner interface { Run( ctx context.Context, ms storetypes.MultiStore, txs [][]byte, deliverTx DeliverTxFunc, ) ([]*abci.ExecTxResult, error) } ``` The `TxRunner` is the primary interface that developers wire into their application. The `baseapp` package provides an option to set it up: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *BaseApp) SetBlockSTMTxRunner(txRunner sdk.TxRunner) { app.txRunner = txRunner } ``` ### Runner Implementations Two implementations of `TxRunner` are provided in the `baseapp/txnrunner` package: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} NewDefaultRunner(txDecoder sdk.TxDecoder) *DefaultRunner NewSTMRunner( txDecoder sdk.TxDecoder, stores []storetypes.StoreKey, workers int, estimate bool, coinDenom func(storetypes.MultiStore) string, ) *STMRunner ``` `NewDefaultRunner` is used by `BaseApp` by default and provides serial execution without using the Block-STM code paths. You do not need to wire this in explicitly. `NewSTMRunner` constructs a runner which uses parallel execution. Its parameters are: * **`txDecoder`** — A standard `sdk.TxDecoder`, readily available in any SDK application. * **`stores`** — A list of every store key used in your application. Since Block-STM needs to track store usage across transactions, it must be passed all module-level store keys. Here is an example taken from the Cosmos EVM: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keys := storetypes.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, consensusparamtypes.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, evidencetypes.StoreKey, authzkeeper.StoreKey, // IBC keys ibcexported.StoreKey, ibctransfertypes.StoreKey, // Cosmos EVM store keys evmtypes.StoreKey, feemarkettypes.StoreKey, erc20types.StoreKey, ) oKeys := storetypes.NewObjectStoreKeys( banktypes.ObjectStoreKey, evmtypes.ObjectKey, ) var nonTransientKeys []storetypes.StoreKey for _, k := range keys { nonTransientKeys = append(nonTransientKeys, k) } for _, k := range oKeys { nonTransientKeys = append(nonTransientKeys, k) } ``` * **`workers`** — The number of parallel workers. Experimentation has shown diminishing returns above your system's hardware parallelism. The recommended value is: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import "runtime" workers := min(runtime.GOMAXPROCS(0), runtime.NumCPU()) ``` * **`estimate`** — Controls whether the system should proactively determine transaction read/write conflicts before execution. Set this to `true` in all cases. * **`coinDenom`** — A function that returns the staking coin denom at runtime. This is used during estimation to reason about which keys in the `bank` module will be modified when fees are collected. A hard-coded value is acceptable; the value should be your chain's bond denom. ### Full Wiring Example Here is a complete example taken from the Cosmos EVM's `evmd` application: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} bApp.SetBlockSTMTxRunner(txnrunner.NewSTMRunner( encodingConfig.TxConfig.TxDecoder(), nonTransientKeys, min(goruntime.GOMAXPROCS(0), goruntime.NumCPU()), true, func(ms storetypes.MultiStore) string { return sdk.DefaultBondDenom }, )) ``` ### Configuration via app.toml The wiring above installs Block-STM programmatically. An application that uses `blockexec.Apply` can instead select the executor from `app.toml`, or from the equivalent `simd start` flags. Three keys control this: | Key | Type | Default | Description | | ------------------------ | ------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | `block-executor` | string | `sequential` | Selects the execution strategy. Set it to `block-stm` to enable parallel execution. | | `block-stm-workers` | int | `0` | Sets the worker count. This maps to the `workers` runner parameter. A value of `0` resolves to `min(GOMAXPROCS, NumCPU)` at runtime. | | `block-stm-pre-estimate` | bool | `false` | Enables pre-estimation of read and write conflicts. This maps to the `estimate` runner parameter. | When `block-executor` is set to `block-stm`, the block gas meter is disabled automatically. This is required, because the parallel runner panics if the block gas meter is still enabled. The programmatic wiring above does not disable it for you. Call `SetDisableBlockGasMeter(true)` when you wire the runner by hand. Example `app.toml` that enables Block-STM: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} block-executor = "block-stm" block-stm-workers = 0 block-stm-pre-estimate = true ``` The `block-stm-pre-estimate` value is set to `true` here to match the `estimate` guidance above. ## Parallel Transaction Optimization Once Block-STM is wired in, you may initially notice that most blocks execute slower than with serial execution. This is due to the overhead of re-executing transactions when any two have conflicting reads or writes. To realize performance gains, you need to reduce storage access conflicts between transactions. An example of this can be seen in [PR #26005](https://github.com/cosmos/cosmos-sdk/pull/26005) where new account creation involved assigning an ID whose value was retrieved and incremented via a single key in the `x/auth` module. The linked PR converts account ID generation to use deterministic UUID generation instead of relying on a conflicting storage location. The result is that multiple transactions in the same block which each create new accounts no longer access this key and can be run in parallel without re-executions. Work has already been done within the SDK and Cosmos EVM for common transaction types such as bank sends and EVM gas sends. The following steps describe the additional configuration needed. ### Enable Virtual Fee Collection (EVM-specific) This alters how fee collection works for EVM transactions, accumulating fees to the fee collector module in the `EndBlocker` instead of using regular sends during transaction execution. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.EVMKeeper.EnableVirtualFeeCollection() ``` ### Set Up the Object Store in the Bank Keeper This enables the bank keeper to collect fees in the `EndBlocker` instead of requiring every transaction to send fees directly to the `FeeCollector` module account. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.BankKeeper = app.BankKeeper.WithObjStoreKey(oKeys[banktypes.ObjectStoreKey]) ``` ### Custom Modules All other changes to parallelize common transactions were done in a way that does not require configuration. For custom transaction types or custom modules, additional changes to KV store access patterns may be required. There is no generalized approach for this yet. The common pattern for functionality that requires access to the same storage key is to write intermediate values to transient or object storage and use an `EndBlocker` to collect the values after all transaction execution completes. ## Benchmarks ### Environment * **Machine:** Apple M3 Pro, 11 cores * **OS:** macOS (Darwin 25.3.0) * **Package:** `github.com/cosmos/cosmos-sdk/internal/blockstm` *** ### Random Workload (10k txs, 100 keys) | Workers | ns/op | B/op | allocs/op | Speedup | | ---------- | ------ | ----- | --------- | -------- | | sequential | 1,169M | 9.9M | 220K | 1.0x | | 1 | 1,208M | 36.3M | 544K | 0.97x | | 5 | 324M | 37.1M | 552K | **3.6x** | | 10 | 218M | 43.7M | 621K | **5.4x** | | 15 | 211M | 77.4M | 975K | **5.5x** | | 20 | 226M | 78.0M | 982K | **5.2x** | ### No-Conflict Workload (10k txs) | Workers | ns/op | B/op | allocs/op | Speedup | | ---------- | ------ | ----- | --------- | -------- | | sequential | 1,381M | 11.7M | 221K | 1.0x | | 1 | 1,358M | 80.3M | 1,095K | 1.0x | | 5 | 291M | 81.1M | 1,103K | **4.8x** | | 10 | 209M | 83.5M | 1,131K | **6.6x** | | 15 | 200M | 83.7M | 1,135K | **6.9x** | | 20 | 204M | 83.8M | 1,136K | **6.8x** | ### Worst-Case Workload (full conflict, 10k txs) | Workers | ns/op | B/op | allocs/op | Speedup | | ---------- | ------ | ----- | --------- | -------- | | sequential | 1,239M | 9.6M | 220K | 1.0x | | 1 | 1,280M | 34.5M | 520K | 0.97x | | 5 | 295M | 42.8M | 607K | **4.2x** | | 10 | 224M | 70.1M | 899K | **5.5x** | | 15 | 246M | 77.3M | 980K | **5.0x** | | 20 | 262M | 77.4M | 980K | **4.7x** | ### Iterate Workload (10k txs, 100 keys) | Workers | ns/op | B/op | allocs/op | Speedup | | ---------- | ------ | ------ | --------- | -------- | | sequential | 1,286M | 16.5M | 290K | 1.0x | | 1 | 1,332M | 75.2M | 843K | 0.97x | | 5 | 288M | 76.5M | 855K | **4.5x** | | 10 | 252M | 123.1M | 1,280K | **5.1x** | | 15 | 317M | 359.5M | 3,405K | **4.1x** | | 20 | 319M | 363.5M | 3,419K | **4.0x** | *** ### Key Takeaways * Peak speedup is **6.9x** at 15 workers on the no-conflict workload * Diminishing returns beyond 10–15 workers, with memory usage increasing significantly * Even the worst-case (full conflict) scenario achieves \~5x speedup at 10 workers * The iterate workload shows performance degradation beyond 10 workers, likely due to increased contention on range reads (memory usage jumps \~3x at 15+ workers) # ABCI Overview Source: https://docs.cosmos.network/sdk/latest/guides/abci/abci ABCI, Application Blockchain Interface is the interface between CometBFT and the application. More information about ABCI can be found here. CometBFT version 0.38 included a new version of ABCI (called ABCI 2.0) which added several new methods. ## What is ABCI? ABCI, Application Blockchain Interface is the interface between CometBFT and the application. More information about ABCI can be found [here](/cometbft/latest/spec/abci/Overview). CometBFT version 0.38 introduced ABCI 2.0, which added several new methods: * `PrepareProposal` * `ProcessProposal` * `ExtendVote` * `VerifyVoteExtension` * `FinalizeBlock` The Cosmos SDK's `BaseApp` implements the full ABCI interface. The source lives in [`baseapp/abci.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/abci.go). ## CheckTx ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} graph TD subgraph SDK[Cosmos SDK] B[BaseApp] A[AnteHandlers] B <-->|Validate TX| A end C[CometBFT] <-->|CheckTx|SDK U((User)) -->|Submit TX| C N[P2P] -->|Receive TX| C ``` `CheckTx` is called by `BaseApp` whenever CometBFT receives a transaction from a client, over the p2p network, or via RPC. Its sole job is to decide whether the transaction is valid enough to enter the mempool. It does not execute messages. The default implementation runs the transaction through the `AnteHandler` chain, which performs signature verification, fee checks, and other stateless or lightweight stateful validation. If the `AnteHandler` returns an error, the transaction is rejected and never reaches the mempool. See the implementation at [`baseapp/abci.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/abci.go). ### Custom CheckTx handler `CheckTxHandler` lets you replace the default `CheckTx` logic entirely. The type is defined in [`types/abci.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/abci.go): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type CheckTxHandler func(runTx RunTx, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) ``` Where `RunTx` is: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type RunTx = func(txBytes []byte, tx Tx) (gInfo GasInfo, result *Result, anteEvents []abci.Event, err error) ``` The handler receives the `runTx` closure from `BaseApp` (bound to the correct execution mode) and the raw ABCI request. It must return deterministic results for the same input bytes. Register a custom handler from `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.SetCheckTxHandler(myCheckTxHandler) ``` ## PrepareProposal Based on validator voting power, CometBFT selects a block proposer and calls `PrepareProposal` on that validator's application. The proposer collects outstanding transactions from the mempool and returns a proposal to CometBFT. CometBFT's own mempool uses FIFO ordering. `PrepareProposal` gives the application full control to reorder, drop, or inject transactions before the proposal is sent. For example, an application can inject vote extension data from the previous block as synthetic transactions. What the application does here has no effect on CometBFT's mempool state. `PrepareProposal` MAY be non-deterministic and is only executed by the current block proposer. The Cosmos SDK provides `DefaultProposalHandler` in [`baseapp/abci_utils.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/abci_utils.go), which selects transactions from the app-side mempool up to `req.MaxTxBytes` and the block gas limit. If you implement a custom `PrepareProposal` handler, the selected transactions MUST NOT exceed the maximum block gas (if set) or `req.MaxTxBytes`. To wire the default handler (or swap in a custom one) from `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} abciPropHandler := baseapp.NewDefaultProposalHandler(mempool, app) app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) ``` Vote extensions are only available at the height after they are enabled. See [Vote Extensions](/sdk/latest/guides/abci/vote-extensions) for details. ## ProcessProposal After the block proposer broadcasts a proposal, every validator calls `ProcessProposal` to accept or reject it. The default implementation checks that each transaction decodes correctly and passes the `AnteHandler`. `ProcessProposal` MUST be deterministic. Non-deterministic results cause apphash mismatches across validators. If the handler panics or returns an error, honest validators prevote nil and CometBFT starts a new round with a new proposal. See the default implementation in [`baseapp/abci_utils.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/abci_utils.go). To wire a custom handler: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.SetProcessProposal(myProcessProposalHandler) ``` ## ExtendVote and VerifyVoteExtensions These methods allow applications to extend the voting process by requiring validators to perform additional actions beyond simply validating blocks. If vote extensions are enabled, `ExtendVote` is called on every validator and each one returns its vote extension — an arbitrary byte slice. This data is only available in the next block height during `PrepareProposal`. Common use cases include prices for a price oracle or encryption shares for an encrypted transaction mempool. `ExtendVote` CAN be non-deterministic. `VerifyVoteExtension` is called on every validator to verify other validators' vote extensions. It MUST be deterministic. Applications must keep vote extension data concise, as large extensions degrade chain performance. See the [CometBFT QA results](/cometbft/latest/docs/qa/CometBFT-QA-38#vote-extensions-testbed) for benchmarks. See [Vote Extensions](/sdk/latest/guides/abci/vote-extensions) for implementation details. ## FinalizeBlock `FinalizeBlock` is called once consensus is reached on a proposal. It executes all transactions in the block, runs `BeginBlock`/`EndBlock` equivalents, and commits the resulting state. It replaces the old `BeginBlock`, `DeliverTx`, and `EndBlock` methods from ABCI 1.0. See the implementation at [`baseapp/abci.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/abci.go). # Application Mempool Source: https://docs.cosmos.network/sdk/latest/guides/abci/app-mempool **Synopsis** This section describes how the app-side mempool can be used and replaced. Since `v0.47` the application has its own mempool to allow much more granular block building than previous versions. This change was enabled by [ABCI 1.0](https://github.com/cometbft/cometbft/blob/v0.37.0/spec/abci). Notably it introduces the `PrepareProposal` and `ProcessProposal` steps of ABCI++. **Prerequisite Readings** * [BaseApp](/sdk/latest/learn/concepts/baseapp) * [ABCI](/sdk/latest/guides/abci/abci) ## Overview The application mempool is an in-process transaction store within the application that gives developers control over how transactions are ordered and selected for block inclusion. Unlike the [CometBFT mempool](/cometbft/latest/docs/core/mempool), which handles transaction receipt and gossip at the network layer, the SDK's application mempool operates at block proposal time to determine the ordering of transactions within a block. When a transaction is submitted to a node, CometBFT receives it first. CometBFT calls `CheckTx` on the application to validate the transaction, then stores it in its own mempool (the `flood` mempool by default) and gossips it to peers. When it is time to build a block, CometBFT calls `PrepareProposal` on the application. This is where the SDK app mempool comes in: the application pulls from its own internal mempool and decides which transactions to include and in what order: by priority, nonce, fees, or any custom logic the developer chooses. So CometBFT decides what gets accepted into the network; the SDK app mempool decides how accepted transactions are ordered within a block. This in-process mempool should not be confused with CometBFT's `app` mempool. The mempool on this page orders transactions at `PrepareProposal` time. Setting `mempool.type = "app"` in CometBFT is a separate mechanism that routes transaction receipt itself to the application through the `InsertTx` and `ReapTxs` ABCI methods. That mechanism is documented in the [CometBFT mempool guide](/cometbft/next/docs/core/mempool). ## Mempool There are countless designs that an application developer can write for a mempool, the SDK opted to provide only simple mempool implementations. Namely, the SDK provides the following mempools: * [No-op Mempool](#no-op-mempool) * [Sender Nonce Mempool](#sender-nonce-mempool) * [Priority Nonce Mempool](#priority-nonce-mempool) By default, the SDK uses the [No-op Mempool](#no-op-mempool), but it can be replaced by the application developer in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} nonceMempool := mempool.NewSenderNonceMempool() mempoolOpt := baseapp.SetMempool(nonceMempool) baseAppOptions = append(baseAppOptions, mempoolOpt) ``` ### No-op Mempool A no-op mempool is a mempool where transactions are completely discarded and ignored when BaseApp interacts with the mempool. When this mempool is used, it is assumed that an application will rely on CometBFT's transaction ordering defined in `RequestPrepareProposal`, which is FIFO-ordered by default. > Note: If a NoOp mempool is used, PrepareProposal and ProcessProposal both should be aware of this as > PrepareProposal could include transactions that could fail verification in ProcessProposal. ### Sender Nonce Mempool The nonce mempool keeps each account's transactions sorted by nonce, so they are proposed in the order the account signed them. It works by storing the transaction in a list sorted by the transaction nonce. When the proposer asks for transactions to be included in a block it randomly selects a sender and gets the first transaction in the list. It repeats this until the mempool is empty or the block is full. It is configurable with the following parameters: #### MaxTxs It is an integer value that sets the mempool in one of three modes, *bounded*, *unbounded*, or *disabled*. * **negative**: Disabled, mempool does not insert new transaction and return early. * **zero**: Unbounded mempool has no transaction limit and will never fail with `ErrMempoolTxMaxCapacity`. * **positive**: Bounded, it fails with `ErrMempoolTxMaxCapacity` when `maxTx` value is the same as `CountTx()` #### Seed Set the seed for the random number generator used to select transactions from the mempool. ### Priority Nonce Mempool The [priority nonce mempool](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/mempool/priority_nonce_spec.md) is a mempool implementation that stores txs in a partially ordered set by 2 dimensions: * priority * sender-nonce (sequence number) Internally it uses one priority ordered [skip list](https://pkg.go.dev/github.com/huandu/skiplist) and one skip list per sender ordered by sender-nonce (sequence number). When there are multiple txs from the same sender, they are not always comparable by priority to other sender txs and must be partially ordered by both sender-nonce and priority. It is configurable with the following parameters: #### MaxTxs It is an integer value that sets the mempool in one of three modes, *bounded*, *unbounded*, or *disabled*. * **negative**: Disabled, mempool does not insert new transaction and return early. * **zero**: Unbounded mempool has no transaction limit and will never fail with `ErrMempoolTxMaxCapacity`. * **positive**: Bounded, it fails with `ErrMempoolTxMaxCapacity` when `maxTx` value is the same as `CountTx()` #### Callback The priority nonce mempool provides mempool options allowing the application sets callback(s). * **OnRead**: Set a callback to be called when a transaction is read from the mempool. * **TxReplacement**: Sets a callback to be called when duplicated transaction nonce detected during mempool insert. Application can define a transaction replacement rule based on tx priority or certain transaction fields. More information on the SDK mempool implementation can be found in the [godocs](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/types/mempool). # Vote Extensions Source: https://docs.cosmos.network/sdk/latest/guides/abci/vote-extensions Vote extensions are arbitrary bytes that validators can attach to their pre-commit vote at block height `H`. They are part of ABCI 2.0 and are available starting from CometBFT v0.38 and Cosmos SDK v0.50. ## Enabling vote extensions Vote extensions are controlled by the `VoteExtensionsEnableHeight` consensus parameter. At the configured height, CometBFT begins calling `ExtendVote` and `VerifyVoteExtension` on every validator. Extensions produced at height `H` are available to the block proposer at height `H+1` via `PrepareProposal`. To check whether vote extensions are active in a handler: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cp := ctx.ConsensusParams() if cp.Abci != nil && req.Height > cp.Abci.VoteExtensionsEnableHeight { // vote extensions are available } ``` `ConsensusParams().Abci` is a pointer and must be nil-checked before use. ## ExtendVote The Cosmos SDK defines [`ExtendVoteHandler`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/abci.go#L48): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ExtendVoteHandler func(Context, *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) ``` Register a handler in `app.go` via `baseapp.SetExtendVoteHandler` (defined in [`baseapp/options.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/options.go)): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.SetExtendVoteHandler(myExtendVoteHandler) ``` If `ExtendVoteHandler` is set, it **must** return a non-nil `VoteExtension`. An empty byte slice is valid. `ExtendVote` is called only on the local validator and does **not** need to be deterministic. Common uses include: * Submitting prices for an oracle * Sharing encryption shares for an encrypted mempool Keep extensions small — large extensions increase consensus latency. See [CometBFT QA results](/cometbft/latest/docs/qa/CometBFT-QA-38#vote-extensions-testbed) for benchmarks. ## VerifyVoteExtension The SDK defines [`VerifyVoteExtensionHandler`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/abci.go#L52): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type VerifyVoteExtensionHandler func(Context, *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) ``` Register it in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.SetVerifyVoteExtensionHandler(myVerifyVoteExtensionHandler) ``` `VerifyVoteExtension` is called on every validator for every peer's pre-commit. It **must** be deterministic — the same extension must produce the same result on every validator. If an application defines `ExtendVoteHandler`, it should also define a `VerifyVoteExtensionHandler`. Always validate the size of incoming extensions in this handler. ## Validating vote extension signatures Before processing vote extensions in `PrepareProposal` or `ProcessProposal`, validate that they are properly signed. The SDK provides [`baseapp.ValidateVoteExtensions`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/abci_utils.go) for this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} err := baseapp.ValidateVoteExtensions(ctx, valStore, req.Height, ctx.ChainID(), req.LocalLastCommit) if err != nil { return nil, err } ``` `ValidateVoteExtensions` verifies that each vote extension in the commit is correctly signed by its validator. `valStore` is a [`baseapp.ValidatorStore`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/abci_utils.go), an interface with a single method: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ValidatorStore interface { GetPubKeyByConsAddr(context.Context, sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) } ``` Call `ValidateVoteExtensions` in both `PrepareProposal` (on `req.LocalLastCommit`) and `ProcessProposal` (on the `ExtendedCommitInfo` recovered from the injected transaction) before trusting any extension data. ## Vote extension propagation Vote extensions from height `H` are provided only to the block proposer at height `H+1` via `req.LocalLastCommit` in `PrepareProposal`. They are **not** provided to other validators during `ProcessProposal`. If all validators need to use extension data at `H+1`, the proposer must inject it into the block proposal. Since the `Txs` field in `PrepareProposal` is a `[][]byte`, any byte slice — including a serialized extensions summary — can be prepended to the proposal: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} injectedVoteExtTx := StakeWeightedPrices{ StakeWeightedPrices: stakeWeightedPrices, ExtendedCommitInfo: req.LocalLastCommit, } bz, err := json.Marshal(injectedVoteExtTx) if err != nil { return nil, err } proposalTxs = append([][]byte{bz}, proposalTxs...) ``` `FinalizeBlock` ignores any byte slice that does not implement `sdk.Tx`, so injected extensions are safely skipped during message execution. For more details on propagation design, see the [ABCI 2.0 ADR](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-064-abci-2.0.md#vote-extension-propagation--verification). ## Recovery via PreBlocker The SDK's `PreBlocker` runs before any message execution in `FinalizeBlock`. Use it to recover injected vote extensions and make the results available to modules during the block: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (h *ProposalHandler) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { res := &sdk.ResponsePreBlock{} if len(req.Txs) == 0 { return res, nil } cp := ctx.ConsensusParams() if cp.Abci != nil && req.Height > cp.Abci.VoteExtensionsEnableHeight { var injectedVoteExtTx StakeWeightedPrices if err := json.Unmarshal(req.Txs[0], &injectedVoteExtTx); err != nil { return nil, err } if err := h.keeper.SetOraclePrices(ctx, injectedVoteExtTx.StakeWeightedPrices); err != nil { return nil, err } } return res, nil } ``` Register the PreBlocker in `app.go` (see [`baseapp/options.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/options.go)): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.SetPreBlocker(proposalHandler.PreBlocker) ``` The `sdk.PreBlocker` type is defined in [`types/abci.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/abci.go): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type PreBlocker func(Context, *abci.RequestFinalizeBlock) (*ResponsePreBlock, error) ``` State written to the context inside `PreBlocker` is available to all `BeginBlock` and message handlers in the same block. # Guides Overview Source: https://docs.cosmos.network/sdk/latest/guides/guides Deep dives into specific Cosmos SDK topics for developers who have completed the tutorial. These guides go deeper on specific topics. If you've completed the [Build a Chain Tutorial](/sdk/latest/tutorials/example/00-overview) and want to learn more about a particular area, this is where to look. ## Module Design Best practices and architectural patterns for building well-structured modules. * [Module Design Considerations](/sdk/latest/guides/module-design/module-design-considerations): module boundaries, state layout, privileged operations, and inter-module dependencies * [Object-Capability Model](/sdk/latest/guides/module-design/ocap): how the SDK uses keeper interfaces to scope access between modules ## ABCI How your application interacts with CometBFT at the protocol level, including advanced features such as mempool design and vote extensions. * [ABCI Overview](/sdk/latest/guides/abci/abci): CheckTx, PrepareProposal, ProcessProposal, FinalizeBlock * [Application Mempool](/sdk/latest/guides/abci/app-mempool): custom mempool implementations and transaction ordering * [Vote Extensions](/sdk/latest/guides/abci/vote-extensions): injecting application data into the consensus process ## Tooling Tools available to Cosmos SDK developers. * [Tool Guide](/sdk/latest/guides/tooling/tool-guide): overview of all available tools by category * [Writing CLI Commands](/sdk/latest/guides/tooling/autocli): AutoCLI and hand-written commands * [Confix](/sdk/latest/guides/tooling/confix): managing and migrating node configuration ## State How modules store and access state. * [Module Store Internals](/sdk/latest/guides/state/store): KVStore, prefix stores, and the multistore * [Collections API](/sdk/latest/guides/state/collections): the modern Collections framework for module state ## Upgrades and Migrations How to upgrade modules and chains without downtime. * [Upgrading Modules](/sdk/latest/guides/upgrades/upgrade): consensus versions, migration handlers, and store upgrades * [Cosmovisor](/sdk/latest/guides/upgrades/cosmovisor): automated binary upgrade management ## Testing and Observability Testing your modules and monitoring a running chain. * [Module Simulation](/sdk/latest/guides/testing/simulator): fuzz testing with the SDK's simulation framework * [Telemetry](/sdk/latest/guides/testing/telemetry): metrics and instrumentation * [Log v2](/sdk/latest/guides/testing/log): structured logging with zerolog and OpenTelemetry # Module Design Considerations Source: https://docs.cosmos.network/sdk/latest/guides/module-design/module-design-considerations **Synopsis** Modules define most of the logic of Cosmos SDK applications. Developers compose modules together using the Cosmos SDK to build their custom application-specific blockchains. This document outlines the basic concepts behind SDK modules and how to approach module management. This page discusses some of the design considerations for building modules in the Cosmos SDK. For more in-depth information on modules, see the following pages: Deep dive into how modules work -- keepers, message handlers, query services, and the module manager. Follow a step-by-step tutorial to build a custom module from scratch on an example Cosmos SDK chain. ## Design Considerations Before writing any code, these are the key design decisions that shape how a module will behave, interoperate, and evolve. ### Define clear module boundaries A module should own a single, well-scoped piece of application state. Resist the temptation to bundle unrelated functionality into one module because it is convenient. Narrow modules are easier to audit, re-use across chains, and upgrade independently. Ask: could a different chain reasonably use this module without modification? If the answer depends on removing half the features, the module is probably doing too much. ### Plan your state structure early Every `KVStore` key your module defines is permanent: removing or renaming keys requires a migration. Use the [Collections](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/collections/README.md) library for structured state management, and name keys to be collision-resistant and self-documenting. Consider what your module needs to index. A value that is only ever looked up by a single key is simple. A value looked up by multiple dimensions (e.g. by owner and by ID) requires secondary indexes, which add complexity and storage overhead. ### Design your message and query surface Keep the `Msg` service minimal. Every message your module accepts becomes part of your public API and must be handled across upgrades. Prefer fewer, general-purpose messages over many narrow ones. Queries are cheaper to add later than messages, but consider what clients need from day one. Poorly designed queries often lead to excessive on-chain state that exists solely to support a query no one else needs. ### Decide how privileged operations are controlled Most modules have parameters that governance should be able to update. Use the standard `MsgUpdateParams` pattern with an `Authority` field, and set that authority to the governance module address at genesis. This ensures parameter changes go through on-chain governance rather than being hardcoded or requiring a chain upgrade. If your module needs to call into another module's privileged functions, establish those permissions through keeper references at app initialization -- not through dynamic lookups at runtime. ### Model inter-module dependencies carefully List every other module your module needs access to. Each dependency becomes a keeper reference injected into your keeper at construction. Avoid circular dependencies: if module A needs B and B needs A, one of them is doing too much. Introduce a third module or restructure the shared logic. Prefer accepting interfaces over concrete keeper types. This makes your module testable in isolation and re-usable across chains with different module implementations. ### Plan for upgrades from the start If your module defines state, it will eventually need a migration. Write migration logic in `x//migrations/` from the first version, even if v1 to v2 is a no-op. Establish the pattern early so upgrades are not an afterthought. See [Module Upgrades](/sdk/latest/guides/upgrades/upgrade) for implementation details. ## Role of Modules in a Cosmos SDK Application The Cosmos SDK can be thought of as the Ruby-on-Rails of blockchain development. It comes with a core that provides the basic functionalities every blockchain application needs, like a [boilerplate implementation of the ABCI](/sdk/latest/learn/concepts/baseapp) to communicate with the underlying consensus engine, a [`multistore`](/sdk/latest/learn/concepts/store#multistore) to persist state, a [server](/sdk/latest/node/run-node) to form a full-node and interfaces to handle queries. On top of this core, the Cosmos SDK enables developers to build modules that implement the business logic of their application. In other words, SDK modules implement the bulk of the logic of applications, while the core does the wiring and enables modules to be composed together. The end goal is to build a robust ecosystem of open-source Cosmos SDK modules, making it increasingly easier to build complex blockchain applications. Cosmos SDK modules can be seen as little state-machines within the state-machine. They generally define a subset of the state using one or more `KVStore`s in the [main multistore](/sdk/latest/learn/concepts/store#multistore), as well as a subset of [message types](/sdk/latest/learn/concepts/transactions#messages). These messages are routed by one of the main components of Cosmos SDK core, [`BaseApp`](/sdk/latest/learn/concepts/baseapp), to a module Protobuf [`Msg` service](/sdk/latest/learn/concepts/transactions#messages) that defines them. ```mermaid expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} flowchart TD A[Transaction relayed from the full-node's consensus engine to the node's application via FinalizeBlock] A --> B[APPLICATION] B --> C["Using baseapp's methods: Decode the Tx, extract and route the message(s)"] C --> D[Message routed to the correct module to be processed] D --> E[AUTH MODULE] D --> F[BANK MODULE] D --> G[STAKING MODULE] D --> H[GOV MODULE] H --> I[Handles message, Updates state] E --> I F --> I G --> I I --> J["Return result to the underlying consensus engine (e.g. CometBFT) (0=Ok, 1=Err)"] ``` As a result of this architecture, building a Cosmos SDK application usually revolves around writing modules to implement the specialized logic of the application and composing them with existing modules to complete the application. Developers will generally work on modules that implement logic needed for their specific use case that do not exist yet, and will use existing modules for more generic functionalities like staking, accounts, or token management. ### Modules as super-users Modules have the ability to perform actions that are not available to regular users. This is because modules are given sudo permissions by the state machine. Modules can reject another modules desire to execute a function but this logic must be explicit. Examples of this can be seen when modules create functions to modify parameters: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package keeper import ( "context" "github.com/hashicorp/go-metrics" errorsmod "cosmossdk.io/errors" "cosmossdk.io/x/bank/types" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) type msgServer struct { Keeper } var _ types.MsgServer = msgServer{ } // NewMsgServerImpl returns an implementation of the bank MsgServer interface // for the provided Keeper. func NewMsgServerImpl(keeper Keeper) types.MsgServer { return &msgServer{ Keeper: keeper } } func (k msgServer) Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { var ( from, to []byte err error ) if base, ok := k.Keeper.(BaseKeeper); ok { from, err = base.ak.AddressCodec().StringToBytes(msg.FromAddress) if err != nil { return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid from address: %s", err) } to, err = base.ak.AddressCodec().StringToBytes(msg.ToAddress) if err != nil { return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid to address: %s", err) } } else { return nil, sdkerrors.ErrInvalidRequest.Wrapf("invalid keeper type: %T", k.Keeper) } if !msg.Amount.IsValid() { return nil, errorsmod.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String()) } if !msg.Amount.IsAllPositive() { return nil, errorsmod.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String()) } if err := k.IsSendEnabledCoins(ctx, msg.Amount...); err != nil { return nil, err } if k.BlockedAddr(to) { return nil, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", msg.ToAddress) } err = k.SendCoins(ctx, from, to, msg.Amount) if err != nil { return nil, err } defer func() { for _, a := range msg.Amount { if a.Amount.IsInt64() { telemetry.SetGaugeWithLabels( []string{"tx", "msg", "send" }, float32(a.Amount.Int64()), []metrics.Label{ telemetry.NewLabel("denom", a.Denom) }, ) } } }() return &types.MsgSendResponse{ }, nil } func (k msgServer) MultiSend(ctx context.Context, msg *types.MsgMultiSend) (*types.MsgMultiSendResponse, error) { if len(msg.Inputs) == 0 { return nil, types.ErrNoInputs } if len(msg.Inputs) != 1 { return nil, types.ErrMultipleSenders } if len(msg.Outputs) == 0 { return nil, types.ErrNoOutputs } if err := types.ValidateInputOutputs(msg.Inputs[0], msg.Outputs); err != nil { return nil, err } // NOTE: totalIn == totalOut should already have been checked for _, in := range msg.Inputs { if err := k.IsSendEnabledCoins(ctx, in.Coins...); err != nil { return nil, err } } for _, out := range msg.Outputs { if base, ok := k.Keeper.(BaseKeeper); ok { accAddr, err := base.ak.AddressCodec().StringToBytes(out.Address) if err != nil { return nil, err } if k.BlockedAddr(accAddr) { return nil, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", out.Address) } } else { return nil, sdkerrors.ErrInvalidRequest.Wrapf("invalid keeper type: %T", k.Keeper) } } err := k.InputOutputCoins(ctx, msg.Inputs[0], msg.Outputs) if err != nil { return nil, err } return &types.MsgMultiSendResponse{ }, nil } func (k msgServer) UpdateParams(ctx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { if k.GetAuthority() != req.Authority { return nil, errorsmod.Wrapf(types.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.GetAuthority(), req.Authority) } if err := req.Params.Validate(); err != nil { return nil, err } if err := k.SetParams(ctx, req.Params); err != nil { return nil, err } return &types.MsgUpdateParamsResponse{ }, nil } func (k msgServer) SetSendEnabled(ctx context.Context, msg *types.MsgSetSendEnabled) (*types.MsgSetSendEnabledResponse, error) { if k.GetAuthority() != msg.Authority { return nil, errorsmod.Wrapf(types.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.GetAuthority(), msg.Authority) } seen := map[string]bool{ } for _, se := range msg.SendEnabled { if _, alreadySeen := seen[se.Denom]; alreadySeen { return nil, sdkerrors.ErrInvalidRequest.Wrapf("duplicate denom entries found for %q", se.Denom) } seen[se.Denom] = true if err := se.Validate(); err != nil { return nil, sdkerrors.ErrInvalidRequest.Wrapf("invalid SendEnabled denom %q: %s", se.Denom, err) } } for _, denom := range msg.UseDefaultFor { if err := sdk.ValidateDenom(denom); err != nil { return nil, sdkerrors.ErrInvalidRequest.Wrapf("invalid UseDefaultFor denom %q: %s", denom, err) } } if len(msg.SendEnabled) > 0 { k.SetAllSendEnabled(ctx, msg.SendEnabled) } if len(msg.UseDefaultFor) > 0 { k.DeleteSendEnabled(ctx, msg.UseDefaultFor...) } return &types.MsgSetSendEnabledResponse{ }, nil } func (k msgServer) Burn(goCtx context.Context, msg *types.MsgBurn) (*types.MsgBurnResponse, error) { var ( from []byte err error ) var coins sdk.Coins for _, coin := range msg.Amount { coins = coins.Add(sdk.NewCoin(coin.Denom, coin.Amount)) } if base, ok := k.Keeper.(BaseKeeper); ok { from, err = base.ak.AddressCodec().StringToBytes(msg.FromAddress) if err != nil { return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid from address: %s", err) } } else { return nil, sdkerrors.ErrInvalidRequest.Wrapf("invalid keeper type: %T", k.Keeper) } if !coins.IsValid() { return nil, errorsmod.Wrap(sdkerrors.ErrInvalidCoins, coins.String()) } if !coins.IsAllPositive() { return nil, errorsmod.Wrap(sdkerrors.ErrInvalidCoins, coins.String()) } err = k.BurnCoins(goCtx, from, coins) if err != nil { return nil, err } return &types.MsgBurnResponse{ }, nil } ``` ## How to Approach Building Modules as a Developer While there are no definitive guidelines for writing modules, here are some important design principles developers should keep in mind when building them: * **Composability**: Cosmos SDK applications are almost always composed of multiple modules. This means developers need to carefully consider the integration of their module not only with the core of the Cosmos SDK, but also with other modules. The former is achieved by following standard design patterns outlined [here](#main-components-of-cosmos-sdk-modules), while the latter is achieved by properly exposing the store(s) of the module via the [`keeper`](/sdk/latest/learn/concepts/modules#keeper). * **Specialization**: A direct consequence of the **composability** feature is that modules should be **specialized**. Developers should carefully establish the scope of their module and not batch multiple functionalities into the same module. This separation of concerns enables modules to be re-used in other projects and improves the upgradability of the application. **Specialization** also plays an important role in the [object-capabilities model](/sdk/latest/guides/module-design/ocap) of the Cosmos SDK. * **Capabilities**: Most modules need to read and/or write to the store(s) of other modules. However, in an open-source environment, it is possible for some modules to be malicious. That is why module developers need to carefully think not only about how their module interacts with other modules, but also about how to give access to the module's store(s). The Cosmos SDK takes a capabilities-oriented approach to inter-module security. This means that each store defined by a module is accessed by a `key`, which is held by the module's [`keeper`](/sdk/latest/learn/concepts/modules#keeper). This `keeper` defines how to access the store(s) and under what conditions. Access to the module's store(s) is done by passing a reference to the module's `keeper`. ## Main Components of Cosmos SDK Modules Modules are by convention defined in the `./x/` subfolder (e.g. the `bank` module will be defined in the `./x/bank` folder). They generally share the same core components: * A [`keeper`](/sdk/latest/learn/concepts/modules#keeper), used to access the module's store(s) and update the state. * A [`Msg` service](/sdk/latest/learn/concepts/transactions#messages), used to process messages when they are routed to the module by [`BaseApp`](/sdk/latest/learn/concepts/baseapp#message-routing) and trigger state-transitions. * A [query service](/sdk/latest/learn/concepts/transactions#queries), used to process user queries when they are routed to the module by [`BaseApp`](/sdk/latest/learn/concepts/baseapp#query-routing). * Interfaces, for end users to query the subset of the state defined by the module and create `message`s of the custom types defined in the module. In addition to these components, modules implement the `AppModule` interface in order to be managed by the [`module manager`](/sdk/latest/learn/concepts/app-go#module-manager). # Object-Capability Model Source: https://docs.cosmos.network/sdk/latest/guides/module-design/ocap How the Cosmos SDK uses object capabilities to isolate modules and limit the blast radius of faulty or malicious code. The Cosmos SDK is built around the **object-capability model** (ocap) — a security model designed for systems that compose untrusted components. The threat model is explicit: a thriving ecosystem of Cosmos SDK modules will eventually include faulty or malicious ones. Ocap limits the damage any single module can do. ## How it works The model has two rules: 1. An object can send a message to another object only if it holds a reference to it. 2. An object can obtain a reference to another object only by receiving it through a message. In practice: a module can only affect the state it has been explicitly handed access to. If the bank keeper was not passed to your module, your module cannot touch balances — full stop. There is no global registry to reach into. This makes security analysis local. You can audit what a module can do by looking at what references it was given at wiring time, without reading its implementation. ## Pointer vs. value Only pass what a module needs. If you pass a pointer, you grant write access. If you pass a value, you grant read access. This code violates the principle — passing a pointer to an external module grants it the ability to mutate the account: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} account := &AppAccount{ Address: pub.Address(), Coins: sdk.Coins{sdk.NewInt64Coin("ATM", 100)}, } sumValue := externalModule.ComputeSumValue(account) // can modify account ``` Pass a copy instead: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sumValue := externalModule.ComputeSumValue(*account) // read-only ``` ## Keeper interfaces The most common place to apply ocap in SDK modules is at keeper boundaries. Instead of accepting a concrete keeper type from another module, define a narrow interface containing only the methods your module actually calls. For example, `x/distribution` needs to query balances and send coins, but it does not need the full bank keeper. It defines its own interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/distribution/types/expected_keepers.go type BankKeeper interface { GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error BlockedAddr(addr sdk.AccAddress) bool } ``` By convention these live in `types/expected_keepers.go`. The benefit is twofold: the interface documents exactly what cross-module access your module requires, and it makes the dependency easy to mock in tests. ## Store isolation Modules do not receive direct access to the global multistore. Instead, each module gets a `store.KVStoreService` scoped to its own prefix — it can only read and write within that namespace. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { storeService store.KVStoreService // ... } ``` This means a bug or malicious call in one module's keeper cannot read or corrupt another module's state. The scoping is enforced at the store layer, not by convention. ## Authority Some operations — updating parameters, pausing a module, triggering emergency actions — should only be callable by governance or another trusted account. The SDK handles this with an explicit `authority` string stored in the keeper. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { // the address capable of executing privileged messages, // typically the x/gov module account authority string } ``` Message handlers check the caller against this address before proceeding: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if msg.Authority != k.authority { return nil, errors.Wrapf(sdkerrors.ErrUnauthorized, "expected %s, got %s", k.authority, msg.Authority) } ``` The authority address is set at wiring time in `app.go` and cannot be changed at runtime. This is ocap applied to governance: privileged capability is a reference, and only the holder of that reference can exercise it. See [`simapp/app.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/simapp/app.go) for how keeper dependencies and authorities are wired in a complete application. For background, see the [Wikipedia article on object-capability model](https://en.wikipedia.org/wiki/Object-capability_model). # Address Encoding Source: https://docs.cosmos.network/sdk/latest/guides/reference/bech32 The Cosmos SDK uses the Bech32 address format for all user-facing addresses. Bech32 encoding provides robust integrity checks through checksums and includes a human-readable prefix (HRP) that provides contextual information about the address type. ## Address Types The SDK defines three distinct address types, each with its own Bech32 prefix: | Address Type | Bech32 Prefix | Example | Purpose | | -------------------------- | --------------- | ------------------------- | ------------------------------------------------ | | Account Address | `cosmos` | `cosmos1r5v5sr...` | User accounts, balances, transactions | | Validator Operator Address | `cosmosvaloper` | `cosmosvaloper1r5v5sr...` | Validator operator identity, staking operations | | Consensus Address | `cosmosvalcons` | `cosmosvalcons1r5v5sr...` | Validator consensus participation, block signing | Each address type also has a corresponding public key prefix: * Account public keys: `cosmospub` * Validator public keys: `cosmosvaloperpub` * Consensus public keys: `cosmosvalconspub` ## Supported Key Schemes The Cosmos SDK supports three key schemes. The choice of scheme affects address length and whether it can be used for transactions or consensus: | | Address length in bytes | Public key length in bytes | Used for transaction authentication | Used for consensus (CometBFT) | | :----------: | :---------------------: | :------------------------: | :---------------------------------: | :---------------------------: | | `secp256k1` | 20 | 33 | yes | no | | `secp256r1` | 32 | 33 | yes | no | | `tm-ed25519` | -- not used -- | 32 | no | yes | `secp256k1` is the default for user accounts. `secp256r1` is supported as an alternative and produces longer addresses (32 bytes). `tm-ed25519` is used exclusively for validator consensus keys and does not produce a user-facing address. ## Address Derivation Addresses are derived from public keys through cryptographic hashing. The process differs based on the key algorithm: ### Secp256k1 Keys (Account Addresses) Account addresses use Bitcoin-style address derivation: ``` 1. Public Key: 33 bytes (compressed secp256k1 public key) 2. SHA-256 hash of public key: 32 bytes 3. RIPEMD-160 hash of result: 20 bytes (final address) ``` **Implementation:** `crypto/keys/secp256k1/secp256k1.go` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (pubKey *PubKey) Address() crypto.Address { sha := sha256.Sum256(pubKey.Key) // Step 1: SHA-256 hasherRIPEMD160 := ripemd160.New() hasherRIPEMD160.Write(sha[:]) return hasherRIPEMD160.Sum(nil) // Step 2: RIPEMD-160 = 20 bytes } ``` ### Ed25519 Keys (Consensus Addresses) Consensus addresses use truncated SHA-256: ``` 1. Public Key: 32 bytes (Ed25519 public key) 2. SHA-256 hash, truncated to first 20 bytes ``` **Implementation:** `crypto/keys/ed25519/ed25519.go` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (pubKey *PubKey) Address() crypto.Address { return crypto.Address(tmhash.SumTruncated(pubKey.Key)) // SHA-256-20 } ``` ## Bech32 Encoding Process Once address bytes are derived, they're converted to Bech32 format: **Step 1: Convert from 8-bit to 5-bit encoding** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Address bytes (20 bytes = 160 bits) addressBytes := []byte{0x12, 0x34, ..., 0xab} // 20 bytes // Convert to 5-bit groups for Bech32 converted, _ := bech32.ConvertBits(addressBytes, 8, 5, true) ``` **Step 2: Encode with Human-Readable Prefix** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Combine HRP with converted bytes bech32Address, _ := bech32.Encode("cosmos", converted) // Result: "cosmos1r5v5srda7xfth3uckstjst6k05kmeyzptewwdk" ``` **Implementation:** `types/bech32/bech32.go` ## Configuring Bech32 prefixes Every Cosmos SDK application sets its Bech32 prefixes and SLIP-44 coin type once at startup via `sdk.GetConfig()`, then seals the config so it cannot be changed at runtime. The defaults (`cosmos`, `cosmosvaloper`, etc.) are defined in [`types/config.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/config.go). Chain developers override them before the app starts: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} config := sdk.GetConfig() config.SetBech32PrefixForAccount("cosmos", "cosmospub") config.SetBech32PrefixForValidator("cosmosvaloper", "cosmosvaloperpub") config.SetBech32PrefixForConsensusNode("cosmosvalcons", "cosmosvalconspub") config.SetCoinType(118) // SLIP-44 coin type config.Seal() ``` ## Address Validation The SDK validates addresses through: 1. **Format validation**: Ensures valid Bech32 encoding 2. **Prefix validation**: Confirms correct HRP for address type 3. **Length validation**: Verifies address is exactly 20 bytes when decoded ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (bc Bech32Codec) StringToBytes(text string) ([]byte, error) { hrp, bz, err := bech32.DecodeAndConvert(text) if err != nil { return nil, err } if hrp != bc.Bech32Prefix { return nil, fmt.Errorf("invalid prefix") } return bz, sdk.VerifyAddressFormat(bz) // Checks length = 20 bytes } ``` ## Module Addresses Module accounts use deterministic address derivation defined in [ADR-028](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-028-public-key-addresses.md): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Module address without derivation keys func Module(moduleName string) []byte { return crypto.AddressHash([]byte(moduleName)) } // Module address with derivation keys (new method) func Module(moduleName string, derivationKeys ...[]byte) []byte { mKey := append([]byte(moduleName), 0) // Null byte separator addr := Hash("module", append(mKey, derivationKeys[0]...)) return addr // 32 bytes (not 20 bytes like user addresses) } ``` Module addresses are longer (32 bytes vs 20 bytes) to reduce collision probability. ## Validator Address Relationships A validator has three related addresses: 1. **Operator Address** (`cosmosvaloper1...`): The validator's operational identity, derived from the operator's account key 2. **Consensus Address** (`cosmosvalcons1...`): Derived from the validator's consensus public key (Ed25519), used for block signing 3. **Account Address** (`cosmos1...`): The operator's account for receiving rewards ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Validator stores its consensus pubkey type Validator struct { OperatorAddress string // cosmosvaloper1... (from operator's account) ConsensusPubkey *Any // Ed25519 public key for signing // ... } // Consensus address is derived from the consensus pubkey func (v Validator) GetConsAddr() ([]byte, error) { pk := v.ConsensusPubkey.GetCachedValue().(cryptotypes.PubKey) return pk.Address().Bytes(), nil // SHA-256-20 of Ed25519 pubkey } ``` ## Performance: Address Caching The SDK caches Bech32-encoded addresses to optimize repeated conversions: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ( accAddrCache *simplelru.LRU // 60,000 entries valAddrCache *simplelru.LRU // 500 entries consAddrCache *simplelru.LRU // 500 entries ) ``` When `Address.String()` is called, the SDK: 1. Checks the LRU cache for the encoded address 2. Returns cached value if found 3. Otherwise, performs Bech32 encoding and caches the result This significantly improves performance during block processing and state queries. ## Complete Example Here's the full pipeline for creating an account address: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // 1. Generate keypair privKey := secp256k1.GenPrivKey() // 32 bytes pubKey := privKey.PubKey() // 33 bytes (compressed) // 2. Derive address bytes sha := sha256.Sum256(pubKey.Bytes()) // 32 bytes ripemd := ripemd160.Sum(sha[:]) // 20 bytes addrBytes := ripemd[:] // 3. Create AccAddress type accAddr := sdk.AccAddress(addrBytes) // 4. Convert to Bech32 string // Internally: bech32.ConvertAndEncode("cosmos", addrBytes) addressStr := accAddr.String() // Result: "cosmos1r5v5srda7xfth3uckstjst6k05kmeyzptewwdk" // 5. Use in account account := auth.NewBaseAccount(accAddr, pubKey, accountNumber, sequence) ``` ## Related Concepts * [Accounts](/sdk/latest/learn/concepts/accounts) - Understanding account types and management * [Store](/sdk/latest/learn/concepts/store) - How addresses are used as keys in state storage * [Transactions](/sdk/latest/learn/concepts/transactions) - How addresses are used in transaction signing # SDK Go Packages Source: https://docs.cosmos.network/sdk/latest/guides/reference/packages The Cosmos SDK is a collection of Go modules. This section provides documentation on various packages that can be used when developing a Cosmos SDK chain. It lists all standalone Go modules that are part of the Cosmos SDK. The Cosmos SDK is a collection of Go modules. This section provides documentation on various packages that can be used when developing a Cosmos SDK chain. For more information on SDK modules, see the [SDK Modules](/sdk/latest/modules/modules) section. For more information on SDK tooling, see the [Tooling](/sdk/latest/guides/tooling/tool-guide) section. ## Core * [Core](https://pkg.go.dev/cosmossdk.io/core) - Core library defining SDK interfaces ([ADR-063](/sdk/latest/reference/architecture/adr-063-core-module-api)) * [API](https://pkg.go.dev/cosmossdk.io/api) - API library containing generated SDK Pulsar API * [Store](https://pkg.go.dev/cosmossdk.io/store) - Implementation of the Cosmos SDK store ## State Management * [Collections](https://pkg.go.dev/cosmossdk.io/collections) - Typed state management library with automatic key encoding, iteration, and secondary indexes. See the [Collections guide](/sdk/latest/guides/state/collections). * [ORM](https://pkg.go.dev/cosmossdk.io/orm) - ORM-style state layer built on top of collections, providing table abstractions with primary and secondary indexes. Based on [ADR-055](/sdk/latest/reference/architecture/adr-055-orm). ## Automation * [Client/v2](https://pkg.go.dev/cosmossdk.io/client/v2) - Library powering [AutoCLI](/sdk/latest/guides/tooling/autocli) ## Transactions * [x/tx](https://pkg.go.dev/cosmossdk.io/x/tx) - Transaction signing types, sign mode implementations (direct, amino JSON, textual), and transaction decoder utilities. ## Utilities * [Log](https://pkg.go.dev/cosmossdk.io/log) - Logging library * [Errors](https://pkg.go.dev/cosmossdk.io/errors) - Error handling library * [Math](https://pkg.go.dev/cosmossdk.io/math) - Math library for SDK arithmetic operations ## SimApp * [SimApp](https://pkg.go.dev/cosmossdk.io/simapp) - SimApp is a sample Cosmos SDK chain used for testing and development. # Cosmos Protobuf Docs Source: https://docs.cosmos.network/sdk/latest/guides/reference/proto-docs # Protobuf Annotations Source: https://docs.cosmos.network/sdk/latest/guides/reference/protobuf-annotations This document explains the various protobuf scalars that have been added to make working with protobuf easier for Cosmos SDK application developers This document explains the various protobuf scalars that have been added to make working with protobuf easier for Cosmos SDK application developers ### Gogoproto Modules are encouraged to utilize Protobuf encoding for their respective types. In the Cosmos SDK, we use the [Gogoproto](https://github.com/cosmos/gogoproto) specific implementation of the Protobuf spec that offers speed and developer experience improvements compared to the official [Google protobuf implementation](https://github.com/protocolbuffers/protobuf). ### Guidelines for protobuf message definitions In addition to [following official Protocol Buffer guidelines](https://developers.google.com/protocol-buffers/docs/proto3#simple), we recommend using these annotations in `.proto` files when dealing with interfaces: * Use `cosmos_proto.accepts_interface` to annotate `Any` fields that accept interfaces: * Pass the same fully qualified name as `protoName` to `InterfaceRegistry.RegisterInterface`. * Example: `(cosmos_proto.accepts_interface) = "cosmos.gov.v1beta1.Content"` (not just `Content`). * Annotate interface implementations with `cosmos_proto.implements_interface`: * Pass the same fully qualified name as `protoName` to `InterfaceRegistry.RegisterInterface`. * Example: `(cosmos_proto.implements_interface) = "cosmos.authz.v1beta1.Authorization"` (not just `Authorization`). Code generators can then match the `accepts_interface` and `implements_interface` annotations to determine whether some Protobuf messages are allowed to be packed in a given `Any` field. ## Signer Signer specifies which field should be used to determine the signer of a message for the Cosmos SDK. This field can be used for clients as well to infer which field should be used to determine the signer of a message. Read more about the signer field [here](/sdk/latest/learn/concepts/encoding#message-signers). ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L40 option (cosmos.msg.v1.signer) = "from_address"; ``` ## Scalar The scalar type defines a way for clients to understand how to construct protobuf messages according to what is expected by the module and sdk. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} (cosmos_proto.scalar) = "cosmos.AddressString" ``` Example of account address string scalar: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L46 string from_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; ``` Example of validator address string scalar: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/query.proto#L107 string validator_address = 1 [(cosmos_proto.scalar) = "cosmos.ValidatorAddressString"]; ``` Example of Dec scalar: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/distribution.proto#L17 string community_tax = 1 [(cosmos_proto.scalar) = "cosmos.Dec"]; ``` Example of Int scalar: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/gov.proto#L127 string yes_count = 1 [(cosmos_proto.scalar) = "cosmos.Int"]; ``` There are a few options for what can be provided as a scalar: `cosmos.AddressString`, `cosmos.ValidatorAddressString`, `cosmos.ConsensusAddressString`, `cosmos.Int`, `cosmos.Dec`. ## Implements\_Interface Implement interface is used to provide information to client tooling like [telescope](https://github.com/cosmology-tech/telescope) on how to encode and decode protobuf messages. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} option (cosmos_proto.implements_interface) = "cosmos.auth.v1beta1.AccountI"; ``` ## Method,Field,Message Added In `method_added_in`, `field_added_in` and `message_added_in` are annotations to indicate to clients that a method, field, or message has been supported since a later version. This is useful when new methods or fields are added in later versions and the client needs to be aware of what it can call. The annotations are used as follows: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} option (cosmos_proto.method_added_in) = "cosmos-sdk 0.50.1"; option (cosmos_proto.field_added_in) = "cosmos-sdk 0.50.1"; option (cosmos_proto.message_added_in) = "cosmos-sdk 0.50.1"; ``` ## Amino The amino codec was removed in `v0.50+`, this means there is not a need register `legacyAminoCodec`. To replace the amino codec, Amino protobuf annotations are used to provide information to the amino codec on how to encode and decode protobuf messages. Amino annotations are only used for backwards compatibility with amino. New modules are not required use amino annotations. The below annotations are used to provide information to the amino codec on how to encode and decode protobuf messages in a backwards compatible manner. ### Name Name specifies the amino name that would show up for the user in order for them see which message they are signing. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L41 option (amino.name) = "cosmos-sdk/MsgSend"; ``` ### Field\_Name Field name specifies the amino name that would show up for the user in order for them see which field they are signing. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/distribution.proto#L165 uint64 height = 3 [(amino.field_name) = "creation_height"]; ``` ### Dont\_OmitEmpty Dont omitempty specifies that the field should not be omitted when encoding to amino. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L48 repeated cosmos.base.v1beta1.Coin amount = 3 [(amino.dont_omitempty) = true]; ``` ### Encoding Encoding instructs the amino json marshaler how to encode certain fields that may differ from the standard encoding behavior. The most common example of this is how `repeated cosmos.base.v1beta1.Coin` is encoded when using the amino json encoding format. The `legacy_coins` option tells the json marshaler [how to encode a null slice](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/tx/signing/aminojson/json_marshal.go#L85) of `cosmos.base.v1beta1.Coin`. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/genesis.proto#L23 (amino.encoding) = "legacy_coins", ``` ## Module Query Safe The `cosmos.query.v1.module_query_safe` annotation ([source](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/query/v1/query.proto)) marks a query method as safe to call from within the state machine — for example from another module's keeper, via ADR-033 intermodule calls, or from CosmWasm contracts. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) { option (cosmos.query.v1.module_query_safe) = true; } ``` When set to `true`, the annotation asserts that the query is: 1. **Deterministic**: given a block height, it returns the exact same response on every call and does not introduce state-machine-breaking changes across SDK patch versions. 2. **Gas-tracked**: gas consumption is correctly accounted for, preventing attack vectors where high-computation queries consume no gas. If you add this annotation to your own query, you must ensure both conditions hold. For queries that may consume significant gas (for example those with pagination that could be misconfigured), add a Protobuf comment warning downstream module developers. This annotation was introduced in v0.47. # Collections API Source: https://docs.cosmos.network/sdk/latest/guides/state/collections Collections is a library meant to simplify the experience with respect to module state handling. Collections is a library meant to simplify the experience with respect to module state handling. Cosmos SDK modules handle their state using the `KVStore` interface. The problem with working with `KVStore` is that it forces you to think of state as a bytes KV pairings when in reality the majority of state comes from complex concrete golang objects (strings, ints, structs, etc.). Collections allows you to work with state as if they were normal golang objects and removes the need for you to think of your state as raw bytes in your code. It also allows you to migrate your existing state without causing any state breakage that forces you into tedious and complex chain state migrations. ## Installation To install collections in your cosmos-sdk chain project, run the following command: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go get cosmossdk.io/collections ``` ## Core types Collections offers 5 different APIs to work with state, which will be explored in the next sections, these APIs are: * `Map`: to work with typed arbitrary KV pairings. * `KeySet`: to work with just typed keys * `Item`: to work with just one typed value * `Sequence`: which is a monotonically increasing number. * `IndexedMap`: which combines `Map` and `KeySet` to provide a `Map` with indexing capabilities. ## Preliminary components Before exploring the different collections types and their capability it is necessary to introduce the three components that every collection shares. In fact when instantiating a collection type by doing, for example, `collections.NewMap/collections.NewItem/...` you will find yourself having to pass them some common arguments. For example, in code: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" ) var AllowListPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema AllowList collections.KeySet[string] } func NewKeeper(storeService store.KVStoreService) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ AllowList: collections.NewKeySet(sb, AllowListPrefix, "allow_list", collections.StringKey), } } ``` Let's analyze the shared arguments, what they do, and why we need them. ### SchemaBuilder The first argument passed is the `SchemaBuilder` `SchemaBuilder` is a structure that keeps track of all the state of a module, it is not required by the collections to deal with state but it offers a dynamic and reflective way for clients to explore a module's state. We instantiate a `SchemaBuilder` by passing it a `store.KVStoreService`, which is the module's store service obtained via dependency injection or `runtime.NewKVStoreService`. We then need to pass the schema builder to every collection type we instantiate in our keeper, in our case the `AllowList`. After creating all collections, call `sb.Build()` to validate prefix uniqueness and finalize the schema. Store the returned `collections.Schema` in the keeper's `Schema` field: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} k := Keeper{ AllowList: collections.NewKeySet(sb, AllowListPrefix, "allow_list", collections.StringKey), } schema, err := sb.Build() if err != nil { panic(err) } k.Schema = schema return k ``` The code examples in this document show the collection instantiation patterns but omit the `sb.Build()` call for brevity. In production code, `sb.Build()` is required. ### Prefix The second argument passed to our `KeySet` is a `collections.Prefix`, a prefix represents a partition of the module's `KVStore` where all the state of a specific collection will be saved. Since a module can have multiple collections, the following is expected: * module params will become a `collections.Item` * the `AllowList` is a `collections.KeySet` We don't want a collection to write over the state of the other collection so we pass it a prefix, which defines a storage partition owned by the collection. If you already built modules, the prefix translates to the items you were creating in your `types/keys.go` file, example: [Link](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/feegrant/key.go#L16-L22) your old: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ( // FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data // - 0x00: allowance FeeAllowanceKeyPrefix = []byte{0x00 } // FeeAllowanceQueueKeyPrefix is the set of the kvstore for fee allowance keys data // - 0x01: FeeAllowanceQueueKeyPrefix = []byte{0x01 } ) ``` becomes: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ( // FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data // - 0x00: allowance FeeAllowanceKeyPrefix = collections.NewPrefix(0) // FeeAllowanceQueueKeyPrefix is the set of the kvstore for fee allowance keys data // - 0x01: FeeAllowanceQueueKeyPrefix = collections.NewPrefix(1) ) ``` #### Rules `collections.NewPrefix` accepts either `int`, `string` or `[]byte`. It is good practice to use a monotonically increasing `int` (values 0–255) for disk space efficiency. A collection **MUST NOT** share the same prefix as another collection in the same module, and a collection prefix **MUST NEVER** start with the same prefix as another, examples: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} prefix1 := collections.NewPrefix("prefix") prefix2 := collections.NewPrefix("prefix") // THIS IS BAD! ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} prefix1 := collections.NewPrefix("a") prefix2 := collections.NewPrefix("aa") // prefix2 starts with the same as prefix1: BAD!!! ``` ### Human-Readable Name The third parameter we pass to a collection is a string, which is a human-readable name. It is needed to make the role of a collection understandable by clients who have no clue about what a module is storing in state. #### Rules Each collection in a module **MUST** have a unique humanized name. ## Key and Value Codecs A collection is generic over the type you can use as keys or values. This makes collections dumb, but also means that hypothetically we can store everything that can be a go type into a collection. We are not bounded to any type of encoding (be it proto, json or whatever) So a collection needs to be given a way to understand how to convert your keys and values to bytes. This is achieved through `KeyCodec` and `ValueCodec`, which are arguments that you pass to your collections when you're instantiating them using the `collections.NewMap/collections.NewItem/...` instantiation functions. NOTE: Generally speaking you will never be required to implement your own `Key/ValueCodec` as the SDK and collections libraries already come with default, safe and fast implementation of those. You might need to implement them only if you're migrating to collections and there are state layout incompatibilities. Let's explore an example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" sdk "github.com/cosmos/cosmos-sdk/types" ) var IDsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema IDs collections.Map[string, uint64] } func NewKeeper(storeService store.KVStoreService) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ IDs: collections.NewMap(sb, IDsPrefix, "ids", collections.StringKey, collections.Uint64Value), } } ``` We're now instantiating a map where the key is string and the value is `uint64`. We already know the first three arguments of the `NewMap` function. The fourth parameter is our `KeyCodec`, we know that the `Map` has `string` as key so we pass it a `KeyCodec` that handles strings as keys. The fifth parameter is our `ValueCodec`, we know that the `Map` has a `uint64` as value so we pass it a `ValueCodec` that handles uint64. Collections already comes with all the required implementations for golang primitive types. Let's make another example, this falls closer to what we build using cosmos SDK, let's say we want to create a `collections.Map` that maps account addresses to their base account. So we want to map an `sdk.AccAddress` to an `auth.BaseAccount` (which is a proto): ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) var AccountsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Accounts collections.Map[sdk.AccAddress, authtypes.BaseAccount] } func NewKeeper(storeService store.KVStoreService, cdc codec.BinaryCodec) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Accounts: collections.NewMap(sb, AccountsPrefix, "accounts", sdk.AccAddressKey, codec.CollValue[authtypes.BaseAccount](cdc)), } } ``` As we can see here since our `collections.Map` maps `sdk.AccAddress` to `authtypes.BaseAccount`, we use the `sdk.AccAddressKey` which is the `KeyCodec` implementation for `AccAddress` and we use `codec.CollValue` to encode our proto type `BaseAccount`. Generally speaking you will always find the respective key and value codecs for types in the `go.mod` path you're using to import that type. If you want to encode proto values refer to the codec `codec.CollValue` function, which allows you to encode any type implement the `proto.Message` interface. ## Map We analyze the first and most important collection type, the `collections.Map`. This is the type that everything else builds on top of. ### Use case A `collections.Map` is used to map arbitrary keys with arbitrary values. ### Example It's easier to explain a `collections.Map` capabilities through an example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" "fmt" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) var AccountsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Accounts collections.Map[sdk.AccAddress, authtypes.BaseAccount] } func NewKeeper(storeService store.KVStoreService, cdc codec.BinaryCodec) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Accounts: collections.NewMap(sb, AccountsPrefix, "accounts", sdk.AccAddressKey, codec.CollValue[authtypes.BaseAccount](cdc)), } } func (k Keeper) CreateAccount(ctx sdk.Context, addr sdk.AccAddress, account authtypes.BaseAccount) error { has, err := k.Accounts.Has(ctx, addr) if err != nil { return err } if has { return fmt.Errorf("account already exists: %s", addr) } err = k.Accounts.Set(ctx, addr, account) if err != nil { return err } return nil } func (k Keeper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) (authtypes.BaseAccount, error) { acc, err := k.Accounts.Get(ctx, addr) if err != nil { return authtypes.BaseAccount{ }, err } return acc, nil } func (k Keeper) RemoveAccount(ctx sdk.Context, addr sdk.AccAddress) error { err := k.Accounts.Remove(ctx, addr) if err != nil { return err } return nil } ``` #### Set method Set maps with the provided `AccAddress` (the key) to the `auth.BaseAccount` (the value). Under the hood the `collections.Map` will convert the key and value to bytes using the [key and value codec](#key-and-value-codecs). It will prepend to our bytes key the [prefix](#prefix) and store it in the KVStore of the module. #### Has method The has method reports if the provided key exists in the store. #### Get method The get method accepts the `AccAddress` and returns the associated `auth.BaseAccount` if it exists, otherwise it errors. #### Remove method The remove method accepts the `AccAddress` and removes it from the store. It won't report errors if it does not exist, to check for existence before removal use the `Has` method. #### Iteration Iteration has a separate section. ## KeySet The second type of collection is `collections.KeySet`, as the word suggests it maintains only a set of keys without values. #### Implementation curiosity A `collections.KeySet` is just a `collections.Map` with a `key` but no value. The value internally is always the same and is represented as an empty byte slice `[]byte{}`. ### Example As always we explore the collection type through an example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) var ValidatorsSetPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema ValidatorsSet collections.KeySet[sdk.ValAddress] } func NewKeeper(storeService store.KVStoreService) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ ValidatorsSet: collections.NewKeySet(sb, ValidatorsSetPrefix, "validators_set", sdk.ValAddressKey), } } func (k Keeper) AddValidator(ctx sdk.Context, validator sdk.ValAddress) error { has, err := k.ValidatorsSet.Has(ctx, validator) if err != nil { return err } if has { return fmt.Errorf("validator already in set: %s", validator) } err = k.ValidatorsSet.Set(ctx, validator) if err != nil { return err } return nil } func (k Keeper) RemoveValidator(ctx sdk.Context, validator sdk.ValAddress) error { err := k.ValidatorsSet.Remove(ctx, validator) if err != nil { return err } return nil } ``` The first difference we notice is that `KeySet` needs use to specify only one type parameter: the key (`sdk.ValAddress` in this case). The second difference we notice is that `KeySet` in its `NewKeySet` function does not require us to specify a `ValueCodec` but only a `KeyCodec`. This is because a `KeySet` only saves keys and not values. Let's explore the methods. #### Has method Has allows us to understand if a key is present in the `collections.KeySet` or not, functions in the same way as `collections.Map.Has ` #### Set method Set inserts the provided key in the `KeySet`. #### Remove method Remove removes the provided key from the `KeySet`, it does not error if the key does not exist, if existence check before removal is required it needs to be coupled with the `Has` method. ## Item The third type of collection is the `collections.Item`. It stores only one single item, it's useful for example for parameters, there's only one instance of parameters in state always. #### implementation curiosity A `collections.Item` is just a `collections.Map` with no key but just a value. The key is the prefix of the collection! ### Example ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "cosmossdk.io/x/staking/types" ) var ParamsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Params collections.Item[stakingtypes.Params] } func NewKeeper(storeService store.KVStoreService, cdc codec.BinaryCodec) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Params: collections.NewItem(sb, ParamsPrefix, "params", codec.CollValue[stakingtypes.Params](cdc)), } } func (k Keeper) UpdateParams(ctx sdk.Context, params stakingtypes.Params) error { err := k.Params.Set(ctx, params) if err != nil { return err } return nil } func (k Keeper) GetParams(ctx sdk.Context) (stakingtypes.Params, error) { return k.Params.Get(ctx) } ``` The first key difference we notice is that we specify only one type parameter, which is the value we're storing. The second key difference is that we don't specify the `KeyCodec`, since we store only one item we already know the key and the fact that it is constant. ## Iteration One of the key features of the `KVStore` is iterating over keys. Collections which deal with keys (so `Map`, `KeySet` and `IndexedMap`) allow you to iterate over keys in a safe and typed way. They all share the same API, the only difference being that `KeySet` returns a different type of `Iterator` because `KeySet` only deals with keys. Every collection shares the same `Iterator` semantics. Let's have a look at the `Map.Iterate` method: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (m Map[K, V]) Iterate(ctx context.Context, ranger Ranger[K]) (Iterator[K, V], error) ``` It accepts a `collections.Ranger[K]`, which is an API that instructs map on how to iterate over keys. As always we don't need to implement anything here as `collections` already provides some generic `Ranger` implementers that expose all you need to work with ranges. ### Example We have a `collections.Map` that maps accounts using `uint64` IDs. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) var AccountsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Accounts collections.Map[uint64, authtypes.BaseAccount] } func NewKeeper(storeService store.KVStoreService, cdc codec.BinaryCodec) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Accounts: collections.NewMap(sb, AccountsPrefix, "accounts", collections.Uint64Key, codec.CollValue[authtypes.BaseAccount](cdc)), } } func (k Keeper) GetAllAccounts(ctx sdk.Context) ([]authtypes.BaseAccount, error) { // passing a nil Ranger equals to: iterate over every possible key iter, err := k.Accounts.Iterate(ctx, nil) if err != nil { return nil, err } accounts, err := iter.Values() if err != nil { return nil, err } return accounts, err } func (k Keeper) IterateAccountsBetween(ctx sdk.Context, start, end uint64) ([]authtypes.BaseAccount, error) { // The collections.Range API offers a lot of capabilities // like defining where the iteration starts or ends. rng := new(collections.Range[uint64]). StartInclusive(start). EndExclusive(end). Descending() iter, err := k.Accounts.Iterate(ctx, rng) if err != nil { return nil, err } accounts, err := iter.Values() if err != nil { return nil, err } return accounts, nil } func (k Keeper) IterateAccounts(ctx sdk.Context, do func(id uint64, acc authtypes.BaseAccount) (stop bool)) error { iter, err := k.Accounts.Iterate(ctx, nil) if err != nil { return err } defer iter.Close() for ; iter.Valid(); iter.Next() { kv, err := iter.KeyValue() if err != nil { return err } if do(kv.Key, kv.Value) { break } } return nil } ``` Let's analyze each method in the example and how it makes use of the `Iterate` and the returned `Iterator` API. #### GetAllAccounts In `GetAllAccounts` we pass to our `Iterate` a nil `Ranger`. This means that the returned `Iterator` will include all the existing keys within the collection. Then we use the `Values` method from the returned `Iterator` API to collect all the values into a slice. `Iterator` offers other methods such as `Keys()` to collect only the keys and not the values and `KeyValues` to collect all the keys and values. #### IterateAccountsBetween Here we make use of the `collections.Range` helper to specialize our range. We make it start in a point through `StartInclusive` and end in the other with `EndExclusive`, then we instruct it to report us results in reverse order through `Descending` Then we pass the range instruction to `Iterate` and get an `Iterator`, which will contain only the results we specified in the range. Then we use again the `Values` method of the `Iterator` to collect all the results. `collections.Range` also offers a `Prefix` API which is not applicable to all keys types, for example uint64 cannot be prefix because it is of constant size, but a `string` key can be prefixed. #### IterateAccounts Here we showcase how to lazily collect values from an Iterator. `Keys/Values/KeyValues` fully consume and close the `Iterator`, here we need to explicitly do a `defer iterator.Close()` call. `Iterator` also exposes a `Value` and `Key` method to collect only the current value or key, if collecting both is not needed. For this `callback` pattern, collections expose a `Walk` API. ## Composite keys So far we've worked only with simple keys, like `uint64`, the account address, etc. There are some more complex cases in, which we need to deal with composite keys. A key is composite when it is composed of multiple keys, for example bank balances as stored as the composite key `(AccAddress, string)` where the first part is the address holding the coins and the second part is the denom. Example, let's say address `BOB` holds `10atom,15osmo`, this is how it is stored in state: ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} (bob, atom) => 10 (bob, osmos) => 15 ``` Now this allows to efficiently get a specific denom balance of an address, by simply `getting` `(address, denom)`, or getting all the balances of an address by prefixing over `(address)`. Let's see now how we can work with composite keys using collections. ### Example In our example we will showcase how we can use collections when we are dealing with balances, similar to bank, a balance is a mapping between `(address, denom) => math.Int` the composite key in our case is `(address, denom)`. ## Instantiation of a composite key collection ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package collections import ( "cosmossdk.io/collections" "cosmossdk.io/math" store "cosmossdk.io/core/store" sdk "github.com/cosmos/cosmos-sdk/types" ) var BalancesPrefix = collections.NewPrefix(1) type Keeper struct { Schema collections.Schema Balances collections.Map[collections.Pair[sdk.AccAddress, string], math.Int] } func NewKeeper(storeService store.KVStoreService) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Balances: collections.NewMap( sb, BalancesPrefix, "balances", collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), sdk.IntValue, ), } } ``` #### The Map Key definition First of all we can see that in order to define a composite key of two elements we use the `collections.Pair` type: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} collections.Map[collections.Pair[sdk.AccAddress, string], math.Int] ``` `collections.Pair` defines a key composed of two other keys, in our case the first part is `sdk.AccAddress`, the second part is `string`. #### The Key Codec instantiation The arguments to instantiate are always the same, the only thing that changes is how we instantiate the `KeyCodec`, since this key is composed of two keys we use `collections.PairKeyCodec`, which generates a `KeyCodec` composed of two key codecs. The first one will encode the first part of the key, the second one will encode the second part of the key. ### Working with composite key collections Let's expand on the example we used before: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var BalancesPrefix = collections.NewPrefix(1) type Keeper struct { Schema collections.Schema Balances collections.Map[collections.Pair[sdk.AccAddress, string], math.Int] } func NewKeeper(storeService store.KVStoreService) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Balances: collections.NewMap( sb, BalancesPrefix, "balances", collections.PairKeyCodec(sdk.AccAddressKey, collections.StringKey), sdk.IntValue, ), } } func (k Keeper) SetBalance(ctx sdk.Context, address sdk.AccAddress, denom string, amount math.Int) error { key := collections.Join(address, denom) return k.Balances.Set(ctx, key, amount) } func (k Keeper) GetBalance(ctx sdk.Context, address sdk.AccAddress, denom string) (math.Int, error) { return k.Balances.Get(ctx, collections.Join(address, denom)) } func (k Keeper) GetAllAddressBalances(ctx sdk.Context, address sdk.AccAddress) (sdk.Coins, error) { balances := sdk.NewCoins() rng := collections.NewPrefixedPairRange[sdk.AccAddress, string](address) iter, err := k.Balances.Iterate(ctx, rng) if err != nil { return nil, err } kvs, err := iter.KeyValues() if err != nil { return nil, err } for _, kv := range kvs { balances = balances.Add(sdk.NewCoin(kv.Key.K2(), kv.Value)) } return balances, nil } func (k Keeper) GetAllAddressBalancesBetween(ctx sdk.Context, address sdk.AccAddress, startDenom, endDenom string) (sdk.Coins, error) { rng := collections.NewPrefixedPairRange[sdk.AccAddress, string](address). StartInclusive(startDenom). EndInclusive(endDenom) iter, err := k.Balances.Iterate(ctx, rng) if err != nil { return nil, err } ... } ``` #### SetBalance As we can see here we're setting the balance of an address for a specific denom. We use the `collections.Join` function to generate the composite key. `collections.Join` returns a `collections.Pair` (which is the key of our `collections.Map`) `collections.Pair` contains the two keys we have joined, it also exposes two methods: `K1` to fetch the 1st part of the key and `K2` to fetch the second part. As always, we use the `collections.Map.Set` method to map the composite key to our value (`math.Int` in this case) #### GetBalance To get a value in composite key collection, we simply use `collections.Join` to compose the key. #### GetAllAddressBalances We use `collections.PrefixedPairRange` to iterate over all the keys starting with the provided address. Concretely the iteration will report all the balances belonging to the provided address. The first part is that we instantiate a `PrefixedPairRange`, which is a `Ranger` implementer aimed to help in `Pair` keys iterations. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rng := collections.NewPrefixedPairRange[sdk.AccAddress, string](address) ``` As we can see here we're passing the type parameters of the `collections.Pair` because golang type inference with respect to generics is not as permissive as other languages, so we need to explicitly say what are the types of the pair key. #### GetAllAddressesBalancesBetween This showcases how we can further specialize our range to limit the results further, by specifying the range between the second part of the key (in our case the denoms, which are strings). ## IndexedMap `collections.IndexedMap` is a collection that uses under the hood a `collections.Map`, and has a struct, which contains the indexes that we need to define. ### Example Let's say we have an `auth.BaseAccount` struct which looks like the following: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type BaseAccount struct { AccountNumber uint64 `protobuf:"varint,3,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` Sequence uint64 `protobuf:"varint,4,opt,name=sequence,proto3" json:"sequence,omitempty"` } ``` First of all, when we save our accounts in state we map them using a primary key `sdk.AccAddress`. If it were to be a `collections.Map` it would be `collections.Map[sdk.AccAddress, authtypes.BaseAccount]`. Then we also want to be able to get an account not only by its `sdk.AccAddress`, but also by its `AccountNumber`. So we can say we want to create an `Index` that maps our `BaseAccount` to its `AccountNumber`. We also know that this `Index` is unique. Unique means that there can only be one `BaseAccount` that maps to a specific `AccountNumber`. First of all, we start by defining the object that contains our index: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var AccountsNumberIndexPrefix = collections.NewPrefix(1) type AccountsIndexes struct { Number *indexes.Unique[uint64, sdk.AccAddress, authtypes.BaseAccount] } func NewAccountIndexes(sb *collections.SchemaBuilder) AccountsIndexes { return AccountsIndexes{ Number: indexes.NewUnique( sb, AccountsNumberIndexPrefix, "accounts_by_number", collections.Uint64Key, sdk.AccAddressKey, func(_ sdk.AccAddress, v authtypes.BaseAccount) (uint64, error) { return v.AccountNumber, nil }, ), } } ``` We create an `AccountIndexes` struct which contains a field: `Number`. This field represents our `AccountNumber` index. `AccountNumber` is a field of `authtypes.BaseAccount` and it's a `uint64`. Then we can see in our `AccountIndexes` struct the `Number` field is defined as: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} *indexes.Unique[uint64, sdk.AccAddress, authtypes.BaseAccount] ``` Where the first type parameter is `uint64`, which is the field type of our index. The second type parameter is the primary key `sdk.AccAddress`. And the third type parameter is the actual object we're storing `authtypes.BaseAccount`. Then we create a `NewAccountIndexes` function that instantiates and returns the `AccountsIndexes` struct. The function takes a `SchemaBuilder`. Then we instantiate our `indexes.Unique`, let's analyze the arguments we pass to `indexes.NewUnique`. #### NOTE: indexes list The `AccountsIndexes` struct contains the indexes, the `NewIndexedMap` function will infer the indexes form that struct using reflection, this happens only at init and is not computationally expensive. In case you want to explicitly declare indexes: implement the `Indexes` interface in the `AccountsIndexes` struct: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, authtypes.BaseAccount] { return []collections.Index[sdk.AccAddress, authtypes.BaseAccount]{ a.Number } } ``` #### Instantiating a `indexes.Unique` The first three arguments, we already know them, they are: `SchemaBuilder`, `Prefix` which is our index prefix (the partition where index keys relationship for the `Number` index will be maintained), and the human name for the `Number` index. The second argument is a `collections.Uint64Key` which is a key codec to deal with `uint64` keys, we pass that because the key we're trying to index is a `uint64` key (the account number), and then we pass as fifth argument the primary key codec, which in our case is `sdk.AccAddress` (remember: we're mapping `sdk.AccAddress` => `BaseAccount`). Then as last parameter we pass a function that: given the `BaseAccount` returns its `AccountNumber`. After this we can proceed instantiating our `IndexedMap`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var AccountsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Accounts *collections.IndexedMap[sdk.AccAddress, authtypes.BaseAccount, AccountsIndexes] } func NewKeeper(storeService store.KVStoreService, cdc codec.BinaryCodec) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Accounts: collections.NewIndexedMap( sb, AccountsPrefix, "accounts", sdk.AccAddressKey, codec.CollValue[authtypes.BaseAccount](cdc), NewAccountIndexes(sb), ), } } ``` As we can see here what we do, for now, is the same thing as we did for `collections.Map`. We pass it the `SchemaBuilder`, the `Prefix` where we plan to store the mapping between `sdk.AccAddress` and `authtypes.BaseAccount`, the human name and the respective `sdk.AccAddress` key codec and `authtypes.BaseAccount` value codec. Then we pass the instantiation of our `AccountIndexes` through `NewAccountIndexes`. Full example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package docs import ( "cosmossdk.io/collections" "cosmossdk.io/collections/indexes" store "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) var AccountsNumberIndexPrefix = collections.NewPrefix(1) type AccountsIndexes struct { Number *indexes.Unique[uint64, sdk.AccAddress, authtypes.BaseAccount] } func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, authtypes.BaseAccount] { return []collections.Index[sdk.AccAddress, authtypes.BaseAccount]{ a.Number } } func NewAccountIndexes(sb *collections.SchemaBuilder) AccountsIndexes { return AccountsIndexes{ Number: indexes.NewUnique( sb, AccountsNumberIndexPrefix, "accounts_by_number", collections.Uint64Key, sdk.AccAddressKey, func(_ sdk.AccAddress, v authtypes.BaseAccount) (uint64, error) { return v.AccountNumber, nil }, ), } } var AccountsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Accounts *collections.IndexedMap[sdk.AccAddress, authtypes.BaseAccount, AccountsIndexes] } func NewKeeper(storeService store.KVStoreService, cdc codec.BinaryCodec) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Accounts: collections.NewIndexedMap( sb, AccountsPrefix, "accounts", sdk.AccAddressKey, codec.CollValue[authtypes.BaseAccount](cdc), NewAccountIndexes(sb), ), } } ``` ### Working with IndexedMaps While instantiating `collections.IndexedMap` is tedious, working with them is extremely smooth. Let's take the full example, and expand it with some use-cases. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package docs import ( "cosmossdk.io/collections" "cosmossdk.io/collections/indexes" store "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) var AccountsNumberIndexPrefix = collections.NewPrefix(1) type AccountsIndexes struct { Number *indexes.Unique[uint64, sdk.AccAddress, authtypes.BaseAccount] } func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, authtypes.BaseAccount] { return []collections.Index[sdk.AccAddress, authtypes.BaseAccount]{ a.Number } } func NewAccountIndexes(sb *collections.SchemaBuilder) AccountsIndexes { return AccountsIndexes{ Number: indexes.NewUnique( sb, AccountsNumberIndexPrefix, "accounts_by_number", collections.Uint64Key, sdk.AccAddressKey, func(_ sdk.AccAddress, v authtypes.BaseAccount) (uint64, error) { return v.AccountNumber, nil }, ), } } var AccountsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Accounts *collections.IndexedMap[sdk.AccAddress, authtypes.BaseAccount, AccountsIndexes] } func NewKeeper(storeService store.KVStoreService, cdc codec.BinaryCodec) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Accounts: collections.NewIndexedMap( sb, AccountsPrefix, "accounts", sdk.AccAddressKey, codec.CollValue[authtypes.BaseAccount](cdc), NewAccountIndexes(sb), ), } } func (k Keeper) CreateAccount(ctx sdk.Context, addr sdk.AccAddress) error { nextAccountNumber := k.getNextAccountNumber() newAcc := authtypes.BaseAccount{ AccountNumber: nextAccountNumber, Sequence: 0, } return k.Accounts.Set(ctx, addr, newAcc) } func (k Keeper) RemoveAccount(ctx sdk.Context, addr sdk.AccAddress) error { return k.Accounts.Remove(ctx, addr) } func (k Keeper) GetAccountByNumber(ctx sdk.Context, accNumber uint64) (sdk.AccAddress, authtypes.BaseAccount, error) { accAddress, err := k.Accounts.Indexes.Number.MatchExact(ctx, accNumber) if err != nil { return nil, authtypes.BaseAccount{ }, err } acc, err := k.Accounts.Get(ctx, accAddress) return accAddress, acc, nil } func (k Keeper) GetAccountsByNumber(ctx sdk.Context, startAccNum, endAccNum uint64) ([]authtypes.BaseAccount, error) { rng := new(collections.Range[uint64]). StartInclusive(startAccNum). EndInclusive(endAccNum) iter, err := k.Accounts.Indexes.Number.Iterate(ctx, rng) if err != nil { return nil, err } return indexes.CollectValues(ctx, k.Accounts, iter) } func (k Keeper) getNextAccountNumber() uint64 { return 0 } ``` ## Collections with interfaces as values Although cosmos-sdk is shifting away from the usage of interface registry, there are still some places where it is used. In order to support old code, we have to support collections with interface values. The generic `codec.CollValue` is not able to handle interface values, so we need to use a special type `codec.CollInterfaceValue`. `codec.CollInterfaceValue` takes a `codec.BinaryCodec` as an argument, and uses it to marshal and unmarshal values as interfaces. The `codec.CollInterfaceValue` lives in the `codec` package, whose import path is `github.com/cosmos/cosmos-sdk/codec`. ### Instantiating Collections with interface values In order to instantiate a collection with interface values, we need to use `codec.CollInterfaceValue` instead of `codec.CollValue`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package example import ( "cosmossdk.io/collections" store "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) var AccountsPrefix = collections.NewPrefix(0) type Keeper struct { Schema collections.Schema Accounts *collections.Map[sdk.AccAddress, sdk.AccountI] } func NewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Accounts: collections.NewMap( sb, AccountsPrefix, "accounts", sdk.AccAddressKey, codec.CollInterfaceValue[sdk.AccountI](cdc), ), } } func (k Keeper) SaveBaseAccount(ctx sdk.Context, account authtypes.BaseAccount) error { return k.Accounts.Set(ctx, account.GetAddress(), account) } func (k Keeper) SaveModuleAccount(ctx sdk.Context, account authtypes.ModuleAccount) error { return k.Accounts.Set(ctx, account.GetAddress(), account) } func (k Keeper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) (sdk.AccountI, error) { return k.Accounts.Get(ctx, addr) } ``` ## Triple key The `collections.Triple` is a special type of key composed of three keys, it's identical to `collections.Pair`. Let's see an example. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package example import ( "context" "cosmossdk.io/collections" store "cosmossdk.io/core/store" ) type AccAddress = string type ValAddress = string type Keeper struct { // let's simulate we have redelegations which are stored as a triple key composed of // the delegator, the source validator and the destination validator. Redelegations collections.KeySet[collections.Triple[AccAddress, ValAddress, ValAddress]] } func NewKeeper(storeService store.KVStoreService) Keeper { sb := collections.NewSchemaBuilder(storeService) return Keeper{ Redelegations: collections.NewKeySet(sb, collections.NewPrefix(0), "redelegations", collections.TripleKeyCodec(collections.StringKey, collections.StringKey, collections.StringKey) } } // RedelegationsByDelegator iterates over all the redelegations of a given delegator and calls onResult providing // each redelegation from source validator towards the destination validator. func (k Keeper) RedelegationsByDelegator(ctx context.Context, delegator AccAddress, onResult func(src, dst ValAddress) (stop bool, err error)) error { rng := collections.NewPrefixedTripleRange[AccAddress, ValAddress, ValAddress](delegator) return k.Redelegations.Walk(ctx, rng, func(key collections.Triple[AccAddress, ValAddress, ValAddress]) (stop bool, err error) { return onResult(key.K2(), key.K3()) }) } // RedelegationsByDelegatorAndValidator iterates over all the redelegations of a given delegator and its source validator and calls onResult for each // destination validator. func (k Keeper) RedelegationsByDelegatorAndValidator(ctx context.Context, delegator AccAddress, validator ValAddress, onResult func(dst ValAddress) (stop bool, err error)) error { rng := collections.NewSuperPrefixedTripleRange[AccAddress, ValAddress, ValAddress](delegator, validator) return k.Redelegations.Walk(ctx, rng, func(key collections.Triple[AccAddress, ValAddress, ValAddress]) (stop bool, err error) { return onResult(key.K3()) }) } ``` ## Advanced Usages ### Alternative Value Codec The `codec.AltValueCodec` allows a collection to decode values using a different codec than the one used to encode them. Basically it enables to decode two different byte representations of the same concrete value. It can be used to lazily migrate values from one bytes representation to another, as long as the new representation is not able to decode the old one. A concrete example can be found in `x/bank` where the balance was initially stored as `Coin` and then migrated to `Int`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var BankBalanceValueCodec = codec.NewAltValueCodec(sdk.IntValue, func(b []byte) (sdk.Int, error) { coin := sdk.Coin{ } err := coin.Unmarshal(b) if err != nil { return sdk.Int{ }, err } return coin.Amount, nil }) ``` The above example shows how to create an `AltValueCodec` that can decode both `sdk.Int` and `sdk.Coin` values. The provided decoder function will be used as a fallback in case the default decoder fails. When the value will be encoded back into state it will use the default encoder. This allows to lazily migrate values to a new bytes representation. # Module Store Internals Source: https://docs.cosmos.network/sdk/latest/guides/state/store The store package defines the interfaces, types and abstractions for Cosmos SDK modules to read and write to Merkleized state within a Cosmos SDK application. The store package provides many primitives for developers to use in order to work with both state storage and state commitment. Below we describe the various abstractions. ## Types ### `Store` The bulk of the store interfaces are defined [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/types/store.go), where the base primitive interface, for which other interfaces build off of, is the `Store` type. The `Store` interface defines the ability to tell the type of the implementing store and the ability to cache wrap via the `CacheWrapper` interface. ### `CacheWrapper` & `CacheWrap` One of the most important features a store has the ability to perform is the ability to cache wrap. Cache wrapping is essentially the underlying store wrapping itself within another store type that performs caching for both reads and writes with the ability to flush writes via `Write()`. ### `KVStore` & `CacheKVStore` One of the most important interfaces that both developers and modules interface with, which also provides the basis of most state storage and commitment operations, is the `KVStore`. The `KVStore` interface provides basic CRUD abilities and prefix-based iteration, including reverse iteration. Typically, each module has its own dedicated `KVStore` instance, which it can get access to via the `sdk.Context` and the use of a pointer-based named key -- `KVStoreKey`. The `KVStoreKey` provides pseudo-OCAP. How exactly a `KVStoreKey` maps to a `KVStore` will be illustrated below through the `CommitMultiStore`. Note, a `KVStore` cannot directly commit state. Instead, a `KVStore` can be wrapped by a `CacheKVStore` which extends a `KVStore` and provides the ability for the caller to execute `Write()` which flushes pending writes to the parent `KVStore` in memory. Note, this doesn't actually flush writes to disk as writes are held in memory until `Commit()` is called on the `CommitMultiStore`. ### `CommitMultiStore` The `CommitMultiStore` interface exposes the top-level interface that is used to manage state commitment and storage by an SDK application and abstracts the concept of multiple `KVStore`s which are used by multiple modules. Specifically, it supports the following high-level primitives: * Allows for a caller to retrieve a `KVStore` by providing a `KVStoreKey`. * Exposes pruning mechanisms to remove state pinned against a specific height/version in the past. * Allows for loading state storage at a particular height/version in the past to provide current head and historical queries. * Provides the ability to rollback state to a previous height/version. * Provides the ability to load state storage at a particular height/version while also performing store upgrades, which are used during live hard-fork application state migrations. * Provides the ability to commit all current accumulated state to disk and performs Merkle commitment. ## Implementation Details While there are many interfaces that the `store` package provides, there is typically a core implementation for each main interface that modules and developers interact with that are defined in the Cosmos SDK. ### `iavl.Store` The `iavl.Store` provides the core implementation for state storage and commitment by implementing the following interfaces: * `KVStore` * `CommitStore` * `CommitKVStore` * `Queryable` * `StoreWithInitialVersion` It allows for all CRUD operations to be performed along with allowing current and historical state queries, prefix iteration, and state commitment along with Merkle proof operations. The `iavl.Store` also provides the ability to remove historical state from the state commitment layer. An overview of the IAVL implementation can be found [here](https://github.com/cosmos/iavl/blob/master/docs/overview.md). It is important to note that the IAVL store provides both state commitment and logical storage operations, which comes with drawbacks as there are various performance impacts, some of which are very drastic, when it comes to the operations mentioned above. When dealing with state management in modules and clients, the Cosmos SDK provides various layers of abstractions or "store wrapping", where the `iavl.Store` is the bottom most layer. When requesting a store to perform reads or writes in a module, the typical abstraction layer in order is defined as follows: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rootmulti.Store -> cachemulti.Store -> gaskv.Store -> cachekv.Store -> iavl.Store ``` ### Concurrent use of IAVL store The tree under `iavl.Store` is not safe for concurrent use. It is the responsibility of the caller to ensure that concurrent access to the store is not performed. The main issue with concurrent use is when data is written at the same time as it's being iterated over. Doing so will cause an irrecoverable fatal error because of concurrent reads and writes to an internal map. Although it's not recommended, you can iterate through values while writing to it by disabling "FastNode" **without guarantees that the values being written will be returned during the iteration** (if you need this, you might want to reconsider the design of your application). This is done by setting `iavl-disable-fastnode` to `true` in the config TOML file. ### `cachekv.Store` The `cachekv.Store` store wraps an underlying `KVStore`, typically a `iavl.Store` and contains an in-memory cache for storing pending writes to underlying `KVStore`. `Set` and `Delete` calls are executed on the in-memory cache. `Has` checks the cache first, falling through to the underlying `KVStore` only on a cache miss. One of the most important calls to a `cachekv.Store` is `Write()`, which ensures that key-value pairs are written to the underlying `KVStore` in a deterministic and ordered manner by sorting the keys first. The store keeps track of "dirty" keys and uses these to determine what keys to sort. Deletions are represented as zero-value (nil) entries; `Write()` detects these and calls `Delete` on the underlying `KVStore` for each one. The `cachekv.Store` also provides the ability to perform iteration and reverse iteration. Iteration is performed through the `cacheMergeIterator` type and uses both the dirty cache and underlying `KVStore` to iterate over key-value pairs. Note, all calls to CRUD and iteration operations on a `cachekv.Store` are thread-safe. ### `gaskv.Store` The `gaskv.Store` store provides a simple implementation of a `KVStore`. Specifically, it just wraps an existing `KVStore`, such as a cache-wrapped `iavl.Store`, and incurs configurable gas costs for CRUD operations via `ConsumeGas()` calls on a `GasMeter` passed at construction time, then proxies the underlying CRUD call to the wrapped store. ### `cachemulti.Store` & `rootmulti.Store` The `rootmulti.Store` acts as an abstraction around a series of stores. Namely, it implements the `CommitMultiStore` an `Queryable` interfaces. Through the `rootmulti.Store`, an SDK module can request access to a `KVStore` to perform state CRUD operations and queries by holding access to a unique `KVStoreKey`. The `rootmulti.Store` ensures these queries and state operations are performed through cached-wrapped instances of `cachekv.Store` which is described above. The `rootmulti.Store` implementation is also responsible for committing all accumulated state from each `KVStore` to disk and returning an application state Merkle root. Queries can be performed to return state data along with associated state commitment proofs for both previous heights/versions and the current state root. Queries are routed based on store name, i.e. a module, along with other parameters defined in the SDK's `RequestQuery` type. The `rootmulti.Store` also provides primitives for pruning data at a given height/version from state storage. When a height is committed, the `rootmulti.Store` will determine if other previous heights should be considered for removal based on the operator's pruning settings defined by `PruningOptions`, which defines how many recent versions to keep on disk and the interval at which to remove "staged" pruned heights from disk. During each interval, the staged heights are removed from each `KVStore`. Note, it is up to the underlying `KVStore` implementation to determine how pruning is actually performed. The `PruningOptions` are defined as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type PruningOptions struct { // KeepRecent defines how many recent heights to keep on disk. KeepRecent uint64 // Interval defines when the pruned heights are removed from disk. Interval uint64 // Strategy defines the kind of pruning strategy. See below for more information on each. Strategy PruningStrategy } ``` The Cosmos SDK defines a preset number of pruning "strategies": `default`, `everything`, `nothing`, and `custom`. It is important to note that the `rootmulti.Store` considers each `KVStore` as a separate logical store. In other words, they do not share a Merkle tree or comparable data structure. This means that when state is committed via `rootmulti.Store`, each store is committed in sequence and thus is not atomic. In terms of store construction and wiring, each Cosmos SDK application contains a `BaseApp` instance which internally has a reference to a `CommitMultiStore` that is implemented by a `rootmulti.Store`. The application then registers one or more `KVStoreKey` that pertain to a unique module and thus a `KVStore`. Through the use of an `sdk.Context` and a `KVStoreKey`, each module can get direct access to it's respective `KVStore` instance. Example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewApp(...) Application { // ... bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) bApp.SetVersion(version.Version) bApp.SetInterfaceRegistry(interfaceRegistry) // ... keys := sdk.NewKVStoreKeys(...) transientKeys := sdk.NewTransientStoreKeys(...) memKeys := sdk.NewMemoryStoreKeys(...) // ... // initialize stores app.MountKVStores(keys) app.MountTransientStores(transientKeys) app.MountMemoryStores(memKeys) // ... } ``` The `rootmulti.Store` itself can be cache-wrapped which returns an instance of a `cachemulti.Store`. For each block, `BaseApp` ensures that the proper abstractions are created on the `CommitMultiStore`, i.e. ensuring that the `rootmulti.Store` is cached-wrapped and uses the resulting `cachemulti.Store` to be set on the `sdk.Context` which is then used for block and transaction execution. As a result, all state mutations due to block and transaction execution are actually held ephemerally until `Commit()` is called by the ABCI client. This concept is further expanded upon when the AnteHandler is executed per transaction to ensure state is not committed for transactions that failed CheckTx. # Log v2 Source: https://docs.cosmos.network/sdk/latest/guides/testing/log `cosmossdk.io/log/v2` is the Cosmos SDK logging package. At a high level, there are three pieces to understand: 1. `log.NewLogger(...)` creates the default Cosmos SDK logger. It is backed by `zerolog`. 2. `cosmossdk.io/log/v2/slog` lets you satisfy the same SDK `Logger` interface with a standard library `*slog.Logger`. 3. `log.NewMultiLogger(...)` fans one log call out to multiple SDK loggers. The SDK uses this during server startup when OpenTelemetry log exporting is enabled. To learn more about how we support OpenTelemetry, read the [Telemetry docs](/sdk/latest/guides/testing/telemetry). If you only need ordinary SDK logging, you usually only need `log.NewLogger`, which is automatically provisioned and set on `sdk.Context`. ## Default Logger The default implementation is a small wrapper around `zerolog`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} logger := log.NewLogger(os.Stderr) logger.Info("starting app", "chain_id", chainID) logger.Error("failed to load state", "err", err) ``` `NewLogger` writes human-readable console output by default. The server command wiring switches options based on CLI configuration, for example: * `OutputJSONOption()` for JSON logs * `LevelOption(...)` for a global log level * `FilterOption(...)` for module-based filtering * `TraceOption(true)` to include stack traces on error logs * `VerboseLevelOption(...)` for temporary verbose mode The SDK also uses the `module` field consistently. The package exposes `log.ModuleKey` for this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} logger = logger.With(log.ModuleKey, "bank") logger.Info("send coins", "from", from, "to", to) ``` That matters because the log filter implementation keys off the `module` field when parsing values such as `consensus:debug,*:error`. ## Structured Context `Logger.With(...)` returns a derived logger with additional fields: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keeperLogger := logger.With(log.ModuleKey, "staking", "component", "keeper") keeperLogger.Info("validator updated", "operator", valAddr) ``` This is the normal way to attach stable metadata to a logger instance. ## Context-Aware Logging The v2 `Logger` interface adds `*Context` methods: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Logger interface { Info(msg string, keyVals ...any) InfoContext(ctx context.Context, msg string, keyVals ...any) Warn(msg string, keyVals ...any) WarnContext(ctx context.Context, msg string, keyVals ...any) Error(msg string, keyVals ...any) ErrorContext(ctx context.Context, msg string, keyVals ...any) Debug(msg string, keyVals ...any) DebugContext(ctx context.Context, msg string, keyVals ...any) With(keyVals ...any) Logger Impl() any } ``` The important distinction is: * `Info`, `Warn`, `Error`, and `Debug` log without inspecting a `context.Context` * `InfoContext`, `WarnContext`, `ErrorContext`, and `DebugContext` use the provided context for trace correlation For the default `zerolog` implementation, the `*Context` methods extract the active OpenTelemetry span from `ctx` and add: * `trace_id` * `span_id` * `trace_flags` when present If there is no valid span in the context, they behave like normal log calls. ## Trace Correlation When you want logs to line up with spans, use the context-aware methods. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) UpdateBalance(ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error { ctx, span := ctx.StartSpan(tracer, "UpdateBalance") defer span.End() logger := ctx.Logger().With(log.ModuleKey, "bank") logger.InfoContext(ctx, "updating balance", "address", addr.String()) return nil } ``` Two details matter here: 1. `sdk.Context.StartSpan(...)` returns a new `sdk.Context` with the Go `context.Context` updated to include the span. 2. The logger only sees trace information when you call one of the logger's `*Context` methods with that updated context. Without the `*Context` call, the default logger will not add trace fields to the log record. ## `log/slog` `cosmossdk.io/log/v2/slog` is an adapter for code that already has a standard library `*slog.Logger`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} base := slog.New(handler) logger := sdklogSlog.NewCustomLogger(base) ``` This does not add extra SDK behavior by itself. It simply makes a `*slog.Logger` satisfy the Cosmos SDK `Logger` interface. Filtering, formatting, sinks, and handler behavior are whatever the underlying `slog.Logger` is configured to do. ## `MultiLogger` `log.NewMultiLogger(loggers...)` returns a logger that dispatches each log call to every wrapped logger. That includes: * ordinary log methods such as `Info(...)` * context-aware methods such as `InfoContext(...)` * `With(...)`, which derives a child logger for each wrapped logger If an underlying logger implements `VerboseModeLogger`, `SetVerboseMode(...)` is also forwarded. In other words, `MultiLogger` is just fanout. It does not merge records or add new fields on its own. ## When The SDK Configures `MultiLogger` `MultiLogger` is not created for every app automatically. During the node's server start, the SDK first builds the normal server logger from CLI/config flags. That logger is the usual `zerolog`-backed logger. Then the SDK initializes OpenTelemetry from `config/otel.yaml`. If `telemetry.IsOtelLoggerEnabled()` reports that the global OpenTelemetry logger provider has active log processors/exporters, the SDK wraps the existing server logger like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} otelLogger := sdkSlog.NewCustomLogger(otelslog.NewLogger("")) svrCtx.Logger = log.NewMultiLogger(svrCtx.Logger, otelLogger) ``` So when OpenTelemetry log exporting is enabled, one log call is sent to: * the existing console/stdout logger * an OpenTelemetry-backed logger for export If OpenTelemetry logging is not enabled, the server continues using only the normal logger. ## What `otelslog` Is `otelslog` is an OpenTelemetry bridge for Go's `log/slog` package. More specifically, it provides a `slog.Handler` and `slog.Logger` that convert `slog.Record` values into OpenTelemetry log records and sends them to the configured OpenTelemetry logger provider. In the Cosmos SDK startup path: * `otelslog.NewLogger("")` creates an `*slog.Logger` backed by that bridge * `cosmossdk.io/log/v2/slog.NewCustomLogger(...)` wraps it so it satisfies the SDK `Logger` interface * `log.NewMultiLogger(...)` fans logs out to both the normal `zerolog` logger and the OpenTelemetry bridge Because `slog` has native `InfoContext`/`WarnContext`/`ErrorContext`/`DebugContext` methods, the `otelslog` side receives the context directly. That means trace/span correlation is handled by the OpenTelemetry logging pipeline without the SDK needing to manually inject `trace_id` fields into that branch. ## Two Common Setups ### 1. Stdout only If you do not configure an OpenTelemetry logger provider, logs only go to the normal SDK logger output. This does not restrict you from log correlation, however. For trace correlation in tools such as Grafana Tempo and Loki, you can: 1. Emit JSON logs to stdout/stderr. 2. Scrape those logs with an agent such as the OpenTelemetry Collector filelog receiver. 3. Forward them to Loki. 4. Query by the `trace_id` field in the logs. Remember, `trace_id` is only injected into the log if a contextual method was called with a context that contains an active span. ### 2. OpenTelemetry log exporter enabled If `otel.yaml` enables an OpenTelemetry log pipeline with real log processors/exporters, the SDK configures a `MultiLogger`. In that setup: * console logging still works as before * logs are also exported through OpenTelemetry * context-aware log calls carry trace context into the OpenTelemetry branch as well This is the path to use when you want the SDK to write logs directly into an OpenTelemetry logging backend, which eliminates the need to set up scraping infrastructure. ## Future Direction Today the SDK uses a `MultiLogger` because the default logger is `zerolog`, while OpenTelemetry currently offers a bridge for `slog` rather than `zerolog`. If a first-class `zerolog` bridge becomes available and suitable, that would likely be a simpler export path than maintaining a separate fanout logger. Relevant discussion: * [https://github.com/rs/zerolog/pull/682](https://github.com/rs/zerolog/pull/682) * [https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5969](https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5969) # Module Simulation Source: https://docs.cosmos.network/sdk/latest/guides/testing/simulator **Prerequisite Readings** * [Testing in the SDK](/sdk/latest/learn/concepts/testing) ## Synopsis This document guides developers on integrating their custom modules with the Cosmos SDK `Simulations`. Simulations are useful for testing edge cases in module implementations. * [Simulation Package](#simulation-package) * [Simulation App Module](#simulation-app-module) * [SimsX](#simsx) * [Example Implementations](#example-implementations) * [Store decoders](#store-decoders) * [Randomized genesis](#randomized-genesis) * [Random weighted operations](#random-weighted-operations) * [Using Simsx](#using-simsx) * [App Simulator manager](#app-simulator-manager) * [Running Simulations](#running-simulations) ## Simulation Package The Cosmos SDK suggests organizing your simulation related code in a `x//simulation` package. ## Simulation App Module To integrate with the Cosmos SDK `SimulationManager`, app modules must implement the `AppModuleSimulation` interface. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // AppModuleSimulation defines the standard functions that every module should expose // for the SDK blockchain simulator type AppModuleSimulation interface { // randomized genesis states GenerateGenesisState(input *SimulationState) // register a func to decode the each module's defined types from their corresponding store key RegisterStoreDecoder(simulation.StoreDecoderRegistry) // simulation operations (i.e msgs) with their respective weight WeightedOperations(simState SimulationState) []simulation.WeightedOperation } // HasProposalMsgs defines the messages that can be used to simulate governance (v1) proposals type HasProposalMsgs interface { // msg functions used to simulate governance proposals ProposalMsgs(simState SimulationState) []simulation.WeightedProposalMsg } ``` See the full source at [`types/module/simulation.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/module/simulation.go). See an example implementation of these methods from `x/distribution` [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/distribution/module.go#L158-L182). ## SimsX Cosmos SDK v0.53.0 introduced a new package, `simsx`, providing improved DevX for writing simulation code. It exposes the following extension interfaces that modules may implement to integrate with the new `simsx` runner. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ( HasWeightedOperationsX interface { WeightedOperationsX(weight WeightSource, reg Registry) } HasWeightedOperationsXWithProposals interface { WeightedOperationsX(weights WeightSource, reg Registry, proposals WeightedProposalMsgIter, legacyProposals []simtypes.WeightedProposalContent) } HasProposalMsgsX interface { ProposalMsgsX(weights WeightSource, reg Registry) } ) ``` See the full source at [`testutil/simsx/runner.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/testutil/simsx/runner.go). `SimMsgFactoryFn` is the default factory for most cases. It does not create future operations but ensures successful message delivery: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SimMsgFactoryFn is the default factory for most cases. It does not create future operations but ensures successful message delivery. type SimMsgFactoryFn[T sdk.Msg] func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg T) ``` See the full source at [`testutil/simsx/msg_factory.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/testutil/simsx/msg_factory.go). These methods allow constructing randomized messages and/or proposal messages. Note that modules should **not** implement both `HasWeightedOperationsX` and `HasWeightedOperationsXWithProposals`. See the runner code [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/testutil/simsx/runner.go#L330-L339) for details If the module does **not** have message handlers or governance proposal handlers, these interface methods do **not** need to be implemented. ### Example Implementations * `HasWeightedOperationsXWithProposals`: [x/gov](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/gov/module.go#L221-L240) * `HasWeightedOperationsX`: [x/bank](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/bank/module.go#L179-L183) * `HasProposalMsgsX`: [x/bank](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/bank/module.go#L174-L177) ## Store decoders Registering the store decoders is required for the `AppImportExport` simulation. This allows for the key-value pairs from the stores to be decoded to their corresponding types. In particular, it matches the key to a concrete type and then unmarshalls the value from the `KVPair` to the type provided. Modules using [collections](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/collections/README.md) can use the `NewStoreDecoderFuncFromCollectionsSchema` function that builds the decoder for you: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // RegisterStoreDecoder registers a decoder for supply module's types func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { sdr[types.StoreKey] = simtypes.NewStoreDecoderFuncFromCollectionsSchema(am.keeper.(keeper.BaseKeeper).Schema) } ``` See the full source at [`types/simulation/collections.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/simulation/collections.go) and the bank module example at [`x/bank/module.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/bank/module.go#L161-L164). Modules not using collections must manually build the store decoder. See the implementation [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/distribution/simulation/decoder.go) from the distribution module for an example. ## Randomized genesis The simulator tests different scenarios and values for genesis parameters. App modules must implement a `GenerateGenesisState` method to generate the initial random `GenesisState` from a given seed. See an example from `x/auth` [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/auth/module.go#L171-L174). Once the module's genesis parameters are generated randomly (or with the key and values defined in a `params` file), they are marshaled to JSON format and added to the app genesis JSON for the simulation. ## Random weighted operations Operations are one of the crucial parts of the Cosmos SDK simulation. They are the transactions (`Msg`) that are simulated with random field values. The sender of the operation is also assigned randomly. Operations on the simulation are simulated using the full [transaction cycle](/sdk/latest/learn/concepts/lifecycle) of a `ABCI` application that exposes the `BaseApp`. ### Using Simsx Simsx introduces the ability to define a `MsgFactory` for each of a module's messages. These factories are registered in `WeightedOperationsX` and/or `ProposalMsgsX`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ProposalMsgsX registers governance proposal messages in the simulation registry. func (AppModule) ProposalMsgsX(weights simsx.WeightSource, reg simsx.Registry) { reg.Add(weights.Get("msg_update_params", 100), simulation.MsgUpdateParamsFactory()) } // WeightedOperationsX registers weighted distribution module operations for simulation. func (am AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { reg.Add(weights.Get("msg_set_withdraw_address", 50), simulation.MsgSetWithdrawAddressFactory(am.keeper)) reg.Add(weights.Get("msg_withdraw_delegation_reward", 50), simulation.MsgWithdrawDelegatorRewardFactory(am.keeper, am.stakingKeeper)) reg.Add(weights.Get("msg_withdraw_validator_commission", 50), simulation.MsgWithdrawValidatorCommissionFactory(am.keeper, am.stakingKeeper)) } ``` Note that the name passed in to `weights.Get` must match the name of the operation set in the `WeightedOperations`. For example, if the module contains an operation `op_weight_msg_set_withdraw_address`, the name passed to `weights.Get` should be `msg_set_withdraw_address`. See the `x/distribution` for an example of implementing message factories [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/distribution/simulation/msg_factory.go) ## App Simulator manager The following step is setting up the `SimulationManager` at the app level. This is required for the simulation test files in the next step. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type CoolApp struct { ... sm *module.SimulationManager } ``` Within the constructor of the application, construct the simulation manager using the modules from `ModuleManager` and call the `RegisterStoreDecoders` method. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} overrideModules := map[string]module.AppModuleSimulation{ authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil), } app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) app.sm.RegisterStoreDecoders() ``` Note that you may override some modules. This is useful if the existing module configuration in the `ModuleManager` should be different in the `SimulationManager`. Finally, the application should expose the `SimulationManager` via the following method defined in the `AppI` interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SimulationManager implements the SimulationApp interface func (app *SimApp) SimulationManager() *module.SimulationManager { return app.sm } ``` See the full simapp setup at [`simapp/app.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/simapp/app.go). ## Running Simulations To run the simulation, use the `simsx` runner. Call `simsx.Run` to begin simulating with the default seeds, or `simsx.RunWithSeeds` to provide specific seeds: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func TestFullAppSimulation(t *testing.T) { sims.Run(t, NewSimApp, setupStateFactory) } func TestAppImportExport(t *testing.T) { sims.Run(t, NewSimApp, setupStateFactory, func(tb testing.TB, ti sims.TestInstance[*SimApp], accs []simtypes.Account) { // post-run assertions: export and compare stores }) } ``` These functions should be called in tests (i.e., `app_test.go`, `app_sim_test.go`, etc.). See the full simapp test file at [`simapp/sim_test.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/simapp/sim_test.go). ### Simulation test types The simulation framework provides four test functions, each testing a different failure scenario: * `TestFullAppSimulation`: General simulation mode. Runs the chain and specified operations for a given number of blocks, checking for panics. * `TestAppImportExport`: Exports the initial app state and creates a new app with the exported `genesis.json` as input, checking for store inconsistencies between the two. * `TestAppSimulationAfterImport`: Chains two simulations -- the first provides its app state to the second. Useful for testing software upgrades or hard-forks from a live chain. * `TestAppStateDeterminism`: Checks that all nodes return the same values in the same order. ### Simulator modes Simulations run in three modes: 1. **Fully random** -- initial state, module parameters, and simulation parameters are all pseudo-randomly generated. 2. **From a `genesis.json` file** -- initial state and module parameters are defined by the file. Useful for testing against a known state such as a live network export. 3. **From a `params.json` file** -- initial state is pseudo-randomly generated but module and simulation parameters are set manually. Available parameters are listed [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/simulation/client/cli/flags.go#L43-L70). These modes are not mutually exclusive. For example, you can combine a randomly generated genesis state (mode 1) with manually defined simulation params (mode 3). ### Running via go test Simulations can be run directly with `go test`: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go test -mod=readonly github.com/cosmos/cosmos-sdk/simapp \ -run=TestApp \ ... \ -v -timeout 24h ``` The full list of available flags is defined [here](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/simulation/client/cli/flags.go#L43-L70). For Makefile examples, see the Cosmos SDK [`Makefile`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/Makefile#L280-L340). ### Debugging tips When encountering a simulation failure: * **Export app state** at the failure height using the `-ExportStatePath` flag. * **Use `-Verbose` logs** for a fuller picture of all operations involved. * **Try a different `-Seed`**. If the same error reproduces sooner, you will spend less time on each run. * **Reduce `-NumBlocks`** to isolate what the app state looks like at the block before failure. * **Add a [`Logger`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/staking/keeper/keeper.go#L82-L86)** to operations that are not being logged. # Telemetry Source: https://docs.cosmos.network/sdk/latest/guides/testing/telemetry Gather relevant insights about your application and modules with custom metrics and telemetry. ## Overview The `telemetry` package provides observability tooling for Cosmos SDK applications using [OpenTelemetry](https://opentelemetry.io/docs/). It offers a unified initialization point for traces, metrics, and logs via the OpenTelemetry declarative configuration API. This package: * Initializes OpenTelemetry SDK using YAML configuration files * Provides backward compatibility with Cosmos SDK's legacy `go-metrics` wrapper API * Includes built-in instrumentation for host, runtime, and disk I/O metrics ## Quick Start ### 1. Start a Local Telemetry Backend ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} docker run -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti grafana/otel-lgtm ``` ### 2. Create Configuration File Create an `otel.yaml` file: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} file_format: "1.0-rc.3" resource: attributes: - name: service.name value: my-cosmos-app tracer_provider: processors: - batch: exporter: otlp_grpc: endpoint: http://localhost:4317 meter_provider: readers: - pull: exporter: prometheus/development: host: 0.0.0.0 port: 9464 logger_provider: processors: - batch: exporter: otlp_grpc: endpoint: http://localhost:4317 extensions: instruments: host: {} runtime: {} diskio: {} propagators: - tracecontext ``` ### 3. Initialize Telemetry **Option A: Environment Variable (Recommended)** Set `OTEL_CONFIG_FILE` to your config path. This initializes the SDK before any meters/tracers are created, avoiding atomic load overhead. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} export OTEL_CONFIG_FILE=/path/to/otel.yaml ``` **Option B: Node Config Directory** An empty `otel.yaml` will now be generated in `~/./config/`. Place the desired configuration in `otel.yaml`. **Option C: Programmatic Initialization** The SDK will first attempt to initialize via env var, then using the config in the node's home directory. You may optionally initialize telemetry yourself using the `telemetry.InitializeOpenTelemetry` function: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} err := telemetry.InitializeOpenTelemetry("/path/to/otel.yaml") if err != nil { log.Fatal(err) } defer telemetry.Shutdown(context.Background()) ``` ## Configuration ### OpenTelemetry Configuration The package uses the [OpenTelemetry declarative configuration spec](https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/). Key sections: | Section | Purpose | | ----------------- | ------------------------------- | | `resource` | Service identity and attributes | | `tracer_provider` | Trace export configuration | | `meter_provider` | Metrics export configuration | | `logger_provider` | Log export configuration | For examples containing available options, see the [OpenTelemetry configuration examples](https://github.com/open-telemetry/opentelemetry-configuration/tree/main/examples). ### Extensions The `extensions` section of the `otel.yaml` configuration file provides additional features not yet supported by the standard otelconf: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} extensions: # Optional file-based exporters trace_file: "/path/to/traces.json" metrics_file: "/path/to/metrics.json" metrics_file_interval: "10s" logs_file: "/path/to/logs.json" # Custom instrumentation additions instruments: host: {} runtime: {} diskio: disable_virtual_device_filter: true # removes the automatic filtering of virtual disks. Operating systems such as Linux typically add virtual disks, which can add duplication to disk io data. These disks usually take the form of loopback, RAID, partitions, etc. # Trace context propagation propagators: - tracecontext - baggage - b3 - jaeger ``` ## Custom Instruments ### Host Instrumentation (`host`) Reports host-level metrics using `go.opentelemetry.io/contrib/instrumentation/host`: * CPU usage * Memory usage * Network I/O ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} extensions: instruments: host: {} ``` ### Runtime Instrumentation (`runtime`) Reports Go runtime metrics using `go.opentelemetry.io/contrib/instrumentation/runtime`: * Goroutine count * GC statistics * Memory allocations ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} extensions: instruments: runtime: {} ``` ### Disk I/O Instrumentation (`diskio`) Reports disk I/O metrics using gopsutil: | Metric | Description | | ---------------------------- | ----------------------------- | | `system.disk.io` | Bytes read/written | | `system.disk.operations` | Read/write operation counts | | `system.disk.io_time` | Time spent on I/O operations | | `system.disk.operation_time` | Time per read/write operation | | `system.disk.merged` | Merged read/write operations | ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} extensions: instruments: diskio: {} # Or with options: diskio: disable_virtual_device_filter: true # Include loopback, RAID, partitions on Linux ``` By default, virtual devices (loopback, RAID, partitions) are filtered out on Linux to avoid double-counting I/O. ## Propagators Configure trace context propagation for distributed tracing: | Propagator | Description | | -------------- | --------------------------- | | `tracecontext` | W3C Trace Context (default) | | `baggage` | W3C Baggage | | `b3` | Zipkin B3 single header | | `b3multi` | Zipkin B3 multi-header | | `jaeger` | Jaeger propagation | ## Developer Usage ### Using Meters and Tracers After initialization, use standard OpenTelemetry APIs: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" ) var ( tracer = otel.Tracer("my-package") meter = otel.Meter("my-package") myCounter metric.Int64Counter ) func init() { var err error myCounter, err = meter.Int64Counter("my.counter") if err != nil { panic(err) } } func MyFunction(ctx context.Context) error { ctx, span := tracer.Start(ctx, "MyFunction") defer span.End() myCounter.Add(ctx, 1) // ... your code return nil } ``` ### Shutdown Always call `Shutdown()` when the application exits: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (a *App) Close() { telemetry.Shutdown(ctx) } ``` ## Legacy API (Deprecated) The package provides backward-compatible wrappers for `github.com/hashicorp/go-metrics`. These are **deprecated** and users should migrate to OpenTelemetry APIs directly. ### OpenTelemetry Bridge Cosmos SDK v0.54.0+ provides a bridge to send existing go-metrics to the meter provider defined in your OpenTelemetry config. To bridge your metrics, set the `metrics-sink` in `app.toml` to "otel". ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ############################################################################### ### Telemetry Configuration ### ############################################################################### [telemetry] # other fields... metrics-sink = "otel" ``` ### Legacy Configuration ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cfg := telemetry.Config{ ServiceName: "my-service", Enabled: true, EnableHostname: true, EnableHostnameLabel: true, EnableServiceLabel: true, PrometheusRetentionTime: 60, // seconds GlobalLabels: [][]string{{"chain_id", "cosmoshub-1"}}, MetricsSink: "otel", // "mem", "statsd", "dogstatsd", "otel" StatsdAddr: "localhost:8125", } m, err := telemetry.New(cfg) ``` ### Legacy Metrics Functions All are deprecated; prefer OpenTelemetry: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Counters telemetry.IncrCounter(1.0, "tx", "count") telemetry.IncrCounterWithLabels([]string{"tx", "count"}, 1.0, labels) // Gauges telemetry.SetGauge(42.0, "mempool", "size") telemetry.SetGaugeWithLabels([]string{"mempool", "size"}, 42.0, labels) // Timing start := telemetry.Now() // ... operation telemetry.MeasureSince(start, "tx", "process_time") // Module-specific helpers telemetry.ModuleMeasureSince("bank", start, "send", "time") telemetry.ModuleSetGauge("bank", 100.0, "balance", "total") ``` ## Metrics Sink Types | Sink | Description | | ----------- | --------------------------------------------------- | | `mem` | In-memory sink with SIGUSR1 dump support (default) | | `statsd` | StatsD protocol | | `dogstatsd` | Datadog DogStatsD | | `otel` | OpenTelemetry (bridges to configured MeterProvider) | ## Best Practices 1. **Use environment variable initialization** for production to avoid atomic load overhead 2. **Always call `Shutdown()`** to ensure metrics/traces are flushed 3. **Thread `context.Context`** properly for correct span correlation ## Viewing Telemetry Data With Grafana LGTM running: 1. Open [http://localhost:3000](http://localhost:3000) 2. Use the Drilldown views to explore: * **Traces**: Distributed trace visualization * **Metrics**: Query and dashboard metrics * **Logs**: Structured log search ## Related Documentation * [OpenTelemetry Go SDK](https://opentelemetry.io/docs/languages/go/) * [OpenTelemetry Configuration Spec](https://opentelemetry.io/docs/languages/sdk-configuration/declarative-configuration/) * [otelconf Go Package](https://pkg.go.dev/go.opentelemetry.io/contrib/otelconf) ## Cosmos SDK Metrics The following metrics are emitted from the Cosmos SDK. | Metric | Description | Unit | Type | | :------------------------------ | :---------------------------------------------------------------------------------------- | :-------------- | :------ | | `tx_count` | Total number of txs processed via `FinalizeBlock` | tx | counter | | `tx_successful` | Total number of successful txs processed via `FinalizeBlock` | tx | counter | | `tx_failed` | Total number of failed txs processed via `FinalizeBlock` | tx | counter | | `tx_gas_used` | The total amount of gas used by a tx | gas | gauge | | `tx_gas_wanted` | The total amount of gas requested by a tx | gas | gauge | | `tx_msg_send` | The total amount of tokens sent in a `MsgSend` (per denom) | token | gauge | | `tx_msg_withdraw_reward` | The total amount of tokens withdrawn in a `MsgWithdrawDelegatorReward` (per denom) | token | gauge | | `tx_msg_withdraw_commission` | The total amount of tokens withdrawn in a `MsgWithdrawValidatorCommission` (per denom) | token | gauge | | `tx_msg_delegate` | The total amount of tokens delegated in a `MsgDelegate` | token | gauge | | `tx_msg_begin_unbonding` | The total amount of tokens undelegated in a `MsgUndelegate` | token | gauge | | `tx_msg_begin_begin_redelegate` | The total amount of tokens redelegated in a `MsgBeginRedelegate` | token | gauge | | `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | | `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | | `new_account` | Total number of new accounts created | account | counter | | `gov_proposal` | Total number of governance proposals | proposal | counter | | `gov_vote` | Total number of governance votes for a proposal | vote | counter | | `gov_deposit` | Total number of governance deposits for a proposal | deposit | counter | | `staking_delegate` | Total number of delegations | delegation | counter | | `staking_undelegate` | Total number of undelegations | undelegation | counter | | `staking_redelegate` | Total number of redelegations | redelegation | counter | | `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | | `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | | `ibc_client_create` | Total number of clients created | create | counter | | `ibc_client_update` | Total number of client updates | update | counter | | `ibc_client_upgrade` | Total number of client upgrades | upgrade | counter | | `ibc_client_misbehaviour` | Total number of client misbehaviors | misbehaviour | counter | | `ibc_connection_open-init` | Total number of connection `OpenInit` handshakes | handshake | counter | | `ibc_connection_open-try` | Total number of connection `OpenTry` handshakes | handshake | counter | | `ibc_connection_open-ack` | Total number of connection `OpenAck` handshakes | handshake | counter | | `ibc_connection_open-confirm` | Total number of connection `OpenConfirm` handshakes | handshake | counter | | `ibc_channel_open-init` | Total number of channel `OpenInit` handshakes | handshake | counter | | `ibc_channel_open-try` | Total number of channel `OpenTry` handshakes | handshake | counter | | `ibc_channel_open-ack` | Total number of channel `OpenAck` handshakes | handshake | counter | | `ibc_channel_open-confirm` | Total number of channel `OpenConfirm` handshakes | handshake | counter | | `ibc_channel_close-init` | Total number of channel `CloseInit` handshakes | handshake | counter | | `ibc_channel_close-confirm` | Total number of channel `CloseConfirm` handshakes | handshake | counter | | `tx_msg_ibc_recv_packet` | Total number of IBC packets received | packet | counter | | `tx_msg_ibc_acknowledge_packet` | Total number of IBC packets acknowledged | acknowledgement | counter | | `ibc_timeout_packet` | Total number of IBC timeout packets | timeout | counter | | `store_iavl_get` | Duration of an IAVL `Store#Get` call | ms | summary | | `store_iavl_set` | Duration of an IAVL `Store#Set` call | ms | summary | | `store_iavl_has` | Duration of an IAVL `Store#Has` call | ms | summary | | `store_iavl_delete` | Duration of an IAVL `Store#Delete` call | ms | summary | | `store_iavl_commit` | Duration of an IAVL `Store#Commit` call | ms | summary | | `store_iavl_query` | Duration of an IAVL `Store#Query` call | ms | summary | # Writing CLI Commands Source: https://docs.cosmos.network/sdk/latest/guides/tooling/autocli For a conceptual overview of how CLI, gRPC, and REST fit together in a Cosmos SDK app, see [CLI, gRPC & REST](/sdk/latest/learn/concepts/cli-grpc-rest). ## Overview `autocli` generates CLI commands and flags for each method defined in your gRPC service. By default, it generates a command for each gRPC service method. The commands are named based on the name of the service method. The [API reference](/sdk/latest/api-reference/index) documents the services the standard modules define, which is what their generated commands call. For example, given the following protobuf definition for a service: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service MyService { rpc MyMethod(MyRequest) returns (MyResponse) {} } ``` The `autocli` package will generate a command named `my-method` for the `MyMethod` method. The command will have flags for each field in the `MyRequest` message. It is possible to customize the generation of transactions and queries by defining options for each service. ## Application Wiring Here are the steps to use AutoCLI: 1. Ensure your app's modules implement the `appmodule.AppModule` interface. 2. (optional) Configure how `autocli` behaves during command generation, by implementing the `func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions` method on the module. 3. Call `app.AutoCliOpts()` to get an `autocli.AppOptions` populated from the module manager, then set `ClientCtx` on it to wire in the keyring. 4. Call `EnhanceRootCommand()` to add the generated CLI commands to your root command. AutoCLI is additive only, meaning *enhancing* the root command will only add subcommands that are not already registered. This means that you can use AutoCLI alongside other custom commands within your app. In practice this looks like (from the [example chain](https://github.com/cosmos/example/blob/main/exampled/cmd/root.go)): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} autoCliOpts := app.AutoCliOpts() autoCliOpts.ClientCtx = initClientCtx // wires keyring + node connection if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { panic(err) } ``` ### Keyring AutoCLI resolves key names and signs transactions using the keyring from `client.Context`. At runtime, it reads the keyring from the command's live context (set by `SetCmdClientContextHandler` in `PersistentPreRunE` — see [Root Command Setup](#root-command-setup)) and adapts it to the [`cosmossdk.io/client/v2/autocli/keyring`](https://pkg.go.dev/cosmossdk.io/client/v2/autocli/keyring) interface via `keyring.NewAutoCLIKeyring` internally. If no keyring is provided, AutoCLI-generated commands can still query the chain but cannot sign transactions. Because AutoCLI resolves key names from the keyring, you can use account names directly instead of addresses: ```sh theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} q bank balances alice tx bank send alice bob 1000denom ``` ## Signing `autocli` supports signing transactions with the keyring. The [`cosmos.msg.v1.signer` protobuf annotation](/sdk/latest/guides/reference/protobuf-annotations) defines the signer field of the message. This field is automatically filled when using the `--from` flag or defining the signer as a positional argument. AutoCLI currently supports only one signer per transaction. ## Module wiring & Customization The `AutoCLIOptions()` method on your module allows to specify custom commands, sub-commands or flags for each service, as it was a `cobra.Command` instance, within the `RpcCommandOptions` struct. Defining such options will customize the behavior of the `autocli` command generation, which by default generates a command for each method in your gRPC service. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} autocliv1.RpcCommandOptions{ RpcMethod: "Params", // The name of the gRPC service Use: "params", // Command usage that is displayed in the help Short: "Query the parameters of the governance process", // Short description of the command Long: "Query the parameters of the governance process. Specify specific param types (voting|tallying|deposit) to filter results.", // Long description of the command PositionalArgs: []*autocliv1.PositionalArgDescriptor{ { ProtoField: "params_type", Optional: true }, // Transform a flag into a positional argument }, } ``` AutoCLI can create a gov proposal of any tx by simply setting the `GovProposal` field to `true` in the `autocliv1.RpcCommandOptions` struct. Users can however use the `--no-proposal` flag to disable the proposal creation (which is useful if the authority isn't the gov module on a chain). ### Specifying Subcommands By default, `autocli` generates a command for each method in your gRPC service. However, you can specify subcommands to group related commands together. To specify subcommands, use the `autocliv1.ServiceCommandDescriptor` struct. For a real-world example, see the `gov` module's [`autocli.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/gov/autocli.go) in the Cosmos SDK. It demonstrates `ServiceCommandDescriptor` with `RpcCommandOptions`, `PositionalArgs`, `SubCommands`, `EnhanceCustomCommand`, and `GovProposal` all in one file. ### Positional Arguments By default `autocli` generates a flag for each field in your protobuf message. However, you can choose to use positional arguments instead of flags for certain fields. To add positional arguments to a command, use the `autocliv1.PositionalArgDescriptor` struct, as seen in the example below. Specify the `ProtoField` parameter, which is the name of the protobuf field that should be used as the positional argument. In addition, if the parameter is a variable-length argument, you can specify the `Varargs` parameter as `true`. This can only be applied to the last positional parameter, and the `ProtoField` must be a repeated field. For a real-world example, see the `auth` module's [`autocli.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/auth/autocli.go) in the Cosmos SDK. It shows positional args wired for every query method, with `address` as a positional argument on the `Account` method. After wiring positional args, the command can be used as follows, instead of having to specify the `--address` flag: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} query auth account cosmos1abcd...xyz ``` #### Flattened Fields in Positional Arguments AutoCLI also supports flattening nested message fields as positional arguments. This means you can access nested fields using dot notation in the `ProtoField` parameter. This is particularly useful when you want to directly set nested message fields as positional arguments. For example, if you have a nested message structure like this: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Permissions { string level = 1; repeated string limit_type_urls = 2; } message MsgAuthorizeCircuitBreaker { string grantee = 1; Permissions permissions = 2; } ``` You can flatten the fields in your AutoCLI configuration: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { RpcMethod: "AuthorizeCircuitBreaker", Use: "authorize [grantee] [level] [msg_type_urls]", PositionalArgs: []*autocliv1.PositionalArgDescriptor{ {ProtoField: "grantee"}, {ProtoField: "permissions.level"}, {ProtoField: "permissions.limit_type_urls", Varargs: true}, }, } ``` This allows users to provide values for nested fields directly as positional arguments: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} tx circuit authorize cosmos1... super-admin "/cosmos.bank.v1beta1.MsgSend" "/cosmos.bank.v1beta1.MsgMultiSend" ``` Instead of having to provide a complex JSON structure for nested fields, flattening makes the CLI more user-friendly by allowing direct access to nested fields. #### Customizing Flag Names By default, `autocli` generates flag names based on the names of the fields in your protobuf message. However, you can customize the flag names by providing a `FlagOptions`. This parameter allows you to specify custom names for flags based on the names of the message fields. For example, if you have a message with the fields `test` and `test1`, you can use the following naming options to customize the flags: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} autocliv1.RpcCommandOptions{ FlagOptions: map[string]*autocliv1.FlagOptions{ "test": { Name: "custom_name", }, "test1": { Name: "other_name", }, }, } ``` ### Combining AutoCLI with Other Commands Within A Module AutoCLI can be used alongside other commands within a module. For example, the `gov` module uses AutoCLI for its query commands while also keeping hand-written tx commands for `submit-proposal`, `weighted-vote`, and similar. Set `EnhanceCustomCommand: true` on each `ServiceCommandDescriptor` where you want AutoCLI to add generated commands alongside existing ones: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { return &autocliv1.ModuleOptions{ Query: &autocliv1.ServiceCommandDescriptor{ Service: govv1.Query_ServiceDesc.ServiceName, EnhanceCustomCommand: true, // keep hand-written gov query commands RpcCommandOptions: []*autocliv1.RpcCommandOptions{ /* ... */ }, }, Tx: &autocliv1.ServiceCommandDescriptor{ Service: govv1.Msg_ServiceDesc.ServiceName, EnhanceCustomCommand: true, // keep hand-written gov tx commands }, } } ``` If `EnhanceCustomCommand` is not set to `true`, AutoCLI skips command generation for any service that already has commands registered via `GetTxCmd()` or `GetQueryCmd()`. ### Skip a command AutoCLI checks the [`cosmos_proto.method_added_in` protobuf annotation](/sdk/latest/guides/reference/protobuf-annotations) and skips commands that were introduced in a newer SDK version than the one currently running. Additionally, a command can be manually skipped using the `autocliv1.RpcCommandOptions`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} autocliv1.RpcCommandOptions{ RpcMethod: "Params", // The name of the gRPC method Skip: true, } ``` ### Use AutoCLI for non module commands It is possible to use `AutoCLI` for non-module commands. The pattern is to add the options directly to `autoCliOpts.ModuleOptions` after calling `AutoCliOpts()`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} nodeCmds := nodeservice.NewNodeCommands() autoCliOpts.ModuleOptions[nodeCmds.Name()] = nodeCmds.AutoCLIOptions() ``` `AutoCliOpts()` only picks up modules registered with the module manager — non-module commands always need to be added to `ModuleOptions` manually, as the example chain does with `nodeservice.NewNodeCommands()`. For a more complete example of this pattern, see [`client/grpc/cmtservice/autocli.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/client/grpc/cmtservice/autocli.go) and [`client/grpc/node/autocli.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/client/grpc/node/autocli.go) in the Cosmos SDK. ## Root Command Setup For AutoCLI-generated commands (and hand-written commands) to work correctly — signing transactions, querying the chain, reading configuration — the root command must set up the `client.Context` and `server.Context` in a `PersistentPreRunE` function. This runs before every subcommand and makes both contexts available to all child commands. See [`simapp/simd/cmd/root.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/simapp/simd/cmd/root.go#L47-L70) for a complete example. The two key calls inside `PersistentPreRun` are: * `SetCmdClientContextHandler` reads persistent flags via `ReadPersistentCommandFlags`, creates a `client.Context`, and sets it on the command context. This is what AutoCLI and hand-written commands use to sign transactions and connect to a node. * `InterceptConfigsPreRunHandler` creates the `server.Context`, loads `app.toml` and `config.toml` from the node home directory, and binds them to the server context's viper instance. This is what makes application configuration available at startup. ### Custom logger By default, `InterceptConfigsPreRunHandler` sets the default SDK logger. To use a custom logger, use `InterceptConfigsAndCreateContext` instead and set the logger manually: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} -return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig) +serverCtx, err := server.InterceptConfigsAndCreateContext(cmd, customAppTemplate, customAppConfig, customCMTConfig) +if err != nil { + return err +} +// overwrite default server logger +logger, err := server.CreateSDKLogger(serverCtx, cmd.OutOrStdout()) +if err != nil { + return err +} +serverCtx.Logger = logger.With(log.ModuleKey, "server") +// set server context +return server.SetCmdServerContext(cmd, serverCtx) ``` ## Environment Variables Every CLI flag is automatically bound to an environment variable. The variable name is the app's `basename` in uppercase followed by the flag name, with `-` replaced by `_`. For example, `--node` for an app with basename `GAIA` binds to `GAIA_NODE`. This lets you pre-configure common flags instead of passing them on every command: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # set once in .env or shell profile GAIA_HOME= GAIA_NODE= GAIA_CHAIN_ID="cosmoshub-4" GAIA_KEYRING_BACKEND="test" # then just run gaiad tx bank send alice bob 1000uatom --fees 500uatom ``` ## Hand-Written Commands AutoCLI covers the standard case: one protobuf RPC method maps to one CLI command. For commands that don't fit that model, you can write Cobra commands manually and combine them with AutoCLI using `EnhanceCustomCommand: true`. Common reasons to write a command manually: * **Complex argument parsing** — multiple positional args that require custom validation or coin parsing before the message is built * **Commands that span multiple RPC calls** — e.g., building a transaction from inputs that require a preceding query * **Non-standard UX** — interactive prompts, offline signing flows, or commands that generate output rather than broadcast ### Pattern A manual transaction command uses `client.GetClientTxContext` to retrieve the signing context, constructs a message, and passes it to `tx.GenerateOrBroadcastTxCLI`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewSendTxCmd(ac address.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "send [from_key_or_address] [to_address] [amount]", Short: "Send tokens from one account to another", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { // set --from from the first positional arg if err := cmd.Flags().Set(flags.FlagFrom, args[0]); err != nil { return err } clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } toAddr, err := ac.StringToBytes(args[1]) if err != nil { return err } coins, err := sdk.ParseCoinsNormalized(args[2]) if err != nil { return err } msg := types.NewMsgSend(clientCtx.GetFromAddress(), toAddr, coins) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) return cmd } ``` Key elements: * `client.GetClientTxContext(cmd)` retrieves the client context (signer, node connection, codec) * `flags.AddTxFlagsToCmd(cmd)` adds standard transaction flags (`--from`, `--fees`, `--gas`, etc.) * `tx.GenerateOrBroadcastTxCLI` handles both `--generate-only` (offline) and live broadcast modes # Confix Source: https://docs.cosmos.network/sdk/latest/guides/tooling/confix Confix is a configuration management tool that allows you to manage your configuration via CLI. `Confix` is a configuration management tool that allows you to manage your configuration via CLI. It is based on the [CometBFT RFC 019](https://github.com/cometbft/cometbft/blob/5013bc3f4a6d64dcc2bf02ccc002ebc9881c62e4/docs/rfc/rfc-019-config-version.md). ## Installation ### Add Config Command To add the confix tool, it's required to add the `ConfigCommand` to your application's root command file (e.g. `/cmd/root.go`). Import the `confixCmd` package: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import confixcmd "cosmossdk.io/tools/confix/cmd" ``` Inside your `initRootCmd` function, add the command to the root: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rootCmd.AddCommand( confixcmd.ConfigCommand(), ) ``` The `ConfigCommand` function builds the `config` root command and is defined in the `confixcmd` package (`cosmossdk.io/tools/confix/cmd`). An implementation example can be found in `simapp`. The command will be available as `simd config`. Using confix directly in the application can have less features than using it standalone. This is because confix is versioned with the SDK, while `latest` is the standalone version. ### Using Confix Standalone To use Confix standalone, without having to add it in your application, install it with the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go install cosmossdk.io/tools/confix/cmd/confix@latest ``` Alternatively, for building from source, simply run `make confix`. The binary will be located in `tools/confix`. ## Usage Use standalone: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} confix --help ``` Use in simd: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd config --help ``` ### Get Get a configuration value, e.g.: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd config get app pruning # gets the value pruning from app.toml simd config get client chain-id # gets the value chain-id from client.toml ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} confix get ~/.simapp/config/app.toml pruning # gets the value pruning from app.toml confix get ~/.simapp/config/client.toml chain-id # gets the value chain-id from client.toml ``` ### Set Set a configuration value, e.g.: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd config set app pruning "enabled" # sets the value pruning from app.toml simd config set client chain-id "foo-1" # sets the value chain-id from client.toml ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} confix set ~/.simapp/config/app.toml pruning "enabled" # sets the value pruning from app.toml confix set ~/.simapp/config/client.toml chain-id "foo-1" # sets the value chain-id from client.toml ``` ### Migrate Migrate a configuration file to a new version, config type defaults to `app.toml`, if you want to change it to `client.toml`, please indicate it by adding the optional parameter, e.g.: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd config migrate v0.53 # migrates defaultHome/config/app.toml to the latest v0.53 config simd config migrate v0.53 --client # migrates defaultHome/config/client.toml to the latest v0.53 config ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} confix migrate v0.53 ~/.simapp/config/app.toml # migrate ~/.simapp/config/app.toml to the latest v0.53 config confix migrate v0.53 ~/.simapp/config/client.toml --client # migrate ~/.simapp/config/client.toml to the latest v0.53 config ``` ### Diff Get the diff between a given configuration file and the default configuration file, e.g.: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd config diff v0.53 # gets the diff between defaultHome/config/app.toml and the latest v0.53 config simd config diff v0.53 --client # gets the diff between defaultHome/config/client.toml and the latest v0.53 config ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} confix diff v0.53 ~/.simapp/config/app.toml # gets the diff between ~/.simapp/config/app.toml and the latest v0.53 config confix diff v0.53 ~/.simapp/config/client.toml --client # gets the diff between ~/.simapp/config/client.toml and the latest v0.53 config ``` ### View View a configuration file, e.g: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd config view client # views the current app client config ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} confix view ~/.simapp/config/client.toml # views the current app client config ``` ### Maintainer At each SDK modification of the default configuration, add the default SDK config under `data/vXX-app.toml`. This allows users to use the tool standalone. ## Credits This project is based on the [CometBFT RFC 019](https://github.com/cometbft/cometbft/blob/5013bc3f4a6d64dcc2bf02ccc002ebc9881c62e4/docs/rfc/rfc-019-config-version.md) and their never released own implementation of [confix](https://github.com/cometbft/cometbft/blob/v0.36.x/scripts/confix/confix.go). # Tool Guide Source: https://docs.cosmos.network/sdk/latest/guides/tooling/tool-guide What tools should I use and for what? A practical guide to the Cosmos SDK developer toolbox. A practical reference for Cosmos chain and module developers: what each tool does and when to reach for it. ## Code generation **[Buf](https://buf.build/cosmos/cosmos-sdk/docs/main)** — Compiles `.proto` files into Go types, gRPC stubs, and REST gateway code. The standard way to run `proto-gen` in a Cosmos project. Also lints and formats proto files, and publishes generated docs to the Buf registry. See the [Protobuf Documentation](https://buf.build/cosmos/cosmos-sdk/docs/main) on the Buf registry. **[Protobuf Annotations](/sdk/latest/guides/reference/protobuf-annotations)** — Cosmos SDK-specific proto field options (scalar descriptors, amino names, query pagination, etc.) that affect code generation output. Consult this when writing `.proto` files for a new module. **[AutoCLI](/sdk/latest/guides/tooling/autocli)** — Generates CLI commands and gRPC-gateway routes for your module's messages and queries directly from proto definitions. Use it instead of hand-writing CLI commands — it also handles pagination, output formatting, and custom flag mappings. ## Client library **[CosmJS](https://github.com/cosmos/cosmjs)** — The official JavaScript and TypeScript library for building clients, frontends, and scripts that interact with Cosmos chains. Handles transaction signing, broadcasting, querying, and wallet integration in browser and Node.js environments. ## State management **[Collections](/sdk/latest/guides/state/collections)** — A typed abstraction over raw `KVStore` access. Handles key encoding, prefix isolation, iteration, and secondary indexes. Also produces a schema used automatically by simulation decoders. Use it for all new module state instead of raw byte keys. **[Store](/sdk/latest/guides/state/store)** — Reference documentation for the SDK store layer: `KVStore`, `CommitMultiStore`, `CacheKVStore`, IAVL, pruning strategies, and store versioning. Read this when you need to understand what is happening under the collections abstraction or need to work with stores directly. ## Testing **[Testing](/sdk/latest/learn/concepts/testing)** — The SDK's testing conventions: unit tests for keepers and message servers, integration tests wired with `depinject`, and end-to-end tests using the `testnet` package. **[Module Simulation](/sdk/latest/guides/testing/simulator)** — A fuzz-testing framework that runs your module's messages with randomized inputs and genesis states. Checks for panics, non-determinism, and import/export inconsistencies. Use it to catch edge cases that unit tests miss. ## Node setup and operations **[Prerequisites](/sdk/latest/node/prerequisites)** — Required software and environment setup before running a node. **[Run a Node](/sdk/latest/node/run-node)** — How to initialize a chain, configure genesis, and start a node with `simd`. **[Run a Testnet](/sdk/latest/node/run-testnet)** — Running a local multi-node testnet using `simd testnet`. **[Production Deployment](/sdk/latest/node/run-production)** — Hardening and deployment guidance for running a node in production: systemd, state sync, backup strategies, and security considerations. **[Cosmovisor](/sdk/latest/guides/upgrades/cosmovisor)** — A process manager for your chain binary that watches for on-chain upgrade proposals and automatically swaps in the new binary at the correct upgrade height. Required for zero-downtime upgrades in production. **[Confix](/sdk/latest/guides/tooling/confix)** — A CLI tool for reading, setting, migrating, and diffing `app.toml` and `client.toml` configuration files across SDK versions. Use it when upgrading a node between SDK versions or scripting config changes. ## Keys and transactions **[Keyring](/sdk/latest/node/keyring)** — The SDK's key management layer. Covers keyring backends (`os`, `file`, `test`, `memory`), key types, and how to manage keys via `simd keys`. Use this to understand key storage security trade-offs in production deployments. **[Building Transactions](/sdk/latest/node/txs)** — How to programmatically construct, sign, encode, and broadcast transactions using the SDK's `TxBuilder` and `TxConfig` APIs. **[Interacting with a Node](/sdk/latest/node/interact-node)** — Using the CLI and gRPC to query state and broadcast transactions against a running node. ## Observability **[Telemetry](/sdk/latest/guides/testing/telemetry)** — OpenTelemetry-based metrics for the SDK and your modules. Emit counters, gauges, and histograms from keeper methods. Integrates with Prometheus and any OTLP-compatible backend. **[Logging](/sdk/latest/guides/testing/log)** — Structured logging via `cosmossdk.io/log` (backed by zerolog). Use it in keepers and servers to emit structured log lines, with support for log correlation and OpenTelemetry log export. ## IBC **[IBC Go](/ibc)** — The canonical IBC implementation for Cosmos SDK chains. Use it to add cross-chain token transfers and arbitrary message passing to your chain. # Cosmovisor Source: https://docs.cosmos.network/sdk/latest/guides/upgrades/cosmovisor `cosmovisor` is a process manager for Cosmos SDK application binaries that automates application binary switch at chain upgrades. It polls the `upgrade-info.json` file that is created by the x/upgrade module at upgrade height, and then can automatically download the new binary, stop the current binary, switch from the old binary to the new one, and finally restart the node with the new binary. * [Design](#design) * [Contributing](#contributing) * [Setup](#setup) * [Installation](#installation) * [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables) * [Folder Layout](#folder-layout) * [Usage](#usage) * [Initialization](#initialization) * [Detecting Upgrades](#detecting-upgrades) * [Adding Upgrade Binary](#adding-upgrade-binary) * [Auto-Download](#auto-download) * [Preparing for an Upgrade](#preparing-for-an-upgrade) * [Example: SimApp Upgrade](#example-simapp-upgrade) * [Chain Setup](#chain-setup) * [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain) * [Update App](#update-app) * [Pre-Upgrade Handling](#pre-upgrade-handling) ## Design Cosmovisor is designed to be used as a wrapper for a `Cosmos SDK` app: * it will pass arguments to the associated app (configured by `DAEMON_NAME` env variable). Running `cosmovisor run arg1 arg2 ....` will run `app arg1 arg2 ...`; * it will manage an app by restarting and upgrading if needed; * it is configured using environment variables, not positional arguments. If new versions of the application are not set up to run in-place store migrations, migrations must be run manually before restarting `cosmovisor` with the new binary. For this reason, applications should adopt in-place store migrations. Only the latest version of cosmovisor is actively developed/maintained. Versions prior to v1.0.0 have a vulnerability that could lead to a DOS. Please upgrade to the latest version. ## Contributing Cosmovisor is part of the Cosmos SDK monorepo, but it's a separate module with its own release schedule. Release branches have the following format `release/cosmovisor/vA.B.x`, where A and B are a number (e.g. `release/cosmovisor/v1.3.x`). Releases are tagged using the following format: `cosmovisor/vA.B.C`. ## Setup ### Installation You can download Cosmovisor from the [GitHub releases](https://github.com/cosmos/cosmos-sdk/releases/tag/cosmovisor%2Fv1.7.1). To install the latest version of `cosmovisor`, run the following command: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest ``` To install a specific version, you can specify the version: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@v1.7.1 ``` Run `cosmovisor version` to check the cosmovisor version. Alternatively, for building from source, simply run `make cosmovisor`. The binary will be located in `tools/cosmovisor`. Installing cosmovisor using `go install` will display the correct `cosmovisor` version. Building from source (`make cosmovisor`) or installing `cosmovisor` by other means won't display the correct version. ### Command Line Arguments And Environment Variables The first argument passed to `cosmovisor` is the action for `cosmovisor` to take. Options are: * `help`, `--help`, or `-h` - Output `cosmovisor` help information and check your `cosmovisor` configuration. * `run` - Run the configured binary using the rest of the provided arguments. * `version` - Output the `cosmovisor` version and also run the binary with the `version` argument. * `config` - Display the current `cosmovisor` configuration, that means displaying the environment variables value that `cosmovisor` is using. * `add-upgrade` - Add an upgrade manually to `cosmovisor`. This command allow you to easily add the binary corresponding to an upgrade in cosmovisor. * `add-batch-upgrade` - Add multiple upgrades at once. * `show-upgrade-info` - Show the current upgrade info from the upgrade-info.json file. * `prepare-upgrade` - Download and place the next scheduled upgrade's binary ahead of time, using upgrade info read from the chain over gRPC. All arguments passed to `cosmovisor run` will be passed to the application binary (as a subprocess). `cosmovisor` will return `/dev/stdout` and `/dev/stderr` of the subprocess as its own. For this reason, `cosmovisor run` cannot accept any command-line arguments other than those available to the application binary. `cosmovisor` reads its configuration from environment variables, or its configuration file (use `--cosmovisor-config `): * `DAEMON_HOME` is the location where the `cosmovisor/` directory is kept that contains the genesis binary, the upgrade binaries, and any additional auxiliary files associated with each binary (e.g. `$HOME/.gaiad`, `$HOME/.regend`, `$HOME/.simd`, etc.). * `DAEMON_NAME` is the name of the binary itself (e.g. `gaiad`, `regend`, `simd`, etc.). * `DAEMON_ALLOW_DOWNLOAD_BINARIES` (*optional*), if set to `true`, will enable auto-downloading of new binaries (for security reasons, this is intended for full nodes rather than validators). By default, `cosmovisor` will not auto-download new binaries. * `DAEMON_DOWNLOAD_MUST_HAVE_CHECKSUM` (*optional*, default = `false`), if `true` cosmovisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. If `false`, cosmovisor will not require a checksum to be provided, but still check the checksum if one is provided. * `DAEMON_RESTART_AFTER_UPGRADE` (*optional*, default = `true`), if `true`, restarts the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (`false`), `cosmovisor` stops running after an upgrade and requires the system administrator to manually restart it. Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs. * `DAEMON_RESTART_DELAY` (*optional*, default none), allow a node operator to define a delay between the node halt (for upgrade) and backup by the specified time. The value must be a duration (e.g. `1s`). * `DAEMON_SHUTDOWN_GRACE` (*optional*, default none), if set, send interrupt to binary and wait the specified time to allow for cleanup/cache flush to disk before sending the kill signal. The value must be a duration (e.g. `1s`). * `DAEMON_POLL_INTERVAL` (*optional*, default 300 milliseconds), is the interval length for polling the upgrade plan file. The value must be a duration (e.g. `1s`). * `DAEMON_DATA_BACKUP_DIR` option to set a custom backup directory. If not set, `DAEMON_HOME` is used. * `UNSAFE_SKIP_BACKUP` (defaults to `false`), if set to `true`, upgrades directly without performing a backup. Otherwise (`false`, default) backs up the data before trying the upgrade. The default value of false is useful and recommended in case of failures and when a backup needed to rollback. We recommend using the default backup option `UNSAFE_SKIP_BACKUP=false`. * `DAEMON_PREUPGRADE_MAX_RETRIES` (defaults to `0`). The maximum number of times to retry [`pre-upgrade`](#pre-upgrade-handling) after exit status of `31`. With the default of `0`, a single exit-31 result immediately fails the upgrade. After retries are exhausted, Cosmovisor fails the upgrade. * `DAEMON_GRPC_ADDRESS` (*optional*, default `localhost:9090`). The gRPC address of the node, used by the `prepare-upgrade` command and the batch upgrade watcher. * `COSMOVISOR_DISABLE_LOGS` (defaults to `false`). If set to true, this will disable Cosmovisor logs (but not the underlying process) completely. This may be useful, for example, when a Cosmovisor subcommand you are executing returns a valid JSON you are then parsing, as logs added by Cosmovisor make this output not a valid JSON. * `COSMOVISOR_COLOR_LOGS` (defaults to `true`). If set to true, this will colorize Cosmovisor logs (but not the underlying process). * `COSMOVISOR_TIMEFORMAT_LOGS` (defaults to `kitchen`). If set to a value (`layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen`), this will add timestamp prefix to Cosmovisor logs (but not the underlying process). * `COSMOVISOR_CUSTOM_PREUPGRADE` (defaults to \`\`). If set, this will run $DAEMON\_HOME/cosmovisor/$COSMOVISOR\_CUSTOM\_PREUPGRADE prior to upgrade with the arguments \[ upgrade.Name, upgrade.Height ]. Executes a custom script (separate and prior to the chain daemon pre-upgrade command) * `COSMOVISOR_DISABLE_RECASE` (defaults to `false`). If set to true, the upgrade directory will expected to match the upgrade plan name without any case changes ### Folder Layout `$DAEMON_HOME/cosmovisor` is expected to belong completely to `cosmovisor` and the subprocesses that are controlled by it. The folder content is organized as follows: ```text expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} . ├── current -> genesis or upgrades/ ├── genesis │   └── bin │   └── $DAEMON_NAME └── upgrades │ └── │ ├── bin │ │   └── $DAEMON_NAME │ └── upgrade-info.json └── preupgrade.sh (optional) ``` The `cosmovisor/` directory includes a subdirectory for each version of the application (i.e. `genesis` or `upgrades/`). Within each subdirectory is the application binary (i.e. `bin/$DAEMON_NAME`) and any additional auxiliary files associated with each binary. `current` is a symbolic link to the currently active directory (i.e. `genesis` or `upgrades/`). The `name` variable in `upgrades/` is the lowercased URI-encoded name of the upgrade as specified in the upgrade module plan. Note that the upgrade name path are normalized to be lowercased: for instance, `MyUpgrade` is normalized to `myupgrade`, and its path is `upgrades/myupgrade`. Please note that `$DAEMON_HOME/cosmovisor` only stores the *application binaries*. The `cosmovisor` binary itself can be stored in any typical location (e.g. `/usr/local/bin`). The application will continue to store its data in the default data directory (e.g. `$HOME/.simapp`) or the data directory specified with the `--home` flag. `$DAEMON_HOME` is dependent of the data directory and must be set to the same directory as the data directory, you will end up with a configuration like the following: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} .simapp ├── config ├── data └── cosmovisor ``` ## Usage The system administrator is responsible for: * installing the `cosmovisor` binary * configuring the host's init system (e.g. `systemd`, `launchd`, etc.) * appropriately setting the environmental variables * creating the `/cosmovisor` directory * creating the `/cosmovisor/genesis/bin` folder * creating the `/cosmovisor/upgrades//bin` folders * placing the different versions of the `` executable in the appropriate `bin` folders. `cosmovisor` will set the `current` link to point to `genesis` at first start (i.e. when no `current` link exists) and then handle switching binaries at the correct points in time so that the system administrator can prepare days in advance and relax at upgrade time. In order to support downloadable binaries, a tarball for each upgrade binary will need to be packaged up and made available through a canonical URL. Additionally, a tarball that includes the genesis binary and all available upgrade binaries can be packaged up and made available so that all the necessary binaries required to sync a fullnode from start can be easily downloaded. The `DAEMON` specific code and operations (e.g. CometBFT config, the application db, syncing blocks, etc.) all work as expected. The application binaries' directives such as command-line flags and environment variables also work as expected. ### Initialization The `cosmovisor init ` command creates the folder structure required for using cosmovisor. It does the following: * creates the `/cosmovisor` folder if it doesn't yet exist * creates the `/cosmovisor/genesis/bin` folder if it doesn't yet exist * copies the provided executable file to `/cosmovisor/genesis/bin/` * creates the `current` link, pointing to the `genesis` folder It uses the `DAEMON_HOME` and `DAEMON_NAME` environment variables for folder location and executable name. The `cosmovisor init` command is specifically for initializing cosmovisor, and should not be confused with a chain's `init` command (e.g. `cosmovisor run init`). ### Detecting Upgrades `cosmovisor` is polling the `$DAEMON_HOME/data/upgrade-info.json` file for new upgrade instructions. The file is created by the x/upgrade module in `PreBlocker` when an upgrade is detected and the blockchain reaches the upgrade height. The following heuristic is applied to detect the upgrade: * When starting, `cosmovisor` doesn't know much about currently running upgrade, except the binary which is `current/bin/`. It tries to read the `current/upgrade-info.json` file to get information about the current upgrade name. * If neither `cosmovisor/current/upgrade-info.json` nor `data/upgrade-info.json` exist, then `cosmovisor` will wait for `data/upgrade-info.json` file to trigger an upgrade. * If `cosmovisor/current/upgrade-info.json` doesn't exist but `data/upgrade-info.json` exists, then `cosmovisor` assumes that whatever is in `data/upgrade-info.json` is a valid upgrade request. In this case `cosmovisor` tries immediately to make an upgrade according to the `name` attribute in `data/upgrade-info.json`. * Otherwise, `cosmovisor` waits for changes in `upgrade-info.json`. As soon as a new upgrade name is recorded in the file, `cosmovisor` will trigger an upgrade mechanism. When the upgrade mechanism is triggered, `cosmovisor` will: 1. if `DAEMON_ALLOW_DOWNLOAD_BINARIES` is enabled, start by auto-downloading a new binary into `cosmovisor//bin` (where `` is the `upgrade-info.json:name` attribute); 2. update the `current` symbolic link to point to the new directory and save `data/upgrade-info.json` to `cosmovisor/current/upgrade-info.json`. ### Adding Upgrade Binary `cosmovisor` has an `add-upgrade` command that allows to easily link a binary to an upgrade. It creates a new folder in `cosmovisor/upgrades/` and copies the provided executable file to `cosmovisor/upgrades//bin/`. Using the `--upgrade-height` flag allows you to specify at which height the binary should be switched, without going via a governance proposal. This enables support for an emergency coordinated upgrades where the binary must be switched at a specific height, but there is no time to go through a governance proposal. `--upgrade-height` creates an `upgrade-info.json` file. This means if a chain upgrade via governance proposal is executed before the specified height with `--upgrade-height`, the governance proposal will overwrite the `upgrade-info.json` plan created by `add-upgrade --upgrade-height `. Take this into consideration when using `--upgrade-height`. ### Auto-Download Generally, `cosmovisor` requires that the system administrator place all relevant binaries on disk before the upgrade happens. However, for people who don't need such control and want an automated setup (maybe they are syncing a non-validating fullnode and want to do little maintenance), there is another option. **NOTE: we don't recommend using auto-download** because it doesn't verify in advance if a binary is available. If there will be any issue with downloading a binary, the cosmovisor will stop and won't restart an App (which could lead to a chain halt). If `DAEMON_ALLOW_DOWNLOAD_BINARIES` is set to `true`, and no local binary can be found when an upgrade is triggered, `cosmovisor` will attempt to download and install the binary itself based on the instructions in the `info` attribute in the `data/upgrade-info.json` file. The files is constructed by the x/upgrade module and contains data from the upgrade `Plan` object. The `Plan` has an info field that is expected to have one of the following two valid formats to specify a download: 1. Store an os/architecture -> binary URI map in the upgrade plan info field as JSON under the `"binaries"` key. For example: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "binaries": { "linux/amd64": "https://example.com/gaia.zip?checksum=sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f" } } ``` You can include multiple binaries at once to ensure more than one environment will receive the correct binaries: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "binaries": { "linux/amd64": "https://example.com/gaia.zip?checksum=sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "linux/arm64": "https://example.com/gaia.zip?checksum=sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "darwin/amd64": "https://example.com/gaia.zip?checksum=sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f" } } ``` When submitting this as a proposal ensure there are no spaces. An example command using `gaiad` could look like: ```shell expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} > gaiad tx upgrade software-upgrade Vega \ --title Vega \ --deposit 100uatom \ --upgrade-height 7368420 \ --upgrade-info '{"binaries":{"linux/amd64":"https://github.com/cosmos/gaia/releases/download/v6.0.0-rc1/gaiad-v6.0.0-rc1-linux-amd64","linux/arm64":"https://github.com/cosmos/gaia/releases/download/v6.0.0-rc1/gaiad-v6.0.0-rc1-linux-arm64","darwin/amd64":"https://github.com/cosmos/gaia/releases/download/v6.0.0-rc1/gaiad-v6.0.0-rc1-darwin-amd64"}}' \ --summary "upgrade to Vega" \ --gas 400000 \ --from user \ --chain-id test \ --home test/val2 \ --node tcp://localhost:36657 \ --yes ``` 2. Store a link to a file that contains all information in the above format (e.g. if you want to specify lots of binaries, changelog info, etc. without filling up the blockchain). For example: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} https://example.com/testnet-1001-info.json?checksum=sha256:deaaa99fda9407c4dbe1d04bd49bab0cc3c1dd76fa392cd55a9425be074af01e ``` When `cosmovisor` is triggered to download the new binary, `cosmovisor` will parse the `"binaries"` field, download the new binary with [go-getter](https://github.com/hashicorp/go-getter), and unpack the new binary in the `upgrades/` folder so that it can be run as if it was installed manually. Note that for this mechanism to provide strong security guarantees, all URLs should include a SHA 256/512 checksum. This ensures that no false binary is run, even if someone hacks the server or hijacks the DNS. `go-getter` will always ensure the downloaded file matches the checksum if it is provided. `go-getter` will also handle unpacking archives into directories (in this case the download link should point to a `zip` file of all data in the `bin` directory). To properly create a sha256 checksum on linux, you can use the `sha256sum` utility. For example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sha256sum ./testdata/repo/zip_directory/autod.zip ``` The result will look something like the following: `29139e1381b8177aec909fab9a75d11381cab5adf7d3af0c05ff1c9c117743a7`. You can also use `sha512sum` if you would prefer to use longer hashes, or `md5sum` if you would prefer to use broken hashes. Whichever you choose, make sure to set the hash algorithm properly in the checksum argument to the URL. ### Preparing for an Upgrade To prepare for an upgrade, use the `prepare-upgrade` command: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmovisor prepare-upgrade ``` This command performs the following actions: 1. Retrieves upgrade information directly from the blockchain about the next scheduled upgrade. 2. Downloads the new binary specified in the upgrade plan. 3. Verifies the binary's checksum (if required by configuration). 4. Places the new binary in the appropriate directory for Cosmovisor to use during the upgrade. This command requires gRPC to be enabled on the node (configured via `DAEMON_GRPC_ADDRESS`, default `localhost:9090`). The `prepare-upgrade` command logs the following: * The name and height of the upcoming upgrade * The URL from which the new binary is being downloaded * Confirmation of successful completion Example output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} INFO Preparing for upgrade name=v1.0.0 height=1000000 INFO Downloading upgrade binary url=https://example.com/binary/v1.0.0?checksum=sha256:339911508de5e20b573ce902c500ee670589073485216bee8b045e853f24bce8 INFO Upgrade preparation complete name=v1.0.0 height=1000000 ``` Downloading manually and placing the binary in the right location still works. ## Example: SimApp Upgrade The following instructions demonstrate `cosmovisor` using the simulation application (`simapp`) shipped with the Cosmos SDK's source code, upgrading a chain from `v0.53.7` to `v0.54.3`. This pair uses the in-repo `v053-to-v054` upgrade handler defined in `simapp/upgrades.go` on the `v0.54.x` line, so no custom code is required. Run these commands from within a clone of the `cosmos-sdk` repository. ### Chain Setup Build the `v0.53.7` version of `simd` (the Cosmos SDK demo app): ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git checkout v0.53.7 make build ``` Initialize the node, overwriting any previous configuration (never do this in a production environment): ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./build/simd init test --chain-id test --overwrite ``` Set up the client config: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./build/simd config set client chain-id test ./build/simd config set client keyring-backend test ./build/simd config set client broadcast-mode sync ``` Clear any previous chain data (never do this in a production environment): ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./build/simd comet unsafe-reset-all ``` For the sake of this demonstration, reduce the governance voting period to `20s`. You must also reduce `expedited_voting_period` because it has to stay strictly less than `voting_period`, otherwise `gentx` and node startup fail genesis validation: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cat <<< $(jq '.app_state.gov.params.voting_period = "20s" | .app_state.gov.params.expedited_voting_period = "10s"' $HOME/.simapp/config/genesis.json) > $HOME/.simapp/config/genesis.json ``` Create a validator and set up the genesis transaction: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./build/simd keys add validator --keyring-backend test ./build/simd genesis add-genesis-account validator 1000000000stake --keyring-backend test ./build/simd genesis gentx validator 1000000stake --chain-id test --keyring-backend test ./build/simd genesis collect-gentxs ``` #### Prepare Cosmovisor and Start the Chain Set the required environment variables: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} export DAEMON_NAME=simd export DAEMON_HOME=$HOME/.simapp ``` Set the optional environment variable to trigger an automatic app restart after the upgrade: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} export DAEMON_RESTART_AFTER_UPGRADE=true ``` Initialize cosmovisor with the current binary. This creates `$DAEMON_HOME/cosmovisor/genesis/bin/simd` and the `current` symlink: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmovisor init ./build/simd ``` Now run the chain through cosmovisor with simapp v0.53.7: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmovisor run start ``` ### Update App Update the app to `v0.54.3`. Migration plans are defined using the `x/upgrade` module and described in [Upgrading Modules](/sdk/latest/guides/upgrades/upgrade). Migrations can perform any deterministic state change. The upgrade name and handler for this example (`const UpgradeName = "v053-to-v054"`) are defined in `simapp/upgrades.go` on the `v0.54.x` branch. In a second terminal, build the new version of `simd`: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git checkout v0.54.3 make build ``` Register the new binary with cosmovisor under the upgrade name. This copies it to `$DAEMON_HOME/cosmovisor/upgrades/v053-to-v054/bin/simd`: The upgrade name must match the one defined in the migration plan (`v053-to-v054`). ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmovisor add-upgrade v053-to-v054 ./build/simd ``` Submit the software-upgrade proposal, then vote on it. Include an initial `--deposit` that meets the minimum deposit so the proposal enters the voting period immediately; both commands must run within the 20-second voting period: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./build/simd tx upgrade software-upgrade v053-to-v054 --title upgrade --summary upgrade --upgrade-height 85 --upgrade-info "{}" --no-validate --deposit 10000000stake --from validator --keyring-backend test --chain-id test --yes ./build/simd tx gov vote 1 yes --from validator --keyring-backend test --chain-id test --yes ``` Pick an `--upgrade-height` that the chain will reach *after* the proposal passes; adjust it if your setup takes longer. With the default block time this example passes well before height 85. At the upgrade height, the running `v0.53.7` binary halts, and cosmovisor switches to the `v0.54.3` binary, runs the migrations, and restarts the node automatically: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ERR UPGRADE "v053-to-v054" NEEDED at height: 85: {} module=x/upgrade INF pre-upgrade command does not exist. continuing the upgrade. module=cosmovisor INF applying upgrade "v053-to-v054" at height: 85 module=x/upgrade INF running migrations for module: auth module=baseapp ... ``` Confirm the upgrade was applied and the chain has continued past the upgrade height: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./build/simd query upgrade applied v053-to-v054 ./build/simd status ``` ## Pre-Upgrade Handling Cosmovisor supports custom pre-upgrade handling. Use pre-upgrade handling when you need to implement application config changes that are required in the newer version before you perform the upgrade. If pre-upgrade handling is not implemented, the upgrade continues normally. Before the application binary is upgraded, Cosmovisor calls a `pre-upgrade` command that can be implemented by the application. The `pre-upgrade` command does not take in any command-line arguments and is expected to terminate with the following exit codes: | Exit status code | How it is handled in Cosmovisor | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `0` | `pre-upgrade` command executed successfully. Cosmovisor continues the upgrade. | | `1` | `pre-upgrade` command is not implemented. Cosmovisor continues the upgrade normally. | | `30` | `pre-upgrade` command failed. Cosmovisor fails the entire upgrade. | | `31` | `pre-upgrade` command failed. Cosmovisor retries until exit code `1` or `30` are returned, or until `DAEMON_PREUPGRADE_MAX_RETRIES` retries are exhausted (at which point the upgrade fails). | The number of allowed retries for exit code `31` is configured via `DAEMON_PREUPGRADE_MAX_RETRIES` (defaults to `0`, meaning no retries -- a single exit-31 result immediately fails the upgrade). Sample `pre-upgrade` command implementation: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func preUpgradeCommand() *cobra.Command { return &cobra.Command{ Use: "pre-upgrade", Short: "Pre-upgrade command", Run: func(cmd *cobra.Command, args []string) { if err := HandlePreUpgrade(); err != nil { os.Exit(30) } os.Exit(0) }, } } ``` Register it in the root command: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rootCmd.AddCommand( // .. preUpgradeCommand(), ) ``` When not using Cosmovisor, install the new binary first, then run ` pre-upgrade` before starting it. The pre-upgrade command is part of the new binary, not the old one. # Upgrades and Store Migrations Source: https://docs.cosmos.network/sdk/latest/guides/upgrades/upgrade Read and understand all of this page before running a migration on a live chain. **Synopsis** In-place store migrations let modules ship breaking state changes during a chain upgrade. This page covers how an upgrade runs, setting up `x/upgrade`, writing and registering migrations, running them in an upgrade handler, and a worked example. The Cosmos SDK supports two approaches to chain upgrades: exporting the entire application state to JSON and starting fresh with a modified genesis file, or performing in-place store migrations that update state directly. In-place migrations are significantly faster for chains with large state and are the standard approach for live networks. This page covers the in-place approach. ## How an upgrade works An upgrade is scheduled on-chain, usually through governance, and runs in this order: 1. Someone submits a governance proposal containing a `MsgSoftwareUpgrade` whose `Plan` names the upgrade (matching the name passed to `SetUpgradeHandler`) and sets a target height. 2. Validators vote. If the proposal passes, `x/upgrade` records the plan. 3. At the plan height every node halts and writes `upgrade-info.json` to its home directory. 4. The node operator, or Cosmovisor, starts the new binary. During the next block's `PreBlock`, `x/upgrade` sees the due plan and runs the registered upgrade handler, which calls `RunMigrations`. 5. The chain continues on the new binary with migrated state. A `Plan` is the on-chain upgrade record: a name and a target height. The `VersionMap` is a map of module name to consensus version, stored by `x/upgrade`, recording the version each module's state was last migrated to. The sections below cover each piece. The [Cosmovisor](/sdk/latest/guides/upgrades/cosmovisor) guide covers the node-operator side. ## Consensus version Successful upgrades of existing modules require each `AppModule` to implement the function `ConsensusVersion() uint64`. * The versions must be hard-coded by the module developer. * The initial version **must** be set to 1. Consensus versions serve as state-breaking versions of app modules and must be incremented when the module introduces breaking changes. `RunMigrations` compares these against the `VersionMap` to decide which migrations to run. ## Set up x/upgrade The rest of this page assumes the app has `x/upgrade` wired in. Create the `UpgradeKeeper` before the module manager so the upgrade module can be registered, register the module, and run its `PreBlocker`. See the full wiring in the [cosmos/example](https://github.com/cosmos/example) app. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "github.com/cosmos/cosmos-sdk/x/upgrade" upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" ) // Reserve a store key for x/upgrade alongside the other module keys: // upgradetypes.StoreKey // Create the UpgradeKeeper before the module manager. homePath is where // ReadUpgradeInfoFromDisk looks for the upgrade-info.json a node writes at the // halt height. The authority is usually the governance module account. app.UpgradeKeeper = upgradekeeper.NewKeeper( skipUpgradeHeights, runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // Register the upgrade module with the module manager. app.ModuleManager = module.NewManager( // other modules... upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), ) // Run x/upgrade first in PreBlock so it can detect a due plan and execute the // handler before the rest of the block. app.ModuleManager.SetOrderPreBlockers( upgradetypes.ModuleName, // other pre-blockers... ) app.SetPreBlocker(app.PreBlocker) ``` The `PreBlocker` method runs the module manager's `PreBlock`, which is where `x/upgrade` detects a due plan and executes its handler: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *MyApp) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { return app.ModuleManager.PreBlock(ctx) } ``` `x/upgrade` only runs during `PreBlock`. If `SetPreBlocker` is not wired to a `PreBlocker` that calls the module manager's `PreBlock`, upgrade plans never execute and no migrations run. Also save the consensus version of each module to state at genesis, so future upgrades can detect when modules with newer consensus versions are introduced. Add this to `InitChainer`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *MyApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { // ... if err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()); err != nil { return nil, err } // ... } ``` ## Registering migrations To register the functionality that takes place during a module upgrade, register the migrations in the `Configurator` using its `RegisterMigration` method, from the `AppModule`'s `RegisterServices` method. Register migrations in increasing order, one per source version, up to the target consensus version. For example, to migrate to version 3 of a module, register migrations for versions 1 and 2: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (am AppModule) RegisterServices(cfg module.Configurator) { // --snip-- if err := cfg.RegisterMigration(types.ModuleName, 1, func(ctx sdk.Context) error { // Perform in-place store migrations from ConsensusVersion 1 to 2. return nil }); err != nil { panic(fmt.Sprintf("failed to migrate %s from version 1 to 2: %v", types.ModuleName, err)) } if err := cfg.RegisterMigration(types.ModuleName, 2, func(ctx sdk.Context) error { // Perform in-place store migrations from ConsensusVersion 2 to 3. return nil }); err != nil { panic(fmt.Sprintf("failed to migrate %s from version 2 to 3: %v", types.ModuleName, err)) } } ``` The migration functions need access to the keeper's store, so they are defined as methods on a `Migrator` that wraps the keeper. The next section writes one. ## Writing migration scripts A migration reads the module's existing state and rewrites it into the new layout. Place migration functions in the module's `keeper` package, or in a versioned `migrations/` directory for larger modules (for example `x/bank/migrations/v2`). The following `Migrator` moves the counter module from consensus version 1 to 2. The breaking change is a re-denomination: every stored count is multiplied by 10. `Migrate1to2` reads the current value, transforms it, and writes it back, treating an unset value as a no-op rather than an error: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Migrator struct { keeper *Keeper } func NewMigrator(keeper *Keeper) Migrator { return Migrator{keeper: keeper} } func (m Migrator) Migrate1to2(ctx sdk.Context) error { count, err := m.keeper.counter.Get(ctx) if err != nil { // A chain that never touched the counter has no stored value yet. if errors.Is(err, collections.ErrNotFound) { return nil } return err } return m.keeper.counter.Set(ctx, count*10) } ``` Register this migration with `RegisterMigration(types.ModuleName, 1, m.Migrate1to2)` as shown in the previous section. For larger modules, keep the transformation in a versioned package so the `Migrator` method stays a thin wrapper. The bank module follows this pattern: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank/exported" v2 "github.com/cosmos/cosmos-sdk/x/bank/migrations/v2" v3 "github.com/cosmos/cosmos-sdk/x/bank/migrations/v3" v4 "github.com/cosmos/cosmos-sdk/x/bank/migrations/v4" ) // Migrator is a struct for handling in-place store migrations. type Migrator struct { keeper BaseKeeper legacySubspace exported.Subspace } // NewMigrator returns a new Migrator. func NewMigrator(keeper BaseKeeper, legacySubspace exported.Subspace) Migrator { return Migrator{keeper: keeper, legacySubspace: legacySubspace} } // Migrate1to2 migrates from version 1 to 2. func (m Migrator) Migrate1to2(ctx sdk.Context) error { return v2.MigrateStore(ctx, m.keeper.storeService, m.keeper.cdc) } // Migrate2to3 migrates x/bank storage from version 2 to 3. func (m Migrator) Migrate2to3(ctx sdk.Context) error { return v3.MigrateStore(ctx, m.keeper.storeService, m.keeper.cdc) } // Migrate3to4 migrates x/bank storage from version 3 to 4. func (m Migrator) Migrate3to4(ctx sdk.Context) error { m.MigrateSendEnabledParams(ctx) return v4.MigrateStore(ctx, m.keeper.storeService, m.legacySubspace, m.keeper.cdc) } ``` For a production example that manipulates raw KV store keys, see [migrateBalanceKeys](https://github.com/cosmos/cosmos-sdk/blob/release/v0.54.x/x/bank/migrations/v2/store.go#L55-L76). This code updated bank addresses to be prefixed by their length in bytes as outlined in [ADR-028](/sdk/latest/reference/architecture/adr-028-public-key-addresses). ## Running migrations in the app Once modules have registered their migrations, the app runs them inside an `UpgradeHandler`. The upgrade handler type is: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type UpgradeHandler func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) ``` As of Cosmos SDK v0.54, `x/upgrade` is part of the main SDK module. Import it from `github.com/cosmos/cosmos-sdk/x/upgrade`, not the standalone `cosmossdk.io/x/upgrade` module, which is not compatible with v0.54. The handler receives the `VersionMap` stored by `x/upgrade` (reflecting the consensus versions from the previous binary), performs any additional upgrade logic, and must return the updated `VersionMap` from `RunMigrations`. Register the handler in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { // optional: additional upgrade logic here return app.ModuleManager.RunMigrations(ctx, app.Configurator(), fromVM) }) ``` `RunMigrations` iterates over all registered modules in order, checks each module's version in the `VersionMap`, and runs all registered migration scripts for modules whose consensus version has increased. The updated `VersionMap` is returned to the upgrade keeper, which persists it in the `x/upgrade` store. ### Order of migrations By default, migrations run in alphabetical order by module name, with one exception: `x/auth` runs last due to state dependencies with other modules (see [cosmos/cosmos-sdk#10591](https://github.com/cosmos/cosmos-sdk/issues/10591)). To change the order, call `app.ModuleManager.SetOrderMigrations(module1, module2, ...)` in `app.go`. The function panics if any registered module is omitted. ### Adding new modules during an upgrade New modules are recognized because they have no entry in the `x/upgrade` `VersionMap` store. `RunMigrations` calls `InitGenesis` for them automatically. If you need to add stores for a new module, configure the store loader before the upgrade runs. The loader reads `upgrade-info.json` at startup and adds the store at the upgrade height: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() if err != nil { panic(err) } if upgradeInfo.Name == "my-plan" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := storetypes.StoreUpgrades{ Added: []string{"newmodule"}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } ``` Configure the store loader in the app constructor, before `LoadLatestVersion` runs, so the new store is mounted when the store loads. To skip `InitGenesis` for a new module (for example, if you are manually initializing state in the handler), set its version in `fromVM` before calling `RunMigrations`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} fromVM["newmodule"] = newmodule.AppModule{}.ConsensusVersion() return app.ModuleManager.RunMigrations(ctx, app.Configurator(), fromVM) ``` ### Overwriting genesis functions The SDK provides modules that app developers can import, and those modules often already have an `InitGenesis` function. If you want to run a custom genesis function for one of those modules during an upgrade instead of the default one, you must both call your custom function in the handler AND manually set that module's consensus version in `fromVM`. Without the second step, `RunMigrations` will run the module's existing `InitGenesis` even though you already initialized it. You must manually set the consensus version in `fromVM` for any module whose `InitGenesis` you are overriding. If you don't, the SDK will call the module's default `InitGenesis` in addition to your custom one. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import foo "github.com/my/module/foo" app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { // Prevent RunMigrations from calling foo's default InitGenesis. fromVM["foo"] = foo.AppModule{}.ConsensusVersion() // Run your custom genesis initialization for foo. // InitGenesis takes sdk.Context, so unwrap the handler's context.Context. // myCustomGenesisState must be a json.RawMessage (the marshaled genesis state). app.ModuleManager.Modules["foo"].(module.HasGenesis).InitGenesis(sdk.UnwrapSDKContext(ctx), app.AppCodec(), myCustomGenesisState) return app.ModuleManager.RunMigrations(ctx, app.Configurator(), fromVM) }) ``` ## Counter module upgrade example The counter module is built step by step in the [example chain tutorial](/sdk/latest/tutorials/example/00-overview), and its source lives in the [cosmos/example](https://github.com/cosmos/example) repository. This example continues from that module: it bumps the counter to consensus version 2 and runs the migration shown earlier during an upgrade named `my-plan`. 1. Increment the module's `ConsensusVersion` so the SDK detects that its state layout changed: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (AppModule) ConsensusVersion() uint64 { return 2 } ``` 2. In `RegisterServices`, wrap the keeper in a `Migrator` and register the version 1 to 2 migration: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} m := keeper.NewMigrator(am.keeper) if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil { panic(fmt.Sprintf("failed to migrate x/%s from version 1 to 2: %v", types.ModuleName, err)) } ``` 3. In `app.go`, register a handler for the plan that calls `RunMigrations`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { return app.ModuleManager.RunMigrations(ctx, app.Configurator(), fromVM) }) ``` 4. Schedule the `my-plan` upgrade through governance. When the chain reaches the plan height it halts, the node operator starts the new binary, and the handler runs the counter migration during `PreBlock`. See [How an upgrade works](#how-an-upgrade-works). ## Syncing a full node to an upgraded blockchain A full node joining an already-upgraded chain must start from the initial binary that the chain used at genesis and replay all historical upgrades. If all upgrade plans include binary download instructions, Cosmovisor's auto-download mode handles this automatically. Otherwise, you must provide each historical binary manually. See the [Cosmovisor](/sdk/latest/guides/upgrades/cosmovisor) guide for setup and configuration. # Create an ML-DSA account Source: https://docs.cosmos.network/sdk/latest/keys/create-ml-dsa-account Create a post-quantum user account with the keyring and move funds into it; there is no in-place migration for accounts. This guide creates a user account backed by `ml_dsa_65`, the post-quantum signature algorithm, and moves funds into it. Accounts do not rotate. An existing account keeps its key for life, so moving to post-quantum means a new account and a transfer. For what ML-DSA is and when account migration matters, see [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). Commands use `simd`. Substitute your chain's binary and adjust key names and denoms. ## Know the limits first * CLI only. Wallet support for ML-DSA accounts is minimal; expect to manage the account with the chain CLI. * No hardware wallets. Ledger devices sign with `secp256k1` only. Recovery works from the mnemonic alone. * No EVM. EVM transactions require `eth_secp256k1` account keys, so ML-DSA accounts do not work with the EVM. See the EVM section of [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). ## Prerequisites * The chain runs Cosmos SDK 0.55 or later, on every node. * A running chain to send transactions to, with its CLI binary on your PATH and a funded account in the keyring. To stand up a local chain first, see [Run a node](/sdk/latest/node/run-node). Enabling ML-DSA accounts needs no chain configuration beyond the SDK version. Upgrading to 0.55 is sufficient, but every validator and node must run it: a binary built with an older SDK cannot process ML-DSA signatures. ## 1. Create the account Generate the account with the keyring and select the ML-DSA algorithm: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd keys add pq-account --key-type ml_dsa_65 --home ~/.node --keyring-backend test ``` Use the same `--home` and `--keyring-backend` on every command in this guide. Without them the key lands in a different keyring from your funded account, and the transfer in step 2 fails to find it. The output shows the new address and a mnemonic. Store the mnemonic securely. No hardware wallet can hold this key, so the mnemonic is the only recovery path. To recover the account later: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd keys add pq-account --recover --key-type ml_dsa_65 --home ~/.node --keyring-backend test ``` Do not create an ML-DSA account from a mnemonic already used for a `secp256k1` account. ML-DSA key generation derives its seed from the same secp256k1 BIP32 path, so at that path the secp256k1 private key and the ML-DSA seed are the same secret. Reusing the mnemonic links the two keys: whoever obtains that secret controls both. Generate each ML-DSA account from a fresh mnemonic. ## 2. Move funds in There is no in-place migration for accounts by design. Send funds from the old account with an ordinary transfer: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send old-account "$(simd keys show pq-account -a --home ~/.node --keyring-backend test)" 1000000stake --from old-account --home ~/.node --keyring-backend test ``` Add `--chain-id` if your client config does not supply it, and `--fees` (or `--gas-prices`) to meet the chain's minimum gas price. ## 3. Verify the account signs Prove the new key works by sending from it: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send pq-account "$(simd keys show old-account -a --home ~/.node --keyring-backend test)" 1stake --from pq-account --gas auto --gas-adjustment 1.5 --home ~/.node --keyring-backend test ``` You may need to raise the gas limit for this transfer. The account's first transaction writes its public key to state, and an ML-DSA public key is large enough that a default limit of 200000 runs out of gas. `--gas auto` sizes the limit to fit. Later transactions from the account are smaller and fit within the default. A successful send from `pq-account` means the chain accepted an ML-DSA signature for the account. Drain and retire the old account afterwards. ## Next steps * Understand what post-quantum protection the account now has. See [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). * Validators migrate differently, by key rotation. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). # Enable ML-DSA keys Source: https://docs.cosmos.network/sdk/latest/keys/enable-ml-dsa-keys Add ml_dsa_65 to a chain's accepted consensus key types, through genesis on a new chain or governance on a live one. Validator key types are a consensus parameter, so allowing post-quantum validator keys is a chain-level change. This guide adds `ml_dsa_65` to the accepted consensus key types: on a new chain through genesis, or on a live chain through a governance proposal, with no coordinated restart. For background on ML-DSA and the key types, see [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). Before any validator rotates to an ML-DSA key, every counterparty chain that verifies this chain over IBC must run CometBFT v0.40 or later. See [IBC considerations](/sdk/latest/keys/post-quantum-keys#ibc-considerations). ## Prerequisites * The chain runs Cosmos SDK 0.55 and CometBFT 0.40 or later. See the [release notes](/sdk/latest/upgrade/v0.55-release). * [jq](https://jqlang.org/) and [curl](https://curl.se/). The commands derive the proposal payload with jq and read the validator set with curl. * The chain's CLI binary on your PATH, with RPC access to a node. To stand up a local chain, see [Run a node](/sdk/latest/node/run-node). * For a live chain: the ability to pass a governance proposal, and an account funded for the proposal deposit and gas. Commands use `simd`; substitute your chain's binary, and adjust key names and denoms to your own. ## Check the current state Consensus params list the allowed key types under `validator.pub_key_types`. Query them: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query consensus params ``` The default is `ed25519` only. To see which types the validator set is currently running, list the validators and read each one's pubkey type: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/validators | jq -r '.result.validators[].pub_key.type' ``` The output shows registered type names, one per validator: `tendermint/PubKeyEd25519` for ed25519 keys, `cometbft/PubKeyMlDsa65` for ML-DSA keys. ## New chain: set the types in genesis Add `ml_dsa_65` to `consensus.params.validator.pub_key_types` in `genesis.json` before launch: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} "validator": { "pub_key_types": ["ed25519", "ml_dsa_65"] } ``` Keep `ed25519` in the list unless every genesis validator starts on an ML-DSA key. Validators whose key type is not in the list cannot join the set. A genesis validator starts on an ML-DSA key by initializing its node with `simd init --consensus-key-algo ml_dsa_65`, and a local ML-DSA testnet comes from the same flag on `simd testnet init-files` or `simd testnet start`. `simd init --consensus-key-algo` replaces the genesis `pub_key_types` list with only the chosen algorithm. A chain that should accept both types must re-add `ed25519` to the list after init, or ed25519 validators cannot join. ## Live chain: expand the types through governance The change is a parameter update executed by governance. It takes effect when the proposal passes, with no node restarts and no coordinated upgrade. All steps read and write `params.json` in the current directory, so run them from one place. 1. Build the proposal params from the live chain state. The update message replaces the entire params object, so it must carry every current value. The following command derives the params from a query, adds `ml_dsa_65` to `pub_key_types`, and converts the evidence duration to the format the proposal parser accepts: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query consensus params -o json \ | jq '.params | {block, evidence, validator, abci} | .validator.pub_key_types += ["ml_dsa_65"] | .evidence.max_age_duration |= ( capture("((?[0-9]+)h)?((?[0-9]+)m)?((?[0-9]+)s)?") | ((((.h // "0") | tonumber) * 3600 + ((.m // "0") | tonumber) * 60 + ((.s // "0") | tonumber)) | tostring) + "s" )' \ > params.json ``` 2. You can then submit the file's contents as a governance proposal. The command takes the four param groups as separate arguments, sliced from the same file. Make sure to update the following command to include your key and correct deposit amount/denomination. Add `--home` pointing at your node's home, since the transaction commands on this page read the keyring from it. Also add `--chain-id` and `--keyring-backend` if your client config does not supply them, and `--fees` (or `--gas-prices`) to meet the chain's minimum gas price: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx consensus update-params-proposal "$(jq -c .block params.json)" "$(jq -c .evidence params.json)" "$(jq -c .validator params.json)" "$(jq -c .abci params.json)" --title "Allow ML-DSA validator keys" --summary "Add ml_dsa_65 to consensus pub_key_types" --deposit 10000000stake --from mykey -y ``` 3. Vote as with any governance proposal, using the proposal ID from `simd query gov proposals`. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov vote yes --from mykey ``` When it passes, the new list is live. The update replaces the entire params object, so every group must be present. If `params.json` is missing `block`, `evidence`, or `validator`, the `jq -c` substitution above emits `null` and the CLI rejects the command before it is submitted, with `invalid argument "null": proto: syntax error`. Always start from the queried current params and change only the key type list. ## Verify Query the params again and confirm the list includes `ml_dsa_65`: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query consensus params ``` New validators can now join with ML-DSA keys. Existing validators can migrate by rotation. See [Key rotation](/sdk/latest/keys/key-rotation). ## Remove a key type To remove a key type, update the `params.json` file to remove the key type from the `pub_key_types` array. Then, submit the updated `params.json` file as a governance proposal. Do not remove a key type while validators still use it. Existing validators are not re-checked when a type leaves the list, but every later voting-power update for such a validator fails validation, and a failed validator update halts the chain. Routine activity is enough to trigger it: any delegation that changes the validator's voting power emits one of these updates. ## Next steps * Understand the rotation mechanics before touching a production validator. See [Key rotation](/sdk/latest/keys/key-rotation). * Migrate a validator to the new key type. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). # Key rotation Source: https://docs.cosmos.network/sdk/latest/keys/key-rotation How consensus key rotation works: which keys rotate, the two-height delay, the fee, the rotation limit, and how slashing follows a rotated key. Key rotation replaces a validator's consensus key in place: no unbonding, no downtime, and no change to the validator's identity, power, or delegations. Rotation is a single operator-signed message on the [`x/staking`](/sdk/latest/keys/rotate-validator-key) and [`enterprise/poa`](/sdk/latest/keys/rotate-validator-key-poa) modules, and realizes a design first proposed in 2019 as [ADR-016](/sdk/latest/reference/architecture/adr-016-validator-consensus-key-rotation). ## Which keys rotate A validator runs on three keys, each with a different owner and job: 1. **Operator key**: an ordinary account key that the operator generates and custodies, often on a hardware wallet or behind a multisig. It owns the validator: it stakes, sets commission, edits the validator's description, withdraws rewards, votes, and signs the rotation message itself. Like any account key, it has no in-place replacement; retiring one means creating a new account and moving funds. 2. **Consensus key**: generated by the stack when a node initializes, stored in `priv_validator_key.json` or held by a remote signer. It is a raw keypair with no mnemonic behind it; the operator custodies the file, not a seed phrase. It signs the validator's vote on every block, and a block proposal whenever the validator's turn comes to propose. Evidence of misbehavior identifies the validator through this key's consensus address. This is the only key a rotation touches. 3. **Node key**: the node's peer-to-peer identity. It exists so peers can address and authenticate each other: its public key hashes to the node ID used in peer addresses, and it secures the connection handshake between nodes. It carries no on-chain state and regenerates freely. On chain, a validator is the pairing of an operator address with a consensus key. Rotation replaces the consensus half of that pairing and leaves everything the operator key controls alone. For the full survey of keys and algorithms, see [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). ## How a rotation works The operator submits `MsgRotateConsPubKey`, carrying the new consensus public key. Everything that defines the validator stays put: the operator address, voting power, delegations, and commission. Only the consensus key and its index change. The new key does not take effect immediately. The chain emits the power hand-off to CometBFT right away, setting the old key to zero and giving the new key the validator's full power, and CometBFT's validator update rule makes it effective two heights later, at the same height the chain swaps its stored key. Zero downtime rides on this: the operator runs a second node with the new key alongside the old one. Until the rotation takes effect, the second node follows the chain as a non-signing full node, because a CometBFT node whose key is outside the validator set produces no votes. The moment the new key enters the set, the second node starts signing, the old key holds no power, and the operator retires the old node. The step-by-step procedure is in [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). ## Safety rails Three rules bound what a rotation can do: 1. A rotation burns a flat fee, set by the `key_rotation_fee` staking param, to make rotation spam expensive. 2. A validator can rotate once per unbonding period. Until the unbonding period ends, the previous key remains accountable, so the chain rejects a second rotation inside the window. 3. A rotation cannot be undone. No cancel message exists, and the once-per-unbonding-period limit blocks an immediate rotation back, so an applied rotation stands until the window expires. ## Security implications Read this section carefully. Key rotation introduces security and performance tradeoffs that chains must be aware of before rotating keys. ### Slashing window length Slashing follows a validator's history, not the key. Evidence of a double sign under the old key still slashes the validator after rotation. When a rotation lands, the chain records the old consensus address. It keeps that address tied to the validator. It tracks the address for as long as evidence against it can still be admitted. It computes this window at rotation time from the evidence params `MaxAgeNumBlocks` and `MaxAgeDuration`. Once both elapse, the chain stops tracking the address. The window can be months, depending on the chain's evidence settings. Rotating away from a key does not let a validator escape slashing within that window. Downtime slashing carries over too. On a rotation, the validator's missed-block record and jailed status move to the new consensus key. A rotation does not reset them. The chain computes a rotation tracking window once at the time of rotation. It never updates this window. If governance extends `MaxAgeNumBlocks` or `MaxAgeDuration` after a rotation, the original window for the rotation remains in effect. In this scenario, the old consensus address will stop being tracked before the current evidence window closes. During that gap, a double sign under the old key cannot be slashed. This is a risk that chains should be aware of. Do not extend the evidence params while rotations are in flight without accounting for it. ### Increased IBC light client updates Frequent rotations may raise the cost of keeping a light client current. Each rotation changes the validator set. Tendermint light clients advance using the overlap between successive validator sets. Heavy rotation churn shrinks that overlap. With less overlap, a relayer must submit more update-client messages to advance the client across the same span. The once-per-unbonding-period limit exists partly to bound this cost. ### Proposer priority reset Rotation resets the validator's proposer priority. CometBFT orders validators by a proposer priority value. That value decides when a validator's turn to propose comes up. A rotation sends the validator to the back of that order. This holds even if the validator was next in line to propose. ### Slower signature verification A validator set with mixed consensus key types verifies signatures more slowly. CometBFT batch-verifies signatures when every validator uses the same key type. Batch verification is faster than checking each signature on its own. One validator on a different key type breaks batching. Verification then falls back to one signature at a time, which can slow block times. This applies whenever the set holds more than one key type, not only during a rotation. ## Exports and restarts In-progress rotations survive a genesis export. A chain exported mid-rotation carries the pending rotation and its remaining history window into the new genesis, so restarting a chain does not lose slashing accountability or drop a queued key change. ## Staking and PoA chains Rotation ships in both validator models. On staked chains, it is the `x/staking` implementation described above; for the procedure, see [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). Chains running `enterprise/poa` get the same rotation with two differences. The admin can rotate any validator's key, not only the operator. And because PoA has no slashing or evidence handling, the safety rails above do not apply: no fee, no rate limit, no rotation history, and the state swap happens in the block the transaction lands. For the procedure, see [Rotate a consensus key, PoA](/sdk/latest/keys/rotate-validator-key-poa). Vote extensions are not supported on PoA chains. PoA chains running custom logic that resolves a validator from a `LastCommit` address or that uses vote extensions may experience unexpected behavior during the two-height delay after a rotation. Read on for more details. The PoS and PoA models differ in how they handle CometBFT's two-height delay. After a key rotation, CometBFT keeps signing `LastCommit` with the old consensus address for two heights. Staking waits out those heights before swapping its own state and keeps a historical address mapping, so the old address still resolves to its validator. PoA swaps immediately and keeps no mapping, so during those two heights it cannot resolve the old address that `LastCommit` still carries. A stock PoA chain never notices, because it runs neither x/distribution nor x/slashing (the modules that generally read those addresses). However, custom logic that resolves a validator from a `LastCommit` address on a PoA chain will not find a rotating validator for those two heights, which can lead to unexpected behavior. For this reason, vote extensions are not supported on PoA chains. The chain verifies a vote-extension signature by the `LastCommit` address. If a validator rotates its key during the two-height delay, the chain will reject the vote extension because the `LastCommit` address will not resolve to the validator. The standard `ValidateVoteExtensions` helper returns an error on the first commit vote whose `LastCommit` address it cannot resolve, before it tallies any voting power. One rotating validator that signed the previous block is therefore enough to have the whole extended commit rejected, whatever its share of voting power. ## Next steps * Perform a rotation on a staked chain. See [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). * Perform a rotation on a PoA chain. See [Rotate a consensus key, PoA](/sdk/latest/keys/rotate-validator-key-poa). * Understand the key types. See [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). * Look up the message, parameters, and state layout. See the [x/staking module reference](/sdk/latest/modules/staking/README#msgrotateconspubkey). # Migrate a validator to ML-DSA Source: https://docs.cosmos.network/sdk/latest/keys/migrate-validator-ml-dsa Move a validator's consensus key to the post-quantum ml_dsa_65 algorithm through an ordinary key rotation. Migrating a validator to post-quantum signing is an ordinary key rotation with an ML-DSA target key. This guide adds the ML-DSA-specific steps around the standard procedure in [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). For more information on ML-DSA, see [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). Key rotation can introduce security implications for your chain. Read the [Key rotation](/sdk/latest/keys/key-rotation) overview in its entirety before proceeding. Before rotating any validator to ML-DSA, confirm every counterparty chain that verifies this chain over IBC runs CometBFT v0.40 or later. An older `07-tendermint` light client cannot verify ML-DSA consensus signatures. See [IBC considerations](/sdk/latest/keys/post-quantum-keys#ibc-considerations) for more information. ## Prerequisites * All prerequisites of the rotation procedure: [jq](https://jqlang.org/) and [curl](https://curl.se/), no rotation in the current unbonding period, and fee funds on the operator account. See [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). * The chain's binary; the examples use `simd`. To build it and run a node, see [Run a node](/sdk/latest/node/run-node). ## 1. Confirm the chain allows ML-DSA Validator key types are a consensus parameter. Check that `ml_dsa_65` is in the list: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query consensus params ``` If `validator.pub_key_types` does not include `ml_dsa_65`, the rotation is rejected. To add the type, see [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys). ## 2. Rotate to an ML-DSA key ### On a staking chain Follow [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key), replacing the `simd init` command in its step 1 with one that adds `--consensus-key-algo ml_dsa_65` to create an ML-DSA key: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd init rotation-node --chain-id my-chain-1 --consensus-key-algo ml_dsa_65 --home ~/.rotation-node ``` Everything else runs as written. The guide's rotation command derives the public key with `simd comet show-validator` on the second node's home, which now prints the ML-DSA key, so the rotation message carries it automatically. ### On a PoA chain Follow [Rotate a consensus key, PoA](/sdk/latest/keys/rotate-validator-key-poa) with two changes. Replace the `simd init` command in its step 1 with: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd init poa-newkey --chain-id my-chain-1 --consensus-key-algo ml_dsa_65 --home ~/.poa-newkey ``` And pass `ml_dsa_65` instead of `ed25519` as the key type when submitting. The ML-DSA public key may be large enough that the default gas limit runs out, so add `--gas auto`: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa rotate-cons-pub-key "$(simd comet show-validator --home ~/.poa-newkey | jq -r .key)" ml_dsa_65 --operator-address "$(simd keys show val -a --home ~/.node)" --from val --home ~/.node --gas auto --gas-adjustment 1.5 ``` The cutover timing is unchanged: keep the node on the old key until the validator set switches, then swap the key file in place, exactly as the guide's [steps 3 and 4](/sdk/latest/keys/rotate-validator-key-poa#3-wait-for-the-validator-set-to-switch) describe. ### On a remote signer If the validator's consensus key lives in Cosmos-KMS rather than a local file, the second node gets its own signer and the public key derivation differs. See [Rotate a consensus key held in Cosmos-KMS](/sdk/latest/kms/rotate-key-remote-signer). ## 3. Verify Check the key type in the validator set: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/validators | jq -r '.result.validators[].pub_key.type' ``` A migrated validator reports `cometbft/PubKeyMlDsa65` instead of `tendermint/PubKeyEd25519`. The chain's consensus is post-quantum secure once validators holding at least two thirds of voting power report a post-quantum type. For more information, see [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). ## What can go wrong * The rotation is rejected for an unsupported key type: the chain does not list `ml_dsa_65` yet. See [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys). * Anything else follows the standard rotation failure modes. See [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). The consensus key this rotation installs is a raw keypair with no mnemonic behind it. Custody the key file, not a seed phrase. ## Next steps * Check which key types the chain allows and which the validator set is running. See [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys). * Understand the storage and bandwidth costs the chain takes on as the set migrates. See [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). # Post-quantum keys Source: https://docs.cosmos.network/sdk/latest/keys/post-quantum-keys The keys and signature algorithms of a Cosmos chain, what post-quantum security means, and what adopting ML-DSA costs. A Cosmos chain uses various keys backed by a set of signature algorithms, including ML-DSA, a native post-quantum option for consensus keys and user accounts. This page surveys the keys and algorithms and explains what post-quantum security means and how it applies. A post-quantum key signs with an algorithm that stays secure against an attacker equipped with a quantum computer. ## Keys and algorithms Keys are the foundation of a chain's security: funds, consensus votes, and governance are only as safe as the keys that sign for them. Every signature on a Cosmos chain comes from one of four keys, each held by a different party and signing different things: | Key | Held by | Signs | Algorithms | | ----------------------- | ------------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | User account key | Anyone with an account | Transactions | | | Validator operator key | The validator's operator | Staking transactions |
  • Same as the user account key.
| | Validator consensus key | The validator node | Votes and proposals | | | Node key | Every node | Peer-to-peer identity | | Consensus params decide which algorithms consensus keys may use on a given chain, while users pick an account algorithm each time they create a key. To check what a chain currently allows and which algorithms its validator set is running, see [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys#check-the-current-state). For information on validator key rotation, see [Key rotation](/sdk/latest/keys/key-rotation). ## What post-quantum means Every algorithm in the stack except `ml_dsa_65` is based on an elliptic curve. A sufficiently powerful quantum computer running [Shor's algorithm](https://en.wikipedia.org/wiki/Shor%27s_algorithm) breaks elliptic curve cryptography outright: no key size makes a curve safe. Account keys, operator keys, and consensus keys therefore share the same long-term exposure, and when practical quantum hardware arrives, curve-based signatures stop being trustworthy. To counter this, the `ml_dsa_65` key algorithm is introduced. ## How ML-DSA works The Module-Lattice-Based Digital Signature Algorithm (ML-DSA) is NIST's lattice-based signature standard, published as [FIPS 204](https://csrc.nist.gov/pubs/fips/204/final) in 2024 and the finalized form of CRYSTALS-Dilithium. Instead of deriving security from elliptic curves, it builds keys and signatures on [lattice problems](https://en.wikipedia.org/wiki/Lattice-based_cryptography), a class of mathematics with no known quantum attack. The Cosmos stack uses the middle FIPS 204 parameter set, ML-DSA-65 (NIST security category 3), as the algorithm `ml_dsa_65` to implement post-quantum security. With the addition of this key algorithm, nothing about the signing workflow changes. The keyring generates and recovers an ML-DSA account like any other, and consensus treats an ML-DSA consensus key like any other key type. What differs is the math underneath and the size of the keys and signatures it produces. ## Is hashing post-quantum secure? Yes, the SHA-256 hash function that underpins the Cosmos SDK and CometBFT (block transaction hashes, merkle trees over application state, etc.) are considered post-quantum secure. Unlike RSA and elliptic-curve cryptography, which are broken by Shor's algorithm, the best known quantum attack against generic hash functions is [Grover's algorithm](https://en.wikipedia.org/wiki/Grover%27s_algorithm). Grover's algorithm provides only a quadratic speedup, reducing SHA-256's preimage resistance from 256 bits to about 128 bits, which is still considered secure. Collision resistance, the property that matters for merkle trees and transaction hashes, was already about 128 bits classically and is essentially unaffected. ## Who can adopt ML-DSA? Only account keys and consensus keys can use `ml_dsa_65`. The node key stays `ed25519` and merely identifies a node to its peers, and module accounts and smart contract accounts hold funds without any key at all, so none of them has anything to migrate. Adoption differs by role. A user can generate a new ML-DSA account and move funds into it at any time, with no chain-level permission required; there is no in-place migration for accounts. A validator migrates its consensus key in place through key rotation, which does require the chain to allow `ml_dsa_65` in consensus params first. ## When is a chain considered post-quantum? Consensus security follows voting power. A chain's consensus becomes post-quantum secure once at least two thirds of voting power signs with post-quantum consensus keys, because two thirds is the threshold an attacker must forge to break finality. Account security is individual: each account is exactly as secure as its own key. ## The cost of post-quantum keys Post-quantum security trades larger keys and signatures for quantum resistance. Signatures dominate the added cost because every block commit carries one per validator, so the totals below scale with the validator set. A single ML-DSA signature is about 3,300 bytes, over 50 times the size of an ed25519 signature. Every block stores one per validator, which drives the block-data growth shown below. The example below assumes 100 validators and six-second blocks. | Measure | `ed25519` | `ml_dsa_65` | | -------------------------------------------- | --------- | ----------- | | Public key | 32 B | 1,952 B | | Signature | 64 B | 3,309 B | | Signature data per block | \~6 KB | \~331 KB | | Total block size, with \~4 KB fixed overhead | \~10 KB | \~335 KB | | Total block data per year | \~56 GB | \~1.8 TB | Signing and verification are slightly slower than with `ed25519`. This is unlikely to affect most chains. ## IBC considerations A chain migrating to ML-DSA consensus keys must be aware that this change affects IBC verification. Any counterparty chain that verifies an ML-DSA-enabled chain with an `07-tendermint` light client must be upgraded to CometBFT v0.40 or later. An older client fails as soon as the first ML-DSA validator joins the set, so every counterparty must upgrade to v0.40 before any validator can rotate to ML-DSA. ML-DSA signatures also enlarge block headers, which enlarges the IBC client updates that carry them. CometBFT v0.40 raises its signature-size limits to accommodate the larger signatures. ## EVM chains Validators on EVM chains can run ML-DSA consensus keys, as on any other chain. User accounts cannot: the EVM requires `eth_secp256k1` account keys, and those cannot move to a post-quantum scheme in place. Ethereum's path to post-quantum accounts runs through account abstraction instead. [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) already lets an account delegate to contract code, so a contract can verify a post-quantum signature inside the VM. [EIP-8051](https://eips.ethereum.org/EIPS/eip-8051) proposes a native precompile for ML-DSA verification, and [EIP-8141](https://eips.ethereum.org/EIPS/eip-8141) proposes frame transactions, which let individual accounts adopt new signature schemes. As a fully EVM-compatible ledger, Cosmos EVM conforms to that roadmap and ships user-side post-quantum support as it lands upstream. ## Next steps * Upgrade first; every flow on this page requires SDK 0.55 and CometBFT 0.40. See the [release notes](/sdk/latest/upgrade/v0.55-release). * Create a post-quantum user account and move funds into it. See [Create an ML-DSA account](/sdk/latest/keys/create-ml-dsa-account). * Allow `ml_dsa_65` on a new or live chain. See [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys). * Learn how rotation works before touching a production validator. See [Key rotation](/sdk/latest/keys/key-rotation). # Rotate a consensus key, Staking Source: https://docs.cosmos.network/sdk/latest/keys/rotate-validator-key Rotate a staked validator's consensus key with no downtime: run a second node, submit the rotation, verify, and retire the old node. This guide rotates a staked validator's consensus key with no downtime: run a second node on the new key, submit the rotation, verify, and retire the old node. For how rotation works and its limits, see [Key rotation](/sdk/latest/keys/key-rotation). Key rotation can introduce security implications for your chain. Read the [Key rotation](/sdk/latest/keys/key-rotation) overview in its entirety before proceeding. Commands use `simd`. Substitute your chain's binary. Examples use `~/.node` for the existing node's home and `~/.rotation-node` for the new one; those paths, the key name `val`, and host addresses are the only values to adjust. ## Prerequisites * The chain runs Cosmos SDK 0.55 and CometBFT 0.40 or later, and allows your target key type. See [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys). * [jq](https://jqlang.org/) and [curl](https://curl.se/) for the verification steps. * A running validator you operate, and a second machine (or spare ports on the same host) for the new node, with the chain's binary installed on it. To build `simd` and run a node, see [Run a node](/sdk/latest/node/run-node). * No rotation in the current unbonding period. Only one rotation is allowed per unbonding period. * The operator account holds enough funds for two separate charges: the rotation fee, which is burned, and the ordinary gas fee for the transaction itself. Check the rotation fee: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking params ``` The `key_rotation_fee` field shows the fee amount. ## 1. Start a second node with the new key Initialize a fresh node home. The init command generates a new consensus key in `priv_validator_key.json`: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd init rotation-node --chain-id my-chain-1 --home ~/.rotation-node ``` To rotate to a post-quantum key, add `--consensus-key-algo ml_dsa_65` to the command above. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). Never copy the old `priv_validator_key.json` to the new node. Two nodes signing with the same consensus key is a double sign, which tombstones the validator. The new node must have its own freshly generated key. A fresh init writes a placeholder genesis. Replace it with the chain's genesis: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cp ~/.node/config/genesis.json ~/.rotation-node/config/genesis.json ``` Start the new node peered with the existing one, and let it sync to the chain head: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd start --home ~/.rotation-node --p2p.persistent_peers "$(simd comet show-node-id --home ~/.node)@127.0.0.1:26656" ``` Adjust the peer address to the existing node's host. If both nodes share a host, also give the new node its own ports with `--p2p.laddr`, `--rpc.laddr`, `--grpc.address`, and `--rpc.pprof_laddr`. The pprof port is required, not optional: without it the new node exits at startup with `address already in use` for the default port 6060, which the first node already holds. Until the rotation applies, the node follows the chain as a non-signing full node. On a chain with history, a fresh node takes days to sync from genesis. Use state sync or a snapshot to reach the chain head quickly. See [State sync](/sdk/latest/node/run-node#state-sync). ## 2. Confirm both nodes are healthy Before you submit, the old node must still be signing and the new node must be caught up to the chain head. If the new node is still syncing when the rotation applies, the validator will miss blocks until it catches up. Check sync status: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/status | jq '.result.sync_info.catching_up' ``` Run this against each node, using the RPC port each one listens on (a co-located new node answers on the `--rpc.laddr` port you gave it, not `26657`). Both must report `false`. ## 3. Submit the rotation The rotation message carries the new node's public key, read directly from that node's home: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking rotate-cons-pub-key "$(simd comet show-validator --home ~/.rotation-node)" --from val --home ~/.node --gas auto --gas-adjustment 1.5 --fees --yes ``` Set `--fees` (or `--gas-prices`) to meet the chain's minimum gas price; without it the node rejects the transaction with `insufficient fees`. This gas fee is separate from the burned rotation fee. The command reads the operator key and chain ID from your client config; add `--chain-id`, `--keyring-backend`, and `--home` if that config does not already supply them. A rotation cannot be undone. After it applies, the validator is committed to the new key for the rest of the unbonding period. Keep the old node running until step 4 verifies the new key is signing. ## 4. Verify the new key is signing The rotation applies two heights after the message executes, so the new key can appear within seconds on a fast chain. A successful broadcast confirms only that the chain accepted the transaction; confirm the validator set actually carries the new key: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/validators | jq -r '.result.validators[].pub_key' ``` The `value` matches the `key` field shown by `simd comet show-validator --home ~/.rotation-node`, and the old key is gone. If the set still shows the old key, wait a few blocks and check again. Watch the new node's logs to see it signing votes. ## 5. Retire the old node Stop the old node and decommission it. Its consensus key holds no power, but it remains slashable for past behavior until equivocation evidence for it can no longer be admitted. That window is at least the unbonding period and can be longer, depending on the chain's evidence params. See [Key rotation](/sdk/latest/keys/key-rotation). Store its key material securely rather than leaving it on shared infrastructure. ## What can go wrong * The transaction is rejected with a rotation limit error: a rotation already happened this unbonding period. Wait out the window. * The transaction is rejected for an unsupported key type: the target type is not in the chain's consensus params. See [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys). * The fee cannot be paid: fund the operator account with at least `key_rotation_fee`. * The transaction is rejected because the new key is unavailable: another validator already uses it, or a recent rotation still holds it locked. Generate a fresh key. * The transaction is rejected because the validator is jailed: unjail it first. * The validator misses blocks after the rotation applies: the new node was not caught up. It resumes signing once synced. ## Next steps * Understand the mechanics behind each step. See [Key rotation](/sdk/latest/keys/key-rotation). * Rotate to a post-quantum key. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). * Rotate a key held in a remote signer. See [Rotate a consensus key held in Cosmos-KMS](/sdk/latest/kms/rotate-key-remote-signer). * Look up the message and parameters. See the [x/staking module reference](/sdk/latest/modules/staking/README#msgrotateconspubkey). # Rotate a consensus key, PoA Source: https://docs.cosmos.network/sdk/latest/keys/rotate-validator-key-poa Rotate a PoA validator's consensus key as the operator or the admin: generate the key, submit the rotation, and time the node cutover. This guide rotates a PoA validator's consensus key using the `enterprise/poa` module. PoA rotation drops three of the staking rails: there is no fee, no rate limit, and no rotation history. PoA chains have no slashing or evidence handling to protect. The operator can rotate its own key, and the chain admin can rotate any validator's key. For how rotation works in general, see [Key rotation](/sdk/latest/keys/key-rotation). Key rotation can introduce security implications for your chain. Read the [Key rotation](/sdk/latest/keys/key-rotation) overview in its entirety before proceeding. In particular, vote extensions are not supported on PoA chains, and custom logic that resolves a validator from a `LastCommit` address will not find a rotating validator for two heights after a rotation. See [Staking and PoA chains](/sdk/latest/keys/key-rotation#staking-and-poa-chains). Commands use `simd`. Substitute your chain's binary. Examples use `~/.node` for the validator node's home, `~/.poa-newkey` for the scratch home holding the new key, and `val` for the operator key name. ## Prerequisites * A chain binary built with the `poa` module. Clone the cosmos-sdk repo and build it: `cd enterprise/poa/simapp && go build -o /tmp/poa-simd ./simd`, then use that binary as `simd`. The `-o` is required, since `./simd` is the package directory. Check with `simd tx poa --help`, which must list `rotate-cons-pub-key`. * [jq](https://jqlang.org/) and [curl](https://curl.se/). The commands extract the new public key with jq and watch the validator set with curl. * A running PoA validator you operate, with the chain's binary on your PATH and a secondary machine or scratch directory for the new key. PoA validators are set in genesis under `app_state.poa`. * For an ML-DSA rotation, `ml_dsa_65` is in the chain's consensus params. See [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys). * The signer is the validator's operator, or the chain admin. Any other sender is rejected. ## 1. Generate a new consensus key Create the new key on a secondary machine or offline. Do not touch the live node's `priv_validator_key.json` yet: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd init poa-newkey --chain-id my-chain-1 --home ~/.poa-newkey ``` To rotate to a post-quantum key, add `--consensus-key-algo ml_dsa_65` to the command above. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). The important output is `~/.poa-newkey/config/priv_validator_key.json`, which is the new consensus key. Confirm it and read its public key: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd comet show-validator --home ~/.poa-newkey ``` ## 2. Submit the rotation The command takes the new public key as base64 plus its type, and identifies the validator by operator address. Both derive from earlier steps: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa rotate-cons-pub-key "$(simd comet show-validator --home ~/.poa-newkey | jq -r .key)" ed25519 --operator-address "$(simd keys show val -a --home ~/.node)" --from val --home ~/.node ``` To rotate to a post-quantum key, pass `ml_dsa_65` instead of `ed25519` in the command above, and add `--gas auto --gas-adjustment 1.5` since the ML-DSA public key exceeds the default gas limit. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). The operator address is a regular account address (`cosmos1...`), not a `cosmosvaloper1...` address, so read it with `simd keys show val -a`. Add standard transaction flags as your setup requires: `--chain-id` and `--keyring-backend` if your client config does not supply them, and `--fees` (or `--gas-prices`) to meet the chain's minimum gas price. The transaction re-keys the validator's state and migrates its accrued fees in the same block. Power, metadata, and the operator address are unchanged. To rotate as the admin instead, see [Rotate as the admin](#rotate-as-the-admin). If the rotated validator holds more than 1/3 of voting power, the chain halts during the cutover period (steps 3 and 4) and does not resume until the node with the rotated key starts signing. ## 3. Wait for the validator set to switch The chain's state swaps immediately, so `simd q poa validators` shows the new key in the same block the transaction lands. CometBFT applies the actual validator set change two blocks later, and only that switch governs when the node must sign with the new key. Until it happens, CometBFT still expects the old key, so keep the live node running untouched. Watch the CometBFT validator set until the new consensus key appears and the old one is gone: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/validators | jq -r '.result.validators[].pub_key.value' ``` The value should match the key you read in step 1. Do not swap the node's key before the set switches (wait at least 2 blocks). Swapping early makes the node sign with a key CometBFT does not yet expect, and the validator misses blocks. ## 4. Swap the node's key Once the new consensus address is in the set, stop the node, replace its key, and restart: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cp ~/.poa-newkey/config/priv_validator_key.json ~/.node/config/priv_validator_key.json ``` Confirm the node signs under the new consensus address after the restart. If you run a redundant standby, make a single switch. Keep the old-key node signing until the set updates, stop it fully, and only then let the new-key node start signing. A validator with power 0 is outside the active set. Its rotation emits no validator set update, so there is no transition to time. Swap the node's key first, then have the admin grant power. ## Rotate as the admin The admin override changes on-chain state only. Whoever runs the node must still swap `priv_validator_key.json` with the timing in steps 3 and 4 above. Otherwise the validator goes dark until its node signs with the new key. Coordinate the node-side swap with the operator before submitting, unless the goal is to cut off a compromised key. The admin rotates any validator's key with the same command, signed by the admin key. Generate a fresh key home for it: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd init poa-adminkey --chain-id my-chain-1 --home ~/.poa-adminkey ``` Then rotate the key: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa rotate-cons-pub-key "$(simd comet show-validator --home ~/.poa-adminkey | jq -r .key)" ed25519 --operator-address --from admin --home ~/.node ``` ## Verify Confirm the module carries the new key: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa validators --home ~/.node ``` The rotation also emits a `rotate_cons_pubkey` event with the operator address and the old and new consensus addresses. Read it from the transaction: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q tx --home ~/.node ``` ## What can go wrong * The transaction is rejected as unauthorized: the signer is neither the validator's operator nor the admin. * The transaction is rejected for the key itself: the new key equals the current one, is already used by another validator, or its type is not in the chain's consensus params. * The validator misses blocks right after the swap: the node's key was replaced before the set switched. Restore the old key, wait for the set, then swap again. * The validator goes dark after an admin rotation: the node still holds the old key. Swap `priv_validator_key.json` and restart. ## Next steps * Rotate to a post-quantum key. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). * Understand the mechanics and the staking differences. See [Key rotation](/sdk/latest/keys/key-rotation). * Look up the message. See the [PoA API reference](/sdk/latest/enterprise/poa/api#rotateconspubkey). # Remote signing best practices Source: https://docs.cosmos.network/sdk/latest/kms/best-practices Choose and defend a signing architecture: placement, transport, key separation, and the one-signer rule. A remote signing setup is a set of trust decisions: where the signer runs, how the connection is secured, and which keys are protected to what degree. This page states the recommended defaults and the reasoning, so a setup can be defended in a security review rather than inherited by accident. ## Place the signer in its own trust domain Run the signer on a separate host, in a separate network segment, with network ACLs between it and the validator. The validator node is the exposed machine: it peers with the public network and sees frequent maintenance. The signing host should do exactly one job, accept no inbound connections, and be reachable by as few people and systems as possible. The signer dials out to the validator, so this layout costs nothing: the signing host needs no open ports at all. The validator's privval listener is the only listening side; bind it to a private interface and firewall it so only the signing host can reach it. Choose the layout by the deployment: * A separate host with a network firewall is the default and reduces the attack surface the most. * If the validator sits behind a sentry node, network isolation is already in place, so running the signer locally is reasonable. * If cost is a constraint, running both processes on the same host still keeps the key in custody and off the node's disk, an acceptable tradeoff for the core benefit of remote signing. In the two local layouts the key still never touches disk, but the signer process and its credentials live on the exposed machine. Every layout adds some network latency between node and signer, so weigh that against the isolation each one provides. ## Prefer the Noise transport Two transports secure the privval connection; the address scheme in `kms.yaml` selects between them. The default `tcp://` uses CometBFT's SecretConnection: the signer authenticates itself to the validator with its identity key. The validator's listener uses an ephemeral key, so the signer cannot verify it is talking to the right validator. The `noise://` transport closes that gap with mutual pinning. Each side asserts a stable peer ID: the signer's derives from its identity key, the validator's from its node key. Each side refuses a connection from any unexpected peer. Exchange the two peer IDs out of band and pin them: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms peer-id --home ~/.kms ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cometbft show-node-id --libp2p --home ~/.node ``` Use `noise://` for any deployment where the signer and validator cross a network you do not fully control. ## Protect keys according to their power The consensus key is the asset; keep it in the HSM or cloud KMS and never in a file on production hosts. The signer's identity key (`identity.json`) only authenticates the connection: it signs no consensus messages, and losing it means re-pinning a new peer ID, not a compromise. The two keys do not need the same protection level. Treating the identity key as low value keeps operational friction down. ## Signer, chain, and key topology One signer can sign for several chains at once. Each chain is backed by exactly one key, so a single signer can hold several keys, one per chain. For redundancy, one signer can also connect to more than one node on the same chain, such as a primary and a backup. Only one live signer may hold a given key, as the next section explains. ## Run exactly one signer per validator Double-sign protection lives in the signer's per-chain state file, which records the highest height, round, and step ever signed. That protection assumes one writer. Two signer instances holding the same consensus key with separate or missing state files can each sign the same height, which is a double sign. Never run two signer instances against the same validator key, because this can cause double signing. ## Keep the gRPC listener off consensus paths The optional gRPC SignerService performs no caller authentication or authorization: any client that reaches the listener can use every configured key. If the service is enabled, front it with TLS, restrict it with network policy, and give it only the keys it exists to serve. ## Next steps * Set up the backend that holds the key. See [Configure a signing backend](/sdk/latest/kms/configure-backend). * Look up transport and connection fields. See the [configuration reference](/sdk/latest/kms/configuration-reference). * Rotate the consensus key without moving it out of custody. See [Rotate a consensus key held in Cosmos-KMS](/sdk/latest/kms/rotate-key-remote-signer). # Cosmos-KMS configuration reference Source: https://docs.cosmos.network/sdk/latest/kms/configuration-reference Every field of kms.yaml: chains, validators, keys with per-backend parameters, and the gRPC block. The signer reads one file, `/kms.yaml`, at startup. Relative paths anywhere in the file resolve against the `--home` directory. Validation runs at `kms start`; a rejected field is named in the error. For task-shaped setup, see [Configure a signing backend](/sdk/latest/kms/configure-backend); this page is the complete field list. ## chains Declares one chain to sign for. One entry per chain. | Field | Type | Required | Description | | ------------ | ------ | -------- | ------------------------------------------------------------------------------------ | | `id` | string | yes | The chain ID, matching the chain's genesis. | | `state_file` | string | no | Path to the double-sign protection state file. Defaults to `/state/.json`. | ## validators Declares one outbound connection to a validator node's privval listener. A chain can have multiple entries, for example a primary and a backup node. | Field | Type | Required | Description | | -------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chain_id` | string | yes | Must match a declared `chains[].id`. | | `addr` | string | yes | The listener address. `tcp://host:port` selects the SecretConnection transport; `noise://@host:port` selects the Noise transport with mutual peer pinning. | | `identity_key` | string | yes | Path to the signer's identity key file, generated by `kms init`. Authenticates the SecretConnection, and doubles as the signer's Noise identity. | | `reconnect` | bool | no | Reconnect automatically after a dropped connection. Defaults to `true`. | ## keys Binds one signing key to one or more chains. Each chain must be backed by exactly one key. The `backend` field selects the custodian, and the remaining fields depend on it; fields belonging to other backends are ignored. Fields shared by every backend: | Field | Type | Required | Description | | ----------- | --------------- | ----------- | ----------------------------------------------------------------------------------------------- | | `chain_ids` | list of strings | yes | Chains this key signs for. Each must match a declared `chains[].id`. | | `backend` | string | no | `file` (default), `pkcs11`, or `awskms`. | | `algorithm` | string | yes | Key algorithm: `ed25519`, `secp256k1`, `secp256k1eth`, or `mldsa65`. Set it explicitly. | | `key_id` | string | per backend | For `pkcs11`: hex `CKA_ID` of the key object. For `awskms`: KMS key ID, ARN, or `alias/`. | Consensus signing supports `ed25519`, `secp256k1eth`, and `mldsa65` on every backend, and `secp256k1` on the AWS KMS backend only. The algorithm name `mldsa65` has no underscores; the chain-side key type `ml_dsa_65` does. ### backend: file A key read from disk into memory. Development and testing only; the key is held in plaintext. | Field | Type | Required | Description | | ---------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `key_file` | string | yes | Path to the key. Accepts a CometBFT `priv_validator_key.json`, or a raw private key file (base64-encoded for `ed25519` and `mldsa65`, hex-encoded for `secp256k1eth`). | ### backend: pkcs11 A key on a PKCS#11 token or HSM. Signing happens on-device; the signer uses an existing key and never generates or imports one. | Field | Type | Required | Description | | ------------- | ------- | ------------------------------------ | ------------------------------------------------ | | `module` | string | yes | Path to the PKCS#11 module shared library. | | `token_label` | string | exactly one of the two | `CKA_LABEL` of the token. | | `slot` | integer | exactly one of the two | Slot number of the token. | | `key_label` | string | at least one of `key_label`/`key_id` | `CKA_LABEL` of the key object. | | `pin` | string | exactly one PIN source | User PIN, inline. Prefer the alternatives below. | | `pin_env` | string | exactly one PIN source | Environment variable holding the PIN. | | `pin_file` | string | exactly one PIN source | Path to a file holding the PIN. | ### backend: awskms A key held in AWS KMS. Signing happens through the KMS Sign API; credentials resolve through the AWS default credential chain, and no secret material appears in the config. | Field | Type | Required | Description | | ---------- | ------ | -------- | --------------------------------------------------------------------------- | | `region` | string | no | AWS region of the key. Falls back to the AWS default chain. | | `profile` | string | no | Shared-config profile name. Falls back to the AWS default chain. | | `endpoint` | string | no | Custom KMS endpoint URL, for LocalStack-style testing. Leave unset for AWS. | ## grpc Optional. When present, the signer also serves the SignerService gRPC API alongside privval. Its usage documentation ships with the interoperability release; the fields are listed here for completeness. | Field | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------------------------------------------------- | | `listen` | string | yes | `host:port` the gRPC server binds to. | | `tls_cert` | string | no | TLS server certificate file. Omitting both TLS fields serves plaintext, for local testing only. | | `tls_key` | string | no | TLS server private key file. | | `keys` | list | yes | The keys the service exposes; see below. | Each `grpc.keys` entry: | Field | Type | Required | Description | | ----------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | yes | Logical key identifier returned to clients. | | `backend` | string | yes | `file`, `awskms`, or `pkcs11`. | | `algorithm` | string | yes | For `file` and `pkcs11`: `ed25519` or `secp256k1eth`. For `awskms`: `ed25519`, `secp256k1`, or `secp256k1eth`. `mldsa65` has no gRPC signature scheme and is privval-only. | | `key_id` | string | awskms | KMS key ID, ARN, or `alias/`. | | `key_file` | string | file | Path to the key. Accepts a CometBFT `priv_validator_key.json`, or a raw private key file (base64-encoded for `ed25519`, hex-encoded for `secp256k1eth`). | A `grpc.keys` entry with `backend: pkcs11` also takes the PKCS#11 fields, with the same rules and the same validation as a privval key. See [backend: pkcs11](#backend-pkcs11) for `module`, `token_label`, `slot`, `key_label`, `key_id`, and the PIN sources. The gRPC server performs no caller authentication or authorization. Any client that can reach the listener can use every configured key. Restrict access with TLS and network controls. ## Constraints checked at startup * Every `validators[].chain_id` and every entry in `keys[].chain_ids` must match a declared `chains[].id`. * Each chain must be backed by exactly one key. * PKCS#11 keys must select the token with exactly one of `token_label` or `slot`, select the key with `key_label` or `key_id`, and supply exactly one PIN source. * A declared chain with no `validators` entry is not rejected. The signer starts, binds nothing, and signs nothing, with no warning, so check that every chain has a validator entry. * Every chain's sign-state file must exist and be non-empty. A missing or empty file fails closed with `sign-state file is missing or empty; refusing to start at height 0`, so the signer cannot re-sign a height it has no record of. See [First start on a new chain](#first-start-on-a-new-chain). This list is not exhaustive. Config-level rejections are prefixed `config:` and name the field at fault; errors raised later, when a backend or chain signer is opened, use their own prefixes such as `app:` or `file:`. ## First start on a new chain A key that has never signed on a chain has no sign-state file, so the checks above block its first start. Write the height-0 floor with: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms start --home --allow-fresh-state ``` `kms state init` writes a floor too, but it loads and validates `kms.yaml` first, so it only works once the chain is declared and the config is complete. It also defaults `--step` to 3, which refuses everything at that height and round, where `--allow-fresh-state` writes step 0. Pass `--step 0` to match. See [Migrate from TMKMS](/sdk/latest/kms/migrate-from-tmkms) for more details. Pass `--allow-fresh-state` only for a chain the key has genuinely never signed on. It will not overwrite an existing floor, but a service definition that carries it permanently removes the protection: a deleted or truncated state file resets the double-sign floor to zero instead of stopping the signer. ## Next steps * Task-shaped backend setup. See [Configure a signing backend](/sdk/latest/kms/configure-backend). * First-time setup end to end. See [Remote signing tutorial](/sdk/latest/kms/tutorial-file-backend). # Configure a signing backend Source: https://docs.cosmos.network/sdk/latest/kms/configure-backend Point Cosmos-KMS at the custodian holding the consensus key: AWS KMS, a PKCS#11 HSM, or a file. A signing backend is the custodian that holds the validator's consensus key and signs with it. Cosmos-KMS supports three: AWS KMS, a PKCS#11 hardware module, and a file on disk. The `keys` block in `kms.yaml` selects one, and this guide configures each in turn. ## Prerequisites * A running signer and node, which [Remote signing tutorial](/sdk/latest/kms/tutorial-file-backend) sets up. Between backends only the `keys` block changes in `kms.yaml`. The file backend reuses the tutorial's existing key, but the AWS KMS and PKCS#11 backends hold a new consensus key the validator must adopt first. See [Adopt the key on a validator](#adopt-the-key-on-a-validator). * Per backend: the [AWS CLI](https://aws.amazon.com/cli/) and an AWS account for AWS KMS; your HSM's tooling plus [OpenSC](https://github.com/OpenSC/OpenSC)'s `pkcs11-tool` for PKCS#11, with [SoftHSM2](https://www.opendnssec.org/softhsm/) as a local test rig. * The chain's binary; the examples use `simd`. To build it and run a node, see [Run a node](/sdk/latest/node/run-node). The `algorithm` field is required for all backends. ## AWS KMS AWS KMS caps messages it signs in raw form at 4096 bytes. This binds `ed25519` and `secp256k1`, which send the raw consensus message, so be careful with features that enlarge it, such as vote extensions. The signer checks the size itself and fails before calling AWS. `mldsa65` and `secp256k1eth` are not bound by the cap. AWS Key Management Service (KMS) is a managed service that stores cryptographic keys and signs with them on request. With this backend, the consensus key lives in KMS and never leaves it. The signer calls the KMS Sign API to produce each signature. Credentials come from the standard AWS default chain: environment, shared config, SSO, or an IAM role. No secrets enter `kms.yaml`. This backend signs with any key type Cosmos-KMS supports: `ed25519`, `secp256k1`, `secp256k1eth`, and post-quantum `mldsa65`. Provision the AWS key with the key spec that matches the algorithm, such as `ECC_NIST_EDWARDS25519` for `ed25519` or `ML_DSA_65` for `mldsa65`. Create an ML-DSA-65 signing key and give it an alias (or skip these and point `key_id` at a key you already have): ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} aws kms create-key --key-spec ML_DSA_65 --key-usage SIGN_VERIFY ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} aws kms create-alias --alias-name alias/validator --target-key-id ``` Then bind it in the `keys` block: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keys: - chain_ids: [my-chain-1] backend: awskms algorithm: mldsa65 key_id: alias/validator region: us-east-1 ``` The `key_id` accepts a key ID, a full ARN, or an alias. `region` is optional and falls back to the AWS default chain. Two more optional fields, not shown above, are `profile` (a named shared-config profile) and `endpoint` (for LocalStack-style testing only). The signer needs only two IAM permissions on the key: `kms:GetPublicKey`, called once at startup, and `kms:Sign`, called per block. `kms:DescribeKey` is not required. Attach a least-privilege policy scoped to the key ARN, not the alias; AWS resolves the alias to the key server-side: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["kms:GetPublicKey", "kms:Sign"], "Resource": "arn:aws:kms:::key/" } ] } ``` A freshly created KMS key is a new consensus key. To move an existing validator onto it, rotate the validator to the new key; to stand up a new validator, register it with the new key's public key. See [Rotate a consensus key held in Cosmos-KMS](/sdk/latest/kms/rotate-key-remote-signer). Creating the key inside KMS keeps it from ever leaving the service. ## `PKCS#11` The `PKCS#11` backend keeps the consensus key on a hardware security module (HSM) or token and signs on the device through `PKCS#11`, the standard interface for cryptographic hardware. The key never leaves the module. The signer uses an existing key only, so provision one with your HSM tooling first. The example commands below show how to provision an ed25519 key with SoftHSM2 for testing: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} softhsm2-util --init-token --free --label validator-token --pin 1234 --so-pin 4321 ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --pin 1234 --keypairgen --key-type EC:edwards25519 --label validator --id 01 ``` The `--module` path is platform-specific: Linux uses `/usr/lib/softhsm/libsofthsm2.so`, and macOS Homebrew uses `/opt/homebrew/lib/softhsm/libsofthsm2.so` (`/usr/local/lib/softhsm/...` on Intel). Use the same path for the `module` field below. Then bind it: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keys: - chain_ids: [my-chain-1] backend: pkcs11 algorithm: ed25519 module: /usr/lib/softhsm/libsofthsm2.so token_label: validator-token key_label: validator pin_env: KMS_PIN ``` The signer enforces three field rules at startup: * Select the token with exactly one of `token_label` or `slot`. * Select the key with `key_label`, `key_id` (the hex `CKA_ID`), or both. * Supply the PIN through exactly one of `pin`, `pin_env`, or `pin_file`. Prefer `pin_env` or `pin_file`. An inline `pin` puts the PIN in the config file. Set the PIN in the environment the signer runs in so it matches `pin_env`: `export KMS_PIN=`. If you are using SoftHSM2 as the test rig, also `export SOFTHSM2_CONF=` so the module can find the token. As with AWS KMS, a key generated in the HSM is a new consensus key. Move an existing validator onto it by rotation, or register a new validator with its public key. See [Rotate a consensus key held in Cosmos-KMS](/sdk/latest/kms/rotate-key-remote-signer). ## File The file backend reads the consensus key from a file on the signer's disk into memory. It is the development and testing backend. The key sits in plaintext, so it is not production custody. The [Remote signing tutorial](/sdk/latest/kms/tutorial-file-backend) covers it end to end: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keys: - chain_ids: [my-chain-1] backend: file algorithm: ed25519 key_file: priv_validator_key.json ``` The `key_file` accepts a CometBFT `priv_validator_key.json` or a raw base64-encoded private key. The file backend also signs post-quantum consensus keys. Generate the key with `simd init --consensus-key-algo ml_dsa_65` and bind it: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keys: - chain_ids: [my-chain-1] backend: file algorithm: mldsa65 key_file: priv_validator_key.json ``` ## Adopt the key on a validator The AWS KMS and PKCS#11 backends generate the key inside the custodian, so it is a new consensus key, not the one your validator already runs. Do not just repoint an existing validator's signer at a fresh key. The node finds its consensus key absent from the validator set and demotes itself, so it never proposes and there is no signature error to look for. The symptom is `This node is not a validator` in the node log and a chain that does not advance. Only the file backend, pointed at the validator's existing `priv_validator_key.json`, skips this step. Adopt the key one of two ways: * Existing validator: rotate its consensus key to the new one, which derives the new public key from a shadow node and swaps it in with no downtime. See [Rotate a consensus key held in Cosmos-KMS](/sdk/latest/kms/rotate-key-remote-signer). * New validator: register it with the new key's consensus public key using `gentx --pubkey` (or the `pubkey` field of `create-validator`'s validator.json). Read the public key from the custodian itself, since `gentx` runs before the chain exists and there is no node to query. For PKCS#11, read the object and strip the DER wrapper, leaving the raw 32 bytes to base64: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pkcs11-tool --module --login --pin --read-object --type pubkey --label validator \ | xxd -p | tr -d '\n' | sed 's/^302a300506032b6570032100//' | xxd -r -p | base64 ``` For AWS KMS, `aws kms get-public-key` returns a DER `SubjectPublicKeyInfo`; strip the same wrapper before encoding. ## Verify any backend Verification is the same regardless of custodian. Start the signer, start the node, and confirm blocks flow, exactly as in [the tutorial](/sdk/latest/kms/tutorial-file-backend). If this key has never signed on the chain, the signer's first start needs `--allow-fresh-state ` to write the height-0 double-sign floor, otherwise it exits with `sign-state file ... is missing or empty; refusing to start at height 0`. Pass it only on that first start. See [Start the signer](/sdk/latest/kms/tutorial-file-backend) in the tutorial for the full explanation, and the [configuration reference](/sdk/latest/kms/configuration-reference) for `kms state init`. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/status | jq '.result.sync_info.latest_block_height' ``` ## What can go wrong * The signer rejects the config at startup: both `token_label` and `slot` set, or more than one PIN source. The error names the offending field. A missing `algorithm` on the file backend instead fails with the less specific `file: unknown key type`. * The signer starts but cannot reach the key: wrong `module` path, wrong `key_id` or alias, or AWS credentials with no permission (denied `kms:GetPublicKey` fails at `kms start` with `awskms: get public key for "": `). These also fail at startup. * The node exits with a pubkey timeout: the signer is not running or not reachable. Start the signer first. It dials and retries. ## Next steps * Look up any config field, its type, and its constraints. See the [configuration reference](/sdk/latest/kms/configuration-reference). * Run the whole flow once with the file backend. See [Remote signing tutorial](/sdk/latest/kms/tutorial-file-backend). * Move an existing validator onto a key in a new backend by rotation. See [Rotate a consensus key held in Cosmos-KMS](/sdk/latest/kms/rotate-key-remote-signer). # Migrate from TMKMS Source: https://docs.cosmos.network/sdk/latest/kms/migrate-from-tmkms Move a validator from TMKMS to Cosmos-KMS: translate the config, move or rotate the key, and cut over without double signing. This guide moves a validator's signing from TMKMS to Cosmos-KMS, the recommended remote signer going forward. Cosmos-KMS includes several upgrades over TMKMS: it adds AWS KMS and PKCS#11 backends and post-quantum ML-DSA signing. The migration translates the config, gets the key into a Cosmos-KMS backend, and cuts over with exactly one signer alive at every moment. For what Cosmos-KMS is and how it relates to TMKMS, see [Cosmos-KMS and remote signing](/sdk/latest/kms/remote-signing). The validator node itself needs no changes: both signers speak the same privval protocol to the same `priv_validator_laddr` listener. TMKMS connects over CometBFT's SecretConnection, and the Cosmos-KMS `tcp://` transport is the same, so the listener works unchanged. ## Prerequisites * A validator currently signing through TMKMS, with access to its `tmkms.toml` and state file. * [jq](https://jqlang.org/), used to translate the state file in step 4. * Cosmos-KMS installed and initialized with `kms init`. [Remote signing tutorial](/sdk/latest/kms/tutorial-file-backend) covers installation, which needs [Go](https://go.dev/doc/install) 1.26 or later, [make](https://www.gnu.org/software/make/), and [git](https://git-scm.com/). ## 1. Translate the config Create `kms.yaml` and translate each block from your `tmkms.toml`: | tmkms.toml | kms.yaml | Notes | | ---------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `[[chain]]` `id` | `chains[].id` | Same value. | | `[[chain]]` `state_file` | `chains[].state_file` | See [step 4](#4-translate-the-double-sign-state) before reusing a path. | | `[[chain]]` `key_format` | none | Not needed; Cosmos-KMS has no per-chain serialization config. | | `[[validator]]` `addr` | `validators[].addr` | Drop the `@` prefix; `tcp://host:port` uses CometBFT's SecretConnection, matching TMKMS. For a node running libp2p, use `noise://@:` instead, where the peer ID is the validator's, from `cometbft show-node-id --libp2p`. A `noise://` host must be an IP literal, bracketed for IPv6; hostnames are rejected. Noise is mutual, so the validator must also carry the signer's peer ID, from `kms peer-id`, in its allowlist, or it rejects the connection. | | `[[validator]]` `chain_id` | `validators[].chain_id` | Same value. | | `[[validator]]` `secret_key` | `validators[].identity_key` | Different format; use the `identity.json` from `kms init` rather than converting. | | `[[validator]]` `protocol_version` | none | Not needed. | | `[[providers.softsign]]` | `keys[]` with `backend: file` | See [step 2](#from-softsign). | | `[[providers.yubihsm]]` | `keys[]` with `backend: pkcs11` | PKCS#11 HSM; see [step 2](#from-yubihsm-or-another-hsm). | | `[[providers.ledgertm]]` | none | Not yet supported in Cosmos-KMS; see [step 2](#from-yubihsm-or-another-hsm). | A standard cosmos-sdk node has no peer ID on its `priv_validator_laddr` listener, so there is usually no `@` prefix to carry over, and both TMKMS and Cosmos-KMS use a bare `tcp://host:port`. Below is a complete `kms.yaml` example for a single validator on the file backend: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} chains: - id: my-chain-1 state_file: state/my-chain-1.json validators: - chain_id: my-chain-1 addr: tcp://127.0.0.1:26659 identity_key: identity.json keys: - chain_ids: [my-chain-1] backend: file algorithm: ed25519 key_file: priv_validator_key.json ``` The `keys` block shown is the file backend. Its fields differ per backend, which step 2 covers. It also needs `keys[].algorithm`, which has no `tmkms.toml` equivalent, so set it explicitly (`ed25519` for a softsign key), as the example shows. The [configuration reference](/sdk/latest/kms/configuration-reference) lists every field. ## 2. Move the consensus key into a backend Get the consensus key into the backend named in your `keys` block. The path depends on where TMKMS holds it today. ### From softsign The softsign backend keeps the key in a file, but the format differs from what the Cosmos-KMS file backend reads. A TMKMS softsign key is the base64-encoded 32-byte ed25519 seed, while the file backend expects a CometBFT `priv_validator_key.json` or the base64-encoded 64-byte ed25519 key. Pointing `key_file` at a softsign key directly fails with `expected 64-byte ed25519 key, got 32`. If you still have the validator's original `priv_validator_key.json` (TMKMS softsign was imported from it), point `key_file` at that file directly. No conversion is needed. From only the softsign key (the file named by the `path` in your `[[providers.softsign]]` block), expand the 32-byte seed into the 64-byte `seed||pubkey` form the file backend accepts. Replace `tmkms_softsign.key` in the command with that file: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} python3 - tmkms_softsign.key converted.key <<'PY' import base64, sys from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat seed = base64.b64decode(open(sys.argv[1]).read().strip()) assert len(seed) == 32, f"expected 32-byte seed, got {len(seed)}" sk = Ed25519PrivateKey.from_private_bytes(seed) pub = sk.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw) open(sys.argv[2], "w").write(base64.b64encode(seed + pub).decode()) PY ``` Point `key_file` at `converted.key`. The command needs Python 3 with the `cryptography` package (`pip install cryptography`). `converted.key` and the exported softsign key are unencrypted private keys. Shred them after import (`shred -u` or `rm -P`) and keep them out of shell history and backups. ### From YubiHSM or another HSM Most HSM-held keys do not move. A YubiHSM, a Fortanix device, or any HSM that exposes the key over PKCS#11 works with the Cosmos-KMS `pkcs11` backend directly: wire up a `keys` block against the same module and keep signing with the same key. Keys generated inside an HSM are typically non-exportable, which is the point of an HSM, so this no-move path is the normal case. For the PKCS#11 config, see [Configure a signing backend](/sdk/latest/kms/configure-backend). When you are changing custodian rather than keeping the key in place, migrate by rotation: generate a new key in the target custodian and rotate the validator to it on chain, which retires the TMKMS-held key entirely. See [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). Ledger-held consensus keys are not currently supported in Cosmos-KMS. If you need it, open a [feature request](https://github.com/cosmos/kms/issues) so demand for it can be gauged. ## 3. Stop TMKMS Stop TMKMS and confirm the process is gone. The validator misses blocks until Cosmos-KMS takes over. That gap is expected: missed blocks are recoverable, a double sign from two live signers is not. Keep it short to avoid downtime jailing. Never run TMKMS and Cosmos-KMS at the same time against the same validator key. Each keeps its own last-signed state, so together they can sign the same height, which is a double sign. Stop one fully before starting the other, in both directions, including any rollback. ## 4. Translate the double-sign state TMKMS and Cosmos-KMS both track the last signed height, round, and step per chain, and the protection only works if the new signer starts at or above the old signer's high-water mark. The two store this state differently, so it needs translating. Point the following command at the TMKMS state file named by `state_file` in your `[[chain]]` block (`tmkms_state.json` is a placeholder). It writes the translated state into the Cosmos-KMS location: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} mkdir -p /state jq '{height, round: (.round|tonumber), step: (.step + 1)}' tmkms_state.json > /state/.json ``` The `jq` expression makes two conversions: `round` from string to number (the source of the `int32` error otherwise), and `step + 1` to remap TMKMS's 0/1/2 signing steps to CometBFT's 1/2/3. The redirection above overwrites whatever is at that path, so a re-run or a stale `tmkms_state.json` silently lowers the double-sign floor. Check the file does not already exist first. ## 5. Start Cosmos-KMS Start Cosmos-KMS with the completed `kms.yaml`. It dials the validator and resumes signing. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms start --home ``` ## 6. Confirm signing resumed Confirm the chain is producing again and the signer's state file is advancing. Run the status check twice; a climbing height means signing resumed: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/status | jq '.result.sync_info.latest_block_height' ``` The signer's `/state/.json` should also advance past the last height TMKMS signed. ## What can go wrong * Both signers briefly alive: the double-sign risk above. Cut over with the old process confirmed dead, not just signaled. * The validator stays dark after cutover: the new signer is not reaching the listener. Check the `validators[].addr` translation and the firewall between the hosts. * `dial tcp: lookup node-id@host: no such host`: the `@` prefix was left in `validators[].addr`. Drop it and keep only host and port. * `file: parse key file "": expected 64-byte ed25519 key, got 32`: a TMKMS softsign key was pointed at directly. See the [key section](#from-softsign). * `chain "": reload sign-state: json: cannot unmarshal string into Go value of type int32`: the TMKMS state file was reused as is. Translate it first; see [step 4](#4-translate-the-double-sign-state). ## Next steps * Harden the new setup. See [Remote signing best practices](/sdk/latest/kms/best-practices). * Full field reference for the translated config. See the [configuration reference](/sdk/latest/kms/configuration-reference). # Overview Source: https://docs.cosmos.network/sdk/latest/kms/remote-signing What privval is, what Cosmos-KMS is and is not, and why validator keys belong in a KMS or HSM rather than local files. This page covers remote signing for validators and Cosmos-KMS, the Cosmos stack's remote signer. The guides in this section set both up. ## What is remote signing? Remote signing splits a validator into two processes: a node that participates in consensus, and a signer that holds the consensus key and produces signatures on the node's behalf. The node handles blocks, gossip, and peers; the signer holds the one secret that matters. Separating the two keeps signing secure and makes the node replaceable. The key moves off the node's exposed filesystem into a hardware security module (HSM) or a cloud key service, where the people and automation that maintain the node never touch it. And because the node holds no secrets, it can be rebuilt, upgraded, or replaced at will. Once it is replaced, the signer reconnects and signing continues. CometBFT supports this through privval, the seam between the node and whatever holds the key. With `priv_validator_laddr` set in `config.toml`, the node signs nothing locally: it listens for a signer connection and sends each vote and proposal out for signature. The wire protocol carries four requests: sign this vote, sign this proposal, return the public key, and a keepalive ping. The node does not necessarily care what is on the other end. ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram participant N as Validator node participant S as Cosmos-KMS signer participant B as Backend (file / PKCS11 / AWS KMS) N->>S: vote or proposal to sign (privval) S->>B: sign with consensus key B-->>S: signature S-->>N: signature ``` The node forwards each vote or proposal to the signer over the privval connection. The signer has its backend produce the signature and returns only that, so the consensus key never leaves the backend. ## Cosmos-KMS The [`cosmos-kms`](https://github.com/cosmos/kms) repo is a remote signer for CometBFT, written in Go. It implements the signer side of privval and answers a node's signing requests from a backend that holds the key. One signer process can sign for multiple chains, with each chain backed by exactly one key, and can hold connections to multiple nodes per chain. It is built for operators who must keep validator keys in real custody, an HSM or a cloud key service, rather than in files on chain infrastructure. Three backends are supported: * File: a key file on the signer's disk, for development and testing, not production custody. * PKCS#11: a hardware security module, signing on-device through the standard HSM interface. * AWS KMS: a key that never leaves AWS KMS, using standard AWS credentials and IAM. Cosmos-KMS is designed to work with existing validator infrastructure, and validators should prefer it over previous remote-signing solutions. If you run [TMKMS](https://github.com/iqlusioninc/tmkms) today, see [Migrate from TMKMS](/sdk/latest/kms/migrate-from-tmkms). Cosmos-KMS speaks the CometBFT privval protocol, so it can sign for any node that implements that protocol. ## How it works A running signer comes down to five moving parts: * Configuration: one file, `kms.yaml`, in three blocks: the chains it signs for, the validators it dials, and the keys binding each chain to exactly one backend. The `kms init` command scaffolds it; `kms start` serves until stopped. * Signing: requests travel the privval connection, the backend signs in place, on disk, on the HSM, or inside AWS KMS, and only the signature returns. The private key never crosses the wire. * Connection: the signer dials out, so the signing host needs no inbound ports. The address scheme selects the transport: `tcp://` uses CometBFT's SecretConnection, and `noise://` adds mutual peer pinning, where each side refuses any connection from an unexpected peer. * Key types: over privval, the signer signs `ed25519`, `secp256k1eth`, and post-quantum `mldsa65`, plus `secp256k1` on the AWS KMS backend; the gRPC signer service signs `ed25519`, `secp256k1eth`, and `secp256k1`, but not `mldsa65`, which has no gRPC signature scheme and is privval-only. For per-backend support, see [Configure a signing backend](/sdk/latest/kms/configure-backend). * Double-sign protection: a per-chain last-signed state file refuses anything at or below a height, round, and step already signed. The protection lives with the key, so even a misbehaving or duplicated validator node cannot force a double sign. Because this protection is per state file, it is recommended never to run two signers for the same key. For a backup, connect a single signer to more than one validator node. ## Next steps * Run a remote signer against a local chain. See [Remote signing tutorial](/sdk/latest/kms/tutorial-file-backend). * Move the key into real custody, AWS KMS or an HSM. See [Configure a signing backend](/sdk/latest/kms/configure-backend). * Harden the signer's placement and transport. See [Remote signing best practices](/sdk/latest/kms/best-practices). * Look up any `kms.yaml` field. See the [configuration reference](/sdk/latest/kms/configuration-reference). * Understand which key the signer holds and how it rotates. See [Key rotation](/sdk/latest/keys/key-rotation). * Understand post-quantum consensus keys and their costs. See [Post-quantum keys](/sdk/latest/keys/post-quantum-keys). # Rotate a consensus key held in Cosmos-KMS Source: https://docs.cosmos.network/sdk/latest/kms/rotate-key-remote-signer Rotate a validator's consensus key when the current key lives in a remote signer: new key in the backend, a second node and signer, rotate, cut over. This tutorial rotates a staked validator's consensus key when Cosmos-KMS holds the current key rather than a local file. It continues the [remote signing tutorial](/sdk/latest/kms/tutorial-file-backend) and reuses that setup: the single-node chain `kms-demo-1`, with node home `~/.kms-demo-node` (RPC on port 26657, privval listener on port 26659) and signer home `~/.kms-demo`, whose signer dials that listener. The rotation is the standard zero-downtime rotation with three differences. The new key is generated in a signing backend. The second node gets its own signer process. The new public key comes from the second node's RPC instead of a key file. For the standard procedure and the rotation rules, see [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). Key rotation can introduce security implications for your chain. Read the [Key rotation](/sdk/latest/keys/key-rotation) overview in its entirety before proceeding. Commands use `simd` as the chain binary and `val` as the validator key name. Substitute your own for a real chain. The live pair and the new pair run on one host, so the second node and its new signer take spare ports: the second node listens for its signer on port 26669 and serves RPC on port 26667. ## Prerequisites * A validator already signing through Cosmos-KMS, from the [remote signing tutorial](/sdk/latest/kms/tutorial-file-backend), still running. * All prerequisites of the standard rotation: Cosmos SDK 0.55 and CometBFT 0.40 or later, the target key type in consensus params, no rotation in the current unbonding period, and funds for the burned rotation fee plus gas. See [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). * [jq](https://jqlang.org/) and [curl](https://curl.se/), used to derive the new public key. * Spare ports on the host for the second node and its signer. * The chain's binary; the examples use `simd`. To build it and run a node, see [Run a node](/sdk/latest/node/run-node). ## 1. Generate the new key in a backend For an HSM or AWS KMS, generate the key with the backend's own tooling. The commands are in [Configure a signing backend](/sdk/latest/kms/configure-backend). This guide uses the file backend, so a scratch `simd init` generates a fresh key file. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd init scratch --chain-id kms-demo-1 --home ~/.kms-demo-scratch ``` For a post-quantum `mldsa65` target on the file backend, add `--consensus-key-algo ml_dsa_65` to the `simd init` command above and set `algorithm: mldsa65` in the signer's `keys` block in step 2. The PKCS#11 and AWS KMS backends also sign `mldsa65`; see [Configure a signing backend](/sdk/latest/kms/configure-backend) for generating the key on those. The new key is `~/.kms-demo-scratch/config/priv_validator_key.json`. The rest of the scratch home is disposable. The new key must be freshly generated. Reusing key material any signer has signed with risks a double sign, which tombstones the validator. ## 2. Configure a second signer with the new key One Cosmos-KMS process cannot hold two keys for the same chain. The new key must run in its own process, with its own home and double-sign state: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms init --home ~/.kms-demo2 ``` Copy the new key to the second signer's home: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cp ~/.kms-demo-scratch/config/priv_validator_key.json ~/.kms-demo2/priv_validator_key.json ``` Replace the contents of `~/.kms-demo2/kms.yaml` with the following. The `addr` is the port the second node listens on for its signer. For an HSM or AWS KMS key, the `keys` block instead binds the backend entry from step 1: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} chains: - id: kms-demo-1 validators: - chain_id: kms-demo-1 addr: tcp://127.0.0.1:26669 identity_key: identity.json keys: - chain_ids: [kms-demo-1] backend: file algorithm: ed25519 key_file: priv_validator_key.json ``` For a post-quantum `mldsa65` key, set `algorithm: mldsa65` in the signer's `keys` block above. Do not start the signer yet. It starts in step 3, right before the second node, so its connection retries are still fast when the node comes up. ## 3. Bring up the second node and its signer Initialize a fresh node home and give it the chain's genesis: On a real chain with history, a fresh node takes days to sync from genesis. Use state sync or a snapshot to reach the chain head quickly. See [State sync](/sdk/latest/node/run-node#state-sync) for more info. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd init shadow --chain-id kms-demo-1 --home ~/.kms-demo-node2 ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cp ~/.kms-demo-node/config/genesis.json ~/.kms-demo-node2/config/genesis.json ``` With the prep done, start the new signer and the second node in quick succession. In one terminal, start the signer. It logs `dial failed` and retries until the node exists: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms start --home ~/.kms-demo2 --allow-fresh-state kms-demo-1 ``` The new key has never signed on this chain, and this signer home is new, so it holds no sign-state file. `--allow-fresh-state` writes the height-0 double-sign floor so the signer will start. Without it the signer exits with `sign-state file ... is missing or empty; refusing to start at height 0`. Use `--allow-fresh-state` only here, for the new key's first start. Do not add it to the live signer or to any later start of this one. Once a state file exists the flag has no effect, so it fails quietly, but it means a lost or truncated state file resets the double-sign floor to zero rather than stopping the signer. After the cutover in step 5, run the new signer with the bare command. Right away, in a second terminal, start the second node. The flags point it at the new signer on port 26669. Move its listeners off the live node's ports, and peer it with the live node so it syncs: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd start --home ~/.kms-demo-node2 \ --priv_validator_laddr tcp://127.0.0.1:26669 \ --p2p.laddr tcp://0.0.0.0:26666 \ --rpc.laddr tcp://127.0.0.1:26667 \ --grpc.address localhost:9092 \ --proxy_app tcp://127.0.0.1:26668 \ --rpc.pprof_laddr localhost:6061 \ --p2p.persistent_peers "$(simd comet show-node-id --home ~/.kms-demo-node)@127.0.0.1:26656" ``` If the second node exits with `can't get pubkey: ... endpoint connection timed out`, the signer has backed off to slow retries. Restart the signer, then start the node again. Confirm the second node has caught up. The value is `false` once it is synced: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26667/status | jq '.result.sync_info.catching_up' ``` Until the rotation applies, the second node follows the chain without signing. Its signer logs `served pubkey request` but no signatures. That is correct. ## 4. Derive the new public key and rotate The second node fetched its key from the new signer at startup and reports it at `/status`. Read it and reformat it into the proto-JSON the rotation command accepts: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} PK=$(curl -s localhost:26667/status | jq -c '{"@type":"/cosmos.crypto.ed25519.PubKey", key: .result.validator_info.pub_key.value}') ``` For an ML-DSA key, the proto type differs: use `"@type":"/cosmos.crypto.mldsa65.PubKey"` in the jq expression instead. Submit the rotation from the operator account and capture the transaction hash: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} TXHASH=$(simd tx staking rotate-cons-pub-key "$PK" --from val --keyring-backend test --home ~/.kms-demo-node --chain-id kms-demo-1 --node tcp://localhost:26657 --gas auto --gas-adjustment 1.5 --fees 2000stake --yes --output json | jq -r .txhash) ``` After the transaction lands in a block, confirm the code is `0`: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query tx "$TXHASH" --node tcp://localhost:26657 --output json | jq '{height: .height, code: .code}' ``` The burned rotation fee is charged separately from the gas fee above. ## 5. Verify and retire the old pair The validator set swaps to the new key atomically two heights after execution. Confirm the set carries only the new key. The value matches `$PK`, and the old key is gone: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/validators | jq -r '.result.validators[].pub_key.value' ``` At the swap, CometBFT stops requesting signatures from the old pair on its own. Stop the live node (`~/.kms-demo-node`) and the live signer (`~/.kms-demo`) with Ctrl-C in their terminals. Confirm blocks keep flowing through the new pair. Run this twice a few seconds apart and watch the height climb: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26667/status | jq '.result.sync_info.latest_block_height' ``` Keep the old key in its backend until the unbonding period ends. The validator remains slashable for its past behavior until then. ## What can go wrong * The new signer exits with `app: multiple signers bound to chain`: both keys are in one `kms.yaml`. Run the new key in its own process. * The validator is jailed after the rotation and neither signer is signing: the rotation targeted the stray local key. Rotate to the key from the second node's `/status`, once the unbonding window allows it. * The transaction is rejected: the standard failure modes apply, including the rotation limit and unsupported key types. See [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key). ## Next steps * Harden the new signer's placement and transport. See [Remote signing best practices](/sdk/latest/kms/best-practices). * Rotate to a post-quantum key with the same procedure. See [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa). # Remote signing tutorial Source: https://docs.cosmos.network/sdk/latest/kms/tutorial-file-backend Tutorial: stand up a local chain whose validator signs through Cosmos-KMS, using the file backend. This tutorial builds a working remote signer from scratch: a single-node local chain where a Cosmos-KMS process signs the votes instead of the node itself. It uses the file backend, which needs no HSM or cloud account and exists for exactly this kind of learning setup. At the end, you stop the signer and watch the chain stall, which proves where signing really happens. This tutorial uses one node, one signer, and one key. Commands use `simd` for the chain binary. The node home is `~/.kms-demo-node` and the signer home is `~/.kms-demo`. ## Prerequisites * [Go](https://go.dev/doc/install) 1.26 or later, [make](https://www.gnu.org/software/make/), [git](https://git-scm.com/), [jq](https://jqlang.org/), and [curl](https://curl.se/). * A chain binary at Cosmos SDK 0.55 or later. The tutorial uses `simd`, built with `make install` in the [cosmos-sdk repo](https://github.com/cosmos/cosmos-sdk). To build it and run a node, see [Run a node](/sdk/latest/node/run-node). ## 1. Install Cosmos-KMS Clone and install the signer: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git clone https://github.com/cosmos/kms ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cd kms && make install ``` Confirm the binary works: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms version ``` ## 2. Create a single-node chain Set up a fresh chain home with one validator. Do not start the node yet: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Initialize the node home with the chain ID simd init signer-demo --chain-id kms-demo-1 --home ~/.kms-demo-node # Create the validator key in the test keyring simd keys add val --keyring-backend test --home ~/.kms-demo-node # Fund the validator account in genesis simd genesis add-genesis-account val 1000000000stake --keyring-backend test --home ~/.kms-demo-node # Register the validator with a genesis staking transaction simd genesis gentx val 500000000stake --chain-id kms-demo-1 --keyring-backend test --home ~/.kms-demo-node # Collect the gentx into the genesis file simd genesis collect-gentxs --home ~/.kms-demo-node ``` ## 3. Initialize the signer Scaffold the signer's home. This writes a stub `kms.yaml` and generates `identity.json`, the key the signer uses to authenticate its connection: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms init --home ~/.kms-demo ``` The command prints `initialized kms in /Users/you/.kms-demo`. Pass the `--home` flag on every `kms` command. Without it, the signer uses the current directory. ## 4. Give the signer the consensus key Copy the consensus key that `simd init` generated into the signer's home: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cp ~/.kms-demo-node/config/priv_validator_key.json ~/.kms-demo/priv_validator_key.json ``` Once the node is configured for remote signing, it never reads its local key file again. In production, move the key instead of copying it. Note that the node regenerates a fresh, unused consensus key file if it finds none, so moving the key reduces what is on the node host rather than leaving it key-free. For this tutorial, the copy keeps things simple. ## 5. Configure the signer Replace the contents of `~/.kms-demo/kms.yaml` with: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} chains: - id: kms-demo-1 validators: - chain_id: kms-demo-1 addr: tcp://127.0.0.1:26659 identity_key: identity.json keys: - chain_ids: [kms-demo-1] backend: file algorithm: ed25519 key_file: priv_validator_key.json ``` The three blocks say: sign for the chain `kms-demo-1`, dial its node at port 26659, and read the copied key file as an `ed25519` key. The file backend has no default algorithm, so the `algorithm` line is required. Relative paths resolve against the signer's home. ## 6. Point the node at the signer Open `~/.kms-demo-node/config/config.toml`, find the `priv_validator_laddr` line, and set it: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} priv_validator_laddr = "tcp://127.0.0.1:26659" ``` With this set, the node signs nothing locally. It listens on that port for a signer connection and forwards every vote and proposal to it. ## 7. Start the signer The node needs its signer available the moment it starts, so bring the signer up first: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms start --home ~/.kms-demo --allow-fresh-state kms-demo-1 ``` This validator has never signed on `kms-demo-1`, so no sign-state file exists yet. `--allow-fresh-state` writes the height-0 double-sign floor on this first start. Without it the signer refuses to start rather than risk re-signing a height it cannot prove it has already passed. Pass `--allow-fresh-state` only on a first start, and only for a chain the key has never signed on. It will not overwrite an existing floor, but leaving it in a service definition means a deleted or truncated state file resets the floor to zero instead of stopping the signer. Later starts in this tutorial use the bare command. To seed the floor as a separate step instead, run `kms state init --chain kms-demo-1 --height 0 --home ~/.kms-demo` and then start the signer with no flag. The signer logs `kms started` and dials the node. The node is not running yet, so the signer logs `dial failed; backing off` and keeps retrying. That is expected. Leave it running. ## 8. Start the node In a second terminal, start the node within five seconds of starting the signer: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd start --home ~/.kms-demo-node ``` If `simd start` exits with `can't get pubkey: ... endpoint connection timed out`, the signer has backed off to slow retries. Restart the signer, then start the node again within five seconds. The node opens its private-validator listener on port 26659. The signer's next dial connects, the node fetches its consensus public key from the signer, and block production begins. Confirm the height is climbing: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -s localhost:26657/status | jq '.result.sync_info.latest_block_height' ``` Also confirm the signer's double-sign protection state file is in place, written when the signer started: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ls ~/.kms-demo/state/kms-demo-1.json ``` ## 9. Prove the signer is doing the signing Stop the signer with Ctrl-C and watch the node's logs. Block production stalls because the validator can no longer sign. Start the signer again, this time with no `--allow-fresh-state`, because the state file now exists and carries the highest height signed so far: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} kms start --home ~/.kms-demo ``` The signer reconnects and the chain resumes. The node never touches a private key. Every signature comes from the signer. ## What you built A validator whose consensus key lives outside the node. The node handles consensus and networking. The signer holds the key and signs, and the double-sign state file travels with it. The file backend keeps this tutorial self-contained, but it holds the key in plaintext on disk and is not production custody. The production version of this setup swaps one config block to move the key into an HSM or AWS KMS. ## Next steps * Swap the file backend for real custody, AWS KMS or an HSM. See [Configure a signing backend](/sdk/latest/kms/configure-backend). * Harden the signer's placement and transport. See [Remote signing best practices](/sdk/latest/kms/best-practices). * Understand the architecture you just ran. See [Cosmos-KMS and remote signing](/sdk/latest/kms/remote-signing). * Look up any config field. See the [configuration reference](/sdk/latest/kms/configuration-reference). # Cosmos SDK Docs Source: https://docs.cosmos.network/sdk/latest/learn Version: v0.55 The Cosmos SDK is the most widely adopted, battle-tested Layer 1 blockchain stack, trusted by 200+ chains live in production. This modular framework enables you to build secure, high-performance blockchains with comprehensive guides covering everything from core concepts to advanced implementation patterns. New to the Cosmos SDK? Find the right starting point based on your background and what you want to build. Learn essential concepts including application anatomy, transaction lifecycles, accounts, and gas mechanics. Build and run a Cosmos chain from scratch, with step-by-step guidance from setup to a working custom module. Develop custom modules with comprehensive guides on module architecture, message handling, and state management. Set up, configure, and maintain nodes from local development environments to production deployments. Understand the fundamentals of Cosmos SDK, application-specific blockchains, and the SDK's architecture. # Accounts Source: https://docs.cosmos.network/sdk/latest/learn/concepts/accounts In [Cosmos Architecture](/sdk/latest/learn/intro/sdk-app-architecture), you learned that transactions change state and must be signed and validated. But who creates and signs these transactions? The answer is **accounts**. Accounts represent identities on a Cosmos SDK chain. They hold balances, authorize transactions with digital signatures, and prevent transaction replay using sequence numbers. Accounts are managed by the auth module (`x/auth`), which tracks account metadata like addresses, public keys, account numbers, and sequence numbers. Every account is controlled by a cryptographic keypair derived from a seed phrase. A seed phrase yields one or more private keys, each of which produces a public key and an account address. ## What is an account An account is an on-chain identity used to authorize transactions. Each account stores an address, a public key, an account number, and a sequence number, as defined by [`BaseAccount`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/auth/types/auth.pb.go#L32) in the `x/auth` module: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type BaseAccount struct { Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` PubKey *anypb.Any `protobuf:"bytes,2,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` AccountNumber uint64 `protobuf:"varint,3,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` Sequence uint64 `protobuf:"varint,4,opt,name=sequence,proto3" json:"sequence,omitempty"` } ``` Accounts can be used in other modules to associate on-chain state with an identity. For example, the bank module (`x/bank`) maps account addresses to token balances, and the staking module maps them to delegations. The private key and [seed phrase](#seed-phrases) are never stored on-chain; they are kept locally by the user or wallet. An account does not execute logic itself; instead, it authorizes [transactions](/sdk/latest/learn/concepts/transactions). Balance changes for accounts are handled by the modules that process the transaction's messages. An account's sequence number is used for [replay protection](#sequences-and-replay-protection) during transaction processing. ## Public and private keys Accounts are rooted in cryptographic keypairs. Cosmos SDK uses asymmetric cryptography, where a private key and public key form a pair. This is a fundamental concept in cryptography and is used to secure data and transactions. * A **private key** is used to sign transactions. Before signing, the transaction data is serialized and hashed; the private key then produces a digital signature over this hash. This signature proves ownership of the private key without revealing it. Private keys must always remain secret. * A **public key** is derived mathematically from the private key. The network uses it to verify signatures produced by the corresponding private key. Because the public key is derived through a one-way function, it is not possible to derive the private key from the public key. ## Seed phrases Most wallets do not generate raw private keys directly. Instead, they start from a seed phrase (mnemonic), a list of human-readable words such as: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} apple maple river stone cloud frame picnic ladder jungle orbit solar velvet ``` A private key is then derived from the seed phrase using a deterministic algorithm. Cosmos wallets follow common standards such as: * [BIP-39 (mnemonic phrases)](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) * [BIP-32 (hierarchical deterministic wallets)](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) * [BIP-44 (multi-account derivation paths)](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) From the seed phrase, a binary seed is computed and used to derive a master private key. From that master key, specific private keys are derived along a path (for example: `m/44'/118'/0'/0/0`, where `118` is the Cosmos coin type). Each private key produces a public key. Control of the seed phrase means control of the derived private keys and therefore control of the corresponding accounts. Losing the seed phrase without backing it up means losing access to the account forever. ## Addresses An address is a shortened identifier derived from the public key. The public key is hashed and encoded, typically in [Bech32](/sdk/latest/guides/reference/bech32) format, with a prefix that indicates the chain, for example `cosmos`. This address is what users share and what appears in state and transactions: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos1qnk2n4nlkpw9xfqntladh74er2xa62wgas7mv0 ``` An address is not the same as a public key. Because an address is only a hash of the public key, users can generate addresses and receive funds entirely offline. The public key is revealed on-chain the first time the account signs a transaction, at which point validators can verify the signature and the chain stores the public key alongside the account metadata. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Seed Phrase ↓ (BIP-39/BIP-32/BIP-44) Private Key (secp256k1) ↓ (elliptic curve math) Public Key ↓ (hash + Bech32 encoding) Address ``` ## Sequences and replay protection There are two types of transactions in the Cosmos SDK: ordered and unordered. Ordered transactions are the default. Each account tracks a sequence number starting at zero that increments with each transaction. The network rejects any transaction whose sequence number does not match the current value, preventing replay attacks and ensuring that dependent transactions from the same account execute in order (for example, sending tokens then immediately staking them). Unordered transactions bypass this check and use a timeout-based mechanism instead. Example: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Initial state: sequence = 0 After first accepted transaction: sequence = 1 After second accepted transaction: sequence = 2 ``` If a signed transaction carries `sequence = 1` but the account's current sequence is `2`, the transaction is rejected, ensuring that ordered transactions are applied in order and cannot be reused. The Cosmos SDK also supports optional unordered transactions, which allow transactions from the same account to be submitted and processed without strict sequence ordering. When a chain enables unordered transactions, replay protection uses a timeout timestamp and unordered nonce tracking instead of the normal per-signer sequence check. See [Transactions, Messages, and Queries](/sdk/latest/learn/concepts/transactions#message-execution-and-atomicity) for more information. ## Balances Accounts are associated with token balances stored on-chain. Balances are managed by the bank module (`x/bank`) and indexed by account address. While account metadata (address, public key, sequence number) is stored in the auth module's state, token balances are stored separately in the bank module's state. When tokens are sent from one account to another, the bank module updates balances in state. Conceptually, a token transfer decreases the sender's balance and increases the recipient's balance. An account must have sufficient balance to cover the tokens being sent and any associated transaction fees. If the balance is insufficient, the transaction is rejected during validation. ## Types of accounts Cosmos SDK supports several account types that extend the base account model: * **Base account**: A standard account that holds balances and signs transactions. This is the most common account type for users. * **Module account**: Owned by a [module](/sdk/latest/learn/concepts/modules) rather than a user. Module accounts are derived from the module name and cannot be controlled by a private key. For example, the staking module uses a module account to hold all delegated tokens, and the distribution module uses a module account to hold rewards before they are distributed. This design allows protocol logic to custody tokens without requiring a private key holder, which is essential for decentralized operations. For a working example of adding a module account to receive fees, see [Module accounts](/sdk/latest/tutorials/example/04-counter-walkthrough#module-accounts) in the Full Counter Module Walkthrough. * **Vesting account**: Holds tokens that unlock gradually over time according to a schedule. Vesting accounts are often used for team allocations or investor tokens that vest over months or years. They restrict spending to only unlocked tokens while still allowing the account to participate in staking and governance. All account types rely on the same key and address structure but may impose additional rules on balance usage. ## Accounts and transaction authorization Accounts authorize [transactions](/sdk/latest/learn/concepts/transactions) by producing digital signatures. A transaction includes: * One or more messages * A signature created using the private key * A sequence number * Associated fees When a transaction is signed, the transaction bytes are serialized and hashed. The private key then generates a digital signature over that hash. This signature proves that the holder of the private key approved the transaction, without revealing the private key itself. During execution of a standard ordered transaction: 1. The signature is verified using the account's public key. 2. The sequence number is checked against the account's current sequence. 3. Fees are deducted from the account's balance. 4. If validation passes, messages execute and may update state. 5. If execution succeeds, the sequence number increments and state updates are committed. High-level flow: ``` Seed Phrase ↓ Private Key ↓ signs Transaction ↓ verified with Public Key ↓ identifies Address ↓ updates State ``` Accounts provide identity and authorization, transactions carry intent, and modules execute the logic. The result is stored in state. To learn more about the transaction flow in a Cosmos blockchain, visit the [Transaction Lifecycle page](/sdk/latest/learn/concepts/lifecycle) ## Summary Accounts are the foundation of user interaction with a Cosmos SDK chain. They connect cryptographic keys to on-chain identity, authorize transaction execution, and prevent replay attacks. Understanding keys, addresses, balances, and sequence numbers provides the basis for understanding how transactions flow through the system. The next page, [Transactions, Messages, and Queries](/sdk/latest/learn/concepts/transactions), explains how accounts authorize the actions a transaction carries. # app.go Overview Source: https://docs.cosmos.network/sdk/latest/learn/concepts/app-go `app.go` is where an application is assembled into a working chain. It creates the `BaseApp` instance that talks to CometBFT, allocates store keys, initializes keepers, registers modules, configures execution ordering, mounts stores, and sets lifecycle hooks and the `AnteHandler`. Finally, it seals the application with `LoadLatestVersion`. The result is a single constructor, `NewExampleApp`, that returns a fully wired, ready-to-run chain. Most examples on this page come from the counter module example in the `example` repo, where `x/counter` is wired into a fuller chain. The minimal counter module example shows the smaller `app.go` delta needed to add `x/counter` to a stripped-down app. See [Step 10: Wire into app.go](/sdk/latest/tutorials/example/03-build-a-module#step-10-wire-into-appgo) in the Build a Module tutorial. ## What `app.go` does `app.go` performs a one-time, ordered initialization of the entire chain: ``` 1. Create BaseApp and codecs 2. Allocate store keys 3. Initialize keepers 4. Create the ModuleManager 5. Configure execution ordering 6. Register module services 7. Mount KV stores 8. Set lifecycle hooks (InitChainer, PreBlocker, BeginBlocker, EndBlocker, AnteHandler) 9. Load latest version ``` This sequence is strict: * Keepers require store keys, so keys come first. * The `ModuleManager` depends on keepers, so modules come after keeper construction. * Lifecycle hooks depend on the `ModuleManager`, so hook wiring comes later. * `LoadLatestVersion` seals `BaseApp`, so it runs last. ## The app struct The application struct embeds [`BaseApp`](/sdk/latest/learn/concepts/baseapp) and holds all keepers and the `ModuleManager`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ExampleApp struct { *baseapp.BaseApp appCodec codec.Codec interfaceRegistry codectypes.InterfaceRegistry keys map[string]*storetypes.KVStoreKey // representative keepers AccountKeeper authkeeper.AccountKeeper BankKeeper bankkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper CounterKeeper *counterkeeper.Keeper // application wiring helpers ModuleManager *module.Manager BasicModuleManager module.BasicManager configurator module.Configurator } ``` Embedding `*baseapp.BaseApp` gives `ExampleApp` the full `BaseApp` interface: ABCI methods, message and query routers, store management, and lifecycle hooks. The keeper fields are exported so test code and CLI helpers can reference them. The `keys` map holds the KV store keys allocated during initialization. The real example app includes additional keepers and helper fields; this excerpt shows the part of the struct that matters for understanding the wiring pattern. ## Creating `BaseApp` `NewExampleApp` begins by setting up codecs and creating the `BaseApp` instance: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} appCodec := codec.NewProtoCodec(interfaceRegistry) txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) bApp.SetVersion(version.Version) bApp.SetInterfaceRegistry(interfaceRegistry) bApp.SetTxEncoder(txConfig.TxEncoder()) ``` `baseapp.NewBaseApp` creates the `BaseApp` with a name, logger, database, and `TxDecoder`. The `TxDecoder` is how `BaseApp` turns raw transaction bytes from CometBFT into an `sdk.Tx` it can inspect and route. Additional functional options (`baseAppOptions`) let callers configure pruning, minimum gas prices, chain ID, and optimistic execution without modifying `NewExampleApp` directly. The full example app also wires legacy Amino support, tracing, and interface registration around this excerpt. See [`BaseApp` Overview](/sdk/latest/learn/concepts/baseapp) for a fuller description of its fields and behavior. ## Allocating store keys Each module that persists state needs a dedicated KV store key. All keys are allocated together before any keeper is created: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keys := storetypes.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, consensusparamtypes.StoreKey, countertypes.StoreKey, ) ``` Each module defines its store key name as a string constant in `types/keys.go` (for example, `countertypes.StoreKey = "counter"` — see [Step 4: Types](/sdk/latest/tutorials/example/03-build-a-module#step-4-types) in the Build a Module tutorial). `NewKVStoreKeys` takes those names and allocates a `*storetypes.KVStoreKey` for each one. Keys are passed to keeper constructors and later mounted on the `CommitMultiStore` via `MountKVStores`. No two modules share a key; that isolation is what keeps module state separate. ## Initializing keepers Each keeper is initialized with its store key, codec, and any dependencies on other keepers. `ConsensusParamsKeeper` is initialized first because it must call `bApp.SetParamStore` before any other keeper is created: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(...) bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) app.AccountKeeper = authkeeper.NewAccountKeeper(...) app.BankKeeper = bankkeeper.NewBaseKeeper(..., app.AccountKeeper, ...) app.CounterKeeper = counterkeeper.NewKeeper( runtime.NewKVStoreService(keys[countertypes.StoreKey]), appCodec, app.BankKeeper, ) ``` `runtime.NewKVStoreService(key)` wraps the raw store key in a service interface that keepers use to open their store from a context. This keeps keepers from holding direct references to the underlying store. Instead, they retrieve it at runtime from the context passed into each method. The keeper initialization order matters: `BankKeeper` receives `app.AccountKeeper` as an argument, so `AccountKeeper` must be initialized first. The same dependency ordering applies throughout. The counter module example also passes `app.BankKeeper` into `counterkeeper.NewKeeper`, showing how custom modules depend on existing module services (see [Expected keepers and fee collection](/sdk/latest/tutorials/example/04-counter-walkthrough#expected-keepers-and-fee-collection) in the Full Counter Module Walkthrough). Where modules are interdependent, hooks connect them after both keepers exist: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.StakingKeeper.SetHooks( stakingtypes.NewMultiStakingHooks( app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks(), ), ) ``` The authority address passed to most keepers (`authtypes.NewModuleAddress(govtypes.ModuleName).String()`) is the address that is allowed to call privileged messages such as `MsgUpdateParams`. Governance controls parameter changes by sending messages from the governance module account. See [Params](/sdk/latest/learn/concepts/modules#params) for how this pattern works. ## Registering modules After all keepers are initialized, the module manager is created with every module the application uses: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ModuleManager = module.NewManager( auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, nil), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), counter.NewAppModule(appCodec, app.CounterKeeper), // ...other modules... ) ``` `module.NewManager` takes a list of `AppModule` implementations. Each `AppModule` wraps a keeper and satisfies the interfaces the `ModuleManager` uses: genesis, block hooks, message and query service registration, and simulation support. The real example app includes the full built-in module set around `x/counter` — see [Step 10: Wire into app.go](/sdk/latest/tutorials/example/03-build-a-module#step-10-wire-into-appgo) for a walkthrough of module registration; this excerpt shows the basic registration pattern. The `BasicModuleManager` is then derived from the `ModuleManager` for codec registration and default genesis handling. ## Module Manager The `ModuleManager` is the application's registry of modules. It holds references to all `AppModule` instances and coordinates their participation in the block lifecycle. When `BaseApp` fires a lifecycle hook (`PreBlock`, `BeginBlock`, `EndBlock`, `InitGenesis`), it delegates to the `ModuleManager`, which calls each module's corresponding method in the configured order. The `ModuleManager` is also responsible for service registration: it iterates all modules and calls each module's `RegisterServices` to register `MsgServer` and `QueryServer` implementations with `BaseApp`'s routers. For the execution-model view, see [Module Manager in `BaseApp`](/sdk/latest/learn/concepts/baseapp#module-manager). ## Execution ordering The order in which modules run their block hooks and genesis initialization matters. Some modules depend on others having already updated state. Ordering is configured explicitly after the module manager is created: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ModuleManager.SetOrderPreBlockers( authtypes.ModuleName, ) app.ModuleManager.SetOrderBeginBlockers( distrtypes.ModuleName, slashingtypes.ModuleName, stakingtypes.ModuleName, countertypes.ModuleName, genutiltypes.ModuleName, ) app.ModuleManager.SetOrderEndBlockers( banktypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, countertypes.ModuleName, genutiltypes.ModuleName, ) ``` Genesis initialization order is separate and equally important: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} genesisModuleOrder := []string{ authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, slashingtypes.ModuleName, govtypes.ModuleName, consensusparamtypes.ModuleName, vestingtypes.ModuleName, countertypes.ModuleName, genutiltypes.ModuleName, } app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) app.ModuleManager.SetOrderExportGenesis(exportModuleOrder...) ``` `SetOrderExportGenesis` controls the order modules serialize their state when the chain is exported to a genesis file, for example during a hard fork or when creating a snapshot-based testnet. The export order can differ from the init genesis order; in the example chain they use different orderings. The comments in the example app explain the reasoning: `genutil` must run after `staking` so that staking pools are initialized before genesis transactions are processed, and after `auth` so that it can access auth parameters. Each hook type has its own ordering constraint: * [`PreBlock`](/sdk/latest/learn/concepts/lifecycle#preblock): runs before `BeginBlock`. Used for upgrades and consensus parameter changes that must take effect before the block begins. * [`BeginBlock`](/sdk/latest/learn/concepts/lifecycle#beginblock): runs at the start of each block. Used for per-block housekeeping such as minting inflation rewards and distributing staking rewards. * [`EndBlock`](/sdk/latest/learn/concepts/lifecycle#endblock): runs after all transactions in the block. Used for logic that depends on cumulative block state, such as tallying governance votes or recalculating validator power. * [`InitGenesis`](/sdk/latest/learn/concepts/store#genesis-and-chain-initialization): runs once at chain start, populating each module's store from `genesis.json`. For a worked example of implementing these hooks in a custom module, see [BeginBlock and EndBlock](/sdk/latest/tutorials/example/04-counter-walkthrough#beginblock-and-endblock) in the Full Counter Module Walkthrough. ## Routing setup After execution ordering is configured, module services are registered with `BaseApp`'s routers: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) err := app.ModuleManager.RegisterServices(app.configurator) ``` `RegisterServices` iterates all modules and calls each module's `RegisterServices(cfg)` method. Each module uses the configurator to register its `MsgServer` with the message router and its `QueryServer` with the gRPC query router. After this step, `BaseApp` can route any registered message type to the correct module handler, and any registered query to the correct query handler. The example app also registers an AutoCLI query service after this step. The AutoCLI query service registration lets the CLI introspect module options without requiring per-module CLI command boilerplate. For how these services are exposed to clients, see [CLI, gRPC, and REST](/sdk/latest/learn/concepts/cli-grpc-rest). ## Block proposal and vote extension handlers `BaseApp` exposes four handlers for the ABCI 2.0 proposal phase: `SetPrepareProposal`, `SetProcessProposal`, `SetExtendVoteHandler`, and `SetVerifyVoteExtensionHandler`. All have sensible defaults. Chains that need custom behavior wire their handlers in `app.go` after the module manager is configured: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.SetPrepareProposal(myPrepareProposalHandler) app.SetProcessProposal(myProcessProposalHandler) ``` For a full explanation of what each handler does, see [Block proposal and vote extensions](/sdk/latest/learn/concepts/baseapp#block-proposal-and-vote-extensions). ## Mounting stores and setting hooks With routing configured, stores are mounted and the application's lifecycle hooks are set: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // initialize stores app.MountKVStores(keys) // initialize BaseApp app.SetInitChainer(app.InitChainer) app.SetPreBlocker(app.PreBlocker()) app.SetBeginBlocker(app.BeginBlocker) app.SetEndBlocker(app.EndBlocker) app.setAnteHandler(txConfig) ``` `MountKVStores` registers each key with `BaseApp`'s `CommitMultiStore`. The hooks delegate to the `ModuleManager`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *ExampleApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { return app.ModuleManager.BeginBlock(ctx) } ``` `EndBlocker` delegates in the same way, and `InitChainer` delegates to `ModuleManager.InitGenesis` after decoding `genesis.json`. The `AnteHandler` is configured separately because it takes a `TxConfig` dependency: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *ExampleApp) setAnteHandler(txConfig client.TxConfig) { anteHandler, err := ante.NewAnteHandler( ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, SignModeHandler: txConfig.SignModeHandler(), SigGasConsumer: ante.DefaultSigVerificationGasConsumer, }, ) if err != nil { panic(err) } app.SetAnteHandler(anteHandler) } ``` The `AnteHandler` runs before any message in a transaction executes. It verifies signatures, validates and increments the account sequence number, deducts fees, and meters gas. If it fails, the transaction is rejected before any module logic runs. A `PostHandler` can also be registered with `app.SetPostHandler`. It runs after all messages in a transaction execute (regardless of whether they succeeded), in the same state branch, and is reverted if it fails. The SDK's default `PostHandler` chain is minimal. For a deeper look at how the `AnteHandler` fits into transaction execution, see [AnteHandler](/sdk/latest/learn/concepts/baseapp#antehandler). ## Sealing with LoadLatestVersion The final step in `NewExampleApp` is loading the latest committed state: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if loadLatest { if err := app.LoadLatestVersion(); err != nil { panic(fmt.Errorf("error loading last version: %w", err)) } } ``` `LoadLatestVersion` calls `storeLoader` to load the latest committed store state from the database, then calls `Init`, which validates that required components are configured, initializes the check state, and sets `BaseApp.sealed` to `true`. Any setter called after this point panics. This enforces that all wiring happens before the application starts serving requests. On first launch, CometBFT calls `InitChain` which triggers `InitChainer`, which calls `ModuleManager.InitGenesis` to populate each module's state from `genesis.json`. ## How everything fits together A Cosmos SDK chain is assembled into a single constructor function that returns a fully wired, ready-to-run chain. Each step builds on the previous: ``` NewBaseApp ↓ NewKVStoreKeys → one key per module ↓ NewKeeper(key, ...) → one keeper per module, dependencies wired explicitly ↓ NewManager(modules) → module manager holds all AppModule instances ↓ SetOrder*(...) → configure hook and genesis execution ordering ↓ RegisterServices(configurator) → wire MsgServer and QueryServer into BaseApp routers ↓ MountKVStores(keys) → attach module stores to CommitMultiStore ↓ SetInitChainer / SetPreBlocker / SetBeginBlocker / SetEndBlocker / SetAnteHandler ↓ LoadLatestVersion → seal BaseApp, ready to serve ``` At runtime, CometBFT drives the application through ABCI. Each ABCI call dispatches through `BaseApp`: * `InitChain` calls `InitChainer`, which runs `ModuleManager.InitGenesis`. * `FinalizeBlock` calls `PreBlocker`, `BeginBlocker`, each transaction's `AnteHandler` and message handlers, then `EndBlocker`. * `CheckTx` validates a transaction through the `AnteHandler` and writes to the internal `CheckTx` state if it passes. * `Commit` persists the finalized block state. Modules never call each other directly. They interact through keeper interfaces wired at initialization time, and they participate in the block lifecycle through hooks that the `ModuleManager` coordinates in a fixed, declared order. See [BaseApp Overview](/sdk/latest/learn/concepts/baseapp) for how ABCI calls flow through the application, and [Intro to Modules](/sdk/latest/learn/concepts/modules) for how individual modules are structured. The next section, [CLI, gRPC, and REST API](/sdk/latest/learn/concepts/cli-grpc-rest), explains how clients interact with the chain once that wiring is in place. # BaseApp Overview Source: https://docs.cosmos.network/sdk/latest/learn/concepts/baseapp `BaseApp` is the execution engine of every Cosmos SDK chain. It implements [ABCI (Application Blockchain Interface)](/sdk/latest/learn/intro/sdk-app-architecture#abci-application-blockchain-interface), the protocol CometBFT uses to communicate with the application, and translates those calls into module execution, transaction processing, and state transitions. Every Cosmos SDK chain embeds `BaseApp`. Your `app.go` creates a `BaseApp` instance, configures it with modules, keepers, and middleware, and the resulting struct is what CometBFT communicates with directly. `BaseApp` provides the base layer of execution infrastructure to your blockchain application. Without it, every chain would need to independently implement ABCI handling, signature verification, gas metering, message routing, block hook orchestration, and state commitment. ## Architectural position `BaseApp` sits between CometBFT and the modules: ``` CometBFT (consensus engine) ↓ ABCI (InitChain, CheckTx, FinalizeBlock, Commit, ...) BaseApp ↓ orchestrates block execution ModuleManager ↓ dispatches to individual modules Modules (x/auth, x/bank, x/staking, ...) ↓ read/write State (KVStores) ``` CometBFT drives the block lifecycle by calling ABCI methods on `BaseApp`. `BaseApp` handles each call, delegating to registered lifecycle hooks and routing messages to the appropriate module handlers. Modules contain the business logic, and KVStores hold the resulting state. ## Key fields [`BaseApp`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/baseapp.go#L63-L168) is defined in `baseapp/baseapp.go`. It holds references to everything needed to run a chain: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type BaseApp struct { logger log.Logger name string // application name from abci.BlockInfo db dbm.DB // common DB backend cms storetypes.CommitMultiStore // Main (uncached) state storeLoader StoreLoader // function to handle store loading grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx mempool mempool.Mempool anteHandler sdk.AnteHandler // ante handler for fee and auth postHandler sdk.PostHandler // post handler, optional // ... sealed bool // ... chainID string // ... } ``` For a complete list of fields, see the [`BaseApp` struct definition](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/baseapp.go#L63-L168). * `cms` (CommitMultiStore): the root state store. All module substores are mounted here, and all state reads and writes during block execution pass through it. * `storeLoader`: a function that opens and mounts the individual module stores at application startup. * `grpcQueryRouter`: routes incoming gRPC queries to the correct module's query handler. * `msgServiceRouter`: routes each message in a transaction to the correct module's `MsgServer` handler. * `txDecoder`: decodes raw transaction bytes from CometBFT into an `sdk.Tx`. * `anteHandler`: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. * `postHandler`: optional middleware that runs after message execution — used for tasks such as tipping or post-execution state adjustments. * `sealed`: set to `true` after `LoadLatestVersion` is called. Setter methods panic if called after sealing. ## Initialization and sealing `BaseApp` enforces a configuration lifecycle: setter methods must be called before `LoadLatestVersion` is invoked. When `LoadLatestVersion` runs, it validates required components, initializes the check state, and sets `sealed` to `true`. Any setter called after sealing panics. On first launch, CometBFT calls `InitChain`. It stores `ConsensusParams` from the genesis file — block gas limit, max block size, evidence rules — in the `ParamStore`, where they can later be adjusted via on-chain governance. It initializes all volatile states by branching the root store, sets the block gas meter to infinite so genesis transactions are not gas-constrained, and calls the application's `initChainer`, which runs each module's `InitGenesis` to populate initial state. How this shapes the structure of `app.go` is covered in the next section. ## Transaction decoding Transactions arrive from CometBFT as raw bytes. Before `BaseApp` can validate or execute them, it must decode them into the SDK's transaction type using the `TxDecoder`: ``` []byte tx ↓ TxDecoder ↓ sdk.Tx ``` This step happens before the transaction enters the execution pipeline. Without it, `BaseApp` cannot inspect messages, run the `AnteHandler`, or route execution to the correct module. ## Execution modes `BaseApp` does not execute everything against the same mutable state. It maintains branched, copy-on-write views of the committed root state for different execution contexts: * `CheckTx` (`ExecModeCheck`): validates a transaction before it enters the mempool, without committing state. * `FinalizeBlock` (`ExecModeFinalize`): executes transactions in a proposed block against a branched state that is committed at the end. * `PrepareProposal` (`ExecModePrepareProposal`): runs when the node is the block proposer, assembling a candidate block. Executes against a branched state that is never committed. * `ProcessProposal` (`ExecModeProcessProposal`): runs on every validator to validate an incoming proposal. Also executes against a branched state that is never committed. * `Simulate` (`ExecModeSimulate`): runs a transaction for gas estimation without committing state. This separation ensures that validation, proposal handling, and simulation cannot accidentally mutate committed application state. ## The transaction execution pipeline When `BaseApp` processes a transaction, it runs through a structured pipeline: ``` RunTx ├─ DecodeTx → raw bytes → sdk.Tx ├─ AnteHandler → signatures, sequence, fees, gas setup ├─ RunMsgs → route each message to the correct module handler └─ PostHandler → optional post-execution middleware ``` If the `AnteHandler` fails, message execution does not begin. If any message fails, message execution reverts atomically; all message writes commit or none do. ## `AnteHandler` The `AnteHandler` is middleware that runs before any message in a transaction executes. It verifies cryptographic signatures, validates and increments the account sequence number, deducts transaction fees, and sets up the gas meter for the transaction. For the application wiring side, including `SetAnteHandler`, `HandlerOptions`, and constructor ordering, see [Mounting stores and setting hooks in `app.go`](/sdk/latest/learn/concepts/app-go#mounting-stores-and-setting-hooks). If the `AnteHandler` fails, the transaction is rejected and its messages never execute. If the `AnteHandler` succeeds but a message later fails, the `AnteHandler`'s state writes, such as fee deduction and sequence increment for ordered transactions, are already flushed to `finalizeBlockState` and will be committed with the block. Fees are charged even for transactions whose messages fail. `BaseApp.runTx()` also handles Go panics that occur during execution — for example, when a keeper encounters an invalid state. By default, panics are caught and logged as errors. Applications can register custom panic recovery logic via `BaseApp.AddRunTxRecoveryHandler`, which adds a `RecoveryHandler` to the chain. See [ADR-022](/sdk/latest/reference/architecture/adr-022-custom-panic-handling) and [`baseapp/recovery.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/baseapp/recovery.go) for details. ## Message routing When a transaction contains messages, `BaseApp` routes each one to the appropriate module handler using the `MsgServiceRouter`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgServiceRouter struct { routes map[string]MsgServiceHandler // ... } ``` The routing process has three steps: 1. Registration: During app startup, each module calls `RegisterService`, which registers its message handlers keyed by message type URL (e.g., `/cosmos.bank.v1beta1.MsgSend`). 2. Lookup: At execution time, `Handler` looks up the registered handler for the incoming message's type URL. 3. Execution: The retrieved handler invokes the module's `MsgServer` implementation, which validates inputs, applies business rules, and updates state through the keeper. This routing is entirely type-URL-based. Modules do not need to know about each other at the routing level; `BaseApp` is the neutral coordinator. ## Queries For read-only access to application state, `BaseApp` uses the **`GRPCQueryRouter`** to route incoming gRPC queries to the correct module query service. Queries bypass the transaction execution pipeline and directly read committed state. They do not go through the `AnteHandler`, do not consume gas in the same way, and do not mutate state. ## Store management `BaseApp` owns the `CommitMultiStore` that holds all module state. At app startup, each module registers its store key, and `BaseApp` mounts the corresponding store: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.MountKVStores(keys) ``` Before executing each transaction, `BaseApp` creates a cached, copy-on-write view of the multistore. All writes during that transaction occur in the cache. If the transaction succeeds, the cache is flushed to the underlying store. If the transaction fails at any point, the cache is discarded and no state changes are applied. ## CheckTx and mempool validation Before a transaction reaches block execution, it goes through `CheckTx`. `BaseApp` runs the `AnteHandler` in `CheckTx` mode to validate signatures, check sequence numbers, and verify fees. Each validator also enforces a configurable `minGasPrices` floor, and transactions offering less than the minimum gas price are rejected here as a spam protection measure. Transactions that fail `CheckTx` are rejected and do not enter the mempool. `CheckTx` does not execute messages. It does run the `AnteHandler`, and if ante succeeds the resulting writes are persisted to `BaseApp`'s internal `CheckTx` state rather than to committed chain state. This is how the mempool tracks transaction validity before block execution. After each block commits, CometBFT triggers a recheck pass (`ReCheckTx`) that re-validates all pending mempool transactions against the new state, and any transactions that became invalid (for example, because their sequence number was consumed by a competing transaction) are evicted at this point. ## Coordinating block execution When CometBFT calls `FinalizeBlock`, `BaseApp` runs the full block execution pipeline in order: ``` FinalizeBlock ├─ PreBlock → module pre-block hooks ├─ BeginBlock → module begin-block hooks ├─ For each transaction: │ ├─ AnteHandler (signature verification, fee deduction, gas setup) │ ├─ Message routing and execution │ └─ Commit or revert (atomic per-transaction) └─ EndBlock → module end-block hooks → returns AppHash ``` [`PreBlock`](/sdk/latest/learn/concepts/lifecycle#preblock) runs before any block logic. It handles changes that must take effect before the block begins, such as activating a chain upgrade or modifying consensus parameters. [`BeginBlock`](/sdk/latest/learn/concepts/modules#beginblock) runs after [`PreBlock`](/sdk/latest/learn/concepts/lifecycle#preblock) and handles per-block housekeeping: minting inflation rewards, distributing staking rewards, resetting per-block counters. [Transactions](/sdk/latest/learn/concepts/transactions) execute sequentially in block order. Message execution for each transaction is atomic: if any message fails, the message execution branch reverts. `AnteHandler` side effects may already have been applied. [`EndBlock`](/sdk/latest/learn/concepts/modules#endblock) runs after all transactions. It handles logic that depends on the block's cumulative state — for example, tallying governance votes after all vote messages have been processed, or updating validator power after all delegation changes. After `FinalizeBlock` completes, `BaseApp` computes and returns the app hash — the Merkle root of all committed state. See [App hash](/sdk/latest/learn/concepts/store#app-hash) for how it relates to the multistore and deterministic execution. When CometBFT subsequently calls `Commit`, `BaseApp` writes `finalizeBlockState` to the root store, resets `checkState` to the newly committed state, and clears `finalizeBlockState` to `nil` in preparation for the next block. ## Module Manager `BaseApp` exposes `PreBlock`, `BeginBlock`, and `EndBlock` as lifecycle hook points. Every standard SDK application wires these to a `ModuleManager`, which holds the full set of registered modules and their execution ordering. When a hook fires, `ModuleManager` iterates its ordered module list and calls each module's corresponding hook in sequence. Ordering matters: some modules depend on others having already updated state before they run. The `app.go` page shows how the application constructs the `ModuleManager`, wires it into `BaseApp`, and configures ordering in practice. See [Module Manager in `app.go`](/sdk/latest/learn/concepts/app-go#module-manager). ## Block proposal and vote extensions [ABCI 2.0](/sdk/latest/guides/abci/abci#abci-20) added a proposal phase that runs during consensus rounds, before `FinalizeBlock` executes. `BaseApp` exposes four handlers for this phase, with default implementations wired at construction: * `PrepareProposal`: called on the current block proposer to assemble a block from the mempool. The default selects transactions up to the block gas limit. Chains can override this to implement custom ordering, filtering, or injection of protocol-level transactions. * `ProcessProposal`: called on every validator to validate an incoming proposal. The default accepts any structurally valid proposal. Chains that use `PrepareProposal` to inject data typically also override this to verify that data is present and valid. * `ExtendVote` / `VerifyVoteExtension`: allow validators to attach arbitrary data to their precommit votes and verify other validators' extensions. One major use case is oracle price feeds: validators inject off-chain data into consensus so it becomes available on-chain at block start. All four are configurable in `app.go` via `SetPrepareProposal`, `SetProcessProposal`, `SetExtendVoteHandler`, and `SetVerifyVoteExtensionHandler`. Chains that do not need custom behavior can leave the defaults in place. ## Putting it all together `BaseApp` is the execution engine of a Cosmos SDK chain: ``` CometBFT → ABCI → BaseApp → Modules → State ``` It implements ABCI, coordinates the block lifecycle (`PreBlock` → `BeginBlock` → transactions → `EndBlock`), routes messages to module handlers via the `MsgServiceRouter`, routes queries via the `GRPCQueryRouter`, runs the `AnteHandler` before each transaction, and manages the multistore with copy-on-write caching for atomicity. State changes are committed at block end; validation and simulation run against branched state and never touch committed data. The next section, [app.go Overview](/sdk/latest/learn/concepts/app-go), explains how `BaseApp` is instantiated, configured, and wired with modules to produce a complete, running chain. # CLI, gRPC, and REST API Source: https://docs.cosmos.network/sdk/latest/learn/concepts/cli-grpc-rest A Cosmos SDK chain exposes three external interfaces for interacting with it: a command-line interface (CLI), a gRPC API, and a REST API. Each is a different surface over the same underlying chain logic. Users and developers can choose whichever interface suits their use case without affecting how the chain processes or validates transactions. ## How users interact with a chain Every operation a user performs falls into one of two categories: * **Transactions**: state-changing operations broadcast to the network and included in blocks (send tokens, delegate stake, vote on a proposal) * **Queries**: read-only requests that return data from the current chain state without going through consensus Both categories are accessible through the CLI, gRPC, and REST interfaces. The interfaces differ in how requests are constructed and transmitted, not in what they can do. None of these interfaces affect consensus. Transactions are validated and ordered by the consensus engine (CometBFT); the interfaces are simply delivery mechanisms that carry signed transactions to the network and return results. For the transaction and query model underneath these interfaces, see [Transactions, Messages, and Queries](/sdk/latest/learn/concepts/transactions). ## Interface comparison All endpoints default to `localhost` and must be configured to be accessible over the public internet. | Interface | Default port | Best for | Notes | | ---------------- | ------------ | ---------------------------------------------------------------- | --------------------------------------------------------- | | **CLI** | — | Development, testing, and node operations | Best for operator and developer workflows | | **gRPC** | 9090 | Wallets, backend services, and SDK clients | Not supported in browsers (requires HTTP/2) | | **REST** | 1317 | Web applications, scripts, and environments without gRPC support | Use when gRPC is unavailable; REST is disabled by default | | **CometBFT RPC** | 26657 | Consensus and blockchain data queries | Limited to consensus-layer data | ## CLI The CLI is the primary tool for developers and operators interacting with a chain from the terminal. Most Cosmos SDK chains ship a single binary that acts as both the server process and the CLI client. It is common to append a `d` suffix to the binary name to indicate that it is a daemon process, such as `exampled` or `simd`. For a hands-on walkthrough of running a local chain and using the CLI, see the [Running and Testing](/sdk/latest/tutorials/example/05-run-and-test) tutorial. When used as a client, the CLI constructs a transaction or query, signs it if required, and submits it through the node client interface. To learn how to run a local node and use the CLI, see [Run a Local Node](/sdk/latest/node/prerequisites). ### Using the CLI CLI commands are organized into two categories: * `query` commands retrieve information from chain state * `tx` commands construct and broadcast transactions Example commands: ``` exampled query counter count ``` ``` exampled tx counter add 10 \ --from mykey \ --chain-id example-1 \ --gas auto \ --gas-adjustment 1.3 \ --fees 1000stake ``` * `--from` specifies the signing key * `--gas auto` asks the CLI to estimate gas usage * `--gas-adjustment` applies a safety multiplier to the estimate * `--fees` specifies the transaction fee Gas limits the computational work a transaction can perform. The full gas model is explained in [Execution Context, Gas, and Events](/sdk/latest/learn/concepts/context-gas-events). For a full CLI reference for the example chain, see [CLI reference](/sdk/latest/tutorials/example/05-run-and-test#cli-reference) in the Running and Testing tutorial. ### How modules expose CLI commands with `AutoCLI` In modern Cosmos SDK applications, modules expose CLI commands through **`AutoCLI`**. `AutoCLI` reads a module's protobuf service definitions and generates CLI commands automatically, without requiring modules to hand-write Cobra command boilerplate. The counter snippets in this section are from the minimal counter module example. See the [Build a Module from Scratch](/sdk/latest/tutorials/example/03-build-a-module) tutorial. A module opts into `AutoCLI` by implementing `AutoCLIOptions()` on its `AppModule`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (a AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { return &autocliv1.ModuleOptions{ Query: &autocliv1.ServiceCommandDescriptor{ Service: "example.counter.Query", EnhanceCustomCommand: true, RpcCommandOptions: []*autocliv1.RpcCommandOptions{ { RpcMethod: "Count", Use: "count", Short: "Query the current counter value", }, }, }, Tx: &autocliv1.ServiceCommandDescriptor{ Service: "example.counter.Msg", EnhanceCustomCommand: true, RpcCommandOptions: []*autocliv1.RpcCommandOptions{ { RpcMethod: "Add", Use: "add [amount]", Short: "Add to the counter", PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "add"}}, }, }, }, } } ``` `AutoCLI` uses this configuration to generate the `exampled tx counter add` and `exampled query counter count` commands. The `Service` field names the protobuf service, and `RpcCommandOptions` maps individual RPC methods to CLI subcommands with positional arguments, flags, and help text. The `AutoCliOpts()` method on the application struct collects these options from all modules and passes them to the `AutoCLI` framework at startup: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *ExampleApp) AutoCliOpts() autocli.AppOptions { modules := make(map[string]appmodule.AppModule) for _, m := range app.ModuleManager.Modules { if moduleWithName, ok := m.(module.HasName); ok { moduleName := moduleWithName.Name() if appModule, ok := moduleWithName.(appmodule.AppModule); ok { modules[moduleName] = appModule } } } return autocli.AppOptions{ Modules: modules, ModuleOptions: runtimeservices.ExtractAutoCLIOptions(app.ModuleManager.Modules), AddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()), ValidatorAddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()), ConsensusAddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()), } } ``` This collects module options and address codecs and hands them to `AutoCLI`, which wires the generated commands into the root command. ## gRPC gRPC is the primary programmatic interface for interacting with a Cosmos chain. It uses Protocol Buffers to define strongly typed request and response structures and supports generated clients for many programming languages. Every query method and transaction message of the standard modules is documented in the [API reference](/sdk/latest/api-reference/index), with a field table and a runnable example for each. Each module exposes its functionality through two protobuf services: * A `Query` service for read-only access to module state * A `Msg` service for state-changing operations These services are defined in the module's `query.proto` and `tx.proto` files. The protobuf definitions for the Cosmos SDK are published at [buf.build/cosmos/cosmos-sdk](https://buf.build/cosmos/cosmos-sdk). ### How modules expose gRPC services Modules register their gRPC services during application startup via `RegisterServices`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) err := app.ModuleManager.RegisterServices(app.configurator) ``` Each module implements `RegisterServices` to connect service implementations to the application routers: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (am AppModule) RegisterServices(cfg module.Configurator) { types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServer(am.keeper)) } ``` `RegisterMsgServer` routes incoming `Msg` service calls to the module's `MsgServer` implementation. `RegisterQueryServer` routes incoming `Query` service calls to the module's `QueryServer` implementation. ### How to interact with gRPC Connect to the node's gRPC endpoint (default: `localhost:9090`) using a generated client: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} conn, _ := grpc.NewClient("localhost:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) queryClient := countertypes.NewQueryClient(conn) resp, _ := queryClient.Count(ctx, &countertypes.QueryCountRequest{}) ``` The gRPC server can be configured in `app.toml`: * `grpc.enable = true|false` — enables or disables the gRPC server (default: `true`) * `grpc.address = {string}` — the `ip:port` the server binds to (default: `localhost:9090`) * `grpc.max-recv-msg-size` — maximum message size in bytes the server can receive (default: 10MB) * `grpc.max-send-msg-size` — maximum message size in bytes the server can send (default: `math.MaxInt32`) For archive node setups, `grpc.historical-grpc-address-block-range` maps gRPC backend addresses to inclusive block height ranges, so historical queries are routed to the node holding that slice of chain history. The value is a JSON string, for example: `'{"archive-node-1:9090": [0, 1000000]}'`. Leave it empty (the default) to disable. For more usage examples, see [Interact with the Node](/sdk/latest/node/interact-node#using-grpc). ## REST via gRPC-gateway The Cosmos SDK also exposes a REST API. REST endpoints are not written by hand; they are generated automatically from the same protobuf definitions used by gRPC, using **gRPC-gateway**. The generated routes for the standard modules, with a request playground for each, are in the [REST reference](/sdk/latest/api-reference/index). gRPC-gateway reads HTTP annotations in the `.proto` files and generates a reverse proxy that translates REST requests into gRPC calls: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service Query { rpc Count(QueryCountRequest) returns (QueryCountResponse) { option (google.api.http) = { get: "/example/counter/v1/count" }; } } ``` This annotation causes gRPC-gateway to generate a `GET /example/counter/v1/count` HTTP endpoint. The gateway receives the HTTP request, marshals it into a `QueryCountRequest`, calls the gRPC `Count` handler, and returns the response as JSON. ### Registering REST routes REST routes are registered in `RegisterAPIRoutes`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *ExampleApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { clientCtx := apiSvr.ClientCtx // Register new tx routes from grpc-gateway. authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // Register new CometBFT queries routes from grpc-gateway. cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // Register node gRPC service for grpc-gateway. nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) // Register grpc-gateway routes for all modules. app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) } ``` The REST server can be configured in `app.toml`: * `api.enable = true|false` — enables or disables the REST server (default: `false`) * `api.address = {string}` — the `ip:port` the server binds to (default: `tcp://localhost:1317`) ### Swagger When the REST server and Swagger are both enabled, the node exposes a Swagger (OpenAPI v2) specification at `http://localhost:1317/swagger/`. Swagger lists all REST endpoints, request parameters, and response schemas, and provides a browser-based interface for exploring the REST API. Both are disabled by default. Enable them in `app.toml`: ``` api.enable = true api.swagger = true ``` To generate Swagger documentation for your own custom modules, see the [`proto-swagger-gen` script](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/scripts/protoc-swagger-gen.sh) in the Cosmos SDK. ## CometBFT RPC CometBFT also exposes its own RPC server, independent of the Cosmos SDK. It serves consensus and blockchain data and is configured under the `rpc` table in `config.toml` (default: `tcp://localhost:26657`). An OpenAPI specification of all CometBFT RPC endpoints is available in the [CometBFT documentation](/cometbft/latest/docs/core/RPC). Some CometBFT RPC endpoints are directly related to the Cosmos SDK: * `/abci_query` — queries the application for state. The `path` parameter accepts: * any protobuf fully-qualified service method, for example `/cosmos.bank.v1beta1.Query/AllBalances` * `/app/simulate` — simulate a transaction and return gas usage * `/app/version` — return the application version * `/store/{storeName}/key` — direct key lookup in a named store * `/store/{storeName}/subspace` — prefix scan in a named store * `/p2p/filter/addr/{addr}` and `/p2p/filter/id/{id}` — filter peers by address or node ID * `/broadcast_tx_sync`, `/broadcast_tx_async`, `/broadcast_tx_commit` — broadcast a signed transaction to peers. The CLI, gRPC, and REST interfaces all use these CometBFT RPCs under the hood. Two gRPC methods on the CometBFT service return ABCI block results: `GetBlockResults` (by height) and `GetLatestBlockResults`. These expose `finalize_block_events` and per-transaction results. ## End-to-end interaction flow To illustrate how these interfaces connect, here is the path of a `counter add` transaction from the user's terminal to a state change on the chain. This example follows the minimal counter module example's CLI shape. See the [Build a Module from Scratch](/sdk/latest/tutorials/example/03-build-a-module#step-9-autocli) tutorial. ``` User runs: exampled tx counter add 10 --from mykey --chain-id example-1 ↓ CLI (AutoCLI generated command) Constructs MsgAddRequest{Sender: mykey, Add: 10} Signs the transaction with mykey Encodes to protobuf bytes ↓ Broadcast through the node client interface ↓ Node: CheckTx AnteHandler verifies signature, deducts fee, meters gas Transaction enters the mempool ↓ CometBFT: block proposal and consensus ↓ FinalizeBlock: transaction executed AnteHandler runs again (finalizeBlock mode) MsgServiceRouter routes MsgAddRequest → counter module MsgServer MsgServer.Add calls keeper.AddCount Keeper reads current count, adds 10, writes new count ↓ Commit: state change persisted ↓ User receives TxResponse with code 0 ``` A query follows a shorter path that bypasses consensus entirely: ``` User runs: exampled query counter count ↓ CLI (AutoCLI generated command) Constructs QueryCountRequest{} Sends directly to node gRPC query endpoint ↓ GRPCQueryRouter routes to counter QueryServer QueryServer.Count calls keeper.GetCount Keeper reads current count from store ↓ QueryCountResponse{Count: 10} returned to user ``` Queries do not enter the mempool, are not included in blocks, and do not pass through the `AnteHandler`. They read committed state and return immediately. ## Interfaces and consensus The CLI, gRPC, and REST interfaces are transport layers. They construct, sign, and deliver messages, but they do not participate in consensus and cannot affect the determinism of block execution. * Transactions become part of consensus only after they pass `CheckTx` and are included in a proposed block. The interface used to submit the transaction has no bearing on how it is validated or ordered. * Queries bypass the transaction pipeline entirely. They read committed state from a node and never reach the consensus engine. * Any node in the network can serve queries or accept transaction submissions. The result is always the same committed state, regardless of which node or which interface is used. This separation means that changing the CLI or REST surface of a module (renaming a command, adding a new query) never requires a chain upgrade. Only changes to message types, keeper logic, or state schema affect consensus. The next section, [Testing in the SDK](/sdk/latest/learn/concepts/testing), shows how to test those behaviors once they are wired up. # Execution Context, Gas, and Events Source: https://docs.cosmos.network/sdk/latest/learn/concepts/context-gas-events In the previous section, [Encoding and Protobuf](/sdk/latest/learn/concepts/encoding) explained how data is serialized and why every validator must encode state identically. This page covers the runtime environment that modules execute within: the context object that carries block metadata and state access, the gas system that limits computation, and the event system that allows modules to emit observable signals. ## What is `sdk.Context` Every message handler, keeper method, and block hook in the Cosmos SDK receives an `sdk.Context`. It is the execution environment for a single unit of work (a transaction, a query, or a block hook) and carries everything that code needs to read state, emit events, and consume gas. Rather than passing the store, gas meter, and block header as separate arguments to every function, `Context` bundles them into a single value. The `Context` struct is defined in [`types/context.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/context.go): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Context struct { ms storetypes.MultiStore chainID string gasMeter storetypes.GasMeter blockGasMeter storetypes.GasMeter eventManager EventManagerI // ... additional fields } ``` Context is a value type. It is passed by value and mutated through `With*` methods that return a new copy. This means a module can safely derive a sub-context (for example, with a different gas meter) without affecting the caller's context. ### Block metadata Context exposes read-only access to the current block's metadata (see [`types/context.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/context.go)): * `ctx.BlockHeight()` returns the current block number. * `ctx.BlockTime()` returns the block's timestamp. * `ctx.ChainID()` returns the chain identifier string. * `ctx.Logger()` returns a structured logger scoped to the current execution context. Modules use this for operational logging (e.g., logging an upgrade activation or an unexpected state) without affecting consensus. These values are populated by [`BaseApp`](/sdk/latest/learn/concepts/baseapp) from the block header provided by CometBFT before any block logic runs. Modules read them to implement time-dependent logic (for example, checking whether a vesting period has elapsed) or to tag events with the block height. `ctx.IsCheckTx()` returns true when the context is being used for mempool validation rather than block execution. For finer-grained branching, `ctx.ExecMode()` returns the precise execution mode: `ExecModeCheck`, `ExecModeReCheck`, `ExecModeSimulate`, `ExecModePrepareProposal`, `ExecModeProcessProposal`, `ExecModeFinalize`, and others (see [`types/context.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/context.go#L21) for more details). Modules that need to behave differently during simulation or proposal handling use `ExecMode()` instead of `IsCheckTx()`. ### Context and state access State is accessed through context. The context holds a reference to the multistore, and each keeper opens its own store through the context: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) GetCount(ctx context.Context) (uint64, error) { return k.counter.Get(ctx) } ``` The keeper does not hold a direct reference to the live multistore; it opens its module's store from the context on each call. This is why context must be passed to every keeper method: it is the gateway to the current block's state, the gas meter, and the event manager for that execution unit. ### Atomic sub-execution with `CacheContext` Modules that need to attempt a sub-operation and revert it on failure can call [`ctx.CacheContext()`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/context.go#L412), which returns a branched copy of the context and a `writeCache` function. All state changes in the sub-operation go into the branch. Calling `writeCache()` flushes them to the parent context; not calling it discards them atomically. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cacheCtx, writeCache := ctx.CacheContext() if err := doRiskyOperation(cacheCtx); err != nil { return err // branch is discarded, no state changes applied } writeCache() // flush branch to parent context ``` ## Gas metering ### What gas measures Gas is a unit of computation. In the Cosmos SDK, gas accounts for both computation and state access. Every store read, store write, and iterator step costs gas. Complex computations such as signature verification in the `AnteHandler` also cost gas. The gas system exists to prevent abuse. Without a gas limit, a single transaction could exhaust a node's resources with an unbounded computation or an unindexed state scan. ### Gas limit and the transaction gas meter Every transaction specifies a gas limit in its `auth_info.fee.gas_limit` field. When `BaseApp` begins executing a transaction, it creates a `GasMeter` initialized with that limit and attaches it to the context. The [`GasMeter`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/types/gas.go#L42) interface provides two key methods: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type GasMeter interface { GasConsumed() Gas ConsumeGas(amount Gas, descriptor string) // ... } ``` `GasConsumed` returns the total gas used so far in the current execution unit. `ConsumeGas` adds to the running total and panics with `ErrorOutOfGas` if consumption exceeds the limit. When submitting a transaction, users specify two of the three values `fees`, `gas`, and `gas-prices` — the third is derived from the equation `fees = gas * gas-prices`. The `gas` value becomes `GasWanted`: the maximum gas the transaction is allowed to consume. The actual gas consumed during execution is `GasUsed`. Both `GasWanted` and `GasUsed` are returned to CometBFT when `FinalizeBlock` completes. ### How gas is consumed Gas is consumed automatically at the store layer. Every read and write through the [`GasKVStore`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/gaskv/store.go#L12) wrapper charges gas before delegating to the underlying store: * A `Get` (store read) charges a flat read cost plus a per-byte cost for the key and value. * A `Set` (store write) charges a flat write cost plus a per-byte cost for the key and value. Modules do not need to manually track gas for ordinary state access — the store layer handles it automatically. Modules call `ctx.GasMeter().ConsumeGas(...)` directly only for computation costs that are not captured by store operations (for example, a module that performs a cryptographic operation outside the store). ### When gas runs out If gas is exhausted during execution, `ConsumeGas` panics with `ErrorOutOfGas`. `BaseApp` recovers from this panic, discards the current message execution branch, and returns an error to the user. Fees may still be charged for the gas consumed up to the point of failure, and `AnteHandler` side effects may already have been applied before message execution started. ### Block gas limit In addition to the per-transaction gas meter, there is a block-level gas meter that tracks total gas consumed by all transactions in a block. The block gas limit prevents a single block from consuming unbounded computation. If a transaction would cause the block's gas total to exceed the limit, it is excluded from the block. The block gas limit and minimum gas prices are configured in [`app.toml`](/sdk/latest/tutorials/example/05-run-and-test#apptoml). ## Events ### What events are Events are observable signals emitted during transaction and block execution. A module emits events to describe what happened: tokens were transferred, a validator was slashed, a governance proposal passed. Events carry structured key-value data alongside a type string. Events are not part of consensus state. They are not stored in the KVStore, do not affect the app hash, and are not required for deterministic execution. Instead, they are collected by `BaseApp` and included in the block result, where indexers, explorers, and relayers consume them. ### EventManager Modules emit events through the [`EventManager`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/events.go#L25), which is attached to the context. The `EventManager` is created fresh for each transaction and collects all events emitted during that execution. ### Standard event types The SDK automatically emits a `message` event for every transaction, with these attributes set by `BaseApp`: * `message.action` — the full type URL of the message (e.g., `/cosmos.bank.v1beta1.Msg/Send`) * `message.module` — the module name, derived from the type URL * `message.sender` — the signer address, if present These are defined as constants in [`types/events.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/events.go#L249-L263). Modules follow the same convention when emitting their own events. ### Emitting events Modules emit events using [`EmitEvent`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/events.go#L35) or [`EmitTypedEvent`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/types/events.go#L58): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // emit an untyped event ctx.EventManager().EmitEvent(sdk.NewEvent( "increment", sdk.NewAttribute("new_count", strconv.FormatUint(newCount, 10)), )) ``` `EmitEvent` appends a raw key-value event to the manager's accumulated list. For events backed by protobuf message types, `EmitTypedEvent` serializes the message's fields into event attributes automatically: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ctx.EventManager().EmitTypedEvent(&types.EventCounterIncremented{ NewCount: newCount, }) ``` Using `EmitTypedEvent` is the modern approach. It provides type safety and makes the event schema explicit through proto definitions, allowing clients to deserialize events back into typed structs. ### Block events and transaction events Events emitted during `BeginBlock` or `EndBlock` hooks are **block events**: they describe things that happened at the block level (inflation minted, validator updates applied). Events emitted inside a message handler are **transaction events**: they describe what a specific transaction did. Both types are included in the `FinalizeBlock` response that CometBFT returns to the network, but they are reported separately so clients can distinguish block-level activity from per-transaction activity. ### Who consumes events Events are consumed outside the node: * **Block explorers** index events to show users what happened in a transaction (which tokens moved, which validator was slashed, which proposal passed). * **Relayers** (IBC) subscribe to specific event types to detect packet sends and acknowledgments. * **Indexers and off-chain services** build queryable databases of chain activity from event streams. Events can also be queried via the node's REST API and WebSocket endpoint. * **Wallets and UIs** display event data to users as transaction receipts. Events are included in the block result that CometBFT returns after each block. They are not replayed or reprocessed; once a block is finalized, its events are fixed. ### Querying events Events are indexed using the format `{type}.{key}={value}` and can be filtered when querying transactions. String values must be wrapped in single quotes. | Filter | Description | | ------------------------------------------------ | ------------------------------------------- | | `tx.height=23` | All transactions at block height 23 | | `message.action='/cosmos.bank.v1beta1.Msg/Send'` | Transactions containing a bank Send message | | `message.module='bank'` | Transactions from the x/bank module | ## Putting it together During transaction execution, context, gas, and events work together as the runtime layer: ``` BaseApp creates Context for the transaction ↓ AnteHandler runs → signature verification, fee deduction, gas meter initialized ↓ Message handler runs → each store read/write consumes gas via GasKVStore → module logic emits events via EventManager ↓ If gas exhausted → panic → state reverted, fees charged for gas consumed If execution succeeds → state changes committed, events returned in block result ``` The context carries the gas meter and event manager into every keeper call. Gas is consumed transparently at the store layer. Events accumulate and are returned as part of the block result once execution completes. The next section, [Intro to SDK Structure](/sdk/latest/learn/concepts/sdk-structure), explains how an SDK application is structured as a codebase: where modules live, what goes in `app/`, and how all the pieces are assembled. # Protobuf and Signing Source: https://docs.cosmos.network/sdk/latest/learn/concepts/encoding As described in [State, Storage, and Genesis](/sdk/latest/learn/concepts/store), modules write structured state values into the KV store as raw bytes. Encoding defines how those structured values are serialized into bytes, and why every validator must produce exactly the same bytes. This page explains how that encoding works, why the Cosmos SDK chose Protocol Buffers, and what that means for module development. ## What is Protobuf? [Protocol Buffers](https://protobuf.dev/) (protobuf) is a language-neutral, binary serialization format developed by Google. You define your data structures in `.proto` files using a schema language, then generate code in your target language from that schema. The generated code handles serialization (converting structured data into bytes) and deserialization (converting bytes back into structured data). A simple protobuf message looks like this: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgSend { string from_address = 1; string to_address = 2; repeated Coin amount = 3; } ``` Each field has a name, a type, and a field number. The field numbers are what protobuf actually uses during encoding; field names are only present in the schema, not in the serialized bytes. ## Why the Cosmos SDK uses protobuf The Cosmos SDK uses protobuf for a fundamental reason: consensus requires determinism. Every validator in the network independently executes each block. After execution, each validator computes the [app hash](/sdk/latest/learn/concepts/store#app-hash), a cryptographic hash of the application state. For validators to agree on the app hash, they must all produce exactly the same bytes for every piece of state they write. Protobuf alone does not guarantee this. The Cosmos SDK uses protobuf **with additional deterministic encoding rules** formalized in [ADR-027 (Deterministic Protobuf Serialization)](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-027-deterministic-protobuf-serialization.md). ADR-027 specifies constraints such as requiring fields to appear in ascending field-number order and varint encodings to be as short as possible. The SDK validates incoming transactions against these rules before processing them, so a non-deterministically encoded transaction is rejected rather than producing divergent state. Every validator encoding the same data under these rules produces an identical byte sequence. Beyond determinism, protobuf provides: * **Compact encoding**: binary wire format is smaller than JSON or XML, which matters for transaction throughput and block size. * **Schema evolution**: fields can be added or deprecated without breaking existing clients, which is critical for chain upgrades. * **Code generation**: `.proto` files generate Go structs, gRPC service stubs, and REST gateway handlers automatically. * **Cross-language support**: clients in any language can interact with the chain by generating code from the same `.proto` files. ## Binary and JSON encoding The Cosmos SDK uses protobuf in two encoding modes: **Binary encoding** is the default for everything that participates in consensus: transactions written to blocks, state stored in KV stores, and genesis data. Binary encoding is compact and deterministic. When a transaction is broadcast to the network, it travels as protobuf binary. When a module writes state, it serializes values to protobuf binary before calling `Set` on the store. **JSON encoding** is used for human-readable output: the [CLI, gRPC-gateway REST endpoints](/sdk/latest/learn/concepts/cli-grpc-rest), and off-chain tooling. The Cosmos SDK uses protobuf's JSON encoding (`ProtoMarshalJSON`) rather than standard Go JSON, which preserves field names from the `.proto` schema and handles special types like `Any` correctly. For the concrete forms these produce on the wire, including the conventions the SDK layers on `string` and `bytes`, see [gRPC services](/sdk/latest/api-reference/grpc/index). It is important to keep in mind that **binary encoding is consensus-critical**. Two validators must produce identical binary bytes for identical data. JSON is only used where humans or external clients need to read the data; it never influences the AppHash. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Consensus-critical path Human-readable path ───────────────────────── ───────────────────────── Transaction bytes (binary) CLI output (JSON) State KV values (binary) REST API responses (JSON) Genesis KV state (binary) Block explorers (JSON) ``` Note: genesis data is distributed as JSON in `genesis.json`, but during chain initialization `InitGenesis` deserializes that JSON into protobuf structs and writes them to the KV store as binary. The KV store (and therefore the AppHash) only ever contains the binary form. ## Transaction encoding Transactions are protobuf messages defined in [`cosmos.tx.v1beta1`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/tx/v1beta1/tx.proto). A transaction is composed of three parts: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Tx ├─ TxBody │ └─ repeated google.protobuf.Any messages ├─ AuthInfo │ ├─ repeated SignerInfo (each with sequence) │ └─ Fee └─ repeated bytes signatures ``` * **TxBody** contains the messages to execute, serialized as `repeated google.protobuf.Any messages`. * **AuthInfo** contains signer information (including the per-signer sequence number) and fee. * **signatures** contains the cryptographic signatures, one per signer. Messages inside the transaction are stored as `google.protobuf.Any` values so that a single transaction can contain multiple message types from different modules. When a user submits a transaction, the SDK encodes it as a `TxRaw`—a flat structure with the `TxBody` bytes, `AuthInfo` bytes, and signatures already serialized. It then broadcasts that binary representation over the network. ## Transaction signing and `SignDoc` Transactions are not signed directly. Instead, the SDK constructs a deterministic structure called a **`SignDoc`**, which defines exactly what bytes the signer commits to: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} SignDoc ├─ body_bytes (serialized TxBody) ├─ auth_info_bytes (serialized AuthInfo, includes sequence per signer) ├─ chain_id (prevents cross-chain replay) └─ account_number (ties the signature to a specific on-chain account) ``` The `SignDoc` is serialized to protobuf binary and then signed with the user's private key: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} signature = Sign(proto.Marshal(SignDoc)) ``` Because `SignDoc` is serialized deterministically, all validators verify the exact same bytes when checking transaction signatures. The per-signer sequence number lives in `AuthInfo.SignerInfo.sequence` and is included in `auth_info_bytes`, which is part of `SignDoc`—this is what prevents replay attacks. ## Sign modes A **sign mode** determines what bytes a signer commits to when signing a transaction. The SDK supports multiple sign modes to accommodate different clients and hardware: * `SIGN_MODE_DIRECT` (default): the signer signs over the protobuf-binary-serialized `SignDoc` described above. This is compact, deterministic, and the correct choice for all new development. * `SIGN_MODE_LEGACY_AMINO_JSON`: the signer signs over an Amino JSON-encoded `StdSignDoc` instead of the protobuf `SignDoc`. This exists for backward compatibility with hardware wallets (e.g., older Ledger firmware) and client tooling that predates protobuf. New modules and chains should not depend on it. * `SIGN_MODE_DIRECT_AUX`: allows N-1 signers in a multi-signer transaction to sign over only `TxBody` and their own `SignerInfo`, without specifying fees. The designated fee payer signs last using `SIGN_MODE_DIRECT`. This simplifies multi-signature UX. The sign mode is negotiated at transaction construction time and does not affect how state is stored or how validators execute transactions. It only affects what bytes are signed. The full list of sign modes is defined in [`signing.proto`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/tx/signing/v1beta1/signing.proto#L17). **For module developers:** `SIGN_MODE_DIRECT` requires no extra work. If you want your module's messages to be signable on Ledger hardware wallets using `SIGN_MODE_LEGACY_AMINO_JSON`, register your message types with the Amino codec via `RegisterLegacyAminoCodec` in your module's `codec.go`. ## Message signers Every transaction message must declare which addresses are authorized to sign it. In v0.50+, this is done via the `cosmos.msg.v1.signer` protobuf annotation — the SDK reads the annotation at startup and automatically extracts signer addresses from that field. See [Protobuf Annotations](/sdk/latest/guides/reference/protobuf-annotations) for the full annotation reference. For messages that cannot use the annotation — for example, messages with non-standard signing logic such as EVM-compatible transactions — you can register a custom signer function using [`signing.CustomGetSigner`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/tx/signing/context.go#L127): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} signer := signing.CustomGetSigner{ MsgType: proto.MessageName(&MyMsg{}), Fn: func(msg proto.Message) ([][]byte, error) { m := msg.(*MyMsg) // extract and return signer address bytes return [][]byte{m.SignerBytes()}, nil }, } ``` To register it, call `signingOptions.DefineCustomGetSigners(msgType, fn)` on the `txsigning.Options` you pass to `authtx.NewTxConfigWithOptions` when building your app's `TxConfig`. ## How protobuf is used in modules Most public and persisted data types in modern SDK modules are defined in `.proto` files and serialized with protobuf. This covers the core API surface: transaction messages, query request/response types, stored state values, and genesis state. ### Messages and transactions Each module defines its transaction messages in a `tx.proto` file. The `MsgSend` definition above is an example. When a user submits a transaction, the SDK serializes the transaction body (including its messages) to binary using protobuf before broadcasting it. For a hands-on example, see [tx.proto](/sdk/latest/tutorials/example/03-build-a-module#txproto) in the Build a Module tutorial. ### Queries Modules define their query services in `query.proto`. Request and response types are protobuf messages. The SDK uses gRPC for queries, and gRPC uses protobuf as its serialization format by definition. For a hands-on example, see [query.proto](/sdk/latest/tutorials/example/03-build-a-module#queryproto) in the Build a Module tutorial. ### State types Data stored in the KV store is protobuf-encoded. A module that stores a custom struct first marshals it to bytes using the codec, then writes those bytes to the store. When reading, it unmarshals the bytes back into the struct. Note that only *values* are protobuf-encoded; *keys* are manually constructed byte sequences, not protobuf. Key layout is covered in the [State, Storage, and Genesis](/sdk/latest/learn/concepts/store) section. ### Genesis Genesis state is defined in `genesis.proto`. `InitGenesis` and `ExportGenesis` use protobuf to deserialize genesis state from `genesis.json` and serialize it back. A concrete example shows how a module reads and writes typed state as bytes: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // write: marshal the coin amount to bytes, then set in store bz, err := k.cdc.Marshal(&amount) store.Set(key, bz) // read: get bytes from store, unmarshal back to coin var amount sdk.Coin bz := store.Get(key) k.cdc.Unmarshal(bz, &amount) ``` The codec (`k.cdc`) is the protobuf codec described in the next section. ## The codec and interface registry The Cosmos SDK wraps protobuf in a **codec** that modules use for marshaling and unmarshaling. The primary implementation is [`ProtoCodec`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/codec/proto_codec.go), which calls protobuf's `Marshal` and `Unmarshal` under the hood. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ProtoCodec struct { interfaceRegistry types.InterfaceRegistry } func (pc *ProtoCodec) Marshal(o ProtoMarshaler) ([]byte, error) func (pc *ProtoCodec) Unmarshal(bz []byte, ptr ProtoMarshaler) error ``` Keepers hold a reference to the codec and use it to encode and decode state: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { cdc codec.BinaryCodec store storetypes.StoreKey } ``` The codec is initialized once at app startup and passed to each keeper during initialization. ### Interface types and `Any` Protobuf is strongly typed. You cannot store a field as "some implementation of an interface" directly in a protobuf message. The Cosmos SDK solves this using protobuf's [`google.protobuf.Any`](https://protobuf.dev/programming-guides/proto3/#any), which wraps an arbitrary message type alongside a URL that identifies what type it contains. `Any` is used anywhere the SDK needs to serialize a value whose concrete type is not known at compile time. The most common example is public keys. An account might use a secp256k1 key, an ed25519 key, or a multisig key. The `BaseAccount` stores the public key as `Any`: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message BaseAccount { string address = 1; google.protobuf.Any pub_key = 2; uint64 account_number = 3; uint64 sequence = 4; } ``` The `Any` field holds the serialized public key bytes plus a type URL like `/cosmos.crypto.secp256k1.PubKey`. When the SDK reads the account, it uses the type URL to look up the concrete Go type, then unmarshals the bytes into that type. #### Messages inside transactions Transaction messages are the most common use of `Any` in the SDK. A transaction can carry multiple message types from different modules (`bank.MsgSend`, `staking.MsgDelegate`, `gov.MsgVote`) in a single `TxBody`. Because protobuf requires concrete types at the field level, each message is packed into an `Any` before being placed inside the transaction: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} MsgSend ↓ pack into Any Any { type_url: "/cosmos.bank.v1beta1.MsgSend" value: } ↓ placed in TxBody.messages repeated google.protobuf.Any messages ``` During decoding, the SDK reads the `type_url`, looks up the concrete type in the interface registry, and unmarshals the bytes into the correct message struct. This is why every `sdk.Msg` implementation must be registered with `RegisterInterfaces` before the application starts. The Cosmos SDK uses type URLs with a leading `/` but without the `type.googleapis.com` prefix (e.g. `/cosmos.bank.v1beta1.MsgSend`, not `type.googleapis.com/cosmos.bank.v1beta1.MsgSend`). If you need to pack a value into an `Any` manually, use `anyutil.New` from `github.com/cosmos/cosmos-proto/anyutil` rather than `anypb.New` from `google.golang.org/protobuf/types/known/anypb` — the standard library helper inserts the `type.googleapis.com` prefix, which breaks SDK type resolution. This lookup is handled by the **interface registry**. ### Interface registry The [`InterfaceRegistry`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/codec/types/interface_registry.go) is a runtime map from type URLs to Go types. When the SDK encounters an `Any` value, it queries the registry with the type URL to find the concrete Go type, then uses protobuf to unmarshal the bytes. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Any { type_url, value_bytes } ↓ InterfaceRegistry.Resolve(type_url) ↓ concrete Go type ↓ proto.Unmarshal(value_bytes, concreteType) ``` Without the interface registry, the SDK cannot decode `Any` values. This is why types must be explicitly registered before they can be deserialized. ## Registering interface implementations Because the interface registry is a runtime lookup table, every concrete type that implements an SDK interface must be registered before the application starts. This is done with `RegisterInterfaces`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // in codec registration, typically in module.go or types/codec.go func RegisterInterfaces(registry codectypes.InterfaceRegistry) { registry.RegisterImplementations( (*cryptotypes.PubKey)(nil), &secp256k1.PubKey{}, &ed25519.PubKey{}, ) } ``` This tells the registry: "a `PubKey` interface can be a `secp256k1.PubKey` or an `ed25519.PubKey`." If a type is used in an `Any` field anywhere in the application and is not registered, the codec will fail to unmarshal it and return an error. Each module calls `RegisterInterfaces` during app initialization, and `app.go` calls these registration functions through the module manager when building the app. Custom types that implement SDK interfaces must follow the same pattern. ### `codec.go` By convention, modules collect all codec registration in a single file: `x/mymodule/types/codec.go`. This file typically contains two functions: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // RegisterInterfaces registers protobuf interface implementations with the registry. // Called during app initialization so the SDK can decode Any values at runtime. func RegisterInterfaces(registry codectypes.InterfaceRegistry) { registry.RegisterImplementations((*sdk.Msg)(nil), &MsgAdd{}, &MsgUpdateParams{}, ) } // RegisterLegacyAminoCodec registers message types for Amino JSON encoding. // Required only if you want messages signable via SIGN_MODE_LEGACY_AMINO_JSON // (e.g., Ledger hardware wallets using older firmware). func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgAdd{}, "mymodule/Add", nil) } ``` `RegisterInterfaces` is required for every module that defines message types. Without it, the SDK cannot decode those messages from transactions. `RegisterLegacyAminoCodec` is optional and only needed for Ledger hardware wallet support via `SIGN_MODE_LEGACY_AMINO_JSON`. For an example of interface registration in a working module, see [Interface Registration](/sdk/latest/tutorials/example/03-build-a-module#interface-registration) in the Build a Module tutorial. ## Proto-to-code generation workflow Writing `.proto` files produces `.pb.go` files through a code generation step. The generated Go code contains struct definitions, marshal/unmarshal methods, and gRPC service stubs. You never edit these generated files directly. The workflow is: **1. Write the `.proto` file** Proto files for a module live in the `proto/` directory at the repository root: ``` proto/myapp/mymodule/v1/ ├── tx.proto # message types (MsgAdd, MsgAddResponse, ...) ├── query.proto # query service (QueryCount, ...) ├── state.proto # on-chain state types └── genesis.proto # genesis state ``` A message definition: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} syntax = "proto3"; package myapp.mymodule.v1; message MsgAdd { string sender = 1; uint64 add = 2; } message MsgAddResponse { uint64 updated_count = 1; } service Msg { rpc Add(MsgAdd) returns (MsgAddResponse); } ``` **2. Run code generation** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # example — the exact target varies by project make proto-gen ``` This runs [`buf`](https://buf.build/cosmos/cosmos-sdk/docs/main) (or `protoc` with plugins) against the `.proto` files and produces Go code under the module's `types/` directory. The full generated API reference for the Cosmos SDK is published at [buf.build/cosmos/cosmos-sdk/docs/main](https://buf.build/cosmos/cosmos-sdk/docs/main). ``` x/mymodule/types/ ├── tx.pb.go # generated: MsgAdd, MsgAddResponse, Marshal/Unmarshal methods ├── query.pb.go # generated: query request/response types ├── query.pb.gw.go # generated: gRPC-gateway REST handlers └── state.pb.go # generated: on-chain state types ``` **3. Use the generated types** The generated structs implement `proto.Message` and can be passed directly to the codec for marshaling, registered with the interface registry, and used in keeper methods and message handlers: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // handler receives the generated type func (m msgServer) Add(ctx context.Context, req *types.MsgAdd) (*types.MsgAddResponse, error) { count, err := m.AddCount(ctx, req.Sender, req.Add) if err != nil { return nil, err } return &types.MsgAddResponse{UpdatedCount: count}, nil } ``` The generated gRPC service stub is registered with BaseApp's message router, connecting the handler to the transaction execution pipeline automatically. To learn how to build a module from scratch using this workflow, visit the [Module building tutorial](/sdk/latest/tutorials/example/00-overview). ## Legacy Amino encoding Before protobuf, the Cosmos SDK used a custom serialization format called **Amino** for transaction encoding, JSON signing documents, and interface serialization. Protobuf has replaced it in all of those roles. The `LegacyAmino` codec still exists for backward compatibility, but is not used in the consensus-critical path. Some legacy components still reference it: * `LegacyAmino` is still present in the codec package for backward-compatibility * `LegacyAminoPubKey` (multisig) is registered alongside protobuf public key types * Some older chains, hardware wallets, and client tooling depend on Amino JSON signing New modules and chains should use protobuf exclusively. ## Encoding in context Every layer of the Cosmos SDK depends on encoding: ``` Transaction (binary protobuf) ↓ broadcast over p2p CometBFT ↓ passes raw bytes to application BaseApp ↓ decodes transaction, extracts messages Module MsgServer ↓ processes message, calls keeper Keeper ↓ marshals state value to bytes KVStore (raw bytes) ↓ committed to disk AppHash (Merkle root over all KV bytes) ``` Determinism comes from the combination of canonical transaction encoding (ADR-027), deterministic application logic, and consistent protobuf serialization of stored state. Two validators executing the same transactions under these rules always produce the same bytes at every layer, and therefore always arrive at the same AppHash. The next section, [Execution Context, Gas, and Events](/sdk/latest/learn/concepts/context-gas-events), explains the runtime execution environment that modules operate within: `sdk.Context`, gas metering, and events. # Transaction Lifecycle Source: https://docs.cosmos.network/sdk/latest/learn/concepts/lifecycle In the [Transactions, Messages, and Queries](/sdk/latest/learn/concepts/transactions) page, you learned that transactions are the actual mechanism that authorizes and executes logic on the chain. This page explains how transactions are validated, executed, and committed in the Cosmos SDK. Before building with the Cosmos SDK, it's important to connect the high-level architecture from [SDK Application Architecture](/sdk/latest/learn/intro/sdk-app-architecture) with how blocks and transactions actually execute in code. The following components are essential for understanding the lifecycle of a transaction in the Cosmos SDK: * [CometBFT](/cometbft) (consensus engine) — orders and proposes blocks * [ABCI](/sdk/latest/learn/intro/sdk-app-architecture#abci-application-blockchain-interface) (Application-Blockchain Interface) — the protocol CometBFT uses to talk to the Cosmos SDK application * SDK application ([`BaseApp`](/sdk/latest/learn/concepts/baseapp) + [modules](/sdk/latest/learn/concepts/modules)) — the deterministic state machine that executes transactions * [Protobuf schemas](/sdk/latest/learn/concepts/encoding) — define transactions, messages, state, and query types This page maps the block and transaction lifecycle back to those components. ## ABCI overview CometBFT and the SDK application are two separate processes with distinct responsibilities. * [CometBFT](/cometbft/latest/docs/introduction/intro) handles consensus: ordering transactions, managing validators, and driving block production. * The [SDK application](/sdk/latest/learn/concepts/sdk-structure) handles state: executing transactions and updating the chain's data. The [ABCI](/sdk/latest/learn/intro/sdk-app-architecture#abci-application-blockchain-interface) (Application Blockchain Interface) is the protocol that connects them: CometBFT calls ABCI methods on the application to drive each phase of the block lifecycle, and the application responds. [`BaseApp`](/sdk/latest/learn/concepts/baseapp) is the SDK's implementation of the ABCI interface. It receives these calls from CometBFT and orchestrates execution across modules. Modules plug into `BaseApp` and execute their logic during the appropriate phases. ```python theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} +---------------------+ | +-------------------------+ | CometBFT | | | SDK Application | | (Consensus) | ABCI | (BaseApp + modules) | +---------------------+ | +-------------------------+ | InitChain (once) | Chain start -------------------|------> InitGenesis per module | CheckTx (per submitted tx) | Mempool validation ------------|------> decode · verify · validate |<------ accept → Mempool | PrepareProposal (proposer only) | Build block proposal ----------|------> select txs (MaxTxBytes, MaxGas) | ProcessProposal (all validators) | Evaluate proposal -------------|------> verify txs → ACCEPT / REJECT | FinalizeBlock (per block) | Execute block -----------------|------> PreBlock hooks | BeginBlock hooks | For each tx: | AnteHandler | → message routing | → MsgServer (module logic) | EndBlock hooks | Return AppHash Commit | Persist state -----------------|------> persist state to disk |<------ return AppHash ``` ## InitChain (genesis only) `InitChain` runs once when the chain starts for the first time. `BaseApp` loads `genesis.json`, which defines the chain's initial state, and calls each module's `InitGenesis` to populate its store. The initial validator set is established. Genesis runs before the first block begins. For how `genesis.json` becomes module state, see [Genesis and chain initialization](/sdk/latest/learn/concepts/store#genesis-and-chain-initialization). ## CheckTx and the mempool Before a transaction can enter a block, it goes through `CheckTx`: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} User ↓ Node ↓ ABCI: CheckTx ↓ Mempool ``` Transactions are sent as raw protobuf-encoded bytes. For how those bytes are encoded deterministically, see [Encoding and Protobuf](/sdk/latest/learn/concepts/encoding). During `CheckTx`, the SDK application's `BaseApp` decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. For the account sequence model, see [Accounts](/sdk/latest/learn/concepts/accounts). For gas metering and fee-related execution details, see [Execution Context, Gas, and Events](/sdk/latest/learn/concepts/context-gas-events). If validation fails, the transaction is rejected. If it passes, it enters the mempool. The mempool is a node's in-memory pool of validated transactions waiting to be included in a block. Validated transactions wait in the mempool until CometBFT selects a block proposer for the next round. ## PrepareProposal Each round, [CometBFT](/cometbft/latest/docs/introduction/intro#intro-to-abci) selects one validator to propose a block. `PrepareProposal` is called on that validator only. `BaseApp` selects transactions from the mempool respecting the block's `MaxTxBytes` and `MaxGas` limits and returns the final transaction list. For where this handler is configured, see [Block proposal and vote extensions](/sdk/latest/learn/concepts/baseapp#block-proposal-and-vote-extensions). ## ProcessProposal Once the other validators receive the proposed block, CometBFT calls `ProcessProposal`. `BaseApp` verifies each transaction and returns `ACCEPT` or `REJECT`. No state is written. Once more than two-thirds of voting power accepts the block and consensus is reached, CometBFT calls `FinalizeBlock`. For the execution-model view of these handlers, see [Block proposal and vote extensions](/sdk/latest/learn/concepts/baseapp#block-proposal-and-vote-extensions). ## FinalizeBlock CometBFT calls `FinalizeBlock` once per block. Inside `FinalizeBlock`, `BaseApp` runs these phases in order: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} PreBlock → BeginBlock → transaction execution → EndBlock ``` ### `PreBlock` `PreBlock` runs before `BeginBlock` and is generally used for logic that must affect consensus-critical state before the block begins, such as activating a chain upgrade or modifying consensus parameters. Because these changes need to take effect before any block logic runs, they cannot happen inside `BeginBlock`. Modules may implement this via the `HasPreBlocker` extension interface on their `AppModule` (typically in `x//module.go`), and the application's `ModuleManager` invokes all registered PreBlockers during `FinalizeBlock`. If a `PreBlocker` modifies consensus parameters, it signals this by returning `ConsensusParamsChanged=true` in its `ResponsePreBlock`. `BaseApp` then refreshes the consensus params in the current context before proceeding to `BeginBlock`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.finalizeBlockState.ctx = app.finalizeBlockState.ctx.WithConsensusParams(app.GetConsensusParams()) ``` ### `BeginBlock` `BeginBlock` runs after `PreBlock` and handles per-block housekeeping that must happen before any transactions execute, regardless of the transactions in the block. Common uses include minting inflation rewards, distributing staking rewards, and resetting per-block state. Modules implement this via the `BeginBlock` function in `x//module.go`. Because `BeginBlock` and `EndBlock` run on every block, complex or expensive logic in these hooks can slow block execution; keep their work lightweight. ### Transaction execution After `BeginBlock`, `BaseApp` iterates over each transaction in the block and runs it through a fixed pipeline. #### Step 1: `AnteHandler` Configured in a Cosmos SDK chain's [`app.go`](/sdk/latest/learn/concepts/app-go), the `AnteHandler` runs first for every transaction. For standard ordered transactions, it verifies signatures, checks sequence numbers, deducts fees, and meters gas. See [BaseApp](/sdk/latest/learn/concepts/baseapp#antehandler) for the full middleware model. If the `AnteHandler` fails, the transaction aborts and its messages do not execute. #### Step 2: Message routing and execution Each message in a transaction is routed via `BaseApp`'s `MsgServiceRouter` to the appropriate module's protobuf `Msg` service. Messages are module-specific and typically defined in a module's `tx.proto`. `BaseApp` routes these messages to the module's registered protobuf `Msg` service handler, which calls the module's `MsgServer` implementation. See [Message routing](/sdk/latest/learn/concepts/baseapp#message-routing) for the router's role in the execution pipeline. The `MsgServer` contains the execution logic for that message type. It validates the message content, applies business rules, and updates state. State is read and written through the module's keeper, which manages access to the module's KV store and encapsulates its storage keys. [Intro to Modules](/sdk/latest/learn/concepts/modules) explains how `MsgServer` and `Keeper` divide responsibilities. Messages execute sequentially in the order they appear in the transaction. #### Step 3: Atomicity Message execution is atomic: all messages succeed or none of the message execution writes are committed. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Tx ├─ Msg 1 ├─ Msg 2 └─ Msg 3 ``` If any message fails, the message execution branch for that transaction is discarded and the transaction returns an error. The next transaction in the block is then executed. `BaseApp` uses cached stores internally to implement this. `AnteHandler` side effects may already have been applied before message execution begins. If the chain enables unordered transactions, the normal sequence check is bypassed and replay protection uses a timeout timestamp plus unordered nonce tracking. For the client-facing flow, see [Generating an Unordered Transaction](/sdk/latest/node/txs#generating-an-unordered-transaction). ### `EndBlock` `EndBlock` runs after all transactions in the block have executed. It is used for logic that depends on the block's cumulative state, like tallying governance votes after all vote transactions have been processed, or recalculating validator power after all delegation changes in the block. Modules implement this via the `EndBlock` function in `x//module.go`. ## Commit After `FinalizeBlock` returns, CometBFT calls `Commit`. This persists the state changes to the node's local disk. ## Deterministic execution Across all validators, the block execution is deterministic. Blocks must contain the same ordered transactions, and transactions must use canonical protobuf binary encoding. State transitions must be deterministic, which ensures that every validator computes the same app hash during `FinalizeBlock`, which guarantees consensus safety. If validators holding more than 1/3 of voting power disagree on the app hash, consensus halts. ## Complete lifecycle overview ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} CometBFT ↓ ABCI InitChain BaseApp → x//InitGenesis For each submitted transaction (async): ↓ ABCI CheckTx → decode, verify, validate → insert into mempool For every block: ↓ ABCI PrepareProposal (proposer only) → select txs from mempool (MaxTxBytes, MaxGas) → return tx list to CometBFT ↓ ABCI ProcessProposal (all validators) → verify txs, check gas limit → ACCEPT or REJECT ↓ ABCI FinalizeBlock → PreBlock → x//BeginBlock For each tx (in the block): → AnteHandler → Message routing → Message execution (atomic) → x//EndBlock ↓ ABCI Commit BaseApp commits KVStores ``` The hooks that run at each phase (the `AnteHandler`, `BeginBlocker`, `EndBlocker`, and `InitChainer`) are registered in your chain's [`app.go`](/sdk/latest/learn/concepts/app-go) before any block executes. `app.go` is the configuration layer that wires modules into `BaseApp`. CometBFT drives block processing through ABCI. `BaseApp` implements ABCI and orchestrates execution. * Transactions are validated in `CheckTx` before entering the mempool * `PrepareProposal` runs on the proposer to build the final tx set for the block * `ProcessProposal` runs on all validators to accept or reject the proposed block * Each block is executed inside a single `FinalizeBlock` call * Within `FinalizeBlock`: `PreBlock` → `BeginBlock` → transactions → `EndBlock` * Each transaction runs through `AnteHandler` → message routing → message execution * Message execution within a transaction is atomic: all messages commit or none do * `FinalizeBlock` computes and returns the app hash; `Commit` persists state to disk [Protobuf](/sdk/latest/learn/concepts/encoding) ensures canonical encoding so all validators interpret transactions identically. The next section, [Intro to Modules](/sdk/latest/learn/concepts/modules), turns from block execution to the module structure that actually implements chain logic. # Intro to Modules Source: https://docs.cosmos.network/sdk/latest/learn/concepts/modules In the previous section, you saw how blocks and transactions are processed. But where does the actual application logic of a blockchain live? In the Cosmos SDK, **modules** define that logic. Modules are the fundamental building blocks of a Cosmos SDK application. Each module encapsulates a specific piece of functionality, such as accounts, token transfers, validator management, governance, or any custom logic you define. To see a complete working module, follow the [Build a module tutorial series](/sdk/latest/tutorials/example/00-overview). ## Why modules exist A blockchain application needs to manage many independent concerns (accounts, balances, validator management, etc). Instead of placing all logic in a single monolithic state machine, the Cosmos SDK divides the application into modules. The SDK provides a base layer that allows these modules to operate together as a cohesive blockchain. Each module owns a slice of state, defines its messages and queries, implements its business rules, and hooks into the block lifecycle and genesis as needed. This keeps the application organized, composable, and easier to reason about. It also separates safety concerns between modules, creating a more secure system. ## What a module defines A module is a self-contained unit of state and logic. At a high level, a module defines: * [State](#state): a `KVStore` namespace that contains the module's data * [Messages](#messages): the actions the module allows * [Queries](#queries): read-only access to the module's state * [`MsgServer`](#message-execution-msgserver): validates, applies business logic, and delegates to the keeper * [Keeper](#keeper): the state access layer: the only sanctioned path to the store * [Params](#params): governance-controlled configuration stored on-chain ### State State is the data persisted on the chain: everything that transactions can read from or write to. When a transaction is executed, modules apply state transitions or deterministic updates to this stored data. Each module owns its own part of the blockchain state. For example: * The `x/auth` module stores account metadata. * The `x/bank` module stores account balances. Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. For example, the bank module's store might contain entries like: ``` // x/bank store (conceptual) balances | cosmos1abc...xyz | uatom → 1000000 balances | cosmos1def...uvw | uatom → 500000 ``` The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [module's keeper](#keeper) can. To learn more about how state is stored and accessed, see [Store](/sdk/latest/learn/concepts/store). ### Messages As you learned in the [Transactions, Messages, and Queries](/sdk/latest/learn/concepts/transactions) section, each module defines the actions it allows via messages (`sdk.Msg`). Messages are defined in the module's `tx.proto` file under a `service Msg` block, and implemented by that module's `MsgServer`. Here is a simplified example from the bank module: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Message type definition message MsgSend { string from_address = 1; string to_address = 2; repeated cosmos.base.v1beta1.Coin amount = 3; } // Msg service — groups all messages for this module service Msg { rpc Send(MsgSend) returns (MsgSendResponse); } ``` The `service Msg` block is referred to as the `Msg service` in the Cosmos SDK. The protobuf compiler generates a `MsgServer` interface from it, which the module implements in Go. [`BaseApp`](/sdk/latest/learn/concepts/baseapp) routes each incoming message by its type URL (for example, `/cosmos.bank.v1beta1.MsgSend`) to the correct implementation. `MsgSend` above is an example of a message type definition. It represents a request to transfer tokens from one account to another. When included in a transaction and executed: 1. The sender's balance is checked. 2. The amount is deducted from `from_address`. 3. The amount is credited to `to_address`. ### Queries Modules expose read-only access to their state through query services, defined in `query.proto`. Queries do not modify state and do not go through block execution. For example: ``` // query.proto rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse); ``` In this case, the caller provides an address and denomination; the query reads the balance from the x/bank keeper and returns it without modifying state. ### Business logic While messages define intent, the `MsgServer` and `Keeper` work together to execute that intent and apply state transitions to the module. Business logic is conceptually split across two layers: * The [`MsgServer`](#message-execution-msgserver) handles the transaction-facing logic: it validates inputs, applies message-level business rules, and where required checks authorization. Once satisfied, it delegates state transitions to the keeper. * The [`Keeper`](#keeper) owns the module's state: it defines the storage schema and provides the only authorized path for reading and writing it. Message handlers, block hooks, and governance proposals all go through keeper methods to make state changes. All state changes must go through the keeper, nothing accesses the store directly. ### Message execution (`MsgServer`) Each module implements a `MsgServer`, which is invoked by `BaseApp`'s message router when a message is routed to that module. The `MsgServer` is responsible for: * Checking authorization when required * Delegating to keeper methods that validate inputs, enforce business rules, and perform state transitions * Returning a response The following example is from the minimal counter module tutorial example, which walks you through building a module that lets users increment a shared counter. See the [Build a Module from Scratch](/sdk/latest/tutorials/example/03-build-a-module) tutorial. The following is the counter module's `Add` handler which is the `MsgServer` method that processes a request to increment the counter: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*types.MsgAddResponse, error) { newCount, err := m.AddCount(ctx, request.GetAdd()) if err != nil { return nil, err } return &types.MsgAddResponse{UpdatedCount: newCount}, nil } ``` In the minimal tutorial, `Add` is permissionless and delegates directly to the keeper. The handler itself contains almost no business logic. That keeps `msg_server.go` focused on request handling, while the keeper owns the actual state transition. The full counter module example adds richer patterns on top of that minimal shape. For privileged messages like `MsgUpdateParams`, the `MsgServer` checks the caller against the stored authority before proceeding. See the [Full Counter Module Walkthrough](/sdk/latest/tutorials/example/04-counter-walkthrough#params-and-authority). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { if m.authority != msg.Authority { return nil, sdkerrors.Wrapf( govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", m.authority, msg.Authority, ) } if err := m.SetParams(ctx, msg.Params); err != nil { return nil, err } return &types.MsgUpdateParamsResponse{}, nil } ``` ## Keeper A module's **`Keeper`** is its state access layer. It owns the module's `KVStore` and provides typed methods for reading and writing state. The store fields are unexported, so nothing outside the `keeper` package can access them directly. The `MsgServer` and `QueryServer` both embed the keeper. It is best practice for business logic to be implemented in keeper methods rather than the `MsgServer`, so the same rules apply whether the caller is a message handler, a block hook, or a governance proposal. The following keeper example is from the minimal counter module example. It holds a single state item and shows the smallest useful keeper shape. See [Step 5: Keeper](/sdk/latest/tutorials/example/03-build-a-module#step-5-keeper) in the tutorial. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { Schema collections.Schema counter collections.Item[uint64] } func (k *Keeper) AddCount(ctx context.Context, amount uint64) (uint64, error) { count, err := k.GetCount(ctx) if err != nil { return 0, err } newCount := count + amount return newCount, k.counter.Set(ctx, newCount) } ``` `counter` uses `collections.Item[uint64]`, a typed single-value entry backed by the module's KV store using the [Collections API](/sdk/latest/learn/concepts/store#collections-api-typed-state-access). `AddCount` reads the current value, increments it, and writes it back. All state access goes through the keeper: nothing outside the `keeper` package can reach `counter` directly. For details on how the keeper opens its store from the context it receives, see [Execution Context](/sdk/latest/learn/concepts/context-gas-events). ### Inter-module access Modules are isolated by default. Each module owns its state, and direct access to that state is restricted through the module's keeper. Other modules cannot arbitrarily mutate another module's storage. Instead, modules interact through explicitly defined keeper interfaces. For example: * The staking module calls methods on the bank keeper to transfer tokens. * The governance module calls parameter update methods on other modules. Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation. To see expected keepers and cross-module fee collection in practice, see [Expected keepers and fee collection](/sdk/latest/tutorials/example/04-counter-walkthrough#expected-keepers-and-fee-collection) in the Full Counter Module Walkthrough. ### Params Most modules expose a `Params` struct: a set of configuration values stored on-chain that control the module's behavior. Unlike regular state, params are intentionally stable: they only change through governance, not through user transactions. Examples include the minimum governance deposit, the maximum number of validators, or mint module inflation bounds. Params are stored under a single key (typically a `collections.Item[Params]`) and updated by submitting a governance proposal. To update params, a governance proposal submits a [`MsgUpdateParams`](/sdk/latest/modules/consensus/README#msgupdateparams) message. The `MsgServer` checks that the caller is the designated authority address (usually the governance module account) before writing the new values to the store. See [Message execution (`MsgServer`)](#message-execution-msgserver) for a code example of this pattern. The authority address is set at keeper construction time in [`app.go`](/sdk/latest/learn/concepts/app-go#initializing-keepers). For chain-level consensus parameters, the [`x/consensus`](/sdk/latest/modules/consensus) module manages them centrally; it also supports [`AuthorityParams`](/sdk/latest/modules/consensus/README#authorityparams), which lets governance update the authority address on-chain without a software upgrade. To see params implemented in a working module, see [Params and authority](/sdk/latest/tutorials/example/04-counter-walkthrough#params-and-authority) in the Full Counter Module Walkthrough. ### Block hooks Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces in the `AppModule` struct in `module.go`: * `HasBeginBlocker` — runs logic at the start of each block * `HasEndBlocker` — runs logic at the end of each block * `HasPreBlocker` — runs logic before `BeginBlock`, used for consensus parameter changes Hooks are optional, and modules should only implement the hooks they need. These hooks are invoked during block execution by the `ModuleManager` in [`BaseApp`](/sdk/latest/learn/concepts/baseapp#module-manager), which calls each registered module's hooks in a configured order. For the application wiring side, see [Module Manager in `app.go`](/sdk/latest/learn/concepts/app-go#module-manager). A module implements a hook by defining the corresponding method on its `AppModule` struct in `module.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (a AppModule) PreBlock(ctx context.Context) (appmodule.ResponsePreBlock, error) { // runs before BeginBlock; used for logic that must take effect before block execution return &sdk.ResponsePreBlock{}, nil } func (a AppModule) BeginBlock(ctx context.Context) error { // runs at the start of each block return nil } func (a AppModule) EndBlock(ctx context.Context) error { // runs at the end of each block return nil } ``` Defining these methods opts the module into the corresponding block hooks. The module also needs to be registered with the `ModuleManager` in [`app.go`](/sdk/latest/learn/concepts/app-go#module-manager) for the hooks to be called. ### Genesis initialization Modules define how their state is initialized when the chain starts. Each module implements: * `DefaultGenesis`: returns the module's default genesis state * `ValidateGenesis`: validates the genesis state before the chain starts * `InitGenesis`: writes the genesis state into the module's store at chain start * `ExportGenesis`: reads the module's current state and serializes it as genesis data During `InitChain`, `BaseApp` calls each module's `InitGenesis` to populate its state from `genesis.json`. For where this happens in the block lifecycle, see [InitChain (genesis only)](/sdk/latest/learn/concepts/lifecycle#initchain-genesis-only). For a walkthrough of genesis implementation, see [Step 2: Proto files](/sdk/latest/tutorials/example/03-build-a-module#step-2-proto-files) and [Step 8: module.go](/sdk/latest/tutorials/example/03-build-a-module#step-8-modulego) in the Build a Module tutorial. ## Built-in and custom modules The Cosmos SDK ships with a set of core modules that most chains use. Click any module to learn more: | Module | What it does | | -------------------------------------------------- | ------------------------------------------------- | | [x/auth](/sdk/latest/modules/auth) | Accounts, authentication, and transaction signing | | [x/bank](/sdk/latest/modules/bank) | Token balances and transfers | | [x/staking](/sdk/latest/modules/staking) | Validator set and delegations | | [x/gov](/sdk/latest/modules/gov) | On-chain governance and proposals | | [x/distribution](/sdk/latest/modules/distribution) | Staking reward distribution | | [x/slashing](/sdk/latest/modules/slashing) | Validator penalty enforcement | | [x/mint](/sdk/latest/modules/mint) | Token issuance | | [x/evidence](/sdk/latest/modules/evidence) | Submission and handling of validator misbehavior | | [x/upgrade](/sdk/latest/modules/upgrade) | Coordinated chain upgrades | | [x/authz](/sdk/latest/modules/authz) | Delegated message authorization | | [x/feegrant](/sdk/latest/modules/feegrant) | Fee allowances between accounts | | [x/consensus](/sdk/latest/modules/consensus) | On-chain management of CometBFT consensus params | The above are just a few of the modules available. Applications can include any subset of these modules and can define entirely new custom modules. For the full list, see [List of Modules](/sdk/latest/modules/modules). [Cosmos Enterprise](/sdk/latest/enterprise/overview) provides additional hardened modules for production networks with more demanding requirements: | Module | What it does | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | [Permissioned Consensus](/sdk/latest/enterprise/poa/overview) | Permissioned validator set managed by an on-chain authority, replacing token-based staking | | [Multi-sig](/sdk/latest/enterprise/group/overview) | On-chain multisig accounts and collective decision-making with configurable voting policies | A Cosmos SDK blockchain is ultimately a collection of modules assembled into a single application. This customization of modules and application logic down to the lowest levels of a chain is what makes the Cosmos SDK so flexible and powerful. ## Anatomy of a module (high-level) Modules live under the `x/` directory of an SDK application: ``` x/ ├── auth/ # Accounts and authentication ├── bank/ # Token balances and transfers ├── poa/ # Proof-of-authority validator management ├── gov/ # Governance system └── mymodule/ # Your custom module ``` Each subdirectory under `x/` is a self-contained module. An application composes multiple modules together to form a complete blockchain. Inside a module, you will typically see a structure like this: ``` x/mymodule/ ├── keeper/ │ ├── keeper.go # Keeper struct and state access methods │ ├── msg_server.go # MsgServer implementation │ └── query_server.go # QueryServer implementation ├── types/ │ ├── expected_keepers.go # Interfaces for other modules' keepers │ ├── keys.go # Store key definitions │ └── *.pb.go # Generated from proto definitions └── module.go # AppModule implementation and hook registration ``` Proto files live separately at the repository root, not inside `x/`: ``` proto/myapp/mymodule/v1/ ├── tx.proto # Message definitions ├── query.proto # Query service definitions ├── state.proto # On-chain state types └── genesis.proto # Genesis state definition ``` * `keeper/`: Contains the `Keeper` struct (state access) and implementations of the `MsgServer` and `QueryServer` interfaces. * `types/`: Defines the module's public types: generated protobuf structs, store keys, and the `expected_keepers.go` interfaces that declare what this module needs from other modules. * `module.go`: Connects the module to the application and registers genesis handlers, block hooks, and message and query services. * `.proto` files: Define messages, queries, state schemas, and genesis state. Go code is generated from these files and used throughout the module. Check out the [Module Tutorial](/sdk/latest/tutorials/example/00-overview) to learn how to build a module from scratch. ## Modules in context Putting everything together you've learned so far: * **Accounts** authorize transactions. * **Transactions** carry messages and execution constraints. * **Blocks** order transactions and define commit boundaries. * **Modules** define the business rules of execution. * **MsgServer** validates messages and orchestrates state transitions. * **Keeper** performs controlled reads and writes to module state. * **State** persists the deterministic result of execution. ``` Account → Transaction → Message → Module → MsgServer → Keeper → State ``` In the next section, [State, Storage, and Genesis](/sdk/latest/learn/concepts/store), you will look more closely at how module state is stored, how genesis initializes it, and how the application commits deterministic state transitions. Before writing a module, review [Module Design Considerations](/sdk/latest/guides/module-design/module-design-considerations) for guidance on state structure, message surface, inter-module dependencies, and upgrade planning. When you are ready to build, follow the [Build a Module tutorial](/sdk/latest/tutorials/example/00-overview) for a step-by-step walkthrough. # Cosmos Blockchain Structure Source: https://docs.cosmos.network/sdk/latest/learn/concepts/sdk-structure Before writing a module or chain, it helps to understand how the Cosmos SDK organizes code and how the pieces connect. This page maps the directory structure of a Cosmos SDK application, explains what lives inside a module, and shows how modules are assembled into a running application. ## What is an SDK application A Cosmos SDK application is a Go binary that implements a deterministic state machine. It runs alongside CometBFT inside the node daemon process. CometBFT drives consensus and the SDK application executes transactions and maintains state. Every SDK application is composed of three main elements: * [`BaseApp`](/sdk/latest/learn/concepts/baseapp): the execution engine that implements the ABCI and orchestrates transaction processing * [Modules](/sdk/latest/learn/concepts/modules): self-contained units of business logic, state, messages, and queries * [`app.go`](/sdk/latest/learn/concepts/app-go): the wiring layer that instantiates `BaseApp`, registers modules, and configures the application at startup `BaseApp` and `app.go` are covered in depth in the next two sections. This page focuses on how they are organized in the codebase. To see all of this in action, follow the [Build a Chain tutorial series](/sdk/latest/tutorials/example/00-overview). ## Repository structure SDK applications repositories generally use the following layout: ``` myapp/ ├── app/ │ └── app.go # Application wiring: configures BaseApp, registers modules ├── cmd/ │ └── main.go # Node binary entrypoint ├── x/ │ ├── mymodule/ # Custom module │ └── ... └── proto/ └── myapp/ └── mymodule/ └── v1/ ├── tx.proto ├── query.proto ├── state.proto └── genesis.proto ``` Each directory has a distinct responsibility: * `x/` contains the modules. Each subdirectory is a separate, self-contained module. Built-in Cosmos SDK modules (`x/auth`, `x/bank`, `x/staking`, etc.) follow the same layout and are imported as Go packages. Your custom modules live alongside them. * `app/` contains `app.go`, which assembles the application: creating `BaseApp`, mounting stores, initializing keepers, and registering all modules with the `ModuleManager`. * `cmd/` contains `main.go`, the entrypoint for the node binary. It parses command-line flags, reads configuration files, and starts the daemon process that runs both the CometBFT node and the SDK application. * `proto/` contains the Protobuf definitions for all custom types: messages, queries, state schemas, and genesis. Go code is generated from these files and consumed throughout the module. Proto files live at the repository root, not inside `x/`, so they can be shared across languages and tooling. ## What lives inside a module Each module under `x/` follows a consistent internal layout: ``` x/mymodule/ ├── keeper/ # Keeper (state access), MsgServer, QueryServer ├── types/ # Generated proto types, store keys, expected_keepers.go └── module.go # AppModule: wires the module into the application ``` Proto definitions live separately in `proto/`, not inside `x/`. See [Intro to Modules](/sdk/latest/learn/concepts/modules#anatomy-of-a-module-high-level) for a complete walkthrough of each file and the role it plays. ## How modules are assembled into an application Modules are assembled through the `ModuleManager` in [`app.go`](/sdk/latest/learn/concepts/app-go). The `ModuleManager` holds the full set of registered modules and coordinates their lifecycle hooks (`InitGenesis`, `BeginBlock`, `EndBlock`, and service registration) across the application. Module ordering is configured explicitly in `app.go` and matters: for example, in `simapp` the distribution module runs before slashing in `BeginBlock` so validator rewards are handled before slashing updates are applied. See [Module Manager](/sdk/latest/learn/concepts/baseapp#module-manager) for details on how `BaseApp` integrates with it. `BaseApp` implements the ABCI interface that CometBFT calls to drive block execution. When CometBFT calls `FinalizeBlock`, `BaseApp` runs the block through all its phases (`PreBlock`, `BeginBlock`, transactions, `EndBlock`) and returns the resulting app hash. `BaseApp` is covered in detail in [BaseApp Overview](/sdk/latest/learn/concepts/baseapp). ## The role of `app.go` [`app.go`](/sdk/latest/learn/concepts/app-go) is the single file that defines a specific chain. It is where the application is assembled from its parts. Here is a breakdown of the steps it takes: 1. Create a `BaseApp` instance with the application name, logger, database, and codec. 2. Create a `StoreKey` for each module and mount it to the multistore. 3. Instantiate each `Keeper`, passing in the codec, store key, and references to other keepers the module depends on. 4. Create the `ModuleManager` with all module instances. 5. Configure execution ordering: which modules run first during genesis, `BeginBlock`, and `EndBlock`. 6. Register all gRPC services (message and query handlers) through the `ModuleManager`. 7. Set the `AnteHandler` and other middleware. Because `app.go` is plain Go code, it is fully customizable. A chain includes exactly the modules it needs, wires keepers together as required, and controls the execution order of all lifecycle hooks. ## Other files in a chain A complete SDK chain repository contains more than just `x/`, `app/`, `cmd/`, and `proto/`. Below are some other files you will typically find in a Cosmos SDK chain repository: ### Additional files in `app/` Real-world applications typically split the `app/` directory across multiple files to keep `app.go` focused on wiring: ``` app/ ├── app.go # Main wiring: BaseApp, keepers, module registration ├── export.go # Exports current state as a genesis file (hard forks, snapshots) ├── upgrades.go # Upgrade handlers for consensus-breaking software changes └── genesis.go # Helpers for genesis state initialization (optional) ``` * `export.go`: Implements `ExportAppStateAndValidators`, which serializes all module state into a `genesis.json`. This is used when migrating to a new chain version (hard fork) or creating a testnet from a live chain snapshot. * `upgrades.go`: Registers named upgrade handlers consumed by the `x/upgrade` module. Each handler runs exactly once, at the block where the governance-approved upgrade height is reached, and performs any necessary state migrations. ### At the repository root ``` myapp/ ├── go.mod # Go module definition: SDK version and all dependencies ├── go.sum # Cryptographic checksums for all dependencies ├── Makefile # Build, test, and codegen tasks └── scripts/ # Automation scripts (proto generation, linting) ``` * `go.mod` / `go.sum`: Standard Go module files. `go.mod` declares the Cosmos SDK version and all other imported packages. `go.sum` provides verifiable checksums for the full dependency tree. * `Makefile`: The standard entry point for development tasks: `make build` compiles the binary, `make test` runs unit tests, `make proto-gen` regenerates Go code from `.proto` files. Most SDK chains include targets for linting, simulation tests, and Docker builds. * `scripts/`: Shell scripts and configuration for tooling that the Makefile invokes. ### The node binary (`cmd/`) ``` cmd/ └── myappdaemon/ ├── main.go # Binary entrypoint └── root.go # Root Cobra command: subcommands (start, tx, query, keys, ...) ``` The `cmd/` directory produces the node daemon binary (e.g., `simd`, `gaiad`, `wasmd`). It uses [Cobra](https://github.com/spf13/cobra) to expose subcommands for starting the node, submitting transactions, querying state, managing keys, and running genesis initialization. The `start` command spins up CometBFT and the SDK application together in a single process. ### Node home directory A typical repository contains the source code for a chain. When you actually run a node, the binary generates a separate home directory on disk that holds runtime configuration and chain data. Running `myappdaemon init` creates this directory: ``` ~/.myapp/ # Node home directory (configurable with --home) ├── config/ │ ├── app.toml # SDK server configuration │ ├── config.toml # CometBFT configuration │ ├── client.toml # CLI client defaults │ └── genesis.json # Initial chain state └── data/ # Database files (block store, state store, snapshots) ``` The location defaults to `~/.myapp` but can be overridden with the `--home` flag or the `MYAPP_HOME` environment variable. Each configuration file controls a distinct layer of the node: * `app.toml`: SDK-level server settings. Controls whether the gRPC server and REST API are enabled, their bind addresses, state sync configuration, pruning strategy, and mempool parameters. * `config.toml`: CometBFT-level settings. Controls P2P networking (seeds, peers, listen address), consensus timeouts, the CometBFT RPC server address, and block size limits. * `client.toml`: Default values for CLI client commands. Stores the chain ID, keyring backend, and the node RPC address so you don't have to pass `--chain-id` and `--node` on every command. * `genesis.json`: The initial state of the chain at block 0. It is distributed out-of-band when joining a network, or generated locally for a new chain. Once the chain starts, this file is no longer read. ## Summary An SDK application is a deterministic state machine composed of modules assembled in `app.go`. The codebase follows a conventional layout: modules in `x/`, application wiring in `app/`, the binary entrypoint in `cmd/`, and Protobuf definitions in `proto/`. The `ModuleManager` assembles modules and coordinates their lifecycle hooks across the application. `BaseApp` provides the ABCI implementation that connects the state machine to CometBFT's consensus engine. The next section, [BaseApp Overview](/sdk/latest/learn/concepts/baseapp), explains what `BaseApp` is and how it coordinates transaction execution in detail. # State, Storage, and Genesis Source: https://docs.cosmos.network/sdk/latest/learn/concepts/store In the previous section, you learned that modules define business logic and that keepers are responsible for reading and writing module state. This page explains how that state is actually stored, committed, and made verifiable across the network. ## What is state? State is the persistent data of the blockchain: account balances, delegations, governance proposals, [module parameters](/sdk/latest/learn/concepts/modules#params), and any other data that survives between blocks. When a transaction executes, modules update state. When a block is committed, that updated state becomes the starting point for the next block: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} State0 ↓ apply Block 1 State1 ↓ apply Block 2 State2 ``` ## The KVStore model At its lowest level, the Cosmos SDK stores state as **key-value pairs**. Both keys and values are byte arrays. Modules encode structured data into those bytes using Protocol Buffers, and decode them back when reading. See [Encoding and Protobuf](/sdk/latest/learn/concepts/encoding) for details on how modules serialize data into bytes. The following example shows a conceptual example of how the bank module stores balances: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} key: 0x2 | len(address) | address_bytes | denom_bytes value: ProtocolBuffer(amount) # example key: 0x2 | 20 | cosmos1abc...xyz | uatom value: ProtocolBuffer(1000000) ``` The key encodes the store prefix, address length, address, and denomination. The value is a Protocol Buffer-encoded amount. See [`x/bank/types/keys.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/bank/types/keys.go) for the actual implementation. Each module owns its own namespace in the key-value store. Keys are defined by the module and typically begin with a byte prefix that distinguishes them from other module keys. ## Multistore A single module store is only part of the picture. At the application level, all module stores are committed together. Every module has its own KVStore, and all module stores are mounted inside a [multistore](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/rootmulti/store.go) that is committed as a single state root. A module can only read and write to its own store through its keeper. Access is gated by a `StoreKey`, which is a typed capability object registered at app startup. Modules that don't hold the key cannot open the store. This isolation follows an object-capabilities model: 1. Modules cannot directly mutate another module's state 2. Cross-module interaction must go through exposed keeper methods When a block finishes executing, the multistore computes a new root hash (the **app hash**) that represents the entire application state. That hash is returned to CometBFT, included in the block header, and is what makes the chain's state verifiable. [Transaction Lifecycle](/sdk/latest/learn/concepts/lifecycle) explains where that app hash is produced and committed. The full storage stack from top to bottom is: ``` Module keeper ↓ KVStore (namespaced, wrapped with gas metering) ↓ CommitMultiStore (multistore, computes app hash) ↓ IAVL tree (versioned Merkle tree) ↓ Database backend (goleveldb by default) ``` ## How state is stored (IAVL and commit stores) Each module's KVStore is backed by a [`CommitKVStore`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/iavl/store.go#L36). [See the store spec for more details.](/sdk/latest/guides/state/store) In the current SDK store implementation described here, the Cosmos SDK uses [IAVL](https://github.com/cosmos/iavl), a versioned AVL Merkle tree. IAVL gives every read and write of the tree `O(log n)` complexity, meaning the time to read or write a key scales with the height of the tree, not the total number of keys. It also versions state on each block commit, and produces deterministic root hashes that can be used to generate Merkle proofs for light clients. Each block commit produces a new tree version with a new root hash: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Block 1 Block 2 Block 3 [root h1] [root h2] [root h3] / \ / \ / \ [branch1] [branch2] [branch1] [branch2'] [branch1] [branch2''] / \ / \ / \ / \ / \ / \ [a] [b] [c] [d] [a] [b] [c] [d'] [a] [b] [c'] [d'] ↑ ↑ (updated) (updated) // branch1 and branch2 are internal nodes (they store hashes, not data). // Leaf nodes (a, b, c, d) are actual key-value entries. // When a leaf changes, only nodes on the path to the root are rewritten (marked '). // Unmodified subtrees (branch1, a, b) are shared across all three versions. ``` Only modified nodes are rewritten, and unchanged nodes are shared across versions. The root hash changes any time any leaf changes. All validators must compute the same root hash. If they disagree, consensus halts. Because of this, state transitions must be deterministic, encoding must be deterministic, and transaction ordering must be consistent. ### App hash The **app hash** is the cryptographic root hash of the application's committed state. It summarizes all module stores together through the `CommitMultiStore`. Because every validator executes the same state transitions deterministically, they should all compute the same app hash for a given block. ### Database backend The IAVL tree does not store data in memory. It writes versioned nodes to a database backend, which is a key-value store on disk. The Cosmos SDK uses [CometBFT's `db` package](https://github.com/cometbft/cometbft-db) to abstract over the database implementation. The default backend is [goleveldb](https://github.com/syndtr/goleveldb). Other supported backends include [PebbleDB](https://github.com/cockroachdb/pebble), [RocksDB](https://github.com/facebook/rocksdb), and memDB (in-memory, for testing). The database backend is selected at node startup and configured in [`app.toml`](/sdk/latest/tutorials/example/05-run-and-test#apptoml). Application code never interacts with it directly; the store layer owns that boundary. ## Store types in the SDK Beyond the base KVStore, the SDK provides several specialized store wrappers. * [CommitKVStore](#commitkvstore-persistent-store) * [CacheMultiStore](#cachemultistore-transaction-isolation) * [Ephemeral store types](#ephemeral-store-types) * [Gas store wrapper](#gas-store-wrapper) * [Prefix store](#prefix-store) ### CommitKVStore (persistent store) The [`CommitKVStore`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/iavl/store.go#L36) is the main persistent store backed by IAVL. It persists across blocks, produces versioned commits, and contributes to the app hash. ### CacheMultiStore (transaction isolation) Before executing each transaction, the Cosmos SDK's `BaseApp` creates a [`CacheMultiStore`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/cachemulti/store.go) — a cached, copy-on-write view of the multistore. All writes during that transaction occur in this cached layer: ``` Multistore ↓ CacheWrap (per transaction) ↓ Execute tx → Success → commit changes → Failure → discard ``` * If the transaction succeeds, changes are written to the underlying store. * If the transaction fails, the cache is discarded and no state changes are committed. This is how transaction atomicity is implemented in the store layer. ### Ephemeral store types [Transient stores](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/transient/store.go) are cleared at the end of each block. They are used for temporary per-block data such as counters or intermediate calculations, and do not affect the app hash. [Memory stores](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/mem/store.go) survive block commits but reset when the node restarts — their `Commit()` is a no-op and data is never written to disk. They are used for in-process caching of data that is expensive to recompute each block but does not need to survive a restart. Modules access them via `MemoryStoreKey`, mounted with `MountMemoryStores` in `app.go`. | Store type | Survives block commit | Survives restart | | -------------------- | ----------------------- | ---------------- | | Transient | No (cleared each block) | No | | Memory | Yes | No | | IAVL (CommitKVStore) | Yes | Yes | ### Gas store wrapper All store accesses are wrapped with additional behavior by the [`GasKVStore`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/gaskv/store.go) wrapper, which charges gas for each read and write. Because state access is the dominant cost of transaction execution, the SDK charges gas at the store layer so that expensive reads and writes are reflected in transaction fees. Every read and write of a KVStore costs gas, and expensive operations naturally cost more. [Execution Context, Gas, and Events](/sdk/latest/learn/concepts/context-gas-events) explains how gas metering works at runtime. ### Prefix store A [**prefix store**](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/prefix/store.go) wraps a KVStore and automatically prepends a fixed byte prefix to every key. This lets keepers scope their reads and writes to a sub-namespace without manually constructing prefixed keys on every call. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} prefixStore := prefix.NewStore(kvStore, types.KeyPrefix("balances")) prefixStore.Set(key, value) // stored as "balances" + key ``` This is how modules avoid key collisions within their own store. ## Collections API (typed state access) In the Cosmos SDK, modules commonly use the collections API to define typed state access. Instead of manually constructing byte keys, modules define typed collections such as: * `collections.Item[T]` * `collections.Map[K, V]` * `collections.Sequence` Example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // declared in the keeper struct Counter collections.Item[uint64] // read and write in message handlers count, _ := k.Counter.Get(ctx) k.Counter.Set(ctx, count+1) ``` The Collections API defines the storage schema, handles encoding and decoding, ensures consistent key construction, and makes state access type-safe. Under the hood, collections still store data in a KVStore. Collections are used to provide a safer abstraction over raw byte keys. See [`collections/collections.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/collections/collections.go) for the base interface definitions. For the full package guide, see [Collections](/sdk/latest/guides/state/collections). ## How modules access state Modules do not interact with the multistore directly. Instead, each module defines a keeper that opens its KVStore through the execution `Context` it receives on each call. For details on how `Context` carries the store reference at runtime, see [Execution Context](/sdk/latest/learn/concepts/context-gas-events). A keeper typically holds: * the module's **store key** (an object-capability used to open the module's `KVStore` from `Context`), * a **Protobuf codec** used to encode and decode values stored as bytes, * **references (interfaces) to other keepers** the module depends on. State access typically flows through the keeper: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} MsgServer / QueryServer ↓ Keeper ↓ KVStore ``` The keeper exposes high-level methods that construct keys, encode values, and enforce business logic: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins func (k Keeper) SetParams(ctx sdk.Context, params types.Params) ``` For the keeper's role within a module, see [Keeper](/sdk/latest/learn/concepts/modules#keeper). ## Genesis and chain initialization Before the first block executes, the chain must start with an initial state called **genesis**, defined in `genesis.json`. Genesis is the first write to the KVStores — it is how every module's state exists before any transaction runs. During `InitChain`, `BaseApp` calls each module's `InitGenesis` to populate its store: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} genesis.json ↓ BaseApp.InitChain ↓ Module.InitGenesis ↓ KVStores populated ``` For information on how modules define their genesis methods (`DefaultGenesis`, `ValidateGenesis`, `InitGenesis`, `ExportGenesis`) and initialization ordering, see [Intro to Modules](/sdk/latest/learn/concepts/modules) and [Transaction Lifecycle](/sdk/latest/learn/concepts/lifecycle). For a walkthrough of genesis implementation in a module, see [Step 2: Proto files](/sdk/latest/tutorials/example/03-build-a-module#step-2-proto-files) and [Step 8: module.go](/sdk/latest/tutorials/example/03-build-a-module#step-8-modulego) in the Build a Module tutorial. ## Next steps For more information on stores, pruning strategies, and store configuration, see the [store spec](/sdk/latest/guides/state/store). For the full store interface definitions, see [`store/types/store.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/types/store.go) in the SDK source. Because KV stores only hold raw bytes, modules must serialize structured data before writing it. The next section, [Encoding and Protobuf](/sdk/latest/learn/concepts/encoding), explains how the Cosmos SDK uses Protocol Buffers to encode that data deterministically, and why every validator must produce exactly the same bytes. # Testing in the SDK Source: https://docs.cosmos.network/sdk/latest/learn/concepts/testing The Cosmos SDK provides a layered testing approach that mirrors the architecture of the framework itself. Tests are organized into three levels, each testing a progressively larger slice of the application. This page uses the counter module example in the `example` repo, not the minimal counter module example, because the fuller module includes the testing surfaces needed for these examples. The examples on this page come from the [Full Counter Module Walkthrough](/sdk/latest/tutorials/example/04-counter-walkthrough#unit-tests) and [Running and Testing](/sdk/latest/tutorials/example/05-run-and-test) tutorials. ## Three testing levels ### Keeper unit tests Keeper unit tests verify keeper logic in isolation, without starting a full application. They construct a minimal in-memory context with a real KV store, initialize the keeper under test, and call its methods directly. No server, no network, no block processing. The counter module keeper tests live in `x/counter/keeper/keeper_test.go`. The test suite sets up a keeper with a live store and mock dependencies: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type KeeperTestSuite struct { suite.Suite ctx sdk.Context keeper *keeper.Keeper queryClient types.QueryClient msgServer types.MsgServer bankKeeper *MockBankKeeper authority string } func (s *KeeperTestSuite) SetupTest() { key := storetypes.NewKVStoreKey("counter") storeService := runtime.NewKVStoreService(key) testCtx := testutil.DefaultContextWithDB(s.T(), key, storetypes.NewTransientStoreKey("transient_test")) ctx := testCtx.Ctx.WithBlockHeader(cmtproto.Header{Time: cmttime.Now()}) encCfg := moduletestutil.MakeTestEncodingConfig() s.authority = "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn" s.bankKeeper = &MockBankKeeper{} k := keeper.NewKeeper(storeService, encCfg.Codec, s.bankKeeper, keeper.WithAuthority(s.authority)) s.ctx = ctx s.keeper = k queryHelper := baseapp.NewQueryServerTestHelper(ctx, encCfg.InterfaceRegistry) types.RegisterQueryServer(queryHelper, keeper.NewQueryServer(k)) s.queryClient = types.NewQueryClient(queryHelper) s.msgServer = keeper.NewMsgServerImpl(k) } ``` `testutil.DefaultContextWithDB` creates a real KV store backed by an in-memory database. `moduletestutil.MakeTestEncodingConfig` returns a codec configured for the test. The `MockBankKeeper` replaces the real bank keeper with a struct whose behavior can be controlled per test case: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MockBankKeeper struct { SendCoinsFromAccountToModuleFn func(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } ``` A typical keeper test case covers the happy path and the error conditions with table-driven tests: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (s *KeeperTestSuite) TestAddCount() { testCases := []struct { name string setup func() sender string amount uint64 expErr bool expErrMsg string expPostCount uint64 }{ { name: "add to zero counter", setup: func() { err := s.keeper.InitGenesis(s.ctx, &types.GenesisState{ Count: 0, Params: types.Params{MaxAddValue: 100}, }) s.Require().NoError(err) }, sender: "cosmos1test", amount: 10, expErr: false, expPostCount: 10, }, { name: "add exceeds max_add_value - should error", setup: func() { err := s.keeper.InitGenesis(s.ctx, &types.GenesisState{ Count: 0, Params: types.Params{MaxAddValue: 50}, }) s.Require().NoError(err) }, sender: "cosmos1test", amount: 100, expErr: true, expErrMsg: "exceeds max allowed", }, } for _, tc := range testCases { s.Run(tc.name, func() { s.SetupTest() tc.setup() newCount, err := s.keeper.AddCount(s.ctx, tc.sender, tc.amount) if tc.expErr { s.Require().Error(err) if tc.expErrMsg != "" { s.Require().Contains(err.Error(), tc.expErrMsg) } } else { s.Require().NoError(err) s.Require().Equal(tc.expPostCount, newCount) count, err := s.keeper.GetCount(s.ctx) s.Require().NoError(err) s.Require().Equal(tc.expPostCount, count) } }) } } ``` `msg_server_test.go` uses the same suite to test the `MsgServer` layer, including event emission: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (s *KeeperTestSuite) TestMsgAddEmitsEvent() { s.SetupTest() err := s.keeper.InitGenesis(s.ctx, &types.GenesisState{ Count: 0, Params: types.Params{MaxAddValue: 100}, }) s.Require().NoError(err) _, err = s.msgServer.Add(s.ctx, &types.MsgAddRequest{Sender: "cosmos1test", Add: 42}) s.Require().NoError(err) events := s.ctx.EventManager().Events() s.Require().NotEmpty(events) found := false for _, event := range events { if event.Type == "count_increased" { found = true } } s.Require().True(found, "count_increased event not found") } ``` Keeper unit tests are fast, deterministic, and surgical. They are the right level for testing business logic, error conditions, edge cases, and event emission. ### Integration tests Integration tests verify behavior across the full application stack. They start a real in-memory network with one or more validators, wait for blocks to be produced, broadcast actual signed transactions via gRPC, and query the resulting state. These tests exercise the AnteHandler, message routing, block execution, and state commitment together. The counter module integration tests live in `tests/counter_test.go`. The test suite uses `testutil/network` from the Cosmos SDK to spin up a full in-memory chain: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type E2ETestSuite struct { suite.Suite cfg network.Config network *network.Network conn *grpc.ClientConn } func (s *E2ETestSuite) SetupSuite() { s.T().Log("setting up e2e test suite") var err error s.cfg = network.DefaultConfig(NewTestNetworkFixture) s.cfg.NumValidators = 1 // Customize counter genesis to set initial count and permissive params genesisState := s.cfg.GenesisState counterGenesis := countertypes.GenesisState{ Count: 0, Params: countertypes.Params{ MaxAddValue: 1000, AddCost: nil, }, } counterGenesisBz, err := s.cfg.Codec.MarshalJSON(&counterGenesis) s.Require().NoError(err) genesisState[countertypes.ModuleName] = counterGenesisBz s.cfg.GenesisState = genesisState s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) s.Require().NoError(err) _, err = s.network.WaitForHeight(2) s.Require().NoError(err) val0 := s.network.Validators[0] s.conn, err = grpc.NewClient( val0.AppConfig.GRPC.Address, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(s.cfg.InterfaceRegistry).GRPCCodec())), ) s.Require().NoError(err) } ``` `NewTestNetworkFixture` (in `tests/test_helpers.go`) constructs the `ExampleApp` with `dbm.NewMemDB()` and returns a `network.TestFixture` that configures the in-memory validator. This lets the SDK's network test helper start a real application with real consensus. A test that exercises the full transaction path: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (s *E2ETestSuite) TestAddCounter() { val := s.network.Validators[0] initialCount := s.getCurrentCount() txBuilder := s.mkCounterAddTx(val, 42) txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) s.Require().NoError(err) txClient := txtypes.NewServiceClient(s.conn) grpcRes, err := txClient.BroadcastTx( context.Background(), &txtypes.BroadcastTxRequest{ Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, TxBytes: txBytes, }, ) s.Require().NoError(err) s.Require().Equal(uint32(0), grpcRes.TxResponse.Code, "tx failed: %s", grpcRes.TxResponse.RawLog) s.Require().NoError(s.network.WaitForNextBlock()) finalCount := s.getCurrentCount() s.Require().Equal(initialCount+42, finalCount) } ``` Integration tests are slower than keeper unit tests because they start a real consensus engine and wait for blocks. They exist to catch failures at the boundaries: AnteHandler rejections, routing errors, genesis state mismatches, and cross-module interactions that only manifest when the full stack is running. ### Simulation tests Simulation tests are property-based tests. Instead of testing specific inputs, they generate large volumes of random operations and verify that the application's invariants hold throughout. They catch bugs that deterministic test cases miss: unexpected ordering effects, state corruption under high load, and invariant violations that only appear after many sequential operations. The Cosmos SDK simulation framework drives this through [`simsx`](#simsx-and-simd). The counter module defines a message factory that generates random `MsgAddRequest` messages: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/simulation/msg_factory.go func MsgAddFactory() simsx.SimMsgFactoryFn[*types.MsgAddRequest] { return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgAddRequest) { sender := testData.AnyAccount(reporter) if reporter.IsSkipped() { return nil, nil } r := testData.Rand() addAmount := uint64(r.Intn(100) + 1) msg := &types.MsgAddRequest{ Sender: sender.AddressBech32, Add: addAmount, } return []simsx.SimAccount{sender}, msg } } ``` The simulation runner selects a random account and a random add amount within valid bounds, then executes the message against the live application. This runs thousands of times across a simulated block sequence. The top-level simulation test in `sim_test.go` wires everything together: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} //go:build sims func TestFullAppSimulation(t *testing.T) { simsx.Run(t, NewExampleApp, setupStateFactory) } func setupStateFactory(app *ExampleApp) simsx.SimStateFactory { return simsx.SimStateFactory{ Codec: app.AppCodec(), AppStateFn: simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()), BlockedAddr: BlockedAddresses(), AccountSource: app.AccountKeeper, BalanceSource: app.BankKeeper, } } ``` The `//go:build sims` build tag means simulation tests are excluded from regular `go test` runs and only execute when explicitly requested with `-tags sims`. This keeps CI fast. The simulation manager is initialized in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} overrideModules := map[string]module.AppModuleSimulation{ authtypes.ModuleName: auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil), } app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) app.sm.RegisterStoreDecoders() ``` `NewSimulationManagerFromAppModules` collects simulation support from all modules that implement `AppModuleSimulation`. `RegisterStoreDecoders` registers human-readable decoders for each module's store entries, used when the simulation framework logs state for debugging. ## Test utilities ### testutil The [`testutil`](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/testutil) package provides helpers for constructing in-memory contexts for unit tests: * `testutil.DefaultContextWithDB` creates a real `sdk.Context` backed by an in-memory KV store. Keeper unit tests use this to get a realistic execution context without starting a full node. * `moduletestutil.MakeTestEncodingConfig` returns a codec with standard interface registration, suitable for keeper tests. * `baseapp.NewQueryServerTestHelper` creates a `QueryServiceTestHelper` that implements both the gRPC Server and ClientConn interfaces, allowing keeper tests to register query services and invoke them directly without a network connection. ### testify suite The SDK's test files use the [`testify/suite`](https://pkg.go.dev/github.com/stretchr/testify/suite) package. A `suite.Suite` groups test setup, teardown, and test methods into a single struct. `SetupTest` runs before each test method; `SetupSuite` runs once before all tests in the suite. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func TestKeeperTestSuite(t *testing.T) { suite.Run(t, new(KeeperTestSuite)) } ``` `suite.Run` discovers methods on the struct whose names start with `Test` and runs them as individual test cases. `s.Require()` returns assertion helpers that stop the test immediately on failure, while `s.Assert()` continues after a failure. ## simsx and simd For a full guide on configuring and running simulations, see the [Module Simulation](/sdk/latest/guides/testing/simulator) page. [`simsx`](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/testutil/simsx) is the simulation execution framework. It provides: * `SimMsgFactoryFn`: a function type that implements the `SimMsgFactoryX` interface for message factories. Each factory selects random accounts and parameters, constructs a message, and returns it for execution. * `ChainDataSource`: provides access to random accounts, balances, and other chain data during message construction. * `SimulationReporter`: allows a factory to signal that it should be skipped (for example, if no suitable account exists). * `simsx.Run`: the top-level entry point that drives a full simulation run against the application. [`simd`](/sdk/latest/node/prerequisites) is the reference simulation binary provided by the Cosmos SDK. It is a fully configured simapp (`simapp`) compiled as a standalone binary, used to run simulations against the SDK's own module set without setting up a custom chain. For a custom chain like the example app, you use your own binary with the `sims` build tag. To learn how to run an example chain, visit the [simd node tutorial](/sdk/latest/node/run-node). To run simulations against the example app: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go test -tags sims -run TestFullAppSimulation ./... ``` To add simulation support to your own module — implementing `AppModuleSimulation`, writing message factories, and wiring the `SimulationManager` — see [Module Simulation](/sdk/latest/guides/testing/simulator). ## Telemetry The counter module uses OpenTelemetry to emit metrics from keeper operations: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ( meter = otel.Meter("github.com/cosmos/example/x/counter") countMetric metric.Int64Counter ) ``` The SDK provides a telemetry package built around OpenTelemetry, with legacy support for go-metrics. Modules can emit counters, gauges, and histograms from keeper methods to expose runtime behavior for monitoring. See [Telemetry](/sdk/latest/guides/testing/telemetry) for full details. # Transactions, Messages, and Queries Source: https://docs.cosmos.network/sdk/latest/learn/concepts/transactions In the previous section, you learned that [accounts](/sdk/latest/learn/concepts/accounts) authorize activity on a chain using digital signatures and sequence numbers. Accounts provide identity and permission, but transactions are the actual mechanism that authorizes and executes logic on the chain. ## Interacting with a chain A Cosmos SDK blockchain is a deterministic state machine. Its state changes only when transactions are executed and committed in blocks. Users and applications interact with the blockchain in two fundamental ways: * **Transactions** modify state and are included in blocks. When a user wants to **change** something (transfer tokens, delegate stake, submit a governance proposal), they submit a transaction. * **Queries** read state and are not included in blocks. When a user wants to **inspect** something (check a balance, view delegations, read proposal details), they perform a query. Only transactions affect consensus state. ## Transactions A **transaction** is a signed container that carries one or more actions to be executed on the blockchain. A transaction includes: * Messages: one or more actions you want to execute (send tokens, delegate stake, vote on a proposal) * Signatures: cryptographic proof that you authorize these actions * Sequence number: prevents someone from resubmitting your transaction (replay protection) * Gas limit: the maximum computational resources you're willing to spend * Fees: what you pay for the transaction to be processed The transaction itself does not define business logic. Instead, it packages intent (messages) to change state, proves authorization (signatures), and specifies execution limits (gas and fees). You can think of a transaction as an envelope you send to the blockchain, with a message inside containing instructions, a signature to prove authenticity, and a stamp to pay for postage. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Transaction ├── Message 1 ├── Message 2 ├── ... ├── Signature(s) ├── Sequence ├── Gas limit └── Fees ``` In the Cosmos SDK, account metadata and transaction authorization are handled by the `x/auth` module. Transaction construction and encoding are configured through the SDK's transaction system (commonly via `x/auth/tx`). ## Messages A **message** (`sdk.Msg`) is the actual instruction inside a transaction. Each message is defined by a specific module and represents a single action. Messages are located in that module's `types` package (like `x/bank/types` or `x/staking/types`). Modules define which messages they support and the rules for executing them. While the transaction provides the envelope with signatures and fees, the message defines the specific action to execute. Examples include `MsgSend` (transfer tokens), `MsgDelegate` (delegate stake), and `MsgVote` (vote on proposals). If a transaction contains multiple messages, they execute in order. See [Message execution and atomicity](#message-execution-and-atomicity) below for details. ### How messages are defined Messages in the Cosmos SDK are defined in each module's [`tx.proto` file](/sdk/latest/tutorials/example/03-build-a-module#txproto) using [Protocol Buffers (protobuf)](/sdk/latest/learn/concepts/encoding), which provides deterministic serialization, backward compatibility, and cross-language support. Each message is defined in a `.proto` file that specifies its fields, data types, and unique identifiers. From this schema, code is generated that allows the message to be constructed, serialized, and validated. Here's an example of a transaction in JSON format: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "body": { "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1...", "to_address": "cosmos1...", "amount": [{"denom": "uatom", "amount": "1000000"}] } ], "memo": "", "timeout_height": "0", "extension_options": [], "non_critical_extension_options": [] }, "auth_info": { "signer_infos": [ { "public_key": { "@type": "/cosmos.crypto.secp256k1.PubKey", "key": "A..." }, "mode_info": {"single": {"mode": "SIGN_MODE_DIRECT"}}, "sequence": "0" } ], "fee": { "amount": [{"denom": "uatom", "amount": "500"}], "gas_limit": "200000", "payer": "", "granter": "" } }, "signatures": ["MEUCIQDx..."] } ``` This transaction transfers 1 ATOM (1,000,000 uatom) from one account to another. You can see the message in the `body.messages` array, the sender's public key and sequence in `auth_info.signer_infos`, the fee and gas limit in `auth_info.fee`, and the cryptographic signature in the `signatures` array. When broadcast, this JSON is serialized into bytes using protobuf, ensuring every validator interprets the transaction identically. ### Message execution and atomicity When a transaction contains multiple messages, they are executed **in the order they appear** in the transaction. For example, a transaction might: 1. Send tokens to another account. 2. Delegate those tokens to a validator. If the order were reversed, the delegation could fail due to insufficient balance. At execution time, messages inside a transaction are applied sequentially. The transaction succeeds only if all messages execute successfully. Conceptually: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Transaction ├── Msg 1 → execute ├── Msg 2 → execute ├── Msg 3 → execute ``` If any message fails, the transaction returns an error and none of the message execution writes from that transaction are committed. Message execution inside a transaction is atomic: all messages commit or none do. The [transaction lifecycle](/sdk/latest/learn/concepts/lifecycle) page covers this execution pipeline in more detail. In v0.53, transactions support an optional **unordered** mode. When `unordered=true`, the normal per-signer sequence check is bypassed and replay protection is handled through `timeout_timestamp` plus unordered nonce tracking in `x/auth`. This enables fire-and-forget and concurrent transaction submission without coordinating sequence numbers. Unordered transactions must have a `timeout_timestamp` set and a sequence of `0`. For how clients build and submit them, see [Generating an Unordered Transaction](/sdk/latest/node/txs#generating-an-unordered-transaction). ## Blocks and transactions A blockchain can be understood as a sequence of blocks. Each block contains an ordered list of transactions. When a new block is committed: 1. Each transaction in the block is applied to the current state. 2. Each transaction executes its messages in order. 3. Modules update their portion of state. 4. The resulting state becomes the starting point for the next block. Conceptually: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} State₀ ↓ apply Block 1 (Tx₁, Tx₂, Tx₃) State₁ ↓ apply Block 2 (Tx₄, Tx₅) State₂ ↓ apply Block 3 (...) State₃ ``` In this way, the blockchain is a deterministic sequence of state transitions driven entirely by transactions. Blocks group transactions, transactions drive execution, and execution updates state. ## Queries A **query** retrieves data from the blockchain's state without modifying it. Queries are read-only. They don't require signatures, aren't included in blocks, and don't affect consensus state. Modules define query services using protobuf in a [`query.proto` file](/sdk/latest/tutorials/example/03-build-a-module#queryproto), exposed over gRPC and REST. For example: * Query an account's balance (the `x/bank` module) * Query staking delegations (the `x/staking` module) * Query governance proposal details (the `x/gov` module) ## Transaction and query flow
Transaction Flow Query Flow
User
      ↓ signs
      Transaction
      ↓ contains
      Message(s)
      ↓ handled by
      Module(s)
      ↓ update
      State
User
      ↓
      Query
      ↓
      Module
      ↓
      State (read-only)
Transactions modify the blockchain. Messages define what modifications occur. Modules execute those modifications in order. Queries allow anyone to observe the resulting state. To see this flow in action with a working chain, see the [Quickstart](/sdk/latest/tutorials/example/02-quickstart) tutorial. The next section, [Transaction Lifecycle](/sdk/latest/learn/concepts/lifecycle), follows a transaction from broadcast through validation, block inclusion, execution, and state commitment to show how these components work together in practice. # Blockchain Basics Source: https://docs.cosmos.network/sdk/latest/learn/intro/blockchain-basics Learn the fundamentals of blockchains, state machines, and how Cosmos SDK applications work. ## What Is a Blockchain? A blockchain is a decentralized ledger that multiple independent computers (called nodes) maintain together. Instead of relying on a single authority to track transactions and maintain state, blockchain networks distribute this responsibility across many nodes. Each node keeps its own copy of the ledger and works with other nodes to agree on what transactions are valid and in what order they should be applied. You can think of a blockchain or decentralized ledger as a shared spreadsheet that dozens of people maintain independently. Everyone has their own copy, and they all follow the same rules for updating it. When someone wants to make a change, the group agrees on whether that change is valid and what order it should happen in. If everyone follows the rules correctly, all copies end up identical. If someone tries to modify their copy without following the consensus rules, the other nodes will reject their version because it doesn't match what the network agreed upon. This makes blockchains resistant to tampering: you'd need to control a majority of the network to force through an invalid change. ## Why Blockchains? Traditional digital systems usually rely on a central authority to maintain accurate records. A bank, for example, maintains the definitive record of account balances. Users trust the bank to process transactions correctly and prevent problems like spending the same money twice (also known as the double-spend problem). Blockchains solve a more difficult challenge: maintaining accurate, trustworthy records without relying on a singular, central authority. In a decentralized network, no single entity has the final say. Instead, independent nodes must agree on the state of the ledger even though they don't trust each other. This requires solving several problems simultaneously: * [Agreement through consensus](#consensus): How do nodes agree on which transactions are included and in what order they’re applied? * [Security through tamper-evident cryptography](#how-blocks-are-linked): How can the network prevent malicious nodes from creating fraudulent transactions or rewriting history? * [Consistency through deterministic execution](#why-“deterministic”): How do all nodes maintain identical copies of the ledger despite network delays and potential failures? Blockchains address these challenges through cryptographic linking, deterministic execution, and decentralized consensus mechanisms. The result is a system where no single party controls the ledger, yet all participants can verify its accuracy and trust its contents. ## State Machines: The Foundation of Blockchains At their core, blockchains are **replicated, deterministic state machines**. ### What Is a State Machine? In computer science, **State** represents all the current data in a system at a specific point in time. For example, in a bank application, the state includes all account balances. In the context of a blockchain or decentralized ledger, the state includes all account balances, smart contract data, and other information the chain tracks. A **state machine** is a system that moves from one state to another by applying transactions. Each transaction describes an action that should change the state. Here's a simple example of a state machine using a bank account: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Current State: User A's balance: $100 User B's balance: $50 Transaction: User A sends $30 to User B New State: User A's balance: $70 User B's balance: $80 ``` The state machine takes the current state (User A has \$100, User B has \$50), applies a transaction (transfer \$30), and produces a new state (User A has \$70, User B has \$80). ### Why "Deterministic"? **Deterministic** means that the same transaction applied to the same state will always produce the same result. This property is critical for blockchains and decentralized ledgers. Using the bank example: if User A starts with \$100 and sends User B \$30, their balance will always become \$70. It doesn't matter who processes this transaction, when they process it, or how many times they recalculate it from the initial state: the result will always be the same. In a blockchain, determinism ensures that all nodes independently arrive at the same final state. If the logic weren't deterministic, different nodes would end up with different versions of the ledger, and the network would break down. In practice, blockchain applications must avoid sources of non-determinism such as local time, floating-point math, or external network calls. ### Why "Replicated"? **Replicated** refers to the fact that many independent nodes each run their own copy of the same state machine. Instead of one central server maintaining the state, multiple independent nodes each maintain their own complete copy. When a new block is added to the blockchain, every node: 1. Receives the block with its ordered list of transactions 2. Independently executes each transaction through their local state machine 3. Arrives at the same new state (thanks to determinism). This replication is what makes blockchains decentralized and resilient. If any single node fails, goes offline, or acts maliciously, the network continues operating as long as a majority of the network's consensus power still have complete, accurate copies of the state. The network doesn't depend on any one node being available or trustworthy. ## How Blockchains Work With an understanding of state machines, the next step is to see how blockchains use them to maintain a shared ledger across many independent nodes. ### Nodes A **node** is a computer that participates in the blockchain network. Each node stores a complete copy of the blockchain's state, receives and validates new transactions, participates in consensus to agree on new blocks, and executes transactions to update its local state. Some nodes, called validators, participate directly in consensus by proposing and voting on blocks, while other nodes simply replicate and verify the chain. In public, permissionless blockchains, anyone can typically run a node, which makes the network decentralized: no single entity controls the ledger. ### Transactions A **transaction (tx)** is a request to change the blockchain's state. In Cosmos SDK blockchains, transactions contain one or more **messages** that represent the specific actions to be executed. These messages can represent many different actions: * Transferring tokens from one account to another * Creating or updating a smart contract * Staking tokens to become a validator * Voting on a governance proposal When a user creates a transaction, it gets broadcast to nodes in the network. Nodes verify that the transaction is valid (proper signature, sufficient balance, etc.) before accepting it into their mempool. ### Blocks Transactions are grouped together into **blocks** for efficiency. A block is a batch of transactions that the network processes together. Each block is cryptographically linked to the previous block, forming a **chain of blocks**. This chain structure creates a permanent, tamper-evident history: if someone tries to alter a past transaction, it would break the cryptographic link to all subsequent blocks, making the tampering obvious to the network. ### From Transactions to Blocks Rather than processing transactions one at a time, blockchains group them into **blocks** for efficiency. Here's how it works: 1. **Transaction pool (Mempool)**: Nodes collect valid transactions into a waiting area called the mempool 2. **Block proposal**: A designated node (called a validator or block proposer) selects transactions from the mempool and proposes them as the next block 3. **Consensus**: Nodes run a consensus algorithm to agree on which proposed block to accept and in what order 4. **Block commitment**: Once consensus is reached, the block becomes final and is added to the blockchain 5. **State transition**: Each node applies the transactions in the new block to their local state machine, updating their copy of the state ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Mempool (pending txs) ↓ Block B [Tx1, Tx2, Tx3, ...] ↓ Consensus ↓ Apply to State Machine ↓ New State ``` This process repeats for every block, creating a chain of blocks, or a "blockchain". ### Consensus **Consensus** is the mechanism by which nodes agree on a single, authoritative version of the blockchain despite operating independently. In step 3 above, nodes must reach consensus on which block to add next and in what order. Transaction ordering is critical. Consider two transactions: "User A sends 100 tokens to User B" and "User A sends 100 tokens to User C." If User A only has 100 tokens, the order matters—only the first transaction can succeed. Different nodes might receive these transactions in different orders, so consensus is used to establish a single, canonical ordering that all nodes follow. This prevents the double-spend problem and ensures that deterministic execution produces identical results on every node. Consensus algorithms ensure that: * All honest nodes agree on the same sequence of blocks * The network can continue operating even if some nodes are offline or malicious * Transactions are ordered consistently across all nodes Most Cosmos SDK blockchains use the CometBFT consensus engine, which implements a Byzantine Fault Tolerant (BFT) consensus algorithm. This means the network can reach agreement as long as more than two-thirds of the voting power comes from honest validators. The specifics of how consensus works are covered in the [Blockchain Architecture](/sdk/latest/learn/intro/sdk-app-architecture) section. It's important to note that consensus only determines the ordering and inclusion of transactions into blocks. Whether a transaction is valid is ultimately determined by the application’s state machine when the block is executed. ### How Blocks Are Linked Each block contains a **block header** with metadata about the block. Critically, every block header includes a cryptographic hash of the previous block's header. A **hash** is like a digital fingerprint: it takes data of any size and produces a unique, fixed-length string of characters. For example, hashing the text "Hello World" might produce something like "a591a6d4...". The key property is that even a tiny change to the input (like changing "Hello World" to "Hello World!") produces a completely different hash. Hash functions are one-way, which means you can't reverse a hash back to the original data. Hash functions are also collision-resistant: no two different inputs produce the same hash. Cosmos blockchains use [SHA-256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) which was created by the NSA as the hash function for block headers and other cryptographic operations to securely link blocks together. This provides **cryptographic security**: finding a different input that produces the same hash output is computationally infeasible, making it virtually impossible to tamper with block data without detection. Block headers also include Merkle roots that commit to the block’s transactions and state, allowing nodes and light clients to verify data efficiently. This hashing mechanism creates a tamper-evident chain. You can see this in action in the demo in the next section. ### Blockchain Demo: Immutability The demo below shows a blockchain with three blocks. You can see how each block is linked to the previous block by the hash in the block header. Try changing the data in a block to see how it changes the hash of that block and invalidates all subsequent blocks. You can add new blocks to the chain by clicking the "Add Block" button. This is a simplified demonstration. Actual Cosmos SDK blocks include additional security features like validator signatures, timestamps, consensus information, and Merkle roots for transaction verification. The cryptographic linking shown here is just one part of blockchain security. If someone tries to alter a transaction in Block 1, it would change the contents of Block 1, which would change Block 1's hash. But Block 2 stores Block 1's original hash in its header. The mismatch would be immediately obvious, and Block 2 would be pointing to a hash that no longer matches Block 1. This broken link would invalidate Block 2 and all subsequent blocks, making the tampering evident to the entire network. This is why blockchains are resistant to any changes: you’d need to control a supermajority of the network’s consensus power to rewrite history. This cryptographic linking is what makes blockchain history **immutable**, or unchangeable. The further back in history a block is, the more subsequent blocks depend on it remaining unchanged, making older blocks increasingly difficult to tamper with. In BFT-based systems like CometBFT, blocks have instant finality: once a block is committed, it cannot be reverted without violating consensus assumptions. ## What's Next? Now that you understand blockchain fundamentals (state machines, deterministic execution, replication, and cryptographic linking), the next step is to learn how Cosmos SDK actually implements these concepts. In [Blockchain Architecture](/sdk/latest/learn/intro/sdk-app-architecture), you'll explore: * How CometBFT handles consensus and networking to maintain the replicated state machine * The Application Blockchain Interface (ABCI) that connects consensus to application logic * How the Cosmos SDK implements the state machine layer * The complete architecture of a Cosmos blockchain application # The Cosmos Stack Source: https://docs.cosmos.network/sdk/latest/learn/intro/cosmos-stack Understanding the modular architecture of the Cosmos blockchain stack Performant, customizable, and EVM-compatible, the Cosmos stack offers builders full control of their blockchain infrastructure and implementation. Its stable and secure open-source codebase enables blockchains to achieve high throughput of 10,000+ TPS, tuned, and fast finality for instant transaction settlement. Development on the Cosmos stack began in 2016, and today, hundreds of public and private blockchains use the Cosmos stack in production. The stack is modular: leverage pre-built components or integrate custom features for your specific use case, from consensus mechanisms to governance and compliance. The components of the stack work together to create a complete blockchain network solution that is secure, performant, scalable, and endlessly customizable. Cosmos Stack Architecture At its core, the Cosmos stack is composed of several interoperable layers: the [Cosmos SDK](/sdk/latest/learn/intro/overview) for application logic, [CometBFT](/cometbft/latest/docs/README) for consensus and networking, [Cosmos EVM](/evm/v0.5.0/documentation/overview) for Ethereum compatibility, and [the Inter-Blockchain Communication Protocol (IBC)](/ibc) for trust-minimized cross-chain communication. For teams running permissioned or production networks, [Cosmos Enterprise modules](/sdk/latest/enterprise/overview) add hardened, licensed Cosmos SDK modules for controlled participation and collective on-chain authorization. Together, these components form a flexible, battle-tested stack for developing performant, reliable, interoperable, and secure blockchains ## Cosmos SDK: Business Logic Layer The Cosmos SDK is the business logic layer of the Cosmos stack. It provides a customizable base layer for building blockchains and digital ledgers, made up of interoperable modules that work together to define how a blockchain behaves, from accounts and transactions to tokenization, compliance, and custom application logic. Builders can compose [pre-built modules](/sdk/latest/modules/modules) and [develop bespoke ones](/sdk/latest/guides/module-design/module-design-considerations) to embed their unique business logic directly into the foundation of the chain, rather than deploying it as isolated smart contracts. This approach enables a level of customization, interoperability, and performance that traditional smart contract platforms and Layer-2 blockchains cannot offer. Developers gain access to block lifecycle hooks ([BeginBlocker and EndBlocker](/sdk/latest/learn/intro/sdk-app-architecture#block-lifecycle-hooks)), fine-grained state separation, and scoped permissions through a security-first [Object Capability Model](/sdk/latest/guides/module-design/ocap). Native execution alongside [CometBFT](/cometbft/latest/docs/README) consensus unlocks significantly higher throughput and deterministic behavior, while upgrade tools like [Cosmovisor](/sdk/latest/guides/upgrades/cosmovisor) make chains easy to maintain and evolve over time. [Explore the Cosmos SDK →](/sdk/latest/learn) ## Cosmos Enterprise modules Cosmos Enterprise modules are hardened Cosmos SDK modules for permissioned and production networks, including permissioned consensus (PoA) and multi-sig (Groups). The module source is published under the Source Available Evaluation License, and production use requires an Enterprise License from Cosmos Labs. [Learn about Cosmos Enterprise modules →](/sdk/latest/enterprise/overview) ## Cosmos EVM: Ethereum Compatibility Layer Cosmos EVM enables plug-and-play Ethereum Virtual Machine compatibility for Cosmos SDK–based chains. It allows developers to deploy Solidity smart contracts, use familiar Ethereum tooling, and interact with native Cosmos modules (including IBC) through precompiles and extensions. It provides software engineers with functionality beyond standard EVM for new use cases and workflows by allowing them to run existing Ethereum contracts without modification while also extending the EVM with new capabilities at the chain level. [Learn about Cosmos EVM →](/evm) ## IBC Protocol: Interoperability Layer The Inter-Blockchain Communication (IBC) protocol is the interoperability layer of the Cosmos stack, enabling blockchains to securely transfer tokens, messages, and arbitrary data. Blockchains communicate over IBC with self-hosted infrastructure through point-to-point connections. It connects them into an interoperable network through trust-minimized communication with configurable permissioning while maintaining secure, independent execution. [Explore IBC Documentation →](/ibc) ## CometBFT: Highly-Performant Consensus Layer CometBFT is the consensus layer of the Cosmos stack and one of the most widely adopted, battle-tested consensus engines for building blockchains and decentralized ledger networks. It is a Byzantine Fault Tolerant (BFT) middleware that takes a deterministic state transition machine (which can be written in any programming language) and securely replicates it across a distributed set of nodes. By separating consensus from application logic, CometBFT allows developers to build custom blockchains without implementing their own networking or consensus protocols. Responsible for proposing blocks, ordering transactions, and finalizing state transitions, CometBFT ensures that all nodes reach agreement on the canonical state of the chain. Highly performant and deterministic, it provides fast finality and can achieve throughput of up to 10,000 transactions per second (TPS), making it well-suited for high-performance, application-specific blockchains. [Learn about CometBFT →](/cometbft/latest/docs/README) # What is the Cosmos SDK Source: https://docs.cosmos.network/sdk/latest/learn/intro/overview The [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) is a secure, open-source framework for building application-specific blockchains and digital ledgers. It gives engineers full control over access control, security, business logic, and governance, while providing a robust set of pre-built modules covering common blockchain functionality. Organizations can use the Cosmos SDK to easily build and maintain any type of blockchain network, including private permissioned networks, public networks, and consortia. Cosmos SDK blockchains are natively interoperable through [IBC](/ibc). As the business logic layer of the Cosmos stack, the Cosmos SDK provides a fully customizable foundation for building blockchains from customizable modules that define how a chain behaves, from accounts and transactions to tokenization, compliance, and custom application logic. Development on the Cosmos SDK has been continuous since 2016. Today, companies in banking and finance, SaaS, AI, and other industries use the Cosmos SDK’s open-source codebase with proven consensus via [CometBFT](/cometbft) and native interoperability through IBC for business use cases like interbank networks, asset tokenization, and business automation. It offers fast performance of 10,000+ transactions per second, strong security, and resiliency in production. ## Purpose of the Cosmos SDK At its core, the Cosmos SDK is designed to give developers full flexibility across the entire blockchain stack. Business logic can run natively at the protocol level, be exposed through modular components, or be extended with optional [virtual machine layers](/evm), depending on the needs of the application. The Cosmos SDK is interoperable by design. Chains built with the SDK can communicate with other blockchains via [IBC](/ibc) while maintaining independent execution and security. ## Modularity of the Cosmos SDK ## Interoperable Modules Blockchains built with the Cosmos SDK are composed of interoperable modules, each responsible for a specific function, such as [accounts](/sdk/latest/learn/concepts/accounts), transactions, governance, tokenization, or compliance logic. These modules are built on top of the [SDK's base application framework](/sdk/latest/learn/intro/sdk-app-architecture), which provides the shared execution environment that allows modules to operate together as a cohesive blockchain. The Cosmos SDK offers engineers a robust set of [predefined modules](/sdk/latest/modules/modules) that cover standard blockchain features, such as consensus, accounts, transfers, governance permissioning, fee distribution, and more. In addition, engineers can [build their own modules](/sdk/latest/guides/module-design/module-design-considerations) tailored to their application's requirements. By composing and extending modules, developers can build blockchains and ledgers that are optimized for performance, security, and long-term maintainability. The SDK's base layer handles core concerns such as [message routing, module lifecycle orchestration, and interaction with the underlying consensus engine](/sdk/latest/learn/intro/sdk-app-architecture). It defines clear boundaries between modules by isolating their state into [independent stores](/sdk/latest/learn/intro/sdk-app-architecture), while providing secure, well-defined interfaces for [cross-module communication](/sdk/latest/learn/concepts/modules#keeper). This structure allows modules to operate and evolve independently while preserving the overall system. ## Cosmos SDK and the modularity of the Cosmos stack While SDK modules define how application logic is composed within a chain, the Cosmos SDK also supports modularity at the component level. Consensus, networking, and data availability are provided by external components like CometBFT. Execution logic is defined by SDK modules. We recommend building Cosmos SDK-based blockchains using [CometBFT](/cometbft/latest/docs/README) for consensus because it offers best-in-class performance and out-of-the-box interoperability between components. Alternatively, engineers can pair the Cosmos SDK with other consensus engines like modular execution and settlement architectures depending on their performance and security requirements. This flexibility allows ledger chains to evolve alongside their application and operational needs. ## Application-Specific Blockchains A common development paradigm in blockchain ecosystems is the use of general-purpose virtual machine chains, where applications are deployed as smart contracts on top of a shared execution environment. While this approach is well suited for some use cases, it imposes constraints around performance, customization, and protocol-level control, which impact an organization’s infrastructure costs and security and compliance profile. Application-specific blockchains offer a different model. An application-specific blockchain is a blockchain or digital ledger that runs custom business logic at the protocol or chain level to accomplish a particular business use case. With the Cosmos SDK, developers can tailor execution logic, fee models, governance rules, and state transitions directly at the protocol level, enabling greater flexibility and performance. ## Virtual Machine Layers The Cosmos SDK allows for the use of smart contracts. Developers can add a virtual machine layer to add smart contract support. The [Cosmos EVM](/evm) supports Ethereum-compatible smart contracts and tooling. Engineers can also choose other VMs. ## Security via the Object-Capability Model The Cosmos SDK uses a capabilities-based security model to enforce strict boundaries between modules. Rather than granting broad access to shared state, modules are given only the specific capabilities they require, making it easier to reason about authority, permissions, and potential attack surfaces. This design improves the security and composability of complex blockchain applications. For a deeper dive into this model, see the [Object-Capability Model](/sdk/latest/guides/module-design/ocap). ## Cosmos Enterprise modules Cosmos Enterprise modules are hardened Cosmos SDK modules for permissioned and production networks, including permissioned consensus (PoA) and multi-sig (Groups). The module source is published under the Source Available Evaluation License, and production use requires an Enterprise License from Cosmos Labs. [Learn about Cosmos Enterprise modules →](/sdk/latest/enterprise/overview) ## Why Build with the Cosmos SDK The Cosmos SDK is one of the most mature and widely adopted frameworks for building custom, modular blockchains. Key advantages include: * **Proven in production**: 200+ blockchains use the Cosmos SDK in production today for use cases such as interbank networks, regulated lending, and banking asset tokenization. * **Strong security foundations**: Capabilities-based security informed by years of production experience. * **Protocol-level customization**: Define application logic, governance, and economic models directly in the blockchain, not just in smart contracts. * **Built-in interoperability**: Native interoperability through [IBC](/ibc) and extensible chain-level integration. * **Flexible execution models**: Combine native modules with optional VM layers such as [Cosmos EVM](/evm/v0.5.0/documentation/overview). ## Getting Started with the Cosmos SDK * Learn about the [architecture of a Cosmos SDK application](/sdk/latest/learn/intro/sdk-app-architecture) * Run a blockchain in under 5 minutes with the [Cosmos SDK Node Tutorial](/sdk/latest/tutorials) # Cosmos Architecture Source: https://docs.cosmos.network/sdk/latest/learn/intro/sdk-app-architecture How Cosmos SDK implements blockchains through separation of consensus, interface, and application logic. In [Blockchain Basics](/sdk/latest/learn/intro/blockchain-basics), you learned that a blockchain is a replicated, deterministic state machine maintained by independent nodes through consensus. The Cosmos SDK implements this model through a clean separation of concerns: CometBFT handles consensus, networking, and block production; ABCI (Application Blockchain Interface) defines the boundary between consensus and application; and the Cosmos SDK implements the application logic and state machine. This page explains the high-level architecture of Cosmos SDK blockchains: how components interact, what each layer is responsible for, and why this separation matters. ## Cosmos Application Architecture A Cosmos blockchain consists of 2 distinct layers: CometBFT for consensus and block production, and the Cosmos SDK layer which contains the application logic, modules, and transaction execution logic. Between these layers is the ABCI (Application Blockchain Interface), which is the interface that connects them. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} +---------------------------------------+ | | | Cosmos SDK Application | <- State machine | (Modules, Keepers, State) | - Transaction execution | | - Business logic +------------------+--------------------+ | ABCI <- Application Blockchain Interface | +------------------+--------------------+ | | | CometBFT | <- Consensus engine | (Consensus, Networking, | - Block production | Block Replication) | - p2p networking | | +---------------------------------------+ ``` * **[CometBFT](#cometbft)**: the consensus engine that handles networking, block production, and Byzantine Fault Tolerant consensus. * **[ABCI](#abci-application-blockchain-interface)**: the interface boundary that defines when and how CometBFT invokes the application, enforcing separation between consensus and execution. The ABCI is implemented in a Cosmos application via [`BaseApp`](#baseapp-and-appgo). * **[Cosmos SDK](#cosmos-sdk-application)**: the framework for building the blockchain application (often simply called "the application"), which is a state machine assembled from composable [modules](/sdk/latest/learn/concepts/modules). Each module owns a specific domain of logic, state, and transactions; together, modules define the chain's business logic, execute transactions, and produce cryptographic state commitments. This layer is defined in the [`app.go`](#baseapp-and-appgo) file of a Cosmos application. ### Separating Consensus and Application Logic Separating consensus from application logic provides several benefits. Security improves because isolating consensus logic prevents application bugs from affecting block production or network stability. Flexibility increases as developers can build application-specific blockchains without reimplementing consensus. The modular design allows consensus and application layers to evolve independently, and reusability means one consensus engine (CometBFT) can power many different blockchains without modification. ## Nodes and Daemons A blockchain is made up of nodes, or participants in the blockchain network. Each node runs a daemon process that includes both a CometBFT instance for consensus and networking, and a Cosmos SDK application for the state machine and execution logic. The daemon participates in networking, reaches consensus with other nodes, and executes transactions to update the application state. Nodes can operate in different roles. There are two main types of nodes, **validators** and **full nodes**: * **Validators** are nodes that participate in consensus by proposing and voting on blocks, with consensus power staked or attributed to them making them responsible for block production. They are in charge of validating blocks before voting, ensuring that only valid blocks are finalized. * **Full nodes** replicate and verify blocks without participating in consensus voting, maintaining complete state and answering queries but not voting on proposals. Although anyone can typically run a full node, becoming a validator depends on the chain's staking, governance, or permissioning rules. In a proof-of-stake blockchain, validator candidates are selected based on their stake, and they must meet certain criteria to be elected as validators. In a proof-of-authority blockchain, validators are selected based on permissioned criteria. To learn how to run a node, visit the [Cosmos Node Tutorial](/sdk/latest/tutorials). ## CometBFT [CometBFT](/cometbft/latest/docs/README) is a Byzantine Fault Tolerant consensus engine that provides fast, deterministic finality. It's used by Cosmos SDK blockchains to replicate the state machine across a decentralized network. ### Core Responsibilities CometBFT handles several core responsibilities: * **Peer-to-peer networking**: CometBFT manages node discovery, establishes and maintains connections with other validators and full nodes, and implements gossip protocols for propagating information across the network. * **Transaction propagation and mempool management**: When users submit transactions to a node, CometBFT gossips them to other nodes. Each node maintains a **mempool** (memory pool), which is a waiting area for valid transactions that have not yet been included in a block. The mempool holds transactions temporarily until a validator includes them in a block proposal. * **Block proposal and transaction ordering**: CometBFT uses deterministic proposer selection to choose which validator will propose the next block. The proposer selects transactions from the mempool, orders them, and packages them into a block proposal. * **Byzantine Fault Tolerant consensus**: CometBFT coordinates voting rounds where validators vote on block proposals. When a proposal receives votes from more than two-thirds of voting power, consensus is reached and the block is finalized. Validators cryptographically sign their votes, ensuring against double-voting and other forms of malicious behavior. * **Block replication**: Once finalized, CometBFT ensures the block is replicated across all nodes in the network, maintaining a consistent, ordered history of all committed blocks. To learn more about how CometbFT works, visit the [CometBFT Documentation](/cometbft/latest/docs/README). ### Byzantine Fault Tolerance and Finality Byzantine Fault Tolerance (BFT) is a system's ability to reach consensus even when some participants crash, send conflicting messages, or act maliciously. The name comes from the [Byzantine Generals Problem](https://lamport.azurewebsites.net/pubs/byz.pdf). CometBFT tolerates up to one-third of voting power being faulty while still producing valid blocks, and maintains liveness as long as more than two-thirds of validators are online. Unlike proof-of-work blockchains, CometBFT provides instant finality: once a block is committed, it cannot be reverted. ### Orchestrating Block Production CometBFT drives the entire block production lifecycle. It determines when blocks are produced (maintaining consistent block times), which validator proposes each block (through deterministic proposer selection), and the order in which transactions are included in blocks. CometBFT coordinates the consensus process by managing the voting rounds where validators evaluate and vote on block proposals. To learn more about how consensus in CometBFT works, visit the [CometBFT Documentation](/cometbft/latest/docs/introduction/intro). ### Content Agnosticism CometBFT is agnostic to block content and application implementation. It treats transactions as opaque byte arrays, ensuring all nodes receive the same ordered sequence without interpreting what they mean. All application-specific logic lives in the SDK layer, which uses [Protocol Buffers](/sdk/latest/learn/concepts/encoding) to serialize structured messages into bytes CometBFT can carry. ## ABCI (Application Blockchain Interface) ABCI is a strict request/response interface between CometBFT and a Cosmos SDK blockchain application. It defines the block execution lifecycle and ensures a clean separation between consensus and application logic to ensure secure, reliable block production that cannot be compromised by application faults or bugs. The ABCI is unidirectional: all calls flow from CometBFT to the application, and the application cannot call into or control CometBFT. The Cosmos SDK application must respond deterministically to all ABCI calls, producing the same results given the same inputs. The ABCI itself is a stateless protocol containing no business logic. It only provides the interface definitions between the CometBFT consensus engine, which handles block production, and the Cosmos SDK application, which defines the business logic and state machine. ### Block Lifecycle Methods As the driver of block production, CometBFT controls when the Cosmos SDK application is invoked. It calls the application through ABCI at specific points during block production to validate transactions for the mempool (CheckTx), to construct or evaluate block proposals (PrepareProposal and ProcessProposal), to execute finalized blocks (FinalizeBlock), and to persist state (Commit). The SDK application responds to these calls but cannot initiate them. This means CometBFT, not the SDK application, determines the timing and cadence of block production and state transitions. This ABCI boundary also prevents application logic from influencing consensus. ```python theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} +---------------+ | +--------------------------+ | CometBFT | | | SDK Application | | (Consensus) | ABCI | (State Machine Logic) | +---------------+ | +--------------------------+ 1) CheckTx | Mempool validation ---------|-------> Validate tx (sigs, fees) | 2) PrepareProposal | Proposer builds block ------|--------> Construct block proposal | 3) ProcessProposal | Validators evaluate --------|--------> Validate proposal | 4) Consensus | BFT voting, finalizing block| (SDK not involved) | | 5) FinalizeBlock | Execute block --------------|--------> PreBlock hooks | BeginBlock hooks | Execute transactions | EndBlock hooks | Return AppHash 6) Commit | Persist state --------------|--------> Persist to disk |<-------- Return AppHash ``` 1. **CheckTx** validates transactions before adding them to the mempool. It checks that transactions are well-formed and economically viable (proper signature, sufficient fees) without making state changes. This protects the mempool against spam. 2. **PrepareProposal** is invoked when a validator constructs a new block proposal. This gives the application limited control over block construction, allowing it to reorder transactions or add application-specific data. 3. **ProcessProposal** is called when validators evaluate a block proposal from another validator. The application can validate the proposed block according to application-specific rules before voting to accept it. 4. **Consensus** occurs entirely within CometBFT. After validators evaluate the proposal (via ProcessProposal), they participate in BFT voting rounds. If the proposal receives votes from more than two-thirds of voting power, consensus is reached and the block is finalized. 5. **FinalizeBlock** is invoked after consensus on a block is reached. This is where state transitions occur, executing the entire block atomically. `BaseApp` invokes lifecycle hooks in this order: PreBlock hooks, BeginBlock hooks, transaction execution, and EndBlock hooks. The application returns the new AppHash (a cryptographic commitment to the state) and any validator set changes. 6. **Commit** is called after FinalizeBlock to persist the finalized state to the nodes' local disk and return the [AppHash](#state) that gets included in the next block header. **PreBlock** hooks were introduced in SDK v0.50, which run before BeginBlock. PreBlockers must be explicitly ordered using `SetOrderPreBlockers`. Some core modules (notably `x/auth`) require PreBlock execution; missing PreBlock wiring will cause runtime errors. CometBFT decides when blocks happen and what order transactions appear in, the Cosmos SDK decides whether those transactions are valid and how they change state, and the ABCI defines the interface between the two. For a more in-depth look at the ABCI, visit the [ABCI page](/sdk/latest/guides/abci/abci). ## Cosmos SDK Application A Cosmos SDK application is a deterministic state machine that defines a blockchain's behavior. It focuses entirely on defining what state the blockchain tracks, what transactions are valid, and how transactions change state. The applications defines transaction and message formats using Protocol Buffers for serialization, validates transactions by checking signatures and fees, executes message handlers to apply state transitions, maintains state across all modules, and produces the AppHash that cryptographically commits to the current state. ### Application Structure A typical Cosmos SDK application consists of: * **[`BaseApp`](#baseapp-and-app-go)**: boilerplate code that provides the ABCI implementation and execution framework for a chain to interact with CometBFT. * **[Modules](#modules-transactions-and-application-logic)**: building blocks of domain-specific logic such as transactions, custom business logic, and governance/permissioning. * **[State Multistore](#kv-stores-and-multistore)**: a collection of key-value stores that store the state of the application, isolated by module. * **[Keepers](#keepers)**: providing interfaces for accessing module state while enforcing access control. * **[app.go](#baseapp-and-app-go)**: serving as the composition root that wires everything together. For a complete overview of Cosmos SDK structure, visit the [Intro to SDK Structure](/sdk/latest/learn/concepts/sdk-structure) ### BaseApp and app.go `BaseApp` is the Cosmos SDK's standard implementation of the ABCI interface. It handles all ABCI method calls from CometBFT, routes messages to the appropriate [module handlers](#modules-transactions-and-application-logic), manages state versioning and caching, and enforces transaction execution semantics. Developers do not implement ABCI directly; instead, they extend `BaseApp` and register their modules, handlers, and execution logic with it. To learn more about `BaseApp`, visit the [`BaseApp` page](/sdk/latest/learn/concepts/baseapp). The `app.go` file is the composition root of a Cosmos SDK application. This is where a specific blockchain is assembled by creating the `BaseApp` instance, instantiating all module keepers with their dependencies, registering store keys for each module's state, wiring module [lifecycle hooks](#block-lifecycle-hooks), and configuring transaction processing. To learn more about `app.go`, visit the [`app.go` page](/sdk/latest/learn/concepts/app-go). Modules also define genesis state initialization and migration logic to support chain upgrades, allowing application state to evolve safely over time. ### Modules, Transactions, and Application Logic Modules are the building blocks of Cosmos SDK applications. Each module implements a specific domain of functionality: the bank module handles token transfers, the staking module manages validator delegation, the governance module implements on-chain proposals, and so on. Every module acts as its own mini state machine, processing transactions and updating state according to its own rules. Together, modules for the entire application form a single, cohesive state machine. A module provides its business logic through message handlers. Messages work like function calls that specify an operation (like "send 100 tokens to address X") with typed parameters. Users invoke module logic by submitting transactions that contain these messages. During execution, each message gets routed to its module's handler, which runs the business logic and updates state. In the Cosmos SDK, a **transaction** is a signed, serialized container that wraps one or more **messages**. Messages represent the actual operations to execute (like "send tokens" or "delegate stake"), while the transaction adds metadata like signatures, fees, and gas limit. Blocks contain transactions, and during block execution (FinalizeBlock), each transaction's messages are extracted and routed to the appropriate module handlers for execution. Transactions contain signatures from their creators authorizing the requested state change. Modules define message types using [Protocol Buffers (Protobuf)](/sdk/latest/learn/concepts/encoding), which provide type-safe, cross-language serialization. Since CometBFT treats transactions as raw bytes and [KV stores](#kv-stores-and-multistore) only accept byte arrays, Protobuf serializes structured messages and state data into bytes for transmission and storage. Modules also define state schemas (what data the module stores), state transitions (how messages modify state), queries (allowing clients to read module state), and optional lifecycle hooks for tasks that run at block boundaries. To learn more about the transactions and messages, visit the [Transactions page](/sdk/latest/learn/concepts/transactions). For more in-depth information on modules, visit the [Intro to Modules page](/sdk/latest/learn/concepts/modules) or check out [the Module Tutorial](/sdk/latest/tutorials/example/00-overview) to learn how to build a module from scratch. ### Block Lifecycle Hooks Beyond processing individual transactions, modules can define lifecycle hooks that run at specific points during block execution. These hooks allow modules to perform tasks at block boundaries, such as minting rewards, updating validator sets, or preparing state before transactions execute. During FinalizeBlock, `BaseApp` invokes module hooks in this sequence: 1. **PreBlock** - Prepare state before block execution begins 2. **BeginBlock** - Perform tasks at block start (e.g., minting rewards) 3. **Transaction execution** - For each transaction: run AnteHandler, execute message handlers, run PostHandler (if configured) 4. **EndBlock** - Perform tasks at block end (e.g., updating validator sets) Modules are coordinated by a [`ModuleManager`](/sdk/latest/learn/concepts/baseapp#module-manager), which orchestrates these lifecycle events along with genesis initialization and module upgrades. ### State In a Cosmos SDK application, state is stored as a collection of key-value pairs in a **multistore**. State changes occur during block execution. After consensus finalizes a block, `FinalizeBlock` is invoked by CometBFT via the ABCI, which executes each transaction in the Cosmos SDK application in order. For each transaction, the messages are extracted and routed to the `MsgServer` of the corresponding module. The `MsgServer` validates messages, and the [keepers](#keepers) executes the business logic of the module and updates the state. After executing the transactions in the block and updating state, each node computes the `AppHash` from its local state. The **`AppHash`** is a cryptographic proof of the state of the application at the end of the block, and is included in the next block header. This ensures that state updates only take effect once consensus is reached. By design, all state changes in a Cosmos SDK application are deterministic and replayable: executing the same block against the same initial state will always produce the same final state. #### Keepers Keepers are the gatekeepers to module state. They provide the only interface for accessing and mutating a module's state, enforce access control between modules, and encapsulate state access logic. This aligns with the [object capability model](/sdk/latest/guides/module-design/ocap), where modules can only access capabilities (other keepers) explicitly passed to them during initialization. Modules interact through keeper interfaces rather than directly accessing each other's state, enforcing modularity and preventing tight coupling. To learn more about keepers, visit the [Intro to Modules page](/sdk/latest/learn/concepts/modules#keeper). #### KV Stores and Multistore The state of a Cosmos SDK application is stored in a **multistore**, which is a collection of key-value stores. Each module owns a namespaced key-value store, isolating its data from other modules. The multistore combines all module stores into a single, unified state representation with height-based versioning for historical queries. KV stores only accept byte arrays (`[]byte`) as values, so any custom data structures must be marshaled using a [codec](/sdk/latest/learn/concepts/encoding) before being stored. This ensures consistent serialization across the application, typically using Protocol Buffers. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} +-----------------------------------------+ | Multistore (Root) | | | | +----------+ +----------+ +-----+ | | | Bank | | Staking | | ... | | | | Store | | Store | | | | | +----------+ +----------+ +-----+ | | | +-----------------------------------------+ ``` To learn more, visit the [Store page](/sdk/latest/learn/concepts/store). #### Merkle Trees and Commitments Application state is committed using Merkle trees. Each module store is organized as a Merkle tree (implemented as an IAVL tree), and the multistore root is a tree of module store roots. The AppHash is the root hash of this multistore structure, uniquely identifying a state version. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} AppHash (Root) | +-------------+-------------+ | | BankRoot StakingRoot | | +----+----+ +----+----+ | | | | Key1 Key2 Key3 Key4 ``` CometBFT includes the AppHash in block headers, allowing anyone to verify state commitments. This is crucial for light clients, which can verify state without downloading all blocks. Visit the [Store page](/sdk/latest/learn/concepts/store#how-state-is-stored-iavl-and-commit-stores) to learn more about how Cosmos uses IAVL trees. ### How State Replicates Across Nodes The state changes described above happen independently on every node in the network. CometBFT does not replicate application state directly—instead, it replicates blocks (ordered transactions) and consensus decisions. Each node then independently executes these blocks through its local Cosmos SDK application. This design relies entirely on deterministic execution. When all nodes execute the same ordered transactions, deterministic execution guarantees they arrive at identical state. Nodes verify this agreement by comparing AppHash commitments. If execution were non-deterministic, nodes would compute different AppHashes and consensus would fail. After executing a block, each node computes the AppHash from its local state. This AppHash is included in the next block header. Validators cryptographically sign block headers that include the AppHash, attesting that they executed the block and arrived at that state. Nodes with divergent state produce different AppHashes, and validators will not sign blocks with incorrect commitments. This makes state disagreement detectable and ensures that consensus implies state agreement across all honest nodes. To maintain determinism, applications should avoid common pitfalls: use block time instead of local timestamps (which vary across nodes), use integer math instead of floating-point arithmetic (which can differ by hardware), use deterministic randomness seeded with block data, and include all necessary data in transactions rather than making external API calls. ## Communication Layers Cosmos SDK blockchains use different communication mechanisms depending on the context. * **Node-to-Node Communication**: Nodes communicate with each other using CometBFT's custom peer-to-peer gossip protocol. This handles transaction propagation across the network, block replication to all nodes, and consensus messages like votes and proposals. This communication is entirely within CometBFT and is application-agnostic. * **Consensus to Application Communication**: CometBFT communicates with the Cosmos SDK application through ABCI using local function calls. This is in-process communication within the same daemon, not a network protocol. CometBFT invokes the application at key points in the block lifecycle, and the application returns execution results and state commitments. * **Client to Node Communication**: Clients (wallets, explorers, and other external applications) interact with nodes through the Cosmos SDK's gRPC API, with HTTP/REST available via gRPC-Gateway. This external API allows clients to query application state and submit transactions for mempool inclusion. This communication is completely separate from consensus—clients never directly interact with CometBFT's consensus protocols. To learn more, visit [the CLI, gRPC, and REST API page](/sdk/latest/learn/concepts/cli-grpc-rest). ## Summary The architecture of a Cosmos SDK blockchain cleanly separates three concerns. CometBFT determines when blocks happen and which transactions they contain through its consensus mechanism. ABCI defines how and when the application is invoked as part of the block lifecycle. The Cosmos SDK defines what those transactions mean and how they change state deterministically. ## What's Next * The [Transaction Lifecycle](/sdk/latest/learn/concepts/lifecycle) page follows a transaction from submission through mempool admission, consensus, and execution. * The [Application Anatomy](/sdk/latest/learn/intro/sdk-app-architecture) page provides a deep dive into building a Cosmos SDK application, exploring modules, keepers, and the composition of app.go in detail. # Start Here Source: https://docs.cosmos.network/sdk/latest/learn/start-here Pick the path that matches what you want to do with the Cosmos SDK. These docs are designed to get you building quickly. Choose the path that best matches your goal: ## What do you want to do? * Understand how the SDK works → [Concepts + Tutorial](#learn-+-build-recommended-path) * New to Cosmos or Blockchain? → [Start here](#new-to-cosmos) * Build something quickly → [Quickstart](#get-running-fast) * Run a node → [Operations](#run-a-node) * Explore modules → [Module Directory](#browse-modules) * Go deeper on advanced topics → [In-depth Guides](#go-deeper) ## Learn + Build (Recommended Path) Get a solid mental model of the SDK and build your first chain. 1. Read the [intro pages](/sdk/latest/learn/intro/overview) and [Cosmos Architecture](/sdk/latest/learn/intro/sdk-app-architecture) for an overview of how a Cosmos chain is structured 2. Read the [Concepts](/sdk/latest/learn/concepts/accounts) section (Fundamentals, Modules, SDK Internals) 3. Follow the [Build a Chain Tutorial](/sdk/latest/tutorials/example/00-overview) This is the most complete path for developers new to the Cosmos SDK. ## Get running fast Spin up a local chain in minutes. 1. [Prerequisites](/sdk/latest/tutorials/example/01-prerequisites) 2. [Chain Quickstart](/sdk/latest/tutorials/example/02-quickstart) From there, continue the tutorial to learn more about building modules: * [Build a Module from Scratch](/sdk/latest/tutorials/example/03-build-a-module): write your first custom module with messages, queries, and state * [Full Counter Module Walkthrough](/sdk/latest/tutorials/example/04-counter-walkthrough): add advanced features like fees, events, and module accounts * [Run, Test, and Configure](/sdk/latest/tutorials/example/05-run-and-test): test your chain and configure it for different environments ## New to Cosmos? Start here if you're new to the ecosystem. * [The Cosmos Stack](/sdk/latest/learn/intro/cosmos-stack) * [What is the Cosmos SDK](/sdk/latest/learn/intro/overview) New to blockchain development? Read [Blockchain Basics](/sdk/latest/learn/intro/blockchain-basics) For a technical overview of how a Cosmos chain is structured: [Cosmos Architecture](/sdk/latest/learn/intro/sdk-app-architecture) ## Run a node Set up and operate a Cosmos node (validators, operators). * [Run a Node](/sdk/latest/tutorials) ## Browse modules Explore production-ready SDK modules and their documentation. * [Module Directory](/sdk/latest/modules/modules) ## Go deeper Dive into advanced topics like ABCI++, vote extensions, mempool design, upgrades, and observability. * [In-depth Guides](/sdk/latest/guides/guides) # x/auth Source: https://docs.cosmos.network/sdk/latest/modules/auth/auth This document specifies the auth module of the Cosmos SDK. ## Abstract This document specifies the auth module of the Cosmos SDK. The auth module is responsible for specifying the base transaction and account types for an application, since the SDK itself is agnostic to these particulars. It contains the middlewares, where all basic transaction validity checks (signatures, nonces, auxiliary fields) are performed, and exposes the account keeper, which allows other modules to read, write, and modify accounts. This module is used in the Cosmos Hub. ## Contents * [Concepts](#concepts) * [Gas & Fees](#gas-&-fees) * [State](#state) * [Accounts](#accounts) * [AnteHandlers](#antehandlers) * [Keepers](#keepers) * [Account Keeper](#account-keeper) * [Parameters](#parameters) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) * [REST](#rest) ## Concepts **Note:** The auth module is different from the [authz module](/sdk/latest/modules/authz/README). The differences are: * `auth` - authentication of accounts and transactions for Cosmos SDK applications and is responsible for specifying the base transaction and account types. * `authz` - authorization for accounts to perform actions on behalf of other accounts and enables a granter to grant authorizations to a grantee that allows the grantee to execute messages on behalf of the granter. ### Gas & Fees Fees serve two purposes for an operator of the network. Fees limit the growth of the state stored by every full node and allow for general purpose censorship of transactions of little economic value. Fees are best suited as an anti-spam mechanism where validators are disinterested in the use of the network and identities of users. Fees are determined by the gas limits and gas prices transactions provide, where `fees = ceil(gasLimit * gasPrices)`. Txs incur gas costs for all state reads/writes, signature verification, as well as costs proportional to the tx size. Operators should set minimum gas prices when starting their nodes. They must set the unit costs of gas in each token denomination they wish to support: `simd start ... --minimum-gas-prices=0.00001stake;0.05photinos` When adding transactions to mempool or gossipping transactions, validators check if the transaction's gas prices, which are determined by the provided fees, meet any of the validator's minimum gas prices. In other words, a transaction must provide a fee of at least one denomination that matches a validator's minimum gas price. CometBFT does not currently provide fee based mempool prioritization, and fee based mempool filtering is local to node and not part of consensus. But with minimum gas prices set, such a mechanism could be implemented by node operators. Because the market value for tokens will fluctuate, validators are expected to dynamically adjust their minimum gas prices to a level that would encourage the use of the network. ## State ### Accounts Accounts contain authentication information for a uniquely identified external user of an SDK blockchain, including public key, address, and account number / sequence number for replay protection. For efficiency, since account balances must also be fetched to pay fees, account structs also store the balance of a user as `sdk.Coins`. Accounts are exposed externally as an interface, and stored internally as either a base account or vesting account. Module clients wishing to add more account types may do so. * `0x01 | Address -> ProtocolBuffer(account)` #### Account Interface The account interface exposes methods to read and write standard account information. Note that all of these methods operate on an account struct conforming to the interface - in order to write the account to the store, the account keeper will need to be used. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // AccountI is an interface used to store coins at a given address within state. // It presumes a notion of sequence numbers for replay protection, // a notion of account numbers for replay protection for previously pruned accounts, // and a pubkey for authentication purposes. // // Many complex conditions can be used in the concrete struct which implements AccountI. type AccountI interface { proto.Message GetAddress() sdk.AccAddress SetAddress(sdk.AccAddress) error // errors if already set. GetPubKey() crypto.PubKey // can return nil. SetPubKey(crypto.PubKey) error GetAccountNumber() uint64 SetAccountNumber(uint64) error GetSequence() uint64 SetSequence(uint64) error // Ensure that account implements stringer String() string } ``` ##### Base Account A base account is the simplest and most common account type, which just stores all requisite fields directly in a struct. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // BaseAccount defines a base account type. It contains all the necessary fields // for basic account functionality. Any custom account type should extend this // type for additional functionality (e.g. vesting). message BaseAccount { string address = 1; google.protobuf.Any pub_key = 2; uint64 account_number = 3; uint64 sequence = 4; } ``` ### Vesting Account See [Vesting](/sdk/latest/modules/auth/auth). ## AnteHandlers The `x/auth` module presently has no transaction handlers of its own, but does expose the special `AnteHandler`, used for performing basic validity checks on a transaction, such that it could be thrown out of the mempool. The `AnteHandler` can be seen as a set of decorators that check transactions within the current context, per [ADR 010](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-010-modular-antehandler.md). Note that the `AnteHandler` is called on both `CheckTx` and `DeliverTx`, as CometBFT proposers presently have the ability to include in their proposed block transactions which fail `CheckTx`. ### Decorators The auth module provides `AnteDecorator`s that are recursively chained together into a single `AnteHandler` in the following order: * `SetUpContextDecorator`: Sets the `GasMeter` in the `Context` and wraps the next `AnteHandler` with a defer clause to recover from any downstream `OutOfGas` panics in the `AnteHandler` chain to return an error with information on gas provided and gas used. * `RejectExtensionOptionsDecorator`: Rejects all extension options which can optionally be included in protobuf transactions. * `MempoolFeeDecorator`: Checks if the `tx` fee is above local mempool `minFee` parameter during `CheckTx`. * `ValidateBasicDecorator`: Calls `tx.ValidateBasic` and returns any non-nil error. * `TxTimeoutHeightDecorator`: Check for a `tx` height timeout. * `ValidateMemoDecorator`: Validates `tx` memo with application parameters and returns any non-nil error. * `ConsumeGasTxSizeDecorator`: Consumes gas proportional to the `tx` size based on application parameters. * `DeductFeeDecorator`: Deducts the `FeeAmount` from first signer of the `tx`. If the `x/feegrant` module is enabled and a fee granter is set, it deducts fees from the fee granter account. * `SetPubKeyDecorator`: Sets the pubkey from a `tx`'s signers that does not already have its corresponding pubkey saved in the state machine and in the current context. * `ValidateSigCountDecorator`: Validates the number of signatures in `tx` based on app-parameters. * `SigGasConsumeDecorator`: Consumes parameter-defined amount of gas for each signature. This requires pubkeys to be set in context for all signers as part of `SetPubKeyDecorator`. * `SigVerificationDecorator`: Verifies all signatures are valid. This requires pubkeys to be set in context for all signers as part of `SetPubKeyDecorator`. * `IncrementSequenceDecorator`: Increments the account sequence for each signer to prevent replay attacks. ## Keepers The auth module only exposes one keeper, the account keeper, which can be used to read and write accounts. ### Account Keeper Presently only one fully-permissioned account keeper is exposed, which has the ability to both read and write all fields of all accounts, and to iterate over all stored accounts. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // AccountKeeperI is the interface contract that x/auth's keeper implements. type AccountKeeperI interface { // Return a new account with the next account number and the specified address. Does not save the new account to the store. NewAccountWithAddress(sdk.Context, sdk.AccAddress) types.AccountI // Return a new account with the next account number. Does not save the new account to the store. NewAccount(sdk.Context, types.AccountI) types.AccountI // Check if an account exists in the store. HasAccount(sdk.Context, sdk.AccAddress) bool // Retrieve an account from the store. GetAccount(sdk.Context, sdk.AccAddress) types.AccountI // Set an account in the store. SetAccount(sdk.Context, types.AccountI) // Remove an account from the store. RemoveAccount(sdk.Context, types.AccountI) // Iterate over all accounts, calling the provided function. Stop iteration when it returns true. IterateAccounts(sdk.Context, func(types.AccountI) bool) // Fetch the public key of an account at a specified address GetPubKey(sdk.Context, sdk.AccAddress) (crypto.PubKey, error) // Fetch the sequence of an account at a specified address. GetSequence(sdk.Context, sdk.AccAddress) (uint64, error) // Fetch the next account number, and increment the internal counter. NextAccountNumber(sdk.Context) uint64 } ``` ## Parameters The auth module contains the following parameters: | Key | Type | Example | | ---------------------- | ------ | ------- | | MaxMemoCharacters | uint64 | 256 | | TxSigLimit | uint64 | 7 | | TxSizeCostPerByte | uint64 | 10 | | SigVerifyCostED25519 | uint64 | 590 | | SigVerifyCostMlDsa65 | uint64 | 750 | | SigVerifyCostSecp256k1 | uint64 | 1000 | ## Client ### CLI A user can query and interact with the `auth` module using the CLI. ### Query The `query` commands allow users to query `auth` state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query auth --help ``` #### account The `account` command allow users to query for an account by it's address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query auth account [address] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query auth account cosmos1... ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} '@type': /cosmos.auth.v1beta1.BaseAccount account_number: "0" address: cosmos1zwg6tpl8aw4rawv8sgag9086lpw5hv33u5ctr2 pub_key: '@type': /cosmos.crypto.secp256k1.PubKey key: ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD sequence: "1" ``` #### accounts The `accounts` command allow users to query all the available accounts. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query auth accounts [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query auth accounts ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} accounts: - '@type': /cosmos.auth.v1beta1.BaseAccount account_number: "0" address: cosmos1zwg6tpl8aw4rawv8sgag9086lpw5hv33u5ctr2 pub_key: '@type': /cosmos.crypto.secp256k1.PubKey key: ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD sequence: "1" - '@type': /cosmos.auth.v1beta1.ModuleAccount base_account: account_number: "8" address: cosmos1yl6hdjhmkf37639730gffanpzndzdpmhwlkfhr pub_key: null sequence: "0" name: transfer permissions: - minter - burner - '@type': /cosmos.auth.v1beta1.ModuleAccount base_account: account_number: "4" address: cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh pub_key: null sequence: "0" name: bonded_tokens_pool permissions: - burner - staking - '@type': /cosmos.auth.v1beta1.ModuleAccount base_account: account_number: "5" address: cosmos1tygms3xhhs3yv487phx3dw4a95jn7t7lpm470r pub_key: null sequence: "0" name: not_bonded_tokens_pool permissions: - burner - staking - '@type': /cosmos.auth.v1beta1.ModuleAccount base_account: account_number: "6" address: cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn pub_key: null sequence: "0" name: gov permissions: - burner - '@type': /cosmos.auth.v1beta1.ModuleAccount base_account: account_number: "3" address: cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl pub_key: null sequence: "0" name: distribution permissions: [] - '@type': /cosmos.auth.v1beta1.BaseAccount account_number: "1" address: cosmos147k3r7v2tvwqhcmaxcfql7j8rmkrlsemxshd3j pub_key: null sequence: "0" - '@type': /cosmos.auth.v1beta1.ModuleAccount base_account: account_number: "7" address: cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q pub_key: null sequence: "0" name: mint permissions: - minter - '@type': /cosmos.auth.v1beta1.ModuleAccount base_account: account_number: "2" address: cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta pub_key: null sequence: "0" name: fee_collector permissions: [] pagination: next_key: null total: "0" ``` #### params The `params` command allow users to query the current auth parameters. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query auth params [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query auth params ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} max_memo_characters: "256" sig_verify_cost_ed25519: "590" sig_verify_cost_mldsa65: "750" sig_verify_cost_secp256k1: "1000" tx_sig_limit: "7" tx_size_cost_per_byte: "10" ``` ### Transactions The `auth` module supports transactions commands to help you with signing and more. Compared to other modules you can access directly the `auth` module transactions commands using the only `tx` command. Use directly the `--help` flag to get more information about the `tx` command. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx --help ``` #### `sign` The `sign` command allows users to sign transactions that was generated offline. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx sign tx.json --from $ALICE > tx.signed.json ``` The result is a signed transaction that can be broadcasted to the network thanks to the broadcast command. More information about the `sign` command can be found running `simd tx sign --help`. #### `sign-batch` The `sign-batch` command allows users to sign multiples offline generated transactions. The transactions can be in one file, with one tx per line, or in multiple files. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx sign txs.json --from $ALICE > tx.signed.json ``` or ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx sign tx1.json tx2.json tx3.json --from $ALICE > tx.signed.json ``` The result is multiples signed transactions. For combining the signed transactions into one transactions, use the `--append` flag. More information about the `sign-batch` command can be found running `simd tx sign-batch --help`. #### `multi-sign` The `multi-sign` command allows users to sign transactions that was generated offline by a multisig account. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx multisign transaction.json k1k2k3 k1sig.json k2sig.json k3sig.json ``` Where `k1k2k3` is the multisig account address, `k1sig.json` is the signature of the first signer, `k2sig.json` is the signature of the second signer, and `k3sig.json` is the signature of the third signer. ##### Nested multisig transactions To allow transactions to be signed by nested multisigs, meaning that a participant of a multisig account can be another multisig account, the `--skip-signature-verification` flag must be used. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # First aggregate signatures of the multisig participant simd tx multi-sign transaction.json ms1 ms1p1sig.json ms1p2sig.json --signature-only --skip-signature-verification > ms1sig.json # Then use the aggregated signatures and the other signatures to sign the final transaction simd tx multi-sign transaction.json k1ms1 k1sig.json ms1sig.json --skip-signature-verification ``` Where `ms1` is the nested multisig account address, `ms1p1sig.json` is the signature of the first participant of the nested multisig account, `ms1p2sig.json` is the signature of the second participant of the nested multisig account, and `ms1sig.json` is the aggregated signature of the nested multisig account. `k1ms1` is a multisig account comprised of an individual signer and another nested multisig account (`ms1`). `k1sig.json` is the signature of the first signer of the individual member. More information about the `multi-sign` command can be found running `simd tx multi-sign --help`. #### `multisign-batch` The `multisign-batch` works the same way as `sign-batch`, but for multisig accounts. With the difference that the `multisign-batch` command requires all transactions to be in one file, and the `--append` flag does not exist. More information about the `multisign-batch` command can be found running `simd tx multisign-batch --help`. #### `validate-signatures` The `validate-signatures` command allows users to validate the signatures of a signed transaction. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} $ simd tx validate-signatures tx.signed.json Signers: 0: cosmos1l6vsqhh7rnwsyr2kyz3jjg3qduaz8gwgyl8275 Signatures: 0: cosmos1l6vsqhh7rnwsyr2kyz3jjg3qduaz8gwgyl8275 [OK] ``` More information about the `validate-signatures` command can be found running `simd tx validate-signatures --help`. #### `broadcast` The `broadcast` command allows users to broadcast a signed transaction to the network. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx broadcast tx.signed.json ``` More information about the `broadcast` command can be found running `simd tx broadcast --help`. ### gRPC A user can query the `auth` module using gRPC endpoints. #### Account The `account` endpoint allow users to query for an account by it's address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.auth.v1beta1.Query/Account ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address":"cosmos1.."}' \ localhost:9090 \ cosmos.auth.v1beta1.Query/Account ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "account":{ "@type":"/cosmos.auth.v1beta1.BaseAccount", "address":"cosmos1zwg6tpl8aw4rawv8sgag9086lpw5hv33u5ctr2", "pubKey":{ "@type":"/cosmos.crypto.secp256k1.PubKey", "key":"ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD" }, "sequence":"1" } } ``` #### Accounts The `accounts` endpoint allow users to query all the available accounts. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.auth.v1beta1.Query/Accounts ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.auth.v1beta1.Query/Accounts ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "accounts":[ { "@type":"/cosmos.auth.v1beta1.BaseAccount", "address":"cosmos1zwg6tpl8aw4rawv8sgag9086lpw5hv33u5ctr2", "pubKey":{ "@type":"/cosmos.crypto.secp256k1.PubKey", "key":"ApDrE38zZdd7wLmFS9YmqO684y5DG6fjZ4rVeihF/AQD" }, "sequence":"1" }, { "@type":"/cosmos.auth.v1beta1.ModuleAccount", "baseAccount":{ "address":"cosmos1yl6hdjhmkf37639730gffanpzndzdpmhwlkfhr", "accountNumber":"8" }, "name":"transfer", "permissions":[ "minter", "burner" ] }, { "@type":"/cosmos.auth.v1beta1.ModuleAccount", "baseAccount":{ "address":"cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh", "accountNumber":"4" }, "name":"bonded_tokens_pool", "permissions":[ "burner", "staking" ] }, { "@type":"/cosmos.auth.v1beta1.ModuleAccount", "baseAccount":{ "address":"cosmos1tygms3xhhs3yv487phx3dw4a95jn7t7lpm470r", "accountNumber":"5" }, "name":"not_bonded_tokens_pool", "permissions":[ "burner", "staking" ] }, { "@type":"/cosmos.auth.v1beta1.ModuleAccount", "baseAccount":{ "address":"cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", "accountNumber":"6" }, "name":"gov", "permissions":[ "burner" ] }, { "@type":"/cosmos.auth.v1beta1.ModuleAccount", "baseAccount":{ "address":"cosmos1jv65s3grqf6v6jl3dp4t6c9t9rk99cd88lyufl", "accountNumber":"3" }, "name":"distribution" }, { "@type":"/cosmos.auth.v1beta1.BaseAccount", "accountNumber":"1", "address":"cosmos147k3r7v2tvwqhcmaxcfql7j8rmkrlsemxshd3j" }, { "@type":"/cosmos.auth.v1beta1.ModuleAccount", "baseAccount":{ "address":"cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q", "accountNumber":"7" }, "name":"mint", "permissions":[ "minter" ] }, { "@type":"/cosmos.auth.v1beta1.ModuleAccount", "baseAccount":{ "address":"cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta", "accountNumber":"2" }, "name":"fee_collector" } ], "pagination":{ "total":"9" } } ``` #### Params The `params` endpoint allow users to query the current auth parameters. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.auth.v1beta1.Query/Params ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.auth.v1beta1.Query/Params ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "maxMemoCharacters": "256", "txSigLimit": "7", "txSizeCostPerByte": "10", "sigVerifyCostEd25519": "590", "sigVerifyCostSecp256k1": "1000" } } ``` ### REST A user can query the `auth` module using REST endpoints. #### Account The `account` endpoint allow users to query for an account by it's address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/auth/v1beta1/account?address={address} ``` #### Accounts The `accounts` endpoint allow users to query all the available accounts. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/auth/v1beta1/accounts ``` #### Params The `params` endpoint allow users to query the current auth parameters. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/auth/v1beta1/params ``` # x/auth/tx Source: https://docs.cosmos.network/sdk/latest/modules/auth/tx **Prerequisite Readings** * [Transactions](/sdk/latest/learn/concepts/lifecycle#transaction-generation) * [Encoding](/sdk/latest/learn/concepts/encoding#transaction-encoding) ## Abstract This document specifies the `x/auth/tx` package of the Cosmos SDK. This package represents the Cosmos SDK implementation of the `client.TxConfig`, `client.TxBuilder`, `client.TxEncoder` and `client.TxDecoder` interfaces. ## Contents * [Transactions](#transactions) * [`TxConfig`](#txconfig) * [`TxBuilder`](#txbuilder) * [`TxEncoder`/ `TxDecoder`](#txencoder-txdecoder) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) ## Transactions ### `TxConfig` `client.TxConfig` defines an interface a client can utilize to generate an application-defined concrete transaction type. The interface defines a set of methods for creating a `client.TxBuilder`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/client/tx_config.go#L26-L36 ``` The default implementation of `client.TxConfig` is instantiated by `NewTxConfig` in `x/auth/tx` module. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/x/auth/tx/config.go#L67-L87 ``` ### `TxBuilder` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/client/tx_config.go#L38-L56 ``` The [`client.TxBuilder`](/sdk/latest/learn/concepts/lifecycle#transaction-generation) interface is as well implemented by `x/auth/tx`. A `client.TxBuilder` can be accessed with `TxConfig.NewTxBuilder()`. ### `TxEncoder`/ `TxDecoder` More information about `TxEncoder` and `TxDecoder` can be found [here](/sdk/latest/learn/concepts/encoding#transaction-encoding). ## Client ### CLI #### Query The `x/auth/tx` module provides a CLI command to query any transaction, given its hash, transaction sequence or signature. Without any argument, the command will query the transaction using the transaction hash. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query tx DFE87B78A630C0EFDF76C80CD24C997E252792E0317502AE1A02B9809F0D8685 ``` When querying a transaction from an account given its sequence, use the `--type=acc_seq` flag: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query tx --type=acc_seq cosmos1u69uyr6v9qwe6zaaeaqly2h6wnedac0xpxq325/1 ``` When querying a transaction given its signature, use the `--type=signature` flag: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query tx --type=signature Ofjvgrqi8twZfqVDmYIhqwRLQjZZ40XbxEamk/veH3gQpRF0hL2PH4ejRaDzAX+2WChnaWNQJQ41ekToIi5Wqw== ``` When querying a transaction given its events, use the `--type=events` flag: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query txs --events 'message.sender=cosmos...' --page 1 --limit 30 ``` The `x/auth/block` module provides a CLI command to query any block, given its hash, height, or events. When querying a block by its hash, use the `--type=hash` flag: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query block --type=hash DFE87B78A630C0EFDF76C80CD24C997E252792E0317502AE1A02B9809F0D8685 ``` When querying a block by its height, use the `--type=height` flag: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query block --type=height 1357 ``` When querying a block by its events, use the `--query` flag: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query blocks --query 'message.sender=cosmos...' --page 1 --limit 30 ``` #### Transactions The `x/auth/tx` module provides a convenient CLI command for decoding and encoding transactions. #### `encode` The `encode` command encodes a transaction created with the `--generate-only` flag or signed with the sign command. The transaction is serialized to Protobuf and returned as base64. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} $ simd tx encode tx.json Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKLWNvc21vczFsNnZzcWhoN3Jud3N5cjJreXozampnM3FkdWF6OGd3Z3lsODI3NRItY29zbW9zMTU4c2FsZHlnOHBteHU3Znd2dDBkNng3amVzd3A0Z3d5a2xrNnkzGgwKBXN0YWtlEgMxMDASBhIEEMCaDA== $ simd tx encode tx.signed.json ``` More information about the `encode` command can be found running `simd tx encode --help`. #### `decode` The `decode` command decodes a transaction encoded with the `encode` command. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx decode Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKLWNvc21vczFsNnZzcWhoN3Jud3N5cjJreXozampnM3FkdWF6OGd3Z3lsODI3NRItY29zbW9zMTU4c2FsZHlnOHBteHU3Znd2dDBkNng3amVzd3A0Z3d5a2xrNnkzGgwKBXN0YWtlEgMxMDASBhIEEMCaDA== ``` More information about the `decode` command can be found running `simd tx decode --help`. ### gRPC A user can query the `x/auth/tx` module using gRPC endpoints. #### `TxDecode` The `TxDecode` endpoint allows to decode a transaction. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.tx.v1beta1.Service/TxDecode ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"tx_bytes":"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKLWNvc21vczFsNnZzcWhoN3Jud3N5cjJreXozampnM3FkdWF6OGd3Z3lsODI3NRItY29zbW9zMTU4c2FsZHlnOHBteHU3Znd2dDBkNng3amVzd3A0Z3d5a2xrNnkzGgwKBXN0YWtlEgMxMDASBhIEEMCaDA=="}' \ localhost:9090 \ cosmos.tx.v1beta1.Service/TxDecode ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "tx": { "body": { "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "amount": [ { "denom": "stake", "amount": "100" } ], "fromAddress": "cosmos1l6vsqhh7rnwsyr2kyz3jjg3qduaz8gwgyl8275", "toAddress": "cosmos158saldyg8pmxu7fwvt0d6x7jeswp4gwyklk6y3" } ] }, "authInfo": { "fee": { "gasLimit": "200000" } } } } ``` #### `TxEncode` The `TxEncode` endpoint allows to encode a transaction. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.tx.v1beta1.Service/TxEncode ``` Example: ```shell expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"tx": { "body": { "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"100"}],"fromAddress":"cosmos1l6vsqhh7rnwsyr2kyz3jjg3qduaz8gwgyl8275","toAddress":"cosmos158saldyg8pmxu7fwvt0d6x7jeswp4gwyklk6y3"} ] }, "authInfo": { "fee": { "gasLimit": "200000" } } }}' \ localhost:9090 \ cosmos.tx.v1beta1.Service/TxEncode ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "txBytes": "Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKLWNvc21vczFsNnZzcWhoN3Jud3N5cjJreXozampnM3FkdWF6OGd3Z3lsODI3NRItY29zbW9zMTU4c2FsZHlnOHBteHU3Znd2dDBkNng3amVzd3A0Z3d5a2xrNnkzGgwKBXN0YWtlEgMxMDASBhIEEMCaDA==" } ``` #### `TxDecodeAmino` The `TxDecode` endpoint allows to decode an amino transaction. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.tx.v1beta1.Service/TxDecodeAmino ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"amino_binary": "KCgWqQpvqKNhmgotY29zbW9zMXRzeno3cDJ6Z2Q3dnZrYWh5ZnJlNHduNXh5dTgwcnB0ZzZ2OWg1Ei1jb3Ntb3MxdHN6ejdwMnpnZDd2dmthaHlmcmU0d241eHl1ODBycHRnNnY5aDUaCwoFc3Rha2USAjEwEhEKCwoFc3Rha2USAjEwEMCaDCIGZm9vYmFy"}' \ localhost:9090 \ cosmos.tx.v1beta1.Service/TxDecodeAmino ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "aminoJson": "{\"type\":\"cosmos-sdk/StdTx\",\"value\":{\"msg\":[{\"type\":\"cosmos-sdk/MsgSend\",\"value\":{\"from_address\":\"cosmos1tszz7p2zgd7vvkahyfre4wn5xyu80rptg6v9h5\",\"to_address\":\"cosmos1tszz7p2zgd7vvkahyfre4wn5xyu80rptg6v9h5\",\"amount\":[{\"denom\":\"stake\",\"amount\":\"10\"}]}}],\"fee\":{\"amount\":[{\"denom\":\"stake\",\"amount\":\"10\"}],\"gas\":\"200000\"},\"signatures\":null,\"memo\":\"foobar\",\"timeout_height\":\"0\"}}" } ``` #### `TxEncodeAmino` The `TxEncodeAmino` endpoint allows to encode an amino transaction. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.tx.v1beta1.Service/TxEncodeAmino ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"amino_json":"{\"type\":\"cosmos-sdk/StdTx\",\"value\":{\"msg\":[{\"type\":\"cosmos-sdk/MsgSend\",\"value\":{\"from_address\":\"cosmos1tszz7p2zgd7vvkahyfre4wn5xyu80rptg6v9h5\",\"to_address\":\"cosmos1tszz7p2zgd7vvkahyfre4wn5xyu80rptg6v9h5\",\"amount\":[{\"denom\":\"stake\",\"amount\":\"10\"}]}}],\"fee\":{\"amount\":[{\"denom\":\"stake\",\"amount\":\"10\"}],\"gas\":\"200000\"},\"signatures\":null,\"memo\":\"foobar\",\"timeout_height\":\"0\"}}"}' \ localhost:9090 \ cosmos.tx.v1beta1.Service/TxEncodeAmino ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "amino_binary": "KCgWqQpvqKNhmgotY29zbW9zMXRzeno3cDJ6Z2Q3dnZrYWh5ZnJlNHduNXh5dTgwcnB0ZzZ2OWg1Ei1jb3Ntb3MxdHN6ejdwMnpnZDd2dmthaHlmcmU0d241eHl1ODBycHRnNnY5aDUaCwoFc3Rha2USAjEwEhEKCwoFc3Rha2USAjEwEMCaDCIGZm9vYmFy" } ``` # x/auth/vesting Source: https://docs.cosmos.network/sdk/latest/modules/auth/vesting * [Intro and Requirements](#intro-and-requirements) * [Note](#note) * [Vesting Account Types](#vesting-account-types) * [BaseVestingAccount](#basevestingaccount) * [ContinuousVestingAccount](#continuousvestingaccount) * [DelayedVestingAccount](#delayedvestingaccount) * [Period](#period) * [PeriodicVestingAccount](#periodicvestingaccount) * [PermanentLockedAccount](#permanentlockedaccount) * [Vesting Account Specification](#vesting-account-specification) * [Determining Vesting & Vested Amounts](#determining-vesting-&-vested-amounts) * [Periodic Vesting Accounts](#periodic-vesting-accounts) * [Transferring/Sending](#transferringsending) * [Delegating](#delegating) * [Undelegating](#undelegating) * [Keepers & Handlers](#keepers-&-handlers) * [Genesis Initialization](#genesis-initialization) * [Examples](#examples) * [Simple](#simple) * [Slashing](#slashing) * [Periodic Vesting](#periodic-vesting) * [Glossary](#glossary) ## Intro and Requirements This specification defines the vesting account implementation that is used by the Cosmos Hub. The requirements for this vesting account is that it should be initialized during genesis with a starting balance `X` and a vesting end time `ET`. A vesting account may be initialized with a vesting start time `ST` and a number of vesting periods `P`. If a vesting start time is included, the vesting period does not begin until start time is reached. If vesting periods are included, the vesting occurs over the specified number of periods. For all vesting accounts, the owner of the vesting account is able to delegate and undelegate from validators, however they cannot transfer coins to another account until those coins are vested. This specification allows for four different kinds of vesting: * Delayed vesting, where all coins are vested once `ET` is reached. * Continuous vesting, where coins begin to vest at `ST` and vest linearly with respect to time until `ET` is reached * Periodic vesting, where coins begin to vest at `ST` and vest periodically according to number of periods and the vesting amount per period. The number of periods, length per period, and amount per period are configurable. A periodic vesting account is distinguished from a continuous vesting account in that coins can be released in staggered tranches. For example, a periodic vesting account could be used for vesting arrangements where coins are released quarterly, yearly, or over any other function of tokens over time. * Permanent locked vesting, where coins are locked forever. Coins in this account can still be used for delegating and for governance votes even while locked. ## Note Vesting accounts can be initialized with some vesting and non-vesting coins. The non-vesting coins would be immediately transferable. DelayedVesting ContinuousVesting, PeriodicVesting and PermanentVesting accounts can be created with normal messages after genesis. Other types of vesting accounts must be created at genesis, or as part of a manual network upgrade. The current specification only allows for *unconditional* vesting (ie. there is no possibility of reaching `ET` and having coins fail to vest). ## Vesting Account Types ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // VestingAccount defines an interface that any vesting account type must // implement. type VestingAccount interface { Account GetVestedCoins(Time) Coins GetVestingCoins(Time) Coins // TrackDelegation performs internal vesting accounting necessary when // delegating from a vesting account. It accepts the current block time, the // delegation amount and balance of all coins whose denomination exists in // the account's original vesting balance. TrackDelegation(Time, Coins, Coins) // TrackUndelegation performs internal vesting accounting necessary when a // vesting account performs an undelegation. TrackUndelegation(Coins) GetStartTime() int64 GetEndTime() int64 } ``` ### BaseVestingAccount ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/proto/cosmos/vesting/v1beta1/vesting.proto#L12-L39 ``` ### ContinuousVestingAccount ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/proto/cosmos/vesting/v1beta1/vesting.proto#L41-L50 ``` ### DelayedVestingAccount ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/proto/cosmos/vesting/v1beta1/vesting.proto#L52-L60 ``` ### Period ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/proto/cosmos/vesting/v1beta1/vesting.proto#L62-L72 ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Stores all vesting periods passed as part of a PeriodicVestingAccount type Periods []Period ``` ### PeriodicVestingAccount ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/proto/cosmos/vesting/v1beta1/vesting.proto#L74-L83 ``` In order to facilitate less ad-hoc type checking and assertions and to support flexibility in account balance usage, the existing `x/bank` `ViewKeeper` interface is updated to contain the following: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ViewKeeper interface { // ... // Calculates the total locked account balance. LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins // Calculates the total spendable balance that can be sent to other accounts. SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins } ``` ### PermanentLockedAccount ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/proto/cosmos/vesting/v1beta1/vesting.proto#L85-L94 ``` ## Vesting Account Specification Given a vesting account, we define the following in the proceeding operations: * `OV`: The original vesting coin amount. It is a constant value. * `V`: The number of `OV` coins that are still *vesting*. It is derived by `OV`, `StartTime` and `EndTime`. This value is computed on demand and not on a per-block basis. * `V'`: The number of `OV` coins that are *vested* (unlocked). This value is computed on demand and not a per-block basis. * `DV`: The number of delegated *vesting* coins. It is a variable value. It is stored and modified directly in the vesting account. * `DF`: The number of delegated *vested* (unlocked) coins. It is a variable value. It is stored and modified directly in the vesting account. * `BC`: The number of `OV` coins less any coins that are transferred (which can be negative or delegated). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account. ### Determining Vesting & Vested Amounts It is important to note that these values are computed on demand and not on a mandatory per-block basis (e.g. `BeginBlocker` or `EndBlocker`). #### Continuously Vesting Accounts To determine the amount of coins that are vested for a given block time `T`, the following is performed: 1. Compute `X := T - StartTime` 2. Compute `Y := EndTime - StartTime` 3. Compute `V' := OV * (X / Y)` 4. Compute `V := OV - V'` Thus, the total amount of *vested* coins is `V'` and the remaining amount, `V`, is *vesting*. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (cva ContinuousVestingAccount) GetVestedCoins(t Time) Coins { if t <= cva.StartTime { // We must handle the case where the start time for a vesting account has // been set into the future or when the start of the chain is not exactly // known. return ZeroCoins } else if t >= cva.EndTime { return cva.OriginalVesting } x := t - cva.StartTime y := cva.EndTime - cva.StartTime return cva.OriginalVesting * (x / y) } func (cva ContinuousVestingAccount) GetVestingCoins(t Time) Coins { return cva.OriginalVesting - cva.GetVestedCoins(t) } ``` ### Periodic Vesting Accounts Periodic vesting accounts require calculating the coins released during each period for a given block time `T`. Note that multiple periods could have passed when calling `GetVestedCoins`, so we must iterate over each period until the end of that period is after `T`. 1. Set `CT := StartTime` 2. Set `V' := 0` For each Period P: 1. Compute `X := T - CT` 2. IF `X >= P.Length` 1. Compute `V' += P.Amount` 2. Compute `CT += P.Length` 3. ELSE break 3. Compute `V := OV - V'` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (pva PeriodicVestingAccount) GetVestedCoins(t Time) Coins { if t < pva.StartTime { return ZeroCoins } ct := pva.StartTime // The start of the vesting schedule vested := 0 periods = pva.GetPeriods() for _, period := range periods { if t - ct < period.Length { break } vested += period.Amount ct += period.Length // increment ct to the start of the next vesting period } return vested } func (pva PeriodicVestingAccount) GetVestingCoins(t Time) Coins { return pva.OriginalVesting - cva.GetVestedCoins(t) } ``` #### Delayed/Discrete Vesting Accounts Delayed vesting accounts are easier to reason about as they only have the full amount vesting up until a certain time, then all the coins become vested (unlocked). This does not include any unlocked coins the account may have initially. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (dva DelayedVestingAccount) GetVestedCoins(t Time) Coins { if t >= dva.EndTime { return dva.OriginalVesting } return ZeroCoins } func (dva DelayedVestingAccount) GetVestingCoins(t Time) Coins { return dva.OriginalVesting - dva.GetVestedCoins(t) } ``` ### Transferring/Sending At any given time, a vesting account may transfer: `min((BC + DV) - V, BC)`. In other words, a vesting account may transfer the minimum of the base account balance and the base account balance plus the number of currently delegated vesting coins less the number of coins vested so far. However, given that account balances are tracked via the `x/bank` module and that we want to avoid loading the entire account balance, we can instead determine the locked balance, which can be defined as `max(V - DV, 0)`, and infer the spendable balance from that. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (va VestingAccount) LockedCoins(t Time) Coins { return max(va.GetVestingCoins(t) - va.DelegatedVesting, 0) } ``` The `x/bank` `ViewKeeper` can then provide APIs to determine locked and spendable coins for any account: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) LockedCoins(ctx Context, addr AccAddress) Coins { acc := k.GetAccount(ctx, addr) if acc != nil { if acc.IsVesting() { return acc.LockedCoins(ctx.BlockTime()) } } // non-vesting accounts do not have any locked coins return NewCoins() } ``` #### Keepers/Handlers The corresponding `x/bank` keeper should appropriately handle sending coins based on if the account is a vesting account or not. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) SendCoins(ctx Context, from Account, to Account, amount Coins) { bc := k.GetBalances(ctx, from) v := k.LockedCoins(ctx, from) spendable := bc - v newCoins := spendable - amount assert(newCoins >= 0) from.SetBalance(newCoins) to.AddBalance(amount) // save balances... } ``` ### Delegating For a vesting account attempting to delegate `D` coins, the following is performed: 1. Verify `BC >= D > 0` 2. Compute `X := min(max(V - DV, 0), D)` (portion of `D` that is vesting) 3. Compute `Y := D - X` (portion of `D` that is free) 4. Set `DV += X` 5. Set `DF += Y` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (va VestingAccount) TrackDelegation(t Time, balance Coins, amount Coins) { assert(balance <= amount) x := min(max(va.GetVestingCoins(t) - va.DelegatedVesting, 0), amount) y := amount - x va.DelegatedVesting += x va.DelegatedFree += y } ``` **Note** `TrackDelegation` only modifies the `DelegatedVesting` and `DelegatedFree` fields, so upstream callers MUST modify the `Coins` field by subtracting `amount`. #### Keepers/Handlers ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func DelegateCoins(t Time, from Account, amount Coins) { if isVesting(from) { from.TrackDelegation(t, amount) } else { from.SetBalance(sc - amount) } // save account... } ``` ### Undelegating For a vesting account attempting to undelegate `D` coins, the following is performed: > NOTE: `DV < D` and `(DV + DF) < D` may be possible due to quirks in the rounding of delegation/undelegation logic. 1. Verify `D > 0` 2. Compute `X := min(DF, D)` (portion of `D` that should become free, prioritizing free coins) 3. Compute `Y := min(DV, D - X)` (portion of `D` that should remain vesting) 4. Set `DF -= X` 5. Set `DV -= Y` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) { x := min(cva.DelegatedFree, amount) y := amount - x cva.DelegatedFree -= x cva.DelegatedVesting -= y } ``` **Note** `TrackUnDelegation` only modifies the `DelegatedVesting` and `DelegatedFree` fields, so upstream callers MUST modify the `Coins` field by adding `amount`. **Note**: If a delegation is slashed, the continuous vesting account ends up with an excess `DV` amount, even after all its coins have vested. This is because undelegating free coins are prioritized. **Note**: The undelegation (bond refund) amount may exceed the delegated vesting (bond) amount due to the way undelegation truncates the bond refund, which can increase the validator's exchange rate (tokens/shares) slightly if the undelegated tokens are non-integral. #### Keepers/Handlers ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func UndelegateCoins(to Account, amount Coins) { if isVesting(to) { if to.DelegatedFree + to.DelegatedVesting >= amount { to.TrackUndelegation(amount) // save account ... } } else { AddBalance(to, amount) // save account... } } ``` ## Keepers & Handlers The `VestingAccount` implementations reside in `x/auth`. However, any keeper in a module (e.g. staking in `x/staking`) wishing to potentially utilize any vesting coins, must call explicit methods on the `x/bank` keeper (e.g. `DelegateCoins`) opposed to `SendCoins` and `SubtractCoins`. In addition, the vesting account should also be able to spend any coins it receives from other users. Thus, the bank module's `MsgSend` handler should error if a vesting account is trying to send an amount that exceeds their unlocked coin amount. See the above specification for full implementation details. ## Genesis Initialization To initialize both vesting and non-vesting accounts, the `GenesisAccount` struct includes new fields: `Vesting`, `StartTime`, and `EndTime`. Accounts meant to be of type `BaseAccount` or any non-vesting type have `Vesting = false`. The genesis initialization logic (e.g. `initFromGenesisState`) must parse and return the correct accounts accordingly based off of these fields. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type GenesisAccount struct { // ... // vesting account fields OriginalVesting sdk.Coins `json:"original_vesting"` DelegatedFree sdk.Coins `json:"delegated_free"` DelegatedVesting sdk.Coins `json:"delegated_vesting"` StartTime int64 `json:"start_time"` EndTime int64 `json:"end_time"` } func ToAccount(gacc GenesisAccount) Account { bacc := NewBaseAccount(gacc) if gacc.OriginalVesting > 0 { if ga.StartTime != 0 && ga.EndTime != 0 { // return a continuous vesting account } else if ga.EndTime != 0 { // return a delayed vesting account } else { // invalid genesis vesting account provided panic() } } return bacc } ``` ## Examples ### Simple Given a continuous vesting account with 10 vesting coins. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} OV = 10 DF = 0 DV = 0 BC = 10 V = 10 V' = 0 ``` 1. Immediately receives 1 coin ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} BC = 11 ``` 2. Time passes, 2 coins vest ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} V = 8 V' = 2 ``` 3. Delegates 4 coins to validator A ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DV = 4 BC = 7 ``` 4. Sends 3 coins ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} BC = 4 ``` 5. More time passes, 2 more coins vest ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} V = 6 V' = 4 ``` 6. Sends 2 coins. At this point the account cannot send anymore until further coins vest or it receives additional coins. It can still however, delegate. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} BC = 2 ``` ### Slashing Same initial starting conditions as the simple example. 1. Time passes, 5 coins vest ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} V = 5 V' = 5 ``` 2. Delegate 5 coins to validator A ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DV = 5 BC = 5 ``` 3. Delegate 5 coins to validator B ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DF = 5 BC = 0 ``` 4. Validator A gets slashed by 50%, making the delegation to A now worth 2.5 coins 5. Undelegate from validator A (2.5 coins) ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DF = 5 - 2.5 = 2.5 BC = 0 + 2.5 = 2.5 ``` 6. Undelegate from validator B (5 coins). The account at this point can only send 2.5 coins unless it receives more coins or until more coins vest. It can still however, delegate. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DV = 5 - 2.5 = 2.5 DF = 2.5 - 2.5 = 0 BC = 2.5 + 5 = 7.5 ``` Notice how we have an excess amount of `DV`. ### Periodic Vesting A vesting account is created where 100 tokens will be released over 1 year, with 1/4 of tokens vesting each quarter. The vesting schedule would be as follows: ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Periods: - amount: 25stake, length: 7884000 - amount: 25stake, length: 7884000 - amount: 25stake, length: 7884000 - amount: 25stake, length: 7884000 ``` ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} OV = 100 DF = 0 DV = 0 BC = 100 V = 100 V' = 0 ``` 1. Immediately receives 1 coin ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} BC = 101 ``` 2. Vesting period 1 passes, 25 coins vest ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} V = 75 V' = 25 ``` 3. During vesting period 2, 5 coins are transferred and 5 coins are delegated ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DV = 5 BC = 91 ``` 4. Vesting period 2 passes, 25 coins vest ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} V = 50 V' = 50 ``` ## Glossary * OriginalVesting: The amount of coins (per denomination) that are initially part of a vesting account. These coins are set at genesis. * StartTime: The BFT time at which a vesting account starts to vest. * EndTime: The BFT time at which a vesting account is fully vested. * DelegatedFree: The tracked amount of coins (per denomination) that are delegated from a vesting account that have been fully vested at time of delegation. * DelegatedVesting: The tracked amount of coins (per denomination) that are delegated from a vesting account that were vesting at time of delegation. * ContinuousVestingAccount: A vesting account implementation that vests coins linearly over time. * DelayedVestingAccount: A vesting account implementation that only fully vests all coins at a given time. * PeriodicVestingAccount: A vesting account implementation that vests coins according to a custom vesting schedule. * PermanentLockedAccount: It does not ever release coins, locking them indefinitely. Coins in this account can still be used for delegating and for governance votes even while locked. ## CLI A user can query and interact with the `vesting` module using the CLI. ### Transactions The `tx` commands allow users to interact with the `vesting` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx vesting --help ``` #### create-periodic-vesting-account The `create-periodic-vesting-account` command creates a new vesting account funded with an allocation of tokens, where a sequence of coins and period length in seconds. Periods are sequential, in that the duration of a period only starts at the end of the previous period. The duration of the first period starts upon account creation. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx vesting create-periodic-vesting-account [to_address] [periods_json_file] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx vesting create-periodic-vesting-account cosmos1.. periods.json ``` #### create-vesting-account The `create-vesting-account` command creates a new vesting account funded with an allocation of tokens. The account can either be a delayed or continuous vesting account, which is determined by the '--delayed' flag. All vesting accouts created will have their start time set by the committed block's time. The end\_time must be provided as a UNIX epoch timestamp. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx vesting create-vesting-account [to_address] [amount] [end_time] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx vesting create-vesting-account cosmos1.. 100stake 2592000 ``` # x/authz Source: https://docs.cosmos.network/sdk/latest/modules/authz/README ## Abstract `x/authz` is an implementation of a Cosmos SDK module, per [ADR 30](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-030-authz-module.md), that allows granting arbitrary privileges from one account (the granter) to another account (the grantee). Authorizations must be granted for a particular Msg service method one by one using an implementation of the `Authorization` interface. ## Contents * [Concepts](#concepts) * [Authorization and Grant](#authorization-and-grant) * [Built-in Authorizations](#built-in-authorizations) * [Gas](#gas) * [State](#state) * [Grant](#grant) * [GrantQueue](#grantqueue) * [Messages](#messages) * [MsgGrant](#msggrant) * [MsgRevoke](#msgrevoke) * [MsgExec](#msgexec) * [Events](#events) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) * [REST](#rest) ## Concepts ### Authorization and Grant The `x/authz` module defines interfaces and messages grant authorizations to perform actions on behalf of one account to other accounts. The design is defined in the [ADR 030](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-030-authz-module.md). A *grant* is an allowance to execute a Msg by the grantee on behalf of the granter. Authorization is an interface that must be implemented by a concrete authorization logic to validate and execute grants. Authorizations are extensible and can be defined for any Msg service method even outside of the module where the Msg method is defined. See the `SendAuthorization` example in the next section for more details. **Note:** The authz module is different from the [auth (authentication)](/sdk/latest/modules/auth/auth/) module that is responsible for specifying the base transaction and account types. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package authz import ( "github.com/cosmos/gogoproto/proto" sdk "github.com/cosmos/cosmos-sdk/types" ) // Authorization represents the interface of various Authorization types implemented // by other modules. type Authorization interface { proto.Message // MsgTypeURL returns the fully-qualified Msg service method URL (as described in ADR 031), // which will process and accept or reject a request. MsgTypeURL() string // Accept determines whether this grant permits the provided sdk.Msg to be performed, // and if so provides an upgraded authorization instance. Accept(ctx sdk.Context, msg sdk.Msg) (AcceptResponse, error) // ValidateBasic does a simple validation check that // doesn't require access to any other information. ValidateBasic() error } // AcceptResponse instruments the controller of an authz message if the request is accepted // and if it should be updated or deleted. type AcceptResponse struct { // If Accept=true, the controller can accept and authorization and handle the update. Accept bool // If Delete=true, the controller must delete the authorization object and release // storage resources. Delete bool // Controller, who is calling Authorization.Accept must check if `Updated != nil`. If yes, // it must use the updated version and handle the update on the storage level. Updated Authorization } ``` ### Built-in Authorizations The Cosmos SDK `x/authz` module comes with following authorization types: #### GenericAuthorization `GenericAuthorization` implements the `Authorization` interface that gives unrestricted permission to execute the provided Msg on behalf of granter's account. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/authz/v1beta1/authz.proto#L13-L21 ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package authz import ( sdk "github.com/cosmos/cosmos-sdk/types" ) var _ Authorization = &GenericAuthorization{ } // NewGenericAuthorization creates a new GenericAuthorization object. func NewGenericAuthorization(msgTypeURL string) *GenericAuthorization { return &GenericAuthorization{ Msg: msgTypeURL, } } // MsgTypeURL implements Authorization.MsgTypeURL. func (a GenericAuthorization) MsgTypeURL() string { return a.Msg } // Accept implements Authorization.Accept. func (a GenericAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (AcceptResponse, error) { return AcceptResponse{ Accept: true }, nil } // ValidateBasic implements Authorization.ValidateBasic. func (a GenericAuthorization) ValidateBasic() error { return nil } ``` * `msg` stores Msg type URL. #### SendAuthorization `SendAuthorization` implements the `Authorization` interface for the `cosmos.bank.v1beta1.MsgSend` Msg. * It takes a (positive) `SpendLimit` that specifies the maximum amount of tokens the grantee can spend. The `SpendLimit` is updated as the tokens are spent. * It takes an (optional) `AllowList` that specifies to which addresses a grantee can send token. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/authz.proto#L11-L29 ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package types import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" ) // TODO: Revisit this once we have proper gas fee framework. // Ref: https://github.com/cosmos/cosmos-sdk/issues/9054 // Ref: https://github.com/cosmos/cosmos-sdk/discussions/9072 const gasCostPerIteration = uint64(10) var _ authz.Authorization = &SendAuthorization{ } // NewSendAuthorization creates a new SendAuthorization object. func NewSendAuthorization(spendLimit sdk.Coins, allowed []sdk.AccAddress) *SendAuthorization { return &SendAuthorization{ AllowList: toBech32Addresses(allowed), SpendLimit: spendLimit, } } // MsgTypeURL implements Authorization.MsgTypeURL. func (a SendAuthorization) MsgTypeURL() string { return sdk.MsgTypeURL(&MsgSend{ }) } // Accept implements Authorization.Accept. func (a SendAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptResponse, error) { mSend, ok := msg.(*MsgSend) if !ok { return authz.AcceptResponse{ }, sdkerrors.ErrInvalidType.Wrap("type mismatch") } toAddr := mSend.ToAddress limitLeft, isNegative := a.SpendLimit.SafeSub(mSend.Amount...) if isNegative { return authz.AcceptResponse{ }, sdkerrors.ErrInsufficientFunds.Wrapf("requested amount is more than spend limit") } if limitLeft.IsZero() { return authz.AcceptResponse{ Accept: true, Delete: true }, nil } isAddrExists := false allowedList := a.GetAllowList() for _, addr := range allowedList { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "send authorization") if addr == toAddr { isAddrExists = true break } } if len(allowedList) > 0 && !isAddrExists { return authz.AcceptResponse{ }, sdkerrors.ErrUnauthorized.Wrapf("cannot send to %s address", toAddr) } return authz.AcceptResponse{ Accept: true, Delete: false, Updated: &SendAuthorization{ SpendLimit: limitLeft, AllowList: allowedList }}, nil } // ValidateBasic implements Authorization.ValidateBasic. func (a SendAuthorization) ValidateBasic() error { if a.SpendLimit == nil { return sdkerrors.ErrInvalidCoins.Wrap("spend limit cannot be nil") } if !a.SpendLimit.IsAllPositive() { return sdkerrors.ErrInvalidCoins.Wrapf("spend limit must be positive") } found := make(map[string]bool, 0) for i := 0; i < len(a.AllowList); i++ { if found[a.AllowList[i]] { return ErrDuplicateEntry } found[a.AllowList[i]] = true } return nil } func toBech32Addresses(allowed []sdk.AccAddress) []string { if len(allowed) == 0 { return nil } allowedAddrs := make([]string, len(allowed)) for i, addr := range allowed { allowedAddrs[i] = addr.String() } return allowedAddrs } ``` * `spend_limit` keeps track of how many coins are left in the authorization. * `allow_list` specifies an optional list of addresses to whom the grantee can send tokens on behalf of the granter. #### StakeAuthorization `StakeAuthorization` implements the `Authorization` interface for messages in the [staking module](/sdk/latest/modules/staking). It takes an `AuthorizationType` to specify whether you want to authorise delegating, undelegating, redelegating, or cancelling an unbonding delegation (i.e. these have to be authorised separately). It also takes an optional `MaxTokens` that keeps track of a limit to the amount of tokens that can be delegated/undelegated/redelegated. If left empty, the amount is unlimited. Additionally, this Msg takes an `AllowList` or a `DenyList`, which allows you to select which validators you allow or deny grantees to stake with. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/authz.proto#L10-L33 ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package types import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" ) // TODO: Revisit this once we have proper gas fee framework. // Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072 const gasCostPerIteration = uint64(10) var _ authz.Authorization = &StakeAuthorization{ } // NewStakeAuthorization creates a new StakeAuthorization object. func NewStakeAuthorization(allowed []sdk.ValAddress, denied []sdk.ValAddress, authzType AuthorizationType, amount *sdk.Coin) (*StakeAuthorization, error) { allowedValidators, deniedValidators, err := validateAllowAndDenyValidators(allowed, denied) if err != nil { return nil, err } a := StakeAuthorization{ } if allowedValidators != nil { a.Validators = &StakeAuthorization_AllowList{ AllowList: &StakeAuthorization_Validators{ Address: allowedValidators }} } else { a.Validators = &StakeAuthorization_DenyList{ DenyList: &StakeAuthorization_Validators{ Address: deniedValidators }} } if amount != nil { a.MaxTokens = amount } a.AuthorizationType = authzType return &a, nil } // MsgTypeURL implements Authorization.MsgTypeURL. func (a StakeAuthorization) MsgTypeURL() string { authzType, err := normalizeAuthzType(a.AuthorizationType) if err != nil { panic(err) } return authzType } func (a StakeAuthorization) ValidateBasic() error { if a.MaxTokens != nil && a.MaxTokens.IsNegative() { return sdkerrors.Wrapf(authz.ErrNegativeMaxTokens, "negative coin amount: %v", a.MaxTokens) } if a.AuthorizationType == AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED { return authz.ErrUnknownAuthorizationType } return nil } // Accept implements Authorization.Accept. func (a StakeAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptResponse, error) { var validatorAddress string var amount sdk.Coin switch msg := msg.(type) { case *MsgDelegate: validatorAddress = msg.ValidatorAddress amount = msg.Amount case *MsgUndelegate: validatorAddress = msg.ValidatorAddress amount = msg.Amount case *MsgBeginRedelegate: validatorAddress = msg.ValidatorDstAddress amount = msg.Amount default: return authz.AcceptResponse{ }, sdkerrors.ErrInvalidRequest.Wrap("unknown msg type") } isValidatorExists := false allowedList := a.GetAllowList().GetAddress() for _, validator := range allowedList { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization") if validator == validatorAddress { isValidatorExists = true break } } denyList := a.GetDenyList().GetAddress() for _, validator := range denyList { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization") if validator == validatorAddress { return authz.AcceptResponse{ }, sdkerrors.ErrUnauthorized.Wrapf("cannot delegate/undelegate to %s validator", validator) } } if len(allowedList) > 0 && !isValidatorExists { return authz.AcceptResponse{ }, sdkerrors.ErrUnauthorized.Wrapf("cannot delegate/undelegate to %s validator", validatorAddress) } if a.MaxTokens == nil { return authz.AcceptResponse{ Accept: true, Delete: false, Updated: &StakeAuthorization{ Validators: a.GetValidators(), AuthorizationType: a.GetAuthorizationType() }, }, nil } limitLeft, err := a.MaxTokens.SafeSub(amount) if err != nil { return authz.AcceptResponse{ }, err } if limitLeft.IsZero() { return authz.AcceptResponse{ Accept: true, Delete: true }, nil } return authz.AcceptResponse{ Accept: true, Delete: false, Updated: &StakeAuthorization{ Validators: a.GetValidators(), AuthorizationType: a.GetAuthorizationType(), MaxTokens: &limitLeft }, }, nil } func validateAllowAndDenyValidators(allowed []sdk.ValAddress, denied []sdk.ValAddress) ([]string, []string, error) { if len(allowed) == 0 && len(denied) == 0 { return nil, nil, sdkerrors.ErrInvalidRequest.Wrap("both allowed & deny list cannot be empty") } if len(allowed) > 0 && len(denied) > 0 { return nil, nil, sdkerrors.ErrInvalidRequest.Wrap("cannot set both allowed & deny list") } allowedValidators := make([]string, len(allowed)) if len(allowed) > 0 { for i, validator := range allowed { allowedValidators[i] = validator.String() } return allowedValidators, nil, nil } deniedValidators := make([]string, len(denied)) for i, validator := range denied { deniedValidators[i] = validator.String() } return nil, deniedValidators, nil } // Normalized Msg type URLs func normalizeAuthzType(authzType AuthorizationType) (string, error) { switch authzType { case AuthorizationType_AUTHORIZATION_TYPE_DELEGATE: return sdk.MsgTypeURL(&MsgDelegate{ }), nil case AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE: return sdk.MsgTypeURL(&MsgUndelegate{ }), nil case AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE: return sdk.MsgTypeURL(&MsgBeginRedelegate{ }), nil default: return "", sdkerrors.Wrapf(authz.ErrUnknownAuthorizationType, "cannot normalize authz type with %T", authzType) } } ``` ### Gas In order to prevent DoS attacks, granting `StakeAuthorization`s with `x/authz` incurs gas. `StakeAuthorization` allows you to authorize another account to delegate, undelegate, or redelegate to validators. The authorizer can define a list of validators they allow or deny delegations to. The Cosmos SDK iterates over these lists and charge 10 gas for each validator in both of the lists. Since the state maintains a list for granter, grantee pair with the same expiration, we are iterating over the list to remove the grant (in case of any revoke of a particular `msgType`) from the list and we are charging 20 gas per iteration. ## State ### Grant Grants are identified by combining granter address (the address bytes of the granter), grantee address (the address bytes of the grantee) and Authorization type (its type URL). Hence we only allow one grant for the (granter, grantee, Authorization) triple. * Grant: `0x01 | granter_address_len (1 byte) | granter_address_bytes | grantee_address_len (1 byte) | grantee_address_bytes | msgType_bytes -> ProtocolBuffer(AuthorizationGrant)` The grant object encapsulates an `Authorization` type and an expiration timestamp: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/authz/v1beta1/authz.proto#L23-L31 ``` ### GrantQueue We are maintaining a queue for authz pruning. Whenever a grant is created, an item will be added to `GrantQueue` with a key of expiration, granter, grantee. In `BeginBlock`, which runs for every block, the module prunes expired grants. It forms a prefix key from the current block time and matches the records in `GrantQueue` whose stored expiration has passed. It deletes those records from both the `GrantQueue` and the `Grant` store. Pruning is capped at 200 grants per block. Any remaining expired grants are pruned in later blocks. * GrantQueue: `0x02 | expiration_bytes | granter_address_len (1 byte) | granter_address_bytes | grantee_address_len (1 byte) | grantee_address_bytes -> ProtocolBuffer(GrantQueueItem)` The `expiration_bytes` are the expiration date in UTC with the format `"2006-01-02T15:04:05.000000000"`. The `GrantQueueItem` object contains the list of type urls between granter and grantee that expire at the time indicated in the key. ## Messages In this section we describe the processing of messages for the authz module. ### MsgGrant An authorization grant is created using the `MsgGrant` message. If there is already a grant for the `(granter, grantee, Authorization)` triple, then the new grant overwrites the previous one. To update or extend an existing grant, a new grant with the same `(granter, grantee, Authorization)` triple should be created. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/authz/v1beta1/tx.proto#L34-L44 ``` The message handling should fail if: * both granter and grantee have the same address. * provided `Expiration` time is less than current unix timestamp (but a grant will be created if no `expiration` time is provided since `expiration` is optional). * provided `Grant.Authorization` is not implemented. * `Authorization.MsgTypeURL()` is not defined in the router (there is no defined handler in the app router to handle that Msg types). ### MsgRevoke A grant can be removed with the `MsgRevoke` message. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/authz/v1beta1/tx.proto#L68-L77 ``` The message handling should fail if: * both granter and grantee have the same address. * provided `MsgTypeUrl` is empty. NOTE: The `MsgExec` message removes a grant if the grant has expired. ### MsgExec When a grantee wants to execute a transaction on behalf of a granter, they must send `MsgExec`. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/authz/v1beta1/tx.proto#L49-L61 ``` The message handling should fail if: * provided `Authorization` is not implemented. * grantee doesn't have permission to run the transaction. * if granted authorization is expired. ## Events The authz module emits proto events defined in [the Protobuf reference](https://buf.build/cosmos/cosmos-sdk/docs/main/cosmos.authz.v1beta1#cosmos.authz.v1beta1.EventGrant). ## Client ### CLI A user can query and interact with the `authz` module using the CLI. #### Query The `query` commands allow users to query `authz` state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query authz --help ``` ##### grants The `grants` command allows users to query grants for a granter-grantee pair. If the message type URL is set, it selects grants only for that message type. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query authz grants [granter-addr] [grantee-addr] [msg-type-url]? [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query authz grants cosmos1.. cosmos1.. /cosmos.bank.v1beta1.MsgSend ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grants: - authorization: '@type': /cosmos.bank.v1beta1.SendAuthorization spend_limit: - amount: "100" denom: stake expiration: "2022-01-01T00:00:00Z" pagination: null ``` #### Transactions The `tx` commands allow users to interact with the `authz` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz --help ``` ##### exec The `exec` command allows a grantee to execute a transaction on behalf of granter. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz exec [tx-json-file] --from [grantee] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz exec tx.json --from=cosmos1.. ``` ##### grant The `grant` command allows a granter to grant an authorization to a grantee. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz grant --from [flags] ``` * The `send` authorization\_type refers to the built-in `SendAuthorization` type. The custom flags available are `spend-limit` (required) and `allow-list` (optional) , documented [here](#SendAuthorization) Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz grant cosmos1.. send --spend-limit=100stake --allow-list=cosmos1...,cosmos2... --from=cosmos1.. ``` * The `generic` authorization\_type refers to the built-in `GenericAuthorization` type. The custom flag available is `msg-type` ( required) documented [here](#GenericAuthorization). > Note: `msg-type` is any valid Cosmos SDK `Msg` type url. Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz grant cosmos1.. generic --msg-type=/cosmos.bank.v1beta1.MsgSend --from=cosmos1.. ``` * The `delegate`,`unbond`,`redelegate` authorization\_types refer to the built-in `StakeAuthorization` type. The custom flags available are `spend-limit` (optional), `allowed-validators` (optional) and `deny-validators` (optional) documented [here](#StakeAuthorization). > Note: `allowed-validators` and `deny-validators` cannot both be empty. `spend-limit` represents the `MaxTokens` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz grant cosmos1.. delegate --spend-limit=100stake --allowed-validators=cosmos...,cosmos... --deny-validators=cosmos... --from=cosmos1.. ``` ##### revoke The `revoke` command allows a granter to revoke an authorization from a grantee. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz revoke [grantee] [msg-type-url] --from=[granter] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx authz revoke cosmos1.. /cosmos.bank.v1beta1.MsgSend --from=cosmos1.. ``` ### gRPC A user can query the `authz` module using gRPC endpoints. #### Grants The `Grants` endpoint allows users to query grants for a granter-grantee pair. If the message type URL is set, it selects grants only for that message type. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.authz.v1beta1.Query/Grants ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"granter":"cosmos1..","grantee":"cosmos1..","msg_type_url":"/cosmos.bank.v1beta1.MsgSend"}' \ localhost:9090 \ cosmos.authz.v1beta1.Query/Grants ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "grants": [ { "authorization": { "@type": "/cosmos.bank.v1beta1.SendAuthorization", "spendLimit": [ { "denom":"stake", "amount":"100" } ] }, "expiration": "2022-01-01T00:00:00Z" } ] } ``` ### REST A user can query the `authz` module using REST endpoints. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/authz/v1beta1/grants ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl "localhost:1317/cosmos/authz/v1beta1/grants?granter=cosmos1..&grantee=cosmos1..&msg_type_url=/cosmos.bank.v1beta1.MsgSend" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "grants": [ { "authorization": { "@type": "/cosmos.bank.v1beta1.SendAuthorization", "spend_limit": [ { "denom": "stake", "amount": "100" } ] }, "expiration": "2022-01-01T00:00:00Z" } ], "pagination": null } ``` # x/bank Source: https://docs.cosmos.network/sdk/latest/modules/bank/README This document specifies the bank module of the Cosmos SDK. ## Abstract This document specifies the bank module of the Cosmos SDK. The bank module is responsible for handling multi-asset coin transfers between accounts and tracking special-case pseudo-transfers which must work differently with particular kinds of accounts (notably delegating/undelegating for vesting accounts). It exposes several interfaces with varying capabilities for secure interaction with other modules which must alter user balances. In addition, the bank module tracks and provides query support for the total supply of all assets used in the application. This module is used in the Cosmos Hub. ## Contents * [Supply](#supply) * [Total Supply](#total-supply) * [Module Accounts](#module-accounts) * [Permissions](#permissions) * [State](#state) * [Params](#params) * [Keepers](#keepers) * [Messages](#messages) * [Events](#events) * [Message Events](#message-events) * [Keeper Events](#keeper-events) * [Parameters](#parameters) * [SendEnabled](#sendenabled) * [DefaultSendEnabled](#defaultsendenabled) * [Client](#client) * [CLI](#cli) * [Query](#query) * [Transactions](#transactions) * [gRPC](#grpc) ## Supply The `supply` functionality: * passively tracks the total supply of coins within a chain, * provides a pattern for modules to hold/interact with `Coins`, and * introduces the invariant check to verify a chain's total supply. ### Total Supply The total `Supply` of the network is equal to the sum of all coins from the account. The total supply is updated every time a `Coin` is minted (eg: as part of the inflation mechanism) or burned (eg: due to slashing or if a governance proposal is vetoed). ## Module Accounts The supply functionality introduces a new type of `auth.Account` which can be used by modules to allocate tokens and in special cases mint or burn tokens. At a base level these module accounts are capable of sending/receiving tokens to and from `auth.Account`s and other module accounts. This design replaces previous alternative designs where, to hold tokens, modules would burn the incoming tokens from the sender account, and then track those tokens internally. Later, in order to send tokens, the module would need to effectively mint tokens within a destination account. The new design removes duplicate logic between modules to perform this accounting. The `ModuleAccount` interface is defined as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ModuleAccount interface { auth.Account // same methods as the Account interface GetName() string // name of the module; used to obtain the address GetPermissions() []string // permissions of module account HasPermission(string) bool } ``` > **WARNING!** > Any module or message handler that allows either direct or indirect sending of funds must explicitly guarantee those funds cannot be sent to module accounts (unless allowed). The supply `Keeper` also introduces new wrapper functions for the auth `Keeper` and the bank `Keeper` that are related to `ModuleAccount`s in order to be able to: * Get and set `ModuleAccount`s by providing the `Name`. * Send coins from and to other `ModuleAccount`s or standard `Account`s (`BaseAccount` or `VestingAccount`) by passing only the `Name`. * `Mint` or `Burn` coins for a `ModuleAccount` (restricted to its permissions). ### Permissions Each `ModuleAccount` has a different set of permissions that provide different object capabilities to perform certain actions. Permissions need to be registered upon the creation of the supply `Keeper` so that every time a `ModuleAccount` calls the allowed functions, the `Keeper` can lookup the permissions to that specific account and perform or not perform the action. The available permissions are: * `Minter`: allows for a module to mint a specific amount of coins. * `Burner`: allows for a module to burn a specific amount of coins. * `Staking`: allows for a module to delegate and undelegate a specific amount of coins. ## State The `x/bank` module keeps state of the following primary objects: 1. Account balances 2. Denomination metadata 3. The total supply of all balances 4. Information on which denominations are allowed to be sent. In addition, the `x/bank` module keeps the following indexes to manage the aforementioned state: * Supply Index: `0x0 | byte(denom) -> byte(amount)` * Denom Metadata Index: `0x1 | byte(denom) -> ProtocolBuffer(Metadata)` * Balances Index: `0x2 | byte(address length) | []byte(address) | []byte(balance.Denom) -> ProtocolBuffer(balance)` * Reverse Denomination to Address Index: `0x03 | byte(denom) | 0x00 | []byte(address) -> 0` ## Params The bank module stores its params in state with the prefix of `0x05`, it can be updated with governance or the address with authority. * Params: `0x05 | ProtocolBuffer(Params)` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/bank.proto#L12-L22 ``` ## Keepers The bank module provides these exported keeper interfaces that can be passed to other modules that read or update account balances. Modules should use the least-permissive interface that provides the functionality they require. Best practices dictate careful review of `bank` module code to ensure that permissions are limited in the way that you expect. ### Denied Addresses The `x/bank` module accepts a map of addresses that are considered blocklisted from directly and explicitly receiving funds through means such as `MsgSend` and `MsgMultiSend` and direct API calls like `SendCoinsFromModuleToAccount`. Typically, these addresses are module accounts. If these addresses receive funds outside the expected rules of the state machine, invariants are likely to be broken and could result in a halted network. By providing the `x/bank` module with a blocklisted set of addresses, an error occurs for the operation if a user or client attempts to directly or indirectly send funds to a blocklisted account, for example, by using [IBC](/ibc/latest/intro). ### Common Types #### Input An input of a multiparty transfer ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Input models transaction input. message Input { string address = 1; repeated cosmos.base.v1beta1.Coin coins = 2; } ``` #### Output An output of a multiparty transfer. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Output models transaction outputs. message Output { string address = 1; repeated cosmos.base.v1beta1.Coin coins = 2; } ``` ### BaseKeeper The base keeper provides full-permission access: the ability to arbitrary modify any account's balance and mint or burn coins. Restricted permission to mint per module could be achieved by using baseKeeper with `WithMintCoinsRestriction` to give specific restrictions to mint (e.g. only minting certain denom). ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Keeper defines a module interface that facilitates the transfer of coins // between accounts. type Keeper interface { SendKeeper WithMintCoinsRestriction(MintingRestrictionFn) BaseKeeper InitGenesis(context.Context, *types.GenesisState) ExportGenesis(context.Context) *types.GenesisState GetSupply(ctx context.Context, denom string) sdk.Coin HasSupply(ctx context.Context, denom string) bool GetPaginatedTotalSupply(ctx context.Context, pagination *query.PageRequest) (sdk.Coins, *query.PageResponse, error) IterateTotalSupply(ctx context.Context, cb func(sdk.Coin) bool) GetDenomMetaData(ctx context.Context, denom string) (types.Metadata, bool) HasDenomMetaData(ctx context.Context, denom string) bool SetDenomMetaData(ctx context.Context, denomMetaData types.Metadata) IterateAllDenomMetaData(ctx context.Context, cb func(types.Metadata) bool) SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error DelegateCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error UndelegateCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error DelegateCoins(ctx context.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error UndelegateCoins(ctx context.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error // GetAuthority gets the address capable of executing governance proposal messages. Usually the gov module account. GetAuthority() string types.QueryServer } ``` ### SendKeeper The send keeper provides access to account balances and the ability to transfer coins between accounts. The send keeper does not alter the total supply (mint or burn coins). ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SendKeeper defines a module interface that facilitates the transfer of coins // between accounts without the possibility of creating coins. type SendKeeper interface { ViewKeeper AppendSendRestriction(restriction SendRestrictionFn) PrependSendRestriction(restriction SendRestrictionFn) ClearSendRestriction() InputOutputCoins(ctx context.Context, input types.Input, outputs []types.Output) error SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error GetParams(ctx context.Context) types.Params SetParams(ctx context.Context, params types.Params) error IsSendEnabledDenom(ctx context.Context, denom string) bool SetSendEnabled(ctx context.Context, denom string, value bool) SetAllSendEnabled(ctx context.Context, sendEnableds []*types.SendEnabled) DeleteSendEnabled(ctx context.Context, denom string) IterateSendEnabledEntries(ctx context.Context, cb func(denom string, sendEnabled bool) (stop bool)) GetAllSendEnabledEntries(ctx context.Context) []types.SendEnabled IsSendEnabledCoin(ctx context.Context, coin sdk.Coin) bool IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error BlockedAddr(addr sdk.AccAddress) bool } ``` #### Send Restrictions The `SendKeeper` applies a `SendRestrictionFn` before each transfer of funds. ```golang theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // A SendRestrictionFn can restrict sends and/or provide a new receiver address. type SendRestrictionFn func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (newToAddr sdk.AccAddress, err error) ``` After the `SendKeeper` (or `BaseKeeper`) has been created, send restrictions can be added to it using the `AppendSendRestriction` or `PrependSendRestriction` functions. Both functions compose the provided restriction with any previously provided restrictions. `AppendSendRestriction` adds the provided restriction to be run after any previously provided send restrictions. `PrependSendRestriction` adds the restriction to be run before any previously provided send restrictions. The composition will short-circuit when an error is encountered. I.e. if the first one returns an error, the second is not run. During `SendCoins`, the send restriction is applied before coins are removed from the from address and adding them to the to address. During `InputOutputCoins`, the send restriction is applied after the input coins are removed and once for each output before the funds are added. A send restriction function should make use of a custom value in the context to allow bypassing that specific restriction. Send Restrictions are not placed on `ModuleToAccount` or `ModuleToModule` transfers. This is done due to modules needing to move funds to user accounts and other module accounts. This is a design decision to allow for more flexibility in the state machine. The state machine should be able to move funds between module accounts and user accounts without restrictions. Secondly this limitation would limit the usage of the state machine even for itself. users would not be able to receive rewards, not be able to move funds between module accounts. In the case that a user sends funds from a user account to the community pool and then a governance proposal is used to get those tokens into the users account this would fall under the discretion of the app chain developer to what they would like to do here. We can not make strong assumptions here. Thirdly, this issue could lead into a chain halt if a token is disabled and the token is moved in the begin/endblock. This is the last reason we see the current change and more damaging then beneficial for users. For example, in your module's keeper package, you'd define the send restriction function: ```golang expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var _ banktypes.SendRestrictionFn = Keeper{ }.SendRestrictionFn func (k Keeper) SendRestrictionFn(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { // Bypass if the context says to. if mymodule.HasBypass(ctx) { return toAddr, nil } // Your custom send restriction logic goes here. return nil, errors.New("not implemented") } ``` The bank keeper should be provided to your keeper's constructor so the send restriction can be added to it: ```golang theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, bankKeeper mymodule.BankKeeper) Keeper { rv := Keeper{/*...*/ } bankKeeper.AppendSendRestriction(rv.SendRestrictionFn) return rv } ``` Then, in the `mymodule` package, define the context helpers: ```golang expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const bypassKey = "bypass-mymodule-restriction" // WithBypass returns a new context that will cause the mymodule bank send restriction to be skipped. func WithBypass(ctx context.Context) context.Context { return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, true) } // WithoutBypass returns a new context that will cause the mymodule bank send restriction to not be skipped. func WithoutBypass(ctx context.Context) context.Context { return sdk.UnwrapSDKContext(ctx).WithValue(bypassKey, false) } // HasBypass checks the context to see if the mymodule bank send restriction should be skipped. func HasBypass(ctx context.Context) bool { bypassValue := ctx.Value(bypassKey) if bypassValue == nil { return false } bypass, isBool := bypassValue.(bool) return isBool && bypass } ``` Now, anywhere where you want to use `SendCoins` or `InputOutputCoins`, but you don't want your send restriction applied: ```golang theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) DoThing(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error { return k.bankKeeper.SendCoins(mymodule.WithBypass(ctx), fromAddr, toAddr, amt) } ``` ### ViewKeeper The view keeper provides read-only access to account balances. The view keeper does not have balance alteration functionality. All balance lookups are `O(1)`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ViewKeeper defines a module interface that facilitates read only access to // account balances. type ViewKeeper interface { ValidateBalance(ctx context.Context, addr sdk.AccAddress) error HasBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin) bool GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins GetAccountsBalances(ctx context.Context) []types.Balance GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin LockedCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin IterateAccountBalances(ctx context.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool)) IterateAllBalances(ctx context.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool)) } ``` ## Messages ### MsgSend Send coins from one address to another. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L38-L54 ``` The message will fail under the following conditions: * The coins do not have sending enabled * The `to` address is restricted ### MsgMultiSend Send coins from one sender and to a series of different address. If any of the receiving addresses do not correspond to an existing account, a new account is created. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L59-L70 ``` The message will fail under the following conditions: * Any of the coins do not have sending enabled * Any of the `to` addresses are restricted * Any of the coins are locked * The inputs and outputs do not correctly correspond to one another ### MsgUpdateParams The `bank` module params can be updated through `MsgUpdateParams`, which can be done using governance proposal. The signer will always be the `gov` module account address. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L75-L88 ``` The message handling can fail if: * signer is not the gov module account address. ### MsgSetSendEnabled Used with the x/gov module to set create/edit SendEnabled entries. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/bank/v1beta1/tx.proto#L96-L117 ``` The message will fail under the following conditions: * The authority is not a bech32 address. * The authority is not x/gov module's address. * There are multiple SendEnabled entries with the same Denom. * One or more SendEnabled entries has an invalid Denom. ## Events The bank module emits the following events: ### Message Events #### MsgSend | Type | Attribute Key | Attribute Value | | -------- | ------------- | -------------------- | | transfer | recipient | `{recipientAddress}` | | transfer | amount | `{amount}` | | message | module | bank | | message | action | send | | message | sender | `{senderAddress}` | #### MsgMultiSend | Type | Attribute Key | Attribute Value | | -------- | ------------- | -------------------- | | transfer | recipient | `{recipientAddress}` | | transfer | amount | `{amount}` | | message | module | bank | | message | action | multisend | | message | sender | `{senderAddress}` | ### Keeper Events In addition to message events, the bank keeper will produce events when the following methods are called (or any method which ends up calling them) #### MintCoins ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "coinbase", "attributes": [ { "key": "minter", "value": "{{sdk.AccAddress of the module minting coins}}", "index": true }, { "key": "amount", "value": "{{sdk.Coins being minted}}", "index": true } ] } ``` ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "coin_received", "attributes": [ { "key": "receiver", "value": "{{sdk.AccAddress of the module minting coins}}", "index": true }, { "key": "amount", "value": "{{sdk.Coins being received}}", "index": true } ] } ``` #### BurnCoins ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "burn", "attributes": [ { "key": "burner", "value": "{{sdk.AccAddress of the module burning coins}}", "index": true }, { "key": "amount", "value": "{{sdk.Coins being burned}}", "index": true } ] } ``` ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "coin_spent", "attributes": [ { "key": "spender", "value": "{{sdk.AccAddress of the module burning coins}}", "index": true }, { "key": "amount", "value": "{{sdk.Coins being burned}}", "index": true } ] } ``` #### addCoins ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "coin_received", "attributes": [ { "key": "receiver", "value": "{{sdk.AccAddress of the address beneficiary of the coins}}", "index": true }, { "key": "amount", "value": "{{sdk.Coins being received}}", "index": true } ] } ``` #### subUnlockedCoins/DelegateCoins ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "coin_spent", "attributes": [ { "key": "spender", "value": "{{sdk.AccAddress of the address which is spending coins}}", "index": true }, { "key": "amount", "value": "{{sdk.Coins being spent}}", "index": true } ] } ``` ## Parameters The bank module contains the following parameters ### SendEnabled The SendEnabled parameter is now deprecated and not to be use. It is replaced with state store records. ### DefaultSendEnabled The default send enabled value controls send transfer capability for all coin denominations unless specifically included in the array of `SendEnabled` parameters. ## Client ### CLI A user can query and interact with the `bank` module using the CLI. #### Query The `query` commands allow users to query `bank` state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank --help ``` ##### balances The `balances` command allows users to query account balances by address. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank balances [address] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank balances cosmos1.. ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} balances: - amount: "1000000000" denom: stake pagination: next_key: null total: "0" ``` ##### denom-metadata The `denom-metadata` command allows users to query metadata for coin denominations. A user can query metadata for a single denomination using the `--denom` flag or all denominations without it. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank denom-metadata [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank denom-metadata --denom stake ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} metadata: base: stake denom_units: - aliases: - STAKE denom: stake description: native staking token of simulation app display: stake name: SimApp Token symbol: STK ``` ##### total The `total` command allows users to query the total supply of coins. A user can query the total supply for a single coin using the `--denom` flag or all coins without it. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank total [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank total --denom stake ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} amount: "10000000000" denom: stake ``` ##### send-enabled The `send-enabled` command allows users to query for all or some SendEnabled entries. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank send-enabled [denom1 ...] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank send-enabled ``` Example output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} send_enabled: - denom: foocoin enabled: true - denom: barcoin pagination: next-key: null total: 2 ``` #### Transactions The `tx` commands allow users to interact with the `bank` module. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank --help ``` ##### send The `send` command allows users to send funds from one account to another. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send [from_key_or_address] [to_address] [amount] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send cosmos1.. cosmos1.. 100stake ``` ## gRPC A user can query the `bank` module using gRPC endpoints. ### Balance The `Balance` endpoint allows users to query account balance by address for a given denomination. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/Balance ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address":"cosmos1..","denom":"stake"}' \ localhost:9090 \ cosmos.bank.v1beta1.Query/Balance ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "balance": { "denom": "stake", "amount": "1000000000" } } ``` ### AllBalances The `AllBalances` endpoint allows users to query account balance by address for all denominations. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/AllBalances ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address":"cosmos1.."}' \ localhost:9090 \ cosmos.bank.v1beta1.Query/AllBalances ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "balances": [ { "denom": "stake", "amount": "1000000000" } ], "pagination": { "total": "1" } } ``` ### DenomMetadata The `DenomMetadata` endpoint allows users to query metadata for a single coin denomination. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/DenomMetadata ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"denom":"stake"}' \ localhost:9090 \ cosmos.bank.v1beta1.Query/DenomMetadata ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "metadata": { "description": "native staking token of simulation app", "denomUnits": [ { "denom": "stake", "aliases": [ "STAKE" ] } ], "base": "stake", "display": "stake", "name": "SimApp Token", "symbol": "STK" } } ``` ### DenomsMetadata The `DenomsMetadata` endpoint allows users to query metadata for all coin denominations. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/DenomsMetadata ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.bank.v1beta1.Query/DenomsMetadata ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "metadatas": [ { "description": "native staking token of simulation app", "denomUnits": [ { "denom": "stake", "aliases": [ "STAKE" ] } ], "base": "stake", "display": "stake", "name": "SimApp Token", "symbol": "STK" } ], "pagination": { "total": "1" } } ``` ### DenomOwners The `DenomOwners` endpoint allows users to query metadata for a single coin denomination. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/DenomOwners ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"denom":"stake"}' \ localhost:9090 \ cosmos.bank.v1beta1.Query/DenomOwners ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "denomOwners": [ { "address": "cosmos1..", "balance": { "denom": "stake", "amount": "5000000000" } }, { "address": "cosmos1..", "balance": { "denom": "stake", "amount": "5000000000" } }, ], "pagination": { "total": "2" } } ``` ### TotalSupply The `TotalSupply` endpoint allows users to query the total supply of all coins. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/TotalSupply ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.bank.v1beta1.Query/TotalSupply ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "supply": [ { "denom": "stake", "amount": "10000000000" } ], "pagination": { "total": "1" } } ``` ### SupplyOf The `SupplyOf` endpoint allows users to query the total supply of a single coin. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/SupplyOf ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"denom":"stake"}' \ localhost:9090 \ cosmos.bank.v1beta1.Query/SupplyOf ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "amount": { "denom": "stake", "amount": "10000000000" } } ``` ### Params The `Params` endpoint allows users to query the parameters of the `bank` module. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/Params ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.bank.v1beta1.Query/Params ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "defaultSendEnabled": true } } ``` ### SendEnabled The `SendEnabled` enpoints allows users to query the SendEnabled entries of the `bank` module. Any denominations NOT returned, use the `Params.DefaultSendEnabled` value. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/SendEnabled ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.bank.v1beta1.Query/SendEnabled ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "send_enabled": [ { "denom": "foocoin", "enabled": true }, { "denom": "barcoin" } ], "pagination": { "next-key": null, "total": 2 } } ``` # x/circuit Source: https://docs.cosmos.network/sdk/latest/modules/circuit/README `x/circuit` has been moved to [`./contrib/x/circuit`](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/contrib/x/circuit) and is no longer actively maintained as part of the core Cosmos SDK. It is still available for use but is not included in the SDK Bug Bounty program. It was moved because it was never widely adopted. ## Concepts Circuit Breaker is a module that is meant to avoid a chain needing to halt/shut down in the presence of a vulnerability, instead the module will allow specific messages or all messages to be disabled. When operating a chain, if it is app specific then a halt of the chain is less detrimental, but if there are applications built on top of the chain then halting is expensive due to the disturbance to applications. Circuit Breaker works with the idea that an address or set of addresses have the right to block messages from being executed and/or included in the mempool. Any address with a permission is able to reset the circuit breaker for the message. The transactions are checked and can be rejected at two points: * In `CircuitBreakerDecorator` [ante handler](/sdk/latest/learn/concepts/baseapp#antehandler): ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package ante import ( "context" "github.com/cockroachdb/errors" sdk "github.com/cosmos/cosmos-sdk/types" ) // CircuitBreaker is an interface that defines the methods for a circuit breaker. type CircuitBreaker interface { IsAllowed(ctx context.Context, typeURL string) (bool, error) } // CircuitBreakerDecorator is an AnteDecorator that checks if the transaction type is allowed to enter the mempool or be executed type CircuitBreakerDecorator struct { circuitKeeper CircuitBreaker } func NewCircuitBreakerDecorator(ck CircuitBreaker) CircuitBreakerDecorator { return CircuitBreakerDecorator{ circuitKeeper: ck, } } func (cbd CircuitBreakerDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { // loop through all the messages and check if the message type is allowed for _, msg := range tx.GetMsgs() { isAllowed, err := cbd.circuitKeeper.IsAllowed(ctx, sdk.MsgTypeURL(msg)) if err != nil { return ctx, err } if !isAllowed { return ctx, errors.New("tx type not allowed") } } return next(ctx, tx, simulate) } ``` * With a [message router check](/sdk/latest/learn/concepts/baseapp#msg-service-router): ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package baseapp import ( "context" "fmt" gogogrpc "github.com/cosmos/gogoproto/grpc" "github.com/cosmos/gogoproto/proto" "google.golang.org/grpc" "google.golang.org/protobuf/runtime/protoiface" errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/baseapp/internal/protocompat" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // MessageRouter ADR 031 request type routing // https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md type MessageRouter interface { Handler(msg sdk.Msg) MsgServiceHandler HandlerByTypeURL(typeURL string) MsgServiceHandler } // MsgServiceRouter routes fully-qualified Msg service methods to their handler. type MsgServiceRouter struct { interfaceRegistry codectypes.InterfaceRegistry routes map[string]MsgServiceHandler hybridHandlers map[string]func(ctx context.Context, req, resp protoiface.MessageV1) error circuitBreaker CircuitBreaker } var _ gogogrpc.Server = &MsgServiceRouter{ } // NewMsgServiceRouter creates a new MsgServiceRouter. func NewMsgServiceRouter() *MsgServiceRouter { return &MsgServiceRouter{ routes: map[string]MsgServiceHandler{ }, hybridHandlers: map[string]func(ctx context.Context, req, resp protoiface.MessageV1) error{ }, } } func (msr *MsgServiceRouter) SetCircuit(cb CircuitBreaker) { msr.circuitBreaker = cb } // MsgServiceHandler defines a function type which handles Msg service message. type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) // Handler returns the MsgServiceHandler for a given msg or nil if not found. func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { return msr.routes[sdk.MsgTypeURL(msg)] } // HandlerByTypeURL returns the MsgServiceHandler for a given query route path or nil // if not found. func (msr *MsgServiceRouter) HandlerByTypeURL(typeURL string) MsgServiceHandler { return msr.routes[typeURL] } // RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC // service description, handler is an object which implements that gRPC service. // // This function PANICs: // - if it is called before the service `Msg`s have been registered using // RegisterInterfaces, // - or if a service is being registered twice. func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler interface{ }) { // Adds a top-level query handler based on the gRPC service name. for _, method := range sd.Methods { err := msr.registerMsgServiceHandler(sd, method, handler) if err != nil { panic(err) } err = msr.registerHybridHandler(sd, method, handler) if err != nil { panic(err) } } } func (msr *MsgServiceRouter) HybridHandlerByMsgName(msgName string) func(ctx context.Context, req, resp protoiface.MessageV1) error { return msr.hybridHandlers[msgName] } func (msr *MsgServiceRouter) registerHybridHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{ }) error { inputName, err := protocompat.RequestFullNameFromMethodDesc(sd, method) if err != nil { return err } cdc := codec.NewProtoCodec(msr.interfaceRegistry) hybridHandler, err := protocompat.MakeHybridHandler(cdc, sd, method, handler) if err != nil { return err } // if circuit breaker is not nil, then we decorate the hybrid handler with the circuit breaker if msr.circuitBreaker == nil { msr.hybridHandlers[string(inputName)] = hybridHandler return nil } // decorate the hybrid handler with the circuit breaker circuitBreakerHybridHandler := func(ctx context.Context, req, resp protoiface.MessageV1) error { messageName := codectypes.MsgTypeURL(req) allowed, err := msr.circuitBreaker.IsAllowed(ctx, messageName) if err != nil { return err } if !allowed { return fmt.Errorf("circuit breaker disallows execution of message %s", messageName) } return hybridHandler(ctx, req, resp) } msr.hybridHandlers[string(inputName)] = circuitBreakerHybridHandler return nil } func (msr *MsgServiceRouter) registerMsgServiceHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{ }) error { fqMethod := fmt.Sprintf("/%s/%s", sd.ServiceName, method.MethodName) methodHandler := method.Handler var requestTypeName string // NOTE: This is how we pull the concrete request type for each handler for registering in the InterfaceRegistry. // This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself. // We use a no-op interceptor to avoid actually calling into the handler itself. _, _ = methodHandler(nil, context.Background(), func(i interface{ }) error { msg, ok := i.(sdk.Msg) if !ok { // We panic here because there is no other alternative and the app cannot be initialized correctly // this should only happen if there is a problem with code generation in which case the app won't // work correctly anyway. panic(fmt.Errorf("unable to register service method %s: %T does not implement sdk.Msg", fqMethod, i)) } requestTypeName = sdk.MsgTypeURL(msg) return nil }, noopInterceptor) // Check that the service Msg fully-qualified method name has already // been registered (via RegisterInterfaces). If the user registers a // service without registering according service Msg type, there might be // some unexpected behavior down the road. Since we can't return an error // (`Server.RegisterService` interface restriction) we panic (at startup). reqType, err := msr.interfaceRegistry.Resolve(requestTypeName) if err != nil || reqType == nil { return fmt.Errorf( "type_url %s has not been registered yet. "+ "Before calling RegisterService, you must register all interfaces by calling the `RegisterInterfaces` "+ "method on module.BasicManager. Each module should call `msgservice.RegisterMsgServiceDesc` inside its "+ "`RegisterInterfaces` method with the `_Msg_serviceDesc` generated by proto-gen", requestTypeName, ) } // Check that each service is only registered once. If a service is // registered more than once, then we should error. Since we can't // return an error (`Server.RegisterService` interface restriction) we // panic (at startup). _, found := msr.routes[requestTypeName] if found { return fmt.Errorf( "msg service %s has already been registered. Please make sure to only register each service once. "+ "This usually means that there are conflicting modules registering the same msg service", fqMethod, ) } msr.routes[requestTypeName] = func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) interceptor := func(goCtx context.Context, _ interface{ }, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{ }, error) { goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx) return handler(goCtx, msg) } if m, ok := msg.(sdk.HasValidateBasic); ok { if err := m.ValidateBasic(); err != nil { return nil, err } } if msr.circuitBreaker != nil { msgURL := sdk.MsgTypeURL(msg) isAllowed, err := msr.circuitBreaker.IsAllowed(ctx, msgURL) if err != nil { return nil, err } if !isAllowed { return nil, fmt.Errorf("circuit breaker disables execution of this message: %s", msgURL) } } // Call the method handler from the service description with the handler object. // We don't do any decoding here because the decoding was already done. res, err := methodHandler(handler, ctx, noopDecoder, interceptor) if err != nil { return nil, err } resMsg, ok := res.(proto.Message) if !ok { return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidType, "Expecting proto.Message, got %T", resMsg) } return sdk.WrapServiceResult(ctx, resMsg, err) } return nil } // SetInterfaceRegistry sets the interface registry for the router. func (msr *MsgServiceRouter) SetInterfaceRegistry(interfaceRegistry codectypes.InterfaceRegistry) { msr.interfaceRegistry = interfaceRegistry } func noopDecoder(_ interface{ }) error { return nil } func noopInterceptor(_ context.Context, _ interface{ }, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{ }, error) { return nil, nil } ``` The `CircuitBreakerDecorator` works for most use cases, but [does not check the inner messages of a transaction](/sdk/latest/learn/concepts/lifecycle#antehandler). This some transactions (such as `x/authz` transactions or some `x/gov` transactions) may pass the ante handler. **This does not affect the circuit breaker** as the message router check will still fail the transaction. This tradeoff is to avoid introducing more dependencies in the `x/circuit` module. Chains can re-define the `CircuitBreakerDecorator` to check for inner messages if they wish to do so. ## State ### Accounts * AccountPermissions `0x1 | account_address -> ProtocolBuffer(CircuitBreakerPermissions)` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type level int32 const ( // LEVEL_NONE_UNSPECIFIED indicates that the account will have no circuit // breaker permissions. LEVEL_NONE_UNSPECIFIED = iota // LEVEL_SOME_MSGS indicates that the account will have permission to // trip or reset the circuit breaker for some Msg type URLs. If this level // is chosen, a non-empty list of Msg type URLs must be provided in // limit_type_urls. LEVEL_SOME_MSGS // LEVEL_ALL_MSGS indicates that the account can trip or reset the circuit // breaker for Msg's of all type URLs. LEVEL_ALL_MSGS // LEVEL_SUPER_ADMIN indicates that the account can take all circuit breaker // actions and can grant permissions to other accounts. LEVEL_SUPER_ADMIN ) type Access struct { level int32 msgs []string // if full permission, msgs can be empty } ``` ### Disable List List of type urls that are disabled. * DisableList `0x2 | msg_type_url -> []byte{}` ## State Transitions ### Authorize Authorize, is called by the module authority (default governance module account) or any account with `LEVEL_SUPER_ADMIN` to give permission to disable/enable messages to another account. There are three levels of permissions that can be granted. `LEVEL_SOME_MSGS` limits the number of messages that can be disabled. `LEVEL_ALL_MSGS` permits all messages to be disabled. `LEVEL_SUPER_ADMIN` allows an account to take all circuit breaker actions including authorizing and deauthorizing other accounts. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // AuthorizeCircuitBreaker allows a super-admin to grant (or revoke) another // account's circuit breaker permissions. rpc AuthorizeCircuitBreaker(MsgAuthorizeCircuitBreaker) returns (MsgAuthorizeCircuitBreakerResponse); ``` ### Trip Trip, is called by an authorized account to disable message execution for a specific msgURL. If empty, all the msgs will be disabled. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // TripCircuitBreaker pauses processing of Msg's in the state machine. rpc TripCircuitBreaker(MsgTripCircuitBreaker) returns (MsgTripCircuitBreakerResponse); ``` ### Reset Reset is called by an authorized account to enable execution for a specific msgURL of previously disabled message. If empty, all the disabled messages will be enabled. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ResetCircuitBreaker resumes processing of Msg's in the state machine that // have been paused using TripCircuitBreaker. rpc ResetCircuitBreaker(MsgResetCircuitBreaker) returns (MsgResetCircuitBreakerResponse); ``` ## Messages ### MsgAuthorizeCircuitBreaker ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/contrib/proto/circuit/v1/tx.proto#L25-L40 ``` This message is expected to fail if: * the granter is not an account with permission level `LEVEL_SUPER_ADMIN` or the module authority ### MsgTripCircuitBreaker ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/contrib/proto/circuit/v1/tx.proto#L47-L60 ``` This message is expected to fail if: * if the signer does not have a permission level with the ability to disable the specified type url message ### MsgResetCircuitBreaker ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/contrib/proto/circuit/v1/tx.proto#L67-L78 ``` This message is expected to fail if: * if the type url is not disabled ## Events - list and describe event tags The circuit module emits the following events: ### Message Events #### MsgAuthorizeCircuitBreaker | Type | Attribute Key | Attribute Value | | ------- | ------------- | --------------------------- | | string | granter | `{granterAddress}` | | string | grantee | `{granteeAddress}` | | string | permission | `{granteePermissions}` | | message | module | circuit | | message | action | authorize\_circuit\_breaker | #### MsgTripCircuitBreaker | Type | Attribute Key | Attribute Value | | --------- | ------------- | ---------------------- | | string | authority | `{authorityAddress}` | | \[]string | msg\_urls | \[]string`{msg\_urls}` | | message | module | circuit | | message | action | trip\_circuit\_breaker | #### ResetCircuitBreaker | Type | Attribute Key | Attribute Value | | --------- | ------------- | ----------------------- | | string | authority | `{authorityAddress}` | | \[]string | msg\_urls | \[]string`{msg\_urls}` | | message | module | circuit | | message | action | reset\_circuit\_breaker | ## Keys - list of key prefixes used by the circuit module * `AccountPermissionPrefix` - `0x01` * `DisableListPrefix` - `0x02` ## Client - list and describe CLI commands and gRPC and REST endpoints ## Examples: Using Circuit Breaker CLI Commands This section provides practical examples for using the Circuit Breaker module through the command-line interface (CLI). These examples demonstrate how to authorize accounts, disable (trip) specific message types, and re-enable (reset) them when needed. ### Querying Circuit Breaker Permissions Check an account's current circuit breaker permissions: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query permissions for a specific account query circuit account-permissions # Example: simd query circuit account-permissions cosmos1... ``` Check which message types are currently disabled: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query all disabled message types query circuit disabled-list # Example: simd query circuit disabled-list ``` ### Authorizing an Account as Circuit Breaker Only a super-admin or the module authority (typically the governance module account) can grant circuit breaker permissions to other accounts: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Grant LEVEL_ALL_MSGS permission (can disable any message type) tx circuit authorize --level=ALL_MSGS --from= --gas=auto --gas-adjustment=1.5 # Grant LEVEL_SOME_MSGS permission (can only disable specific message types) tx circuit authorize --level=SOME_MSGS --limit-type-urls="/cosmos.bank.v1beta1.MsgSend,/cosmos.staking.v1beta1.MsgDelegate" --from= --gas=auto --gas-adjustment=1.5 # Grant LEVEL_SUPER_ADMIN permission (can disable messages and authorize other accounts) tx circuit authorize --level=SUPER_ADMIN --from= --gas=auto --gas-adjustment=1.5 ``` ### Disabling Message Processing (Trip) Disable specific message types to prevent their execution (requires authorization): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Disable a single message type tx circuit trip --type-urls="/cosmos.bank.v1beta1.MsgSend" --from= --gas=auto --gas-adjustment=1.5 # Disable multiple message types tx circuit trip --type-urls="/cosmos.bank.v1beta1.MsgSend,/cosmos.staking.v1beta1.MsgDelegate" --from= --gas=auto --gas-adjustment=1.5 # Disable all message types (emergency measure) tx circuit trip --from= --gas=auto --gas-adjustment=1.5 ``` ### Re-enabling Message Processing (Reset) Re-enable previously disabled message types (requires authorization): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Re-enable a single message type tx circuit reset --type-urls="/cosmos.bank.v1beta1.MsgSend" --from= --gas=auto --gas-adjustment=1.5 # Re-enable multiple message types tx circuit reset --type-urls="/cosmos.bank.v1beta1.MsgSend,/cosmos.staking.v1beta1.MsgDelegate" --from= --gas=auto --gas-adjustment=1.5 # Re-enable all disabled message types tx circuit reset --from= --gas=auto --gas-adjustment=1.5 ``` ### Usage in Emergency Scenarios In case of a critical vulnerability in a specific message type: 1. Quickly disable the vulnerable message type: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} tx circuit trip --type-urls="/cosmos.vulnerable.v1beta1.MsgVulnerable" --from= --gas=auto --gas-adjustment=1.5 ``` 2. After a fix is deployed, re-enable the message type: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} tx circuit reset --type-urls="/cosmos.vulnerable.v1beta1.MsgVulnerable" --from= --gas=auto --gas-adjustment=1.5 ``` This allows chains to surgically disable problematic functionality without halting the entire chain, providing time for developers to implement and deploy fixes. # x/consensus Source: https://docs.cosmos.network/sdk/latest/modules/consensus/README Functionality to modify CometBFT's ABCI consensus params. The `x/consensus` module allows governance to update CometBFT's ABCI consensus parameters on a live chain without a software upgrade. ## Consensus Parameters The module manages the following CometBFT consensus parameters: ### Block Parameters | Parameter | Description | | ---------- | ------------------------------------------ | | `MaxBytes` | Maximum block size in bytes | | `MaxGas` | Maximum gas per block (`-1` for unlimited) | ### Evidence Parameters | Parameter | Description | | ----------------- | ---------------------------------------------- | | `MaxAgeNumBlocks` | Maximum age of evidence in blocks | | `MaxAgeDuration` | Maximum age of evidence as a duration | | `MaxBytes` | Maximum total evidence size per block in bytes | ### Validator Parameters | Parameter | Description | | ------------- | ------------------------------------------------------------------------------------ | | `PubKeyTypes` | Supported public key types for validators (e.g., `ed25519`, `secp256k1`, `bls12381`) | ### ABCI Parameters | Parameter | Description | | ---------------------------- | ------------------------------------------------------------------ | | `VoteExtensionsEnableHeight` | Block height at which vote extensions are enabled (`0` to disable) | ## Messages ### MsgUpdateParams Updates consensus parameters via governance. All of `block`, `evidence`, and `validator` must be provided. `abci` is optional. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} msg := &types.MsgUpdateParams{ Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), Block: &cmtproto.BlockParams{ MaxBytes: 200000, MaxGas: 100000000, }, Evidence: &cmtproto.EvidenceParams{ MaxAgeNumBlocks: 302400, MaxAgeDuration: 504 * time.Hour, MaxBytes: 10000, }, Validator: &cmtproto.ValidatorParams{ PubKeyTypes: []string{"ed25519"}, }, Abci: &cmtproto.ABCIParams{ VoteExtensionsEnableHeight: 0, }, } ``` ## AuthorityParams Authority management can be centralized via the `x/consensus` module using `AuthorityParams`. The `AuthorityParams` field in `ConsensusParams` stores the authority address on-chain. When set, it takes precedence over the per-keeper authority parameter. Keeper constructors still accept the `authority` parameter. It is used as a fallback when no authority is configured in consensus params. ### How It Works When a module validates authority (e.g., in `UpdateParams`), it checks consensus params first. If no authority is set there, it falls back to the keeper's `authority` field: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} authority := sdkCtx.Authority() // from consensus params if authority == "" { authority = k.authority // fallback to keeper field } if authority != msg.Authority { return nil, errors.Wrapf(...) } ``` To enable centralized authority, set the `AuthorityParams` in consensus params via a governance proposal targeting the `x/consensus` module's `MsgUpdateParams`. ## CLI ### Query #### params Query the current consensus parameters: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query consensus params ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} params: abci: vote_extensions_enable_height: "0" block: max_bytes: "200000" max_gas: "-1" evidence: max_age_duration: 1814400s max_age_num_blocks: "302400" max_bytes: "10000" validator: pub_key_types: - ed25519 ``` ### Transactions #### update-params-proposal Submit a governance proposal to update consensus parameters: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx consensus update-params-proposal [block] [evidence] [validator] [abci] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx consensus update-params-proposal \ '{"max_bytes":"200000","max_gas":"100000000"}' \ '{"max_age_num_blocks":"302400","max_age_duration":"1814400s","max_bytes":"10000"}' \ '{"pub_key_types":["ed25519"]}' \ '{"vote_extensions_enable_height":"0"}' \ --from mykey ``` ## gRPC ### Params Query the current consensus parameters: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.consensus.v1.Query/Params ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "block": { "maxBytes": "200000", "maxGas": "-1" }, "evidence": { "maxAgeNumBlocks": "302400", "maxAgeDuration": "1814400s", "maxBytes": "10000" }, "validator": { "pubKeyTypes": ["ed25519"] }, "abci": { "voteExtensionsEnableHeight": "0" } } } ``` ## REST ``` GET /cosmos/consensus/v1/params ``` # x/crisis Source: https://docs.cosmos.network/sdk/latest/modules/crisis/README x/crisis has been moved to ./contrib/x/crisis and is no longer part of the core Cosmos SDK. `x/crisis` has been moved to [`./contrib/x/crisis`](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/contrib/x/crisis) and is no longer actively maintained as part of the core Cosmos SDK. It is still available for use but is not included in the SDK Bug Bounty program. The module was moved because it never worked as intended. ## Overview The crisis module halts the blockchain under the circumstance that a blockchain invariant is broken. Invariants can be registered with the application during the application initialization process. ## Contents * [State](#state) * [Messages](#messages) * [Events](#events) * [Parameters](#parameters) * [Client](#client) * [CLI](#cli) ## State ### ConstantFee Due to the anticipated large gas cost requirement to verify an invariant (and potential to exceed the maximum allowable block gas limit) a constant fee is used instead of the standard gas consumption method. The constant fee is intended to be larger than the anticipated gas cost of running the invariant with the standard gas consumption method. The ConstantFee param is stored in the module params state with the prefix of `0x01`, it can be updated with governance or the address with authority. * ConstantFee: `0x01 -> ProtocolBuffer(Coin)` ## Messages In this section we describe the processing of the crisis messages and the corresponding updates to the state. ### MsgVerifyInvariant Blockchain invariants can be checked using the `MsgVerifyInvariant` message. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/contrib/proto/crisis/v1beta1/tx.proto#L26-L42 ``` This message is expected to fail if: * the sender does not have enough coins for the constant fee * the invariant route is not registered This message checks the invariant provided, and if the invariant is broken it panics, halting the blockchain. If the invariant is broken, the constant fee is never deducted as the transaction is never committed to a block (equivalent to being refunded). However, if the invariant is not broken, the constant fee will not be refunded. ## Events The crisis module emits the following events: ### Handlers #### MsgVerifyInvariant | Type | Attribute Key | Attribute Value | | --------- | ------------- | ------------------ | | invariant | route | `{invariantRoute}` | | message | module | crisis | | message | action | verify\_invariant | | message | sender | `{senderAddress}` | ## Parameters The crisis module contains the following parameters: | Key | Type | Example | | ----------- | ------------- | ----------------------------------- | | ConstantFee | object (coin) | `{"denom":"uatom","amount":"1000"}` | ## Client ### CLI A user can query and interact with the `crisis` module using the CLI. #### Transactions The `tx` commands allow users to interact with the `crisis` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx crisis --help ``` ##### invariant-broken The `invariant-broken` command submits proof when an invariant was broken to halt the chain ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx crisis invariant-broken [module-name] [invariant-route] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx crisis invariant-broken bank total-supply --from=[keyname or address] ``` # x/distribution Source: https://docs.cosmos.network/sdk/latest/modules/distribution/README ## Overview This *simple* distribution mechanism describes a functional way to passively distribute rewards between validators and delegators. Note that this mechanism does not distribute funds in as precisely as active reward distribution mechanisms and will therefore be upgraded in the future. The mechanism operates as follows. Collected rewards are pooled globally and divided out passively to validators and delegators. Each validator has the opportunity to charge commission to the delegators on the rewards collected on behalf of the delegators. Fees are collected directly into a global reward pool and validator proposer-reward pool. Due to the nature of passive accounting, whenever changes to parameters which affect the rate of reward distribution occurs, withdrawal of rewards must also occur. * Whenever withdrawing, one must withdraw the maximum amount they are entitled to, leaving nothing in the pool. * Whenever bonding, unbonding, or re-delegating tokens to an existing account, a full withdrawal of the rewards must occur (as the rules for lazy accounting change). * Whenever a validator chooses to change the commission on rewards, all accumulated commission rewards must be simultaneously withdrawn. The above scenarios are covered in `hooks.md`. The distribution mechanism outlined herein is used to lazily distribute the following rewards between validators and associated delegators: * multi-token fees to be socially distributed * inflated staked asset provisions * validator commission on all rewards earned by their delegators stake Fees are pooled within a global pool. The mechanisms used allow for validators and delegators to independently and lazily withdraw their rewards. ## Shortcomings As a part of the lazy computations, each delegator holds an accumulation term specific to each validator which is used to estimate what their approximate fair portion of tokens held in the global fee pool is owed to them. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} entitlement = delegator-accumulation / all-delegators-accumulation ``` Under the circumstance that there was constant and equal flow of incoming reward tokens every block, this distribution mechanism would be equal to the active distribution (distribute individually to all delegators each block). However, this is unrealistic so deviations from the active distribution will occur based on fluctuations of incoming reward tokens as well as timing of reward withdrawal by other delegators. If you happen to know that incoming rewards are about to significantly increase, you are incentivized to not withdraw until after this event, increasing the worth of your existing *accum*. See [#2764](https://github.com/cosmos/cosmos-sdk/issues/2764) for further details. ## Effect on Staking Charging commission on Atom provisions while also allowing for Atom-provisions to be auto-bonded (distributed directly to the validators bonded stake) is problematic within BPoS. Fundamentally, these two mechanisms are mutually exclusive. If both commission and auto-bonding mechanisms are simultaneously applied to the staking-token then the distribution of staking-tokens between any validator and its delegators will change with each block. This then necessitates a calculation for each delegation records for each block - which is considered computationally expensive. In conclusion, we can only have Atom commission and unbonded atoms provisions or bonded atom provisions with no Atom commission, and we elect to implement the former. Stakeholders wishing to rebond their provisions may elect to set up a script to periodically withdraw and rebond rewards. ## Contents * [Concepts](#concepts) * [State](#state) * [FeePool](#feepool) * [Validator Distribution](#validator-distribution) * [Delegation Distribution](#delegation-distribution) * [Params](#params) * [Begin Block](#begin-block) * [Messages](#messages) * [Hooks](#hooks) * [Events](#events) * [Parameters](#parameters) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) ## Concepts In Proof of Stake (PoS) blockchains, rewards gained from transaction fees are paid to validators. The fee distribution module fairly distributes the rewards to the validators' constituent delegators. Rewards are calculated per period. The period is updated each time a validator's delegation changes, for example, when the validator receives a new delegation. The rewards for a single validator can then be calculated by taking the total rewards for the period before the delegation started, minus the current total rewards. To learn more, see the [F1 Fee Distribution paper](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/spec/fee_distribution/f1_fee_distr.pdf). The commission to the validator is paid when the validator is removed or when the validator requests a withdrawal. The commission is calculated and incremented at every `BeginBlock` operation to update accumulated fee amounts. The rewards to a delegator are distributed when the delegation is changed or removed, or a withdrawal is requested. Before rewards are distributed, all slashes to the validator that occurred during the current delegation are applied. ### Reference Counting in F1 Fee Distribution In F1 fee distribution, the rewards a delegator receives are calculated when their delegation is withdrawn. This calculation must read the terms of the summation of rewards divided by the share of tokens from the period which they ended when they delegated, and the final period that was created for the withdrawal. Additionally, as slashes change the amount of tokens a delegation will have (but we calculate this lazily, only when a delegator un-delegates), we must calculate rewards in separate periods before / after any slashes which occurred in between when a delegator delegated and when they withdrew their rewards. Thus slashes, like delegations, reference the period which was ended by the slash event. All stored historical rewards records for periods which are no longer referenced by any delegations or any slashes can thus be safely removed, as they will never be read (future delegations and future slashes will always reference future periods). This is implemented by tracking a `ReferenceCount` along with each historical reward storage entry. Each time a new object (delegation or slash) is created which might need to reference the historical record, the reference count is incremented. Each time one object which previously needed to reference the historical record is deleted, the reference count is decremented. If the reference count hits zero, the historical record is deleted. ## State ### FeePool All globally tracked parameters for distribution are stored within `FeePool`. Rewards are collected and added to the reward pool and distributed to validators/delegators from here. Note that the reward pool holds decimal coins (`DecCoins`) to allow for fractions of coins to be received from operations like inflation. When coins are distributed from the pool they are truncated back to `sdk.Coins` which are non-decimal. * FeePool: `0x00 -> ProtocolBuffer(FeePool)` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // coins with decimal type DecCoins []DecCoin type DecCoin struct { Amount math.LegacyDec Denom string } ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/distribution.proto#L117-L124 ``` ### Validator Distribution Validator distribution information for the relevant validator is updated each time: 1. delegation amount to a validator is updated, 2. any delegator withdraws from a validator, or 3. the validator withdraws its commission. * ValidatorDistInfo: `0x02 | ValOperatorAddrLen (1 byte) | ValOperatorAddr -> ProtocolBuffer(validatorDistribution)` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ValidatorDistInfo struct { OperatorAddress sdk.AccAddress SelfBondRewards sdkmath.DecCoins ValidatorCommission types.ValidatorAccumulatedCommission } ``` ### Delegation Distribution Each delegation distribution only needs to record the height at which it last withdrew fees. Because a delegation must withdraw fees each time it's properties change (aka bonded tokens etc.) its properties will remain constant and the delegator's *accumulation* factor can be calculated passively knowing only the height of the last withdrawal and its current properties. * DelegationDistInfo: `0x02 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValOperatorAddrLen (1 byte) | ValOperatorAddr -> ProtocolBuffer(delegatorDist)` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type DelegationDistInfo struct { WithdrawalHeight int64 // last time this delegation withdrew rewards } ``` ### Params The distribution module stores its params in state with the prefix of `0x09`, it can be updated with governance or the address with authority. * Params: `0x09 | ProtocolBuffer(Params)` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/distribution.proto#L12-L44 ``` ## Begin Block At each `BeginBlock`, all fees received in the previous block are transferred to the distribution `ModuleAccount` account. When a delegator or validator withdraws their rewards, they are taken out of the `ModuleAccount`. During begin block, the different claims on the fees collected are updated as follows: * The reserve community tax is charged. * The remainder is distributed proportionally by voting power to all bonded validators ### The Distribution Scheme See [params](#params) for description of parameters. Let `fees` be the total fees collected in the previous block, including inflationary rewards to the stake. All fees are collected in a specific module account during the block. During `BeginBlock`, they are sent to the `"distribution"` `ModuleAccount`. No other sending of tokens occurs. Instead, the rewards each account is entitled to are stored, and withdrawals can be triggered through the messages `FundCommunityPool`, `WithdrawValidatorCommission` and `WithdrawDelegatorReward`. #### Reward to the Community Pool The community pool gets `community_tax * fees`, plus any remaining dust after validators get their rewards that are always rounded down to the nearest integer value. #### Reward To the Validators The proposer receives no extra rewards. All fees are distributed among all the bonded validators, including the proposer, in proportion to their consensus power. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} powFrac = validator power / total bonded validator power voteMul = 1 - community_tax ``` All validators receive `fees * voteMul * powFrac`. #### Rewards to Delegators Each validator's rewards are distributed to its delegators. The validator also has a self-delegation that is treated like a regular delegation in distribution calculations. The validator sets a commission rate. The commission rate is flexible, but each validator sets a maximum rate and a maximum daily increase. These maximums cannot be exceeded and protect delegators from sudden increases of validator commission rates to prevent validators from taking all of the rewards. The outstanding rewards that the operator is entitled to are stored in `ValidatorAccumulatedCommission`, while the rewards the delegators are entitled to are stored in `ValidatorCurrentRewards`. The [F1 fee distribution scheme](#concepts) is used to calculate the rewards per delegator as they withdraw or update their delegation, and is thus not handled in `BeginBlock`. #### Example Distribution For this example distribution, the underlying consensus engine selects block proposers in proportion to their power relative to the entire bonded power. All validators are equally performant at including pre-commits in their proposed blocks. Then hold `(pre_commits included) / (total bonded validator power)` constant so that the amortized block reward for the validator is `( validator power / total bonded power) * (1 - community tax rate)` of the total rewards. Consequently, the reward for a single delegator is: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} (delegator proportion of the validator power / validator power) * (validator power / total bonded power) * (1 - community tax rate) * (1 - validator commission rate) = (delegator proportion of the validator power / total bonded power) * (1 - community tax rate) * (1 - validator commission rate) ``` ## Messages ### MsgSetWithdrawAddress By default, the withdraw address is the delegator address. To change its withdraw address, a delegator must send a `MsgSetWithdrawAddress` message. Changing the withdraw address is possible only if the parameter `WithdrawAddrEnabled` is set to `true`. The withdraw address cannot be any of the module accounts. The distribution keeper does not track these itself; it asks the bank keeper through `BlockedAddr`, so the blocked set is the one bank maintains. A blocked withdraw address is handled differently depending on how the withdrawal is triggered. A withdrawal triggered by a user message fails with `ErrUnauthorized`. An automatic withdrawal during `BeginBlock` or `EndBlock` does not fail. Instead, the funds fall back to the owner's own address. The owner is the delegator for rewards, or the validator for commission. If the owner's address is also blocked, the funds go to the community pool. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/tx.proto#L55-L66 ``` ### MsgWithdrawDelegatorReward A delegator can withdraw its rewards. Internally in the distribution module, this transaction simultaneously removes the previous delegation with associated rewards, the same as if the delegator simply started a new delegation of the same value. The rewards are sent immediately from the distribution `ModuleAccount` to the withdraw address. Any remainder (truncated decimals) are sent to the community pool. The starting height of the delegation is set to the current validator period, and the reference count for the previous period is decremented. The amount withdrawn is deducted from the `ValidatorOutstandingRewards` variable for the validator. In the F1 distribution, the total rewards are calculated per validator period, and a delegator receives a piece of those rewards in proportion to their stake in the validator. In basic F1, the total rewards that all the delegators are entitled to between to periods is calculated the following way. Let `R(X)` be the total accumulated rewards up to period `X` divided by the tokens staked at that time. The delegator allocation is `R(X) * delegator_stake`. Then the rewards for all the delegators for staking between periods `A` and `B` are `(R(B) - R(A)) * total stake`. However, these calculated rewards don't account for slashing. Taking the slashes into account requires iteration. Let `F(X)` be the fraction a validator is to be slashed for a slashing event that happened at period `X`. If the validator was slashed at periods `P1, ..., PN`, where `A < P1`, `PN < B`, the distribution module calculates the individual delegator's rewards, `T(A, B)`, as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} stake := initial stake rewards := 0 previous := A for P in P1, ..., PN`: rewards = (R(P) - previous) * stake stake = stake * F(P) previous = P rewards = rewards + (R(B) - R(PN)) * stake ``` The historical rewards are calculated retroactively by playing back all the slashes and then attenuating the delegator's stake at each step. The final calculated stake is equivalent to the actual staked coins in the delegation with a margin of error due to rounding errors. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/tx.proto#L72-L83 ``` ### WithdrawValidatorCommission The validator can send the WithdrawValidatorCommission message to withdraw their accumulated commission. The commission is calculated in every block during `BeginBlock`, so no iteration is required to withdraw. The amount withdrawn is deducted from the `ValidatorOutstandingRewards` variable for the validator. Only integer amounts can be sent. If the accumulated awards have decimals, the amount is truncated before the withdrawal is sent, and the remainder is left to be withdrawn later. ### FundCommunityPool This message sends coins directly from the sender to the community pool. The transaction fails if the amount cannot be transferred from the sender to the distribution module account. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) FundCommunityPool(ctx context.Context, amount sdk.Coins, sender sdk.AccAddress) error { if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount); err != nil { return err } feePool, err := k.FeePool.Get(ctx) if err != nil { return err } feePool.CommunityPool = feePool.CommunityPool.Add(sdk.NewDecCoinsFromCoins(amount...)...) if err := k.FeePool.Set(ctx, feePool); err != nil { return err } return nil } ``` ### Common distribution operations These operations take place during many different messages. #### Initialize delegation Each time a delegation is changed, the rewards are withdrawn and the delegation is reinitialized. Initializing a delegation increments the validator period and keeps track of the starting period of the delegation. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // initialize starting info for a new delegation func (k Keeper) initializeDelegation(ctx context.Context, val sdk.ValAddress, del sdk.AccAddress) { // period has already been incremented - we want to store the period ended by this delegation action previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1 // increment reference count for the period we're going to track k.incrementReferenceCount(ctx, val, previousPeriod) validator := k.stakingKeeper.Validator(ctx, val) delegation := k.stakingKeeper.Delegation(ctx, del, val) // calculate delegation stake in tokens // we don't store directly, so multiply delegation shares * (tokens per share) // note: necessary to truncate so we don't allow withdrawing more rewards than owed stake := validator.TokensFromSharesTruncated(delegation.GetShares()) k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight()))) } ``` ### MsgUpdateParams Distribution module params can be updated through `MsgUpdateParams`, which can be done using governance proposal and the signer will always be gov module account address. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/distribution/v1beta1/tx.proto#L142-L155 ``` The message handling can fail if: * signer is not the gov module account address. ## Hooks Available hooks that can be called by and from this module. ### Create or modify delegation distribution * triggered-by: `staking.MsgDelegate`, `staking.MsgBeginRedelegate`, `staking.MsgUndelegate` #### Before * The delegation rewards are withdrawn to the withdraw address of the delegator. The rewards include the current period and exclude the starting period. * The validator period is incremented. The validator period is incremented because the validator's power and share distribution might have changed. * The reference count for the delegator's starting period is decremented. #### After The starting height of the delegation is set to the previous period. Because of the `Before`-hook, this period is the last period for which the delegator was rewarded. ### Validator created * triggered-by: `staking.MsgCreateValidator` When a validator is created, the following validator variables are initialized: * Historical rewards * Current accumulated rewards * Accumulated commission * Total outstanding rewards * Period By default, all values are set to a `0`, except period, which is set to `1`. ### Validator removed * triggered-by: `staking.RemoveValidator` Outstanding commission is sent to the validator's self-delegation withdrawal address. Remaining delegator rewards get sent to the community fee pool. Note: The validator gets removed only when it has no remaining delegations. At that time, all outstanding delegator rewards will have been withdrawn. Any remaining rewards are dust amounts. ### Validator is slashed * triggered-by: `staking.Slash` * The current validator period reference count is incremented. The reference count is incremented because the slash event has created a reference to it. * The validator period is incremented. * The slash event is stored for later use. The slash event will be referenced when calculating delegator rewards. ## Events The distribution module emits the following events: ### BeginBlocker | Type | Attribute Key | Attribute Value | | ---------------- | ------------- | -------------------- | | proposer\_reward | validator | `{validatorAddress}` | | proposer\_reward | reward | `{proposerReward}` | | commission | amount | `{commissionAmount}` | | commission | validator | `{validatorAddress}` | | rewards | amount | `{rewardAmount}` | | rewards | validator | `{validatorAddress}` | ### Handlers #### MsgSetWithdrawAddress | Type | Attribute Key | Attribute Value | | ---------------------- | ----------------- | ---------------------- | | set\_withdraw\_address | withdraw\_address | `{withdrawAddress}` | | message | module | distribution | | message | action | set\_withdraw\_address | | message | sender | `{senderAddress}` | #### MsgWithdrawDelegatorReward | Type | Attribute Key | Attribute Value | | ----------------- | ------------- | --------------------------- | | withdraw\_rewards | amount | `{rewardAmount}` | | withdraw\_rewards | validator | `{validatorAddress}` | | message | module | distribution | | message | action | withdraw\_delegator\_reward | | message | sender | `{senderAddress}` | #### MsgWithdrawValidatorCommission | Type | Attribute Key | Attribute Value | | -------------------- | ------------- | ------------------------------- | | withdraw\_commission | amount | `{commissionAmount}` | | message | module | distribution | | message | action | withdraw\_validator\_commission | | message | sender | `{senderAddress}` | ## Parameters The distribution module contains the following parameters: | Key | Type | Example | | ------------------- | ------------ | --------------------------- | | communitytax | string (dec) | "0.020000000000000000" \[0] | | withdrawaddrenabled | bool | true | * \[0] `communitytax` must be positive and cannot exceed 1.00. * `baseproposerreward` and `bonusproposerreward` were parameters that are deprecated in v0.47 and are not used. The reserve pool is the pool of collected funds for use by governance taken via the `CommunityTax`. Currently with the Cosmos SDK, tokens collected by the CommunityTax are accounted for but unspendable. ## Client ## CLI A user can query and interact with the `distribution` module using the CLI. #### Query The `query` commands allow users to query `distribution` state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution --help ``` ##### commission The `commission` command allows users to query validator commission rewards by address. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution commission [address] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution commission cosmosvaloper1... ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} commission: - amount: "1000000.000000000000000000" denom: stake ``` ##### community-pool The `community-pool` command allows users to query all coin balances within the community pool. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution community-pool [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution community-pool ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pool: - amount: "1000000.000000000000000000" denom: stake ``` ##### params The `params` command allows users to query the parameters of the `distribution` module. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution params [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution params ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} base_proposer_reward: "0.000000000000000000" bonus_proposer_reward: "0.000000000000000000" community_tax: "0.020000000000000000" withdraw_addr_enabled: true ``` ##### rewards The `rewards` command allows users to query delegator rewards. Users can optionally include the validator address to query rewards earned from a specific validator. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution rewards [delegator-addr] [validator-addr] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution rewards cosmos1... ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rewards: - reward: - amount: "1000000.000000000000000000" denom: stake validator_address: cosmosvaloper1.. total: - amount: "1000000.000000000000000000" denom: stake ``` ##### slashes The `slashes` command allows users to query all slashes for a given block range. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution slashes [validator] [start-height] [end-height] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution slashes cosmosvaloper1... 1 1000 ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "0" slashes: - validator_period: 20, fraction: "0.009999999999999999" ``` ##### validator-outstanding-rewards The `validator-outstanding-rewards` command allows users to query all outstanding (un-withdrawn) rewards for a validator and all their delegations. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution validator-outstanding-rewards [validator] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution validator-outstanding-rewards cosmosvaloper1... ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rewards: - amount: "1000000.000000000000000000" denom: stake ``` ##### validator-distribution-info The `validator-distribution-info` command allows users to query validator commission and self-delegation rewards for validator. ```shell expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution validator-distribution-info cosmosvaloper1... ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} commission: - amount: "100000.000000000000000000" denom: stake operator_address: cosmosvaloper1... self_bond_rewards: - amount: "100000.000000000000000000" denom: stake ``` ##### validator-historical-rewards The `validator-historical-rewards` command allows users to query historical rewards for a validator at a specific period. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution validator-historical-rewards [validator] [period] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution validator-historical-rewards cosmosvaloper1... 5 ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rewards: cumulative_reward_ratio: - amount: "1000000.000000000000000000" denom: stake reference_count: 2 ``` ##### validator-current-rewards The `validator-current-rewards` command allows users to query current rewards for a validator. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution validator-current-rewards [validator] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution validator-current-rewards cosmosvaloper1... ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rewards: period: "3" rewards: - amount: "1000000.000000000000000000" denom: stake ``` ##### delegator-starting-info The `delegator-starting-info` command allows users to query the starting info for a delegator on a given validator. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution delegator-starting-info [delegator-address] [validator-address] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query distribution delegator-starting-info cosmos1... cosmosvaloper1... ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} starting_info: creation_height: "10" previous_period: "2" stake: "1000000.000000000000000000" ``` #### Transactions The `tx` commands allow users to interact with the `distribution` module. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution --help ``` ##### fund-community-pool The `fund-community-pool` command allows users to send funds to the community pool. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution fund-community-pool [amount] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution fund-community-pool 100stake --from cosmos1... ``` ##### set-withdraw-addr The `set-withdraw-addr` command allows users to set the withdraw address for rewards associated with a delegator address. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution set-withdraw-addr [withdraw-addr] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution set-withdraw-addr cosmos1... --from cosmos1... ``` ##### withdraw-all-rewards The `withdraw-all-rewards` command allows users to withdraw all rewards for a delegator. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution withdraw-all-rewards [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution withdraw-all-rewards --from cosmos1... ``` ##### withdraw-rewards The `withdraw-rewards` command allows users to withdraw all rewards from a given delegation address, and optionally withdraw validator commission if the delegation address given is a validator operator and the user proves the `--commission` flag. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution withdraw-rewards [validator-addr] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx distribution withdraw-rewards cosmosvaloper1... --from cosmos1... --commission ``` ### gRPC A user can query the `distribution` module using gRPC endpoints. #### Params The `Params` endpoint allows users to query parameters of the `distribution` module. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.distribution.v1beta1.Query/Params ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "communityTax": "20000000000000000", "baseProposerReward": "00000000000000000", "bonusProposerReward": "00000000000000000", "withdrawAddrEnabled": true } } ``` #### ValidatorDistributionInfo The `ValidatorDistributionInfo` queries validator commission and self-delegation rewards for validator. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"validator_address":"cosmosvalop1..."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/ValidatorDistributionInfo ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "commission": { "commission": [ { "denom": "stake", "amount": "1000000000000000" } ] }, "self_bond_rewards": [ { "denom": "stake", "amount": "1000000000000000" } ], "validator_address": "cosmosvalop1..." } ``` #### ValidatorOutstandingRewards The `ValidatorOutstandingRewards` endpoint allows users to query rewards of a validator address. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"validator_address":"cosmosvalop1.."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/ValidatorOutstandingRewards ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "rewards": { "rewards": [ { "denom": "stake", "amount": "1000000000000000" } ] } } ``` #### ValidatorCommission The `ValidatorCommission` endpoint allows users to query accumulated commission for a validator. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"validator_address":"cosmosvalop1.."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/ValidatorCommission ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "commission": { "commission": [ { "denom": "stake", "amount": "1000000000000000" } ] } } ``` #### ValidatorSlashes The `ValidatorSlashes` endpoint allows users to query slash events of a validator. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"validator_address":"cosmosvalop1.."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/ValidatorSlashes ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "slashes": [ { "validator_period": "20", "fraction": "0.009999999999999999" } ], "pagination": { "total": "1" } } ``` #### DelegationRewards The `DelegationRewards` endpoint allows users to query the total rewards accrued by a delegation. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_address":"cosmos1...","validator_address":"cosmosvalop1..."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/DelegationRewards ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "rewards": [ { "denom": "stake", "amount": "1000000000000000" } ] } ``` #### DelegationTotalRewards The `DelegationTotalRewards` endpoint allows users to query the total rewards accrued by each validator. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_address":"cosmos1..."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/DelegationTotalRewards ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "rewards": [ { "validatorAddress": "cosmosvaloper1...", "reward": [ { "denom": "stake", "amount": "1000000000000000" } ] } ], "total": [ { "denom": "stake", "amount": "1000000000000000" } ] } ``` #### DelegatorValidators The `DelegatorValidators` endpoint allows users to query all validators for given delegator. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_address":"cosmos1..."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/DelegatorValidators ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validators": ["cosmosvaloper1..."] } ``` #### DelegatorWithdrawAddress The `DelegatorWithdrawAddress` endpoint allows users to query the withdraw address of a delegator. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_address":"cosmos1..."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/DelegatorWithdrawAddress ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "withdrawAddress": "cosmos1..." } ``` #### CommunityPool The `CommunityPool` endpoint allows users to query the community pool coins. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.distribution.v1beta1.Query/CommunityPool ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "pool": [ { "denom": "stake", "amount": "1000000000000000000" } ] } ``` #### ValidatorHistoricalRewards The `ValidatorHistoricalRewards` endpoint allows users to query historical rewards for a validator at a specific period. This is useful for debugging reward calculations by inspecting internal distribution state. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"validator_address":"cosmosvaloper1...","period":"5"}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/ValidatorHistoricalRewards ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "rewards": { "cumulativeRewardRatio": [ { "denom": "stake", "amount": "1000000000000000" } ], "referenceCount": 2 } } ``` #### ValidatorCurrentRewards The `ValidatorCurrentRewards` endpoint allows users to query current rewards for a validator. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"validator_address":"cosmosvaloper1..."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/ValidatorCurrentRewards ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "rewards": { "rewards": [ { "denom": "stake", "amount": "1000000000000000" } ], "period": "3" } } ``` #### DelegatorStartingInfo The `DelegatorStartingInfo` endpoint allows users to query the starting info for a delegator on a given validator. Combined with `ValidatorHistoricalRewards`, this enables verification of reward calculations by retrieving the previous period and stake, then looking up cumulative reward ratios for that period. Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_address":"cosmos1...","validator_address":"cosmosvaloper1..."}' \ localhost:9090 \ cosmos.distribution.v1beta1.Query/DelegatorStartingInfo ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "startingInfo": { "previousPeriod": "2", "stake": "1000000000000000000", "creationHeight": "10" } } ``` # x/epochs Source: https://docs.cosmos.network/sdk/latest/modules/epochs/README ## Abstract Often in the SDK, we would like to run certain code every-so often. The purpose of `epochs` module is to allow other modules to set that they would like to be signaled once every period. So another module can specify it wants to execute code once a week, starting at UTC-time = x. `epochs` creates a generalized epoch interface to other modules so that they can easily be signaled upon such events. ## Contents 1. **[Concept](#concepts)** 2. **[State](#state)** 3. **[Events](#events)** 4. **[Keeper](#keepers)** 5. **[Hooks](#hooks)** 6. **[Queries](#queries)** ## Concepts The epochs module defines on-chain timers that execute at fixed time intervals. Other SDK modules can then register logic to be executed at the timer ticks. We refer to the period in between two timer ticks as an "epoch". Every timer has a unique identifier. Every epoch will have a start time, and an end time, where `end time = start time + timer interval`. On mainnet, we only utilize one identifier, with a time interval of `one day`. The timer will tick at the first block whose block time is greater than the timer end time, and set the start as the prior timer end time. (Notably, it's not set to the block time!) This means that if the chain has been down for a while, you will get one timer tick per block, until the timer has caught up. ## State The Epochs module keeps a single `EpochInfo` per identifier. This contains the current state of the timer with the corresponding identifier. Its fields are modified at every timer tick. EpochInfos are initialized as part of genesis initialization or upgrade logic, and are only modified on begin blockers. ## Events The `epochs` module emits the following events: ### BeginBlocker | Type | Attribute Key | Attribute Value | | ------------ | ------------- | ----------------- | | epoch\_start | epoch\_number | `{epoch\_number}` | | epoch\_start | start\_time | `{start\_time}` | ### EndBlocker | Type | Attribute Key | Attribute Value | | ---------- | ------------- | ----------------- | | epoch\_end | epoch\_number | `{epoch\_number}` | ## Keepers ### Keeper functions Epochs keeper module provides utility functions to manage epochs. ## Hooks ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // the first block whose timestamp is after the duration is counted as the end of the epoch AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) // new epoch is next block of epoch end block BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNumber int64) ``` ### How modules receive hooks On hook receiver function of other modules, they need to filter `epochIdentifier` and only do executions for only specific epochIdentifier. Filtering epochIdentifier could be in `Params` of other modules so that they can be modified by governance. This is the standard dev UX of this: ```golang theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k MyModuleKeeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) { params := k.GetParams(ctx) if epochIdentifier == params.DistrEpochIdentifier { // my logic } } ``` ### Panic isolation If a given epoch hook panics, its state update is reverted, but we keep proceeding through the remaining hooks. This allows more advanced epoch logic to be used, without concern over state machine halting, or halting subsequent modules. This does mean that if there is behavior you expect from a prior epoch hook, and that epoch hook reverted, your hook may also have an issue. So do keep in mind "what if a prior hook didn't get executed" in the safety checks you consider for a new epoch hook. ## Queries The Epochs module provides the following queries to check the module's state. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service Query { // EpochInfos provide running epochInfos rpc EpochInfos(QueryEpochsInfoRequest) returns (QueryEpochsInfoResponse) {} // CurrentEpoch provide current epoch of specified identifier rpc CurrentEpoch(QueryCurrentEpochRequest) returns (QueryCurrentEpochResponse) {} } ``` ### Epoch Infos Query the currently running epochInfos ```sh theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} query epochs epoch-infos ``` **Example** An example output: ```sh expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} epochs: - current_epoch: "183" current_epoch_start_height: "2438409" current_epoch_start_time: "2021-12-18T17:16:09.898160996Z" duration: 86400s epoch_counting_started: true identifier: day start_time: "2021-06-18T17:00:00Z" - current_epoch: "26" current_epoch_start_height: "2424854" current_epoch_start_time: "2021-12-17T17:02:07.229632445Z" duration: 604800s epoch_counting_started: true identifier: week start_time: "2021-06-18T17:00:00Z" ``` ### Current Epoch Query the current epoch by the specified identifier ```sh theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} query epochs current-epoch [identifier] ``` **Example** Query the current `day` epoch: ```sh theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} query epochs current-epoch day ``` Which in this example outputs: ```sh theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} current_epoch: "183" ``` # x/evidence Source: https://docs.cosmos.network/sdk/latest/modules/evidence/README Concepts State Messages Events Parameters BeginBlock Client CLI REST gRPC * [Concepts](#concepts) * [State](#state) * [Messages](#messages) * [Events](#events) * [Parameters](#parameters) * [BeginBlock](#beginblock) * [Client](#client) * [CLI](#cli) * [REST](#rest) * [gRPC](#grpc) ## Abstract `x/evidence` is an implementation of a Cosmos SDK module, per [ADR 009](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-009-evidence-module.md), that allows for the submission and handling of arbitrary evidence of misbehavior such as equivocation and counterfactual signing. The evidence module differs from standard evidence handling which typically expects the underlying consensus engine, e.g. CometBFT, to automatically submit evidence when it is discovered by allowing clients and foreign chains to submit more complex evidence directly. All concrete evidence types must implement the `Evidence` interface contract. Submitted `Evidence` is first routed through the evidence module's `Router` in which it attempts to find a corresponding registered `Handler` for that specific `Evidence` type. Each `Evidence` type must have a `Handler` registered with the evidence module's keeper in order for it to be successfully routed and executed. Each corresponding handler must also fulfill the `Handler` interface contract. The `Handler` for a given `Evidence` type can perform any arbitrary state transitions such as slashing, jailing, and tombstoning. ## Concepts ### Evidence Any concrete type of evidence submitted to the `x/evidence` module must fulfill the `Evidence` contract outlined below. Not all concrete types of evidence will fulfill this contract in the same way and some data may be entirely irrelevant to certain types of evidence. An additional `ValidatorEvidence`, which extends `Evidence`, has also been created to define a contract for evidence against malicious validators. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Evidence defines the contract which concrete evidence types of misbehavior // must implement. type Evidence interface { proto.Message Route() string String() string Hash() []byte ValidateBasic() error // Height at which the infraction occurred GetHeight() int64 } // ValidatorEvidence extends Evidence interface to define contract // for evidence against malicious validators type ValidatorEvidence interface { Evidence // The consensus address of the malicious validator at time of infraction GetConsensusAddress() sdk.ConsAddress // The total power of the malicious validator at time of infraction GetValidatorPower() int64 // The total validator set power at time of infraction GetTotalPower() int64 } ``` ### Registration & Handling The `x/evidence` module must first know about all types of evidence it is expected to handle. This is accomplished by registering the `Route` method in the `Evidence` contract with what is known as a `Router` (defined below). The `Router` accepts `Evidence` and attempts to find the corresponding `Handler` for the `Evidence` via the `Route` method. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Router interface { AddRoute(r string, h Handler) Router HasRoute(r string) bool GetRoute(path string) Handler Seal() Sealed() bool } ``` The `Handler` (defined below) is responsible for executing the entirety of the business logic for handling `Evidence`. This typically includes validating the evidence, both stateless checks via `ValidateBasic` and stateful checks via any keepers provided to the `Handler`. In addition, the `Handler` may also perform capabilities such as slashing and jailing a validator. All `Evidence` handled by the `Handler` should be persisted. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Handler defines an agnostic Evidence handler. The handler is responsible // for executing all corresponding business logic necessary for verifying the // evidence as valid. In addition, the Handler may execute any necessary // slashing and potential jailing. type Handler func(context.Context, Evidence) error ``` ## State Currently the `x/evidence` module only stores valid submitted `Evidence` in state. The evidence state is also stored and exported in the `x/evidence` module's `GenesisState`. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // GenesisState defines the evidence module's genesis state. message GenesisState { // evidence defines all the evidence at genesis. repeated google.protobuf.Any evidence = 1; } ``` All `Evidence` is retrieved and stored via a prefix `KVStore` using prefix `0x00` (`KeyPrefixEvidence`). ## Messages ### MsgSubmitEvidence Evidence is submitted through a `MsgSubmitEvidence` message: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MsgSubmitEvidence represents a message that supports submitting arbitrary // Evidence of misbehavior such as equivocation or counterfactual signing. message MsgSubmitEvidence { string submitter = 1; google.protobuf.Any evidence = 2; } ``` Note, the `Evidence` of a `MsgSubmitEvidence` message must have a corresponding `Handler` registered with the `x/evidence` module's `Router` in order to be processed and routed correctly. Given the `Evidence` is registered with a corresponding `Handler`, it is processed as follows: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func SubmitEvidence(ctx Context, evidence Evidence) error { if _, err := GetEvidence(ctx, evidence.Hash()); err == nil { return errorsmod.Wrap(types.ErrEvidenceExists, strings.ToUpper(hex.EncodeToString(evidence.Hash()))) } if !router.HasRoute(evidence.Route()) { return errorsmod.Wrap(types.ErrNoEvidenceHandlerExists, evidence.Route()) } handler := router.GetRoute(evidence.Route()) if err := handler(ctx, evidence); err != nil { return errorsmod.Wrap(types.ErrInvalidEvidence, err.Error()) } ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSubmitEvidence, sdk.NewAttribute(types.AttributeKeyEvidenceHash, strings.ToUpper(hex.EncodeToString(evidence.Hash()))), ), ) SetEvidence(ctx, evidence) return nil } ``` First, there must not already exist valid submitted `Evidence` of the exact same type. Secondly, the `Evidence` is routed to the `Handler` and executed. Finally, if there is no error in handling the `Evidence`, an event is emitted and it is persisted to state. ## Events The `x/evidence` module emits the following events: ### Handlers #### MsgSubmitEvidence | Type | Attribute Key | Attribute Value | | ---------------- | -------------- | ----------------- | | submit\_evidence | evidence\_hash | `{evidenceHash}` | | message | module | evidence | | message | sender | `{senderAddress}` | | message | action | submit\_evidence | ## Parameters The evidence module does not contain any parameters. ## BeginBlock ### Evidence Handling CometBFT blocks can include [Evidence](https://github.com/cometbft/cometbft/blob/v0.40.x/spec/abci/abci%2B%2B_basic_concepts.md#evidence) that indicates if a validator committed malicious behavior. The relevant information is forwarded to the application as ABCI Evidence in `abci.RequestBeginBlock` so that the validator can be punished accordingly. #### Equivocation The Cosmos SDK handles two types of evidence inside the ABCI `BeginBlock`: * `DuplicateVoteEvidence`, * `LightClientAttackEvidence`. The evidence module handles these two evidence types the same way. First, the Cosmos SDK converts the CometBFT concrete evidence type to an SDK `Evidence` interface using `Equivocation` as the concrete type. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/evidence/v1beta1/evidence.proto#L12-L31 ``` For some `Equivocation` submitted in `block` to be valid, it must satisfy: `Evidence.Timestamp >= block.Timestamp - MaxEvidenceAge` Where: * `Evidence.Timestamp` is the timestamp in the block at height `Evidence.Height` * `block.Timestamp` is the current block timestamp. If valid `Equivocation` evidence is included in a block, the validator's stake is reduced (slashed) by `SlashFractionDoubleSign` as defined by the `x/slashing` module of what their stake was when the infraction occurred, rather than when the evidence was discovered. We want to "follow the stake", i.e., the stake that contributed to the infraction should be slashed, even if it has since been redelegated or started unbonding. In addition, the validator is permanently jailed and tombstoned to make it impossible for that validator to ever re-enter the validator set. The `Equivocation` evidence is handled as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/x/evidence/keeper/infraction.go#L13-L155 ``` **Note:** The slashing, jailing, and tombstoning calls are delegated through the `x/slashing` module that emits informative events and finally delegates calls to the `x/staking` module. See documentation on slashing and jailing in [State Transitions](/sdk/latest/modules/staking/README#state-transitions). ## Client ### CLI A user can query and interact with the `evidence` module using the CLI. #### Query The `query` command allows users to query `evidence` state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query evidence --help ``` #### evidence The `evidence` command allows users to list all evidence or evidence by hash. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query evidence evidence [flags] ``` To query evidence by hash Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query evidence evidence "DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660" ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evidence: consensus_address: cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h height: 11 power: 100 time: "2021-10-20T16:08:38.194017624Z" ``` To get all evidence Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query evidence list ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evidence: consensus_address: cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h height: 11 power: 100 time: "2021-10-20T16:08:38.194017624Z" pagination: next_key: null total: "1" ``` ### REST A user can query the `evidence` module using REST endpoints. #### Evidence Get evidence by hash ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/evidence/v1beta1/evidence/{hash} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/evidence/v1beta1/evidence/DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660" ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "evidence": { "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h", "height": "11", "power": "100", "time": "2021-10-20T16:08:38.194017624Z" } } ``` #### All evidence Get all evidence ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/evidence/v1beta1/evidence ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/evidence/v1beta1/evidence" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "evidence": [ { "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h", "height": "11", "power": "100", "time": "2021-10-20T16:08:38.194017624Z" } ], "pagination": { "total": "1" } } ``` ### gRPC A user can query the `evidence` module using gRPC endpoints. #### Evidence Get evidence by hash ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.evidence.v1beta1.Query/Evidence ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"evidence_hash":"DF0C23E8634E480F84B9D5674A7CDC9816466DEC28A3358F73260F68D28D7660"}' localhost:9090 cosmos.evidence.v1beta1.Query/Evidence ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "evidence": { "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h", "height": "11", "power": "100", "time": "2021-10-20T16:08:38.194017624Z" } } ``` #### All evidence Get all evidence ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.evidence.v1beta1.Query/AllEvidence ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.evidence.v1beta1.Query/AllEvidence ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "evidence": [ { "consensus_address": "cosmosvalcons1ntk8eualewuprz0gamh8hnvcem2nrcdsgz563h", "height": "11", "power": "100", "time": "2021-10-20T16:08:38.194017624Z" } ], "pagination": { "total": "1" } } ``` # x/feegrant Source: https://docs.cosmos.network/sdk/latest/modules/feegrant/README This document specifies the fee grant module. For the full ADR, please see Fee Grant ADR-029. ## Abstract This document specifies the fee grant module. For the full ADR, please see [Fee Grant ADR-029](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-029-fee-grant-module.md). This module allows accounts to grant fee allowances and to use fees from their accounts. Grantees can execute any transaction without the need to maintain sufficient fees. ## Contents * [Concepts](#concepts) * [State](#state) * [FeeAllowance](#feeallowance) * [FeeAllowanceQueue](#feeallowancequeue) * [Messages](#messages) * [Msg/GrantAllowance](#msggrantallowance) * [Msg/RevokeAllowance](#msgrevokeallowance) * [Events](#events) * [Msg Server](#msg-server) * [MsgGrantAllowance](#msggrantallowance-1) * [MsgRevokeAllowance](#msgrevokeallowance-1) * [Exec fee allowance](#exec-fee-allowance) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) ## Concepts ### Grant `Grant` is stored in the KVStore to record a grant with full context. Every grant will contain `granter`, `grantee` and what kind of `allowance` is granted. `granter` is an account address who is giving permission to `grantee` (the beneficiary account address) to pay for some or all of `grantee`'s transaction fees. `allowance` defines what kind of fee allowance is granted to `grantee`. `allowance` accepts an interface which implements `FeeAllowanceI`, encoded as `Any` type. There can be only one existing fee grant allowed for a `grantee` and `granter`, self grants are not allowed. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/feegrant/v1beta1/feegrant.proto#L85-L95 ``` `FeeAllowanceI` looks like: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package feegrant import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" ) // FeeAllowance implementations are tied to a given fee delegator and delegatee, // and are used to enforce fee grant limits. type FeeAllowanceI interface { // Accept can use fee payment requested as well as timestamp of the current block // to determine whether or not to process this. This is checked in // Keeper.UseGrantedFees and the return values should match how it is handled there. // // If it returns an error, the fee payment is rejected, otherwise it is accepted. // The FeeAllowance implementation is expected to update its internal state // and will be saved again after an acceptance. // // If remove is true (regardless of the error), the FeeAllowance will be deleted from storage // (eg. when it is used up). (See call to RevokeAllowance in Keeper.UseGrantedFees) Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg) (remove bool, err error) // ValidateBasic should evaluate this FeeAllowance for internal consistency. // Don't allow negative amounts, or negative periods for example. ValidateBasic() error // ExpiresAt returns the expiry time of the allowance. ExpiresAt() (*time.Time, error) } ``` ### Fee Allowance types There are three types of fee allowances: * `BasicAllowance` * `PeriodicAllowance` * `AllowedMsgAllowance` ### BasicAllowance `BasicAllowance` is permission for `grantee` to use fee from a `granter`'s account. If any of the `spend_limit` or `expiration` reaches its limit, the grant will be removed from the state. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/feegrant/v1beta1/feegrant.proto#L14-L32 ``` * `spend_limit` is the limit of coins that are allowed to be used from the `granter` account. If it is empty, it assumes there's no spend limit, `grantee` can use any number of available coins from `granter` account address before the expiration. * `expiration` specifies an optional time when this allowance expires. If the value is left empty, there is no expiry for the grant. * When a grant is created with empty values for `spend_limit` and `expiration`, it is still a valid grant. It won't restrict the `grantee` to use any number of coins from `granter` and it won't have any expiration. The only way to restrict the `grantee` is by revoking the grant. ### PeriodicAllowance `PeriodicAllowance` is a repeating fee allowance for the mentioned period, we can mention when the grant can expire as well as when a period can reset. We can also define the maximum number of coins that can be used in a mentioned period of time. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/feegrant/v1beta1/feegrant.proto#L34-L70 ``` * `basic` is the instance of `BasicAllowance` which is optional for periodic fee allowance. If empty, the grant will have no `expiration` and no `spend_limit`. * `period` is the specific period of time, after each period passes, `period_can_spend` will be reset. * `period_spend_limit` specifies the maximum number of coins that can be spent in the period. * `period_can_spend` is the number of coins left to be spent before the period\_reset time. * `period_reset` keeps track of when a next period reset should happen. ### AllowedMsgAllowance `AllowedMsgAllowance` is a fee allowance, it can be any of `BasicFeeAllowance`, `PeriodicAllowance` but restricted only to the allowed messages mentioned by the granter. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/feegrant/v1beta1/feegrant.proto#L72-L83 ``` * `allowance` is either `BasicAllowance` or `PeriodicAllowance`. * `allowed_messages` is array of messages allowed to execute the given allowance. ### FeeGranter flag `feegrant` module introduces a `FeeGranter` flag for CLI for the sake of executing transactions with fee granter. When this flag is set, `clientCtx` will append the granter account address for transactions generated through CLI. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package client import ( "crypto/tls" "fmt" "strings" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/tendermint/tendermint/libs/cli" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" ) // ClientContextKey defines the context key used to retrieve a client.Context from // a command's Context. const ClientContextKey = sdk.ContextKey("client.context") // SetCmdClientContextHandler is to be used in a command pre-hook execution to // read flags that populate a Context and sets that to the command's Context. func SetCmdClientContextHandler(clientCtx Context, cmd *cobra.Command) (err error) { clientCtx, err = ReadPersistentCommandFlags(clientCtx, cmd.Flags()) if err != nil { return err } return SetCmdClientContext(cmd, clientCtx) } // ValidateCmd returns unknown command error or Help display if help flag set func ValidateCmd(cmd *cobra.Command, args []string) error { var unknownCmd string var skipNext bool for _, arg := range args { // search for help flag if arg == "--help" || arg == "-h" { return cmd.Help() } // check if the current arg is a flag switch { case len(arg) > 0 && (arg[0] == '-'): // the next arg should be skipped if the current arg is a // flag and does not use "=" to assign the flag's value if !strings.Contains(arg, "=") { skipNext = true } else { skipNext = false } case skipNext: // skip current arg skipNext = false case unknownCmd == "": // unknown command found // continue searching for help flag unknownCmd = arg } } // return the help screen if no unknown command is found if unknownCmd != "" { err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", unknownCmd, cmd.CalledAs()) // build suggestions for unknown argument if suggestions := cmd.SuggestionsFor(unknownCmd); len(suggestions) > 0 { err += "\n\nDid you mean this?\n" for _, s := range suggestions { err += fmt.Sprintf("\t%v\n", s) } } return errors.New(err) } return cmd.Help() } // ReadPersistentCommandFlags returns a Context with fields set for "persistent" // or common flags that do not necessarily change with context. // // Note, the provided clientCtx may have field pre-populated. The following order // of precedence occurs: // // - client.Context field not pre-populated & flag not set: uses default flag value // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { if clientCtx.OutputFormat == "" || flagSet.Changed(cli.OutputFlag) { output, _ := flagSet.GetString(cli.OutputFlag) clientCtx = clientCtx.WithOutputFormat(output) } if clientCtx.HomeDir == "" || flagSet.Changed(flags.FlagHome) { homeDir, _ := flagSet.GetString(flags.FlagHome) clientCtx = clientCtx.WithHomeDir(homeDir) } if !clientCtx.Simulate || flagSet.Changed(flags.FlagDryRun) { dryRun, _ := flagSet.GetBool(flags.FlagDryRun) clientCtx = clientCtx.WithSimulation(dryRun) } if clientCtx.KeyringDir == "" || flagSet.Changed(flags.FlagKeyringDir) { keyringDir, _ := flagSet.GetString(flags.FlagKeyringDir) // The keyring directory is optional and falls back to the home directory // if omitted. if keyringDir == "" { keyringDir = clientCtx.HomeDir } clientCtx = clientCtx.WithKeyringDir(keyringDir) } if clientCtx.ChainID == "" || flagSet.Changed(flags.FlagChainID) { chainID, _ := flagSet.GetString(flags.FlagChainID) clientCtx = clientCtx.WithChainID(chainID) } if clientCtx.Keyring == nil || flagSet.Changed(flags.FlagKeyringBackend) { keyringBackend, _ := flagSet.GetString(flags.FlagKeyringBackend) if keyringBackend != "" { kr, err := NewKeyringFromBackend(clientCtx, keyringBackend) if err != nil { return clientCtx, err } clientCtx = clientCtx.WithKeyring(kr) } } if clientCtx.Client == nil || flagSet.Changed(flags.FlagNode) { rpcURI, _ := flagSet.GetString(flags.FlagNode) if rpcURI != "" { clientCtx = clientCtx.WithNodeURI(rpcURI) client, err := NewClientFromNode(rpcURI) if err != nil { return clientCtx, err } clientCtx = clientCtx.WithClient(client) } } if clientCtx.GRPCClient == nil || flagSet.Changed(flags.FlagGRPC) { grpcURI, _ := flagSet.GetString(flags.FlagGRPC) if grpcURI != "" { var dialOpts []grpc.DialOption useInsecure, _ := flagSet.GetBool(flags.FlagGRPCInsecure) if useInsecure { dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) } else { dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ MinVersion: tls.VersionTLS12, }))) } grpcClient, err := grpc.Dial(grpcURI, dialOpts...) if err != nil { return Context{ }, err } clientCtx = clientCtx.WithGRPCClient(grpcClient) } } return clientCtx, nil } // readQueryCommandFlags returns an updated Context with fields set based on flags // defined in AddQueryFlagsToCmd. An error is returned if any flag query fails. // // Note, the provided clientCtx may have field pre-populated. The following order // of precedence occurs: // // - client.Context field not pre-populated & flag not set: uses default flag value // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value func readQueryCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { if clientCtx.Height == 0 || flagSet.Changed(flags.FlagHeight) { height, _ := flagSet.GetInt64(flags.FlagHeight) clientCtx = clientCtx.WithHeight(height) } if !clientCtx.UseLedger || flagSet.Changed(flags.FlagUseLedger) { useLedger, _ := flagSet.GetBool(flags.FlagUseLedger) clientCtx = clientCtx.WithUseLedger(useLedger) } return ReadPersistentCommandFlags(clientCtx, flagSet) } // readTxCommandFlags returns an updated Context with fields set based on flags // defined in AddTxFlagsToCmd. An error is returned if any flag query fails. // // Note, the provided clientCtx may have field pre-populated. The following order // of precedence occurs: // // - client.Context field not pre-populated & flag not set: uses default flag value // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) { clientCtx, err := ReadPersistentCommandFlags(clientCtx, flagSet) if err != nil { return clientCtx, err } if !clientCtx.GenerateOnly || flagSet.Changed(flags.FlagGenerateOnly) { genOnly, _ := flagSet.GetBool(flags.FlagGenerateOnly) clientCtx = clientCtx.WithGenerateOnly(genOnly) } if !clientCtx.Offline || flagSet.Changed(flags.FlagOffline) { offline, _ := flagSet.GetBool(flags.FlagOffline) clientCtx = clientCtx.WithOffline(offline) } if !clientCtx.UseLedger || flagSet.Changed(flags.FlagUseLedger) { useLedger, _ := flagSet.GetBool(flags.FlagUseLedger) clientCtx = clientCtx.WithUseLedger(useLedger) } if clientCtx.BroadcastMode == "" || flagSet.Changed(flags.FlagBroadcastMode) { bMode, _ := flagSet.GetString(flags.FlagBroadcastMode) clientCtx = clientCtx.WithBroadcastMode(bMode) } if !clientCtx.SkipConfirm || flagSet.Changed(flags.FlagSkipConfirmation) { skipConfirm, _ := flagSet.GetBool(flags.FlagSkipConfirmation) clientCtx = clientCtx.WithSkipConfirmation(skipConfirm) } if clientCtx.SignModeStr == "" || flagSet.Changed(flags.FlagSignMode) { signModeStr, _ := flagSet.GetString(flags.FlagSignMode) clientCtx = clientCtx.WithSignModeStr(signModeStr) } if clientCtx.FeePayer == nil || flagSet.Changed(flags.FlagFeePayer) { payer, _ := flagSet.GetString(flags.FlagFeePayer) if payer != "" { payerAcc, err := sdk.AccAddressFromBech32(payer) if err != nil { return clientCtx, err } clientCtx = clientCtx.WithFeePayerAddress(payerAcc) } } if clientCtx.FeeGranter == nil || flagSet.Changed(flags.FlagFeeGranter) { granter, _ := flagSet.GetString(flags.FlagFeeGranter) if granter != "" { granterAcc, err := sdk.AccAddressFromBech32(granter) if err != nil { return clientCtx, err } clientCtx = clientCtx.WithFeeGranterAddress(granterAcc) } } if clientCtx.From == "" || flagSet.Changed(flags.FlagFrom) { from, _ := flagSet.GetString(flags.FlagFrom) fromAddr, fromName, keyType, err := GetFromFields(clientCtx, clientCtx.Keyring, from) if err != nil { return clientCtx, err } clientCtx = clientCtx.WithFrom(from).WithFromAddress(fromAddr).WithFromName(fromName) // If the `from` signer account is a ledger key, we need to use // SIGN_MODE_AMINO_JSON, because ledger doesn't support proto yet. // ref: https://github.com/cosmos/cosmos-sdk/issues/8109 if keyType == keyring.TypeLedger && clientCtx.SignModeStr != flags.SignModeLegacyAminoJSON && !clientCtx.LedgerHasProtobuf { fmt.Println("Default sign-mode 'direct' not supported by Ledger, using sign-mode 'amino-json'.") clientCtx = clientCtx.WithSignModeStr(flags.SignModeLegacyAminoJSON) } } if !clientCtx.IsAux || flagSet.Changed(flags.FlagAux) { isAux, _ := flagSet.GetBool(flags.FlagAux) clientCtx = clientCtx.WithAux(isAux) if isAux { // If the user didn't explicitly set an --output flag, use JSON by // default. if clientCtx.OutputFormat == "" || !flagSet.Changed(cli.OutputFlag) { clientCtx = clientCtx.WithOutputFormat("json") } // If the user didn't explicitly set a --sign-mode flag, use // DIRECT_AUX by default. if clientCtx.SignModeStr == "" || !flagSet.Changed(flags.FlagSignMode) { clientCtx = clientCtx.WithSignModeStr(flags.SignModeDirectAux) } } } return clientCtx, nil } // GetClientQueryContext returns a Context from a command with fields set based on flags // defined in AddQueryFlagsToCmd. An error is returned if any flag query fails. // // - client.Context field not pre-populated & flag not set: uses default flag value // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value func GetClientQueryContext(cmd *cobra.Command) (Context, error) { ctx := GetClientContextFromCmd(cmd) return readQueryCommandFlags(ctx, cmd.Flags()) } // GetClientTxContext returns a Context from a command with fields set based on flags // defined in AddTxFlagsToCmd. An error is returned if any flag query fails. // // - client.Context field not pre-populated & flag not set: uses default flag value // - client.Context field not pre-populated & flag set: uses set flag value // - client.Context field pre-populated & flag not set: uses pre-populated value // - client.Context field pre-populated & flag set: uses set flag value func GetClientTxContext(cmd *cobra.Command) (Context, error) { ctx := GetClientContextFromCmd(cmd) return readTxCommandFlags(ctx, cmd.Flags()) } // GetClientContextFromCmd returns a Context from a command or an empty Context // if it has not been set. func GetClientContextFromCmd(cmd *cobra.Command) Context { if v := cmd.Context().Value(ClientContextKey); v != nil { clientCtxPtr := v.(*Context) return *clientCtxPtr } return Context{ } } // SetCmdClientContext sets a command's Context value to the provided argument. func SetCmdClientContext(cmd *cobra.Command, clientCtx Context) error { v := cmd.Context().Value(ClientContextKey) if v == nil { return errors.New("client context not set") } clientCtxPtr := v.(*Context) *clientCtxPtr = clientCtx return nil } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package tx import ( "bufio" "context" "encoding/json" "errors" "fmt" "os" gogogrpc "github.com/cosmos/gogoproto/grpc" "github.com/spf13/pflag" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/input" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) // GenerateOrBroadcastTxCLI will either generate and print and unsigned transaction // or sign it and broadcast it returning an error upon failure. func GenerateOrBroadcastTxCLI(clientCtx client.Context, flagSet *pflag.FlagSet, msgs ...sdk.Msg) error { txf := NewFactoryCLI(clientCtx, flagSet) return GenerateOrBroadcastTxWithFactory(clientCtx, txf, msgs...) } // GenerateOrBroadcastTxWithFactory will either generate and print and unsigned transaction // or sign it and broadcast it returning an error upon failure. func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error { // Validate all msgs before generating or broadcasting the tx. // We were calling ValidateBasic separately in each CLI handler before. // Right now, we're factorizing that call inside this function. // ref: https://github.com/cosmos/cosmos-sdk/pull/9236#discussion_r623803504 for _, msg := range msgs { if err := msg.ValidateBasic(); err != nil { return err } } // If the --aux flag is set, we simply generate and print the AuxSignerData. if clientCtx.IsAux { auxSignerData, err := makeAuxSignerData(clientCtx, txf, msgs...) if err != nil { return err } return clientCtx.PrintProto(&auxSignerData) } if clientCtx.GenerateOnly { return txf.PrintUnsignedTx(clientCtx, msgs...) } return BroadcastTx(clientCtx, txf, msgs...) } // BroadcastTx attempts to generate, sign and broadcast a transaction with the // given set of messages. It will also simulate gas requirements if necessary. // It will return an error upon failure. func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error { txf, err := txf.Prepare(clientCtx) if err != nil { return err } if txf.SimulateAndExecute() || clientCtx.Simulate { _, adjusted, err := CalculateGas(clientCtx, txf, msgs...) if err != nil { return err } txf = txf.WithGas(adjusted) _, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{ GasEstimate: txf.Gas() }) } if clientCtx.Simulate { return nil } tx, err := txf.BuildUnsignedTx(msgs...) if err != nil { return err } if !clientCtx.SkipConfirm { txBytes, err := clientCtx.TxConfig.TxJSONEncoder()(tx.GetTx()) if err != nil { return err } if err := clientCtx.PrintRaw(json.RawMessage(txBytes)); err != nil { _, _ = fmt.Fprintf(os.Stderr, "%s\n", txBytes) } buf := bufio.NewReader(os.Stdin) ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr) if err != nil || !ok { _, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") return err } } err = Sign(txf, clientCtx.GetFromName(), tx, true) if err != nil { return err } txBytes, err := clientCtx.TxConfig.TxEncoder()(tx.GetTx()) if err != nil { return err } // broadcast to a Tendermint node res, err := clientCtx.BroadcastTx(txBytes) if err != nil { return err } return clientCtx.PrintProto(res) } // CalculateGas simulates the execution of a transaction and returns the // simulation response obtained by the query and the adjusted gas amount. func CalculateGas( clientCtx gogogrpc.ClientConn, txf Factory, msgs ...sdk.Msg, ) (*tx.SimulateResponse, uint64, error) { txBytes, err := txf.BuildSimTx(msgs...) if err != nil { return nil, 0, err } txSvcClient := tx.NewServiceClient(clientCtx) simRes, err := txSvcClient.Simulate(context.Background(), &tx.SimulateRequest{ TxBytes: txBytes, }) if err != nil { return nil, 0, err } return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)), nil } // SignWithPrivKey signs a given tx with the given private key, and returns the // corresponding SignatureV2 if the signing is successful. func SignWithPrivKey( signMode signing.SignMode, signerData authsigning.SignerData, txBuilder client.TxBuilder, priv cryptotypes.PrivKey, txConfig client.TxConfig, accSeq uint64, ) (signing.SignatureV2, error) { var sigV2 signing.SignatureV2 // Generate the bytes to be signed. signBytes, err := txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx()) if err != nil { return sigV2, err } // Sign those bytes signature, err := priv.Sign(signBytes) if err != nil { return sigV2, err } // Construct the SignatureV2 struct sigData := signing.SingleSignatureData{ SignMode: signMode, Signature: signature, } sigV2 = signing.SignatureV2{ PubKey: priv.PubKey(), Data: &sigData, Sequence: accSeq, } return sigV2, nil } // countDirectSigners counts the number of DIRECT signers in a signature data. func countDirectSigners(data signing.SignatureData) int { switch data := data.(type) { case *signing.SingleSignatureData: if data.SignMode == signing.SignMode_SIGN_MODE_DIRECT { return 1 } return 0 case *signing.MultiSignatureData: directSigners := 0 for _, d := range data.Signatures { directSigners += countDirectSigners(d) } return directSigners default: panic("unreachable case") } } // checkMultipleSigners checks that there can be maximum one DIRECT signer in // a tx. func checkMultipleSigners(tx authsigning.Tx) error { directSigners := 0 sigsV2, err := tx.GetSignaturesV2() if err != nil { return err } for _, sig := range sigsV2 { directSigners += countDirectSigners(sig.Data) if directSigners > 1 { return sdkerrors.ErrNotSupported.Wrap("txs signed with CLI can have maximum 1 DIRECT signer") } } return nil } // Sign signs a given tx with a named key. The bytes signed over are canconical. // The resulting signature will be added to the transaction builder overwriting the previous // ones if overwrite=true (otherwise, the signature will be appended). // Signing a transaction with mutltiple signers in the DIRECT mode is not supprted and will // return an error. // An error is returned upon failure. func Sign(txf Factory, name string, txBuilder client.TxBuilder, overwriteSig bool) error { if txf.keybase == nil { return errors.New("keybase must be set prior to signing a transaction") } signMode := txf.signMode if signMode == signing.SignMode_SIGN_MODE_UNSPECIFIED { // use the SignModeHandler's default mode if unspecified signMode = txf.txConfig.SignModeHandler().DefaultMode() } k, err := txf.keybase.Key(name) if err != nil { return err } pubKey, err := k.GetPubKey() if err != nil { return err } signerData := authsigning.SignerData{ ChainID: txf.chainID, AccountNumber: txf.accountNumber, Sequence: txf.sequence, PubKey: pubKey, Address: sdk.AccAddress(pubKey.Address()).String(), } // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on // TxBuilder under the hood, and SignerInfos is needed to generated the // sign bytes. This is the reason for setting SetSignatures here, with a // nil signature. // // Note: this line is not needed for SIGN_MODE_LEGACY_AMINO, but putting it // also doesn't affect its generated sign bytes, so for code's simplicity // sake, we put it here. sigData := signing.SingleSignatureData{ SignMode: signMode, Signature: nil, } sig := signing.SignatureV2{ PubKey: pubKey, Data: &sigData, Sequence: txf.Sequence(), } var prevSignatures []signing.SignatureV2 if !overwriteSig { prevSignatures, err = txBuilder.GetTx().GetSignaturesV2() if err != nil { return err } } // Overwrite or append signer infos. var sigs []signing.SignatureV2 if overwriteSig { sigs = []signing.SignatureV2{ sig } } else { sigs = append(sigs, prevSignatures...) sigs = append(sigs, sig) } if err := txBuilder.SetSignatures(sigs...); err != nil { return err } if err := checkMultipleSigners(txBuilder.GetTx()); err != nil { return err } // Generate the bytes to be signed. bytesToSign, err := txf.txConfig.SignModeHandler().GetSignBytes(signMode, signerData, txBuilder.GetTx()) if err != nil { return err } // Sign those bytes sigBytes, _, err := txf.keybase.Sign(name, bytesToSign) if err != nil { return err } // Construct the SignatureV2 struct sigData = signing.SingleSignatureData{ SignMode: signMode, Signature: sigBytes, } sig = signing.SignatureV2{ PubKey: pubKey, Data: &sigData, Sequence: txf.Sequence(), } if overwriteSig { err = txBuilder.SetSignatures(sig) } else { prevSignatures = append(prevSignatures, sig) err = txBuilder.SetSignatures(prevSignatures...) } if err != nil { return fmt.Errorf("unable to set signatures on payload: %w", err) } // Run optional preprocessing if specified. By default, this is unset // and will return nil. return txf.PreprocessTx(name, txBuilder) } // GasEstimateResponse defines a response definition for tx gas estimation. type GasEstimateResponse struct { GasEstimate uint64 `json:"gas_estimate" yaml:"gas_estimate"` } func (gr GasEstimateResponse) String() string { return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) } // makeAuxSignerData generates an AuxSignerData from the client inputs. func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...sdk.Msg) (tx.AuxSignerData, error) { b := NewAuxTxBuilder() fromAddress, name, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, clientCtx.From) if err != nil { return tx.AuxSignerData{ }, err } b.SetAddress(fromAddress.String()) if clientCtx.Offline { b.SetAccountNumber(f.accountNumber) b.SetSequence(f.sequence) } else { accNum, seq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, fromAddress) if err != nil { return tx.AuxSignerData{ }, err } b.SetAccountNumber(accNum) b.SetSequence(seq) } err = b.SetMsgs(msgs...) if err != nil { return tx.AuxSignerData{ }, err } if f.tip != nil { if _, err := sdk.AccAddressFromBech32(f.tip.Tipper); err != nil { return tx.AuxSignerData{ }, sdkerrors.ErrInvalidAddress.Wrap("tipper must be a bech32 address") } b.SetTip(f.tip) } err = b.SetSignMode(f.SignMode()) if err != nil { return tx.AuxSignerData{ }, err } key, err := clientCtx.Keyring.Key(name) if err != nil { return tx.AuxSignerData{ }, err } pub, err := key.GetPubKey() if err != nil { return tx.AuxSignerData{ }, err } err = b.SetPubKey(pub) if err != nil { return tx.AuxSignerData{ }, err } b.SetChainID(clientCtx.ChainID) signBz, err := b.GetSignBytes() if err != nil { return tx.AuxSignerData{ }, err } sig, _, err := clientCtx.Keyring.Sign(name, signBz) if err != nil { return tx.AuxSignerData{ }, err } b.SetSignature(sig) return b.GetAuxSignerData() } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package tx import ( "github.com/cosmos/gogoproto/proto" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) // wrapper is a wrapper around the tx.Tx proto.Message which retain the raw // body and auth_info bytes. type wrapper struct { cdc codec.Codec tx *tx.Tx // bodyBz represents the protobuf encoding of TxBody. This should be encoding // from the client using TxRaw if the tx was decoded from the wire bodyBz []byte // authInfoBz represents the protobuf encoding of TxBody. This should be encoding // from the client using TxRaw if the tx was decoded from the wire authInfoBz []byte txBodyHasUnknownNonCriticals bool } var ( _ authsigning.Tx = &wrapper{ } _ client.TxBuilder = &wrapper{ } _ tx.TipTx = &wrapper{ } _ ante.HasExtensionOptionsTx = &wrapper{ } _ ExtensionOptionsTxBuilder = &wrapper{ } _ tx.TipTx = &wrapper{ } ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. type ExtensionOptionsTxBuilder interface { client.TxBuilder SetExtensionOptions(...*codectypes.Any) SetNonCriticalExtensionOptions(...*codectypes.Any) } func newBuilder(cdc codec.Codec) *wrapper { return &wrapper{ cdc: cdc, tx: &tx.Tx{ Body: &tx.TxBody{ }, AuthInfo: &tx.AuthInfo{ Fee: &tx.Fee{ }, }, }, } } func (w *wrapper) GetMsgs() []sdk.Msg { return w.tx.GetMsgs() } func (w *wrapper) ValidateBasic() error { return w.tx.ValidateBasic() } func (w *wrapper) getBodyBytes() []byte { if len(w.bodyBz) == 0 { // if bodyBz is empty, then marshal the body. bodyBz will generally // be set to nil whenever SetBody is called so the result of calling // this method should always return the correct bytes. Note that after // decoding bodyBz is derived from TxRaw so that it matches what was // transmitted over the wire var err error w.bodyBz, err = proto.Marshal(w.tx.Body) if err != nil { panic(err) } } return w.bodyBz } func (w *wrapper) getAuthInfoBytes() []byte { if len(w.authInfoBz) == 0 { // if authInfoBz is empty, then marshal the body. authInfoBz will generally // be set to nil whenever SetAuthInfo is called so the result of calling // this method should always return the correct bytes. Note that after // decoding authInfoBz is derived from TxRaw so that it matches what was // transmitted over the wire var err error w.authInfoBz, err = proto.Marshal(w.tx.AuthInfo) if err != nil { panic(err) } } return w.authInfoBz } func (w *wrapper) GetSigners() []sdk.AccAddress { return w.tx.GetSigners() } func (w *wrapper) GetPubKeys() ([]cryptotypes.PubKey, error) { signerInfos := w.tx.AuthInfo.SignerInfos pks := make([]cryptotypes.PubKey, len(signerInfos)) for i, si := range signerInfos { // NOTE: it is okay to leave this nil if there is no PubKey in the SignerInfo. // PubKey's can be left unset in SignerInfo. if si.PublicKey == nil { continue } pkAny := si.PublicKey.GetCachedValue() pk, ok := pkAny.(cryptotypes.PubKey) if ok { pks[i] = pk } else { return nil, sdkerrors.Wrapf(sdkerrors.ErrLogic, "Expecting PubKey, got: %T", pkAny) } } return pks, nil } func (w *wrapper) GetGas() uint64 { return w.tx.AuthInfo.Fee.GasLimit } func (w *wrapper) GetFee() sdk.Coins { return w.tx.AuthInfo.Fee.Amount } func (w *wrapper) FeePayer() sdk.AccAddress { feePayer := w.tx.AuthInfo.Fee.Payer if feePayer != "" { return sdk.MustAccAddressFromBech32(feePayer) } // use first signer as default if no payer specified return w.GetSigners()[0] } func (w *wrapper) FeeGranter() sdk.AccAddress { feePayer := w.tx.AuthInfo.Fee.Granter if feePayer != "" { return sdk.MustAccAddressFromBech32(feePayer) } return nil } func (w *wrapper) GetTip() *tx.Tip { return w.tx.AuthInfo.Tip } func (w *wrapper) GetMemo() string { return w.tx.Body.Memo } // GetTimeoutHeight returns the transaction's timeout height (if set). func (w *wrapper) GetTimeoutHeight() uint64 { return w.tx.Body.TimeoutHeight } func (w *wrapper) GetSignaturesV2() ([]signing.SignatureV2, error) { signerInfos := w.tx.AuthInfo.SignerInfos sigs := w.tx.Signatures pubKeys, err := w.GetPubKeys() if err != nil { return nil, err } n := len(signerInfos) res := make([]signing.SignatureV2, n) for i, si := range signerInfos { // handle nil signatures (in case of simulation) if si.ModeInfo == nil { res[i] = signing.SignatureV2{ PubKey: pubKeys[i], } } else { var err error sigData, err := ModeInfoAndSigToSignatureData(si.ModeInfo, sigs[i]) if err != nil { return nil, err } // sequence number is functionally a transaction nonce and referred to as such in the SDK nonce := si.GetSequence() res[i] = signing.SignatureV2{ PubKey: pubKeys[i], Data: sigData, Sequence: nonce, } } } return res, nil } func (w *wrapper) SetMsgs(msgs ...sdk.Msg) error { anys, err := tx.SetMsgs(msgs) if err != nil { return err } w.tx.Body.Messages = anys // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil return nil } // SetTimeoutHeight sets the transaction's height timeout. func (w *wrapper) SetTimeoutHeight(height uint64) { w.tx.Body.TimeoutHeight = height // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil } func (w *wrapper) SetMemo(memo string) { w.tx.Body.Memo = memo // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil } func (w *wrapper) SetGasLimit(limit uint64) { if w.tx.AuthInfo.Fee == nil { w.tx.AuthInfo.Fee = &tx.Fee{ } } w.tx.AuthInfo.Fee.GasLimit = limit // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil } func (w *wrapper) SetFeeAmount(coins sdk.Coins) { if w.tx.AuthInfo.Fee == nil { w.tx.AuthInfo.Fee = &tx.Fee{ } } w.tx.AuthInfo.Fee.Amount = coins // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil } func (w *wrapper) SetTip(tip *tx.Tip) { w.tx.AuthInfo.Tip = tip // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil } func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { if w.tx.AuthInfo.Fee == nil { w.tx.AuthInfo.Fee = &tx.Fee{ } } w.tx.AuthInfo.Fee.Payer = feePayer.String() // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil } func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { if w.tx.AuthInfo.Fee == nil { w.tx.AuthInfo.Fee = &tx.Fee{ } } w.tx.AuthInfo.Fee.Granter = feeGranter.String() // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil } func (w *wrapper) SetSignatures(signatures ...signing.SignatureV2) error { n := len(signatures) signerInfos := make([]*tx.SignerInfo, n) rawSigs := make([][]byte, n) for i, sig := range signatures { var modeInfo *tx.ModeInfo modeInfo, rawSigs[i] = SignatureDataToModeInfoAndSig(sig.Data) any, err := codectypes.NewAnyWithValue(sig.PubKey) if err != nil { return err } signerInfos[i] = &tx.SignerInfo{ PublicKey: any, ModeInfo: modeInfo, Sequence: sig.Sequence, } } w.setSignerInfos(signerInfos) w.setSignatures(rawSigs) return nil } func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) { w.tx.AuthInfo.SignerInfos = infos // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil } func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { if w.tx.AuthInfo.SignerInfos == nil { w.tx.AuthInfo.SignerInfos = make([]*tx.SignerInfo, len(w.GetSigners())) } w.tx.AuthInfo.SignerInfos[index] = info // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil } func (w *wrapper) setSignatures(sigs [][]byte) { w.tx.Signatures = sigs } func (w *wrapper) setSignatureAtIndex(index int, sig []byte) { if w.tx.Signatures == nil { w.tx.Signatures = make([][]byte, len(w.GetSigners())) } w.tx.Signatures[index] = sig } func (w *wrapper) GetTx() authsigning.Tx { return w } func (w *wrapper) GetProtoTx() *tx.Tx { return w.tx } // Deprecated: AsAny extracts proto Tx and wraps it into Any. // NOTE: You should probably use `GetProtoTx` if you want to serialize the transaction. func (w *wrapper) AsAny() *codectypes.Any { return codectypes.UnsafePackAny(w.tx) } // WrapTx creates a TxBuilder wrapper around a tx.Tx proto message. func WrapTx(protoTx *tx.Tx) client.TxBuilder { return &wrapper{ tx: protoTx, } } func (w *wrapper) GetExtensionOptions() []*codectypes.Any { return w.tx.Body.ExtensionOptions } func (w *wrapper) GetNonCriticalExtensionOptions() []*codectypes.Any { return w.tx.Body.NonCriticalExtensionOptions } func (w *wrapper) SetExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.ExtensionOptions = extOpts w.bodyBz = nil } func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.NonCriticalExtensionOptions = extOpts w.bodyBz = nil } func (w *wrapper) AddAuxSignerData(data tx.AuxSignerData) error { err := data.ValidateBasic() if err != nil { return err } w.bodyBz = data.SignDoc.BodyBytes var body tx.TxBody err = w.cdc.Unmarshal(w.bodyBz, &body) if err != nil { return err } if w.tx.Body.Memo != "" && w.tx.Body.Memo != body.Memo { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has memo %s, got %s in AuxSignerData", w.tx.Body.Memo, body.Memo) } if w.tx.Body.TimeoutHeight != 0 && w.tx.Body.TimeoutHeight != body.TimeoutHeight { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has timeout height %d, got %d in AuxSignerData", w.tx.Body.TimeoutHeight, body.TimeoutHeight) } if len(w.tx.Body.ExtensionOptions) != 0 { if len(w.tx.Body.ExtensionOptions) != len(body.ExtensionOptions) { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has %d extension options, got %d in AuxSignerData", len(w.tx.Body.ExtensionOptions), len(body.ExtensionOptions)) } for i, o := range w.tx.Body.ExtensionOptions { if !o.Equal(body.ExtensionOptions[i]) { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has extension option %+v at index %d, got %+v in AuxSignerData", o, i, body.ExtensionOptions[i]) } } } if len(w.tx.Body.NonCriticalExtensionOptions) != 0 { if len(w.tx.Body.NonCriticalExtensionOptions) != len(body.NonCriticalExtensionOptions) { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has %d non-critical extension options, got %d in AuxSignerData", len(w.tx.Body.NonCriticalExtensionOptions), len(body.NonCriticalExtensionOptions)) } for i, o := range w.tx.Body.NonCriticalExtensionOptions { if !o.Equal(body.NonCriticalExtensionOptions[i]) { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has non-critical extension option %+v at index %d, got %+v in AuxSignerData", o, i, body.NonCriticalExtensionOptions[i]) } } } if len(w.tx.Body.Messages) != 0 { if len(w.tx.Body.Messages) != len(body.Messages) { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has %d Msgs, got %d in AuxSignerData", len(w.tx.Body.Messages), len(body.Messages)) } for i, o := range w.tx.Body.Messages { if !o.Equal(body.Messages[i]) { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has Msg %+v at index %d, got %+v in AuxSignerData", o, i, body.Messages[i]) } } } if w.tx.AuthInfo.Tip != nil && data.SignDoc.Tip != nil { if !w.tx.AuthInfo.Tip.Amount.IsEqual(data.SignDoc.Tip.Amount) { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has tip %+v, got %+v in AuxSignerData", w.tx.AuthInfo.Tip.Amount, data.SignDoc.Tip.Amount) } if w.tx.AuthInfo.Tip.Tipper != data.SignDoc.Tip.Tipper { return sdkerrors.ErrInvalidRequest.Wrapf("TxBuilder has tipper %s, got %s in AuxSignerData", w.tx.AuthInfo.Tip.Tipper, data.SignDoc.Tip.Tipper) } } w.SetMemo(body.Memo) w.SetTimeoutHeight(body.TimeoutHeight) w.SetExtensionOptions(body.ExtensionOptions...) w.SetNonCriticalExtensionOptions(body.NonCriticalExtensionOptions...) msgs := make([]sdk.Msg, len(body.Messages)) for i, msgAny := range body.Messages { msgs[i] = msgAny.GetCachedValue().(sdk.Msg) } w.SetMsgs(msgs...) w.SetTip(data.GetSignDoc().GetTip()) // Get the aux signer's index in GetSigners. signerIndex := -1 for i, signer := range w.GetSigners() { if signer.String() == data.Address { signerIndex = i } } if signerIndex < 0 { return sdkerrors.ErrLogic.Wrapf("address %s is not a signer", data.Address) } w.setSignerInfoAtIndex(signerIndex, &tx.SignerInfo{ PublicKey: data.SignDoc.PublicKey, ModeInfo: &tx.ModeInfo{ Sum: &tx.ModeInfo_Single_{ Single: &tx.ModeInfo_Single{ Mode: data.Mode }}}, Sequence: data.SignDoc.Sequence, }) w.setSignatureAtIndex(signerIndex, data.Sig) return nil } ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/tx/v1beta1/tx.proto#L221-L248 ``` Example cmd: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./simd tx gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --from validator-key --fee-granter=cosmos1xh44hxt7spr67hqaa7nyx5gnutrz5fraw6grxn --chain-id=testnet --fees="10stake" ``` ### Granted Fee Deductions Fees are deducted from grants in the `x/auth` ante handler. To learn more about how ante handlers work, read the [Auth Module AnteHandlers Guide](/sdk/latest/modules/auth/auth#antehandlers). ### Gas In order to prevent DoS attacks, using a filtered `x/feegrant` incurs gas. The SDK must assure that the `grantee`'s transactions all conform to the filter set by the `granter`. The SDK does this by iterating over the allowed messages in the filter and charging 10 gas per filtered message. The SDK will then iterate over the messages being sent by the `grantee` to ensure the messages adhere to the filter, also charging 10 gas per message. The SDK will stop iterating and fail the transaction if it finds a message that does not conform to the filter. **WARNING**: The gas is charged against the granted allowance. Ensure your messages conform to the filter, if any, before sending transactions using your allowance. ### Pruning A queue in the state maintained with the prefix of expiration of the grants and checks them on EndBlock with the current block time for every block to prune. ## State ### FeeAllowance Fee Allowances are identified by combining `Grantee` (the account address of fee allowance grantee) with the `Granter` (the account address of fee allowance granter). Fee allowance grants are stored in the state as follows: * Grant: `0x00 | grantee_addr_len (1 byte) | grantee_addr_bytes | granter_addr_len (1 byte) | granter_addr_bytes -> ProtocolBuffer(Grant)` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: cosmos/feegrant/v1beta1/feegrant.proto package feegrant import ( fmt "fmt" _ "github.com/cosmos/cosmos-proto" types1 "github.com/cosmos/cosmos-sdk/codec/types" github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/cosmos-sdk/types/tx/amino" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" _ "google.golang.org/protobuf/types/known/durationpb" _ "google.golang.org/protobuf/types/known/timestamppb" io "io" math "math" math_bits "math/bits" time "time" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf var _ = time.Kitchen // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // BasicAllowance implements Allowance with a one-time grant of coins // that optionally expires. The grantee can use up to SpendLimit to cover fees. type BasicAllowance struct { // spend_limit specifies the maximum amount of coins that can be spent // by this allowance and will be updated as coins are spent. If it is // empty, there is no spend limit and any amount of coins can be spent. SpendLimit github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=spend_limit,json=spendLimit,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"spend_limit"` // expiration specifies an optional time when this allowance expires Expiration *time.Time `protobuf:"bytes,2,opt,name=expiration,proto3,stdtime" json:"expiration,omitempty"` } func (m *BasicAllowance) Reset() { *m = BasicAllowance{ } } func (m *BasicAllowance) String() string { return proto.CompactTextString(m) } func (*BasicAllowance) ProtoMessage() { } func (*BasicAllowance) Descriptor() ([]byte, []int) { return fileDescriptor_7279582900c30aea, []int{0 } } func (m *BasicAllowance) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *BasicAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_BasicAllowance.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *BasicAllowance) XXX_Merge(src proto.Message) { xxx_messageInfo_BasicAllowance.Merge(m, src) } func (m *BasicAllowance) XXX_Size() int { return m.Size() } func (m *BasicAllowance) XXX_DiscardUnknown() { xxx_messageInfo_BasicAllowance.DiscardUnknown(m) } var xxx_messageInfo_BasicAllowance proto.InternalMessageInfo func (m *BasicAllowance) GetSpendLimit() github_com_cosmos_cosmos_sdk_types.Coins { if m != nil { return m.SpendLimit } return nil } func (m *BasicAllowance) GetExpiration() *time.Time { if m != nil { return m.Expiration } return nil } // PeriodicAllowance extends Allowance to allow for both a maximum cap, // as well as a limit per time period. type PeriodicAllowance struct { // basic specifies a struct of `BasicAllowance` Basic BasicAllowance `protobuf:"bytes,1,opt,name=basic,proto3" json:"basic"` // period specifies the time duration in which period_spend_limit coins can // be spent before that allowance is reset Period time.Duration `protobuf:"bytes,2,opt,name=period,proto3,stdduration" json:"period"` // period_spend_limit specifies the maximum number of coins that can be spent // in the period PeriodSpendLimit github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,3,rep,name=period_spend_limit,json=periodSpendLimit,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"period_spend_limit"` // period_can_spend is the number of coins left to be spent before the period_reset time PeriodCanSpend github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,4,rep,name=period_can_spend,json=periodCanSpend,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"period_can_spend"` // period_reset is the time at which this period resets and a new one begins, // it is calculated from the start time of the first transaction after the // last period ended PeriodReset time.Time `protobuf:"bytes,5,opt,name=period_reset,json=periodReset,proto3,stdtime" json:"period_reset"` } func (m *PeriodicAllowance) Reset() { *m = PeriodicAllowance{ } } func (m *PeriodicAllowance) String() string { return proto.CompactTextString(m) } func (*PeriodicAllowance) ProtoMessage() { } func (*PeriodicAllowance) Descriptor() ([]byte, []int) { return fileDescriptor_7279582900c30aea, []int{1 } } func (m *PeriodicAllowance) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PeriodicAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_PeriodicAllowance.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *PeriodicAllowance) XXX_Merge(src proto.Message) { xxx_messageInfo_PeriodicAllowance.Merge(m, src) } func (m *PeriodicAllowance) XXX_Size() int { return m.Size() } func (m *PeriodicAllowance) XXX_DiscardUnknown() { xxx_messageInfo_PeriodicAllowance.DiscardUnknown(m) } var xxx_messageInfo_PeriodicAllowance proto.InternalMessageInfo func (m *PeriodicAllowance) GetBasic() BasicAllowance { if m != nil { return m.Basic } return BasicAllowance{ } } func (m *PeriodicAllowance) GetPeriod() time.Duration { if m != nil { return m.Period } return 0 } func (m *PeriodicAllowance) GetPeriodSpendLimit() github_com_cosmos_cosmos_sdk_types.Coins { if m != nil { return m.PeriodSpendLimit } return nil } func (m *PeriodicAllowance) GetPeriodCanSpend() github_com_cosmos_cosmos_sdk_types.Coins { if m != nil { return m.PeriodCanSpend } return nil } func (m *PeriodicAllowance) GetPeriodReset() time.Time { if m != nil { return m.PeriodReset } return time.Time{ } } // AllowedMsgAllowance creates allowance only for specified message types. type AllowedMsgAllowance struct { // allowance can be any of basic and periodic fee allowance. Allowance *types1.Any `protobuf:"bytes,1,opt,name=allowance,proto3" json:"allowance,omitempty"` // allowed_messages are the messages for which the grantee has the access. AllowedMessages []string `protobuf:"bytes,2,rep,name=allowed_messages,json=allowedMessages,proto3" json:"allowed_messages,omitempty"` } func (m *AllowedMsgAllowance) Reset() { *m = AllowedMsgAllowance{ } } func (m *AllowedMsgAllowance) String() string { return proto.CompactTextString(m) } func (*AllowedMsgAllowance) ProtoMessage() { } func (*AllowedMsgAllowance) Descriptor() ([]byte, []int) { return fileDescriptor_7279582900c30aea, []int{2 } } func (m *AllowedMsgAllowance) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *AllowedMsgAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_AllowedMsgAllowance.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *AllowedMsgAllowance) XXX_Merge(src proto.Message) { xxx_messageInfo_AllowedMsgAllowance.Merge(m, src) } func (m *AllowedMsgAllowance) XXX_Size() int { return m.Size() } func (m *AllowedMsgAllowance) XXX_DiscardUnknown() { xxx_messageInfo_AllowedMsgAllowance.DiscardUnknown(m) } var xxx_messageInfo_AllowedMsgAllowance proto.InternalMessageInfo // Grant is stored in the KVStore to record a grant with full context type Grant struct { // granter is the address of the user granting an allowance of their funds. Granter string `protobuf:"bytes,1,opt,name=granter,proto3" json:"granter,omitempty"` // grantee is the address of the user being granted an allowance of another user's funds. Grantee string `protobuf:"bytes,2,opt,name=grantee,proto3" json:"grantee,omitempty"` // allowance can be any of basic, periodic, allowed fee allowance. Allowance *types1.Any `protobuf:"bytes,3,opt,name=allowance,proto3" json:"allowance,omitempty"` } func (m *Grant) Reset() { *m = Grant{ } } func (m *Grant) String() string { return proto.CompactTextString(m) } func (*Grant) ProtoMessage() { } func (*Grant) Descriptor() ([]byte, []int) { return fileDescriptor_7279582900c30aea, []int{3 } } func (m *Grant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Grant) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_Grant.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *Grant) XXX_Merge(src proto.Message) { xxx_messageInfo_Grant.Merge(m, src) } func (m *Grant) XXX_Size() int { return m.Size() } func (m *Grant) XXX_DiscardUnknown() { xxx_messageInfo_Grant.DiscardUnknown(m) } var xxx_messageInfo_Grant proto.InternalMessageInfo func (m *Grant) GetGranter() string { if m != nil { return m.Granter } return "" } func (m *Grant) GetGrantee() string { if m != nil { return m.Grantee } return "" } func (m *Grant) GetAllowance() *types1.Any { if m != nil { return m.Allowance } return nil } func init() { proto.RegisterType((*BasicAllowance)(nil), "cosmos.feegrant.v1beta1.BasicAllowance") proto.RegisterType((*PeriodicAllowance)(nil), "cosmos.feegrant.v1beta1.PeriodicAllowance") proto.RegisterType((*AllowedMsgAllowance)(nil), "cosmos.feegrant.v1beta1.AllowedMsgAllowance") proto.RegisterType((*Grant)(nil), "cosmos.feegrant.v1beta1.Grant") } func init() { proto.RegisterFile("cosmos/feegrant/v1beta1/feegrant.proto", fileDescriptor_7279582900c30aea) } var fileDescriptor_7279582900c30aea = []byte{ // 639 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0x3f, 0x6f, 0xd3, 0x40, 0x14, 0x8f, 0x9b, 0xb6, 0x28, 0x17, 0x28, 0xad, 0xa9, 0x84, 0x53, 0x21, 0xbb, 0x8a, 0x04, 0x4d, 0x2b, 0xd5, 0x56, 0x8b, 0x58, 0x3a, 0x35, 0x2e, 0xa2, 0x80, 0x5a, 0xa9, 0x72, 0x99, 0x90, 0x50, 0x74, 0xb6, 0xaf, 0xe6, 0x44, 0xec, 0x33, 0x3e, 0x17, 0x1a, 0x06, 0x66, 0xc4, 0x80, 0x32, 0x32, 0x32, 0x22, 0xa6, 0x0e, 0xe5, 0x3b, 0x54, 0x0c, 0xa8, 0x62, 0x62, 0x22, 0x28, 0x19, 0x3a, 0xf3, 0x0d, 0x90, 0xef, 0xce, 0x8e, 0x9b, 0x50, 0x68, 0x25, 0xba, 0x24, 0x77, 0xef, 0xde, 0xfb, 0xfd, 0x79, 0xef, 0x45, 0x01, 0xb7, 0x1c, 0x42, 0x7d, 0x42, 0x8d, 0x1d, 0x84, 0xbc, 0x08, 0x06, 0xb1, 0xf1, 0x62, 0xc9, 0x46, 0x31, 0x5c, 0xca, 0x02, 0x7a, 0x18, 0x91, 0x98, 0xc8, 0xd7, 0x79, 0x9e, 0x9e, 0x85, 0x45, 0xde, 0xcc, 0xb4, 0x47, 0x3c, 0xc2, 0x72, 0x8c, 0xe4, 0xc4, 0xd3, 0x67, 0x2a, 0x1e, 0x21, 0x5e, 0x13, 0x19, 0xec, 0x66, 0xef, 0xee, 0x18, 0x30, 0x68, 0xa5, 0x4f, 0x1c, 0xa9, 0xc1, 0x6b, 0x04, 0x2c, 0x7f, 0x52, 0x85, 0x18, 0x1b, 0x52, 0x94, 0x09, 0x71, 0x08, 0x0e, 0xc4, 0xfb, 0x14, 0xf4, 0x71, 0x40, 0x0c, 0xf6, 0x29, 0x42, 0xda, 0x20, 0x51, 0x8c, 0x7d, 0x44, 0x63, 0xe8, 0x87, 0x29, 0xe6, 0x60, 0x82, 0xbb, 0x1b, 0xc1, 0x18, 0x13, 0x81, 0x59, 0x7d, 0x37, 0x02, 0x26, 0x4c, 0x48, 0xb1, 0x53, 0x6f, 0x36, 0xc9, 0x4b, 0x18, 0x38, 0x48, 0x7e, 0x0e, 0xca, 0x34, 0x44, 0x81, 0xdb, 0x68, 0x62, 0x1f, 0xc7, 0x8a, 0x34, 0x5b, 0xac, 0x95, 0x97, 0x2b, 0xba, 0x90, 0x9a, 0x88, 0x4b, 0xdd, 0xeb, 0x6b, 0x04, 0x07, 0xe6, 0x9d, 0xc3, 0x1f, 0x5a, 0xe1, 0x53, 0x47, 0xab, 0x79, 0x38, 0x7e, 0xba, 0x6b, 0xeb, 0x0e, 0xf1, 0x85, 0x2f, 0xf1, 0xb5, 0x48, 0xdd, 0x67, 0x46, 0xdc, 0x0a, 0x11, 0x65, 0x05, 0xf4, 0xe3, 0xf1, 0xfe, 0x82, 0x64, 0x01, 0x46, 0xb2, 0x91, 0x70, 0xc8, 0xab, 0x00, 0xa0, 0xbd, 0x10, 0x73, 0x65, 0xca, 0xc8, 0xac, 0x54, 0x2b, 0x2f, 0xcf, 0xe8, 0x5c, 0xba, 0x9e, 0x4a, 0xd7, 0x1f, 0xa5, 0xde, 0xcc, 0xd1, 0x76, 0x47, 0x93, 0xac, 0x5c, 0xcd, 0xca, 0xfa, 0x97, 0x83, 0xc5, 0x9b, 0xa7, 0x0c, 0x49, 0xbf, 0x87, 0x50, 0x66, 0xef, 0xc1, 0xdb, 0xe3, 0xfd, 0x85, 0x4a, 0x4e, 0xd8, 0x49, 0xf7, 0xd5, 0xcf, 0xa3, 0x60, 0x6a, 0x0b, 0x45, 0x98, 0xb8, 0xf9, 0x9e, 0xdc, 0x07, 0x63, 0x76, 0x92, 0xa7, 0x48, 0x4c, 0xdb, 0x9c, 0x7e, 0x1a, 0xd5, 0x49, 0x34, 0xb3, 0x94, 0xf4, 0x86, 0xfb, 0xe5, 0x00, 0xf2, 0x2a, 0x18, 0x0f, 0x19, 0xbc, 0xb0, 0x59, 0x19, 0xb2, 0x79, 0x57, 0x4c, 0xc8, 0xbc, 0x92, 0x14, 0xbf, 0xef, 0x68, 0x12, 0x07, 0x10, 0x75, 0xf2, 0x6b, 0x20, 0xf3, 0x53, 0x23, 0x3f, 0xa6, 0xe2, 0x05, 0x8d, 0x69, 0x92, 0x73, 0x6d, 0xf7, 0x87, 0xf5, 0x0a, 0x88, 0x58, 0xc3, 0x81, 0x01, 0xd7, 0xa0, 0x8c, 0x5e, 0x10, 0xfb, 0x04, 0x67, 0x5a, 0x83, 0x01, 0x13, 0x20, 0x6f, 0x80, 0xcb, 0x82, 0x3b, 0x42, 0x14, 0xc5, 0xca, 0xd8, 0x3f, 0x57, 0x85, 0x35, 0xb1, 0x9d, 0x35, 0xb1, 0xcc, 0xcb, 0xad, 0xa4, 0x7a, 0xe5, 0xe1, 0xb9, 0x96, 0xe6, 0x46, 0x4e, 0xe8, 0xd0, 0x86, 0x54, 0x7f, 0x49, 0xe0, 0x1a, 0xbb, 0x21, 0x77, 0x93, 0x7a, 0xfd, 0xcd, 0x79, 0x02, 0x4a, 0x30, 0xbd, 0x88, 0xed, 0x99, 0x1e, 0x92, 0x5b, 0x0f, 0x5a, 0xe6, 0xfc, 0x99, 0xc5, 0x58, 0x7d, 0x44, 0x79, 0x1e, 0x4c, 0x42, 0xce, 0xda, 0xf0, 0x11, 0xa5, 0xd0, 0x43, 0x54, 0x19, 0x99, 0x2d, 0xd6, 0x4a, 0xd6, 0x55, 0x11, 0xdf, 0x14, 0xe1, 0x95, 0xad, 0x37, 0x1f, 0xb4, 0xc2, 0xb9, 0x1c, 0xab, 0x39, 0xc7, 0x7f, 0xf0, 0x56, 0xfd, 0x2a, 0x81, 0xb1, 0xf5, 0x04, 0x42, 0x5e, 0x06, 0x97, 0x18, 0x16, 0x8a, 0x98, 0xc7, 0x92, 0xa9, 0x7c, 0x3b, 0x58, 0x9c, 0x16, 0x44, 0x75, 0xd7, 0x8d, 0x10, 0xa5, 0xdb, 0x71, 0x84, 0x03, 0xcf, 0x4a, 0x13, 0xfb, 0x35, 0x88, 0xfd, 0x14, 0xce, 0x50, 0x33, 0xd0, 0xcd, 0xe2, 0xff, 0xee, 0xa6, 0x59, 0x3f, 0xec, 0xaa, 0xd2, 0x51, 0x57, 0x95, 0x7e, 0x76, 0x55, 0xa9, 0xdd, 0x53, 0x0b, 0x47, 0x3d, 0xb5, 0xf0, 0xbd, 0xa7, 0x16, 0x1e, 0xcf, 0xfd, 0x75, 0x6f, 0xf7, 0xb2, 0xff, 0x0b, 0x7b, 0x9c, 0xc9, 0xb8, 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xe4, 0x3d, 0x09, 0x1d, 0x5a, 0x06, 0x00, 0x00, } func (m *BasicAllowance) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *BasicAllowance) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *BasicAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.Expiration != nil { n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(*m.Expiration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.Expiration):]) if err1 != nil { return 0, err1 } i -= n1 i = encodeVarintFeegrant(dAtA, i, uint64(n1)) i-- dAtA[i] = 0x12 } if len(m.SpendLimit) > 0 { for iNdEx := len(m.SpendLimit) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.SpendLimit[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintFeegrant(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa } } return len(dAtA) - i, nil } func (m *PeriodicAllowance) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *PeriodicAllowance) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PeriodicAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.PeriodReset, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.PeriodReset):]) if err2 != nil { return 0, err2 } i -= n2 i = encodeVarintFeegrant(dAtA, i, uint64(n2)) i-- dAtA[i] = 0x2a if len(m.PeriodCanSpend) > 0 { for iNdEx := len(m.PeriodCanSpend) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.PeriodCanSpend[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintFeegrant(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 } } if len(m.PeriodSpendLimit) > 0 { for iNdEx := len(m.PeriodSpendLimit) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.PeriodSpendLimit[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintFeegrant(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a } } n3, err3 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.Period, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.Period):]) if err3 != nil { return 0, err3 } i -= n3 i = encodeVarintFeegrant(dAtA, i, uint64(n3)) i-- dAtA[i] = 0x12 { size, err := m.Basic.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintFeegrant(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa return len(dAtA) - i, nil } func (m *AllowedMsgAllowance) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *AllowedMsgAllowance) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *AllowedMsgAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if len(m.AllowedMessages) > 0 { for iNdEx := len(m.AllowedMessages) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.AllowedMessages[iNdEx]) copy(dAtA[i:], m.AllowedMessages[iNdEx]) i = encodeVarintFeegrant(dAtA, i, uint64(len(m.AllowedMessages[iNdEx]))) i-- dAtA[i] = 0x12 } } if m.Allowance != nil { { size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintFeegrant(dAtA, i, uint64(size)) } i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *Grant) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Grant) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Grant) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.Allowance != nil { { size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintFeegrant(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a } if len(m.Grantee) > 0 { i -= len(m.Grantee) copy(dAtA[i:], m.Grantee) i = encodeVarintFeegrant(dAtA, i, uint64(len(m.Grantee))) i-- dAtA[i] = 0x12 } if len(m.Granter) > 0 { i -= len(m.Granter) copy(dAtA[i:], m.Granter) i = encodeVarintFeegrant(dAtA, i, uint64(len(m.Granter))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintFeegrant(dAtA []byte, offset int, v uint64) int { offset -= sovFeegrant(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *BasicAllowance) Size() (n int) { if m == nil { return 0 } var l int _ = l if len(m.SpendLimit) > 0 { for _, e := range m.SpendLimit { l = e.Size() n += 1 + l + sovFeegrant(uint64(l)) } } if m.Expiration != nil { l = github_com_cosmos_gogoproto_types.SizeOfStdTime(*m.Expiration) n += 1 + l + sovFeegrant(uint64(l)) } return n } func (m *PeriodicAllowance) Size() (n int) { if m == nil { return 0 } var l int _ = l l = m.Basic.Size() n += 1 + l + sovFeegrant(uint64(l)) l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.Period) n += 1 + l + sovFeegrant(uint64(l)) if len(m.PeriodSpendLimit) > 0 { for _, e := range m.PeriodSpendLimit { l = e.Size() n += 1 + l + sovFeegrant(uint64(l)) } } if len(m.PeriodCanSpend) > 0 { for _, e := range m.PeriodCanSpend { l = e.Size() n += 1 + l + sovFeegrant(uint64(l)) } } l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.PeriodReset) n += 1 + l + sovFeegrant(uint64(l)) return n } func (m *AllowedMsgAllowance) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.Allowance != nil { l = m.Allowance.Size() n += 1 + l + sovFeegrant(uint64(l)) } if len(m.AllowedMessages) > 0 { for _, s := range m.AllowedMessages { l = len(s) n += 1 + l + sovFeegrant(uint64(l)) } } return n } func (m *Grant) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Granter) if l > 0 { n += 1 + l + sovFeegrant(uint64(l)) } l = len(m.Grantee) if l > 0 { n += 1 + l + sovFeegrant(uint64(l)) } if m.Allowance != nil { l = m.Allowance.Size() n += 1 + l + sovFeegrant(uint64(l)) } return n } func sovFeegrant(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozFeegrant(x uint64) (n int) { return sovFeegrant(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *BasicAllowance) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: BasicAllowance: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: BasicAllowance: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SpendLimit", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } m.SpendLimit = append(m.SpendLimit, types.Coin{ }) if err := m.SpendLimit[len(m.SpendLimit)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Expiration", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } if m.Expiration == nil { m.Expiration = new(time.Time) } if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(m.Expiration, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipFeegrant(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthFeegrant } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *PeriodicAllowance) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: PeriodicAllowance: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PeriodicAllowance: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Basic", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } if err := m.Basic.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Period", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.Period, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PeriodSpendLimit", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } m.PeriodSpendLimit = append(m.PeriodSpendLimit, types.Coin{ }) if err := m.PeriodSpendLimit[len(m.PeriodSpendLimit)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PeriodCanSpend", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } m.PeriodCanSpend = append(m.PeriodCanSpend, types.Coin{ }) if err := m.PeriodCanSpend[len(m.PeriodCanSpend)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PeriodReset", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.PeriodReset, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipFeegrant(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthFeegrant } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *AllowedMsgAllowance) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: AllowedMsgAllowance: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: AllowedMsgAllowance: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Allowance", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } if m.Allowance == nil { m.Allowance = &types1.Any{ } } if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AllowedMessages", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } m.AllowedMessages = append(m.AllowedMessages, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipFeegrant(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthFeegrant } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *Grant) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: Grant: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Grant: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } m.Granter = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } m.Grantee = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Allowance", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowFeegrant } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthFeegrant } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthFeegrant } if postIndex > l { return io.ErrUnexpectedEOF } if m.Allowance == nil { m.Allowance = &types1.Any{ } } if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipFeegrant(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthFeegrant } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipFeegrant(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowFeegrant } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowFeegrant } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowFeegrant } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthFeegrant } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupFeegrant } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthFeegrant } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthFeegrant = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowFeegrant = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupFeegrant = fmt.Errorf("proto: unexpected end of group") ) ``` ### FeeAllowanceQueue Fee Allowances queue items are identified by combining the `FeeAllowancePrefixQueue` (i.e., 0x01), `expiration`, `grantee` (the account address of fee allowance grantee), `granter` (the account address of fee allowance granter). Endblocker checks `FeeAllowanceQueue` state for the expired grants and prunes them from `FeeAllowance` if there are any found. Fee allowance queue keys are stored in the state as follows: * Grant: `0x01 | expiration_bytes | grantee_addr_len (1 byte) | grantee_addr_bytes | granter_addr_len (1 byte) | granter_addr_bytes -> EmptyBytes` ## Messages ### Msg/GrantAllowance A fee allowance grant will be created with the `MsgGrantAllowance` message. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/feegrant/v1beta1/tx.proto#L29-L43 ``` ### Msg/RevokeAllowance An allowed grant fee allowance can be removed with the `MsgRevokeAllowance` message. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/feegrant/v1beta1/tx.proto#L48-L58 ``` ## Events The feegrant module emits the following events: ## Msg Server ### MsgGrantAllowance | Type | Attribute Key | Attribute Value | | ------- | ------------- | ------------------ | | message | action | set\_feegrant | | message | granter | `{granterAddress}` | | message | grantee | `{granteeAddress}` | ### MsgRevokeAllowance | Type | Attribute Key | Attribute Value | | ------- | ------------- | ------------------ | | message | action | revoke\_feegrant | | message | granter | `{granterAddress}` | | message | grantee | `{granteeAddress}` | ### Exec fee allowance | Type | Attribute Key | Attribute Value | | ------- | ------------- | ------------------ | | message | action | use\_feegrant | | message | granter | `{granterAddress}` | | message | grantee | `{granteeAddress}` | ### Prune fee allowances | Type | Attribute Key | Attribute Value | | ------- | ------------- | ----------------- | | message | action | prune\_feegrant | | message | pruner | `{prunerAddress}` | ## Client ### CLI A user can query and interact with the `feegrant` module using the CLI. #### Query The `query` commands allow users to query `feegrant` state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query feegrant --help ``` ##### grant The `grant` command allows users to query a grant for a given granter-grantee pair. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query feegrant grant [granter] [grantee] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query feegrant grant cosmos1.. cosmos1.. ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} allowance: '@type': /cosmos.feegrant.v1beta1.BasicAllowance expiration: null spend_limit: - amount: "100" denom: stake grantee: cosmos1.. granter: cosmos1.. ``` ##### grants The `grants` command allows users to query all grants for a given grantee. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query feegrant grants [grantee] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query feegrant grants cosmos1.. ``` Example Output: ```yml expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} allowances: - allowance: '@type': /cosmos.feegrant.v1beta1.BasicAllowance expiration: null spend_limit: - amount: "100" denom: stake grantee: cosmos1.. granter: cosmos1.. pagination: next_key: null total: "0" ``` #### Transactions The `tx` commands allow users to interact with the `feegrant` module. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx feegrant --help ``` ##### grant The `grant` command allows users to grant fee allowances to another account. The fee allowance can have an expiration date, a total spend limit, and/or a periodic spend limit. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx feegrant grant [granter] [grantee] [flags] ``` Example (one-time spend limit): ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx feegrant grant cosmos1.. cosmos1.. --spend-limit 100stake ``` Example (periodic spend limit): ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx feegrant grant cosmos1.. cosmos1.. --period 3600 --period-limit 10stake ``` ##### revoke The `revoke` command allows users to revoke a granted fee allowance. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx feegrant revoke [granter] [grantee] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx feegrant revoke cosmos1.. cosmos1.. ``` ### gRPC A user can query the `feegrant` module using gRPC endpoints. #### Allowance The `Allowance` endpoint allows users to query a granted fee allowance. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.feegrant.v1beta1.Query/Allowance ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"grantee":"cosmos1..","granter":"cosmos1.."}' \ localhost:9090 \ cosmos.feegrant.v1beta1.Query/Allowance ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "allowance": { "granter": "cosmos1..", "grantee": "cosmos1..", "allowance": { "@type": "/cosmos.feegrant.v1beta1.BasicAllowance", "spendLimit": [ { "denom": "stake", "amount": "100" } ] } } } ``` #### Allowances The `Allowances` endpoint allows users to query all granted fee allowances for a given grantee. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.feegrant.v1beta1.Query/Allowances ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address":"cosmos1.."}' \ localhost:9090 \ cosmos.feegrant.v1beta1.Query/Allowances ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "allowances": [ { "granter": "cosmos1..", "grantee": "cosmos1..", "allowance": { "@type": "/cosmos.feegrant.v1beta1.BasicAllowance", "spendLimit": [ { "denom": "stake", "amount": "100" } ] } } ], "pagination": { "total": "1" } } ``` # x/genutil Source: https://docs.cosmos.network/sdk/latest/modules/genutil/README The genutil package contains a variety of genesis utility functionalities for usage within a blockchain application. Namely: ## Concepts The `genutil` package contains a variety of genesis utility functionalities for usage within a blockchain application. Namely: * Genesis transactions related (gentx) * Commands for collection and creation of gentxs * `InitChain` processing of gentxs * Genesis file creation * Genesis file validation * Genesis file migration * CometBFT related initialization * Translation of an app genesis to a CometBFT genesis ## Genesis Genutil contains the data structure that defines an application genesis. An application genesis consist of a consensus genesis (g.e. CometBFT genesis) and application related genesis data. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package types import ( "bytes" "encoding/json" "errors" "fmt" "os" "time" cmtjson "github.com/cometbft/cometbft/libs/json" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmttypes "github.com/cometbft/cometbft/types" cmttime "github.com/cometbft/cometbft/types/time" "github.com/cosmos/cosmos-sdk/version" ) const ( // MaxChainIDLen is the maximum length of a chain ID. MaxChainIDLen = cmttypes.MaxChainIDLen ) // AppGenesis defines the app's genesis. type AppGenesis struct { AppName string `json:"app_name"` AppVersion string `json:"app_version"` GenesisTime time.Time `json:"genesis_time"` ChainID string `json:"chain_id"` InitialHeight int64 `json:"initial_height"` AppHash []byte `json:"app_hash"` AppState json.RawMessage `json:"app_state,omitempty"` Consensus *ConsensusGenesis `json:"consensus,omitempty"` } // NewAppGenesisWithVersion returns a new AppGenesis with the app name and app version already. func NewAppGenesisWithVersion(chainID string, appState json.RawMessage) *AppGenesis { return &AppGenesis{ AppName: version.AppName, AppVersion: version.Version, ChainID: chainID, AppState: appState, Consensus: &ConsensusGenesis{ Validators: nil, }, } } // ValidateAndComplete performs validation and completes the AppGenesis. func (ag *AppGenesis) ValidateAndComplete() error { if ag.ChainID == "" { return errors.New("genesis doc must include non-empty chain_id") } if len(ag.ChainID) > MaxChainIDLen { return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen) } if ag.InitialHeight < 0 { return fmt.Errorf("initial_height cannot be negative (got %v)", ag.InitialHeight) } if ag.InitialHeight == 0 { ag.InitialHeight = 1 } if ag.GenesisTime.IsZero() { ag.GenesisTime = cmttime.Now() } if err := ag.Consensus.ValidateAndComplete(); err != nil { return err } return nil } // SaveAs is a utility method for saving AppGenesis as a JSON file. func (ag *AppGenesis) SaveAs(file string) error { appGenesisBytes, err := json.MarshalIndent(ag, "", " ") if err != nil { return err } return os.WriteFile(file, appGenesisBytes, 0o600) } // AppGenesisFromFile reads the AppGenesis from the provided file. func AppGenesisFromFile(genFile string) (*AppGenesis, error) { jsonBlob, err := os.ReadFile(genFile) if err != nil { return nil, fmt.Errorf("couldn't read AppGenesis file (%s): %w", genFile, err) } var appGenesis AppGenesis if err := json.Unmarshal(jsonBlob, &appGenesis); err != nil { // fallback to CometBFT genesis var ctmGenesis cmttypes.GenesisDoc if err2 := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); err2 != nil { return nil, fmt.Errorf("error unmarshalling AppGenesis at %s: %w\n failed fallback to CometBFT GenDoc: %w", genFile, err, err2) } appGenesis = AppGenesis{ AppName: version.AppName, // AppVersion is not filled as we do not know it from a CometBFT genesis GenesisTime: ctmGenesis.GenesisTime, ChainID: ctmGenesis.ChainID, InitialHeight: ctmGenesis.InitialHeight, AppHash: ctmGenesis.AppHash, AppState: ctmGenesis.AppState, Consensus: &ConsensusGenesis{ Validators: ctmGenesis.Validators, Params: ctmGenesis.ConsensusParams, }, } } return &appGenesis, nil } // -------------------------- // CometBFT Genesis Handling // -------------------------- // ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc. func (ag *AppGenesis) ToGenesisDoc() (*cmttypes.GenesisDoc, error) { return &cmttypes.GenesisDoc{ GenesisTime: ag.GenesisTime, ChainID: ag.ChainID, InitialHeight: ag.InitialHeight, AppHash: ag.AppHash, AppState: ag.AppState, Validators: ag.Consensus.Validators, ConsensusParams: ag.Consensus.Params, }, nil } // ConsensusGenesis defines the consensus layer's genesis. // TODO(@julienrbrt) eventually abstract from CometBFT types type ConsensusGenesis struct { Validators []cmttypes.GenesisValidator `json:"validators,omitempty"` Params *cmttypes.ConsensusParams `json:"params,omitempty"` } // NewConsensusGenesis returns a ConsensusGenesis with given values. // It takes a proto consensus params so it can called from server export command. func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis { return &ConsensusGenesis{ Params: &cmttypes.ConsensusParams{ Block: cmttypes.BlockParams{ MaxBytes: params.Block.MaxBytes, MaxGas: params.Block.MaxGas, }, Evidence: cmttypes.EvidenceParams{ MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks, MaxAgeDuration: params.Evidence.MaxAgeDuration, MaxBytes: params.Evidence.MaxBytes, }, Validator: cmttypes.ValidatorParams{ PubKeyTypes: params.Validator.PubKeyTypes, }, }, Validators: validators, } } func (cs *ConsensusGenesis) MarshalJSON() ([]byte, error) { type Alias ConsensusGenesis return cmtjson.Marshal(&Alias{ Validators: cs.Validators, Params: cs.Params, }) } func (cs *ConsensusGenesis) UnmarshalJSON(b []byte) error { type Alias ConsensusGenesis result := Alias{ } if err := cmtjson.Unmarshal(b, &result); err != nil { return err } cs.Params = result.Params cs.Validators = result.Validators return nil } func (cs *ConsensusGenesis) ValidateAndComplete() error { if cs == nil { return fmt.Errorf("consensus genesis cannot be nil") } if cs.Params == nil { cs.Params = cmttypes.DefaultConsensusParams() } else if err := cs.Params.ValidateBasic(); err != nil { return err } for i, v := range cs.Validators { if v.Power == 0 { return fmt.Errorf("the genesis file cannot contain validators with no voting power: %v", v) } if len(v.Address) > 0 && !bytes.Equal(v.PubKey.Address(), v.Address) { return fmt.Errorf("incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address()) } if len(v.Address) == 0 { cs.Validators[i].Address = v.PubKey.Address() } } return nil } ``` The application genesis can then be translated to the consensus engine to the right format: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package types import ( "bytes" "encoding/json" "errors" "fmt" "os" "time" cmtjson "github.com/cometbft/cometbft/libs/json" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmttypes "github.com/cometbft/cometbft/types" cmttime "github.com/cometbft/cometbft/types/time" "github.com/cosmos/cosmos-sdk/version" ) const ( // MaxChainIDLen is the maximum length of a chain ID. MaxChainIDLen = cmttypes.MaxChainIDLen ) // AppGenesis defines the app's genesis. type AppGenesis struct { AppName string `json:"app_name"` AppVersion string `json:"app_version"` GenesisTime time.Time `json:"genesis_time"` ChainID string `json:"chain_id"` InitialHeight int64 `json:"initial_height"` AppHash []byte `json:"app_hash"` AppState json.RawMessage `json:"app_state,omitempty"` Consensus *ConsensusGenesis `json:"consensus,omitempty"` } // NewAppGenesisWithVersion returns a new AppGenesis with the app name and app version already. func NewAppGenesisWithVersion(chainID string, appState json.RawMessage) *AppGenesis { return &AppGenesis{ AppName: version.AppName, AppVersion: version.Version, ChainID: chainID, AppState: appState, Consensus: &ConsensusGenesis{ Validators: nil, }, } } // ValidateAndComplete performs validation and completes the AppGenesis. func (ag *AppGenesis) ValidateAndComplete() error { if ag.ChainID == "" { return errors.New("genesis doc must include non-empty chain_id") } if len(ag.ChainID) > MaxChainIDLen { return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen) } if ag.InitialHeight < 0 { return fmt.Errorf("initial_height cannot be negative (got %v)", ag.InitialHeight) } if ag.InitialHeight == 0 { ag.InitialHeight = 1 } if ag.GenesisTime.IsZero() { ag.GenesisTime = cmttime.Now() } if err := ag.Consensus.ValidateAndComplete(); err != nil { return err } return nil } // SaveAs is a utility method for saving AppGenesis as a JSON file. func (ag *AppGenesis) SaveAs(file string) error { appGenesisBytes, err := json.MarshalIndent(ag, "", " ") if err != nil { return err } return os.WriteFile(file, appGenesisBytes, 0o600) } // AppGenesisFromFile reads the AppGenesis from the provided file. func AppGenesisFromFile(genFile string) (*AppGenesis, error) { jsonBlob, err := os.ReadFile(genFile) if err != nil { return nil, fmt.Errorf("couldn't read AppGenesis file (%s): %w", genFile, err) } var appGenesis AppGenesis if err := json.Unmarshal(jsonBlob, &appGenesis); err != nil { // fallback to CometBFT genesis var ctmGenesis cmttypes.GenesisDoc if err2 := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); err2 != nil { return nil, fmt.Errorf("error unmarshalling AppGenesis at %s: %w\n failed fallback to CometBFT GenDoc: %w", genFile, err, err2) } appGenesis = AppGenesis{ AppName: version.AppName, // AppVersion is not filled as we do not know it from a CometBFT genesis GenesisTime: ctmGenesis.GenesisTime, ChainID: ctmGenesis.ChainID, InitialHeight: ctmGenesis.InitialHeight, AppHash: ctmGenesis.AppHash, AppState: ctmGenesis.AppState, Consensus: &ConsensusGenesis{ Validators: ctmGenesis.Validators, Params: ctmGenesis.ConsensusParams, }, } } return &appGenesis, nil } // -------------------------- // CometBFT Genesis Handling // -------------------------- // ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc. func (ag *AppGenesis) ToGenesisDoc() (*cmttypes.GenesisDoc, error) { return &cmttypes.GenesisDoc{ GenesisTime: ag.GenesisTime, ChainID: ag.ChainID, InitialHeight: ag.InitialHeight, AppHash: ag.AppHash, AppState: ag.AppState, Validators: ag.Consensus.Validators, ConsensusParams: ag.Consensus.Params, }, nil } // ConsensusGenesis defines the consensus layer's genesis. // TODO(@julienrbrt) eventually abstract from CometBFT types type ConsensusGenesis struct { Validators []cmttypes.GenesisValidator `json:"validators,omitempty"` Params *cmttypes.ConsensusParams `json:"params,omitempty"` } // NewConsensusGenesis returns a ConsensusGenesis with given values. // It takes a proto consensus params so it can called from server export command. func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis { return &ConsensusGenesis{ Params: &cmttypes.ConsensusParams{ Block: cmttypes.BlockParams{ MaxBytes: params.Block.MaxBytes, MaxGas: params.Block.MaxGas, }, Evidence: cmttypes.EvidenceParams{ MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks, MaxAgeDuration: params.Evidence.MaxAgeDuration, MaxBytes: params.Evidence.MaxBytes, }, Validator: cmttypes.ValidatorParams{ PubKeyTypes: params.Validator.PubKeyTypes, }, }, Validators: validators, } } func (cs *ConsensusGenesis) MarshalJSON() ([]byte, error) { type Alias ConsensusGenesis return cmtjson.Marshal(&Alias{ Validators: cs.Validators, Params: cs.Params, }) } func (cs *ConsensusGenesis) UnmarshalJSON(b []byte) error { type Alias ConsensusGenesis result := Alias{ } if err := cmtjson.Unmarshal(b, &result); err != nil { return err } cs.Params = result.Params cs.Validators = result.Validators return nil } func (cs *ConsensusGenesis) ValidateAndComplete() error { if cs == nil { return fmt.Errorf("consensus genesis cannot be nil") } if cs.Params == nil { cs.Params = cmttypes.DefaultConsensusParams() } else if err := cs.Params.ValidateBasic(); err != nil { return err } for i, v := range cs.Validators { if v.Power == 0 { return fmt.Errorf("the genesis file cannot contain validators with no voting power: %v", v) } if len(v.Address) > 0 && !bytes.Equal(v.PubKey.Address(), v.Address) { return fmt.Errorf("incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address()) } if len(v.Address) == 0 { cs.Validators[i].Address = v.PubKey.Address() } } return nil } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package server import ( "context" "errors" "fmt" "io" "net" "os" "runtime/pprof" "github.com/cometbft/cometbft/abci/server" cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" cmtcfg "github.com/cometbft/cometbft/config" "github.com/cometbft/cometbft/node" "github.com/cometbft/cometbft/p2p" pvm "github.com/cometbft/cometbft/privval" "github.com/cometbft/cometbft/proxy" "github.com/cometbft/cometbft/rpc/client/local" cmttypes "github.com/cometbft/cometbft/types" dbm "github.com/cosmos/cosmos-db" "github.com/hashicorp/go-metrics" "github.com/spf13/cobra" "github.com/spf13/pflag" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pruningtypes "cosmossdk.io/store/pruning/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server/api" serverconfig "github.com/cosmos/cosmos-sdk/server/config" servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" servercmtlog "github.com/cosmos/cosmos-sdk/server/log" "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/telemetry" "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/cosmos-sdk/version" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" ) const ( // CometBFT full-node start flags flagWithComet = "with-comet" flagAddress = "address" flagTransport = "transport" flagTraceStore = "trace-store" flagCPUProfile = "cpu-profile" FlagMinGasPrices = "minimum-gas-prices" FlagQueryGasLimit = "query-gas-limit" FlagHaltHeight = "halt-height" FlagHaltTime = "halt-time" FlagInterBlockCache = "inter-block-cache" FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades" FlagTrace = "trace" FlagInvCheckPeriod = "inv-check-period" FlagPruning = "pruning" FlagPruningKeepRecent = "pruning-keep-recent" FlagPruningInterval = "pruning-interval" FlagIndexEvents = "index-events" FlagMinRetainBlocks = "min-retain-blocks" FlagIAVLCacheSize = "iavl-cache-size" FlagDisableIAVLFastNode = "iavl-disable-fastnode" // state sync-related flags FlagStateSyncSnapshotInterval = "state-sync.snapshot-interval" FlagStateSyncSnapshotKeepRecent = "state-sync.snapshot-keep-recent" // api-related flags FlagAPIEnable = "api.enable" FlagAPISwagger = "api.swagger" FlagAPIAddress = "api.address" FlagAPIMaxOpenConnections = "api.max-open-connections" FlagRPCReadTimeout = "api.rpc-read-timeout" FlagRPCWriteTimeout = "api.rpc-write-timeout" FlagRPCMaxBodyBytes = "api.rpc-max-body-bytes" FlagAPIEnableUnsafeCORS = "api.enabled-unsafe-cors" // gRPC-related flags flagGRPCOnly = "grpc-only" flagGRPCEnable = "grpc.enable" flagGRPCAddress = "grpc.address" flagGRPCWebEnable = "grpc-web.enable" // mempool flags FlagMempoolMaxTxs = "mempool.max-txs" ) // StartCmdOptions defines options that can be customized in `StartCmdWithOptions`, type StartCmdOptions struct { // DBOpener can be used to customize db opening, for example customize db options or support different db backends, // default to the builtin db opener. DBOpener func(rootDir string, backendType dbm.BackendType) (dbm.DB, error) // PostSetup can be used to setup extra services under the same cancellable context, // it's not called in stand-alone mode, only for in-process mode. PostSetup func(svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error // AddFlags add custom flags to start cmd AddFlags func(cmd *cobra.Command) } // StartCmd runs the service passed in, either stand-alone or in-process with // CometBFT. func StartCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command { return StartCmdWithOptions(appCreator, defaultNodeHome, StartCmdOptions{ }) } // StartCmdWithOptions runs the service passed in, either stand-alone or in-process with // CometBFT. func StartCmdWithOptions(appCreator types.AppCreator, defaultNodeHome string, opts StartCmdOptions) *cobra.Command { if opts.DBOpener == nil { opts.DBOpener = openDB } cmd := &cobra.Command{ Use: "start", Short: "Run the full node", Long: `Run the full node application with CometBFT in or out of process. By default, the application will run with CometBFT in process. Pruning options can be provided via the '--pruning' flag or alternatively with '--pruning-keep-recent', and 'pruning-interval' together. For '--pruning' the options are as follows: default: the last 362880 states are kept, pruning at 10 block intervals nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) everything: 2 latest states will be kept; pruning at 10 block intervals. custom: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval' Node halting configurations exist in the form of two flags: '--halt-height' and '--halt-time'. During the ABCI Commit phase, the node will check if the current block height is greater than or equal to the halt-height or if the current block time is greater than or equal to the halt-time. If so, the node will attempt to gracefully shutdown and the block will not be committed. In addition, the node will not be able to commit subsequent blocks. For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag which accepts a path for the resulting pprof file. The node may be started in a 'query only' mode where only the gRPC and JSON HTTP API services are enabled via the 'grpc-only' flag. In this mode, CometBFT is bypassed and can be used when legacy queries are needed after an on-chain upgrade is performed. Note, when enabled, gRPC will also be automatically enabled. `, PreRunE: func(cmd *cobra.Command, _ []string) error { serverCtx := GetServerContextFromCmd(cmd) // Bind flags to the Context's Viper so the app construction can set // options accordingly. if err := serverCtx.Viper.BindPFlags(cmd.Flags()); err != nil { return err } _, err := GetPruningOptionsFromFlags(serverCtx.Viper) return err }, RunE: func(cmd *cobra.Command, _ []string) error { serverCtx := GetServerContextFromCmd(cmd) clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { return err } withCMT, _ := cmd.Flags().GetBool(flagWithComet) if !withCMT { serverCtx.Logger.Info("starting ABCI without CometBFT") } return wrapCPUProfile(serverCtx, func() error { return start(serverCtx, clientCtx, appCreator, withCMT, opts) }) }, } cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") cmd.Flags().Bool(flagWithComet, true, "Run abci app embedded in-process with CometBFT") cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc") cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file") cmd.Flags().String(FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)") cmd.Flags().Uint64(FlagQueryGasLimit, 0, "Maximum gas a Rest/Grpc query can consume. Blank and 0 imply unbounded.") cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{ }, "Skip a set of upgrade heights to continue the old binary") cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching") cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file") cmd.Flags().Bool(FlagTrace, false, "Provide full stack traces for errors in ABCI Log") cmd.Flags().String(FlagPruning, pruningtypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)") cmd.Flags().Uint64(FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')") cmd.Flags().Uint64(FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')") cmd.Flags().Uint(FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks") cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune CometBFT blocks") cmd.Flags().Bool(FlagAPIEnable, false, "Define if the API server should be enabled") cmd.Flags().Bool(FlagAPISwagger, false, "Define if swagger documentation should automatically be registered (Note: the API must also be enabled)") cmd.Flags().String(FlagAPIAddress, serverconfig.DefaultAPIAddress, "the API server address to listen on") cmd.Flags().Uint(FlagAPIMaxOpenConnections, 1000, "Define the number of maximum open connections") cmd.Flags().Uint(FlagRPCReadTimeout, 10, "Define the CometBFT RPC read timeout (in seconds)") cmd.Flags().Uint(FlagRPCWriteTimeout, 0, "Define the CometBFT RPC write timeout (in seconds)") cmd.Flags().Uint(FlagRPCMaxBodyBytes, 1000000, "Define the CometBFT maximum request body (in bytes)") cmd.Flags().Bool(FlagAPIEnableUnsafeCORS, false, "Define if CORS should be enabled (unsafe - use it at your own risk)") cmd.Flags().Bool(flagGRPCOnly, false, "Start the node in gRPC query only mode (no CometBFT process is started)") cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled") cmd.Flags().String(flagGRPCAddress, serverconfig.DefaultGRPCAddress, "the gRPC server address to listen on") cmd.Flags().Bool(flagGRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled)") cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") cmd.Flags().Bool(FlagDisableIAVLFastNode, false, "Disable fast node for IAVL tree") cmd.Flags().Int(FlagMempoolMaxTxs, mempool.DefaultMaxTx, "Sets MaxTx value for the app-side mempool") // support old flags name for backwards compatibility cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { if name == "with-tendermint" { name = flagWithComet } return pflag.NormalizedName(name) }) // add support for all CometBFT-specific command line options cmtcmd.AddNodeFlags(cmd) if opts.AddFlags != nil { opts.AddFlags(cmd) } return cmd } func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator, withCmt bool, opts StartCmdOptions) error { svrCfg, err := getAndValidateConfig(svrCtx) if err != nil { return err } app, appCleanupFn, err := startApp(svrCtx, appCreator, opts) if err != nil { return err } defer appCleanupFn() metrics, err := startTelemetry(svrCfg) if err != nil { return err } emitServerInfoMetrics() if !withCmt { return startStandAlone(svrCtx, app, opts) } return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts) } func startStandAlone(svrCtx *Context, app types.Application, opts StartCmdOptions) error { addr := svrCtx.Viper.GetString(flagAddress) transport := svrCtx.Viper.GetString(flagTransport) cmtApp := NewCometABCIWrapper(app) svr, err := server.NewServer(addr, transport, cmtApp) if err != nil { return fmt.Errorf("error creating listener: %v", err) } svr.SetLogger(servercmtlog.CometLoggerWrapper{ Logger: svrCtx.Logger.With("module", "abci-server") }) g, ctx := getCtx(svrCtx, false) g.Go(func() error { if err := svr.Start(); err != nil { svrCtx.Logger.Error("failed to start out-of-process ABCI server", "err", err) return err } // Wait for the calling process to be canceled or close the provided context, // so we can gracefully stop the ABCI server. <-ctx.Done() svrCtx.Logger.Info("stopping the ABCI server...") return errors.Join(svr.Stop(), app.Close()) }) return g.Wait() } func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, metrics *telemetry.Metrics, opts StartCmdOptions, ) error { cmtCfg := svrCtx.Config home := cmtCfg.RootDir gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly) g, ctx := getCtx(svrCtx, true) if gRPCOnly { // TODO: Generalize logic so that gRPC only is really in startStandAlone svrCtx.Logger.Info("starting node in gRPC only mode; CometBFT is disabled") svrCfg.GRPC.Enable = true } else { svrCtx.Logger.Info("starting node with ABCI CometBFT in-process") tmNode, cleanupFn, err := startCmtNode(ctx, cmtCfg, app, svrCtx) if err != nil { return err } defer cleanupFn() // Add the tx service to the gRPC router. We only need to register this // service if API or gRPC is enabled, and avoid doing so in the general // case, because it spawns a new local CometBFT RPC client. if svrCfg.API.Enable || svrCfg.GRPC.Enable { // Re-assign for making the client available below do not use := to avoid // shadowing the clientCtx variable. clientCtx = clientCtx.WithClient(local.New(tmNode)) app.RegisterTxService(clientCtx) app.RegisterTendermintService(clientCtx) app.RegisterNodeService(clientCtx, svrCfg) } } grpcSrv, clientCtx, err := startGrpcServer(ctx, g, svrCfg.GRPC, clientCtx, svrCtx, app) if err != nil { return err } err = startAPIServer(ctx, g, cmtCfg, svrCfg, clientCtx, svrCtx, app, home, grpcSrv, metrics) if err != nil { return err } if opts.PostSetup != nil { if err := opts.PostSetup(svrCtx, clientCtx, ctx, g); err != nil { return err } } // wait for signal capture and gracefully return // we are guaranteed to be waiting for the "ListenForQuitSignals" goroutine. return g.Wait() } // TODO: Move nodeKey into being created within the function. func startCmtNode( ctx context.Context, cfg *cmtcfg.Config, app types.Application, svrCtx *Context, ) (tmNode *node.Node, cleanupFn func(), err error) { nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile()) if err != nil { return nil, cleanupFn, err } cmtApp := NewCometABCIWrapper(app) tmNode, err = node.NewNodeWithContext( ctx, cfg, pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()), nodeKey, proxy.NewLocalClientCreator(cmtApp), getGenDocProvider(cfg), cmtcfg.DefaultDBProvider, node.DefaultMetricsProvider(cfg.Instrumentation), servercmtlog.CometLoggerWrapper{ Logger: svrCtx.Logger }, ) if err != nil { return tmNode, cleanupFn, err } if err := tmNode.Start(); err != nil { return tmNode, cleanupFn, err } cleanupFn = func() { if tmNode != nil && tmNode.IsRunning() { _ = tmNode.Stop() _ = app.Close() } } return tmNode, cleanupFn, nil } func getAndValidateConfig(svrCtx *Context) (serverconfig.Config, error) { config, err := serverconfig.GetConfig(svrCtx.Viper) if err != nil { return config, err } if err := config.ValidateBasic(); err != nil { return config, err } return config, nil } // returns a function which returns the genesis doc from the genesis file. func getGenDocProvider(cfg *cmtcfg.Config) func() (*cmttypes.GenesisDoc, error) { return func() (*cmttypes.GenesisDoc, error) { appGenesis, err := genutiltypes.AppGenesisFromFile(cfg.GenesisFile()) if err != nil { return nil, err } return appGenesis.ToGenesisDoc() } } func setupTraceWriter(svrCtx *Context) (traceWriter io.WriteCloser, cleanup func(), err error) { // clean up the traceWriter when the server is shutting down cleanup = func() { } traceWriterFile := svrCtx.Viper.GetString(flagTraceStore) traceWriter, err = openTraceWriter(traceWriterFile) if err != nil { return traceWriter, cleanup, err } // if flagTraceStore is not used then traceWriter is nil if traceWriter != nil { cleanup = func() { if err = traceWriter.Close(); err != nil { svrCtx.Logger.Error("failed to close trace writer", "err", err) } } } return traceWriter, cleanup, nil } func startGrpcServer( ctx context.Context, g *errgroup.Group, config serverconfig.GRPCConfig, clientCtx client.Context, svrCtx *Context, app types.Application, ) (*grpc.Server, client.Context, error) { if !config.Enable { // return grpcServer as nil if gRPC is disabled return nil, clientCtx, nil } _, port, err := net.SplitHostPort(config.Address) if err != nil { return nil, clientCtx, err } maxSendMsgSize := config.MaxSendMsgSize if maxSendMsgSize == 0 { maxSendMsgSize = serverconfig.DefaultGRPCMaxSendMsgSize } maxRecvMsgSize := config.MaxRecvMsgSize if maxRecvMsgSize == 0 { maxRecvMsgSize = serverconfig.DefaultGRPCMaxRecvMsgSize } grpcAddress := fmt.Sprintf("127.0.0.1:%s", port) // if gRPC is enabled, configure gRPC client for gRPC gateway grpcClient, err := grpc.Dial( grpcAddress, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultCallOptions( grpc.ForceCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()), grpc.MaxCallRecvMsgSize(maxRecvMsgSize), grpc.MaxCallSendMsgSize(maxSendMsgSize), ), ) if err != nil { return nil, clientCtx, err } clientCtx = clientCtx.WithGRPCClient(grpcClient) svrCtx.Logger.Debug("gRPC client assigned to client context", "target", grpcAddress) grpcSrv, err := servergrpc.NewGRPCServer(clientCtx, app, config) if err != nil { return nil, clientCtx, err } // Start the gRPC server in a goroutine. Note, the provided ctx will ensure // that the server is gracefully shut down. g.Go(func() error { return servergrpc.StartGRPCServer(ctx, svrCtx.Logger.With("module", "grpc-server"), config, grpcSrv) }) return grpcSrv, clientCtx, nil } func startAPIServer( ctx context.Context, g *errgroup.Group, cmtCfg *cmtcfg.Config, svrCfg serverconfig.Config, clientCtx client.Context, svrCtx *Context, app types.Application, home string, grpcSrv *grpc.Server, metrics *telemetry.Metrics, ) error { if !svrCfg.API.Enable { return nil } clientCtx = clientCtx.WithHomeDir(home) apiSrv := api.New(clientCtx, svrCtx.Logger.With("module", "api-server"), grpcSrv) app.RegisterAPIRoutes(apiSrv, svrCfg.API) if svrCfg.Telemetry.Enabled { apiSrv.SetTelemetry(metrics) } g.Go(func() error { return apiSrv.Start(ctx, svrCfg) }) return nil } func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) { if !cfg.Telemetry.Enabled { return nil, nil } return telemetry.New(cfg.Telemetry) } // wrapCPUProfile starts CPU profiling, if enabled, and executes the provided // callbackFn in a separate goroutine, then will wait for that callback to // return. // // NOTE: We expect the caller to handle graceful shutdown and signal handling. func wrapCPUProfile(svrCtx *Context, callbackFn func() error) error { if cpuProfile := svrCtx.Viper.GetString(flagCPUProfile); cpuProfile != "" { f, err := os.Create(cpuProfile) if err != nil { return err } svrCtx.Logger.Info("starting CPU profiler", "profile", cpuProfile) if err := pprof.StartCPUProfile(f); err != nil { return err } defer func() { svrCtx.Logger.Info("stopping CPU profiler", "profile", cpuProfile) pprof.StopCPUProfile() if err := f.Close(); err != nil { svrCtx.Logger.Info("failed to close cpu-profile file", "profile", cpuProfile, "err", err.Error()) } }() } return callbackFn() } // emitServerInfoMetrics emits server info related metrics using application telemetry. func emitServerInfoMetrics() { var ls []metrics.Label versionInfo := version.NewInfo() if len(versionInfo.GoVersion) > 0 { ls = append(ls, telemetry.NewLabel("go", versionInfo.GoVersion)) } if len(versionInfo.CosmosSdkVersion) > 0 { ls = append(ls, telemetry.NewLabel("version", versionInfo.CosmosSdkVersion)) } if len(ls) == 0 { return } telemetry.SetGaugeWithLabels([]string{"server", "info" }, 1, ls) } func getCtx(svrCtx *Context, block bool) (*errgroup.Group, context.Context) { ctx, cancelFn := context.WithCancel(context.Background()) g, ctx := errgroup.WithContext(ctx) // listen for quit signals so the calling parent process can gracefully exit ListenForQuitSignals(g, block, cancelFn, svrCtx.Logger) return g, ctx } func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions) (app types.Application, cleanupFn func(), err error) { traceWriter, traceCleanupFn, err := setupTraceWriter(svrCtx) if err != nil { return app, traceCleanupFn, err } home := svrCtx.Config.RootDir db, err := opts.DBOpener(home, GetAppDBBackend(svrCtx.Viper)) if err != nil { return app, traceCleanupFn, err } app = appCreator(svrCtx.Logger, db, traceWriter, svrCtx.Viper) cleanupFn = func() { traceCleanupFn() if localErr := app.Close(); localErr != nil { svrCtx.Logger.Error(localErr.Error()) } } return app, cleanupFn, nil } ``` ## Client ### CLI The genutil commands are available under the `genesis` subcommand. #### add-genesis-account Add a genesis account to `genesis.json`. Learn more [here](/sdk/latest/node/run-node#adding-genesis-accounts). #### collect-gentxs Collect genesis txs and output a `genesis.json` file. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis collect-gentxs ``` This will create a new `genesis.json` file that includes data from all the validators (we sometimes call it the "super genesis file" to distinguish it from single-validator genesis files). #### gentx Generate a genesis tx carrying a self delegation. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis gentx [key_name] [amount] --chain-id [chain-id] ``` This will create the genesis transaction for your new chain. Here `amount` should be at least `1000000000stake`. If you provide too much or too little, you will encounter an error when starting a node. #### migrate Migrate genesis to a specified target (SDK) version. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis migrate [target-version] ``` The `migrate` command is extensible and takes a `MigrationMap`. This map is a mapping of target versions to genesis migrations functions. When not using the default `MigrationMap`, it is recommended to still call the default `MigrationMap` corresponding the SDK version of the chain and prepend/append your own genesis migrations. #### validate-genesis Validates the genesis file at the default location or at the location passed as an argument. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis validate-genesis ``` Validate genesis only validates if the genesis is valid at the **current application binary**. For validating a genesis from a previous version of the application, use the `migrate` command to migrate the genesis to the current version. # x/gov Source: https://docs.cosmos.network/sdk/latest/modules/gov/README This paper specifies the Governance module of the Cosmos SDK, which was first described in the Cosmos Whitepaper in June 2016. ## Abstract This paper specifies the Governance module of the Cosmos SDK, which was first described in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md) in June 2016. The module enables Cosmos SDK based blockchain to support an on-chain governance system. In this system, holders of the native staking token of the chain can vote on proposals on a 1 token 1 vote basis. Next is a list of features the module currently supports: * **Proposal submission:** Users can submit proposals with a deposit. Once the minimum deposit is reached, the proposal enters voting period. The minimum deposit can be reached by collecting deposits from different users (including proposer) within deposit period. * **Vote:** Participants can vote on proposals that reached MinDeposit and entered voting period. * **Inheritance and penalties:** Delegators inherit their validator's vote if they don't vote themselves. * **Claiming deposit:** Users that deposited on proposals can recover their deposits if the proposal was accepted or rejected. If the proposal was vetoed, or never entered voting period (minimum deposit not reached within deposit period), the deposit is burned. This module is in use on the Cosmos Hub (a.k.a [gaia](https://github.com/cosmos/gaia)). Features that may be added in the future are described in [Future Improvements](#future-improvements). ## Contents The following specification uses *ATOM* as the native staking token. The module can be adapted to any Proof-Of-Stake blockchain by replacing *ATOM* with the native staking token of the chain. * [Concepts](#concepts) * [Proposal submission](#proposal-submission) * [Deposit](#deposit) * [Vote](#vote) * [State](#state) * [Constitution](#constitution) * [Proposals](#proposals) * [Parameters and base types](#parameters-and-base-types) * [Deposit](#deposit-1) * [ValidatorGovInfo](#validatorgovinfo) * [Stores](#stores) * [Proposal Processing Queue](#proposal-processing-queue) * [Legacy Proposal](#legacy-proposal) * [Messages](#messages) * [Proposal Submission](#proposal-submission-1) * [Deposit](#deposit-2) * [Vote](#vote-1) * [Events](#events) * [EndBlocker](#endblocker) * [Handlers](#handlers) * [Hooks](#hooks) * [AfterProposalSubmission](#afterproposalsubmission) * [AfterProposalDeposit](#afterproposaldeposit) * [AfterProposalVote](#afterproposalvote) * [AfterProposalFailedMinDeposit](#afterproposalfailedmindeposit) * [AfterProposalVotingPeriodEnded](#afterproposalvotingperiodended) * [Parameters](#parameters) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) * [REST](#rest) * [Metadata](#metadata) * [Proposal](#proposal-3) * [Vote](#vote-5) * [Future Improvements](#future-improvements) ## Concepts The governance process is divided in a few steps that are outlined below: * **Proposal submission:** Proposal is submitted to the blockchain with a deposit. * **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` transactions to vote on the proposal. * **Execution** After a period of time, the votes are tallied and depending on the result, the messages in the proposal will be executed. ### Proposal submission #### Right to submit a proposal Every account can submit proposals by sending a `MsgSubmitProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`. #### Proposal Messages A proposal includes an array of `sdk.Msg`s which are executed automatically if the proposal passes. The messages are executed by the governance `ModuleAccount` itself. Modules such as `x/upgrade`, that want to allow certain messages to be executed by governance only should add a whitelist within the respective msg server, granting the governance module the right to execute the message once a quorum has been reached. The governance module uses the `MsgServiceRouter` to check that these messages are correctly constructed and have a respective path to execute on but do not perform a full validity check. ### Deposit To prevent spam, proposals must be submitted with a deposit in the coins defined by the `MinDeposit` param. When a proposal is submitted, it has to be accompanied with a deposit that must be strictly positive, but can be inferior to `MinDeposit`. The submitter doesn't need to pay for the entire deposit on their own. The newly created proposal is stored in an *inactive proposal queue* and stays there until its deposit passes the `MinDeposit`. Other token holders can increase the proposal's deposit by sending a `Deposit` transaction. If a proposal doesn't pass the `MinDeposit` before the deposit end time (the time when deposits are no longer accepted), the proposal will be destroyed: the proposal will be removed from state and the deposit will be burned (see x/gov `EndBlocker`). When a proposal deposit passes the `MinDeposit` threshold (even during the proposal submission) before the deposit end time, the proposal will be moved into the *active proposal queue* and the voting period will begin. The deposit is kept in escrow and held by the governance `ModuleAccount` until the proposal is finalized (passed or rejected). #### Deposit refund and burn When a proposal is finalized, the coins from the deposit are either refunded or burned according to the final tally of the proposal: * If the proposal is approved or rejected but *not* vetoed, each deposit will be automatically refunded to its respective depositor (transferred from the governance `ModuleAccount`). * When the proposal is vetoed with greater than 1/3, deposits will be burned from the governance `ModuleAccount` and the proposal information along with its deposit information will be removed from state. * All refunded or burned deposits are removed from the state. Events are issued when burning or refunding a deposit. ### Vote #### Participants *Participants* are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals. Note that when *participants* have bonded and unbonded Atoms, their voting power is calculated from their bonded Atom holdings only. #### Voting period Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We define `Voting period` as the interval between the moment the vote opens and the moment the vote closes. The initial value of `Voting period` is 2 weeks. #### Option set The option set of a proposal refers to the set of choices a participant can choose from when casting its vote. The initial option set includes the following options: * `Yes` * `No` * `NoWithVeto` * `Abstain` `NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote. *Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ option that casts a `NoWithVeto` vote.* #### Weighted Votes [ADR-037](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-037-gov-split-vote.md) introduces the weighted vote feature which allows a staker to split their votes into several voting options. For example, it could use 70% of its voting power to vote Yes and 30% of its voting power to vote No. Often times the entity owning that address might not be a single individual. For example, a company might have different stakeholders who want to vote differently, and so it makes sense to allow them to split their voting power. Currently, it is not possible for them to do "passthrough voting" and giving their users voting rights over their tokens. However, with this system, exchanges can poll their users for voting preferences, and then vote on-chain proportionally to the results of the poll. To represent weighted vote on chain, we use the following Protobuf message. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1beta1/gov.proto#L32-L45 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1beta1/gov.proto#L180-L198 ``` For a weighted vote to be valid, the `options` field must not contain duplicate vote options, and the sum of weights of all options must be equal to 1. #### Custom Vote Calculation Cosmos SDK v0.53.0 introduced an option for developers to define a custom vote result and voting power calculation function. As of v0.54, `x/gov` has been decoupled from `x/staking`: the `keeper.NewKeeper` constructor now requires a `CalculateVoteResultsAndVotingPowerFn` as a required parameter instead of a `StakingKeeper`. To use the default staking-based tally logic, wrap your staking keeper with `keeper.NewDefaultCalculateVoteResultsAndVotingPower(stakingKeeper)`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package keeper import ( "context" "fmt" "cosmossdk.io/collections" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // CalculateVoteResultsAndVotingPowerFn is a function signature for calculating vote results and voting power // It can be overridden to customize the voting power calculation for proposals // It gets the proposal tallied and the validators governance infos (validator power, voting power, etc.) // It must return the total voting power and the results of the vote type CalculateVoteResultsAndVotingPowerFn func( ctx context.Context, k Keeper, proposal v1.Proposal, validators map[string]v1.ValidatorGovInfo, ) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) func defaultCalculateVoteResultsAndVotingPower( ctx context.Context, k Keeper, proposal v1.Proposal, validators map[string]v1.ValidatorGovInfo, ) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) { totalVotingPower := math.LegacyZeroDec() results = make(map[v1.VoteOption]math.LegacyDec) results[v1.OptionYes] = math.LegacyZeroDec() results[v1.OptionAbstain] = math.LegacyZeroDec() results[v1.OptionNo] = math.LegacyZeroDec() results[v1.OptionNoWithVeto] = math.LegacyZeroDec() rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id) votesToRemove := []collections.Pair[uint64, sdk.AccAddress]{ } err = k.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) { // if validator, just record it in the map voter, err := k.authKeeper.AddressCodec().StringToBytes(vote.Voter) if err != nil { return false, err } valAddrStr, err := k.sk.ValidatorAddressCodec().BytesToString(voter) if err != nil { return false, err } if val, ok := validators[valAddrStr]; ok { val.Vote = vote.Options validators[valAddrStr] = val } // iterate over all delegations from voter, deduct from any delegated-to validators err = k.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) { valAddrStr := delegation.GetValidatorAddr() if val, ok := validators[valAddrStr]; ok { // There is no need to handle the special case that validator address equal to voter address. // Because voter's voting power will tally again even if there will be deduction of voter's voting power from validator. val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares()) validators[valAddrStr] = val // delegation shares * bonded / total shares votingPower := delegation.GetShares().MulInt(val.ValidatorPower).Quo(val.DelegatorShares) for _, option := range vote.Options { weight, _ := math.LegacyNewDecFromStr(option.Weight) subPower := votingPower.Mul(weight) results[option.Option] = results[option.Option].Add(subPower) } totalVotingPower = totalVotingPower.Add(votingPower) } return false }) if err != nil { return false, err } votesToRemove = append(votesToRemove, key) return false, nil }) if err != nil { return math.LegacyZeroDec(), nil, fmt.Errorf("error while iterating delegations: %w", err) } // remove all votes from store for _, key := range votesToRemove { if err := k.Votes.Remove(ctx, key); err != nil { return math.LegacyDec{ }, nil, fmt.Errorf("error while removing vote (%d/%s): %w", key.K1(), key.K2(), err) } } // iterate over the validators again to tally their voting power for _, val := range validators { if len(val.Vote) == 0 { continue } sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions) votingPower := sharesAfterDeductions.MulInt(val.ValidatorPower).Quo(val.DelegatorShares) for _, option := range val.Vote { weight, _ := math.LegacyNewDecFromStr(option.Weight) subPower := votingPower.Mul(weight) results[option.Option] = results[option.Option].Add(subPower) } totalVotingPower = totalVotingPower.Add(votingPower) } return totalVotingPower, results, nil } // getCurrentValidators fetches all the bonded validators, insert them into currValidators func (k Keeper) getCurrentValidators(ctx context.Context) (map[string]v1.ValidatorGovInfo, error) { currValidators := make(map[string]v1.ValidatorGovInfo) if err := k.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { valBz, err := k.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator()) if err != nil { return false } currValidators[validator.GetOperator()] = v1.NewValidatorGovInfo( valBz, validator.GetValidatorPower(), validator.GetDelegatorShares(), math.LegacyZeroDec(), v1.WeightedVoteOptions{ }, ) return false }); err != nil { return nil, err } return currValidators, nil } // Tally iterates over the votes and updates the tally of a proposal based on the voting power of the // voters func (k Keeper) Tally(ctx context.Context, proposal v1.Proposal) (passes, burnDeposits bool, tallyResults v1.TallyResult, err error) { currValidators, err := k.getCurrentValidators(ctx) if err != nil { return false, false, tallyResults, fmt.Errorf("error while getting current validators: %w", err) } tallyFn := k.calculateVoteResultsAndVotingPowerFn totalVotingPower, results, err := tallyFn(ctx, k, proposal, currValidators) if err != nil { return false, false, tallyResults, fmt.Errorf("error while calculating tally results: %w", err) } tallyResults = v1.NewTallyResultFromMap(results) // TODO: Upgrade the spec to cover all of these cases & remove pseudocode. // If there is no staked coins, the proposal fails totalBonded, err := k.sk.TotalValidatorPower(ctx) if err != nil { return false, false, tallyResults, err } if totalBonded.IsZero() { return false, false, tallyResults, nil } params, err := k.Params.Get(ctx) if err != nil { return false, false, tallyResults, fmt.Errorf("error while getting params: %w", err) } // If there is not enough quorum of votes, the proposal fails percentVoting := totalVotingPower.Quo(math.LegacyNewDecFromInt(totalBonded)) quorum, _ := math.LegacyNewDecFromStr(params.Quorum) if percentVoting.LT(quorum) { return false, params.BurnVoteQuorum, tallyResults, nil } // If no one votes (everyone abstains), proposal fails if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(math.LegacyZeroDec()) { return false, false, tallyResults, nil } // If more than 1/3 of voters veto, proposal fails vetoThreshold, _ := math.LegacyNewDecFromStr(params.VetoThreshold) if results[v1.OptionNoWithVeto].Quo(totalVotingPower).GT(vetoThreshold) { return false, params.BurnVoteVeto, tallyResults, nil } // If more than 1/2 of non-abstaining voters vote Yes, proposal passes // For expedited 2/3 var thresholdStr string if proposal.Expedited { thresholdStr = params.GetExpeditedThreshold() } else { thresholdStr = params.GetThreshold() } threshold, _ := math.LegacyNewDecFromStr(thresholdStr) if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) { return true, false, tallyResults, nil } // If more than 1/2 of non-abstaining voters vote No, proposal fails return false, false, tallyResults, nil } ``` This gives developers a more expressive way to handle governance on their appchains. Developers can now build systems with: * Quadratic Voting * Time-weighted Voting * Reputation-Based voting ##### Example ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func myCustomVotingFunction( ctx context.Context, k Keeper, proposal v1.Proposal, validators map[string]v1.ValidatorGovInfo, ) (totalVoterPower math.LegacyDec, results map[v1.VoteOption]math.LegacyDec, err error) { // ... tally logic } govKeeper := govkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.DistrKeeper, // optional: can be nil if the module address is not used as a cancellation fee destination app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), myCustomVotingFunction, // required: CalculateVoteResultsAndVotingPowerFn ) ``` ### Quorum Quorum is defined as the minimum percentage of voting power that needs to be cast on a proposal for the result to be valid. ### Expedited Proposals A proposal can be expedited, making the proposal use shorter voting duration and a higher tally threshold by its default. If an expedited proposal fails to meet the threshold within the scope of shorter voting duration, the expedited proposal is then converted to a regular proposal and restarts voting under regular voting conditions. #### Threshold Threshold is defined as the minimum proportion of `Yes` votes (excluding `Abstain` votes) for the proposal to be accepted. Initially, the threshold is set at 50% of `Yes` votes, excluding `Abstain` votes. A possibility to veto exists if more than 1/3rd of all votes are `NoWithVeto` votes. Note, both of these values are derived from the `Params` on-chain parameter, which is modifiable by governance. This means that proposals are accepted iff: * There exist bonded tokens. * Quorum has been achieved. * The proportion of `Abstain` votes is inferior to 1/1. * The proportion of `NoWithVeto` votes is inferior to 1/3, including `Abstain` votes. * The proportion of `Yes` votes, excluding `Abstain` votes, at the end of the voting period is superior to 1/2. For expedited proposals, by default, the threshold is higher than with a *normal proposal*, namely, 66.7%. #### Inheritance If a delegator does not vote, it will inherit its validator vote. * If the delegator votes before its validator, it will not inherit from the validator's vote. * If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is urgent, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as proposals require more than 2/3rd of the total voting power to pass, when tallied at the end of the voting period. Because as little as 1/3 + 1 validation power could collude to censor transactions, non-collusion is already assumed for ranges exceeding this threshold. #### Validator’s punishment for non-voting At present, validators are not punished for failing to vote. #### Governance address Later, we may add permissioned keys that could only sign txs from certain modules. For the MVP, the `Governance address` will be the main validator address generated at account creation. This address corresponds to a different PrivKey than the CometBFT PrivKey which is responsible for signing consensus messages. Validators thus do not have to sign governance transactions with the sensitive CometBFT PrivKey. #### Burnable Params There are three parameters that define if the deposit of a proposal should be burned or returned to the depositors. * `BurnVoteVeto` burns the proposal deposit if the proposal gets vetoed. * `BurnVoteQuorum` burns the proposal deposit if the proposal deposit if the vote does not reach quorum. * `BurnProposalDepositPrevote` burns the proposal deposit if it does not enter the voting phase. > Note: These parameters are modifiable via governance. ## State ### Constitution `Constitution` is found in the genesis state. It is a string field intended to be used to describe the purpose of a particular blockchain, and its expected norms. A few examples of how the constitution field can be used: * define the purpose of the chain, laying a foundation for its future development * set expectations for delegators * set expectations for validators * define the chain's relationship to "meatspace" entities, like a foundation or corporation Since this is more of a social feature than a technical feature, we'll now get into some items that may have been useful to have in a genesis constitution: * What limitations on governance exist, if any? * is it okay for the community to slash the wallet of a whale that they no longer feel that they want around? (viz: Juno Proposal 4 and 16) * can governance "socially slash" a validator who is using unapproved MEV? (viz: commonwealth.im/osmosis) * In the event of an economic emergency, what should validators do? * Terra crash of May, 2022, saw validators choose to run a new binary with code that had not been approved by governance, because the governance token had been inflated to nothing. * What is the purpose of the chain, specifically? * best example of this is the Cosmos hub, where different founding groups, have different interpertations of the purpose of the network. This genesis entry, "constitution" hasn't been designed for existing chains, who should likely just ratify a constitution using their governance system. Instead, this is for new chains. It will allow for validators to have a much clearer idea of purpose and the expectations placed on them while operating their nodes. Likewise, for community members, the constitution will give them some idea of what to expect from both the "chain team" and the validators, respectively. This constitution is designed to be immutable, and placed only in genesis, though that could change over time by a pull request to the cosmos-sdk that allows for the constitution to be changed by governance. Communities wishing to make amendments to their original constitution should use the governance mechanism and a "signaling proposal" to do exactly that. **Ideal use scenario for a cosmos chain constitution** As a chain developer, you decide that you'd like to provide clarity to your key user groups: * validators * token holders * developers (yourself) You use the constitution to immutably store some Markdown in genesis, so that when difficult questions come up, the constitution can provide guidance to the community. ### Proposals `Proposal` objects are used to tally votes and generally track the proposal's state. They contain an array of arbitrary `sdk.Msg`'s which the governance module will attempt to resolve and then execute if the proposal passes. `Proposal`'s are identified by a unique id and contains a series of timestamps: `submit_time`, `deposit_end_time`, `voting_start_time`, `voting_end_time` which track the lifecycle of a proposal ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/gov.proto#L50-L101 ``` A proposal will generally require more than just a set of messages to explain its purpose but need some greater justification and allow a means for interested participants to discuss and debate the proposal. In most cases, **it is encouraged to have an off-chain system that supports the on-chain governance process**. To accommodate for this, a proposal contains a special **`metadata`** field, a string, which can be used to add context to the proposal. The `metadata` field allows custom use for networks, however, it is expected that the field contains a URL or some form of CID using a system such as [IPFS](https://docs.ipfs.io/concepts/content-addressing/). To support the case of interoperability across networks, the SDK recommends that the `metadata` represents the following `JSON` template: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "...", "description": "...", "forum": "...", // a link to the discussion platform (i.e. Discord) "other": "..." // any extra data that doesn't correspond to the other fields } ``` This makes it far easier for clients to support multiple networks. The metadata has a maximum length that is chosen by the app developer, and passed into the gov keeper as a config. The default maximum length in the SDK is 255 characters. #### Writing a module that uses governance There are many aspects of a chain, or of the individual modules that you may want to use governance to perform such as changing various parameters. This is very simple to do. First, write out your message types and `MsgServer` implementation. Add an `authority` field to the keeper which will be populated in the constructor with the governance module account: `govKeeper.GetGovernanceAccount().GetAddress()`. Then for the methods in the `msg_server.go`, perform a check on the message that the signer matches `authority`. This will prevent any user from executing that message. ### Parameters and base types `Parameters` define the rules according to which votes are run. There can only be one active parameter set at any given time. If governance wants to change a parameter set, either to modify a value or add/remove a parameter field, a new parameter set has to be created and the previous one rendered inactive. #### Params ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/gov.proto#L193-L255 ``` Additionally, we introduce some basic types: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Vote byte const ( VoteYes = 0x1 VoteNo = 0x2 VoteNoWithVeto = 0x3 VoteAbstain = 0x4 ) type ProposalType string const ( ProposalTypePlainText = "Text" ProposalTypeSoftwareUpgrade = "SoftwareUpgrade" ) type ProposalStatus byte const ( StatusNil ProposalStatus = 0x00 StatusDepositPeriod ProposalStatus = 0x01 // Proposal is submitted. Participants can deposit on it but not vote StatusVotingPeriod ProposalStatus = 0x02 // MinDeposit is reached, participants can vote StatusPassed ProposalStatus = 0x03 // Proposal passed and successfully executed StatusRejected ProposalStatus = 0x04 // Proposal has been rejected StatusFailed ProposalStatus = 0x05 // Proposal passed but failed execution ) ``` ### Deposit ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/gov.proto#L37-L48 ``` ### ValidatorGovInfo This type is used in a temp map when tallying ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ValidatorGovInfo struct { Minus sdk.Dec Vote Vote } ``` ## Stores Stores are KVStores in the multi-store. The key to find the store is the first parameter in the list We will use one KVStore `Governance` to store four mappings: * A mapping from `proposalID|'proposal'` to `Proposal`. * A mapping from `proposalID|'addresses'|address` to `Vote`. This mapping allows us to query all addresses that voted on the proposal along with their vote by doing a range query on `proposalID:addresses`. * A mapping from `ParamsKey|'Params'` to `Params`. This map allows to query all x/gov params. * A mapping from `VotingPeriodProposalKeyPrefix|proposalID` to a single byte. This allows us to know if a proposal is in the voting period or not with very low gas cost. For pseudocode purposes, here are the two function we will use to read or write in stores: * `load(StoreKey, Key)`: Retrieve item stored at key `Key` in store found at key `StoreKey` in the multistore * `store(StoreKey, Key, value)`: Write value `Value` at key `Key` in store found at key `StoreKey` in the multistore ### Proposal Processing Queue **Store:** * `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the `ProposalIDs` of proposals that reached `MinDeposit`. During each `EndBlock`, all the proposals that have reached the end of their voting period are processed. To process a finished proposal, the application tallies the votes, computes the votes of each validator and checks if every validator in the validator set has voted. If the proposal is accepted, deposits are refunded. Finally, the proposal content `Handler` is executed. And the pseudocode for the `ProposalProcessingQueue`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} in EndBlock do for finishedProposalID in GetAllFinishedProposalIDs(block.Time) proposal = load(Governance, ) // proposal is a const key validators = Keeper.getAllValidators() tmpValMap := map(sdk.AccAddress) ValidatorGovInfo // Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes for each validator in validators tmpValMap(validator.OperatorAddr).Minus = 0 // Tally voterIterator = rangeQuery(Governance, ) //return all the addresses that voted on the proposal for each (voterAddress, vote) in voterIterator delegations = stakingKeeper.getDelegations(voterAddress) // get all delegations for current voter for each delegation in delegations // make sure delegation.Shares does NOT include shares being unbonded tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares proposal.updateTally(vote, delegation.Shares) _, isVal = stakingKeeper.getValidator(voterAddress) if (isVal) tmpValMap(voterAddress).Vote = vote tallyingParam = load(Params, 'TallyingParam') // Update tally if validator voted for each validator in validators if tmpValMap(validator).HasVoted proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus)) // Check if proposal is accepted or rejected totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingParam.Veto) // proposal was accepted at the end of the voting period // refund deposits (non-voters already punished) for each (amount, depositor) in proposal.Deposits depositor.AtomBalance += amount stateWriter, err := proposal.Handler() if err != nil // proposal passed but failed during state execution proposal.CurrentStatus = ProposalStatusFailed else // proposal pass and state is persisted proposal.CurrentStatus = ProposalStatusAccepted stateWriter.save() else // proposal was rejected proposal.CurrentStatus = ProposalStatusRejected store(Governance, , proposal) ``` ### Legacy Proposal Legacy proposals are deprecated. Use the new proposal flow by granting the governance module the right to execute the message. A legacy proposal is the old implementation of governance proposal. Contrary to proposal that can contain any messages, a legacy proposal allows to submit a set of pre-defined proposals. These proposals are defined by their types and handled by handlers that are registered in the gov v1beta1 router. More information on how to submit proposals in the [client section](#client). ## Messages ### Proposal Submission Proposals can be submitted by any account via a `MsgSubmitProposal` transaction. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/tx.proto#L57-L88 ``` All `sdk.Msgs` passed into the `messages` field of a `MsgSubmitProposal` message must be registered in the app's `MsgServiceRouter`. Each of these messages must have one signer, namely the gov module account. And finally, the metadata length must not be larger than the `maxMetadataLen` config passed into the gov keeper. The `initialDeposit` must be strictly positive and conform to the accepted denom of the `MinDeposit` param. **State modifications:** * Generate new `proposalID` * Create new `Proposal` * Initialize `Proposal`'s attributes * Decrease balance of sender by `InitialDeposit` * If `MinDeposit` is reached: * Push `proposalID` in `ProposalProcessingQueue` * Transfer `InitialDeposit` from the `Proposer` to the governance `ModuleAccount` ### Deposit Once a proposal is submitted, if `Proposal.TotalDeposit < ActiveParam.MinDeposit`, Atom holders can send `MsgDeposit` transactions to increase the proposal's deposit. A deposit is accepted iff: * The proposal exists * The proposal is not in the voting period * The deposited coins are conform to the accepted denom from the `MinDeposit` param ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/tx.proto#L153-L166 ``` **State modifications:** * Decrease balance of sender by `deposit` * Add `deposit` of sender in `proposal.Deposits` * Increase `proposal.TotalDeposit` by sender's `deposit` * If `MinDeposit` is reached: * Push `proposalID` in `ProposalProcessingQueueEnd` * Transfer `Deposit` from the `proposer` to the governance `ModuleAccount` ### Vote Once `ActiveParam.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `MsgVote` transactions to cast their vote on the proposal. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/tx.proto#L111-L127 ``` **State modifications:** * Record `Vote` of sender Gas cost for this message has to take into account the future tallying of the vote in EndBlocker. ## Events The governance module emits the following events: ### EndBlocker | Type | Attribute Key | Attribute Value | | ------------------ | ---------------- | ------------------ | | inactive\_proposal | proposal\_id | `{proposalID}` | | inactive\_proposal | proposal\_result | `{proposalResult}` | | active\_proposal | proposal\_id | `{proposalID}` | | active\_proposal | proposal\_result | `{proposalResult}` | ### Handlers #### MsgSubmitProposal | Type | Attribute Key | Attribute Value | | --------------------- | --------------------- | ----------------- | | submit\_proposal | proposal\_id | `{proposalID}` | | submit\_proposal \[0] | voting\_period\_start | `{proposalID}` | | proposal\_deposit | amount | `{depositAmount}` | | proposal\_deposit | proposal\_id | `{proposalID}` | | message | module | governance | | message | action | submit\_proposal | | message | sender | `{senderAddress}` | * \[0] Event only emitted if the voting period starts during the submission. #### MsgVote | Type | Attribute Key | Attribute Value | | -------------- | ------------- | ----------------- | | proposal\_vote | option | `{voteOption}` | | proposal\_vote | proposal\_id | `{proposalID}` | | message | module | governance | | message | action | vote | | message | sender | `{senderAddress}` | #### MsgVoteWeighted | Type | Attribute Key | Attribute Value | | -------------- | ------------- | ----------------------- | | proposal\_vote | option | `{weightedVoteOptions}` | | proposal\_vote | proposal\_id | `{proposalID}` | | message | module | governance | | message | action | vote | | message | sender | `{senderAddress}` | #### MsgDeposit | Type | Attribute Key | Attribute Value | | ---------------------- | --------------------- | ----------------- | | proposal\_deposit | amount | `{depositAmount}` | | proposal\_deposit | proposal\_id | `{proposalID}` | | proposal\_deposit \[0] | voting\_period\_start | `{proposalID}` | | message | module | governance | | message | action | deposit | | message | sender | `{senderAddress}` | * \[0] Event only emitted if the voting period starts during the submission. ## Hooks The governance module exposes a `GovHooks` interface that allows other modules to react to governance events. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type GovHooks interface { AfterProposalSubmission(ctx context.Context, proposalID uint64, proposerAddr sdk.AccAddress) error AfterProposalDeposit(ctx context.Context, proposalID uint64, depositorAddr sdk.AccAddress) error AfterProposalVote(ctx context.Context, proposalID uint64, voterAddr sdk.AccAddress) error AfterProposalFailedMinDeposit(ctx context.Context, proposalID uint64) error AfterProposalVotingPeriodEnded(ctx context.Context, proposalID uint64) error } ``` ### AfterProposalSubmission Called after a proposal is submitted. The hook receives the proposal ID and the proposer's address. **Note:** The `proposerAddr` parameter was added in a recent release. If you are implementing `GovHooks`, you must update your `AfterProposalSubmission` method signature to include `proposerAddr sdk.AccAddress` as a third parameter. **Before:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64) error { // implementation } ``` **After:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64, proposerAddr sdk.AccAddress) error { // implementation } ``` ### AfterProposalDeposit Called after a deposit is made on a proposal. ### AfterProposalVote Called after a vote is cast on a proposal. ### AfterProposalFailedMinDeposit Called when a proposal fails to reach the minimum deposit within the deposit period. ### AfterProposalVotingPeriodEnded Called when a proposal's voting period ends. ## Parameters The governance module contains the following parameters: | Key | Type | Example | | -------------------------------- | ---------------- | ------------------------------------------ | | min\_deposit | array (coins) | \[`{"denom":"uatom","amount":"10000000"}`] | | max\_deposit\_period | string (time ns) | "172800000000000" (17280s) | | voting\_period | string (time ns) | "172800000000000" (17280s) | | quorum | string (dec) | "0.334000000000000000" | | threshold | string (dec) | "0.500000000000000000" | | veto | string (dec) | "0.334000000000000000" | | expedited\_threshold | string (time ns) | "0.667000000000000000" | | expedited\_voting\_period | string (time ns) | "86400000000000" (8600s) | | expedited\_min\_deposit | array (coins) | \[`{"denom":"uatom","amount":"50000000"}`] | | burn\_proposal\_deposit\_prevote | bool | false | | burn\_vote\_quorum | bool | false | | burn\_vote\_veto | bool | true | | min\_initial\_deposit\_ratio | string | "0.1" | **NOTE**: The governance module contains parameters that are objects unlike other modules. If only a subset of parameters are desired to be changed, only they need to be included and not the entire parameter object structure. ## Client ### CLI A user can query and interact with the `gov` module using the CLI. #### Query The `query` commands allow users to query `gov` state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov --help ``` ##### deposit The `deposit` command allows users to query a deposit for a given proposal from a given depositor. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov deposit [proposal-id] [depositer-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov deposit 1 cosmos1.. ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} amount: - amount: "100" denom: stake depositor: cosmos1.. proposal_id: "1" ``` ##### deposits The `deposits` command allows users to query all deposits for a given proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov deposits [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov deposits 1 ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} deposits: - amount: - amount: "100" denom: stake depositor: cosmos1.. proposal_id: "1" pagination: next_key: null total: "0" ``` ##### param The `param` command allows users to query a given parameter for the `gov` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov param [param-type] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov param voting ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} voting_period: "172800000000000" ``` ##### params The `params` command allows users to query all parameters for the `gov` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov params [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov params ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} deposit_params: max_deposit_period: 172800s min_deposit: - amount: "10000000" denom: stake params: expedited_min_deposit: - amount: "50000000" denom: stake expedited_threshold: "0.670000000000000000" expedited_voting_period: 86400s max_deposit_period: 172800s min_deposit: - amount: "10000000" denom: stake min_initial_deposit_ratio: "0.000000000000000000" proposal_cancel_burn_rate: "0.500000000000000000" quorum: "0.334000000000000000" threshold: "0.500000000000000000" veto_threshold: "0.334000000000000000" voting_period: 172800s tally_params: quorum: "0.334000000000000000" threshold: "0.500000000000000000" veto_threshold: "0.334000000000000000" voting_params: voting_period: 172800s ``` ##### proposal The `proposal` command allows users to query a given proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov proposal [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov proposal 1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} deposit_end_time: "2022-03-30T11:50:20.819676256Z" final_tally_result: abstain_count: "0" no_count: "0" no_with_veto_count: "0" yes_count: "0" id: "1" messages: - '@type': /cosmos.bank.v1beta1.MsgSend amount: - amount: "10" denom: stake from_address: cosmos1.. to_address: cosmos1.. metadata: AQ== status: PROPOSAL_STATUS_DEPOSIT_PERIOD submit_time: "2022-03-28T11:50:20.819676256Z" total_deposit: - amount: "10" denom: stake voting_end_time: null voting_start_time: null ``` ##### proposals The `proposals` command allows users to query all proposals with optional filters. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov proposals [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov proposals ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "0" proposals: - deposit_end_time: "2022-03-30T11:50:20.819676256Z" final_tally_result: abstain_count: "0" no_count: "0" no_with_veto_count: "0" yes_count: "0" id: "1" messages: - '@type': /cosmos.bank.v1beta1.MsgSend amount: - amount: "10" denom: stake from_address: cosmos1.. to_address: cosmos1.. metadata: AQ== status: PROPOSAL_STATUS_DEPOSIT_PERIOD submit_time: "2022-03-28T11:50:20.819676256Z" total_deposit: - amount: "10" denom: stake voting_end_time: null voting_start_time: null - deposit_end_time: "2022-03-30T14:02:41.165025015Z" final_tally_result: abstain_count: "0" no_count: "0" no_with_veto_count: "0" yes_count: "0" id: "2" messages: - '@type': /cosmos.bank.v1beta1.MsgSend amount: - amount: "10" denom: stake from_address: cosmos1.. to_address: cosmos1.. metadata: AQ== status: PROPOSAL_STATUS_DEPOSIT_PERIOD submit_time: "2022-03-28T14:02:41.165025015Z" total_deposit: - amount: "10" denom: stake voting_end_time: null voting_start_time: null ``` ##### proposer The `proposer` command allows users to query the proposer for a given proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov proposer [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov proposer 1 ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} proposal_id: "1" proposer: cosmos1.. ``` ##### tally The `tally` command allows users to query the tally of a given proposal vote. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov tally [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov tally 1 ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} abstain: "0" "no": "0" no_with_veto: "0" "yes": "1" ``` ##### vote The `vote` command allows users to query a vote for a given proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov vote [proposal-id] [voter-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov vote 1 cosmos1.. ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} option: VOTE_OPTION_YES options: - option: VOTE_OPTION_YES weight: "1.000000000000000000" proposal_id: "1" voter: cosmos1.. ``` ##### votes The `votes` command allows users to query all votes for a given proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov votes [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query gov votes 1 ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "0" votes: - option: VOTE_OPTION_YES options: - option: VOTE_OPTION_YES weight: "1.000000000000000000" proposal_id: "1" voter: cosmos1.. ``` #### Transactions The `tx` commands allow users to interact with the `gov` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov --help ``` ##### deposit The `deposit` command allows users to deposit tokens for a given proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov deposit [proposal-id] [deposit] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov deposit 1 10000000stake --from cosmos1.. ``` ##### draft-proposal The `draft-proposal` command allows users to draft any type of proposal. The command returns a `draft_proposal.json`, to be used by `submit-proposal` after being completed. The `draft_metadata.json` is meant to be uploaded to [IPFS](#metadata). ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov draft-proposal ``` ##### submit-proposal The `submit-proposal` command allows users to submit a governance proposal along with some messages and metadata. Messages, metadata and deposit are defined in a JSON file. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-proposal [path-to-proposal-json] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-proposal /path/to/proposal.json --from cosmos1.. ``` where `proposal.json` contains: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1...", // The gov module module address "to_address": "cosmos1...", "amount":[{ "denom": "stake", "amount": "10"}] } ], "metadata": "AQ==", "deposit": "10stake", "title": "Proposal Title", "summary": "Proposal Summary" } ``` By default the metadata, summary and title are both limited by 255 characters, this can be overridden by the application developer. When metadata is not specified, the title is limited to 255 characters and the summary 40x the title length. ##### submit-legacy-proposal The `submit-legacy-proposal` command allows users to submit a governance legacy proposal along with an initial deposit. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-legacy-proposal [command] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-legacy-proposal --title="Test Proposal" --description="testing" --type="Text" --deposit="100000000stake" --from cosmos1.. ``` Example (`param-change`): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-legacy-proposal param-change proposal.json --from cosmos1.. ``` ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "Test Proposal", "description": "testing, testing, 1, 2, 3", "changes": [ { "subspace": "staking", "key": "MaxValidators", "value": 100 } ], "deposit": "10000000stake" } ``` #### cancel-proposal Once proposal is canceled, from the deposits of proposal `deposits * proposal_cancel_ratio` will be burned or sent to `ProposalCancelDest` address , if `ProposalCancelDest` is empty then deposits will be burned. The `remaining deposits` will be sent to depositers. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov cancel-proposal [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov cancel-proposal 1 --from cosmos1... ``` ##### vote The `vote` command allows users to submit a vote for a given governance proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov vote [command] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov vote 1 yes --from cosmos1.. ``` ##### weighted-vote The `weighted-vote` command allows users to submit a weighted vote for a given governance proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov weighted-vote [proposal-id] [weighted-options] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov weighted-vote 1 yes=0.5,no=0.5 --from cosmos1.. ``` ### gRPC A user can query the `gov` module using gRPC endpoints. #### Proposal The `Proposal` endpoint allows users to query a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/Proposal ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1beta1.Query/Proposal ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposal": { "proposalId": "1", "content": {"@type":"/cosmos.gov.v1beta1.TextProposal","description":"testing, testing, 1, 2, 3","title":"Test Proposal"}, "status": "PROPOSAL_STATUS_VOTING_PERIOD", "finalTallyResult": { "yes": "0", "abstain": "0", "no": "0", "noWithVeto": "0" }, "submitTime": "2021-09-16T19:40:08.712440474Z", "depositEndTime": "2021-09-18T19:40:08.712440474Z", "totalDeposit": [ { "denom": "stake", "amount": "10000000" } ], "votingStartTime": "2021-09-16T19:40:08.712440474Z", "votingEndTime": "2021-09-18T19:40:08.712440474Z", "title": "Test Proposal", "summary": "testing, testing, 1, 2, 3" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/Proposal ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1.Query/Proposal ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposal": { "id": "1", "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} ], "status": "PROPOSAL_STATUS_VOTING_PERIOD", "finalTallyResult": { "yesCount": "0", "abstainCount": "0", "noCount": "0", "noWithVetoCount": "0" }, "submitTime": "2022-03-28T11:50:20.819676256Z", "depositEndTime": "2022-03-30T11:50:20.819676256Z", "totalDeposit": [ { "denom": "stake", "amount": "10000000" } ], "votingStartTime": "2022-03-28T14:25:26.644857113Z", "votingEndTime": "2022-03-30T14:25:26.644857113Z", "metadata": "AQ==", "title": "Test Proposal", "summary": "testing, testing, 1, 2, 3" } } ``` #### Proposals The `Proposals` endpoint allows users to query all proposals with optional filters. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/Proposals ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.gov.v1beta1.Query/Proposals ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposals": [ { "proposalId": "1", "status": "PROPOSAL_STATUS_VOTING_PERIOD", "finalTallyResult": { "yes": "0", "abstain": "0", "no": "0", "noWithVeto": "0" }, "submitTime": "2022-03-28T11:50:20.819676256Z", "depositEndTime": "2022-03-30T11:50:20.819676256Z", "totalDeposit": [ { "denom": "stake", "amount": "10000000010" } ], "votingStartTime": "2022-03-28T14:25:26.644857113Z", "votingEndTime": "2022-03-30T14:25:26.644857113Z" }, { "proposalId": "2", "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD", "finalTallyResult": { "yes": "0", "abstain": "0", "no": "0", "noWithVeto": "0" }, "submitTime": "2022-03-28T14:02:41.165025015Z", "depositEndTime": "2022-03-30T14:02:41.165025015Z", "totalDeposit": [ { "denom": "stake", "amount": "10" } ], "votingStartTime": "0001-01-01T00:00:00Z", "votingEndTime": "0001-01-01T00:00:00Z" } ], "pagination": { "total": "2" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/Proposals ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ cosmos.gov.v1.Query/Proposals ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposals": [ { "id": "1", "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} ], "status": "PROPOSAL_STATUS_VOTING_PERIOD", "finalTallyResult": { "yesCount": "0", "abstainCount": "0", "noCount": "0", "noWithVetoCount": "0" }, "submitTime": "2022-03-28T11:50:20.819676256Z", "depositEndTime": "2022-03-30T11:50:20.819676256Z", "totalDeposit": [ { "denom": "stake", "amount": "10000000010" } ], "votingStartTime": "2022-03-28T14:25:26.644857113Z", "votingEndTime": "2022-03-30T14:25:26.644857113Z", "metadata": "AQ==", "title": "Proposal Title", "summary": "Proposal Summary" }, { "id": "2", "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"10"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} ], "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD", "finalTallyResult": { "yesCount": "0", "abstainCount": "0", "noCount": "0", "noWithVetoCount": "0" }, "submitTime": "2022-03-28T14:02:41.165025015Z", "depositEndTime": "2022-03-30T14:02:41.165025015Z", "totalDeposit": [ { "denom": "stake", "amount": "10" } ], "metadata": "AQ==", "title": "Proposal Title", "summary": "Proposal Summary" } ], "pagination": { "total": "2" } } ``` #### Vote The `Vote` endpoint allows users to query a vote for a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/Vote ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1","voter":"cosmos1.."}' \ localhost:9090 \ cosmos.gov.v1beta1.Query/Vote ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vote": { "proposalId": "1", "voter": "cosmos1..", "option": "VOTE_OPTION_YES", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1000000000000000000" } ] } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/Vote ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1","voter":"cosmos1.."}' \ localhost:9090 \ cosmos.gov.v1.Query/Vote ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vote": { "proposalId": "1", "voter": "cosmos1..", "option": "VOTE_OPTION_YES", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1.000000000000000000" } ] } } ``` #### Votes The `Votes` endpoint allows users to query all votes for a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/Votes ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1beta1.Query/Votes ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposalId": "1", "voter": "cosmos1..", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1000000000000000000" } ] } ], "pagination": { "total": "1" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/Votes ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1.Query/Votes ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposalId": "1", "voter": "cosmos1..", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1.000000000000000000" } ] } ], "pagination": { "total": "1" } } ``` #### Params The `Params` endpoint allows users to query all parameters for the `gov` module. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/Params ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"params_type":"voting"}' \ localhost:9090 \ cosmos.gov.v1beta1.Query/Params ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votingParams": { "votingPeriod": "172800s" }, "depositParams": { "maxDepositPeriod": "0s" }, "tallyParams": { "quorum": "MA==", "threshold": "MA==", "vetoThreshold": "MA==" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/Params ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"params_type":"voting"}' \ localhost:9090 \ cosmos.gov.v1.Query/Params ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votingParams": { "votingPeriod": "172800s" } } ``` #### Deposit The `Deposit` endpoint allows users to query a deposit for a given proposal from a given depositor. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/Deposit ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ '{"proposal_id":"1","depositor":"cosmos1.."}' \ localhost:9090 \ cosmos.gov.v1beta1.Query/Deposit ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposit": { "proposalId": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/Deposit ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ '{"proposal_id":"1","depositor":"cosmos1.."}' \ localhost:9090 \ cosmos.gov.v1.Query/Deposit ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposit": { "proposalId": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } } ``` #### deposits The `Deposits` endpoint allows users to query all deposits for a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/Deposits ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1beta1.Query/Deposits ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposits": [ { "proposalId": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } ], "pagination": { "total": "1" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/Deposits ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1.Query/Deposits ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposits": [ { "proposalId": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } ], "pagination": { "total": "1" } } ``` #### TallyResult The `TallyResult` endpoint allows users to query the tally of a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1beta1.Query/TallyResult ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1beta1.Query/TallyResult ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "tally": { "yes": "1000000", "abstain": "0", "no": "0", "noWithVeto": "0" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.gov.v1.Query/TallyResult ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' \ localhost:9090 \ cosmos.gov.v1.Query/TallyResult ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "tally": { "yes": "1000000", "abstain": "0", "no": "0", "noWithVeto": "0" } } ``` ### REST A user can query the `gov` module using REST endpoints. #### proposal The `proposals` endpoint allows users to query a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/proposals/{proposal_id} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/proposals/1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposal": { "proposal_id": "1", "content": null, "status": "PROPOSAL_STATUS_VOTING_PERIOD", "final_tally_result": { "yes": "0", "abstain": "0", "no": "0", "no_with_veto": "0" }, "submit_time": "2022-03-28T11:50:20.819676256Z", "deposit_end_time": "2022-03-30T11:50:20.819676256Z", "total_deposit": [ { "denom": "stake", "amount": "10000000010" } ], "voting_start_time": "2022-03-28T14:25:26.644857113Z", "voting_end_time": "2022-03-30T14:25:26.644857113Z" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/proposals/{proposal_id} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/proposals/1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposal": { "id": "1", "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1..", "to_address": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10" } ] } ], "status": "PROPOSAL_STATUS_VOTING_PERIOD", "final_tally_result": { "yes_count": "0", "abstain_count": "0", "no_count": "0", "no_with_veto_count": "0" }, "submit_time": "2022-03-28T11:50:20.819676256Z", "deposit_end_time": "2022-03-30T11:50:20.819676256Z", "total_deposit": [ { "denom": "stake", "amount": "10000000" } ], "voting_start_time": "2022-03-28T14:25:26.644857113Z", "voting_end_time": "2022-03-30T14:25:26.644857113Z", "metadata": "AQ==", "title": "Proposal Title", "summary": "Proposal Summary" } } ``` #### proposals The `proposals` endpoint also allows users to query all proposals with optional filters. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/proposals ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/proposals ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposals": [ { "proposal_id": "1", "content": null, "status": "PROPOSAL_STATUS_VOTING_PERIOD", "final_tally_result": { "yes": "0", "abstain": "0", "no": "0", "no_with_veto": "0" }, "submit_time": "2022-03-28T11:50:20.819676256Z", "deposit_end_time": "2022-03-30T11:50:20.819676256Z", "total_deposit": [ { "denom": "stake", "amount": "10000000" } ], "voting_start_time": "2022-03-28T14:25:26.644857113Z", "voting_end_time": "2022-03-30T14:25:26.644857113Z" }, { "proposal_id": "2", "content": null, "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD", "final_tally_result": { "yes": "0", "abstain": "0", "no": "0", "no_with_veto": "0" }, "submit_time": "2022-03-28T14:02:41.165025015Z", "deposit_end_time": "2022-03-30T14:02:41.165025015Z", "total_deposit": [ { "denom": "stake", "amount": "10" } ], "voting_start_time": "0001-01-01T00:00:00Z", "voting_end_time": "0001-01-01T00:00:00Z" } ], "pagination": { "next_key": null, "total": "2" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/proposals ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/proposals ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposals": [ { "id": "1", "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1..", "to_address": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10" } ] } ], "status": "PROPOSAL_STATUS_VOTING_PERIOD", "final_tally_result": { "yes_count": "0", "abstain_count": "0", "no_count": "0", "no_with_veto_count": "0" }, "submit_time": "2022-03-28T11:50:20.819676256Z", "deposit_end_time": "2022-03-30T11:50:20.819676256Z", "total_deposit": [ { "denom": "stake", "amount": "10000000010" } ], "voting_start_time": "2022-03-28T14:25:26.644857113Z", "voting_end_time": "2022-03-30T14:25:26.644857113Z", "metadata": "AQ==", "title": "Proposal Title", "summary": "Proposal Summary" }, { "id": "2", "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1..", "to_address": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10" } ] } ], "status": "PROPOSAL_STATUS_DEPOSIT_PERIOD", "final_tally_result": { "yes_count": "0", "abstain_count": "0", "no_count": "0", "no_with_veto_count": "0" }, "submit_time": "2022-03-28T14:02:41.165025015Z", "deposit_end_time": "2022-03-30T14:02:41.165025015Z", "total_deposit": [ { "denom": "stake", "amount": "10" } ], "voting_start_time": null, "voting_end_time": null, "metadata": "AQ==", "title": "Proposal Title", "summary": "Proposal Summary" } ], "pagination": { "next_key": null, "total": "2" } } ``` #### voter vote The `votes` endpoint allows users to query a vote for a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vote": { "proposal_id": "1", "voter": "cosmos1..", "option": "VOTE_OPTION_YES", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1.000000000000000000" } ] } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/proposals/{proposal_id}/votes/{voter} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/proposals/1/votes/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vote": { "proposal_id": "1", "voter": "cosmos1..", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1.000000000000000000" } ], "metadata": "" } } ``` #### votes The `votes` endpoint allows users to query all votes for a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/proposals/{proposal_id}/votes ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/proposals/1/votes ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposal_id": "1", "voter": "cosmos1..", "option": "VOTE_OPTION_YES", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1.000000000000000000" } ] } ], "pagination": { "next_key": null, "total": "1" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/proposals/{proposal_id}/votes ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/proposals/1/votes ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposal_id": "1", "voter": "cosmos1..", "options": [ { "option": "VOTE_OPTION_YES", "weight": "1.000000000000000000" } ], "metadata": "" } ], "pagination": { "next_key": null, "total": "1" } } ``` #### params The `params` endpoint allows users to query all parameters for the `gov` module. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/params/{params_type} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/params/voting ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "voting_params": { "voting_period": "172800s" }, "deposit_params": { "min_deposit": [ ], "max_deposit_period": "0s" }, "tally_params": { "quorum": "0.000000000000000000", "threshold": "0.000000000000000000", "veto_threshold": "0.000000000000000000" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/params/{params_type} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/params/voting ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "voting_params": { "voting_period": "172800s" }, "deposit_params": { "min_deposit": [ ], "max_deposit_period": "0s" }, "tally_params": { "quorum": "0.000000000000000000", "threshold": "0.000000000000000000", "veto_threshold": "0.000000000000000000" } } ``` #### deposits The `deposits` endpoint allows users to query a deposit for a given proposal from a given depositor. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/proposals/{proposal_id}/deposits/{depositor} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposit": { "proposal_id": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/proposals/{proposal_id}/deposits/{depositor} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/proposals/1/deposits/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposit": { "proposal_id": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } } ``` #### proposal deposits The `deposits` endpoint allows users to query all deposits for a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/proposals/{proposal_id}/deposits ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/proposals/1/deposits ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposits": [ { "proposal_id": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } ], "pagination": { "next_key": null, "total": "1" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/proposals/{proposal_id}/deposits ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/proposals/1/deposits ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "deposits": [ { "proposal_id": "1", "depositor": "cosmos1..", "amount": [ { "denom": "stake", "amount": "10000000" } ] } ], "pagination": { "next_key": null, "total": "1" } } ``` #### tally The `tally` endpoint allows users to query the tally of a given proposal. Using legacy v1beta1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1beta1/proposals/{proposal_id}/tally ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1beta1/proposals/1/tally ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "tally": { "yes": "1000000", "abstain": "0", "no": "0", "no_with_veto": "0" } } ``` Using v1: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/gov/v1/proposals/{proposal_id}/tally ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/gov/v1/proposals/1/tally ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "tally": { "yes": "1000000", "abstain": "0", "no": "0", "no_with_veto": "0" } } ``` ## Metadata The gov module has two locations for metadata where users can provide further context about the on-chain actions they are taking. By default all metadata fields have a 255 character length field where metadata can be stored in json format, either on-chain or off-chain depending on the amount of data required. Here we provide a recommendation for the json structure and where the data should be stored. There are two important factors in making these recommendations. First, that the gov and group modules are consistent with one another, note the number of proposals made by all groups may be quite large. Second, that client applications such as block explorers and governance interfaces have confidence in the consistency of metadata structure accross chains. ### Proposal Location: off-chain as json object stored on IPFS (mirrors [group proposal](/sdk/latest/modules/group/README#metadata)) ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "", "authors": [""], "summary": "", "details": "", "proposal_forum_url": "", "vote_option_context": "", } ``` The `authors` field is an array of strings, this is to allow for multiple authors to be listed in the metadata. In v0.46, the `authors` field is a comma-separated string. Frontends are encouraged to support both formats for backwards compatibility. ### Vote Location: on-chain as json within 255 character limit (mirrors [group vote](/sdk/latest/modules/group/README#metadata)) ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "justification": "", } ``` ## Future Improvements The current documentation only describes the minimum viable product for the governance module. Future improvements may include: * **`BountyProposals`:** If accepted, a `BountyProposal` creates an open bounty. The `BountyProposal` specifies how many Atoms will be given upon completion. These Atoms will be taken from the `reserve pool`. After a `BountyProposal` is accepted by governance, anybody can submit a `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a `BountyProposal` is accepted, the corresponding funds in the `reserve pool` are locked so that payment can always be honored. In order to link a `SoftwareUpgradeProposal` to an open bounty, the submitter of the `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by governance, the funds that were reserved are automatically transferred to the submitter. * **Complex delegation:** Delegators could choose other representatives than their validators. Ultimately, the chain of representatives would always end up to a validator, but delegators could inherit the vote of their chosen representative before they inherit the vote of their validator. In other words, they would only inherit the vote of their validator if their other appointed representative did not vote. * **Better process for proposal review:** There would be two parts to `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to reward third party auditors. # x/group Source: https://docs.cosmos.network/sdk/latest/modules/group/README The following documents specify the group module. The `x/group` module is now maintained under the Cosmos Enterprise offering. If your application uses `x/group`, you will need to migrate your code to the Enterprise-distributed package and obtain a Cosmos Enterprise license to continue using it. Please see [Cosmos Enterprise](/sdk/latest/enterprise/overview) to learn more. ## Abstract The following documents specify the group module. This module allows the creation and management of on-chain multisig accounts and enables voting for message execution based on configurable decision policies. ## Contents * [Concepts](#concepts) * [Group](#group) * [Group Policy](#group-policy) * [Decision Policy](#decision-policy) * [Proposal](#proposal) * [Pruning](#pruning) * [State](#state) * [Group Table](#group-table) * [Group Member Table](#group-member-table) * [Group Policy Table](#group-policy-table) * [Proposal Table](#proposal-table) * [Vote Table](#vote-table) * [Msg Service](#msg-service) * [Msg/CreateGroup](#msgcreategroup) * [Msg/UpdateGroupMembers](#msgupdategroupmembers) * [Msg/UpdateGroupAdmin](#msgupdategroupadmin) * [Msg/UpdateGroupMetadata](#msgupdategroupmetadata) * [Msg/CreateGroupPolicy](#msgcreategrouppolicy) * [Msg/CreateGroupWithPolicy](#msgcreategroupwithpolicy) * [Msg/UpdateGroupPolicyAdmin](#msgupdategrouppolicyadmin) * [Msg/UpdateGroupPolicyDecisionPolicy](#msgupdategrouppolicydecisionpolicy) * [Msg/UpdateGroupPolicyMetadata](#msgupdategrouppolicymetadata) * [Msg/SubmitProposal](#msgsubmitproposal) * [Msg/WithdrawProposal](#msgwithdrawproposal) * [Msg/Vote](#msgvote) * [Msg/Exec](#msgexec) * [Msg/LeaveGroup](#msgleavegroup) * [Events](#events) * [EventCreateGroup](#eventcreategroup) * [EventUpdateGroup](#eventupdategroup) * [EventCreateGroupPolicy](#eventcreategrouppolicy) * [EventUpdateGroupPolicy](#eventupdategrouppolicy) * [EventCreateProposal](#eventcreateproposal) * [EventWithdrawProposal](#eventwithdrawproposal) * [EventVote](#eventvote) * [EventExec](#eventexec) * [EventLeaveGroup](#eventleavegroup) * [EventProposalPruned](#eventproposalpruned) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) * [REST](#rest) * [Metadata](#metadata) ## Concepts ### Group A group is simply an aggregation of accounts with associated weights. It is not an account and doesn't have a balance. It doesn't in and of itself have any sort of voting or decision weight. It does have an "administrator" which has the ability to add, remove and update members in the group. Note that a group policy account could be an administrator of a group, and that the administrator doesn't necessarily have to be a member of the group. ### Group Policy A group policy is an account associated with a group and a decision policy. Group policies are abstracted from groups because a single group may have multiple decision policies for different types of actions. Managing group membership separately from decision policies results in the least overhead and keeps membership consistent across different policies. The pattern that is recommended is to have a single master group policy for a given group, and then to create separate group policies with different decision policies and delegate the desired permissions from the master account to those "sub-accounts" using the `x/authz` module. ### Decision Policy A decision policy is the mechanism by which members of a group can vote on proposals, as well as the rules that dictate whether a proposal should pass or not based on its tally outcome. All decision policies generally would have a mininum execution period and a maximum voting window. The minimum execution period is the minimum amount of time that must pass after submission in order for a proposal to potentially be executed, and it may be set to 0. The maximum voting window is the maximum time after submission that a proposal may be voted on before it is tallied. The chain developer also defines an app-wide maximum execution period, which is the maximum amount of time after a proposal's voting period end where users are allowed to execute a proposal. The current group module comes shipped with two decision policies: threshold and percentage. Any chain developer can extend upon these two, by creating custom decision policies, as long as they adhere to the `DecisionPolicy` interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/x/group/types.go#L44-L62 ``` #### Threshold decision policy A threshold decision policy defines a threshold of yes votes (based on a tally of voter weights) that must be achieved in order for a proposal to pass. For this decision policy, abstain and veto are simply treated as no's. This decision policy also has a VotingPeriod window and a MinExecutionPeriod window. The former defines the duration after proposal submission where members are allowed to vote, after which tallying is performed. The latter specifies the minimum duration after proposal submission where the proposal can be executed. If set to 0, then the proposal is allowed to be executed immediately on submission (using the `TRY_EXEC` option). Obviously, MinExecutionPeriod cannot be greater than VotingPeriod+MaxExecutionPeriod (where MaxExecution is the app-defined duration that specifies the window after voting ended where a proposal can be executed). #### Percentage decision policy A percentage decision policy is similar to a threshold decision policy, except that the threshold is not defined as a constant weight, but as a percentage. It's more suited for groups where the group members' weights can be updated, as the percentage threshold stays the same, and doesn't depend on how those member weights get updated. Same as the Threshold decision policy, the percentage decision policy has the two VotingPeriod and MinExecutionPeriod parameters. ### Proposal Any member(s) of a group can submit a proposal for a group policy account to decide upon. A proposal consists of a set of messages that will be executed if the proposal passes as well as any metadata associated with the proposal. #### Voting There are four choices to choose while voting - yes, no, abstain and veto. Not all decision policies will take the four choices into account. Votes can contain some optional metadata. In the current implementation, the voting window begins as soon as a proposal is submitted, and the end is defined by the group policy's decision policy. #### Withdrawing Proposals Proposals can be withdrawn any time before the voting period end, either by the admin of the group policy or by one of the proposers. Once withdrawn, it is marked as `PROPOSAL_STATUS_WITHDRAWN`, and no more voting or execution is allowed on it. #### Aborted Proposals If the group policy is updated during the voting period of the proposal, then the proposal is marked as `PROPOSAL_STATUS_ABORTED`, and no more voting or execution is allowed on it. This is because the group policy defines the rules of proposal voting and execution, so if those rules change during the lifecycle of a proposal, then the proposal should be marked as stale. #### Tallying Tallying is the counting of all votes on a proposal. It happens only once in the lifecycle of a proposal, but can be triggered by two factors, whichever happens first: * either someone tries to execute the proposal (see next section), which can happen on a `Msg/Exec` transaction, or a `Msg/{SubmitProposal,Vote}` transaction with the `Exec` field set. When a proposal execution is attempted, a tally is done first to make sure the proposal passes. * or on `EndBlock` when the proposal's voting period end just passed. If the tally result passes the decision policy's rules, then the proposal is marked as `PROPOSAL_STATUS_ACCEPTED`, or else it is marked as `PROPOSAL_STATUS_REJECTED`. In any case, no more voting is allowed anymore, and the tally result is persisted to state in the proposal's `FinalTallyResult`. #### Executing Proposals Proposals are executed only when the tallying is done, and the group account's decision policy allows the proposal to pass based on the tally outcome. They are marked by the status `PROPOSAL_STATUS_ACCEPTED`. Execution must happen before a duration of `MaxExecutionPeriod` (set by the chain developer) after each proposal's voting period end. Proposals will not be automatically executed by the chain in this current design, but rather a user must submit a `Msg/Exec` transaction to attempt to execute the proposal based on the current votes and decision policy. Any user (not only the group members) can execute proposals that have been accepted, and execution fees are paid by the proposal executor. It's also possible to try to execute a proposal immediately on creation or on new votes using the `Exec` field of `Msg/SubmitProposal` and `Msg/Vote` requests. In the former case, proposers signatures are considered as yes votes. In these cases, if the proposal can't be executed (i.e. it didn't pass the decision policy's rules), it will still be opened for new votes and could be tallied and executed later on. A successful proposal execution will have its `ExecutorResult` marked as `PROPOSAL_EXECUTOR_RESULT_SUCCESS`. The proposal will be automatically pruned after execution. On the other hand, a failed proposal execution will be marked as `PROPOSAL_EXECUTOR_RESULT_FAILURE`. Such a proposal can be re-executed multiple times, until it expires after `MaxExecutionPeriod` after voting period end. ### Pruning Proposals and votes are automatically pruned to avoid state bloat. Votes are pruned: * either after a successful tally, i.e. a tally whose result passes the decision policy's rules, which can be trigged by a `Msg/Exec` or a `Msg/{SubmitProposal,Vote}` with the `Exec` field set, * or on `EndBlock` right after the proposal's voting period end. This applies to proposals with status `aborted` or `withdrawn` too. whichever happens first. Proposals are pruned: * on `EndBlock` whose proposal status is `withdrawn` or `aborted` on proposal's voting period end before tallying, * and either after a successful proposal execution, * or on `EndBlock` right after the proposal's `voting_period_end` + `max_execution_period` (defined as an app-wide configuration) is passed, whichever happens first. ## State The `group` module uses the `orm` package which provides table storage with support for primary keys and secondary indexes. `orm` also defines `Sequence` which is a persistent unique key generator based on a counter that can be used along with `Table`s. Here's the list of tables and associated sequences and indexes stored as part of the `group` module. ### Group Table The `groupTable` stores `GroupInfo`: `0x0 | BigEndian(GroupId) -> ProtocolBuffer(GroupInfo)`. #### groupSeq The value of `groupSeq` is incremented when creating a new group and corresponds to the new `GroupId`: `0x1 | 0x1 -> BigEndian`. The second `0x1` corresponds to the ORM `sequenceStorageKey`. #### groupByAdminIndex `groupByAdminIndex` allows to retrieve groups by admin address: `0x2 | len([]byte(group.Admin)) | []byte(group.Admin) | BigEndian(GroupId) -> []byte()`. ### Group Member Table The `groupMemberTable` stores `GroupMember`s: `0x10 | BigEndian(GroupId) | []byte(member.Address) -> ProtocolBuffer(GroupMember)`. The `groupMemberTable` is a primary key table and its `PrimaryKey` is given by `BigEndian(GroupId) | []byte(member.Address)` which is used by the following indexes. #### groupMemberByGroupIndex `groupMemberByGroupIndex` allows to retrieve group members by group id: `0x11 | BigEndian(GroupId) | PrimaryKey -> []byte()`. #### groupMemberByMemberIndex `groupMemberByMemberIndex` allows to retrieve group members by member address: `0x12 | len([]byte(member.Address)) | []byte(member.Address) | PrimaryKey -> []byte()`. ### Group Policy Table The `groupPolicyTable` stores `GroupPolicyInfo`: `0x20 | len([]byte(Address)) | []byte(Address) -> ProtocolBuffer(GroupPolicyInfo)`. The `groupPolicyTable` is a primary key table and its `PrimaryKey` is given by `len([]byte(Address)) | []byte(Address)` which is used by the following indexes. #### groupPolicySeq The value of `groupPolicySeq` is incremented when creating a new group policy and is used to generate the new group policy account `Address`: `0x21 | 0x1 -> BigEndian`. The second `0x1` corresponds to the ORM `sequenceStorageKey`. #### groupPolicyByGroupIndex `groupPolicyByGroupIndex` allows to retrieve group policies by group id: `0x22 | BigEndian(GroupId) | PrimaryKey -> []byte()`. #### groupPolicyByAdminIndex `groupPolicyByAdminIndex` allows to retrieve group policies by admin address: `0x23 | len([]byte(Address)) | []byte(Address) | PrimaryKey -> []byte()`. ### Proposal Table The `proposalTable` stores `Proposal`s: `0x30 | BigEndian(ProposalId) -> ProtocolBuffer(Proposal)`. #### proposalSeq The value of `proposalSeq` is incremented when creating a new proposal and corresponds to the new `ProposalId`: `0x31 | 0x1 -> BigEndian`. The second `0x1` corresponds to the ORM `sequenceStorageKey`. #### proposalByGroupPolicyIndex `proposalByGroupPolicyIndex` allows to retrieve proposals by group policy account address: `0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`. #### ProposalsByVotingPeriodEndIndex `proposalsByVotingPeriodEndIndex` allows to retrieve proposals sorted by chronological `voting_period_end`: `0x33 | sdk.FormatTimeBytes(proposal.VotingPeriodEnd) | BigEndian(ProposalId) -> []byte()`. This index is used when tallying the proposal votes at the end of the voting period, and for pruning proposals at `VotingPeriodEnd + MaxExecutionPeriod`. ### Vote Table The `voteTable` stores `Vote`s: `0x40 | BigEndian(ProposalId) | []byte(voter.Address) -> ProtocolBuffer(Vote)`. The `voteTable` is a primary key table and its `PrimaryKey` is given by `BigEndian(ProposalId) | []byte(voter.Address)` which is used by the following indexes. #### voteByProposalIndex `voteByProposalIndex` allows to retrieve votes by proposal id: `0x41 | BigEndian(ProposalId) | PrimaryKey -> []byte()`. #### voteByVoterIndex `voteByVoterIndex` allows to retrieve votes by voter address: `0x42 | len([]byte(voter.Address)) | []byte(voter.Address) | PrimaryKey -> []byte()`. ## Msg Service ### Msg/CreateGroup A new group can be created with the `MsgCreateGroup`, which has an admin address, a list of members and some optional metadata. The metadata has a maximum length that is chosen by the app developer, and passed into the group keeper as a config. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L80-L93 ``` It's expected to fail if * metadata length is greater than `MaxMetadataLen` config * members are not correctly set (e.g. wrong address format, duplicates, or with 0 weight). ### Msg/UpdateGroupMembers Group members can be updated with the `UpdateGroupMembers`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L101-L115 ``` In the list of `MemberUpdates`, an existing member can be removed by setting its weight to 0. It's expected to fail if: * the signer is not the admin of the group. * for any one of the associated group policies, if its decision policy's `Validate()` method fails against the updated group. ### Msg/UpdateGroupAdmin The `UpdateGroupAdmin` can be used to update a group admin. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L120-L133 ``` It's expected to fail if the signer is not the admin of the group. ### Msg/UpdateGroupMetadata The `UpdateGroupMetadata` can be used to update a group metadata. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L138-L151 ``` It's expected to fail if: * new metadata length is greater than `MaxMetadataLen` config. * the signer is not the admin of the group. ### Msg/CreateGroupPolicy A new group policy can be created with the `MsgCreateGroupPolicy`, which has an admin address, a group id, a decision policy and some optional metadata. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L160-L178 ``` It's expected to fail if: * the signer is not the admin of the group. * metadata length is greater than `MaxMetadataLen` config. * the decision policy's `Validate()` method doesn't pass against the group. ### Msg/CreateGroupWithPolicy A new group with policy can be created with the `MsgCreateGroupWithPolicy`, which has an admin address, a list of members, a decision policy, a `group_policy_as_admin` field to optionally set group and group policy admin with group policy address and some optional metadata for group and group policy. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L204-L228 ``` It's expected to fail for the same reasons as `Msg/CreateGroup` and `Msg/CreateGroupPolicy`. ### Msg/UpdateGroupPolicyAdmin The `UpdateGroupPolicyAdmin` can be used to update a group policy admin. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L186-L199 ``` It's expected to fail if the signer is not the admin of the group policy. ### Msg/UpdateGroupPolicyDecisionPolicy The `UpdateGroupPolicyDecisionPolicy` can be used to update a decision policy. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L239-L254 ``` It's expected to fail if: * the signer is not the admin of the group policy. * the new decision policy's `Validate()` method doesn't pass against the group. ### Msg/UpdateGroupPolicyMetadata The `UpdateGroupPolicyMetadata` can be used to update a group policy metadata. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L259-L272 ``` It's expected to fail if: * new metadata length is greater than `MaxMetadataLen` config. * the signer is not the admin of the group. ### Msg/SubmitProposal A new proposal can be created with the `MsgSubmitProposal`, which has a group policy account address, a list of proposers addresses, a list of messages to execute if the proposal is accepted and some optional metadata. An optional `Exec` value can be provided to try to execute the proposal immediately after proposal creation. Proposers signatures are considered as yes votes in this case. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L294-L324 ``` It's expected to fail if: * metadata, title, or summary length is greater than `MaxMetadataLen` config. * if any of the proposers is not a group member. ### Msg/WithdrawProposal A proposal can be withdrawn using `MsgWithdrawProposal` which has an `address` (can be either a proposer or the group policy admin) and a `proposal_id` (which has to be withdrawn). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L332-L342 ``` It's expected to fail if: * the signer is neither the group policy admin nor proposer of the proposal. * the proposal is already closed or aborted. ### Msg/Vote A new vote can be created with the `MsgVote`, given a proposal id, a voter address, a choice (yes, no, veto or abstain) and some optional metadata. An optional `Exec` value can be provided to try to execute the proposal immediately after voting. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L347-L367 ``` It's expected to fail if: * metadata length is greater than `MaxMetadataLen` config. * the proposal is not in voting period anymore. ### Msg/Exec A proposal can be executed with the `MsgExec`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L372-L382 ``` The messages that are part of this proposal won't be executed if: * the proposal has not been accepted by the group policy. * the proposal has already been successfully executed. ### Msg/LeaveGroup The `MsgLeaveGroup` allows group member to leave a group. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/proto/cosmos/group/v1/tx.proto#L390-L400 ``` It's expected to fail if: * the group member is not part of the group. * for any one of the associated group policies, if its decision policy's `Validate()` method fails against the updated group. ## Events The group module emits the following events: ### EventCreateGroup | Type | Attribute Key | Attribute Value | | -------------------------------- | ------------- | -------------------------------- | | message | action | /cosmos.group.v1.Msg/CreateGroup | | cosmos.group.v1.EventCreateGroup | group\_id | `{groupId}` | ### EventUpdateGroup | Type | Attribute Key | Attribute Value | | -------------------------------- | ------------- | ------------------------------------------------------------ | | message | action | `/cosmos.group.v1.Msg/UpdateGroup{Admin\|Metadata\|Members}` | | cosmos.group.v1.EventUpdateGroup | group\_id | `{groupId}` | ### EventCreateGroupPolicy | Type | Attribute Key | Attribute Value | | -------------------------------------- | ------------- | -------------------------------------- | | message | action | /cosmos.group.v1.Msg/CreateGroupPolicy | | cosmos.group.v1.EventCreateGroupPolicy | address | `{groupPolicyAddress}` | ### EventUpdateGroupPolicy | Type | Attribute Key | Attribute Value | | -------------------------------------- | ------------- | ------------------------------------------------------------------------- | | message | action | `/cosmos.group.v1.Msg/UpdateGroupPolicy{Admin\|Metadata\|DecisionPolicy}` | | cosmos.group.v1.EventUpdateGroupPolicy | address | `{groupPolicyAddress}` | ### EventCreateProposal | Type | Attribute Key | Attribute Value | | ----------------------------------- | ------------- | ----------------------------------- | | message | action | /cosmos.group.v1.Msg/CreateProposal | | cosmos.group.v1.EventCreateProposal | proposal\_id | `{proposalId}` | ### EventWithdrawProposal | Type | Attribute Key | Attribute Value | | ------------------------------------- | ------------- | ------------------------------------- | | message | action | /cosmos.group.v1.Msg/WithdrawProposal | | cosmos.group.v1.EventWithdrawProposal | proposal\_id | `{proposalId}` | ### EventVote | Type | Attribute Key | Attribute Value | | ------------------------- | ------------- | ------------------------- | | message | action | /cosmos.group.v1.Msg/Vote | | cosmos.group.v1.EventVote | proposal\_id | `{proposalId}` | ## EventExec | Type | Attribute Key | Attribute Value | | ------------------------- | ------------- | ------------------------- | | message | action | /cosmos.group.v1.Msg/Exec | | cosmos.group.v1.EventExec | proposal\_id | `{proposalId}` | | cosmos.group.v1.EventExec | logs | `{logs\_string}` | ### EventLeaveGroup | Type | Attribute Key | Attribute Value | | ------------------------------- | ------------- | ------------------------------- | | message | action | /cosmos.group.v1.Msg/LeaveGroup | | cosmos.group.v1.EventLeaveGroup | proposal\_id | `{proposalId}` | | cosmos.group.v1.EventLeaveGroup | address | `{address}` | ### EventProposalPruned | Type | Attribute Key | Attribute Value | | ----------------------------------- | ------------- | ------------------------------- | | message | action | /cosmos.group.v1.Msg/LeaveGroup | | cosmos.group.v1.EventProposalPruned | proposal\_id | `{proposalId}` | | cosmos.group.v1.EventProposalPruned | status | `{ProposalStatus}` | | cosmos.group.v1.EventProposalPruned | tally\_result | `{TallyResult}` | ## Client ### CLI A user can query and interact with the `group` module using the CLI. #### Query The `query` commands allow users to query `group` state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group --help ``` ##### group-info The `group-info` command allows users to query for group info by given group id. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-info [id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-info 1 ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} admin: cosmos1.. group_id: "1" metadata: AQ== total_weight: "3" version: "1" ``` ##### group-policy-info The `group-policy-info` command allows users to query for group policy info by account address of group policy . ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-policy-info [group-policy-account] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-policy-info cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} address: cosmos1.. admin: cosmos1.. decision_policy: '@type': /cosmos.group.v1.ThresholdDecisionPolicy threshold: "1" windows: min_execution_period: 0s voting_period: 432000s group_id: "1" metadata: AQ== version: "1" ``` ##### group-members The `group-members` command allows users to query for group members by group id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-members [id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-members 1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} members: - group_id: "1" member: address: cosmos1.. metadata: AQ== weight: "2" - group_id: "1" member: address: cosmos1.. metadata: AQ== weight: "1" pagination: next_key: null total: "2" ``` ##### groups-by-admin The `groups-by-admin` command allows users to query for groups by admin account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group groups-by-admin [admin] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group groups-by-admin cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} groups: - admin: cosmos1.. group_id: "1" metadata: AQ== total_weight: "3" version: "1" - admin: cosmos1.. group_id: "2" metadata: AQ== total_weight: "3" version: "1" pagination: next_key: null total: "2" ``` ##### group-policies-by-group The `group-policies-by-group` command allows users to query for group policies by group id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-policies-by-group [group-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-policies-by-group 1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} group_policies: - address: cosmos1.. admin: cosmos1.. decision_policy: '@type': /cosmos.group.v1.ThresholdDecisionPolicy threshold: "1" windows: min_execution_period: 0s voting_period: 432000s group_id: "1" metadata: AQ== version: "1" - address: cosmos1.. admin: cosmos1.. decision_policy: '@type': /cosmos.group.v1.ThresholdDecisionPolicy threshold: "1" windows: min_execution_period: 0s voting_period: 432000s group_id: "1" metadata: AQ== version: "1" pagination: next_key: null total: "2" ``` ##### group-policies-by-admin The `group-policies-by-admin` command allows users to query for group policies by admin account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-policies-by-admin [admin] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group group-policies-by-admin cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} group_policies: - address: cosmos1.. admin: cosmos1.. decision_policy: '@type': /cosmos.group.v1.ThresholdDecisionPolicy threshold: "1" windows: min_execution_period: 0s voting_period: 432000s group_id: "1" metadata: AQ== version: "1" - address: cosmos1.. admin: cosmos1.. decision_policy: '@type': /cosmos.group.v1.ThresholdDecisionPolicy threshold: "1" windows: min_execution_period: 0s voting_period: 432000s group_id: "1" metadata: AQ== version: "1" pagination: next_key: null total: "2" ``` ##### proposal The `proposal` command allows users to query for proposal by id. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group proposal [id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group proposal 1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} proposal: address: cosmos1.. executor_result: EXECUTOR_RESULT_NOT_RUN group_policy_version: "1" group_version: "1" metadata: AQ== msgs: - '@type': /cosmos.bank.v1beta1.MsgSend amount: - amount: "100000000" denom: stake from_address: cosmos1.. to_address: cosmos1.. proposal_id: "1" proposers: - cosmos1.. result: RESULT_UNFINALIZED status: STATUS_SUBMITTED submitted_at: "2021-12-17T07:06:26.310638964Z" windows: min_execution_period: 0s voting_period: 432000s vote_state: abstain_count: "0" no_count: "0" veto_count: "0" yes_count: "0" summary: "Summary" title: "Title" ``` ##### proposals-by-group-policy The `proposals-by-group-policy` command allows users to query for proposals by account address of group policy with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group proposals-by-group-policy [group-policy-account] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group proposals-by-group-policy cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "1" proposals: - address: cosmos1.. executor_result: EXECUTOR_RESULT_NOT_RUN group_policy_version: "1" group_version: "1" metadata: AQ== msgs: - '@type': /cosmos.bank.v1beta1.MsgSend amount: - amount: "100000000" denom: stake from_address: cosmos1.. to_address: cosmos1.. proposal_id: "1" proposers: - cosmos1.. result: RESULT_UNFINALIZED status: STATUS_SUBMITTED submitted_at: "2021-12-17T07:06:26.310638964Z" windows: min_execution_period: 0s voting_period: 432000s vote_state: abstain_count: "0" no_count: "0" veto_count: "0" yes_count: "0" summary: "Summary" title: "Title" ``` ##### vote The `vote` command allows users to query for vote by proposal id and voter account address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group vote [proposal-id] [voter] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group vote 1 cosmos1.. ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} vote: choice: CHOICE_YES metadata: AQ== proposal_id: "1" submitted_at: "2021-12-17T08:05:02.490164009Z" voter: cosmos1.. ``` ##### votes-by-proposal The `votes-by-proposal` command allows users to query for votes by proposal id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group votes-by-proposal [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group votes-by-proposal 1 ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "1" votes: - choice: CHOICE_YES metadata: AQ== proposal_id: "1" submitted_at: "2021-12-17T08:05:02.490164009Z" voter: cosmos1.. ``` ##### votes-by-voter The `votes-by-voter` command allows users to query for votes by voter account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group votes-by-voter [voter] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query group votes-by-voter cosmos1.. ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "1" votes: - choice: CHOICE_YES metadata: AQ== proposal_id: "1" submitted_at: "2021-12-17T08:05:02.490164009Z" voter: cosmos1.. ``` ### Transactions The `tx` commands allow users to interact with the `group` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group --help ``` #### create-group The `create-group` command allows users to create a group which is an aggregation of member accounts with associated weights and an administrator account. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group [admin] [metadata] [members-json-file] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group cosmos1.. "AQ==" members.json ``` #### update-group-admin The `update-group-admin` command allows users to update a group's admin. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-admin [admin] [group-id] [new-admin] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-admin cosmos1.. 1 cosmos1.. ``` #### update-group-members The `update-group-members` command allows users to update a group's members. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-members [admin] [group-id] [members-json-file] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-members cosmos1.. 1 members.json ``` #### update-group-metadata The `update-group-metadata` command allows users to update a group's metadata. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-metadata [admin] [group-id] [metadata] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-metadata cosmos1.. 1 "AQ==" ``` #### create-group-policy The `create-group-policy` command allows users to create a group policy which is an account associated with a group and a decision policy. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group-policy [admin] [group-id] [metadata] [decision-policy] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group-policy cosmos1.. 1 "AQ==" '{"@type":"/cosmos.group.v1.ThresholdDecisionPolicy", "threshold":"1", "windows": {"voting_period": "120h", "min_execution_period": "0s"}}' ``` #### create-group-with-policy The `create-group-with-policy` command allows users to create a group which is an aggregation of member accounts with associated weights and an administrator account with decision policy. If the `--group-policy-as-admin` flag is set to `true`, the group policy address becomes the group and group policy admin. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group-with-policy [admin] [group-metadata] [group-policy-metadata] [members-json-file] [decision-policy] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group-with-policy cosmos1.. "AQ==" "AQ==" members.json '{"@type":"/cosmos.group.v1.ThresholdDecisionPolicy", "threshold":"1", "windows": {"voting_period": "120h", "min_execution_period": "0s"}}' ``` #### update-group-policy-admin The `update-group-policy-admin` command allows users to update a group policy admin. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-admin [admin] [group-policy-account] [new-admin] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-admin cosmos1.. cosmos1.. cosmos1.. ``` #### update-group-policy-metadata The `update-group-policy-metadata` command allows users to update a group policy metadata. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-metadata [admin] [group-policy-account] [new-metadata] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-metadata cosmos1.. cosmos1.. "AQ==" ``` #### update-group-policy-decision-policy The `update-group-policy-decision-policy` command allows users to update a group policy's decision policy. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-decision-policy [admin] [group-policy-account] [decision-policy] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-decision-policy cosmos1.. cosmos1.. '{"@type":"/cosmos.group.v1.ThresholdDecisionPolicy", "threshold":"2", "windows": {"voting_period": "120h", "min_execution_period": "0s"}}' ``` #### submit-proposal The `submit-proposal` command allows users to submit a new proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group submit-proposal [group-policy-account] [proposer[,proposer]*] [msg_tx_json_file] [metadata] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group submit-proposal cosmos1.. cosmos1.. msg_tx.json "AQ==" ``` #### withdraw-proposal The `withdraw-proposal` command allows users to withdraw a proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group withdraw-proposal [proposal-id] [group-policy-admin-or-proposer] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group withdraw-proposal 1 cosmos1.. ``` #### vote The `vote` command allows users to vote on a proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group vote proposal-id] [voter] [choice] [metadata] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group vote 1 cosmos1.. CHOICE_YES "AQ==" ``` #### exec The `exec` command allows users to execute a proposal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group exec [proposal-id] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group exec 1 ``` #### leave-group The `leave-group` command allows group member to leave the group. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group leave-group [member-address] [group-id] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group leave-group cosmos1... 1 ``` ### gRPC A user can query the `group` module using gRPC endpoints. #### GroupInfo The `GroupInfo` endpoint allows users to query for group info by given group id. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/GroupInfo ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"group_id":1}' localhost:9090 cosmos.group.v1.Query/GroupInfo ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "info": { "groupId": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "totalWeight": "3" } } ``` #### GroupPolicyInfo The `GroupPolicyInfo` endpoint allows users to query for group policy info by account address of group policy. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/GroupPolicyInfo ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address":"cosmos1.."}' localhost:9090 cosmos.group.v1.Query/GroupPolicyInfo ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "info": { "address": "cosmos1..", "groupId": "1", "admin": "cosmos1..", "version": "1", "decisionPolicy": {"@type":"/cosmos.group.v1.ThresholdDecisionPolicy","threshold":"1","windows": {"voting_period": "120h", "min_execution_period": "0s"}}, } } ``` #### GroupMembers The `GroupMembers` endpoint allows users to query for group members by group id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/GroupMembers ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"group_id":"1"}' localhost:9090 cosmos.group.v1.Query/GroupMembers ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "members": [ { "groupId": "1", "member": { "address": "cosmos1..", "weight": "1" } }, { "groupId": "1", "member": { "address": "cosmos1..", "weight": "2" } } ], "pagination": { "total": "2" } } ``` #### GroupsByAdmin The `GroupsByAdmin` endpoint allows users to query for groups by admin account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/GroupsByAdmin ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"admin":"cosmos1.."}' localhost:9090 cosmos.group.v1.Query/GroupsByAdmin ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "groups": [ { "groupId": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "totalWeight": "3" }, { "groupId": "2", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "totalWeight": "3" } ], "pagination": { "total": "2" } } ``` #### GroupPoliciesByGroup The `GroupPoliciesByGroup` endpoint allows users to query for group policies by group id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/GroupPoliciesByGroup ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"group_id":"1"}' localhost:9090 cosmos.group.v1.Query/GroupPoliciesByGroup ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "GroupPolicies": [ { "address": "cosmos1..", "groupId": "1", "admin": "cosmos1..", "version": "1", "decisionPolicy": {"@type":"/cosmos.group.v1.ThresholdDecisionPolicy","threshold":"1","windows":{"voting_period": "120h", "min_execution_period": "0s"}}, }, { "address": "cosmos1..", "groupId": "1", "admin": "cosmos1..", "version": "1", "decisionPolicy": {"@type":"/cosmos.group.v1.ThresholdDecisionPolicy","threshold":"1","windows":{"voting_period": "120h", "min_execution_period": "0s"}}, } ], "pagination": { "total": "2" } } ``` #### GroupPoliciesByAdmin The `GroupPoliciesByAdmin` endpoint allows users to query for group policies by admin account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/GroupPoliciesByAdmin ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"admin":"cosmos1.."}' localhost:9090 cosmos.group.v1.Query/GroupPoliciesByAdmin ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "GroupPolicies": [ { "address": "cosmos1..", "groupId": "1", "admin": "cosmos1..", "version": "1", "decisionPolicy": {"@type":"/cosmos.group.v1.ThresholdDecisionPolicy","threshold":"1","windows":{"voting_period": "120h", "min_execution_period": "0s"}}, }, { "address": "cosmos1..", "groupId": "1", "admin": "cosmos1..", "version": "1", "decisionPolicy": {"@type":"/cosmos.group.v1.ThresholdDecisionPolicy","threshold":"1","windows":{"voting_period": "120h", "min_execution_period": "0s"}}, } ], "pagination": { "total": "2" } } ``` #### Proposal The `Proposal` endpoint allows users to query for proposal by id. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/Proposal ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' localhost:9090 cosmos.group.v1.Query/Proposal ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposal": { "proposalId": "1", "address": "cosmos1..", "proposers": [ "cosmos1.." ], "submittedAt": "2021-12-17T07:06:26.310638964Z", "groupVersion": "1", "GroupPolicyVersion": "1", "status": "STATUS_SUBMITTED", "result": "RESULT_UNFINALIZED", "voteState": { "yesCount": "0", "noCount": "0", "abstainCount": "0", "vetoCount": "0" }, "windows": { "min_execution_period": "0s", "voting_period": "432000s" }, "executorResult": "EXECUTOR_RESULT_NOT_RUN", "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"100000000"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} ], "title": "Title", "summary": "Summary", } } ``` #### ProposalsByGroupPolicy The `ProposalsByGroupPolicy` endpoint allows users to query for proposals by account address of group policy with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/ProposalsByGroupPolicy ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address":"cosmos1.."}' localhost:9090 cosmos.group.v1.Query/ProposalsByGroupPolicy ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposals": [ { "proposalId": "1", "address": "cosmos1..", "proposers": [ "cosmos1.." ], "submittedAt": "2021-12-17T08:03:27.099649352Z", "groupVersion": "1", "GroupPolicyVersion": "1", "status": "STATUS_CLOSED", "result": "RESULT_ACCEPTED", "voteState": { "yesCount": "1", "noCount": "0", "abstainCount": "0", "vetoCount": "0" }, "windows": { "min_execution_period": "0s", "voting_period": "432000s" }, "executorResult": "EXECUTOR_RESULT_NOT_RUN", "messages": [ {"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"stake","amount":"100000000"}],"fromAddress":"cosmos1..","toAddress":"cosmos1.."} ], "title": "Title", "summary": "Summary", } ], "pagination": { "total": "1" } } ``` #### VoteByProposalVoter The `VoteByProposalVoter` endpoint allows users to query for vote by proposal id and voter account address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/VoteByProposalVoter ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1","voter":"cosmos1.."}' localhost:9090 cosmos.group.v1.Query/VoteByProposalVoter ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vote": { "proposalId": "1", "voter": "cosmos1..", "choice": "CHOICE_YES", "submittedAt": "2021-12-17T08:05:02.490164009Z" } } ``` #### VotesByProposal The `VotesByProposal` endpoint allows users to query for votes by proposal id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/VotesByProposal ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"proposal_id":"1"}' localhost:9090 cosmos.group.v1.Query/VotesByProposal ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposalId": "1", "voter": "cosmos1..", "choice": "CHOICE_YES", "submittedAt": "2021-12-17T08:05:02.490164009Z" } ], "pagination": { "total": "1" } } ``` #### VotesByVoter The `VotesByVoter` endpoint allows users to query for votes by voter account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.group.v1.Query/VotesByVoter ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"voter":"cosmos1.."}' localhost:9090 cosmos.group.v1.Query/VotesByVoter ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposalId": "1", "voter": "cosmos1..", "choice": "CHOICE_YES", "submittedAt": "2021-12-17T08:05:02.490164009Z" } ], "pagination": { "total": "1" } } ``` ### REST A user can query the `group` module using REST endpoints. #### GroupInfo The `GroupInfo` endpoint allows users to query for group info by given group id. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/group_info/{group_id} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/group_info/1 ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "info": { "id": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "total_weight": "3" } } ``` #### GroupPolicyInfo The `GroupPolicyInfo` endpoint allows users to query for group policy info by account address of group policy. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/group_policy_info/{address} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/group_policy_info/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "info": { "address": "cosmos1..", "group_id": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "decision_policy": { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "1", "windows": { "voting_period": "120h", "min_execution_period": "0s" } }, } } ``` #### GroupMembers The `GroupMembers` endpoint allows users to query for group members by group id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/group_members/{group_id} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/group_members/1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "members": [ { "group_id": "1", "member": { "address": "cosmos1..", "weight": "1", "metadata": "AQ==" } }, { "group_id": "1", "member": { "address": "cosmos1..", "weight": "2", "metadata": "AQ==" } ], "pagination": { "next_key": null, "total": "2" } } ``` #### GroupsByAdmin The `GroupsByAdmin` endpoint allows users to query for groups by admin account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/groups_by_admin/{admin} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/groups_by_admin/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "groups": [ { "id": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "total_weight": "3" }, { "id": "2", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "total_weight": "3" } ], "pagination": { "next_key": null, "total": "2" } } ``` #### GroupPoliciesByGroup The `GroupPoliciesByGroup` endpoint allows users to query for group policies by group id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/group_policies_by_group/{group_id} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/group_policies_by_group/1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "group_policies": [ { "address": "cosmos1..", "group_id": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "decision_policy": { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "1", "windows": { "voting_period": "120h", "min_execution_period": "0s" } }, }, { "address": "cosmos1..", "group_id": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "decision_policy": { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "1", "windows": { "voting_period": "120h", "min_execution_period": "0s" } }, } ], "pagination": { "next_key": null, "total": "2" } } ``` #### GroupPoliciesByAdmin The `GroupPoliciesByAdmin` endpoint allows users to query for group policies by admin account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/group_policies_by_admin/{admin} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/group_policies_by_admin/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "group_policies": [ { "address": "cosmos1..", "group_id": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "decision_policy": { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "1", "windows": { "voting_period": "120h", "min_execution_period": "0s" } }, }, { "address": "cosmos1..", "group_id": "1", "admin": "cosmos1..", "metadata": "AQ==", "version": "1", "decision_policy": { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "1", "windows": { "voting_period": "120h", "min_execution_period": "0s" } }, } ], "pagination": { "next_key": null, "total": "2" } ``` #### Proposal The `Proposal` endpoint allows users to query for proposal by id. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/proposal/{proposal_id} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/proposal/1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposal": { "proposal_id": "1", "address": "cosmos1..", "metadata": "AQ==", "proposers": [ "cosmos1.." ], "submitted_at": "2021-12-17T07:06:26.310638964Z", "group_version": "1", "group_policy_version": "1", "status": "STATUS_SUBMITTED", "result": "RESULT_UNFINALIZED", "vote_state": { "yes_count": "0", "no_count": "0", "abstain_count": "0", "veto_count": "0" }, "windows": { "min_execution_period": "0s", "voting_period": "432000s" }, "executor_result": "EXECUTOR_RESULT_NOT_RUN", "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1..", "to_address": "cosmos1..", "amount": [ { "denom": "stake", "amount": "100000000" } ] } ], "title": "Title", "summary": "Summary", } } ``` #### ProposalsByGroupPolicy The `ProposalsByGroupPolicy` endpoint allows users to query for proposals by account address of group policy with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/proposals_by_group_policy/{address} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/proposals_by_group_policy/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "proposals": [ { "id": "1", "group_policy_address": "cosmos1..", "metadata": "AQ==", "proposers": [ "cosmos1.." ], "submit_time": "2021-12-17T08:03:27.099649352Z", "group_version": "1", "group_policy_version": "1", "status": "STATUS_CLOSED", "result": "RESULT_ACCEPTED", "vote_state": { "yes_count": "1", "no_count": "0", "abstain_count": "0", "veto_count": "0" }, "windows": { "min_execution_period": "0s", "voting_period": "432000s" }, "executor_result": "EXECUTOR_RESULT_NOT_RUN", "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1..", "to_address": "cosmos1..", "amount": [ { "denom": "stake", "amount": "100000000" } ] } ] } ], "pagination": { "next_key": null, "total": "1" } } ``` #### VoteByProposalVoter The `VoteByProposalVoter` endpoint allows users to query for vote by proposal id and voter account address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/vote_by_proposal_voter/{proposal_id}/{voter} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1beta1/vote_by_proposal_voter/1/cosmos1.. ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vote": { "proposal_id": "1", "voter": "cosmos1..", "choice": "CHOICE_YES", "metadata": "AQ==", "submitted_at": "2021-12-17T08:05:02.490164009Z" } } ``` #### VotesByProposal The `VotesByProposal` endpoint allows users to query for votes by proposal id with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/votes_by_proposal/{proposal_id} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/votes_by_proposal/1 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposal_id": "1", "voter": "cosmos1..", "option": "CHOICE_YES", "metadata": "AQ==", "submit_time": "2021-12-17T08:05:02.490164009Z" } ], "pagination": { "next_key": null, "total": "1" } } ``` #### VotesByVoter The `VotesByVoter` endpoint allows users to query for votes by voter account address with pagination flags. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/group/v1/votes_by_voter/{voter} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl localhost:1317/cosmos/group/v1/votes_by_voter/cosmos1.. ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "votes": [ { "proposal_id": "1", "voter": "cosmos1..", "choice": "CHOICE_YES", "metadata": "AQ==", "submitted_at": "2021-12-17T08:05:02.490164009Z" } ], "pagination": { "next_key": null, "total": "1" } } ``` ## Metadata The group module has four locations for metadata where users can provide further context about the on-chain actions they are taking. By default all metadata fields have a 255 character length field where metadata can be stored in json format, either on-chain or off-chain depending on the amount of data required. Here we provide a recommendation for the json structure and where the data should be stored. There are two important factors in making these recommendations. First, that the group and gov modules are consistent with one another, note the number of proposals made by all groups may be quite large. Second, that client applications such as block explorers and governance interfaces have confidence in the consistency of metadata structure across chains. ### Proposal Location: off-chain as json object stored on IPFS (mirrors [gov proposal](/sdk/latest/modules/gov/README#metadata)) ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "", "authors": [""], "summary": "", "details": "", "proposal_forum_url": "", "vote_option_context": "", } ``` The `authors` field is an array of strings, this is to allow for multiple authors to be listed in the metadata. In v0.46, the `authors` field is a comma-separated string. Frontends are encouraged to support both formats for backwards compatibility. ### Vote Location: on-chain as json within 255 character limit (mirrors [gov vote](/sdk/latest/modules/gov/README#metadata)) ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "justification": "", } ``` ### Group Location: off-chain as json object stored on IPFS ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "name": "", "description": "", "group_website_url": "", "group_forum_url": "", } ``` ### Decision policy Location: on-chain as json within 255 character limit ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "name": "", "description": "", } ``` # x/mint Source: https://docs.cosmos.network/sdk/latest/modules/mint/README The x/mint module handles the regular minting of new tokens in a configurable manner. The `x/mint` module handles the regular minting of new tokens in a configurable manner. ## Contents * [State](#state) * [Minter](#minter) * [Params](#params) * [Begin-Block](#begin-block) * [NextInflationRate](#nextinflationrate) * [NextAnnualProvisions](#nextannualprovisions) * [BlockProvision](#blockprovision) * [Parameters](#parameters) * [Events](#events) * [BeginBlocker](#beginblocker) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) * [REST](#rest) ## Concepts ### The Minting Mechanism The default minting mechanism was designed to: * allow for a flexible inflation rate determined by market demand targeting a particular bonded-stake ratio * effect a balance between market liquidity and staked supply In order to best determine the appropriate market rate for inflation rewards, a moving change rate is used. The moving change rate mechanism ensures that if the % bonded is either over or under the goal %-bonded, the inflation rate will adjust to further incentivize or disincentivize being bonded, respectively. Setting the goal %-bonded at less than 100% encourages the network to maintain some non-staked tokens which should help provide some liquidity. It can be broken down in the following way: * If the actual percentage of bonded tokens is below the goal %-bonded the inflation rate will increase until a maximum value is reached * If the goal % bonded (67% in Cosmos-Hub) is maintained, then the inflation rate will stay constant * If the actual percentage of bonded tokens is above the goal %-bonded the inflation rate will decrease until a minimum value is reached ### Custom Minters As of Cosmos SDK v0.53.0, developers can set a custom `MintFn` for the module for specialized token minting logic. The function signature that a `MintFn` must implement is as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MintFn defines the function that needs to be implemented in order to customize the minting process. type MintFn func(ctx sdk.Context, k *Keeper) error ``` This can be passed to the `Keeper` upon creation with an additional `Option`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.MintKeeper = mintkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[minttypes.StoreKey]), app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String(), // mintkeeper.WithMintFn(CUSTOM_MINT_FN), // custom mintFn can be added here ) ``` #### Custom Minter DI Example Below is a simple approach to creating a custom mint function with extra dependencies in DI configurations. For this basic example, we will make the minter simply double the supply of `foo` coin. First, we will define a function that takes our required dependencies, and returns a `MintFn`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MyCustomMintFunction is a custom mint function that doubles the supply of `foo` coin. func MyCustomMintFunction(bank bankkeeper.BaseKeeper) mintkeeper.MintFn { return func(ctx sdk.Context, k *mintkeeper.Keeper) error { supply := bank.GetSupply(ctx, "foo") err := k.MintCoins(ctx, sdk.NewCoins(supply.Add(supply))) if err != nil { return err } return nil } } ``` Then, pass the function defined above into the `depinject.Supply` function with the required dependencies. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // NewSimApp returns a reference to an initialized SimApp. func NewSimApp( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { var ( app = &SimApp{ } appBuilder *runtime.AppBuilder appConfig = depinject.Configs( AppConfig, depinject.Supply( appOpts, logger, // our custom mint function with the necessary dependency passed in. MyCustomMintFunction(app.BankKeeper), ), ) ) // ... } ``` ## State ### Minter The minter is a space for holding current inflation information. * Minter: `0x00 -> ProtocolBuffer(minter)` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/mint/v1beta1/mint.proto#L10-L24 ``` ### Params The mint module stores its params in state with the prefix of `0x01`, it can be updated with governance or the address with authority. **Note:** The `MaxSupply` parameter controls the maximum supply of tokens the module can mint. A value of `0` indicates an unlimited supply. * Params: `0x01 -> ProtocolBuffer(Params)` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/mint/v1beta1/mint.proto#L26-L71 ``` ## Begin-Block Minting parameters are recalculated and inflation paid at the beginning of each block. ### Inflation rate calculation Inflation rate is calculated using an "inflation calculation function" that's passed to the `NewAppModule` function. If no function is passed, then the SDK's default inflation function will be used (`NextInflationRate`). In case a custom inflation calculation logic is needed, this can be achieved by defining and passing a function that matches `InflationCalculationFn`'s signature. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type InflationCalculationFn func(ctx sdk.Context, minter Minter, params Params, bondedRatio math.LegacyDec) math.LegacyDec ``` #### NextInflationRate The target annual inflation rate is recalculated each block. The inflation is also subject to a rate change (positive or negative) depending on the distance from the desired ratio (67%). The maximum rate change possible is defined to be 13% per year, however, the annual inflation is capped as between 7% and 20%. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} NextInflationRate(params Params, bondedRatio math.LegacyDec) (inflation math.LegacyDec) { inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange inflationRateChange = inflationRateChangePerYear/blocksPerYr // increase the new annual inflation for this next block inflation += inflationRateChange if inflation > params.InflationMax { inflation = params.InflationMax } if inflation < params.InflationMin { inflation = params.InflationMin } return inflation } ``` ### NextAnnualProvisions Calculate the annual provisions based on current total supply and inflation rate. This parameter is calculated once per block. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} NextAnnualProvisions(params Params, totalSupply math.LegacyDec) (provisions math.LegacyDec) { return Inflation * totalSupply ``` ### BlockProvision Calculate the provisions generated for each block based on current annual provisions. The provisions are then minted by the `mint` module's `ModuleMinterAccount` and then transferred to the `auth`'s `FeeCollector` `ModuleAccount`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} BlockProvision(params Params) sdk.Coin { provisionAmt = AnnualProvisions/ params.BlocksPerYear return sdk.NewCoin(params.MintDenom, provisionAmt.Truncate()) ``` ## Parameters The minting module contains the following parameters: | Key | Type | Example | | ------------------- | ----------------- | ---------------------- | | MintDenom | string | "uatom" | | InflationRateChange | string (dec) | "0.130000000000000000" | | InflationMax | string (dec) | "0.200000000000000000" | | InflationMin | string (dec) | "0.070000000000000000" | | GoalBonded | string (dec) | "0.670000000000000000" | | BlocksPerYear | string (uint64) | "6311520" | | MaxSupply | string (math.Int) | "0" | A `MaxSupply` value of `0` means no maximum supply is enforced. Minting stops automatically once the total supply reaches the configured `MaxSupply`. For legacy Amino JSON compatibility, `max_supply` is encoded even when set to `"0"`. ## Events The minting module emits the following events: ### BeginBlocker | Type | Attribute Key | Attribute Value | | ---- | ------------------ | -------------------- | | mint | bonded\_ratio | `{bondedRatio}` | | mint | inflation | `{inflation}` | | mint | annual\_provisions | `{annualProvisions}` | | mint | amount | `{amount}` | ## Client ### CLI A user can query and interact with the `mint` module using the CLI. #### Query The `query` commands allows users to query `mint` state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query mint --help ``` ##### annual-provisions The `annual-provisions` command allows users to query the current minting annual provisions value ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query mint annual-provisions [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query mint annual-provisions ``` Example Output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} 22268504368893.612100895088410693 ``` ##### inflation The `inflation` command allows users to query the current minting inflation value ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query mint inflation [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query mint inflation ``` Example Output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} 0.199200302563256955 ``` ##### params The `params` command allows users to query the current minting parameters ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query mint params [flags] ``` Example: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} blocks_per_year: "4360000" goal_bonded: "0.670000000000000000" inflation_max: "0.200000000000000000" inflation_min: "0.070000000000000000" inflation_rate_change: "0.130000000000000000" max_supply: "0" mint_denom: stake ``` ### gRPC A user can query the `mint` module using gRPC endpoints. #### AnnualProvisions The `AnnualProvisions` endpoint allows users to query the current minting annual provisions value ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos.mint.v1beta1.Query/AnnualProvisions ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/AnnualProvisions ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "annualProvisions": "1432452520532626265712995618" } ``` #### Inflation The `Inflation` endpoint allows users to query the current minting inflation value ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos.mint.v1beta1.Query/Inflation ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/Inflation ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "inflation": "130197115720711261" } ``` #### Params The `Params` endpoint allows users to query the current minting parameters ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos.mint.v1beta1.Query/Params ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/Params ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "mintDenom": "stake", "inflationRateChange": "130000000000000000", "inflationMax": "200000000000000000", "inflationMin": "70000000000000000", "goalBonded": "670000000000000000", "blocksPerYear": "6311520", "maxSupply": "0" } } ``` ### REST A user can query the `mint` module using REST endpoints. #### annual-provisions ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/mint/v1beta1/annual_provisions ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl "localhost:1317/cosmos/mint/v1beta1/annual_provisions" ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "annualProvisions": "1432452520532626265712995618" } ``` #### inflation ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/mint/v1beta1/inflation ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl "localhost:1317/cosmos/mint/v1beta1/inflation" ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "inflation": "130197115720711261" } ``` #### params ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/mint/v1beta1/params ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl "localhost:1317/cosmos/mint/v1beta1/params" ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "mintDenom": "stake", "inflationRateChange": "130000000000000000", "inflationMax": "200000000000000000", "inflationMin": "70000000000000000", "goalBonded": "670000000000000000", "blocksPerYear": "6311520", "maxSupply": "0" } } ``` # Module Directory Source: https://docs.cosmos.network/sdk/latest/modules/modules Here are some production-grade modules that can be used in Cosmos SDK applications, along with their respective documentation. Here are some production-grade modules that can be used in Cosmos SDK applications, along with their respective documentation: ## Essential Modules Essential modules include functionality that *must* be included in your Cosmos SDK blockchain. These modules provide the core behaviors that are needed for users and operators such as balance tracking, proof-of-stake capabilities and governance. * [Auth](/sdk/latest/modules/auth/auth) - Authentication of accounts and transactions for Cosmos SDK applications. * [Bank](/sdk/latest/modules/bank/README) - Token transfer functionalities. * [Circuit](/sdk/latest/modules/circuit/README) - Circuit breaker module for pausing messages. * [Consensus](/sdk/latest/modules/consensus/README) - Consensus module for modifying CometBFT's ABCI consensus params. * [Distribution](/sdk/latest/modules/distribution/README) - Fee distribution, and staking token provision distribution. * [Evidence](/sdk/latest/modules/evidence/README) - Evidence handling for double signing, misbehaviour, etc. * [Governance](/sdk/latest/modules/gov/README) - On-chain proposals and voting. * [Genutil](/sdk/latest/modules/genutil/README) - Genesis utilities for the Cosmos SDK. * [Mint](/sdk/latest/modules/mint/README) - Creation of new units of staking token. * [Slashing](/sdk/latest/modules/slashing/README) - Validator punishment mechanisms. * [Staking](/sdk/latest/modules/staking/README) - Proof-of-Stake layer for public blockchains. * [Upgrade](/sdk/latest/modules/upgrade/README) - Software upgrades handling and coordination. ## Supplementary Modules Supplementary modules are modules that are maintained in the Cosmos SDK but are not necessary for the core functionality of your blockchain. They can be thought of as ways to extend the capabilities of your blockchain or further specialize it. * [Authz](/sdk/latest/modules/authz/README) - Authorization for accounts to perform actions on behalf of other accounts. * [Epochs](/sdk/latest/modules/epochs/README) - Registration so SDK modules can have logic to be executed at the timed tickers. * [Feegrant](/sdk/latest/modules/feegrant/README) - Grant fee allowances for executing transactions. * [Group](/sdk/latest/modules/group/README) - Allows for the creation and management of on-chain multisig accounts. * [NFT](/sdk/latest/modules/nft/README) - NFT module implemented based on [ADR43](/sdk/latest/reference/architecture/adr-043-nft-module). ## Deprecated Modules The following modules are deprecated. They will no longer be maintained and eventually will be removed in an upcoming release of the Cosmos SDK per the [release family lifecycle](/sdk/latest/release-family). * [Crisis](/sdk/latest/modules/crisis/README) - *Deprecated* halting the blockchain under certain circumstances (e.g. if an invariant is broken). * [Params](/sdk/latest/modules/params/README) - *Deprecated* Globally available parameter store. To learn more about the process of building modules, visit the [building modules reference documentation](/sdk/latest/guides/module-design/module-design-considerations). ## IBC The IBC module for the SDK is maintained by the IBC Go team in its [own repository](https://github.com/cosmos/ibc-go). Additionally, the [capability module](https://github.com/cosmos/ibc-go/tree/fdd664698d79864f1e00e147f9879e58497b5ef1/modules/capability) is from v0.50+ maintained by the IBC Go team in its [own repository](https://github.com/cosmos/ibc-go/tree/fdd664698d79864f1e00e147f9879e58497b5ef1/modules/capability). ## CosmWasm The CosmWasm module enables smart contracts, learn more by going to their [documentation site](https://book.cosmwasm.com/), or visit [the repository](https://github.com/CosmWasm/cosmwasm). ## EVM Read more about writing smart contracts with solidity at the official [`evm` documentation page](https://evm.cosmos.network/). # x/nft Source: https://docs.cosmos.network/sdk/latest/modules/nft/README ## Abstract `x/nft` has been moved to [`./contrib/x/nft`](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/contrib/x/nft) and is no longer actively maintained as part of the core Cosmos SDK. It is still available for use but is not included in the SDK Bug Bounty program. It was moved because it was never widely adopted. ## Contents ## Abstract `x/nft` is an implementation of a Cosmos SDK module, per [ADR 43](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-043-nft-module.md), that allows you to create nft classification, create nft, transfer nft, update nft, and support various queries by integrating the module. It is fully compatible with the ERC721 specification. * [Concepts](#concepts) * [Class](#class) * [NFT](#nft) * [State](#state) * [Class](#class-1) * [NFT](#nft-1) * [NFTOfClassByOwner](#nftofclassbyowner) * [Owner](#owner) * [TotalSupply](#totalsupply) * [Messages](#messages) * [MsgSend](#msgsend) * [Events](#events) ## Concepts ### Class `x/nft` module defines a struct `Class` to describe the common characteristics of a class of nft, under this class, you can create a variety of nft, which is equivalent to an erc721 contract for Ethereum. The design is defined in the [ADR 043](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-043-nft-module.md). ### NFT The full name of NFT is Non-Fungible Tokens. Because of the irreplaceable nature of NFT, it means that it can be used to represent unique things. The nft implemented by this module is fully compatible with Ethereum ERC721 standard. ## State ### Class Class is mainly composed of `id`, `name`, `symbol`, `description`, `uri`, `uri_hash`,`data` where `id` is the unique identifier of the class, similar to the Ethereum ERC721 contract address, the others are optional. * Class: `0x01 | classID | -> ProtocolBuffer(Class)` ### NFT NFT is mainly composed of `class_id`, `id`, `uri`, `uri_hash` and `data`. Among them, `class_id` and `id` are two-tuples that identify the uniqueness of nft, `uri` and `uri_hash` is optional, which identifies the off-chain storage location of the nft, and `data` is an Any type. Use Any chain of `x/nft` modules can be customized by extending this field * NFT: `0x02 | classID | 0x00 | nftID |-> ProtocolBuffer(NFT)` ### NFTOfClassByOwner NFTOfClassByOwner is mainly to realize the function of querying all nfts using classID and owner, without other redundant functions. * NFTOfClassByOwner: `0x03 | owner | 0x00 | classID | 0x00 | nftID |-> 0x01` ### Owner Since there is no extra field in NFT to indicate the owner of nft, an additional key-value pair is used to save the ownership of nft. With the transfer of nft, the key-value pair is updated synchronously. * OwnerKey: `0x04 | classID | 0x00 | nftID |-> owner` ### TotalSupply TotalSupply is responsible for tracking the number of all nfts under a certain class. Mint operation is performed under the changed class, supply increases by one, burn operation, and supply decreases by one. * OwnerKey: `0x05 | classID |-> totalSupply` ## Messages In this section we describe the processing of messages for the NFT module. The validation of `ClassID` and `NftID` is left to the app developer.\ The SDK does not provide any validation for these fields. ### MsgSend You can use the `MsgSend` message to transfer the ownership of nft. This is a function provided by the `x/nft` module. Of course, you can use the `Transfer` method to implement your own transfer logic, but you need to pay extra attention to the transfer permissions. The message handling should fail if: * provided `ClassID` does not exist. * provided `Id` does not exist. * provided `Sender` does not the owner of nft. ## Events The nft module emits proto events defined in [the Protobuf reference](https://buf.build/cosmos/cosmos-sdk/docs/main:cosmos.nft.v1beta1). # x/params Source: https://docs.cosmos.network/sdk/latest/modules/params/README NOTE: x/params is deprecated as of Cosmos SDK v0.53 and will be removed in the next release. NOTE: `x/params` is deprecated as of Cosmos SDK v0.53 and will be removed in the next release. ## Abstract Package params provides a globally available parameter store. There are two main types, Keeper and Subspace. Subspace is an isolated namespace for a paramstore, where keys are prefixed by preconfigured spacename. Keeper has a permission to access all existing spaces. Subspace can be used by the individual keepers, which need a private parameter store that the other keepers cannot modify. The params Keeper can be used to add a route to `x/gov` router in order to modify any parameter in case a proposal passes. The following contents explains how to use params module for master and user modules. ## Contents * [Keeper](#keeper) * [Subspace](#subspace) * [Key](#key) * [KeyTable](#keytable) * [ParamSet](#paramset) ## Keeper In the app initialization stage, [subspaces](#subspace) can be allocated for other modules' keeper using `Keeper.Subspace` and are stored in `Keeper.spaces`. Then, those modules can have a reference to their specific parameter store through `Keeper.GetSubspace`. Example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ExampleKeeper struct { paramSpace paramtypes.Subspace } func (k ExampleKeeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSpace.SetParamSet(ctx, ¶ms) } ``` ## Subspace `Subspace` is a prefixed subspace of the parameter store. Each module which uses the parameter store will take a `Subspace` to isolate permission to access. ### Key Parameter keys are human readable alphanumeric strings. A parameter for the key `"ExampleParameter"` is stored under `[]byte("SubspaceName" + "/" + "ExampleParameter")`, where `"SubspaceName"` is the name of the subspace. Subkeys are secondary parameter keys those are used along with a primary parameter key. Subkeys can be used for grouping or dynamic parameter key generation during runtime. ### KeyTable All of the parameter keys that will be used should be registered at the compile time. `KeyTable` is essentially a `map[string]attribute`, where the `string` is a parameter key. Currently, `attribute` consists of a `reflect.Type`, which indicates the parameter type to check that provided key and value are compatible and registered, as well as a function `ValueValidatorFn` to validate values. Only primary keys have to be registered on the `KeyTable`. Subkeys inherit the attribute of the primary key. ### ParamSet Modules often define parameters as a proto message. The generated struct can implement `ParamSet` interface to be used with the following methods: * `KeyTable.RegisterParamSet()`: registers all parameters in the struct * `Subspace.{Get, Set}ParamSet()`: Get to & Set from the struct The implementor should be a pointer in order to use `GetParamSet()`. # x/slashing Source: https://docs.cosmos.network/sdk/latest/modules/slashing/README This section specifies the slashing module of the Cosmos SDK, which implements functionality first outlined in the Cosmos Whitepaper in June 2016. ## Abstract This section specifies the slashing module of the Cosmos SDK, which implements functionality first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md) in June 2016. The slashing module enables Cosmos SDK-based blockchains to disincentivize any attributable action by a protocol-recognized actor with value at stake by penalizing them ("slashing"). Penalties may include, but are not limited to: * Burning some amount of their stake * Removing their ability to vote on future blocks for a period of time. This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosystem. ## Contents * [Concepts](#concepts) * [States](#states) * [Tombstone Caps](#tombstone-caps) * [Infraction Timelines](#infraction-timelines) * [State](#state) * [Signing Info (Liveness)](#signing-info-liveness) * [Params](#params) * [Messages](#messages) * [Unjail](#unjail) * [BeginBlock](#beginblock) * [Liveness Tracking](#liveness-tracking) * [Hooks](#hooks) * [Events](#events) * [Staking Tombstone](#staking-tombstone) * [Parameters](#parameters) * [CLI](#cli) * [Query](#query) * [Transactions](#transactions) * [gRPC](#grpc) * [REST](#rest) ## Concepts ### States At any given time, there are any number of validators registered in the state machine. Each block, the top `MaxValidators` (defined by `x/staking`) validators who are not jailed become *bonded*, meaning that they may propose and vote on blocks. Validators who are *bonded* are *at stake*, meaning that part or all of their stake and their delegators' stake is at risk if they commit a protocol fault. For each of these validators we keep a `ValidatorSigningInfo` record that contains information pertaining to validator's liveness and other infraction related attributes. ### Tombstone Caps In order to mitigate the impact of initially likely categories of non-malicious protocol faults, the Cosmos Hub implements for each validator a *tombstone* cap, which only allows a validator to be slashed once for a double sign fault. For example, if you misconfigure your HSM and double-sign a bunch of old blocks, you'll only be punished for the first double-sign (and then immediately tombstoned). This will still be quite expensive and desirable to avoid, but tombstone caps somewhat blunt the economic impact of unintentional misconfiguration. Liveness faults do not have caps, as they can't stack upon each other. Liveness bugs are "detected" as soon as the infraction occurs, and the validators are immediately put in jail, so it is not possible for them to commit multiple liveness faults without unjailing in between. ### Infraction Timelines To illustrate how the `x/slashing` module handles submitted evidence through CometBFT consensus, consider the following examples: **Definitions**: *\[* : timeline start\ *]* : timeline end\ *Cn* : infraction `n` committed\ *Dn* : infraction `n` discovered\ *Vb* : validator bonded\ *Vu* : validator unbonded #### Single Double Sign Infraction \[----------C1----D1,Vu-----] A single infraction is committed then later discovered, at which point the validator is unbonded and slashed at the full amount for the infraction. #### Multiple Double Sign Infractions \[----------C1--C2---C3---D1,D2,D3Vu-----] Multiple infractions are committed and then later discovered, at which point the validator is jailed and slashed for only one infraction. Because the validator is also tombstoned, they can not rejoin the validator set. ## State ### Signing Info (Liveness) Every block includes a set of precommits by the validators for the previous block, known as the `LastCommitInfo` provided by CometBFT. A `LastCommitInfo` is valid so long as it contains precommits from +2/3 of total voting power. Proposers are incentivized to include precommits from all validators in the CometBFT `LastCommitInfo` by receiving additional fees proportional to the difference between the voting power included in the `LastCommitInfo` and +2/3 (see [fee distribution](/sdk/latest/modules/distribution/README#begin-block)). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type LastCommitInfo struct { Round int32 Votes []VoteInfo } ``` Validators are penalized for failing to be included in the `LastCommitInfo` for some number of blocks by being automatically jailed, potentially slashed, and unbonded. Information about validator's liveness activity is tracked through `ValidatorSigningInfo`. It is indexed in the store as follows: * ValidatorSigningInfo: `0x01 | ConsAddrLen (1 byte) | ConsAddress -> ProtocolBuffer(ValSigningInfo)` * MissedBlocksBitArray: `0x02 | ConsAddrLen (1 byte) | ConsAddress | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)` (varint is a number encoding format) The first mapping allows us to easily lookup the recent signing info for a validator based on the validator's consensus address. The second mapping (`MissedBlocksBitArray`) acts as a bit-array of size `SignedBlocksWindow` that tells us if the validator missed the block for a given index in the bit-array. The index in the bit-array is given as little endian uint64. The result is a `varint` that takes on `0` or `1`, where `0` indicates the validator did not miss (did sign) the corresponding block, and `1` indicates they missed the block (did not sign). Note that the `MissedBlocksBitArray` is not explicitly initialized up-front. Keys are added as we progress through the first `SignedBlocksWindow` blocks for a newly bonded validator. The `SignedBlocksWindow` parameter defines the size (number of blocks) of the sliding window used to track validator liveness. The information stored for tracking validator liveness is as follows: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/slashing/v1beta1/slashing.proto#L13-L35 ``` ### Params The slashing module stores it's params in state with the prefix of `0x00`, it can be updated with governance or the address with authority. * Params: `0x00 | ProtocolBuffer(Params)` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/slashing/v1beta1/slashing.proto#L37-L62 ``` ## Messages In this section we describe the processing of messages for the `slashing` module. ### Unjail If a validator was automatically unbonded due to downtime and wishes to come back online & possibly rejoin the bonded set, it must send `MsgUnjail`: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MsgUnjail is an sdk.Msg used for unjailing a jailed validator, thus returning // them into the bonded validator set, so they can begin receiving provisions // and rewards again. message MsgUnjail { string validator_addr = 1; } ``` Below is a pseudocode of the `MsgSrv/Unjail` RPC: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} unjail(tx MsgUnjail) validator = getValidator(tx.ValidatorAddr) if validator == nil fail with "No validator found" if getSelfDelegation(validator) == 0 fail with "validator must self delegate before unjailing" if !validator.Jailed fail with "Validator not jailed, cannot unjail" info = GetValidatorSigningInfo(operator) if info.Tombstoned fail with "Tombstoned validator cannot be unjailed" if block time < info.JailedUntil fail with "Validator still jailed, cannot unjail until period has expired" validator.Jailed = false setValidator(validator) return ``` If the validator has enough stake to be in the top `n = MaximumBondedValidators`, it will be automatically rebonded, and all delegators still delegated to the validator will be rebonded and begin to again collect provisions and rewards. ## BeginBlock ### Liveness Tracking At the beginning of each block, we update the `ValidatorSigningInfo` for each validator and check if they've crossed below the liveness threshold over a sliding window. This sliding window is defined by `SignedBlocksWindow` and the index in this window is determined by `IndexOffset` found in the validator's `ValidatorSigningInfo`. For each block processed, the `IndexOffset` is incremented regardless if the validator signed or not. Once the index is determined, the `MissedBlocksBitArray` and `MissedBlocksCounter` are updated accordingly. Finally, in order to determine if a validator crosses below the liveness threshold, we fetch the maximum number of blocks missed, `maxMissed`, which is `SignedBlocksWindow - (MinSignedPerWindow * SignedBlocksWindow)` and the minimum height at which we can determine liveness, `minHeight`. If the current block is greater than `minHeight` and the validator's `MissedBlocksCounter` is greater than `maxMissed`, they will be slashed by `SlashFractionDowntime`, will be jailed for `DowntimeJailDuration`, and have the following values reset: `MissedBlocksBitArray`, `MissedBlocksCounter`, and `IndexOffset`. **Note**: Liveness slashes do **NOT** lead to a tombstoning. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} height := block.Height for vote in block.LastCommitInfo.Votes { signInfo := GetValidatorSigningInfo(vote.Validator.Address) // This is a relative index, so we counts blocks the validator SHOULD have // signed. We use the 0-value default signing info if not present, except for // start height. index := signInfo.IndexOffset % SignedBlocksWindow() signInfo.IndexOffset++ // Update MissedBlocksBitArray and MissedBlocksCounter. The MissedBlocksCounter // just tracks the sum of MissedBlocksBitArray. That way we avoid needing to // read/write the whole array each time. missedPrevious := GetValidatorMissedBlockBitArray(vote.Validator.Address, index) missed := !signed switch { case !missedPrevious && missed: // array index has changed from not missed to missed, increment counter SetValidatorMissedBlockBitArray(vote.Validator.Address, index, true) signInfo.MissedBlocksCounter++ case missedPrevious && !missed: // array index has changed from missed to not missed, decrement counter SetValidatorMissedBlockBitArray(vote.Validator.Address, index, false) signInfo.MissedBlocksCounter-- default: // array index at this index has not changed; no need to update counter } if missed { // emit events... } minHeight := signInfo.StartHeight + SignedBlocksWindow() maxMissed := SignedBlocksWindow() - MinSignedPerWindow() // If we are past the minimum height and the validator has missed too many // jail and slash them. if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { validator := ValidatorByConsAddr(vote.Validator.Address) // emit events... // We need to retrieve the stake distribution which signed the block, so we // subtract ValidatorUpdateDelay from the block height, and subtract an // additional 1 since this is the LastCommit. // // Note, that this CAN result in a negative "distributionHeight" up to // -ValidatorUpdateDelay-1, i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. // That's fine since this is just used to filter unbonding delegations & redelegations. distributionHeight := height - sdk.ValidatorUpdateDelay - 1 SlashWithInfractionReason(vote.Validator.Address, distributionHeight, vote.Validator.Power, SlashFractionDowntime(), stakingtypes.Downtime) Jail(vote.Validator.Address) signInfo.JailedUntil = block.Time.Add(DowntimeJailDuration()) // We need to reset the counter & array so that the validator won't be // immediately slashed for downtime upon rebonding. signInfo.MissedBlocksCounter = 0 signInfo.IndexOffset = 0 ClearValidatorMissedBlockBitArray(vote.Validator.Address) } SetValidatorSigningInfo(vote.Validator.Address, signInfo) } ``` ## Hooks This section contains a description of the module's `hooks`. Hooks are operations that are executed automatically when events are raised. ### Staking hooks The slashing module implements the `StakingHooks` defined in `x/staking` and are used as record-keeping of validators information. During the app initialization, these hooks should be registered in the staking module struct. The following hooks impact the slashing state: * `AfterValidatorBonded` creates a `ValidatorSigningInfo` instance as described in the following section. * `AfterValidatorCreated` stores a validator's consensus key. * `AfterValidatorRemoved` removes a validator's consensus key. ### Validator Bonded Upon successful first-time bonding of a new validator, we create a new `ValidatorSigningInfo` structure for the now-bonded validator, which `StartHeight` of the current block. If the validator was out of the validator set and gets bonded again, its new bonded height is set. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} onValidatorBonded(address sdk.ValAddress) signingInfo, found = GetValidatorSigningInfo(address) if !found { signingInfo = ValidatorSigningInfo { StartHeight : CurrentHeight, IndexOffset : 0, JailedUntil : time.Unix(0, 0), Tombstone : false, MissedBlocksCounter : 0 } else { signingInfo.StartHeight = CurrentHeight } setValidatorSigningInfo(signingInfo) } return ``` ## Events The slashing module emits the following events: ### MsgServer #### MsgUnjail | Type | Attribute Key | Attribute Value | | ------- | ------------- | -------------------- | | message | module | slashing | | message | sender | `{validatorAddress}` | ### Keeper ### BeginBlocker: HandleValidatorSignature | Type | Attribute Key | Attribute Value | | ----- | ------------- | ----------------------------- | | slash | address | `{validatorConsensusAddress}` | | slash | power | `{validatorPower}` | | slash | reason | `{slashReason}` | | slash | jailed \[0] | `{validatorConsensusAddress}` | | slash | burned coins | `{math.Int}` | * \[0] Only included if the validator is jailed. | Type | Attribute Key | Attribute Value | | -------- | -------------- | ----------------------------- | | liveness | address | `{validatorConsensusAddress}` | | liveness | missed\_blocks | `{missedBlocksCounter}` | | liveness | height | `{blockHeight}` | #### Slash * same as `"slash"` event from `HandleValidatorSignature`, but without the `jailed` attribute. #### Jail | Type | Attribute Key | Attribute Value | | ----- | ------------- | -------------------- | | slash | jailed | `{validatorAddress}` | ## Staking Tombstone ### Abstract In the current implementation of the `slashing` module, when the consensus engine informs the state machine of a validator's consensus fault, the validator is partially slashed, and put into a "jail period", a period of time in which they are not allowed to rejoin the validator set. However, because of the nature of consensus faults and ABCI, there can be a delay between an infraction occurring, and evidence of the infraction reaching the state machine (this is one of the primary reasons for the existence of the unbonding period). > Note: The tombstone concept, only applies to faults that have a delay between > the infraction occurring and evidence reaching the state machine. For example, > evidence of a validator double signing may take a while to reach the state machine > due to unpredictable evidence gossip layer delays and the ability of validators to > selectively reveal double-signatures (e.g. to infrequently-online light clients). > Liveness slashing, on the other hand, is detected immediately as soon as the > infraction occurs, and therefore no slashing period is needed. A validator is > immediately put into jail period, and they cannot commit another liveness fault > until they unjail. In the future, there may be other types of byzantine faults > that have delays (for example, submitting evidence of an invalid proposal as a transaction). > When implemented, it will have to be decided whether these future types of > byzantine faults will result in a tombstoning (and if not, the slash amounts > will not be capped by a slashing period). In the current system design, once a validator is put in the jail for a consensus fault, after the `JailPeriod` they are allowed to send a transaction to `unjail` themselves, and thus rejoin the validator set. One of the "design desires" of the `slashing` module is that if multiple infractions occur before evidence is executed (and a validator is put in jail), they should only be punished for single worst infraction, but not cumulatively. For example, if the sequence of events is: 1. Validator A commits Infraction 1 (worth 30% slash) 2. Validator A commits Infraction 2 (worth 40% slash) 3. Validator A commits Infraction 3 (worth 35% slash) 4. Evidence for Infraction 1 reaches state machine (and validator is put in jail) 5. Evidence for Infraction 2 reaches state machine 6. Evidence for Infraction 3 reaches state machine Only Infraction 2 should have its slash take effect, as it is the highest. This is done, so that in the case of the compromise of a validator's consensus key, they will only be punished once, even if the hacker double-signs many blocks. Because, the unjailing has to be done with the validator's operator key, they have a chance to re-secure their consensus key, and then signal that they are ready using their operator key. We call this period during which we track only the max infraction, the "slashing period". Once, a validator rejoins by unjailing themselves, we begin a new slashing period; if they commit a new infraction after unjailing, it gets slashed cumulatively on top of the worst infraction from the previous slashing period. However, while infractions are grouped based off of the slashing periods, because evidence can be submitted up to an `unbondingPeriod` after the infraction, we still have to allow for evidence to be submitted for previous slashing periods. For example, if the sequence of events is: 1. Validator A commits Infraction 1 (worth 30% slash) 2. Validator A commits Infraction 2 (worth 40% slash) 3. Evidence for Infraction 1 reaches state machine (and Validator A is put in jail) 4. Validator A unjails We are now in a new slashing period, however we still have to keep the door open for the previous infraction, as the evidence for Infraction 2 may still come in. As the number of slashing periods increase, it creates more complexity as we have to keep track of the highest infraction amount for every single slashing period. > Note: Currently, according to the `slashing` module spec, a new slashing period > is created every time a validator is unbonded then rebonded. This should probably > be changed to jailed/unjailed. See issue [#3205](https://github.com/cosmos/cosmos-sdk/issues/3205) > for further details. For the remainder of this, I will assume that we only start > a new slashing period when a validator gets unjailed. The maximum number of slashing periods is the `len(UnbondingPeriod) / len(JailPeriod)`. The current defaults in Gaia for the `UnbondingPeriod` and `JailPeriod` are 3 weeks and 2 days, respectively. This means there could potentially be up to 11 slashing periods concurrently being tracked per validator. If we set the `JailPeriod >= UnbondingPeriod`, we only have to track 1 slashing period (i.e not have to track slashing periods). Currently, in the jail period implementation, once a validator unjails, all of their delegators who are delegated to them (haven't unbonded / redelegated away), stay with them. Given that consensus safety faults are so egregious (way more so than liveness faults), it is probably prudent to have delegators not "auto-rebond" to the validator. #### Proposal: infinite jail We propose setting the "jail time" for a validator who commits a consensus safety fault, to `infinite` (i.e. a tombstone state). This essentially kicks the validator out of the validator set and does not allow them to re-enter the validator set. All of their delegators (including the operator themselves) have to either unbond or redelegate away. The validator operator can create a new validator if they would like, with a new operator key and consensus key, but they have to "re-earn" their delegations back. Implementing the tombstone system and getting rid of the slashing period tracking will make the `slashing` module way simpler, especially because we can remove all of the hooks defined in the `slashing` module consumed by the `staking` module (the `slashing` module still consumes hooks defined in `staking`). #### Single slashing amount Another optimization that can be made is that if we assume that all ABCI faults for CometBFT consensus are slashed at the same level, we don't have to keep track of "max slash". Once an ABCI fault happens, we don't have to worry about comparing potential future ones to find the max. Currently the only CometBFT ABCI fault is: * Unjustified precommits (double signs) It is currently planned to include the following fault in the near future: * Signing a precommit when you're in unbonding phase (needed to make light client bisection safe) Given that these faults are both attributable byzantine faults, we will likely want to slash them equally, and thus we can enact the above change. > Note: This change may make sense for current CometBFT consensus, but maybe > not for a different consensus algorithm or future versions of CometBFT that > may want to punish at different levels (for example, partial slashing). ## Parameters The slashing module contains the following parameters: | Key | Type | Example | | ----------------------- | -------------- | ---------------------- | | SignedBlocksWindow | string (int64) | "100" | | MinSignedPerWindow | string (dec) | "0.500000000000000000" | | DowntimeJailDuration | string (ns) | "600000000000" | | SlashFractionDoubleSign | string (dec) | "0.050000000000000000" | | SlashFractionDowntime | string (dec) | "0.010000000000000000" | ## CLI A user can query and interact with the `slashing` module using the CLI. ### Query The `query` commands allow users to query `slashing` state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query slashing --help ``` #### params The `params` command allows users to query genesis parameters for the slashing module. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query slashing params [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query slashing params ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} downtime_jail_duration: 600s min_signed_per_window: "0.500000000000000000" signed_blocks_window: "100" slash_fraction_double_sign: "0.050000000000000000" slash_fraction_downtime: "0.010000000000000000" ``` #### signing-info The `signing-info` command allows users to query signing-info of the validator using consensus public key. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query slashing signing-infos [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query slashing signing-info '{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Auxs3865HpB/EfssYOzfqNhEJjzys6jD5B6tPgC8="}' ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} address: cosmosvalcons1nrqsld3aw6lh6t082frdqc84uwxn0t958c index_offset: "2068" jailed_until: "1970-01-01T00:00:00Z" missed_blocks_counter: "0" start_height: "0" tombstoned: false ``` #### signing-infos The `signing-infos` command allows users to query signing infos of all validators. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query slashing signing-infos [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query slashing signing-infos ``` Example Output: ```yml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} info: - address: cosmosvalcons1nrqsld3aw6lh6t082frdqc84uwxn0t958c index_offset: "2075" jailed_until: "1970-01-01T00:00:00Z" missed_blocks_counter: "0" start_height: "0" tombstoned: false pagination: next_key: null total: "0" ``` ### Transactions The `tx` commands allow users to interact with the `slashing` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx slashing --help ``` #### unjail The `unjail` command allows users to unjail a validator previously jailed for downtime. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx slashing unjail --from mykey [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx slashing unjail --from mykey ``` ### gRPC A user can query the `slashing` module using gRPC endpoints. #### Params The `Params` endpoint allows users to query the parameters of slashing module. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.slashing.v1beta1.Query/Params ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.slashing.v1beta1.Query/Params ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "signedBlocksWindow": "100", "minSignedPerWindow": "NTAwMDAwMDAwMDAwMDAwMDAw", "downtimeJailDuration": "600s", "slashFractionDoubleSign": "NTAwMDAwMDAwMDAwMDAwMDA=", "slashFractionDowntime": "MTAwMDAwMDAwMDAwMDAwMDA=" } } ``` #### SigningInfo The SigningInfo queries the signing info of given cons address. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.slashing.v1beta1.Query/SigningInfo ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"cons_address":"cosmosvalcons1nrqsld3aw6lh6t082frdqc84uwxn0t958c"}' localhost:9090 cosmos.slashing.v1beta1.Query/SigningInfo ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "valSigningInfo": { "address": "cosmosvalcons1nrqsld3aw6lh6t082frdqc84uwxn0t958c", "indexOffset": "3493", "jailedUntil": "1970-01-01T00:00:00Z" } } ``` #### SigningInfos The SigningInfos queries signing info of all validators. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.slashing.v1beta1.Query/SigningInfos ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.slashing.v1beta1.Query/SigningInfos ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "info": [ { "address": "cosmosvalcons1nrqslkwd3pz096lh6t082frdqc84uwxn0t958c", "indexOffset": "2467", "jailedUntil": "1970-01-01T00:00:00Z" } ], "pagination": { "total": "1" } } ``` ### REST A user can query the `slashing` module using REST endpoints. #### Params ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/slashing/v1beta1/params ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl "localhost:1317/cosmos/slashing/v1beta1/params" ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "signed_blocks_window": "100", "min_signed_per_window": "0.500000000000000000", "downtime_jail_duration": "600s", "slash_fraction_double_sign": "0.050000000000000000", "slash_fraction_downtime": "0.010000000000000000" } ``` #### signing\_info ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/slashing/v1beta1/signing_infos/%s ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl "localhost:1317/cosmos/slashing/v1beta1/signing_infos/cosmosvalcons1nrqslkwd3pz096lh6t082frdqc84uwxn0t958c" ``` Example Output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "val_signing_info": { "address": "cosmosvalcons1nrqslkwd3pz096lh6t082frdqc84uwxn0t958c", "start_height": "0", "index_offset": "4184", "jailed_until": "1970-01-01T00:00:00Z", "tombstoned": false, "missed_blocks_counter": "0" } } ``` #### signing\_infos ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/slashing/v1beta1/signing_infos ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl "localhost:1317/cosmos/slashing/v1beta1/signing_infos ``` Example Output: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "info": [ { "address": "cosmosvalcons1nrqslkwd3pz096lh6t082frdqc84uwxn0t958c", "start_height": "0", "index_offset": "4169", "jailed_until": "1970-01-01T00:00:00Z", "tombstoned": false, "missed_blocks_counter": "0" } ], "pagination": { "next_key": null, "total": "1" } } ``` # x/staking Source: https://docs.cosmos.network/sdk/latest/modules/staking/README This paper specifies the Staking module of the Cosmos SDK that was first described in the Cosmos Whitepaper in June 2016. ## Abstract This paper specifies the Staking module of the Cosmos SDK that was first described in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md) in June 2016. The module enables Cosmos SDK-based blockchain to support an advanced Proof-of-Stake (PoS) system. In this system, holders of the native staking token of the chain can become validators and can delegate tokens to validators, ultimately determining the effective validator set for the system. This module is used in the Cosmos Hub, the first Hub in the Cosmos network. ## Contents * [State](#state) * [Pool](#pool) * [LastTotalPower](#lasttotalpower) * [ValidatorUpdates](#validatorupdates) * [UnbondingID](#unbondingid) * [Params](#params) * [Validator](#validator) * [Delegation](#delegation) * [UnbondingDelegation](#unbondingdelegation) * [Redelegation](#redelegation) * [Queues](#queues) * [HistoricalInfo](#historicalinfo) * [State Transitions](#state-transitions) * [Validators](#validators) * [Delegations](#delegations) * [Slashing](#slashing) * [How Shares are calculated](#how-shares-are-calculated) * [Messages](#messages) * [MsgCreateValidator](#msgcreatevalidator) * [MsgEditValidator](#msgeditvalidator) * [MsgDelegate](#msgdelegate) * [MsgUndelegate](#msgundelegate) * [MsgCancelUnbondingDelegation](#msgcancelunbondingdelegation) * [MsgBeginRedelegate](#msgbeginredelegate) * [MsgUpdateParams](#msgupdateparams) * [MsgRotateConsPubKey](#msgrotateconspubkey) * [Begin-Block](#begin-block) * [Historical Info Tracking](#historical-info-tracking) * [End-Block](#end-block) * [Validator Set Changes](#validator-set-changes) * [Queues](#queues-1) * [Hooks](#hooks) * [Events](#events) * [EndBlocker](#endblocker) * [Msg's](#msgs) * [Parameters](#parameters) * [Client](#client) * [CLI](#cli) * [gRPC](#grpc) * [REST](#rest) ## State ### Pool Pool is used for tracking bonded and not-bonded token supply of the bond denomination. ### LastTotalPower LastTotalPower tracks the total amounts of bonded tokens recorded during the previous end block. Store entries prefixed with "Last" must remain unchanged until EndBlock. * LastTotalPower: `0x12 -> ProtocolBuffer(math.Int)` ### ValidatorUpdates ValidatorUpdates contains the validator updates returned to ABCI at the end of every block. The values are overwritten in every block. * ValidatorUpdates `0x61 -> []abci.ValidatorUpdate` ### UnbondingID UnbondingID stores the ID of the latest unbonding operation. It enables creating unique IDs for unbonding operations, i.e., UnbondingID is incremented every time a new unbonding operation (validator unbonding, unbonding delegation, redelegation) is initiated. * UnbondingID: `0x37 -> uint64` ### Params The staking module stores its params in state with the prefix of `0x51`, it can be updated with governance or the address with authority. * Params: `0x51 | ProtocolBuffer(Params)` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L298-L324 ``` ### Validator Validators can have one of three statuses * `Unbonded`: The validator is not in the active set. They cannot sign blocks and do not earn rewards. They can receive delegations. * `Bonded`: Once the validator receives sufficient bonded tokens they automatically join the active set during [`EndBlock`](#validator-set-changes) and their status is updated to `Bonded`. They are signing blocks and receiving rewards. They can receive further delegations. They can be slashed for misbehavior. Delegators to this validator who unbond their delegation must wait the duration of the UnbondingTime, a chain-specific param, during which time they are still slashable for offences of the source validator if those offences were committed during the period of time that the tokens were bonded. * `Unbonding`: When a validator leaves the active set, either by choice or due to slashing, jailing or tombstoning, an unbonding of all their delegations begins. All delegations must then wait the UnbondingTime before their tokens are moved to their accounts from the `BondedPool`. Tombstoning is permanent, once tombstoned a validator's consensus key can not be reused within the chain where the tombstoning happened. Validators objects should be primarily stored and accessed by the `OperatorAddr`, an SDK validator address for the operator of the validator. Two additional indices are maintained per validator object in order to fulfill required lookups for slashing and validator-set updates. A third special index (`LastValidatorPower`) is also maintained which however remains constant throughout each block, unlike the first two indices which mirror the validator records within a block. * Validators: `0x21 | OperatorAddrLen (1 byte) | OperatorAddr -> ProtocolBuffer(validator)` * ValidatorsByConsAddr: `0x22 | ConsAddrLen (1 byte) | ConsAddr -> OperatorAddr` * ValidatorsByPower: `0x23 | BigEndian(ConsensusPower) | OperatorAddrLen (1 byte) | OperatorAddr -> OperatorAddr` * LastValidatorsPower: `0x11 | OperatorAddrLen (1 byte) | OperatorAddr -> ProtocolBuffer(ConsensusPower)` * ValidatorsByUnbondingID: `0x38 | UnbondingID -> 0x21 | OperatorAddrLen (1 byte) | OperatorAddr` `Validators` is the primary index - it ensures that each operator can have only one associated validator, where the public key of that validator can change in the future. Delegators can refer to the immutable operator of the validator, without concern for the changing public key. `ValidatorsByUnbondingID` is an additional index that enables lookups for validators by the unbonding IDs corresponding to their current unbonding. `ValidatorByConsAddr` is an additional index that enables lookups for slashing. When CometBFT reports evidence, it provides the validator address, so this map is needed to find the operator. Note that the `ConsAddr` corresponds to the address which can be derived from the validator's `ConsPubKey`. `ValidatorsByPower` is an additional index that provides a sorted list of potential validators to quickly determine the current active set. Here ConsensusPower is validator.Tokens/10^6 by default. Note that all validators where `Jailed` is true are not stored within this index. `LastValidatorsPower` is a special index that provides a historical list of the last-block's bonded validators. This index remains constant during a block but is updated during the validator set update process which takes place in [`EndBlock`](#end-block). Each validator's state is stored in a `Validator` struct: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L82-L136 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L26-L80 ``` ### Delegation Delegations are identified by combining `DelegatorAddr` (the address of the delegator) with the `ValidatorAddr` Delegators are indexed in the store as follows: * Delegation: `0x31 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr -> ProtocolBuffer(delegation)` Stake holders may delegate coins to validators; under this circumstance their funds are held in a `Delegation` data structure. It is owned by one delegator, and is associated with the shares for one validator. The sender of the transaction is the owner of the bond. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L191-L208 ``` #### Delegator Shares When one delegates tokens to a Validator, they are issued a number of delegator shares based on a dynamic exchange rate, calculated as follows from the total number of tokens delegated to the validator and the number of shares issued so far: `Shares per Token = validator.TotalShares() / validator.Tokens()` Only the number of shares received is stored on the DelegationEntry. When a delegator then Undelegates, the token amount they receive is calculated from the number of shares they currently hold and the inverse exchange rate: `Tokens per Share = validator.Tokens() / validatorShares()` These `Shares` are simply an accounting mechanism. They are not a fungible asset. The reason for this mechanism is to simplify the accounting around slashing. Rather than iteratively slashing the tokens of every delegation entry, instead the Validator's total bonded tokens can be slashed, effectively reducing the value of each issued delegator share. ### UnbondingDelegation Shares in a `Delegation` can be unbonded, but they must for some time exist as an `UnbondingDelegation`, where shares can be reduced if Byzantine behavior is detected. `UnbondingDelegation` are indexed in the store as: * UnbondingDelegation: `0x32 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr -> ProtocolBuffer(unbondingDelegation)` * UnbondingDelegationsFromValidator: `0x33 | ValidatorAddrLen (1 byte) | ValidatorAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil` * UnbondingDelegationByUnbondingId: `0x38 | UnbondingId -> 0x32 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorAddr` `UnbondingDelegation` is used in queries, to lookup all unbonding delegations for a given delegator. `UnbondingDelegationsFromValidator` is used in slashing, to lookup all unbonding delegations associated with a given validator that need to be slashed. `UnbondingDelegationByUnbondingId` is an additional index that enables lookups for unbonding delegations by the unbonding IDs of the containing unbonding delegation entries. A UnbondingDelegation object is created every time an unbonding is initiated. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L210-L251 ``` ### Redelegation The bonded tokens worth of a `Delegation` may be instantly redelegated from a source validator to a different validator (destination validator). However when this occurs they must be tracked in a `Redelegation` object, whereby their shares can be slashed if their tokens have contributed to a Byzantine fault committed by the source validator. `Redelegation` are indexed in the store as: * Redelegations: `0x34 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddr -> ProtocolBuffer(redelegation)` * RedelegationsBySrc: `0x35 | ValidatorSrcAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddrLen (1 byte) | ValidatorDstAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil` * RedelegationsByDst: `0x36 | ValidatorDstAddrLen (1 byte) | ValidatorDstAddr | ValidatorSrcAddrLen (1 byte) | ValidatorSrcAddr | DelegatorAddrLen (1 byte) | DelegatorAddr -> nil` * RedelegationByUnbondingId: `0x38 | UnbondingId -> 0x34 | DelegatorAddrLen (1 byte) | DelegatorAddr | ValidatorAddrLen (1 byte) | ValidatorSrcAddr | ValidatorDstAddr` `Redelegations` is used for queries, to lookup all redelegations for a given delegator. `RedelegationsBySrc` is used for slashing based on the `ValidatorSrcAddr`. `RedelegationsByDst` is used for slashing based on the `ValidatorDstAddr` The first map here is used for queries, to lookup all redelegations for a given delegator. The second map is used for slashing based on the `ValidatorSrcAddr`, while the third map is for slashing based on the `ValidatorDstAddr`. `RedelegationByUnbondingId` is an additional index that enables lookups for redelegations by the unbonding IDs of the containing redelegation entries. A redelegation object is created every time a redelegation occurs. To prevent "redelegation hopping" redelegations may not occur under the situation that: * the (re)delegator already has another immature redelegation in progress with a destination to a validator (let's call it `Validator X`) * and, the (re)delegator is attempting to create a *new* redelegation where the source validator for this new redelegation is `Validator X`. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L253-L296 ``` ### Queues All queue objects are sorted by timestamp. The time used within any queue is firstly converted to UTC, rounded to the nearest nanosecond then sorted. The sortable time format used is a slight modification of the RFC3339Nano and uses the format string `"2006-01-02T15:04:05.000000000"`. Notably this format: * right pads all zeros * drops the time zone info (we already use UTC) In all cases, the stored timestamp represents the maturation time of the queue element. #### UnbondingDelegationQueue For the purpose of tracking progress of unbonding delegations the unbonding delegations queue is kept. * UnbondingDelegation: `0x41 | format(time) -> []DVPair` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L157-L166 ``` #### RedelegationQueue For the purpose of tracking progress of redelegations the redelegation queue is kept. * RedelegationQueue: `0x42 | format(time) -> []DVVTriplet` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/staking.proto#L173-L184 ``` #### ValidatorQueue For the purpose of tracking progress of unbonding validators the validator queue is kept. * ValidatorQueueTime: `0x43 | format(time) -> []sdk.ValAddress` The stored object by each key is an array of validator operator addresses from which the validator object can be accessed. Typically it is expected that only a single validator record will be associated with a given timestamp however it is possible that multiple validators exist in the queue at the same location. ### HistoricalInfo HistoricalInfo objects are stored and pruned at each block such that the staking keeper persists the `n` most recent historical info defined by staking module parameter: `HistoricalEntries`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} syntax = "proto3"; package cosmos.staking.v1beta1; import "gogoproto/gogo.proto"; import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "cosmos_proto/cosmos.proto"; import "cosmos/base/v1beta1/coin.proto"; import "amino/amino.proto"; import "tendermint/types/types.proto"; import "tendermint/abci/types.proto"; option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types"; // HistoricalInfo contains header and validator information for a given block. // It is stored as part of staking module's state, which persists the `n` most // recent HistoricalInfo // (`n` is set by the staking module's `historical_entries` parameter). message HistoricalInfo { tendermint.types.Header header = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; repeated Validator valset = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; } // CommissionRates defines the initial commission rates to be used for creating // a validator. message CommissionRates { option (gogoproto.equal) = true; option (gogoproto.goproto_stringer) = false; // rate is the commission rate charged to delegators, as a fraction. string rate = 1 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // max_rate defines the maximum commission rate which validator can ever charge, as a fraction. string max_rate = 2 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // max_change_rate defines the maximum daily increase of the validator commission, as a fraction. string max_change_rate = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; } // Commission defines commission parameters for a given validator. message Commission { option (gogoproto.equal) = true; option (gogoproto.goproto_stringer) = false; // commission_rates defines the initial commission rates to be used for creating a validator. CommissionRates commission_rates = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false, (amino.dont_omitempty) = true]; // update_time is the last time the commission rate was changed. google.protobuf.Timestamp update_time = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdtime) = true]; } // Description defines a validator description. message Description { option (gogoproto.equal) = true; option (gogoproto.goproto_stringer) = false; // moniker defines a human-readable name for the validator. string moniker = 1; // identity defines an optional identity signature (ex. UPort or Keybase). string identity = 2; // website defines an optional website link. string website = 3; // security_contact defines an optional email for security contact. string security_contact = 4; // details define other optional details. string details = 5; } // Validator defines a validator, together with the total amount of the // Validator's bond shares and their exchange rate to coins. Slashing results in // a decrease in the exchange rate, allowing correct calculation of future // undelegations without iterating over delegators. When coins are delegated to // this validator, the validator is credited with a delegation whose number of // bond shares is based on the amount of coins delegated divided by the current // exchange rate. Voting power can be calculated as total bonded shares // multiplied by exchange rate. message Validator { option (gogoproto.equal) = false; option (gogoproto.goproto_stringer) = false; option (gogoproto.goproto_getters) = false; // operator_address defines the address of the validator's operator; bech encoded in JSON. string operator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // consensus_pubkey is the consensus public key of the validator, as a Protobuf Any. google.protobuf.Any consensus_pubkey = 2 [(cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey"]; // jailed defined whether the validator has been jailed from bonded status or not. bool jailed = 3; // status is the validator status (bonded/unbonding/unbonded). BondStatus status = 4; // tokens define the delegated tokens (incl. self-delegation). string tokens = 5 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; // delegator_shares defines total shares issued to a validator's delegators. string delegator_shares = 6 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // description defines the description terms for the validator. Description description = 7 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; // unbonding_height defines, if unbonding, the height at which this validator has begun unbonding. int64 unbonding_height = 8; // unbonding_time defines, if unbonding, the min time for the validator to complete unbonding. google.protobuf.Timestamp unbonding_time = 9 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdtime) = true]; // commission defines the commission parameters. Commission commission = 10 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; // min_self_delegation is the validator's self declared minimum self delegation. // // Since: cosmos-sdk 0.46 string min_self_delegation = 11 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; // strictly positive if this validator's unbonding has been stopped by external modules int64 unbonding_on_hold_ref_count = 12; // list of unbonding ids, each uniquely identifing an unbonding of this validator repeated uint64 unbonding_ids = 13; } // BondStatus is the status of a validator. enum BondStatus { option (gogoproto.goproto_enum_prefix) = false; // UNSPECIFIED defines an invalid validator status. BOND_STATUS_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "Unspecified"]; // UNBONDED defines a validator that is not bonded. BOND_STATUS_UNBONDED = 1 [(gogoproto.enumvalue_customname) = "Unbonded"]; // UNBONDING defines a validator that is unbonding. BOND_STATUS_UNBONDING = 2 [(gogoproto.enumvalue_customname) = "Unbonding"]; // BONDED defines a validator that is bonded. BOND_STATUS_BONDED = 3 [(gogoproto.enumvalue_customname) = "Bonded"]; } // ValAddresses defines a repeated set of validator addresses. message ValAddresses { option (gogoproto.goproto_stringer) = false; option (gogoproto.stringer) = true; repeated string addresses = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } // DVPair is struct that just has a delegator-validator pair with no other data. // It is intended to be used as a marshalable pointer. For example, a DVPair can // be used to construct the key to getting an UnbondingDelegation from state. message DVPair { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; option (gogoproto.goproto_stringer) = false; string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } // DVPairs defines an array of DVPair objects. message DVPairs { repeated DVPair pairs = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; } // DVVTriplet is struct that just has a delegator-validator-validator triplet // with no other data. It is intended to be used as a marshalable pointer. For // example, a DVVTriplet can be used to construct the key to getting a // Redelegation from state. message DVVTriplet { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; option (gogoproto.goproto_stringer) = false; string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; string validator_src_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; string validator_dst_address = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } // DVVTriplets defines an array of DVVTriplet objects. message DVVTriplets { repeated DVVTriplet triplets = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; } // Delegation represents the bond with tokens held by an account. It is // owned by one delegator, and is associated with the voting power of one // validator. message Delegation { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; option (gogoproto.goproto_stringer) = false; // delegator_address is the bech32-encoded address of the delegator. string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // validator_address is the bech32-encoded address of the validator. string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // shares define the delegation shares received. string shares = 3 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; } // UnbondingDelegation stores all of a single delegator's unbonding bonds // for a single validator in an time-ordered list. message UnbondingDelegation { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; option (gogoproto.goproto_stringer) = false; // delegator_address is the bech32-encoded address of the delegator. string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // validator_address is the bech32-encoded address of the validator. string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // entries are the unbonding delegation entries. repeated UnbondingDelegationEntry entries = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; // unbonding delegation entries } // UnbondingDelegationEntry defines an unbonding object with relevant metadata. message UnbondingDelegationEntry { option (gogoproto.equal) = true; option (gogoproto.goproto_stringer) = false; // creation_height is the height which the unbonding took place. int64 creation_height = 1; // completion_time is the unix time for unbonding completion. google.protobuf.Timestamp completion_time = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdtime) = true]; // initial_balance defines the tokens initially scheduled to receive at completion. string initial_balance = 3 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; // balance defines the tokens to receive at completion. string balance = 4 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; // Incrementing id that uniquely identifies this entry uint64 unbonding_id = 5; // Strictly positive if this entry's unbonding has been stopped by external modules int64 unbonding_on_hold_ref_count = 6; } // RedelegationEntry defines a redelegation object with relevant metadata. message RedelegationEntry { option (gogoproto.equal) = true; option (gogoproto.goproto_stringer) = false; // creation_height defines the height which the redelegation took place. int64 creation_height = 1; // completion_time defines the unix time for redelegation completion. google.protobuf.Timestamp completion_time = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdtime) = true]; // initial_balance defines the initial balance when redelegation started. string initial_balance = 3 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; // shares_dst is the amount of destination-validator shares created by redelegation. string shares_dst = 4 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // Incrementing id that uniquely identifies this entry uint64 unbonding_id = 5; // Strictly positive if this entry's unbonding has been stopped by external modules int64 unbonding_on_hold_ref_count = 6; } // Redelegation contains the list of a particular delegator's redelegating bonds // from a particular source validator to a particular destination validator. message Redelegation { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; option (gogoproto.goproto_stringer) = false; // delegator_address is the bech32-encoded address of the delegator. string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // validator_src_address is the validator redelegation source operator address. string validator_src_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // validator_dst_address is the validator redelegation destination operator address. string validator_dst_address = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // entries are the redelegation entries. repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; // redelegation entries } // Params defines the parameters for the x/staking module. message Params { option (amino.name) = "cosmos-sdk/x/staking/Params"; option (gogoproto.equal) = true; option (gogoproto.goproto_stringer) = false; // unbonding_time is the time duration of unbonding. google.protobuf.Duration unbonding_time = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true]; // max_validators is the maximum number of validators. uint32 max_validators = 2; // max_entries is the max entries for either unbonding delegation or redelegation (per pair/trio). uint32 max_entries = 3; // historical_entries is the number of historical entries to persist. uint32 historical_entries = 4; // bond_denom defines the bondable coin denomination. string bond_denom = 5; // min_commission_rate is the chain-wide minimum commission rate that a validator can charge their delegators string min_commission_rate = 6 [ (gogoproto.moretags) = "yaml:\"min_commission_rate\"", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; // key_rotation_fee is the fee charged when rotating a validator's consensus key. cosmos.base.v1beta1.Coin key_rotation_fee = 7 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; } // DelegationResponse is equivalent to Delegation except that it contains a // balance in addition to shares which is more suitable for client responses. message DelegationResponse { option (gogoproto.equal) = false; option (gogoproto.goproto_stringer) = false; Delegation delegation = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; cosmos.base.v1beta1.Coin balance = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; } // RedelegationEntryResponse is equivalent to a RedelegationEntry except that it // contains a balance in addition to shares which is more suitable for client // responses. message RedelegationEntryResponse { option (gogoproto.equal) = true; RedelegationEntry redelegation_entry = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; string balance = 4 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false ]; } // RedelegationResponse is equivalent to a Redelegation except that its entries // contain a balance in addition to shares which is more suitable for client // responses. message RedelegationResponse { option (gogoproto.equal) = false; Redelegation redelegation = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; repeated RedelegationEntryResponse entries = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; } // Pool is used for tracking bonded and not-bonded token supply of the bond // denomination. message Pool { option (gogoproto.description) = true; option (gogoproto.equal) = true; string not_bonded_tokens = 1 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false, (gogoproto.jsontag) = "not_bonded_tokens", (amino.dont_omitempty) = true ]; string bonded_tokens = 2 [ (cosmos_proto.scalar) = "cosmos.Int", (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false, (gogoproto.jsontag) = "bonded_tokens", (amino.dont_omitempty) = true ]; } // Infraction indicates the infraction a validator committed. enum Infraction { // UNSPECIFIED defines an empty infraction. INFRACTION_UNSPECIFIED = 0; // DOUBLE_SIGN defines a validator that double-signs a block. INFRACTION_DOUBLE_SIGN = 1; // DOWNTIME defines a validator that missed signing too many blocks. INFRACTION_DOWNTIME = 2; } // ValidatorUpdates defines an array of abci.ValidatorUpdate objects. // TODO: explore moving this to proto/cosmos/base to separate modules from tendermint dependence message ValidatorUpdates { repeated tendermint.abci.ValidatorUpdate updates = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; } ``` At each BeginBlock, the staking keeper will persist the current Header and the Validators that committed the current block in a `HistoricalInfo` object. The Validators are sorted on their address to ensure that they are in a deterministic order. The oldest HistoricalEntries will be pruned to ensure that there only exist the parameter-defined number of historical entries. ### Consensus key rotation Pending and historical consensus key rotations are tracked in five stores: * ConsKeyRotationQueue: `0x91 | format(maturityTime) | ValAddress` - rotations awaiting the end of their unbonding period, at which point the re-rotation rate limit is retired and the entry is pruned * ValidatorConsKeyRotation: `0x92 | ValAddress` - a marker that the validator has rotated within the current unbonding period, enforcing the one-rotation-per-unbonding-period limit. The value is empty, and the entry is removed when the maturity queue retires it * RotationLockedConsAddrIndex: `0x93 | ConsAddress` - consensus addresses a rotation has claimed, valued with a lock kind and the validator's operator address. A rotated-away address stays locked until equivocation evidence for it can no longer be admitted, and resolves back to the validator for slashing. A pending rotation's target address is reserved so no other validator can claim it, and that entry is released once the rotation applies in the end blocker * ConsKeyRotationApplyQueue: `0x94 | BigEndian(applyHeight) | ValAddress` - height-keyed queue of rotations, valued with the new consensus public key, applied two heights after the rotation message * ConsKeyEvidenceExpiryQueue: `0x95 | format(evidenceExpiryTime) | ConsAddress` - queue that retires an old address's lock once equivocation evidence for the rotated-away key can no longer be admitted, using the evidence time and block-height windows captured at rotation time ## State Transitions ### Validators State transitions in validators are performed on every [`EndBlock`](#validator-set-changes) in order to check for changes in the active `ValidatorSet`. A validator can be `Unbonded`, `Unbonding` or `Bonded`. `Unbonded` and `Unbonding` are collectively called `Not Bonded`. A validator can move directly between all the states, except for from `Bonded` to `Unbonded`. #### Not bonded to Bonded The following transition occurs when a validator's ranking in the `ValidatorPowerIndex` surpasses that of the `LastValidator`. * set `validator.Status` to `Bonded` * send the `validator.Tokens` from the `NotBondedTokens` to the `BondedPool` `ModuleAccount` * delete the existing record from `ValidatorByPowerIndex` * add a new updated record to the `ValidatorByPowerIndex` * update the `Validator` object for this validator * if it exists, delete any `ValidatorQueue` record for this validator #### Bonded to Unbonding When a validator begins the unbonding process the following operations occur: * send the `validator.Tokens` from the `BondedPool` to the `NotBondedTokens` `ModuleAccount` * set `validator.Status` to `Unbonding` * delete the existing record from `ValidatorByPowerIndex` * add a new updated record to the `ValidatorByPowerIndex` * update the `Validator` object for this validator * insert a new record into the `ValidatorQueue` for this validator #### Unbonding to Unbonded A validator moves from unbonding to unbonded when the `ValidatorQueue` object moves from bonded to unbonded * update the `Validator` object for this validator * set `validator.Status` to `Unbonded` #### Jail/Unjail when a validator is jailed it is effectively removed from the CometBFT set. this process may be also be reversed. the following operations occur: * set `Validator.Jailed` and update object * if jailed delete record from `ValidatorByPowerIndex` * if unjailed add record to `ValidatorByPowerIndex` Jailed validators are not present in any of the following stores: * the power store (from consensus power to address) ### Delegations #### Delegate When a delegation occurs both the validator and the delegation objects are affected * determine the delegators shares based on tokens delegated and the validator's exchange rate * remove tokens from the sending account * add shares the delegation object or add them to a created validator object * add new delegator shares and update the `Validator` object * transfer the `delegation.Amount` from the delegator's account to the `BondedPool` or the `NotBondedPool` `ModuleAccount` depending if the `validator.Status` is `Bonded` or not * delete the existing record from `ValidatorByPowerIndex` * add an new updated record to the `ValidatorByPowerIndex` #### Begin Unbonding As a part of the Undelegate and Complete Unbonding state transitions Unbond Delegation may be called. * subtract the unbonded shares from delegator * add the unbonded tokens to an `UnbondingDelegationEntry` * update the delegation or remove the delegation if there are no more shares * if the delegation is the operator of the validator and no more shares exist then trigger a jail validator * update the validator with removed the delegator shares and associated coins * if the validator state is `Bonded`, transfer the `Coins` worth of the unbonded shares from the `BondedPool` to the `NotBondedPool` `ModuleAccount` * remove the validator if it is unbonded and there are no more delegation shares. * remove the validator if it is unbonded and there are no more delegation shares * get a unique `unbondingId` and map it to the `UnbondingDelegationEntry` in `UnbondingDelegationByUnbondingId` * call the `AfterUnbondingInitiated(unbondingId)` hook * add the unbonding delegation to `UnbondingDelegationQueue` with the completion time set to `UnbondingTime` #### Cancel an `UnbondingDelegation` Entry When a `cancel unbond delegation` occurs both the `validator`, the `delegation` and an `UnbondingDelegationQueue` state will be updated. * if cancel unbonding delegation amount equals to the `UnbondingDelegation` entry `balance`, then the `UnbondingDelegation` entry deleted from `UnbondingDelegationQueue`. * if the `cancel unbonding delegation amount is less than the `UnbondingDelegation`entry balance, then the`UnbondingDelegation`entry will be updated with new balance in the`UnbondingDelegationQueue\`. * cancel `amount` is [Delegated](#delegations) back to the original `validator`. #### Complete Unbonding For undelegations which do not complete immediately, the following operations occur when the unbonding delegation queue element matures: * remove the entry from the `UnbondingDelegation` object * transfer the tokens from the `NotBondedPool` `ModuleAccount` to the delegator `Account` #### Begin Redelegation Redelegations affect the delegation, source and destination validators. * perform an `unbond` delegation from the source validator to retrieve the tokens worth of the unbonded shares * using the unbonded tokens, `Delegate` them to the destination validator * if the `sourceValidator.Status` is `Bonded`, and the `destinationValidator` is not, transfer the newly delegated tokens from the `BondedPool` to the `NotBondedPool` `ModuleAccount` * otherwise, if the `sourceValidator.Status` is not `Bonded`, and the `destinationValidator` is `Bonded`, transfer the newly delegated tokens from the `NotBondedPool` to the `BondedPool` `ModuleAccount` * record the token amount in an new entry in the relevant `Redelegation` From when a redelegation begins until it completes, the delegator is in a state of "pseudo-unbonding", and can still be slashed for infractions that occurred before the redelegation began. #### Complete Redelegation When a redelegations complete the following occurs: * remove the entry from the `Redelegation` object ### Slashing #### Slash Validator When a Validator is slashed, the following occurs: * The total `slashAmount` is calculated as the `slashFactor` (a chain parameter) \* `TokensFromConsensusPower`, the total number of tokens bonded to the validator at the time of the infraction. * Every unbonding delegation and pseudo-unbonding redelegation such that the infraction occurred before the unbonding or redelegation began from the validator are slashed by the `slashFactor` percentage of the initialBalance. * Each amount slashed from redelegations and unbonding delegations is subtracted from the total slash amount. * The `remaingSlashAmount` is then slashed from the validator's tokens in the `BondedPool` or `NonBondedPool` depending on the validator's status. This reduces the total supply of tokens. In the case of a slash due to any infraction that requires evidence to submitted (for example double-sign), the slash occurs at the block where the evidence is included, not at the block where the infraction occurred. Put otherwise, validators are not slashed retroactively, only when they are caught. #### Slash Unbonding Delegation When a validator is slashed, so are those unbonding delegations from the validator that began unbonding after the time of the infraction. Every entry in every unbonding delegation from the validator is slashed by `slashFactor`. The amount slashed is calculated from the `InitialBalance` of the delegation and is capped to prevent a resulting negative balance. Completed (or mature) unbondings are not slashed. #### Slash Redelegation When a validator is slashed, so are all redelegations from the validator that began after the infraction. Redelegations are slashed by `slashFactor`. Redelegations that began before the infraction are not slashed. The amount slashed is calculated from the `InitialBalance` of the delegation and is capped to prevent a resulting negative balance. Mature redelegations (that have completed pseudo-unbonding) are not slashed. ### How Shares are calculated At any given point in time, each validator has a number of tokens, `T`, and has a number of shares issued, `S`. Each delegator, `i`, holds a number of shares, `S_i`. The number of tokens is the sum of all tokens delegated to the validator, plus the rewards, minus the slashes. The delegator is entitled to a portion of the underlying tokens proportional to their proportion of shares. So delegator `i` is entitled to `T * S_i / S` of the validator's tokens. When a delegator delegates new tokens to the validator, they receive a number of shares proportional to their contribution. So when delegator `j` delegates `T_j` tokens, they receive `S_j = S * T_j / T` shares. The total number of tokens is now `T + T_j`, and the total number of shares is `S + S_j`. `j`s proportion of the shares is the same as their proportion of the total tokens contributed: `(S + S_j) / S = (T + T_j) / T`. A special case is the initial delegation, when `T = 0` and `S = 0`, so `T_j / T` is undefined. For the initial delegation, delegator `j` who delegates `T_j` tokens receive `S_j = T_j` shares. So a validator that hasn't received any rewards and has not been slashed will have `T = S`. ## Messages In this section we describe the processing of the staking messages and the corresponding updates to the state. All created/modified state objects specified by each message are defined within the [state](#state) section. ### MsgCreateValidator A validator is created using the `MsgCreateValidator` message. The validator must be created with an initial delegation from the operator. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L20-L21 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L55-L78 ``` This message is expected to fail if: * another validator with this operator address is already registered * another validator with this pubkey is already registered * the initial self-delegation tokens are of a denom not specified as the bonding denom * the commission parameters are faulty, namely: * `MaxRate` is either > 1 or \< 0 * the initial `Rate` is either negative or > `MaxRate` * the initial `MaxChangeRate` is either negative or > `MaxRate` * the description fields are too large This message creates and stores the `Validator` object at appropriate indexes. Additionally a self-delegation is made with the initial tokens delegation tokens `Delegation`. The validator always starts as unbonded but may be bonded in the first end-block. ### MsgEditValidator The `Description`, `CommissionRate` of a validator can be updated using the `MsgEditValidator` message. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L23-L24 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L83-L102 ``` This message is expected to fail if: * the initial `CommissionRate` is either negative or > `MaxRate` * the `CommissionRate` has already been updated within the previous 24 hours * the `CommissionRate` is > `MaxChangeRate` * the description fields are too large This message stores the updated `Validator` object. ### MsgDelegate Within this message the delegator provides coins, and in return receives some amount of their validator's (newly created) delegator-shares that are assigned to `Delegation.Shares`. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L26-L28 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L107-L119 ``` This message is expected to fail if: * the validator does not exist * the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom` * the exchange rate is invalid, meaning the validator has no tokens (due to slashing) but there are outstanding shares * the amount delegated is less than the minimum allowed delegation If an existing `Delegation` object for provided addresses does not already exist then it is created as part of this message otherwise the existing `Delegation` is updated to include the newly received shares. The delegator receives newly minted shares at the current exchange rate. The exchange rate is the number of existing shares in the validator divided by the number of currently delegated tokens. The validator is updated in the `ValidatorByPower` index, and the delegation is tracked in validator object in the `Validators` index. It is possible to delegate to a jailed validator, the only difference being it will not be added to the power index until it is unjailed. ![Delegation sequence](https://raw.githubusercontent.com/cosmos/cosmos-sdk/release/v0.46.x/docs/uml/svg/delegation_sequence.svg) ### MsgUndelegate The `MsgUndelegate` message allows delegators to undelegate their tokens from validator. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L34-L36 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L145-L157 ``` This message returns a response containing the completion time of the undelegation: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L159-L167 ``` This message is expected to fail if: * the delegation doesn't exist * the validator doesn't exist * the delegation has less shares than the ones worth of `Amount` * existing `UnbondingDelegation` has maximum entries as defined by `params.MaxEntries` * the `Amount` has a denomination different than one defined by `params.BondDenom` When this message is processed the following actions occur: * validator's `DelegatorShares` and the delegation's `Shares` are both reduced by the message `SharesAmount` * calculate the token worth of the shares remove that amount tokens held within the validator * with those removed tokens, if the validator is: * `Bonded` - add them to an entry in `UnbondingDelegation` (create `UnbondingDelegation` if it doesn't exist) with a completion time a full unbonding period from the current time. Update pool shares to reduce BondedTokens and increase NotBondedTokens by token worth of the shares. * `Unbonding` - add them to an entry in `UnbondingDelegation` (create `UnbondingDelegation` if it doesn't exist) with the same completion time as the validator (`UnbondingMinTime`). * `Unbonded` - then send the coins the message `DelegatorAddr` * if there are no more `Shares` in the delegation, then the delegation object is removed from the store * under this situation if the delegation is the validator's self-delegation then also jail the validator. ![Unbond sequence](https://raw.githubusercontent.com/cosmos/cosmos-sdk/release/v0.46.x/docs/uml/svg/unbond_sequence.svg) ### MsgCancelUnbondingDelegation The `MsgCancelUnbondingDelegation` message allows delegators to cancel the `unbondingDelegation` entry and delegate back to a previous validator. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L38-L42 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L169-L183 ``` This message is expected to fail if: * the `unbondingDelegation` entry is already processed. * the `cancel unbonding delegation` amount is greater than the `unbondingDelegation` entry balance. * the `cancel unbonding delegation` height doesn't exist in the `unbondingDelegationQueue` of the delegator. When this message is processed the following actions occur: * if the `unbondingDelegation` Entry balance is zero * in this condition `unbondingDelegation` entry will be removed from `unbondingDelegationQueue`. * otherwise `unbondingDelegationQueue` will be updated with new `unbondingDelegation` entry balance and initial balance * the validator's `DelegatorShares` and the delegation's `Shares` are both increased by the message `Amount`. ### MsgBeginRedelegate The redelegation command allows delegators to instantly switch validators. Once the unbonding period has passed, the redelegation is automatically completed in the EndBlocker. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L30-L32 ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L124-L137 ``` This message returns a response containing the completion time of the redelegation: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L139-L143 ``` This message is expected to fail if: * the delegation doesn't exist * the source or destination validators don't exist * the delegation has less shares than the ones worth of `Amount` * the source validator has a receiving redelegation which is not matured (aka. the redelegation may be transitive) * existing `Redelegation` has maximum entries as defined by `params.MaxEntries` * the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom` When this message is processed the following actions occur: * the source validator's `DelegatorShares` and the delegations `Shares` are both reduced by the message `SharesAmount` * calculate the token worth of the shares remove that amount tokens held within the source validator. * if the source validator is: * `Bonded` - add an entry to the `Redelegation` (create `Redelegation` if it doesn't exist) with a completion time a full unbonding period from the current time. Update pool shares to reduce BondedTokens and increase NotBondedTokens by token worth of the shares (this may be effectively reversed in the next step however). * `Unbonding` - add an entry to the `Redelegation` (create `Redelegation` if it doesn't exist) with the same completion time as the validator (`UnbondingMinTime`). * `Unbonded` - no action required in this step * Delegate the token worth to the destination validator, possibly moving tokens back to the bonded state. * if there are no more `Shares` in the source delegation, then the source delegation object is removed from the store * under this situation if the delegation is the validator's self-delegation then also jail the validator. ![Begin redelegation sequence](https://raw.githubusercontent.com/cosmos/cosmos-sdk/release/v0.46.x/docs/uml/svg/begin_redelegation_sequence.svg) ### MsgUpdateParams The `MsgUpdateParams` update the staking module parameters. The params are updated through a governance proposal where the signer is the gov module account address. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L190-L202 ``` The message handling can fail if: * signer is not the authority defined in the staking keeper (usually the gov module account). * the `bond_denom` in the updated params has zero supply in the bank module (i.e., the denom does not exist on-chain). ### MsgRotateConsPubKey The `MsgRotateConsPubKey` message replaces a validator's consensus public key in place. The message is signed by the validator's operator address and carries the new public key. Power, delegations, commission, and the operator address are unchanged. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/staking/v1beta1/tx.proto#L210-L220 ``` Handling the message burns the `KeyRotationFee` param from the operator account and enqueues the rotation. The new key enters the CometBFT validator set two heights after the message executes. The rotated-away consensus address is retained until equivocation evidence for it can no longer be admitted, which is at least the unbonding period and often longer, so evidence against the old key still slashes the validator. The message handling can fail if: * the fee cannot be deducted from the operator account. * the validator already rotated within the current unbonding period. * the new key's type is not in the chain's consensus params `validator.pub_key_types`. * the validator does not exist. * the validator is jailed. * the new key is already in use by another validator. * the new key is locked by a rotation, either because a validator rotated away from it and evidence against it can still be admitted, or because a pending rotation already targets it. For the operational procedure, see [Rotate a validator consensus key, x/staking](/sdk/latest/keys/rotate-validator-key). For the concepts, see [Key rotation](/sdk/latest/keys/key-rotation). ## Begin-Block Each abci begin block call, the historical info will get stored and pruned according to the `HistoricalEntries` parameter. ### Historical Info Tracking If the `HistoricalEntries` parameter is 0, then the `BeginBlock` performs a no-op. Otherwise, the latest historical info is stored under the key `historicalInfoKey|height`, while any entries older than `height - HistoricalEntries` is deleted. In most cases, this results in a single entry being pruned per block. However, if the parameter `HistoricalEntries` has changed to a lower value there will be multiple entries in the store that must be pruned. ## End-Block Each abci end block call, the operations to update queues and validator set changes are specified to execute. ### Validator Set Changes The staking validator set is updated during this process by state transitions that run at the end of every block. As a part of this process any updated validators are also returned back to CometBFT for inclusion in the CometBFT validator set which is responsible for validating CometBFT messages at the consensus layer. Operations are as following: * the new validator set is taken as the top `params.MaxValidators` number of validators retrieved from the `ValidatorsByPower` index * the previous validator set is compared with the new validator set: * missing validators begin unbonding and their `Tokens` are transferred from the `BondedPool` to the `NotBondedPool` `ModuleAccount` * new validators are instantly bonded and their `Tokens` are transferred from the `NotBondedPool` to the `BondedPool` `ModuleAccount` In all cases, any validators leaving or entering the bonded validator set or changing balances and staying within the bonded validator set incur an update message reporting their new consensus power which is passed back to CometBFT. The `LastTotalPower` and `LastValidatorsPower` hold the state of the total power and validator power from the end of the last block, and are used to check for changes that have occurred in `ValidatorsByPower` and the total new power, which is calculated during `EndBlock`. ### Queues Within staking, certain state-transitions are not instantaneous but take place over a duration of time (typically the unbonding period). When these transitions are mature certain operations must take place in order to complete the state operation. This is achieved through the use of queues which are checked/processed at the end of each block. #### Unbonding Validators When a validator is kicked out of the bonded validator set (either through being jailed, or not having sufficient bonded tokens) it begins the unbonding process along with all its delegations begin unbonding (while still being delegated to this validator). At this point the validator is said to be an "unbonding validator", whereby it will mature to become an "unbonded validator" after the unbonding period has passed. Each block the validator queue is to be checked for mature unbonding validators (namely with a completion time `<=` current time and completion height `<=` current block height). At this point any mature validators which do not have any delegations remaining are deleted from state. For all other mature unbonding validators that still have remaining delegations, the `validator.Status` is switched from `types.Unbonding` to `types.Unbonded`. Unbonding operations can be put on hold by external modules via the `PutUnbondingOnHold(unbondingId)` method. As a result, an unbonding operation (e.g., an unbonding delegation) that is on hold, cannot complete even if it reaches maturity. For an unbonding operation with `unbondingId` to eventually complete (after it reaches maturity), every call to `PutUnbondingOnHold(unbondingId)` must be matched by a call to `UnbondingCanComplete(unbondingId)`. #### Unbonding Delegations Complete the unbonding of all mature `UnbondingDelegations.Entries` within the `UnbondingDelegations` queue with the following procedure: * transfer the balance coins to the delegator's wallet address * remove the mature entry from `UnbondingDelegation.Entries` * remove the `UnbondingDelegation` object from the store if there are no remaining entries. #### Redelegations Complete the unbonding of all mature `Redelegation.Entries` within the `Redelegations` queue with the following procedure: * remove the mature entry from `Redelegation.Entries` * remove the `Redelegation` object from the store if there are no remaining entries. ### Consensus Key Rotations At the height a rotation message executes, the end blocker emits the validator update handing the validator's power from the old consensus key to the new one. Two heights later, when CometBFT makes the update effective, the apply queue swaps the stored consensus key. When a rotation's unbonding period ends, the maturity queue retires the re-rotation rate limit and the validator may rotate again. The old consensus address stays locked on its own, longer schedule, until equivocation evidence for it can no longer be admitted. ## Hooks Other modules may register operations to execute when a certain event has occurred within staking. These events can be registered to execute either right `Before` or `After` the staking event (as per the hook name). The following hooks can registered with staking: * `AfterValidatorCreated(Context, ValAddress) error` * called when a validator is created * `BeforeValidatorModified(Context, ValAddress) error` * called when a validator's state is changed * `AfterValidatorRemoved(Context, ConsAddress, ValAddress) error` * called when a validator is deleted * `AfterValidatorBonded(Context, ConsAddress, ValAddress) error` * called when a validator is bonded * `AfterValidatorBeginUnbonding(Context, ConsAddress, ValAddress) error` * called when a validator begins unbonding * `BeforeDelegationCreated(Context, AccAddress, ValAddress) error` * called when a delegation is created * `BeforeDelegationSharesModified(Context, AccAddress, ValAddress) error` * called when a delegation's shares are modified * `AfterDelegationModified(Context, AccAddress, ValAddress) error` * called when a delegation is created or modified * `BeforeDelegationRemoved(Context, AccAddress, ValAddress) error` * called when a delegation is removed * `AfterUnbondingInitiated(Context, UnbondingID)` * called when an unbonding operation (validator unbonding, unbonding delegation, redelegation) was initiated ## Events The staking module emits the following events: ### EndBlocker | Type | Attribute Key | Attribute Value | | ---------------------- | ---------------------- | --------------------------- | | complete\_unbonding | amount | `{totalUnbondingAmount}` | | complete\_unbonding | validator | `{validatorAddress}` | | complete\_unbonding | delegator | `{delegatorAddress}` | | complete\_redelegation | amount | `{totalRedelegationAmount}` | | complete\_redelegation | source\_validator | `{srcValidatorAddress}` | | complete\_redelegation | destination\_validator | `{dstValidatorAddress}` | | complete\_redelegation | delegator | `{delegatorAddress}` | ## Msg's ### MsgCreateValidator | Type | Attribute Key | Attribute Value | | ----------------- | ------------- | -------------------- | | create\_validator | validator | `{validatorAddress}` | | create\_validator | amount | `{delegationAmount}` | | message | module | staking | | message | action | create\_validator | | message | sender | `{senderAddress}` | ### MsgEditValidator | Type | Attribute Key | Attribute Value | | --------------- | --------------------- | --------------------- | | edit\_validator | commission\_rate | `{commissionRate}` | | edit\_validator | min\_self\_delegation | `{minSelfDelegation}` | | message | module | staking | | message | action | edit\_validator | | message | sender | `{senderAddress}` | ### MsgDelegate | Type | Attribute Key | Attribute Value | | -------- | ------------- | -------------------- | | delegate | validator | `{validatorAddress}` | | delegate | amount | `{delegationAmount}` | | message | module | staking | | message | action | delegate | | message | sender | `{senderAddress}` | ### MsgUndelegate | Type | Attribute Key | Attribute Value | | ------- | --------------------- | -------------------- | | unbond | validator | `{validatorAddress}` | | unbond | amount | `{unbondAmount}` | | unbond | completion\_time \[0] | `{completionTime}` | | message | module | staking | | message | action | begin\_unbonding | | message | sender | `{senderAddress}` | * \[0] Time is formatted in the RFC3339 standard ### MsgCancelUnbondingDelegation | Type | Attribute Key | Attribute Value | | ----------------------------- | ---------------- | ----------------------------------- | | cancel\_unbonding\_delegation | validator | `{validatorAddress}` | | cancel\_unbonding\_delegation | delegator | `{delegatorAddress}` | | cancel\_unbonding\_delegation | amount | `{cancelUnbondingDelegationAmount}` | | cancel\_unbonding\_delegation | creation\_height | `{unbondingCreationHeight}` | | message | module | staking | | message | action | cancel\_unbond | | message | sender | `{senderAddress}` | ### MsgBeginRedelegate | Type | Attribute Key | Attribute Value | | ---------- | ---------------------- | ----------------------- | | redelegate | source\_validator | `{srcValidatorAddress}` | | redelegate | destination\_validator | `{dstValidatorAddress}` | | redelegate | amount | `{unbondAmount}` | | redelegate | completion\_time \[0] | `{completionTime}` | | message | module | staking | | message | action | begin\_redelegate | | message | sender | `{senderAddress}` | * \[0] Time is formatted in the RFC3339 standard ## Parameters The staking module contains the following parameters: | Key | Type | Example | | ----------------- | ---------------- | -------------------------------------- | | UnbondingTime | string (time ns) | "259200000000000" | | MaxValidators | uint32 | 100 | | MaxEntries | uint32 | 7 | | HistoricalEntries | uint32 | 3 | | BondDenom | string | "stake" | | MinCommissionRate | string | "0.000000000000000000" | | KeyRotationFee | sdk.Coin | `{"denom":"stake","amount":"1000000"}` | The limit of one consensus key rotation per unbonding period is fixed and is not a parameter. ## Client ### CLI A user can query and interact with the `staking` module using the CLI. #### Query The `query` commands allows users to query `staking` state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking --help ``` ##### delegation The `delegation` command allows users to query delegations for an individual delegator on an individual validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking delegation [delegator-addr] [validator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking delegation cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} balance: amount: "10000000000" denom: stake delegation: delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p shares: "10000000000.000000000000000000" validator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` ##### delegations The `delegations` command allows users to query delegations for an individual delegator on all validators. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking delegations [delegator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking delegations cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} delegation_responses: - balance: amount: "10000000000" denom: stake delegation: delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p shares: "10000000000.000000000000000000" validator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj - balance: amount: "10000000000" denom: stake delegation: delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p shares: "10000000000.000000000000000000" validator_address: cosmosvaloper1x20lytyf6zkcrv5edpkfkn8sz578qg5sqfyqnp pagination: next_key: null total: "0" ``` ##### delegations-to The `delegations-to` command allows users to query delegations on an individual validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking delegations-to [validator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking delegations-to cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - balance: amount: "504000000" denom: stake delegation: delegator_address: cosmos1q2qwwynhv8kh3lu5fkeex4awau9x8fwt45f5cp shares: "504000000.000000000000000000" validator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj - balance: amount: "78125000000" denom: uixo delegation: delegator_address: cosmos1qvppl3479hw4clahe0kwdlfvf8uvjtcd99m2ca shares: "78125000000.000000000000000000" validator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj pagination: next_key: null total: "0" ``` ##### historical-info The `historical-info` command allows users to query historical information at given height. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking historical-info [height] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking historical-info 10 ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} header: app_hash: Lbx8cXpI868wz8sgp4qPYVrlaKjevR5WP/IjUxwp3oo= chain_id: testnet consensus_hash: BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8= data_hash: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= evidence_hash: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= height: "10" last_block_id: hash: RFbkpu6pWfSThXxKKl6EZVDnBSm16+U0l0xVjTX08Fk= part_set_header: hash: vpIvXD4rxD5GM4MXGz0Sad9I7//iVYLzZsEU4BVgWIU= total: 1 last_commit_hash: Ne4uXyx4QtNp4Zx89kf9UK7oG9QVbdB6e7ZwZkhy8K0= last_results_hash: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= next_validators_hash: nGBgKeWBjoxeKFti00CxHsnULORgKY4LiuQwBuUrhCs= proposer_address: mMEP2c2IRPLr99LedSRtBg9eONM= time: "2021-10-01T06:00:49.785790894Z" validators_hash: nGBgKeWBjoxeKFti00CxHsnULORgKY4LiuQwBuUrhCs= version: app: "0" block: "11" valset: - commission: commission_rates: max_change_rate: "0.010000000000000000" max_rate: "0.200000000000000000" rate: "0.100000000000000000" update_time: "2021-10-01T05:52:50.380144238Z" consensus_pubkey: '@type': /cosmos.crypto.ed25519.PubKey key: Auxs3865HpB/EfssYOzfqNhEJjzys2Fo6jD5B8tPgC8= delegator_shares: "10000000.000000000000000000" description: details: "" identity: "" moniker: myvalidator security_contact: "" website: "" jailed: false min_self_delegation: "1" operator_address: cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc status: BOND_STATUS_BONDED tokens: "10000000" unbonding_height: "0" unbonding_time: "1970-01-01T00:00:00Z" ``` ##### params The `params` command allows users to query values set as staking parameters. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking params [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking params ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} bond_denom: stake historical_entries: 10000 key_rotation_fee: amount: "1000000" denom: stake max_entries: 7 max_validators: 50 min_commission_rate: "0.000000000000000000" unbonding_time: 1814400s ``` ##### pool The `pool` command allows users to query values for amounts stored in the staking pool. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q staking pool [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q staking pool ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} bonded_tokens: "10000000" not_bonded_tokens: "0" ``` ##### redelegation The `redelegation` command allows users to query a redelegation record based on delegator and a source and destination validator address. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking redelegation [delegator-addr] [src-validator-addr] [dst-validator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking redelegation cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: null redelegation_responses: - entries: - balance: "50000000" redelegation_entry: completion_time: "2021-10-24T20:33:21.960084845Z" creation_height: 2.382847e+06 initial_balance: "50000000" shares_dst: "50000000.000000000000000000" - balance: "5000000000" redelegation_entry: completion_time: "2021-10-25T21:33:54.446846862Z" creation_height: 2.397271e+06 initial_balance: "5000000000" shares_dst: "5000000000.000000000000000000" redelegation: delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p entries: null validator_dst_address: cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm validator_src_address: cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm ``` ##### redelegations The `redelegations` command allows users to query all redelegation records for an individual delegator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking redelegations [delegator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking redelegation cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "0" redelegation_responses: - entries: - balance: "50000000" redelegation_entry: completion_time: "2021-10-24T20:33:21.960084845Z" creation_height: 2.382847e+06 initial_balance: "50000000" shares_dst: "50000000.000000000000000000" - balance: "5000000000" redelegation_entry: completion_time: "2021-10-25T21:33:54.446846862Z" creation_height: 2.397271e+06 initial_balance: "5000000000" shares_dst: "5000000000.000000000000000000" redelegation: delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p entries: null validator_dst_address: cosmosvaloper1uccl5ugxrm7vqlzwqr04pjd320d2fz0z3hc6vm validator_src_address: cosmosvaloper1zppjyal5emta5cquje8ndkpz0rs046m7zqxrpp - entries: - balance: "562770000000" redelegation_entry: completion_time: "2021-10-25T21:42:07.336911677Z" creation_height: 2.39735e+06 initial_balance: "562770000000" shares_dst: "562770000000.000000000000000000" redelegation: delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p entries: null validator_dst_address: cosmosvaloper1uccl5ugxrm7vqlzwqr04pjd320d2fz0z3hc6vm validator_src_address: cosmosvaloper1zppjyal5emta5cquje8ndkpz0rs046m7zqxrpp ``` ##### redelegations-from The `redelegations-from` command allows users to query delegations that are redelegating *from* a validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking redelegations-from [validator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking redelegations-from cosmosvaloper1y4rzzrgl66eyhzt6gse2k7ej3zgwmngeleucjy ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "0" redelegation_responses: - entries: - balance: "50000000" redelegation_entry: completion_time: "2021-10-24T20:33:21.960084845Z" creation_height: 2.382847e+06 initial_balance: "50000000" shares_dst: "50000000.000000000000000000" - balance: "5000000000" redelegation_entry: completion_time: "2021-10-25T21:33:54.446846862Z" creation_height: 2.397271e+06 initial_balance: "5000000000" shares_dst: "5000000000.000000000000000000" redelegation: delegator_address: cosmos1pm6e78p4pgn0da365plzl4t56pxy8hwtqp2mph entries: null validator_dst_address: cosmosvaloper1uccl5ugxrm7vqlzwqr04pjd320d2fz0z3hc6vm validator_src_address: cosmosvaloper1y4rzzrgl66eyhzt6gse2k7ej3zgwmngeleucjy - entries: - balance: "221000000" redelegation_entry: completion_time: "2021-10-05T21:05:45.669420544Z" creation_height: 2.120693e+06 initial_balance: "221000000" shares_dst: "221000000.000000000000000000" redelegation: delegator_address: cosmos1zqv8qxy2zgn4c58fz8jt8jmhs3d0attcussrf6 entries: null validator_dst_address: cosmosvaloper10mseqwnwtjaqfrwwp2nyrruwmjp6u5jhah4c3y validator_src_address: cosmosvaloper1y4rzzrgl66eyhzt6gse2k7ej3zgwmngeleucjy ``` ##### unbonding-delegation The `unbonding-delegation` command allows users to query unbonding delegations for an individual delegator on an individual validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking unbonding-delegation [delegator-addr] [validator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking unbonding-delegation cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p entries: - balance: "52000000" completion_time: "2021-11-02T11:35:55.391594709Z" creation_height: "55078" initial_balance: "52000000" validator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` ##### unbonding-delegations The `unbonding-delegations` command allows users to query all unbonding-delegations records for one delegator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking unbonding-delegations [delegator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking unbonding-delegations cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "0" unbonding_responses: - delegator_address: cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p entries: - balance: "52000000" completion_time: "2021-11-02T11:35:55.391594709Z" creation_height: "55078" initial_balance: "52000000" validator_address: cosmosvaloper1t8ehvswxjfn3ejzkjtntcyrqwvmvuknzmvtaaa ``` ##### unbonding-delegations-from The `unbonding-delegations-from` command allows users to query delegations that are unbonding *from* a validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking unbonding-delegations-from [validator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking unbonding-delegations-from cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: null total: "0" unbonding_responses: - delegator_address: cosmos1qqq9txnw4c77sdvzx0tkedsafl5s3vk7hn53fn entries: - balance: "150000000" completion_time: "2021-11-01T21:41:13.098141574Z" creation_height: "46823" initial_balance: "150000000" validator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj - delegator_address: cosmos1peteje73eklqau66mr7h7rmewmt2vt99y24f5z entries: - balance: "24000000" completion_time: "2021-10-31T02:57:18.192280361Z" creation_height: "21516" initial_balance: "24000000" validator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` ##### validator The `validator` command allows users to query details about an individual validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking validator [validator-addr] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking validator cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} commission: commission_rates: max_change_rate: "0.020000000000000000" max_rate: "0.200000000000000000" rate: "0.050000000000000000" update_time: "2021-10-01T19:24:52.663191049Z" consensus_pubkey: '@type': /cosmos.crypto.ed25519.PubKey key: sIiexdJdYWn27+7iUHQJDnkp63gq/rzUq1Y+fxoGjXc= delegator_shares: "32948270000.000000000000000000" description: details: Witval is the validator arm from Vitwit. Vitwit is into software consulting and services business since 2015. We are working closely with Cosmos ecosystem since 2018. We are also building tools for the ecosystem, Aneka is our explorer for the cosmos ecosystem. identity: 51468B615127273A moniker: Witval security_contact: "" website: "" jailed: false min_self_delegation: "1" operator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj status: BOND_STATUS_BONDED tokens: "32948270000" unbonding_height: "0" unbonding_time: "1970-01-01T00:00:00Z" ``` ##### validators The `validators` command allows users to query details about all validators on a network. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking validators [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query staking validators ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pagination: next_key: FPTi7TKAjN63QqZh+BaXn6gBmD5/ total: "0" validators: commission: commission_rates: max_change_rate: "0.020000000000000000" max_rate: "0.200000000000000000" rate: "0.050000000000000000" update_time: "2021-10-01T19:24:52.663191049Z" consensus_pubkey: '@type': /cosmos.crypto.ed25519.PubKey key: sIiexdJdYWn27+7iUHQJDnkp63gq/rzUq1Y+fxoGjXc= delegator_shares: "32948270000.000000000000000000" description: details: Witval is the validator arm from Vitwit. Vitwit is into software consulting and services business since 2015. We are working closely with Cosmos ecosystem since 2018. We are also building tools for the ecosystem, Aneka is our explorer for the cosmos ecosystem. identity: 51468B615127273A moniker: Witval security_contact: "" website: "" jailed: false min_self_delegation: "1" operator_address: cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj status: BOND_STATUS_BONDED tokens: "32948270000" unbonding_height: "0" unbonding_time: "1970-01-01T00:00:00Z" - commission: commission_rates: max_change_rate: "0.100000000000000000" max_rate: "0.200000000000000000" rate: "0.050000000000000000" update_time: "2021-10-04T18:02:21.446645619Z" consensus_pubkey: '@type': /cosmos.crypto.ed25519.PubKey key: GDNpuKDmCg9GnhnsiU4fCWktuGUemjNfvpCZiqoRIYA= delegator_shares: "559343421.000000000000000000" description: details: Noderunners is a professional validator in POS networks. We have a huge node running experience, reliable soft and hardware. Our commissions are always low, our support to delegators is always full. Stake with us and start receiving your Cosmos rewards now! identity: 812E82D12FEA3493 moniker: Noderunners security_contact: info@noderunners.biz website: http://noderunners.biz jailed: false min_self_delegation: "1" operator_address: cosmosvaloper1q5ku90atkhktze83j9xjaks2p7uruag5zp6wt7 status: BOND_STATUS_BONDED tokens: "559343421" unbonding_height: "0" unbonding_time: "1970-01-01T00:00:00Z" ``` #### Transactions The `tx` commands allows users to interact with the `staking` module. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking --help ``` ##### create-validator The command `create-validator` allows users to create new validator initialized with a self-delegation to it. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking create-validator [path/to/validator.json] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking create-validator /path/to/validator.json \ --chain-id="name_of_chain_id" \ --gas="auto" \ --gas-adjustment="1.2" \ --gas-prices="0.025stake" \ --from=mykey ``` where `validator.json` contains: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "BnbwFpeONLqvWqJb3qaUbL5aoIcW3fSuAp9nT3z5f20=" }, "amount": "1000000stake", "moniker": "my-moniker", "website": "https://myweb.site", "security": "security-contact@gmail.com", "details": "description of your validator", "commission-rate": "0.10", "commission-max-rate": "0.20", "commission-max-change-rate": "0.01", "min-self-delegation": "1" } ``` and pubkey can be obtained by using `simd tendermint show-validator` command. ##### delegate The command `delegate` allows users to delegate liquid tokens to a validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking delegate [validator-addr] [amount] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking delegate cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm 1000stake --from mykey ``` ##### edit-validator The command `edit-validator` allows users to edit an existing validator account. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking edit-validator [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking edit-validator --moniker "new_moniker_name" --website "new_webiste_url" --from mykey ``` ##### redelegate The command `redelegate` allows users to redelegate illiquid tokens from one validator to another. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking redelegate [src-validator-addr] [dst-validator-addr] [amount] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking redelegate cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm 100stake --from mykey ``` ##### unbond The command `unbond` allows users to unbond shares from a validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking unbond [validator-addr] [amount] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking unbond cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake --from mykey ``` ##### cancel unbond The command `cancel-unbond` allow users to cancel the unbonding delegation entry and delegate back to the original validator. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking cancel-unbond [validator-addr] [amount] [creation-height] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking cancel-unbond cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake 123123 --from mykey ``` ##### rotate-cons-pub-key The command `rotate-cons-pub-key` allows a validator operator to replace the validator's consensus public key. The new key is given as proto-JSON, and the validator address is derived from the `--from` signer. Handling the message burns the `key_rotation_fee` param. Usage: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking rotate-cons-pub-key [new-pubkey] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking rotate-cons-pub-key '{"@type":"/cosmos.crypto.ed25519.PubKey","key":"..."}' --from myvalidator ``` ### gRPC A user can query the `staking` module using gRPC endpoints. #### Validators The `Validators` endpoint queries all validators that match the given status. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/Validators ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.staking.v1beta1.Query/Validators ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validators": [ { "operatorAddress": "cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc", "consensusPubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"Auxs3865HpB/EfssYOzfqNhEJjzys2Fo6jD5B8tPgC8="}, "status": "BOND_STATUS_BONDED", "tokens": "10000000", "delegatorShares": "10000000000000000000000000", "description": { "moniker": "myvalidator" }, "unbondingTime": "1970-01-01T00:00:00Z", "commission": { "commissionRates": { "rate": "100000000000000000", "maxRate": "200000000000000000", "maxChangeRate": "10000000000000000" }, "updateTime": "2021-10-01T05:52:50.380144238Z" }, "minSelfDelegation": "1" } ], "pagination": { "total": "1" } } ``` #### Validator The `Validator` endpoint queries validator information for given validator address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/Validator ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_addr":"cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc"}' \ localhost:9090 cosmos.staking.v1beta1.Query/Validator ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validator": { "operatorAddress": "cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc", "consensusPubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"Auxs3865HpB/EfssYOzfqNhEJjzys2Fo6jD5B8tPgC8="}, "status": "BOND_STATUS_BONDED", "tokens": "10000000", "delegatorShares": "10000000000000000000000000", "description": { "moniker": "myvalidator" }, "unbondingTime": "1970-01-01T00:00:00Z", "commission": { "commissionRates": { "rate": "100000000000000000", "maxRate": "200000000000000000", "maxChangeRate": "10000000000000000" }, "updateTime": "2021-10-01T05:52:50.380144238Z" }, "minSelfDelegation": "1" } } ``` #### ValidatorDelegations The `ValidatorDelegations` endpoint queries delegate information for given validator. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/ValidatorDelegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_addr":"cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc"}' \ localhost:9090 cosmos.staking.v1beta1.Query/ValidatorDelegations ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "delegationResponses": [ { "delegation": { "delegatorAddress": "cosmos1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgy3ua5t", "validatorAddress": "cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc", "shares": "10000000000000000000000000" }, "balance": { "denom": "stake", "amount": "10000000" } } ], "pagination": { "total": "1" } } ``` #### ValidatorUnbondingDelegations The `ValidatorUnbondingDelegations` endpoint queries delegate information for given validator. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_addr":"cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc"}' \ localhost:9090 cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "unbonding_responses": [ { "delegator_address": "cosmos1z3pzzw84d6xn00pw9dy3yapqypfde7vg6965fy", "validator_address": "cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc", "entries": [ { "creation_height": "25325", "completion_time": "2021-10-31T09:24:36.797320636Z", "initial_balance": "20000000", "balance": "20000000" } ] }, { "delegator_address": "cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77", "validator_address": "cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc", "entries": [ { "creation_height": "13100", "completion_time": "2021-10-30T12:53:02.272266791Z", "initial_balance": "1000000", "balance": "1000000" } ] }, ], "pagination": { "next_key": null, "total": "8" } } ``` #### Delegation The `Delegation` endpoint queries delegate information for given validator delegator pair. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/Delegation ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_addr": "cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77", validator_addr":"cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc"}' \ localhost:9090 cosmos.staking.v1beta1.Query/Delegation ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "delegation_response": { "delegation": { "delegator_address":"cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77", "validator_address":"cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc", "shares":"25083119936.000000000000000000" }, "balance": { "denom":"stake", "amount":"25083119936" } } } ``` #### UnbondingDelegation The `UnbondingDelegation` endpoint queries unbonding information for given validator delegator. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/UnbondingDelegation ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_addr": "cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77", validator_addr":"cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc"}' \ localhost:9090 cosmos.staking.v1beta1.Query/UnbondingDelegation ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "unbond": { "delegator_address": "cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77", "validator_address": "cosmosvaloper1rne8lgs98p0jqe82sgt0qr4rdn4hgvmgp9ggcc", "entries": [ { "creation_height": "136984", "completion_time": "2021-11-08T05:38:47.505593891Z", "initial_balance": "400000000", "balance": "400000000" }, { "creation_height": "137005", "completion_time": "2021-11-08T05:40:53.526196312Z", "initial_balance": "385000000", "balance": "385000000" } ] } } ``` #### DelegatorDelegations The `DelegatorDelegations` endpoint queries all delegations of a given delegator address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/DelegatorDelegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_addr": "cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77"}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorDelegations ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "delegation_responses": [ {"delegation":{"delegator_address":"cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77","validator_address":"cosmosvaloper1eh5mwu044gd5ntkkc2xgfg8247mgc56fww3vc8","shares":"25083339023.000000000000000000"},"balance":{"denom":"stake","amount":"25083339023"}} ], "pagination": { "next_key": null, "total": "1" } } ``` #### DelegatorUnbondingDelegations The `DelegatorUnbondingDelegations` endpoint queries all unbonding delegations of a given delegator address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_addr": "cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77"}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "unbonding_responses": [ { "delegator_address": "cosmos1y8nyfvmqh50p6ldpzljk3yrglppdv3t8phju77", "validator_address": "cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9uxyejze", "entries": [ { "creation_height": "136984", "completion_time": "2021-11-08T05:38:47.505593891Z", "initial_balance": "400000000", "balance": "400000000" }, { "creation_height": "137005", "completion_time": "2021-11-08T05:40:53.526196312Z", "initial_balance": "385000000", "balance": "385000000" } ] } ], "pagination": { "next_key": null, "total": "1" } } ``` #### Redelegations The `Redelegations` endpoint queries redelegations of given address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/Redelegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_addr": "cosmos1ld5p7hn43yuh8ht28gm9pfjgj2fctujp2tgwvf", "src_validator_addr" : "cosmosvaloper1j7euyj85fv2jugejrktj540emh9353ltgppc3g", "dst_validator_addr" : "cosmosvaloper1yy3tnegzmkdcm7czzcy3flw5z0zyr9vkkxrfse"}' \ localhost:9090 cosmos.staking.v1beta1.Query/Redelegations ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "redelegation_responses": [ { "redelegation": { "delegator_address": "cosmos1ld5p7hn43yuh8ht28gm9pfjgj2fctujp2tgwvf", "validator_src_address": "cosmosvaloper1j7euyj85fv2jugejrktj540emh9353ltgppc3g", "validator_dst_address": "cosmosvaloper1yy3tnegzmkdcm7czzcy3flw5z0zyr9vkkxrfse", "entries": null }, "entries": [ { "redelegation_entry": { "creation_height": 135932, "completion_time": "2021-11-08T03:52:55.299147901Z", "initial_balance": "2900000", "shares_dst": "2900000.000000000000000000" }, "balance": "2900000" } ] } ], "pagination": null } ``` #### DelegatorValidators The `DelegatorValidators` endpoint queries all validators information for given delegator. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/DelegatorValidators ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_addr": "cosmos1ld5p7hn43yuh8ht28gm9pfjgj2fctujp2tgwvf"}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorValidators ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validators": [ { "operator_address": "cosmosvaloper1eh5mwu044gd5ntkkc2xgfg8247mgc56fww3vc8", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "UPwHWxH1zHJWGOa/m6JB3f5YjHMvPQPkVbDqqi+U7Uw=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "347260647559", "delegator_shares": "347260647559.000000000000000000", "description": { "moniker": "BouBouNode", "identity": "", "website": "https://boubounode.com", "security_contact": "", "details": "AI-based Validator. #1 AI Validator on Game of Stakes. Fairly priced. Don't trust (humans), verify. Made with BouBou love." }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.061000000000000000", "max_rate": "0.300000000000000000", "max_change_rate": "0.150000000000000000" }, "update_time": "2021-10-01T15:00:00Z" }, "min_self_delegation": "1" } ], "pagination": { "next_key": null, "total": "1" } } ``` #### DelegatorValidator The `DelegatorValidator` endpoint queries validator information for given delegator validator ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/DelegatorValidator ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"delegator_addr": "cosmos1eh5mwu044gd5ntkkc2xgfg8247mgc56f3n8rr7", "validator_addr": "cosmosvaloper1eh5mwu044gd5ntkkc2xgfg8247mgc56fww3vc8"}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorValidator ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validator": { "operator_address": "cosmosvaloper1eh5mwu044gd5ntkkc2xgfg8247mgc56fww3vc8", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "UPwHWxH1zHJWGOa/m6JB3f5YjHMvPQPkVbDqqi+U7Uw=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "347262754841", "delegator_shares": "347262754841.000000000000000000", "description": { "moniker": "BouBouNode", "identity": "", "website": "https://boubounode.com", "security_contact": "", "details": "AI-based Validator. #1 AI Validator on Game of Stakes. Fairly priced. Don't trust (humans), verify. Made with BouBou love." }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.061000000000000000", "max_rate": "0.300000000000000000", "max_change_rate": "0.150000000000000000" }, "update_time": "2021-10-01T15:00:00Z" }, "min_self_delegation": "1" } } ``` #### HistoricalInfo ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/HistoricalInfo ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"height" : 1}' localhost:9090 cosmos.staking.v1beta1.Query/HistoricalInfo ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "hist": { "header": { "version": { "block": "11", "app": "0" }, "chain_id": "simd-1", "height": "140142", "time": "2021-10-11T10:56:29.720079569Z", "last_block_id": { "hash": "9gri/4LLJUBFqioQ3NzZIP9/7YHR9QqaM6B2aJNQA7o=", "part_set_header": { "total": 1, "hash": "Hk1+C864uQkl9+I6Zn7IurBZBKUevqlVtU7VqaZl1tc=" } }, "last_commit_hash": "VxrcS27GtvGruS3I9+AlpT7udxIT1F0OrRklrVFSSKc=", "data_hash": "80BjOrqNYUOkTnmgWyz9AQ8n7SoEmPVi4QmAe8RbQBY=", "validators_hash": "95W49n2hw8RWpr1GPTAO5MSPi6w6Wjr3JjjS7AjpBho=", "next_validators_hash": "95W49n2hw8RWpr1GPTAO5MSPi6w6Wjr3JjjS7AjpBho=", "consensus_hash": "BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=", "app_hash": "ZZaxnSY3E6Ex5Bvkm+RigYCK82g8SSUL53NymPITeOE=", "last_results_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", "evidence_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", "proposer_address": "aH6dO428B+ItuoqPq70efFHrSMY=" }, "valset": [ { "operator_address": "cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcqcnylw", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "/O7BtNW0pafwfvomgR4ZnfldwPXiFfJs9mHg3gwfv5Q=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "1426045203613", "delegator_shares": "1426045203613.000000000000000000", "description": { "moniker": "SG-1", "identity": "48608633F99D1B60", "website": "https://sg-1.online", "security_contact": "", "details": "SG-1 - your favorite validator on Witval. We offer 100% Soft Slash protection." }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.037500000000000000", "max_rate": "0.200000000000000000", "max_change_rate": "0.030000000000000000" }, "update_time": "2021-10-01T15:00:00Z" }, "min_self_delegation": "1" } ] } } ``` #### Pool The `Pool` endpoint queries the pool information. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/Pool ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d localhost:9090 cosmos.staking.v1beta1.Query/Pool ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "pool": { "not_bonded_tokens": "369054400189", "bonded_tokens": "15657192425623" } } ``` #### Params The `Params` endpoint queries the pool information. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.staking.v1beta1.Query/Params ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.staking.v1beta1.Query/Params ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "unbondingTime": "1814400s", "maxValidators": 100, "maxEntries": 7, "historicalEntries": 10000, "bondDenom": "stake", "minCommissionRate": "0.000000000000000000", "keyRotationFee": { "denom": "stake", "amount": "1000000" } } } ``` ### REST A user can query the `staking` module using REST endpoints. #### DelegatorDelegations The `DelegatorDelegations` REST endpoint queries all delegations of a given delegator address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/delegations/{delegatorAddr} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/staking/v1beta1/delegations/cosmos1vcs68xf2tnqes5tg0khr0vyevm40ff6zdxatp5" -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "delegation_responses": [ { "delegation": { "delegator_address": "cosmos1vcs68xf2tnqes5tg0khr0vyevm40ff6zdxatp5", "validator_address": "cosmosvaloper1quqxfrxkycr0uzt4yk0d57tcq3zk7srm7sm6r8", "shares": "256250000.000000000000000000" }, "balance": { "denom": "stake", "amount": "256250000" } }, { "delegation": { "delegator_address": "cosmos1vcs68xf2tnqes5tg0khr0vyevm40ff6zdxatp5", "validator_address": "cosmosvaloper194v8uwee2fvs2s8fa5k7j03ktwc87h5ym39jfv", "shares": "255150000.000000000000000000" }, "balance": { "denom": "stake", "amount": "255150000" } } ], "pagination": { "next_key": null, "total": "2" } } ``` #### Redelegations The `Redelegations` REST endpoint queries redelegations of given address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/delegators/{delegatorAddr}/redelegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/delegators/cosmos1thfntksw0d35n2tkr0k8v54fr8wxtxwxl2c56e/redelegations?srcValidatorAddr=cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qx4cuznf&dstValidatorAddr=cosmosvaloper1vq8tw77kp8lvxq9u3c8eeln9zymn68rng8pgt4" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "redelegation_responses": [ { "redelegation": { "delegator_address": "cosmos1thfntksw0d35n2tkr0k8v54fr8wxtxwxl2c56e", "validator_src_address": "cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qx4cuznf", "validator_dst_address": "cosmosvaloper1vq8tw77kp8lvxq9u3c8eeln9zymn68rng8pgt4", "entries": null }, "entries": [ { "redelegation_entry": { "creation_height": 151523, "completion_time": "2021-11-09T06:03:25.640682116Z", "initial_balance": "200000000", "shares_dst": "200000000.000000000000000000" }, "balance": "200000000" } ] } ], "pagination": null } ``` #### DelegatorUnbondingDelegations The `DelegatorUnbondingDelegations` REST endpoint queries all unbonding delegations of a given delegator address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/delegators/{delegatorAddr}/unbonding_delegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/delegators/cosmos1nxv42u3lv642q0fuzu2qmrku27zgut3n3z7lll/unbonding_delegations" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "unbonding_responses": [ { "delegator_address": "cosmos1nxv42u3lv642q0fuzu2qmrku27zgut3n3z7lll", "validator_address": "cosmosvaloper1e7mvqlz50ch6gw4yjfemsc069wfre4qwmw53kq", "entries": [ { "creation_height": "2442278", "completion_time": "2021-10-12T10:59:03.797335857Z", "initial_balance": "50000000000", "balance": "50000000000" } ] } ], "pagination": { "next_key": null, "total": "1" } } ``` #### DelegatorValidators The `DelegatorValidators` REST endpoint queries all validators information for given delegator address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/delegators/{delegatorAddr}/validators ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/delegators/cosmos1xwazl8ftks4gn00y5x3c47auquc62ssune9ppv/validators" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validators": [ { "operator_address": "cosmosvaloper1xwazl8ftks4gn00y5x3c47auquc62ssuvynw64", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "5v4n3px3PkfNnKflSgepDnsMQR1hiNXnqOC11Y72/PQ=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "21592843799", "delegator_shares": "21592843799.000000000000000000", "description": { "moniker": "jabbey", "identity": "", "website": "https://twitter.com/JoeAbbey", "security_contact": "", "details": "just another dad in the cosmos" }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.100000000000000000", "max_rate": "0.200000000000000000", "max_change_rate": "0.100000000000000000" }, "update_time": "2021-10-09T19:03:54.984821705Z" }, "min_self_delegation": "1" } ], "pagination": { "next_key": null, "total": "1" } } ``` #### DelegatorValidator The `DelegatorValidator` REST endpoint queries validator information for given delegator validator pair. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/delegators/{delegatorAddr}/validators/{validatorAddr} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/delegators/cosmos1xwazl8ftks4gn00y5x3c47auquc62ssune9ppv/validators/cosmosvaloper1xwazl8ftks4gn00y5x3c47auquc62ssuvynw64" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validator": { "operator_address": "cosmosvaloper1xwazl8ftks4gn00y5x3c47auquc62ssuvynw64", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "5v4n3px3PkfNnKflSgepDnsMQR1hiNXnqOC11Y72/PQ=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "21592843799", "delegator_shares": "21592843799.000000000000000000", "description": { "moniker": "jabbey", "identity": "", "website": "https://twitter.com/JoeAbbey", "security_contact": "", "details": "just another dad in the cosmos" }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.100000000000000000", "max_rate": "0.200000000000000000", "max_change_rate": "0.100000000000000000" }, "update_time": "2021-10-09T19:03:54.984821705Z" }, "min_self_delegation": "1" } } ``` #### HistoricalInfo The `HistoricalInfo` REST endpoint queries the historical information for given height. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/historical_info/{height} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/staking/v1beta1/historical_info/153332" -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "hist": { "header": { "version": { "block": "11", "app": "0" }, "chain_id": "cosmos-1", "height": "153332", "time": "2021-10-12T09:05:35.062230221Z", "last_block_id": { "hash": "NX8HevR5khb7H6NGKva+jVz7cyf0skF1CrcY9A0s+d8=", "part_set_header": { "total": 1, "hash": "zLQ2FiKM5tooL3BInt+VVfgzjlBXfq0Hc8Iux/xrhdg=" } }, "last_commit_hash": "P6IJrK8vSqU3dGEyRHnAFocoDGja0bn9euLuy09s350=", "data_hash": "eUd+6acHWrNXYju8Js449RJ99lOYOs16KpqQl4SMrEM=", "validators_hash": "mB4pravvMsJKgi+g8aYdSeNlt0kPjnRFyvtAQtaxcfw=", "next_validators_hash": "mB4pravvMsJKgi+g8aYdSeNlt0kPjnRFyvtAQtaxcfw=", "consensus_hash": "BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=", "app_hash": "fuELArKRK+CptnZ8tu54h6xEleSWenHNmqC84W866fU=", "last_results_hash": "p/BPexV4LxAzlVcPRvW+lomgXb6Yze8YLIQUo/4Kdgc=", "evidence_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", "proposer_address": "G0MeY8xQx7ooOsni8KE/3R/Ib3Q=" }, "valset": [ { "operator_address": "cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcqcnylw", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "/O7BtNW0pafwfvomgR4ZnfldwPXiFfJs9mHg3gwfv5Q=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "1416521659632", "delegator_shares": "1416521659632.000000000000000000", "description": { "moniker": "SG-1", "identity": "48608633F99D1B60", "website": "https://sg-1.online", "security_contact": "", "details": "SG-1 - your favorite validator on cosmos. We offer 100% Soft Slash protection." }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.037500000000000000", "max_rate": "0.200000000000000000", "max_change_rate": "0.030000000000000000" }, "update_time": "2021-10-01T15:00:00Z" }, "min_self_delegation": "1" }, { "operator_address": "cosmosvaloper1t8ehvswxjfn3ejzkjtntcyrqwvmvuknzmvtaaa", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "uExZyjNLtr2+FFIhNDAMcQ8+yTrqE7ygYTsI7khkA5Y=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "1348298958808", "delegator_shares": "1348298958808.000000000000000000", "description": { "moniker": "Cosmostation", "identity": "AE4C403A6E7AA1AC", "website": "https://www.cosmostation.io", "security_contact": "admin@stamper.network", "details": "Cosmostation validator node. Delegate your tokens and Start Earning Staking Rewards" }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.050000000000000000", "max_rate": "1.000000000000000000", "max_change_rate": "0.200000000000000000" }, "update_time": "2021-10-01T15:06:38.821314287Z" }, "min_self_delegation": "1" } ] } } ``` #### Parameters The `Parameters` REST endpoint queries the staking parameters. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/params ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/staking/v1beta1/params" -H "accept: application/json" ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "unbonding_time": "2419200s", "max_validators": 100, "max_entries": 7, "historical_entries": 10000, "bond_denom": "stake" } } ``` #### Pool The `Pool` REST endpoint queries the pool information. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/pool ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/staking/v1beta1/pool" -H "accept: application/json" ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "pool": { "not_bonded_tokens": "432805737458", "bonded_tokens": "15783637712645" } } ``` #### Validators The `Validators` REST endpoint queries all validators that match the given status. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/validators ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/staking/v1beta1/validators" -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validators": [ { "operator_address": "cosmosvaloper1q3jsx9dpfhtyqqgetwpe5tmk8f0ms5qywje8tw", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "N7BPyek2aKuNZ0N/8YsrqSDhGZmgVaYUBuddY8pwKaE=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "383301887799", "delegator_shares": "383301887799.000000000000000000", "description": { "moniker": "SmartNodes", "identity": "D372724899D1EDC8", "website": "https://smartnodes.co", "security_contact": "", "details": "Earn Rewards with Crypto Staking & Node Deployment" }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.050000000000000000", "max_rate": "0.200000000000000000", "max_change_rate": "0.100000000000000000" }, "update_time": "2021-10-01T15:51:31.596618510Z" }, "min_self_delegation": "1" }, { "operator_address": "cosmosvaloper1q5ku90atkhktze83j9xjaks2p7uruag5zp6wt7", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "GDNpuKDmCg9GnhnsiU4fCWktuGUemjNfvpCZiqoRIYA=" }, "jailed": false, "status": "BOND_STATUS_UNBONDING", "tokens": "1017819654", "delegator_shares": "1017819654.000000000000000000", "description": { "moniker": "Noderunners", "identity": "812E82D12FEA3493", "website": "http://noderunners.biz", "security_contact": "info@noderunners.biz", "details": "Noderunners is a professional validator in POS networks. We have a huge node running experience, reliable soft and hardware. Our commissions are always low, our support to delegators is always full. Stake with us and start receiving your cosmos rewards now!" }, "unbonding_height": "147302", "unbonding_time": "2021-11-08T22:58:53.718662452Z", "commission": { "commission_rates": { "rate": "0.050000000000000000", "max_rate": "0.200000000000000000", "max_change_rate": "0.100000000000000000" }, "update_time": "2021-10-04T18:02:21.446645619Z" }, "min_self_delegation": "1" } ], "pagination": { "next_key": "FONDBFkE4tEEf7yxWWKOD49jC2NK", "total": "2" } } ``` #### Validator The `Validator` REST endpoint queries validator information for given validator address. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/validators/{validatorAddr} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/validators/cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validator": { "operator_address": "cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q", "consensus_pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "sIiexdJdYWn27+7iUHQJDnkp63gq/rzUq1Y+fxoGjXc=" }, "jailed": false, "status": "BOND_STATUS_BONDED", "tokens": "33027900000", "delegator_shares": "33027900000.000000000000000000", "description": { "moniker": "Witval", "identity": "51468B615127273A", "website": "", "security_contact": "", "details": "Witval is the validator arm from Vitwit. Vitwit is into software consulting and services business since 2015. We are working closely with Cosmos ecosystem since 2018. We are also building tools for the ecosystem, Aneka is our explorer for the cosmos ecosystem." }, "unbonding_height": "0", "unbonding_time": "1970-01-01T00:00:00Z", "commission": { "commission_rates": { "rate": "0.050000000000000000", "max_rate": "0.200000000000000000", "max_change_rate": "0.020000000000000000" }, "update_time": "2021-10-01T19:24:52.663191049Z" }, "min_self_delegation": "1" } } ``` #### ValidatorDelegations The `ValidatorDelegations` REST endpoint queries delegate information for given validator. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/staking/v1beta1/validators/cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q/delegations" -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "delegation_responses": [ { "delegation": { "delegator_address": "cosmos190g5j8aszqhvtg7cprmev8xcxs6csra7xnk3n3", "validator_address": "cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q", "shares": "31000000000.000000000000000000" }, "balance": { "denom": "stake", "amount": "31000000000" } }, { "delegation": { "delegator_address": "cosmos1ddle9tczl87gsvmeva3c48nenyng4n56qwq4ee", "validator_address": "cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q", "shares": "628470000.000000000000000000" }, "balance": { "denom": "stake", "amount": "628470000" } }, { "delegation": { "delegator_address": "cosmos10fdvkczl76m040smd33lh9xn9j0cf26kk4s2nw", "validator_address": "cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q", "shares": "838120000.000000000000000000" }, "balance": { "denom": "stake", "amount": "838120000" } }, { "delegation": { "delegator_address": "cosmos1n8f5fknsv2yt7a8u6nrx30zqy7lu9jfm0t5lq8", "validator_address": "cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q", "shares": "500000000.000000000000000000" }, "balance": { "denom": "stake", "amount": "500000000" } }, { "delegation": { "delegator_address": "cosmos16msryt3fqlxtvsy8u5ay7wv2p8mglfg9hrek2e", "validator_address": "cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q", "shares": "61310000.000000000000000000" }, "balance": { "denom": "stake", "amount": "61310000" } } ], "pagination": { "next_key": null, "total": "5" } } ``` #### Delegation The `Delegation` REST endpoint queries delegate information for given validator delegator pair. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations/{delegatorAddr} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/validators/cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q/delegations/cosmos1n8f5fknsv2yt7a8u6nrx30zqy7lu9jfm0t5lq8" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "delegation_response": { "delegation": { "delegator_address": "cosmos1n8f5fknsv2yt7a8u6nrx30zqy7lu9jfm0t5lq8", "validator_address": "cosmosvaloper16msryt3fqlxtvsy8u5ay7wv2p8mglfg9g70e3q", "shares": "500000000.000000000000000000" }, "balance": { "denom": "stake", "amount": "500000000" } } } ``` #### UnbondingDelegation The `UnbondingDelegation` REST endpoint queries unbonding information for given validator delegator pair. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations/{delegatorAddr}/unbonding_delegation ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/validators/cosmosvaloper13v4spsah85ps4vtrw07vzea37gq5la5gktlkeu/delegations/cosmos1ze2ye5u5k3qdlexvt2e0nn0508p04094ya0qpm/unbonding_delegation" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "unbond": { "delegator_address": "cosmos1ze2ye5u5k3qdlexvt2e0nn0508p04094ya0qpm", "validator_address": "cosmosvaloper13v4spsah85ps4vtrw07vzea37gq5la5gktlkeu", "entries": [ { "creation_height": "153687", "completion_time": "2021-11-09T09:41:18.352401903Z", "initial_balance": "525111", "balance": "525111" } ] } } ``` #### ValidatorUnbondingDelegations The `ValidatorUnbondingDelegations` REST endpoint queries unbonding delegations of a validator. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/staking/v1beta1/validators/{validatorAddr}/unbonding_delegations ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET \ "http://localhost:1317/cosmos/staking/v1beta1/validators/cosmosvaloper13v4spsah85ps4vtrw07vzea37gq5la5gktlkeu/unbonding_delegations" \ -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "unbonding_responses": [ { "delegator_address": "cosmos1q9snn84jfrd9ge8t46kdcggpe58dua82vnj7uy", "validator_address": "cosmosvaloper13v4spsah85ps4vtrw07vzea37gq5la5gktlkeu", "entries": [ { "creation_height": "90998", "completion_time": "2021-11-05T00:14:37.005841058Z", "initial_balance": "24000000", "balance": "24000000" } ] }, { "delegator_address": "cosmos1qf36e6wmq9h4twhdvs6pyq9qcaeu7ye0s3dqq2", "validator_address": "cosmosvaloper13v4spsah85ps4vtrw07vzea37gq5la5gktlkeu", "entries": [ { "creation_height": "47478", "completion_time": "2021-11-01T22:47:26.714116854Z", "initial_balance": "8000000", "balance": "8000000" } ] } ], "pagination": { "next_key": null, "total": "2" } } ``` # x/upgrade Source: https://docs.cosmos.network/sdk/latest/modules/upgrade/README ## Abstract `x/upgrade` is an implementation of a Cosmos SDK module that facilitates smoothly upgrading a live Cosmos chain to a new (breaking) software version. It accomplishes this by providing a `PreBlocker` hook that prevents the blockchain state machine from proceeding once a pre-defined upgrade block height has been reached. The module does not prescribe anything regarding how governance decides to do an upgrade, but just the mechanism for coordinating the upgrade safely. Without software support for upgrades, upgrading a live chain is risky because all of the validators need to pause their state machines at exactly the same point in the process. If this is not done correctly, there can be state inconsistencies which are hard to recover from. * [Concepts](#concepts) * [State](#state) * [Events](#events) * [Client](#client) * [CLI](#cli) * [REST](#rest) * [gRPC](#grpc) * [Resources](#resources) ## Concepts ### Plan The `x/upgrade` module defines a `Plan` type in which a live upgrade is scheduled to occur. A `Plan` can be scheduled at a specific block height. A `Plan` is created once a (frozen) release candidate along with an appropriate upgrade `Handler` (see below) is agreed upon, where the `Name` of a `Plan` corresponds to a specific `Handler`. Typically, a `Plan` is created through a governance proposal process, where if voted upon and passed, will be scheduled. The `Info` of a `Plan` may contain various metadata about the upgrade, typically application specific upgrade info to be included on-chain such as a git commit that validators could automatically upgrade to. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Plan struct { Name string Height int64 Info string } ``` #### Sidecar Process If an operator running the application binary also runs a sidecar process to assist in the automatic download and upgrade of a binary, the `Info` allows this process to be seamless. This tool is [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/tools/cosmovisor#readme). ### Handler The `x/upgrade` module facilitates upgrading from major version X to major version Y. To accomplish this, node operators must first upgrade their current binary to a new binary that has a corresponding `Handler` for the new version Y. It is assumed that this version has fully been tested and approved by the community at large. This `Handler` defines what state migrations need to occur before the new binary Y can successfully run the chain. Naturally, this `Handler` is application specific and not defined on a per-module basis. Registering a `Handler` is done via `Keeper#SetUpgradeHandler` in the application. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type UpgradeHandler func(Context, Plan, VersionMap) (VersionMap, error) ``` During each `EndBlock` execution, the `x/upgrade` module checks if there exists a `Plan` that should execute (is scheduled at that height). If so, the corresponding `Handler` is executed. If the `Plan` is expected to execute but no `Handler` is registered or if the binary was upgraded too early, the node will gracefully panic and exit. ### StoreLoader The `x/upgrade` module also facilitates store migrations as part of the upgrade. The `StoreLoader` sets the migrations that need to occur before the new binary can successfully run the chain. This `StoreLoader` is also application specific and not defined on a per-module basis. Registering this `StoreLoader` is done via `app#SetStoreLoader` in the application. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func UpgradeStoreLoader (upgradeHeight int64, storeUpgrades *store.StoreUpgrades) baseapp.StoreLoader ``` If there's a planned upgrade and the upgrade height is reached, the old binary writes `Plan` to the disk before panicking. This information is critical to ensure the `StoreUpgrades` happens smoothly at the correct height and expected upgrade. It eliminates the chances for the new binary to execute `StoreUpgrades` multiple times every time on restart. Also, if there are multiple upgrades planned on the same height, the `Name` will ensure these `StoreUpgrades` take place only in the planned upgrade handler. ### Proposal Typically, a `Plan` is proposed and submitted through governance via a proposal containing a `MsgSoftwareUpgrade` message. This proposal prescribes to the standard governance process. If the proposal passes, the `Plan`, which targets a specific `Handler`, is persisted and scheduled. The upgrade can be delayed or hastened by updating the `Plan.Height` in a new proposal. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/upgrade/v1beta1/tx.proto#L28-L39 ``` #### Cancelling Upgrade Proposals Upgrade proposals can be cancelled. There exists a gov-enabled `MsgCancelUpgrade` message type, which can be embedded in a proposal, voted on and, if passed, will remove the scheduled upgrade `Plan`. Of course this requires that the upgrade was known to be a bad idea well before the upgrade itself, to allow time for a vote. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Reference: https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/upgrade/v1beta1/tx.proto#L46-L54 ``` If such a possibility is desired, the upgrade height is to be `2 * (VotingPeriod + DepositPeriod) + (SafetyDelta)` from the beginning of the upgrade proposal. The `SafetyDelta` is the time available from the success of an upgrade proposal and the realization it was a bad idea (due to external social consensus). A `MsgCancelUpgrade` proposal can also be made while the original `MsgSoftwareUpgrade` proposal is still being voted upon, as long as the `VotingPeriod` ends after the `MsgSoftwareUpgrade` proposal. ## State The internal state of the `x/upgrade` module is relatively minimal and simple. The state contains the currently active upgrade `Plan` (if one exists) by key `0x0` and if a `Plan` is marked as "done" by key `0x1`. The state contains the consensus versions of all app modules in the application. The versions are stored as big endian `uint64`, and can be accessed with prefix `0x2` appended by the corresponding module name of type `string`. The state maintains a `Protocol Version` which can be accessed by key `0x3`. * Plan: `0x0 -> Plan` * Done: `0x1 | byte(plan name) -> BigEndian(Block Height)` * ConsensusVersion: `0x2 | byte(module name) -> BigEndian(Module Consensus Version)` * ProtocolVersion: `0x3 -> BigEndian(Protocol Version)` The `x/upgrade` module contains no genesis state. ## Events The `x/upgrade` does not emit any events by itself. Any and all proposal related events are emitted through the `x/gov` module. ## Client ### CLI A user can query and interact with the `upgrade` module using the CLI. #### Query The `query` commands allow users to query `upgrade` state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query upgrade --help ``` ##### applied The `applied` command allows users to query the block header for height at which a completed upgrade was applied. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query upgrade applied [upgrade-name] [flags] ``` If upgrade-name was previously executed on the chain, this returns the header for the block at which it was applied. This helps a client determine which binary was valid over a given range of blocks, as well as more context to understand past migrations. Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query upgrade applied "test-upgrade" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} "block_id": { "hash": "A769136351786B9034A5F196DC53F7E50FCEB53B48FA0786E1BFC45A0BB646B5", "parts": { "total": 1, "hash": "B13CBD23011C7480E6F11BE4594EE316548648E6A666B3575409F8F16EC6939E" } }, "block_size": "7213", "header": { "version": { "block": "11" }, "chain_id": "testnet-2", "height": "455200", "time": "2021-04-10T04:37:57.085493838Z", "last_block_id": { "hash": "0E8AD9309C2DC411DF98217AF59E044A0E1CCEAE7C0338417A70338DF50F4783", "parts": { "total": 1, "hash": "8FE572A48CD10BC2CBB02653CA04CA247A0F6830FF19DC972F64D339A355E77D" } }, "last_commit_hash": "DE890239416A19E6164C2076B837CC1D7F7822FC214F305616725F11D2533140", "data_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", "validators_hash": "A31047ADE54AE9072EE2A12FF260A8990BA4C39F903EAF5636B50D58DBA72582", "next_validators_hash": "A31047ADE54AE9072EE2A12FF260A8990BA4C39F903EAF5636B50D58DBA72582", "consensus_hash": "048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F", "app_hash": "28ECC486AFC332BA6CC976706DBDE87E7D32441375E3F10FD084CD4BAF0DA021", "last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", "evidence_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", "proposer_address": "2ABC4854B1A1C5AA8403C4EA853A81ACA901CC76" }, "num_txs": "0" } ``` ##### module versions The `module_versions` command gets a list of module names and their respective consensus versions. Following the command with a specific module name will return only that module's information. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query upgrade module_versions [optional module_name] [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query upgrade module_versions ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} module_versions: - name: auth version: "2" - name: authz version: "1" - name: bank version: "2" - name: distribution version: "2" - name: evidence version: "1" - name: feegrant version: "1" - name: genutil version: "1" - name: gov version: "2" - name: ibc version: "2" - name: mint version: "1" - name: params version: "1" - name: slashing version: "2" - name: staking version: "2" - name: transfer version: "1" - name: upgrade version: "1" - name: vesting version: "1" ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} regen query upgrade module_versions ibc ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} module_versions: - name: ibc version: "2" ``` ##### plan The `plan` command gets the currently scheduled upgrade plan, if one exists. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} regen query upgrade plan [flags] ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query upgrade plan ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} height: "130" info: "" name: test-upgrade time: "0001-01-01T00:00:00Z" upgraded_client_state: null ``` #### Transactions The upgrade module supports the following transactions: * `software-proposal` - submits an upgrade proposal: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx upgrade software-upgrade v2 --title="Test Proposal" --summary="testing" --deposit="100000000stake" --upgrade-height 1000000 \ --upgrade-info '{ "binaries": { "linux/amd64":"https://example.com/simd.zip?checksum=sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f" } }' --from cosmos1.. ``` * `cancel-software-upgrade` - cancels a previously submitted upgrade proposal: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx upgrade cancel-software-upgrade --title="Test Proposal" --summary="testing" --deposit="100000000stake" --from cosmos1.. ``` ### REST A user can query the `upgrade` module using REST endpoints. #### Applied Plan `AppliedPlan` queries a previously applied upgrade plan by its name. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/upgrade/v1beta1/applied_plan/{name} ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/upgrade/v1beta1/applied_plan/v2.0-upgrade" -H "accept: application/json" ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "height": "30" } ``` #### Current Plan `CurrentPlan` queries the current upgrade plan. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/upgrade/v1beta1/current_plan ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/upgrade/v1beta1/current_plan" -H "accept: application/json" ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "plan": "v2.1-upgrade" } ``` #### Module versions `ModuleVersions` queries the list of module versions from state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos/upgrade/v1beta1/module_versions ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:1317/cosmos/upgrade/v1beta1/module_versions" -H "accept: application/json" ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "module_versions": [ { "name": "auth", "version": "2" }, { "name": "authz", "version": "1" }, { "name": "bank", "version": "2" }, { "name": "distribution", "version": "2" }, { "name": "evidence", "version": "1" }, { "name": "feegrant", "version": "1" }, { "name": "genutil", "version": "1" }, { "name": "gov", "version": "2" }, { "name": "ibc", "version": "2" }, { "name": "mint", "version": "1" }, { "name": "params", "version": "1" }, { "name": "slashing", "version": "2" }, { "name": "staking", "version": "2" }, { "name": "transfer", "version": "1" }, { "name": "upgrade", "version": "1" }, { "name": "vesting", "version": "1" } ] } ``` ### gRPC A user can query the `upgrade` module using gRPC endpoints. #### Applied Plan `AppliedPlan` queries a previously applied upgrade plan by its name. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.upgrade.v1beta1.Query/AppliedPlan ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"name":"v2.0-upgrade"}' \ localhost:9090 \ cosmos.upgrade.v1beta1.Query/AppliedPlan ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "height": "30" } ``` #### Current Plan `CurrentPlan` queries the current upgrade plan. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.upgrade.v1beta1.Query/CurrentPlan ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.slashing.v1beta1.Query/CurrentPlan ``` Example Output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "plan": "v2.1-upgrade" } ``` #### Module versions `ModuleVersions` queries the list of module versions from state. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.upgrade.v1beta1.Query/ModuleVersions ``` Example: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.slashing.v1beta1.Query/ModuleVersions ``` Example Output: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "module_versions": [ { "name": "auth", "version": "2" }, { "name": "authz", "version": "1" }, { "name": "bank", "version": "2" }, { "name": "distribution", "version": "2" }, { "name": "evidence", "version": "1" }, { "name": "feegrant", "version": "1" }, { "name": "genutil", "version": "1" }, { "name": "gov", "version": "2" }, { "name": "ibc", "version": "2" }, { "name": "mint", "version": "1" }, { "name": "params", "version": "1" }, { "name": "slashing", "version": "2" }, { "name": "staking", "version": "2" }, { "name": "transfer", "version": "1" }, { "name": "upgrade", "version": "1" }, { "name": "vesting", "version": "1" } ] } ``` ## Resources A list of (external) resources to learn more about the `x/upgrade` module. * [Cosmos Dev Series: Cosmos Blockchain Upgrade](https://medium.com/web3-surfers/cosmos-dev-series-cosmos-sdk-based-blockchain-upgrade-b5e99181554c) - The blog post that explains how software upgrades work in detail. # Interacting with a Node Source: https://docs.cosmos.network/sdk/latest/node/interact-node **Synopsis** There are multiple ways to interact with a node: using the CLI, gRPC, or REST endpoints. **Prerequisite Readings** * [gRPC, REST and CometBFT Endpoints](/sdk/latest/learn/concepts/cli-grpc-rest) * [Running a Node](/sdk/latest/node/run-node) ## Using the CLI Now that your chain is running, it is time to try sending tokens from the first account you created to a second account. In a new terminal window, start by running the following query command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank balances $MY_VALIDATOR_ADDRESS ``` You should see the current balance of the account you created, equal to the original balance of `stake` you granted it minus the amount you delegated via the `gentx`. Now, create a second account: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd keys add recipient --keyring-backend test # Put the generated address in a variable for later use. RECIPIENT=$(simd keys show recipient -a --keyring-backend test) ``` The command above creates a local key-pair that is not yet registered on the chain. An account is created the first time it receives tokens from another account. Now, run the following command to send tokens to the `recipient` account: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000000stake --chain-id my-test-chain --keyring-backend test # Check that the recipient account did receive the tokens. simd query bank balances $RECIPIENT ``` Add the `-y` or `--yes` flag to skip the confirmation prompt, which is useful for scripts and automation: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000000stake --chain-id my-test-chain --keyring-backend test -y ``` Finally, delegate some of the stake tokens sent to the `recipient` account to the validator: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx staking delegate $(simd keys show my_validator --bech val -a --keyring-backend test) 500stake --from recipient --chain-id my-test-chain --keyring-backend test # Query the total delegations to `validator`. simd query staking delegations-to $(simd keys show my_validator --bech val -a --keyring-backend test) ``` You should see two delegations, the first one made from the `gentx`, and the second one you just performed from the `recipient` account. ## Using gRPC The Protobuf ecosystem developed tools for different use cases, including code-generation from `*.proto` files into various languages. These tools allow the building of clients easily. Often, the client connection (i.e. the transport) can be plugged and replaced very easily. This section explores one of the most popular transports: [gRPC](/sdk/latest/learn/concepts/cli-grpc-rest). The methods available to call, with a `grpcurl` example for each, are in the [API reference](/sdk/latest/api-reference/index). Since the code generation library largely depends on your own tech stack, three alternatives are presented: * `grpcurl` for generic debugging and testing, * programmatically via Go, * CosmJS for JavaScript/TypeScript developers. ### grpcurl [grpcurl](https://github.com/fullstorydev/grpcurl) is like `curl` but for gRPC. It is also available as a Go library, but this tutorial uses it only as a CLI command for debugging and testing purposes. Follow the instructions in the previous link to install it. Assuming you have a local node running (either a localnet, or connected to a live network), you should be able to run the following command to list the Protobuf services available (you can replace `localhost:9090` with the gRPC server endpoint of another node, which is configured under the `grpc.address` field inside [`app.toml`](/sdk/latest/node/run-node#configuring-the-node-using-apptoml-and-configtoml)): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 list ``` You should see a list of gRPC services, like `cosmos.bank.v1beta1.Query`. This is called reflection, which is a Protobuf endpoint returning a description of all available endpoints. Each of these represents a different Protobuf service, and each service exposes multiple RPC methods you can query against. In order to get a description of the service you can run the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ describe cosmos.bank.v1beta1.Query # Service we want to inspect ``` It's also possible to execute an RPC call to query the node for information: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl \ -plaintext \ -d "{\"address\":\"$MY_VALIDATOR_ADDRESS\"}" \ localhost:9090 \ cosmos.bank.v1beta1.Query/AllBalances ``` The list of all available gRPC query endpoints is [coming soon](https://github.com/cosmos/cosmos-sdk/issues/7786). #### Query for historical state using grpcurl You may also query for historical data by passing some [gRPC metadata](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md) to the query: the `x-cosmos-block-height` metadata should contain the block to query. Using grpcurl as above, the command looks like: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl \ -plaintext \ -H "x-cosmos-block-height: 123" \ -d "{\"address\":\"$MY_VALIDATOR_ADDRESS\"}" \ localhost:9090 \ cosmos.bank.v1beta1.Query/AllBalances ``` Assuming the state at that block has not yet been pruned by the node, this query should return a non-empty response. ### Programmatically via Go The following snippet shows how to query the state using gRPC inside a Go program. The idea is to create a gRPC connection, and use the Protobuf-generated client code to query the gRPC server. #### Install Cosmos SDK ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go get github.com/cosmos/cosmos-sdk@main ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package main import ( "context" "fmt" "google.golang.org/grpc" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) func queryState() error { myAddress, err := sdk.AccAddressFromBech32("cosmos1...") // the my_validator or recipient address. if err != nil { return err } // Create a connection to the gRPC server. grpcConn, err := grpc.Dial( "127.0.0.1:9090", // your gRPC server address. grpc.WithInsecure(), // The Cosmos SDK doesn't support any transport security mechanisms. // This instantiates a general gRPC codec which handles proto bytes. We pass in a nil interface registry // if the request/response types contain an interface instead of 'nil' you should pass the application specific codec. grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(nil).GRPCCodec())), ) if err != nil { return err } defer grpcConn.Close() // This creates a gRPC client to query the x/bank service. bankClient := banktypes.NewQueryClient(grpcConn) bankRes, err := bankClient.Balance( context.Background(), &banktypes.QueryBalanceRequest{ Address: myAddress.String(), Denom: "stake" }, ) if err != nil { return err } fmt.Println(bankRes.GetBalance()) // Prints the account balance return nil } func main() { if err := queryState(); err != nil { panic(err) } } ``` You can replace the query client (here we are using `x/bank`'s) with one generated from any other Protobuf service. The list of all available gRPC query endpoints is [coming soon](https://github.com/cosmos/cosmos-sdk/issues/7786). #### Query for historical state using Go Querying for historical blocks is done by adding the block height metadata in the gRPC request. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package main import ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) func queryState() error { myAddress, err := sdk.AccAddressFromBech32("cosmos1yerherx4d43gj5wa3zl5vflj9d4pln42n7kuzu") // the my_validator or recipient address. if err != nil { return err } // Create a connection to the gRPC server. grpcConn, err := grpc.Dial( "127.0.0.1:9090", // your gRPC server address. grpc.WithInsecure(), // The Cosmos SDK doesn't support any transport security mechanisms. // This instantiates a general gRPC codec which handles proto bytes. We pass in a nil interface registry // if the request/response types contain an interface instead of 'nil' you should pass the application specific codec. grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(nil).GRPCCodec())), ) if err != nil { return err } defer grpcConn.Close() // This creates a gRPC client to query the x/bank service. bankClient := banktypes.NewQueryClient(grpcConn) var header metadata.MD _, err = bankClient.Balance( metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, "12"), // Add metadata to request &banktypes.QueryBalanceRequest{ Address: myAddress.String(), Denom: "stake" }, grpc.Header(&header), // Retrieve header from response ) if err != nil { return err } blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader) fmt.Println(blockHeight) // Prints the block height (12) return nil } func main() { if err := queryState(); err != nil { panic(err) } } ``` ### CosmJS CosmJS documentation can be found at [Link](https://cosmos.github.io/cosmjs). ## Using the REST Endpoints As described in the [gRPC guide](/sdk/latest/learn/concepts/cli-grpc-rest), all gRPC services on the Cosmos SDK are made available for more convenient REST-based queries through gRPC-gateway. The format of the URL path is based on the Protobuf service method's full-qualified name, but may contain small customizations so that final URLs look more idiomatic. For example, the REST endpoint for the `cosmos.bank.v1beta1.Query/AllBalances` method is `GET /cosmos/bank/v1beta1/balances/{address}`. Request arguments are passed as query parameters. Rather than deriving each path, the [API reference](/sdk/latest/api-reference/index) lists every generated route for the standard modules. Note that the REST endpoints are not enabled by default. To enable them, edit the `api` section of your `~/.simapp/config/app.toml` file: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Enable defines if the API server should be enabled. enable = true ``` After enabling the API, you must restart your node for the changes to take effect. Stop the node with `Ctrl+C` and run `simd start` again. As a concrete example, the `curl` command to make balances request is: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl \ -X GET \ -H "Content-Type: application/json" \ http://localhost:1317/cosmos/bank/v1beta1/balances/$MY_VALIDATOR_ADDRESS ``` Make sure to replace `localhost:1317` with the REST endpoint of your node, configured under the `api.address` field. The list of all available REST endpoints is available as a Swagger specification file, which can be viewed at `localhost:1317/swagger`. Make sure that the `api.swagger` field is set to true in your [`app.toml`](/sdk/latest/node/run-node#configuring-the-node-using-apptoml-and-configtoml) file. ### Query for historical state using REST Querying for historical state is done using the HTTP header `x-cosmos-block-height`. For example, a curl command would look like: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl \ -X GET \ -H "Content-Type: application/json" \ -H "x-cosmos-block-height: 123" \ http://localhost:1317/cosmos/bank/v1beta1/balances/$MY_VALIDATOR_ADDRESS ``` Assuming the state at that block has not yet been pruned by the node, this query should return a non-empty response. ### Cross-Origin Resource Sharing (CORS) [CORS policies](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) are not enabled by default to help with security. If you would like to use the rest-server in a public environment, we recommend you provide a reverse proxy, which can be done with [nginx](https://www.nginx.com/). For testing and development purposes, there is an `enabled-unsafe-cors` field inside [`app.toml`](/sdk/latest/node/run-node#configuring-the-node-using-apptoml-and-configtoml). ## Congratulations! You have successfully interacted with your Cosmos SDK node using the CLI, gRPC, and REST endpoints. You can now query state and submit transactions through multiple interfaces. ## Next steps * [Generate and sign transactions](/sdk/latest/node/txs) to learn manual transaction workflows * Explore the [gRPC and REST guide](/sdk/latest/learn/concepts/cli-grpc-rest) for more advanced querying techniques # Setting up the keyring Source: https://docs.cosmos.network/sdk/latest/node/keyring **Prerequisite Readings** * [Prerequisites](/sdk/latest/node/prerequisites) - Set up Go and build the `simd` binary The keyring holds the private/public key pairs used to interact with a node. A validator key needs to be set up before running the blockchain node so that blocks can be correctly signed. ## Create a key 1. Create a new key for your validator: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd keys add my_validator --keyring-backend test ``` 2. Store the address for later use: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} MY_VALIDATOR_ADDRESS=$(simd keys show my_validator -a --keyring-backend test) ``` This generates a 24-word mnemonic phrase and stores your key. **Save the mnemonic** if you'll use this key for value-bearing tokens. This tutorial uses the `test` backend (unencrypted, for testing only). For production, use the `os` backend which integrates with your system's secure keyring. See [keyring backends](#reference-keyring-backends) below for more information. ## Next steps You have just created your first key. The keyring is now ready to manage keys for interacting with your blockchain node. If you are running through this tutorial as a test, continue to [Run a node](/sdk/latest/node/run-node) to initialize your blockchain and start your node. For more information on the keyring and its various backends, continue reading below. ## Reference: Keyring backends The Cosmos SDK keyring supports multiple storage backends. The private key can be stored in different locations such as a file or the operating system's own key storage. ### The `os` backend The `os` backend relies on operating system-specific defaults to handle key storage securely. Typically, an operating system's credential subsystem handles password prompts, private key storage, and user sessions according to the user's password policies. Here is a list of the most popular operating systems and their respective password managers: * macOS: [Keychain](https://support.apple.com/en-gb/guide/keychain-access/welcome/mac) * Windows: [Credentials Management API](https://docs.microsoft.com/en-us/windows/win32/secauthn/credentials-management) * GNU/Linux: * [libsecret](https://gitlab.gnome.org/GNOME/libsecret) * [kwallet](https://api.kde.org/kwallet-index.html) * [keyctl](https://www.kernel.org/doc/html/latest/security/keys/core.html) GNU/Linux distributions that use GNOME as the default desktop environment typically come with [Seahorse](https://wiki.gnome.org/Apps/Seahorse). Users of KDE based distributions are commonly provided with [KDE Wallet Manager](https://userbase.kde.org/KDE_Wallet_Manager). Whilst the former is in fact a `libsecret` convenient frontend, the latter is a `kwallet` client. `keyctl` is a secure backend that leverages the Linux's kernel security key management system to store cryptographic keys securely in memory. `os` is the default option since operating systems' default credentials managers are designed to meet users' most common needs and provide them with a comfortable experience without compromising on security. The recommended backends for headless environments are `file` and `pass`. ### The `file` backend The `file` backend more closely resembles the keybase implementation used prior to v0.38.1. It stores the keyring encrypted within the app's configuration directory. This keyring will request a password each time it is accessed, which may occur multiple times in a single command, resulting in repeated password prompts. If using bash scripts to execute commands using the `file` option, you may want to utilize the following format for multiple prompts: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # assuming that KEYPASSWD is set in the environment $ gaiacli config keyring-backend file # use file backend $ (echo $KEYPASSWD; echo $KEYPASSWD) | gaiacli keys add me # multiple prompts $ echo $KEYPASSWD | gaiacli keys show me # single prompt ``` The first time you add a key to an empty keyring, you will be prompted to type the password twice. ### The `pass` backend The `pass` backend uses the [pass](https://www.passwordstore.org/) utility to manage on-disk encryption of keys' sensitive data and metadata. Keys are stored inside `gpg`-encrypted files within app-specific directories. `pass` is available for the most popular UNIX operating systems as well as GNU/Linux distributions. Please refer to its manual page for information on how to download and install it. **pass** uses [GnuPG](https://gnupg.org/) for encryption. `gpg` automatically invokes the `gpg-agent` daemon upon execution, which handles the caching of GnuPG credentials. Please refer to `gpg-agent` man page for more information on how to configure cache parameters such as credentials TTL and passphrase expiration. The password store must be set up prior to first use: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} pass init ``` Replace `` with your GPG key ID. You can use your personal GPG key or an alternative one you may want to use specifically to encrypt the password store. ### The `kwallet` backend The `kwallet` backend uses `KDE Wallet Manager`, which comes installed by default on the GNU/Linux distributions that ships KDE as default desktop environment. Please refer to [KWallet Handbook](https://userbase.kde.org/KDE_Wallet_Manager) for more information. ### The `keyctl` backend The *Kernel Key Retention Service* is a security facility that has been added to the Linux kernel relatively recently. It allows sensitive cryptographic data such as passwords, private keys, authentication tokens, etc. to be stored securely in memory. The `keyctl` backend is available on Linux platforms only. ### The `test` backend The `test` backend is a password-less variation of the `file` backend. Keys are stored unencrypted on disk. **Provided for testing purposes only. The `test` backend is not recommended for use in production environments**. ### The `memory` backend The `memory` backend stores keys in memory. The keys are immediately deleted after the program has exited. **Provided for testing purposes only. The `memory` backend is not recommended for use in production environments**. ### Setting backend using the env variable You can set the keyring-backend using an environment variable: `BINNAME_KEYRING_BACKEND`. For example, if your binary name is `gaia-v5`, then set: `export GAIA_V5_KEYRING_BACKEND=pass` ### Additional key management By default, the keyring generates a `secp256k1` keypair. The keyring also supports `ml_dsa_65`, the post-quantum signature algorithm, selected with the `--key-type` flag. A keyring can hold both types of keys simultaneously. For the algorithm and its tradeoffs, see [Post-quantum keys](/sdk/latest/keys/post-quantum-keys); to create and fund a post-quantum account, see [Create an ML-DSA account](/sdk/latest/keys/create-ml-dsa-account). List the key types your binary supports with `simd keys list-key-types`. For help with key management commands, use `simd keys --help` or `simd keys [command] --help`. # Prerequisites Source: https://docs.cosmos.network/sdk/latest/node/prerequisites ## Introduction The Cosmos SDK requires Go and a built binary to run a blockchain node. This tutorial walks through installing Go, building the `simd` binary, and configuring your environment to run a Cosmos SDK node. ## Prerequisites This tutorial assumes you have the following installed: * A terminal application * A code editor * Basic familiarity with command-line operations ## 1. Install Go The Cosmos SDK requires [Go](https://go.dev/) version 1.25 or higher. Download the installer from the [official Go downloads page](https://go.dev/dl/) and follow the installation instructions for your operating system. Verify the installation: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go version ``` ### Configure Go environment variables Open your shell config file (`~/.bashrc` or `~/.zshrc`) and add: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin ``` Apply changes: `source ~/.bashrc` Open `~/.zshrc` and add: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin ``` Apply changes: `source ~/.zshrc` In PowerShell (as Administrator): ```powershell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [System.Environment]::SetEnvironmentVariable('GOPATH', "$HOME\go", 'User') [System.Environment]::SetEnvironmentVariable('Path', "$env:Path;$HOME\go\bin", 'User') ``` Restart PowerShell after setting. Verify: `go env GOPATH` ## 2. Clone the Cosmos SDK repository This tutorial uses `simapp`, the Cosmos SDK example application. Clone the [Cosmos SDK repository](https://github.com/cosmos/cosmos-sdk) to access `simapp`. 1. Navigate to your preferred directory. This example uses `~/Documents/GitHub`: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cd ~/Documents/GitHub ``` 2. Clone the Cosmos SDK repository: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git clone https://github.com/cosmos/cosmos-sdk.git ``` 3. Navigate into the cloned repository: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cd cosmos-sdk ``` If you are building your own chain, clone your chain's repository instead. Replace `simd` with your chain's binary name throughout this tutorial. ## 3. Build the simd binary The `simd` binary is the command-line interface for interacting with the Cosmos SDK blockchain. Build and install the `simd` binary: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make install ``` **Windows users**: If `make` is not available, you can install it via [Chocolatey](https://chocolatey.org/) (`choco install make`) or use WSL2. Verify `simd` is working: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd version ``` Your environment is now set up to run a Cosmos SDK node. ## Next steps * [Set up the keyring](/sdk/latest/node/keyring) to create and manage cryptographic keys # Running a Node Source: https://docs.cosmos.network/sdk/latest/node/run-node **Synopsis** This section explains how to run a blockchain node. The application used in this tutorial is [`simapp`](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/simapp), and its corresponding CLI binary `simd`. **Prerequisite Readings** * [Prerequisites](/sdk/latest/node/prerequisites) - Set up Go and build the `simd` binary * [Anatomy of a Cosmos SDK Application](/sdk/latest/learn/intro/sdk-app-architecture) * [Setting up the keyring](/sdk/latest/node/keyring) ## 1. Initialize the chain Before running the node, initialize the chain and its genesis file. Use the `init` subcommand: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # The argument is the custom username of your node, it should be human-readable. simd init --chain-id my-test-chain ``` The command above creates all the configuration files needed for your node to run, as well as a default genesis file, which defines the initial state of the network. The `init` command also selects the validator consensus key algorithm with the optional `--consensus-key-algo` flag. It defaults to `ed25519`. To initialize the node on a post-quantum key instead, see [the ML-DSA consensus key guides](/sdk/latest/keys/post-quantum-keys). All these configuration files are in `~/.simapp` by default, but you can overwrite the location of this folder by passing the `--home` flag to each command, or set an `$APPD_HOME` environment variable (where `APPD` is the name of the binary). **Windows users**: Replace `~/.simapp` with `%USERPROFILE%\.simapp` (or `$HOME\.simapp` in PowerShell). For `jq` and `sed` commands in this tutorial, install via [Chocolatey](https://chocolatey.org/) (`choco install jq sed`) or use [Git Bash](https://gitforwindows.org/)/WSL2. The `~/.simapp` folder has the following structure: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} . # ~/.simapp |- data # Contains the databases used by the node. |- config/ |- app.toml # Application-related configuration file. |- config.toml # CometBFT-related configuration file. |- genesis.json # The genesis file. |- node_key.json # Private key to use for node authentication in the p2p protocol. |- priv_validator_key.json # Private key to use as a validator in the consensus protocol. ``` ## 2. Update configuration settings (optional) To change field values in configuration files (for example, genesis.json), use `jq` ([installation](https://jqlang.org/download/) & [docs](https://jqlang.org/manual/#assignment)) and `sed` commands. A few examples are listed here. ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # to change the chain-id jq '.chain_id = "testing"' genesis.json > temp.json && mv temp.json genesis.json # to enable the api server sed -i '/\[api\]/,+3 s/enable = false/enable = true/' app.toml # to change the voting_period jq '.app_state.gov.voting_params.voting_period = "600s"' genesis.json > temp.json && mv temp.json genesis.json # to change the inflation jq '.app_state.mint.minter.inflation = "0.300000000000000000"' genesis.json > temp.json && mv temp.json genesis.json ``` ### Client Interaction When instantiating a node, gRPC and REST are defaulted to localhost to avoid unknown exposure of your node to the public. It is recommended not to expose these endpoints without a proxy that can handle load balancing or authentication set up between your node and the public. A commonly used tool for this is [nginx](https://nginx.org). ## 3. Add genesis accounts Earlier in this tutorial, you [created an account in the keyring](/sdk/latest/node/keyring#create-a-key) named `my_validator` under the `test` keyring backend. Now, you can grant this account some `stake` tokens in your chain's genesis file. Doing so will also make sure your chain is aware of this account's existence: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis add-genesis-account $MY_VALIDATOR_ADDRESS 100000000000stake ``` Recall that `$MY_VALIDATOR_ADDRESS` is a variable that holds the address of the `my_validator` key in the [keyring](/sdk/latest/node/keyring#create-a-key). Also note that the tokens in the Cosmos SDK have the `{amount}{denom}` format: `amount` is an 18-digit-precision decimal number, and `denom` is the unique token identifier with its denomination key (e.g., `atom` or `uatom`). Here, `stake` tokens are granted, as `stake` is the token identifier used for staking in [`simapp`](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/simapp). For your own chain with its own staking denom, that token identifier should be used instead. ## 4. Create genesis transaction Now that your account has some tokens, you need to add a validator to your chain. Validators are special full-nodes that participate in the consensus process (implemented in the [underlying consensus engine](/sdk/latest/learn/intro/sdk-app-architecture#cometbft)) in order to add new blocks to the chain. Any account can declare its intention to become a validator operator, but only those with sufficient delegation get to enter the active set (for example, only the top 125 validator candidates with the most delegation get to be validators in the Cosmos Hub). For this guide, your local node (created via the `init` command above) will be added as a validator of your chain. Validators can be declared before a chain is first started via a special transaction included in the genesis file called a `gentx`: 1. Create a gentx. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis gentx my_validator 100000000stake --chain-id my-test-chain --keyring-backend test ``` 2. Add the gentx to the genesis file: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis collect-gentxs ``` A `gentx` does three things: 1. Registers the validator account you created as a validator operator account (i.e., the account that controls the validator). 2. Self-delegates the provided `amount` of staking tokens. 3. Link the operator account with a CometBFT node pubkey that will be used for signing blocks. If no `--pubkey` flag is provided, it defaults to the local node pubkey created via the `simd init` command above. For more information on `gentx`, use the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd genesis gentx --help ``` ## 5. Configure the node using `app.toml` and `config.toml` The Cosmos SDK automatically generates two configuration files inside `~/.simapp/config`: * `config.toml`: used to configure the CometBFT, learn more on [CometBFT's documentation](/cometbft/latest/docs/core/configuration), * `app.toml`: generated by the Cosmos SDK, and used to configure your app, such as state pruning strategies, telemetry, gRPC and REST server configuration, state sync, etc. See the [default `app.toml` template](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/server/config/toml.go) for every field and its inline documentation. Both files are heavily commented, please refer to them directly to tweak your node. Each field carries an inline comment that explains it. This includes operational settings such as `query-gas-limit` that matter for nodes serving public RPC. One example config to tweak is the `minimum-gas-prices` field inside `app.toml`, which defines the minimum gas prices the validator node is willing to accept for processing a transaction. Depending on the chain, it might be an empty string or not. If it's empty, make sure to edit the field with some value, for example `10token`, or else the node will halt on startup. For the purposes of this tutorial, the minimum gas price is set to 0: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # The minimum gas prices a validator is willing to accept for processing a # transaction. A transaction's fees must meet the minimum of any denomination # specified in this config (e.g. 0.25token1,0.0001token2). minimum-gas-prices = "0stake" ``` When running a node (not a validator!) and not wanting to run the application mempool, set the `max-txs` field to `-1`. ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [mempool] # Setting max-txs to 0 will allow for an unbounded amount of transactions in the mempool. # Setting max_txs to negative 1 (-1) will disable transactions from being inserted into the mempool (no-op mempool). # Setting max_txs to a positive number (> 0) will limit the number of transactions in the mempool, by the specified amount. # # Note, this configuration only applies to SDK built-in app-side mempool # implementations. max-txs = "-1" ``` ## 6. Start the node Now that everything is set up, you can finally start your node: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd start ``` You should see blocks come in. ### What happens when the node starts The `start` command (defined in [`server/start.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/server/start.go)) boots up the full-node in the following sequence: 1. It opens the `db` (LevelDB by default) containing the latest persisted state. On first start, this is empty. 2. It creates a new instance of the application via an `appCreator` function, which is the [application constructor](/sdk/latest/learn/intro/sdk-app-architecture#constructor-function). 3. It instantiates a CometBFT node using the application. As part of `node.New`, CometBFT checks that the application's block height matches its own. If the application is behind, it replays blocks to catch up. If the height is `0`, it calls [`InitChain`](/sdk/latest/learn/concepts/baseapp#initchain) to initialize state from the genesis file. 4. Once in sync, the node starts its RPC and P2P servers and begins dialing peers. During the handshake, if the node is behind its peers, it queries missing blocks sequentially. Once caught up, it waits for new block proposals and validator signatures. The previous command allows you to run a single node. This is enough for the next section on interacting with this node, but you may wish to run multiple nodes at the same time, and see how consensus happens between them. The naive way would be to run the same commands again in separate terminal windows. This is possible. However, [Docker Compose](https://docs.docker.com/compose/) can be leveraged to run a localnet. If you need inspiration on how to set up your own localnet with Docker Compose, refer to the Cosmos SDK's [`docker-compose.yml`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docker-compose.yml). ### Standalone App/CometBFT By default, the Cosmos SDK runs CometBFT in-process with the application If you want to run the application and CometBFT in separate processes, start the application with the `--with-comet=false` flag and set `rpc.laddr` in `config.toml` to the CometBFT node's RPC address. ## Logging Logging provides a way to see what is going on with a node. The default logging level is `info`. This is a global level and all info logs will be outputted to the terminal. If you would like to filter specific logs to the terminal instead of all, then setting `module:log_level` is how this can work. Example in `config.toml`: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} log_level: "state:info,p2p:info,consensus:info,x/staking:info,x/ibc:info,*error" ``` ### Verbose log level Some operations, such as chain upgrades, emit additional log messages when a higher log level is active. You can control this with the `--verbose_log_level` flag: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd start --verbose_log_level debug ``` See the [Log Overview](/sdk/latest/guides/testing/log) for more information on logging. ## State Sync State sync is the act in which a node syncs the latest or close to the latest state of a blockchain. This is useful for users who don't want to sync all the blocks in history. Read more in [CometBFT documentation](/cometbft/latest/docs/core/state-sync). State sync works thanks to snapshots. For how the SDK produces and stores them, see the [store/snapshots README](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/store/snapshots/README.md). ### Produce and serve snapshots A node serves snapshots to state-syncing peers only after it takes them. Configure snapshots in `app.toml` under `[state-sync]`: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [state-sync] # Block interval at which the node takes a local snapshot (0 disables snapshots). snapshot-interval = 1000 # Number of recent snapshots to keep and serve (0 keeps all). snapshot-keep-recent = 2 ``` A node with `snapshot-interval = 0` takes no snapshots and cannot serve state sync. The `snapshot-keep-recent` setting bounds how many snapshots the node retains and offers to peers. Restart the node after changing these values. Snapshots work alongside pruning. When pruning is enabled, the SDK retains heights that are multiples of `snapshot-interval` until the snapshot at that height completes, so a pruning node can still produce snapshots. To take a snapshot on demand instead of waiting for the interval, run ` snapshots export`. It snapshots the latest committed height by default, or the height set with `--height`. This command opens the application database directly, so stop the node before running it. The automatic `snapshot-interval` is the only way to produce snapshots without downtime. ### Local State Sync Local state sync works similarly to normal state sync except that it works off a local snapshot of state instead of one provided via the p2p network. The steps to start local state sync are similar to normal state sync with a few different design considerations. 1. As mentioned in the [state sync documentation](/cometbft/latest/docs/core/state-sync), one must set a height and hash in the config.toml along with a few RPC servers (the aforementioned link has instructions on how to do this). 2. Run ` snapshots restore ` to restore a local snapshot (first load it from a file with ` snapshots load`). 3. Bootstrap Comet state to start the node after the snapshot has been ingested. Run ` comet bootstrap-state`. ### Snapshots Commands The Cosmos SDK provides commands for managing snapshots. These commands can be added in an app with the following snippet in `cmd//root.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "github.com/cosmos/cosmos-sdk/client/snapshot" ) func initRootCmd(/* ... */) { // ... rootCmd.AddCommand( snapshot.Cmd(appCreator), ) } ``` Then the following commands are available at ` snapshots [command]`: * **list**: list local snapshots * **load**: Load a snapshot archive file into snapshot store * **restore**: Restore app state from local snapshot * **export**: Export app state to snapshot store * **dump**: Dump the snapshot as portable archive format * **delete**: Delete a local snapshot ## Congratulations! Your node is now running and producing blocks. You have successfully initialized a Cosmos SDK blockchain from scratch. ## Next steps * [Interact with the node](/sdk/latest/node/interact-node) to send transactions and query state * [API reference](/sdk/latest/api-reference/index) for every query method and transaction message the standard modules expose * [Generate and sign transactions](/sdk/latest/node/txs) to learn advanced transaction workflows * [Key rotation](/sdk/latest/keys/key-rotation) to understand the consensus key in `priv_validator_key.json` and how a validator replaces it * [Cosmos-KMS and remote signing](/sdk/latest/kms/remote-signing) to move that key off the node entirely # Running in Production Source: https://docs.cosmos.network/sdk/latest/node/run-production **Synopsis** This section describes how to securely run a node in a public setting and/or on a mainnet on one of the many Cosmos SDK public blockchains. When operating a node, full node, or validator in production it is important to set your server up securely. This walkthrough assumes the underlying operating system is Ubuntu. There are many different ways to secure a server and your node. The steps described here are for informational purposes only. ## Server Setup ### User When creating a server most times it is created as user `root`. This user has heightened privileges on the server. When operating a node, it is recommended to not run your node as the root user. 1. Create a new user ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo adduser change_me ``` 2. We want to allow this user to perform sudo tasks ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo usermod -aG sudo change_me ``` Now when logging into the server, the non `root` user can be used. ### Go 1. Install the [Go](https://go.dev/doc/install) version recommended by the application. In the past, validators [have had issues](https://github.com/cosmos/cosmos-sdk/issues/13976) when using different versions of Go. It is recommended that the whole validator set uses the version of Go that is recommended by the application. ### Firewall Nodes should not have all ports open to the public; this is a simple way to get DDoS'd. Secondly, it is recommended by [CometBFT](https://github.com/cometbft/cometbft) to never expose ports that are not required to operate a node. When setting up a firewall, there are a few ports that can be open when operating a Cosmos SDK node. These include the CometBFT JSON-RPC, Prometheus, p2p, remote signer, and Cosmos SDK gRPC and REST. If the node is being operated as a node that does not offer endpoints to be used for submission or querying, then a maximum of three endpoints are needed. Most, if not all servers come equipped with [ufw](https://help.ubuntu.com/community/UFW). Ufw will be used in this tutorial. 1. Reset UFW to disallow all incoming connections and allow outgoing ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw default deny incoming sudo ufw default allow outgoing ``` 2. Let's make sure that port 22 (SSH) stays open. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw allow ssh ``` or ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw allow 22 ``` Both of the above commands are the same. 3. Allow Port 26656 (cometbft p2p port). If the node has a modified p2p port then that port must be used here. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw allow 26656/tcp ``` 4. Allow port 26660 (CometBFT [Prometheus](https://prometheus.io)). This acts as the application's monitoring port as well. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw allow 26660/tcp ``` 5. If the node which is being set up would like to expose CometBFT's JSON-RPC and Cosmos SDK gRPC and REST, then follow this step. (Optional) ##### CometBFT JSON-RPC ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw allow 26657/tcp ``` ##### Cosmos SDK gRPC ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw allow 9090/tcp ``` ##### Cosmos SDK REST ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw allow 1317/tcp ``` 6. Lastly, enable ufw ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sudo ufw enable ``` ### Signing If the node that is being started is a validator there are multiple ways a validator could sign blocks. #### File File-based signing is the simplest and default approach. This approach works by storing the consensus key generated on initialization to sign blocks. This approach is only as safe as your server setup, as if the server is compromised, so is your key. This key is located in the `config/priv_validator_key.json` file generated on initialization. A second file exists that users must be aware of; the file is located in the data directory `data/priv_validator_state.json`. This file protects your node from double signing. It keeps track of the consensus key's last sign height, round, and latest signature. If the node crashes and needs to be recovered, this file must be kept in order to ensure that the consensus key will not be used for signing a block that was previously signed. #### Remote signer A remote signer is a secondary server that is separate from the running node that signs blocks with the consensus key. This means that the consensus key does not live on the node itself. This increases security because your full node which is connected to the remote signer can be swapped without missing blocks. The Cosmos stack's remote signer is Cosmos-KMS, which holds the consensus key in a file, a PKCS#11 HSM, or AWS KMS. For what remote signing is and how it works, see [Cosmos-KMS and remote signing](/sdk/latest/kms/remote-signing). To set a signer up end to end, follow the [remote signing tutorial](/sdk/latest/kms/tutorial-file-backend), then harden the setup with [remote signing best practices](/sdk/latest/kms/best-practices). TMKMS is the previous remote signer; Cosmos-KMS is the recommended one going forward. Validators running TMKMS should [migrate](/sdk/latest/kms/migrate-from-tmkms). # Running a Testnet Source: https://docs.cosmos.network/sdk/latest/node/run-testnet **Synopsis** The `simd testnet` subcommand makes it easy to initialize and start a simulated test network for testing purposes. In addition to the commands for [running a node](/sdk/latest/node/run-node), the `simd` binary also includes a `testnet` command that allows you to start a simulated test network in-process or to initialize files for a simulated test network that runs in a separate process. ## Initialize Files First, let's take a look at the `init-files` subcommand. This is similar to the `init` command when initializing a single node, but in this case we are initializing multiple nodes, generating the genesis transactions for each node, and then collecting those transactions. The `init-files` subcommand initializes the necessary files to run a test network in a separate process (i.e. using a Docker container). Running this command is not a prerequisite for the `start` subcommand ([see below](#start-testnet)). In order to initialize the files for a test network, run the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd testnet init-files ``` You should see the following output in your terminal: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Successfully initialized 4 node directories ``` The default output directory is a relative `.testnets` directory. Let's take a look at the files created within the `.testnets` directory. ### gentxs The `gentxs` directory includes a genesis transaction for each validator node. Each file includes a JSON encoded genesis transaction used to register a validator node at the time of genesis. The genesis transactions are added to the `genesis.json` file within each node directory during the initialization process. ### nodes A node directory is created for each validator node. Within each node directory is a `simd` directory. The `simd` directory is the home directory for each node, which includes the configuration and data files for that node (i.e. the same files included in the default `~/.simapp` directory when running a single node). ## Start Testnet Now, let's take a look at the `start` subcommand. The `start` subcommand both initializes and starts an in-process test network. This is the fastest way to spin up a local test network for testing purposes. You can start the local test network by running the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd testnet start ``` You should see something similar to the following: ```bash expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} acquiring test network lock preparing test network with chain-id "chain-mtoD9v" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++ THIS MNEMONIC IS FOR TESTING PURPOSES ONLY ++ ++ DO NOT USE IN PRODUCTION ++ ++ ++ ++ sustain know debris minute gate hybrid stereo custom ++ ++ divorce cross spoon machine latin vibrant term oblige ++ ++ moment beauty laundry repeat grab game bronze truly ++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ starting test network... started test network press the Enter Key to terminate ``` The first validator node is now running in-process, which means the test network will terminate once you either close the terminal window or you press the Enter key. In the output, the mnemonic phrase for the first validator node is provided for testing purposes. The validator node is using the same default addresses being used when initializing and starting a single node (no need to provide a `--node` flag). Check the status of the first validator node: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd status ``` Import the key from the provided mnemonic: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd keys add test --recover --keyring-backend test ``` Check the balance of the account address: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q bank balances [address] ``` Use this test account to manually test against the test network. ## Testnet Options You can customize the configuration of the test network with flags. In order to see all flag options, append the `--help` flag to each command. # Generating, Signing and Broadcasting Transactions Source: https://docs.cosmos.network/sdk/latest/node/txs **Synopsis** This document describes how to generate an (unsigned) transaction, signing it (with one or multiple keys), and broadcasting it to the network. ## Using the CLI The easiest way to send transactions is using the CLI, as shown in the previous page when [interacting with a node](/sdk/latest/node/interact-node#using-the-cli). For example, running the following command ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain --keyring-backend test ``` will run the following steps: * generate a transaction with one `Msg` (`x/bank`'s `MsgSend`), and print the generated transaction to the console. * ask the user for confirmation to send the transaction from the `$MY_VALIDATOR_ADDRESS` account. * fetch `$MY_VALIDATOR_ADDRESS` from the keyring. This is possible because the [CLI's keyring was set up](/sdk/latest/node/keyring) in a previous step. * sign the generated transaction with the keyring's account. * broadcast the signed transaction to the network. This is possible because the CLI connects to the node's CometBFT RPC endpoint. The CLI bundles all the necessary steps into a simple-to-use user experience. However, it is possible to run all the steps individually too. ### Generating a Transaction Generating a transaction can simply be done by appending the `--generate-only` flag on any `tx` command, e.g.: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain --generate-only ``` This will output the unsigned transaction as JSON in the console. The unsigned transaction can also be saved to a file (to be passed around between signers more easily) by appending `> unsigned_tx.json` to the above command. For what each field in that JSON means, see [Sending Transactions](/sdk/latest/api-reference/transactions#the-envelope) in the API reference. ### Signing a Transaction Signing a transaction using the CLI requires the unsigned transaction to be saved in a file. For this example, assume the unsigned transaction is in a file called `unsigned_tx.json` in the current directory (see previous paragraph on how to do that). Then, simply run the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx sign unsigned_tx.json --chain-id my-test-chain --keyring-backend test --from $MY_VALIDATOR_ADDRESS ``` This command will decode the unsigned transaction and sign it with `SIGN_MODE_DIRECT` with `$MY_VALIDATOR_ADDRESS`'s key, which was already set up in the keyring. The signed transaction will be output as JSON to the console, and, as above, it can be saved to a file by appending `--output-document signed_tx.json`. Some useful flags to consider in the `tx sign` command: * `--sign-mode`: you may use `amino-json` to sign the transaction using `SIGN_MODE_LEGACY_AMINO_JSON`, * `--offline`: sign in offline mode. This means that the `tx sign` command doesn't connect to the node to retrieve the signer's account number and sequence, both needed for signing. In this case, you must manually supply the `--account-number` and `--sequence` flags. This is useful for offline signing, i.e. signing in a secure environment which doesn't have access to the internet. #### Signing with Multiple Signers Please note that signing a transaction with multiple signers or with a multisig account, where at least one signer uses `SIGN_MODE_DIRECT`, is not yet possible. You may follow [this Github issue](https://github.com/cosmos/cosmos-sdk/issues/8141) for more info. Signing with multiple signers is done with the `tx multisign` command. This command assumes that all signers use `SIGN_MODE_LEGACY_AMINO_JSON`. The flow is similar to the `tx sign` command flow, but instead of signing an unsigned transaction file, each signer signs the file signed by previous signer(s). The `tx multisign` command will append signatures to the existing transactions. It is important that signers sign the transaction **in the same order** as given by the transaction, which is retrievable using the `GetSigners()` method. For example, starting with the `unsigned_tx.json`, and assuming the transaction has 4 signers, we would run: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Let signer1 sign the unsigned tx. simd tx multisign unsigned_tx.json signer_key_1 --chain-id my-test-chain --keyring-backend test > partial_tx_1.json # Now signer1 will send the partial_tx_1.json to the signer2. # Signer2 appends their signature: simd tx multisign partial_tx_1.json signer_key_2 --chain-id my-test-chain --keyring-backend test > partial_tx_2.json # Signer2 sends the partial_tx_2.json file to signer3, and signer3 can append his signature: simd tx multisign partial_tx_2.json signer_key_3 --chain-id my-test-chain --keyring-backend test > partial_tx_3.json ``` ### Broadcasting a Transaction Broadcasting a transaction is done using the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx broadcast tx_signed.json ``` You may optionally pass the `--broadcast-mode` flag to specify which response to receive from the node: * `sync`: the CLI waits for a CheckTx execution response only. * `async`: the CLI returns immediately (transaction might fail). ### Encoding a Transaction In order to broadcast a transaction using the gRPC or REST endpoints, the transaction will need to be encoded first. This can be done using the CLI. Encoding a transaction is done using the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx encode tx_signed.json ``` This will read the transaction from the file, serialize it using Protobuf, and output the transaction bytes as base64 in the console. ### Decoding a Transaction The CLI can also be used to decode transaction bytes. Decoding a transaction is done using the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx decode [protobuf-byte-string] ``` This will decode the transaction bytes and output the transaction as JSON in the console. You can also save the transaction to a file by appending `> tx.json` to the above command. ## Programmatically with Go It is possible to manipulate transactions programmatically via Go using the Cosmos SDK's `TxBuilder` interface. ### Generating a Transaction Before generating a transaction, a new instance of a `TxBuilder` needs to be created. Since the Cosmos SDK supports both Amino and Protobuf transactions, the first step would be to decide which encoding scheme to use. All the subsequent steps remain unchanged, whether you're using Amino or Protobuf, as `TxBuilder` abstracts the encoding mechanisms. In the following snippet, we will use Protobuf. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "github.com/cosmos/cosmos-sdk/simapp" ) func sendTx() error { // Choose your codec: Amino or Protobuf. Here, we use Protobuf, given by the following function. app := simapp.NewSimApp(...) // Create a new TxBuilder. txBuilder := app.TxConfig().NewTxBuilder() // --snip-- } ``` The following example sets up some keys and addresses that will send and receive the transactions. For the purpose of this tutorial, dummy data is used to create keys. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "github.com/cosmos/cosmos-sdk/testutil/testdata" ) priv1, _, addr1 := testdata.KeyTestPubAddr() priv2, _, addr2 := testdata.KeyTestPubAddr() priv3, _, addr3 := testdata.KeyTestPubAddr() ``` Populating the `TxBuilder` can be done via its methods: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package client import ( "time" txsigning "cosmossdk.io/x/tx/signing" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx" signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" ) type ( // TxEncodingConfig defines an interface that contains transaction // encoders and decoders TxEncodingConfig interface { TxEncoder() sdk.TxEncoder TxDecoder() sdk.TxDecoder TxJSONEncoder() sdk.TxEncoder TxJSONDecoder() sdk.TxDecoder MarshalSignatureJSON([]signingtypes.SignatureV2) ([]byte, error) UnmarshalSignatureJSON([]byte) ([]signingtypes.SignatureV2, error) } // TxConfig defines an interface a client can utilize to generate an // application-defined concrete transaction type. The type returned must // implement TxBuilder. TxConfig interface { TxEncodingConfig NewTxBuilder() TxBuilder WrapTxBuilder(sdk.Tx) (TxBuilder, error) SignModeHandler() *txsigning.HandlerMap SigningContext() *txsigning.Context } // TxBuilder defines an interface which an application-defined concrete transaction // type must implement. Namely, it must be able to set messages, generate // signatures, and provide canonical bytes to sign over. The transaction must // also know how to encode itself. TxBuilder interface { GetTx() signing.Tx SetMsgs(msgs ...sdk.Msg) error SetSignatures(signatures ...signingtypes.SignatureV2) error SetMemo(memo string) SetFeeAmount(amount sdk.Coins) SetFeePayer(feePayer sdk.AccAddress) SetGasLimit(limit uint64) SetTimeoutHeight(height uint64) SetTimeoutTimestamp(timestamp time.Time) SetUnordered(v bool) SetFeeGranter(feeGranter sdk.AccAddress) AddAuxSignerData(tx.AuxSignerData) error } // ExtendedTxBuilder extends the TxBuilder interface, // which is used to set extension options to be included in a transaction. ExtendedTxBuilder interface { SetExtensionOptions(extOpts ...*codectypes.Any) } ) ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) func sendTx() error { // --snip-- // Define two x/bank MsgSend messages: // - from addr1 to addr3 // - from addr2 to addr3 // This means that the transaction needs two signers: addr1 and addr2. msg1 := banktypes.NewMsgSend(addr1, addr3, sdk.NewCoins(sdk.NewInt64Coin("atom", 12))) msg2 := banktypes.NewMsgSend(addr2, addr3, sdk.NewCoins(sdk.NewInt64Coin("atom", 34))) err := txBuilder.SetMsgs(msg1, msg2) if err != nil { return err } txBuilder.SetGasLimit(...) txBuilder.SetFeeAmount(...) txBuilder.SetMemo(...) txBuilder.SetTimeoutHeight(...) } ``` At this point, `TxBuilder`'s underlying transaction is ready to be signed. #### Generating an Unordered Transaction Starting with Cosmos SDK v0.53.0, users may send unordered transactions to chains that have the feature enabled. Unordered transactions MUST leave sequence values unset. When a transaction is both unordered and contains a non-zero sequence value, the transaction will be rejected. External services that operate on prior assumptions about transaction sequence values should be updated to handle unordered transactions. Services should be aware that when the transaction is unordered, the transaction sequence will always be zero. Using the example above, we can set the required fields to mark a transaction as unordered. By default, unordered transactions charge an extra 2240 units of gas to offset the additional storage overhead that supports their functionality. The extra units of gas are customizable and therefore vary by chain, so be sure to check the chain's ante handler for the gas value set, if any. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func sendTx() error { // --snip-- expiration := 5 * time.Minute txBuilder.SetUnordered(true) txBuilder.SetTimeoutTimestamp(time.Now().Add(expiration + (1 * time.Nanosecond))) } ``` Unordered transactions from the same account must use a unique timeout timestamp value. The difference between each timeout timestamp value may be as small as a nanosecond, however. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "github.com/cosmos/cosmos-sdk/client" ) func sendMessages(txBuilders []client.TxBuilder) error { // --snip-- expiration := 5 * time.Minute for _, txb := range txBuilders { txb.SetUnordered(true) txb.SetTimeoutTimestamp(time.Now().Add(expiration + (1 * time.Nanosecond))) } } ``` ### Signing a Transaction The encoding config is set to use Protobuf, which will use `SIGN_MODE_DIRECT` by default. As per [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/docs/architecture/adr-020-protobuf-transaction-encoding.md), each signer needs to sign the `SignerInfo`s of all other signers. This means that two steps must be performed sequentially: * for each signer, populate the signer's `SignerInfo` inside `TxBuilder` * once all `SignerInfo`s are populated, for each signer, sign the `SignDoc` (the payload to be signed). In the current `TxBuilder`'s API, both steps are done using the same method: `SetSignatures()`. The current API requires a first round of `SetSignatures()` *with empty signatures*, only to populate `SignerInfo`s, and a second round of `SetSignatures()` to actually sign the correct payload. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/types/tx/signing" xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) func sendTx() error { // --snip-- privs := []cryptotypes.PrivKey{ priv1, priv2 } accNums:= []uint64{..., ... } // The accounts' account numbers accSeqs:= []uint64{..., ... } // The accounts' sequence numbers // First round: we gather all the signer infos. We use the "set empty // signature" hack to do that. var sigsV2 []signing.SignatureV2 for i, priv := range privs { sigV2 := signing.SignatureV2{ PubKey: priv.PubKey(), Data: &signing.SingleSignatureData{ SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(), Signature: nil, }, Sequence: accSeqs[i], } sigsV2 = append(sigsV2, sigV2) } err := txBuilder.SetSignatures(sigsV2...) if err != nil { return err } // Second round: all signer infos are set, so each signer can sign. sigsV2 = []signing.SignatureV2{ } for i, priv := range privs { signerData := xauthsigning.SignerData{ ChainID: chainID, AccountNumber: accNums[i], Sequence: accSeqs[i], } sigV2, err := tx.SignWithPrivKey( encCfg.TxConfig.SignModeHandler().DefaultMode(), signerData, txBuilder, priv, encCfg.TxConfig, accSeqs[i]) if err != nil { return nil, err } sigsV2 = append(sigsV2, sigV2) } err = txBuilder.SetSignatures(sigsV2...) if err != nil { return err } } ``` The `TxBuilder` is now correctly populated. To print it, you can use the `TxConfig` interface from the initial encoding config `encCfg`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func sendTx() error { // --snip-- // Generated Protobuf-encoded bytes. txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx()) if err != nil { return err } // Generate a JSON string. txJSONBytes, err := encCfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) if err != nil { return err } txJSON := string(txJSONBytes) } ``` ### Broadcasting a Transaction The preferred way to broadcast a transaction is to use gRPC, though using REST (via `gRPC-gateway`) or the CometBFT RPC is also possible. An overview of the differences between these methods is exposed [here](/sdk/latest/learn/concepts/cli-grpc-rest). For this tutorial, we will only describe the gRPC method. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "context" "fmt" "google.golang.org/grpc" "github.com/cosmos/cosmos-sdk/types/tx" ) func sendTx(ctx context.Context) error { // --snip-- // Create a connection to the gRPC server. grpcConn, err := grpc.Dial( "127.0.0.1:9090", // Or your gRPC server address. grpc.WithInsecure(), // The Cosmos SDK doesn't support any transport security mechanisms. ) if err != nil { return err } defer grpcConn.Close() // Broadcast the tx via gRPC. We create a new client for the Protobuf Tx // service. txClient := tx.NewServiceClient(grpcConn) // We then call the BroadcastTx method on this client. grpcRes, err := txClient.BroadcastTx( ctx, &tx.BroadcastTxRequest{ Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC, TxBytes: txBytes, // Proto-binary of the signed transaction, see previous step. }, ) if err != nil { return err } fmt.Println(grpcRes.TxResponse.Code) // Should be `0` if the tx is successful return nil } ``` #### Simulating a Transaction Before broadcasting a transaction, we sometimes may want to dry-run the transaction to estimate some information about the transaction without actually committing it. This is called simulating a transaction, and can be done as follows: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "context" "fmt" "testing" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/types/tx" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" ) func simulateTx() error { // --snip-- // Simulate the tx via gRPC. We create a new client for the Protobuf Tx // service. txClient := tx.NewServiceClient(grpcConn) txBytes := /* Fill in with your signed transaction bytes. */ // We then call the Simulate method on this client. grpcRes, err := txClient.Simulate( context.Background(), &tx.SimulateRequest{ TxBytes: txBytes, }, ) if err != nil { return err } fmt.Println(grpcRes.GasInfo) // Prints estimated gas used. return nil } ``` ## Using gRPC It is not possible to generate or sign a transaction using gRPC, only to broadcast one. In order to broadcast a transaction using gRPC, you will need to generate, sign, and encode the transaction using either the CLI or programmatically with Go. ### Broadcasting a Transaction Broadcasting a transaction using the gRPC endpoint can be done by sending a `BroadcastTx` request as follows, where the `txBytes` are the protobuf-encoded bytes of a signed transaction: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"tx_bytes":"{{txBytes}}","mode":"BROADCAST_MODE_SYNC"}' \ localhost:9090 \ cosmos.tx.v1beta1.Service/BroadcastTx ``` ## Using REST It is not possible to generate or sign a transaction using REST, only to broadcast one. In order to broadcast a transaction using REST, you will need to generate, sign, and encode the transaction using either the CLI or programmatically with Go. ### Broadcasting a Transaction Broadcasting a transaction using the REST endpoint (served by `gRPC-gateway`) can be done by sending a POST request as follows, where the `txBytes` are the protobuf-encoded bytes of a signed transaction: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST \ -H "Content-Type: application/json" \ -d'{"tx_bytes":"{{txBytes}}","mode":"BROADCAST_MODE_SYNC"}' \ localhost:1317/cosmos/tx/v1beta1/txs ``` ## Using CosmJS (JavaScript & TypeScript) CosmJS aims to build client libraries in JavaScript that can be embedded in web applications. Please see [Link](https://cosmos.github.io/cosmjs) for more information. ## Congratulations! You have learned how to manually generate, sign, and broadcast transactions using the Cosmos SDK. These workflows provide the foundation for building custom transaction tools and integrations. ## Next steps * [Run in production](/sdk/latest/node/run-production) for security and deployment best practices. * [Run a testnet](/sdk/latest/node/run-testnet) to test your blockchain. # Cosmos SDK Repository Source: https://docs.cosmos.network/sdk/latest/reference/cosmos-sdk-repo # Tutorial Example Repository Source: https://docs.cosmos.network/sdk/latest/reference/example-repo # Release Families Source: https://docs.cosmos.network/sdk/latest/release-family What release families are, what they contain, and how upgrades work. ## Overview A release family is a curated set of component versions across the Cosmos Stack that are tested for compatibility with one another. Cosmos Labs provides maintenance and bug fixes only for active families. This page is the canonical source of truth for release family lifecycle, active support windows, and retirement expectations. ## What a Release Family Contains Each release family includes pinned versions of the following components: * [CometBFT](https://github.com/cometbft/cometbft) * [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) * [Cosmos EVM](https://github.com/cosmos/evm) * [IBC Go](https://github.com/cosmos/ibc-go) * [Solidity IBC Eureka](https://github.com/cosmos/solidity-ibc-eureka) * [Relayer](https://github.com/cosmos/ibc-relayer) * [Attestor](https://github.com/cosmos/ibc-attestor) The goal is to guarantee that every version listed in a family is compatible with every other version in that family. Certain packages within the SDK may not be listed as Cosmos Labs consolidates separate Go modules over time. ## Current Release Families ### 2026.1 | Component | Version | | ----------------------------------------------------------------------------------------------- | ------- | | [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) | 0.55.x | | [Enterprise Groups](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/enterprise/group) | 1.x.y | | [Enterprise PoA](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/enterprise/poa) | 1.x.y | | [CometBFT](https://github.com/cometbft/cometbft) | 0.40.x | | [Cosmos EVM](https://github.com/cosmos/evm) | 0.7.x | | [IBC Go](https://github.com/cosmos/ibc-go) | v11.x.y | | [Solidity IBC Eureka](https://github.com/cosmos/solidity-ibc-eureka) | 3.0.x | | [Relayer](https://github.com/cosmos/ibc-relayer) | 1.1.x | | [Attestor](https://github.com/cosmos/ibc-attestor) | 1.0.x | ### 2025.1 | Component | Version | | -------------------------------------------------- | ------- | | [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) | 0.53.x | | [CometBFT](https://github.com/cometbft/cometbft) | 0.38.x | | [Cosmos EVM](https://github.com/cosmos/evm) | 0.6.x | | [IBC Go](https://github.com/cosmos/ibc-go) | v10.x.y | ## Upgrades and Support Supported versions within a release family are updated over time, and upgrade paths are provided where generalized upgrades make sense. New release families include breaking changes from the previous family. New features are only considered for backporting to the most recent release family, and only when they are non-breaking. Cosmos Labs supports up to two release families at a time. Release cadence targets two new release families per year. If a planned successor family is delayed, the most recent supported family remains active until its successor is formally released. Lifecycle policy applies to families, not individual component versions in isolation. A component version is supported only when it appears in an active release family. For security reporting and vulnerability handling details, see the [Security and Maintenance Policy](/sdk/latest/security/security-policy). ## End of Life Notices The following releases are end of life and no longer receive maintenance, security patches, or compatibility support from Cosmos Labs: * CometBFT v0.37.x and lower * ibc-go v0.7.x and lower * Cosmos EVM v0.5.x and lower * Cosmos SDK v0.50.x and lower CometBFT v1.x is not supported. That release line was retracted and is not part of any supported release family. # Node Tutorial Source: https://docs.cosmos.network/sdk/latest/tutorials Version: v0.55 This guide covers everything you need to run, configure, and maintain a Cosmos SDK node. Whether you're setting up a local development node, deploying to a testnet, or running production infrastructure, you'll find step-by-step instructions and best practices. The node tutorial uses the `simapp` example application and its corresponding CLI binary `simd` as the blockchain application and CLI. You can view the source code for `simapp` [on GitHub](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/simapp). Learn the fundamentals of running a node, from initial setup through keyring management and starting your node. Connect to your node and query data using CLI, gRPC, or REST endpoints. Create, sign, and broadcast transactions to your node using various methods. Configure and deploy nodes for testnet environments and production networks. Monitor your node's health and performance using built-in telemetry and metrics collection. Manage chain upgrades and migrations safely using in-place upgrade mechanisms and Cosmovisor. # Tutorial Intro Source: https://docs.cosmos.network/sdk/latest/tutorials/example/00-overview Build a module from scratch, wire it into a chain, and run it locally, all in minutes. The Cosmos SDK is a developer-first framework for building custom blockchains. This tutorial series shows you how to build a module from scratch, wire it into a chain, and run it locally, all in minutes. By the end, you will have: * A working Cosmos SDK chain running on your machine * A custom module you built yourself, wired into the chain * A clear mental model of how modules, keepers, messages, and queries fit together This series starts from zero; you don't need any prior Cosmos SDK experience to follow along. ## The example repo All tutorials in this series are based on [cosmos/example](https://github.com/cosmos/example), a reference Cosmos SDK chain built around a custom `x/counter` module. The repo has two main branches: * `main`: the complete chain with the full `x/counter` module wired in. This is used in the [Quickstart guide](/sdk/latest/tutorials/example/02-quickstart). * `tutorial/start`: the same chain without the counter module. The `x/counter` directory and its app wiring are stripped out so you can build the module from scratch by [following the tutorial](/sdk/latest/tutorials/example/03-build-a-module). If you want to follow along and build the module yourself, start from `tutorial/start`. If you want to browse the finished implementation first, use `main`. ## What's in this series 1. [Prerequisites](/sdk/latest/tutorials/example/01-prerequisites): Install Go, Make, Docker, and Git. Clone the repo and get familiar with the layout. 2. [Quickstart](/sdk/latest/tutorials/example/02-quickstart): Build and run the chain in minutes. Submit a transaction, query the result, and see the counter module in action before you build it yourself. 3. [Build a Module from Scratch](/sdk/latest/tutorials/example/03-build-a-module): Build a minimal counter module step by step: proto definitions, keeper, message server, query server, and app wiring. Start here if you want to understand how a module comes together. 4. [Full Module Walkthrough](/sdk/latest/tutorials/example/04-counter-walkthrough): Walk through the complete `x/counter` implementation on `main`. Covers everything added on top of the minimal module: params, governance-gated authority, validation, fees, sentinel errors, telemetry, AutoCLI, simulation, block hooks, and a full unit test suite. 5. [Run and Test](/sdk/latest/tutorials/example/05-run-and-test): Learn the full development workflow: running a local chain, using the CLI, and working with the three layers of testing: unit tests, end-to-end tests, and simulation. # Prerequisites Source: https://docs.cosmos.network/sdk/latest/tutorials/example/01-prerequisites Install dependencies Before starting the tutorial, make sure you have the following tools installed on your machine. This tutorial is intended for macOS and Linux systems. Other systems may have additional requirements. ## Go The example chain requires Go 1.26 or higher. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go version # go version go1.26.5 linux/amd64 # Linux # go version go1.26.5 darwin/arm64 # macOS ``` If Go is not installed, download it from [go.dev/dl](https://go.dev/dl). ### Configure Go Environment Variables After installing Go, make sure `$GOPATH/bin` is on your `PATH` so installed binaries (like `exampled`) are accessible. Open your shell config file (`~/.zshrc` on macOS or `~/.bashrc` on Linux) and add: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin ``` Then apply the changes: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} source ~/.zshrc # macOS source ~/.bashrc # Linux ``` Verify: `go env GOPATH` ## Make Make is used to run build and development commands throughout the tutorial. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make --version # GNU Make 3.81 ``` Make is pre-installed on most Linux and macOS systems. If it is missing: * **macOS:** `xcode-select --install` * **Linux (Debian/Ubuntu):** `sudo apt install build-essential` ## Docker Docker is required to run `make proto-gen`, which generates Go code from the module's proto files using [buf](https://buf.build). ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} docker --version # Docker version 29.2.1 ``` Download Docker from [docs.docker.com/get-docker](https://docs.docker.com/get-docker). Docker must be running before you execute `make proto-gen`. ## Git ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git --version # git version 2.52.0 ``` ## Clone the repository Clone [cosmos/example](https://github.com/cosmos/example) and navigate into it: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git clone https://github.com/cosmos/example cd example ``` The repo has two branches used in this tutorial series: * `main` — the complete chain with the full `x/counter` module wired in. * `tutorial/start` — the same chain with the counter module stripped out. Start here if you want to build the module yourself from scratch. ## Repository Layout After cloning, the repository looks like this: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} example/ ├── exampled/ # Binary entrypoint (main.go + CLI root command) ├── app.go # Chain application, module wiring lives here ├── proto/ # Proto definitions for all modules ├── x/ # Module implementations │ └── counter/ # The example counter module ├── tests/ # E2E and integration tests ├── scripts/ # Local node and proto generation scripts ├── docs/ # This tutorial series └── Makefile # Build, test, and dev commands ``` ## Where things live The tutorials in this section will walk you through the most common kinds of chain changes and show you where they usually live in the repo: * Add or modify a module: `x//` and `proto/` * Wire a module into the chain: `app.go` * Change the binary or CLI: `exampled/` * Run the chain or tests: `Makefile` targets *** Next: [Quickstart →](/sdk/latest/tutorials/example/02-quickstart) # Chain Quickstart Source: https://docs.cosmos.network/sdk/latest/tutorials/example/02-quickstart Start a chain, submit a transaction, and query the result in minutes Building on Cosmos is simple: you can start a chain with a [single command](#start-the-chain). This quickstart gets you from zero to a running chain, a submitted transaction, and a queried result in minutes. `exampled` is a simple Cosmos SDK chain that shows the core pieces of a working app chain. It includes the basic building-block modules for accounts, bank, staking, distribution, slashing, governance, and more, plus a custom `x/counter` module. In the next tutorials, you'll build a simple version of that module yourself and then walk through the full implementation. Before continuing, make sure you have completed the [Prerequisites](/sdk/latest/tutorials/example/01-prerequisites) to get your environment set up. ## Install the binary Run the following to compile the `exampled` binary and place it on your `$PATH`. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make install ``` Verify the install by running: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled version ``` You can also run the following to see all available node CLI commands: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled ``` ## Start the chain Run the following to start a single-node local chain. It handles all setup automatically: initializes the chain data, creates test accounts, and starts the node. Leave it running in this terminal. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make start ``` ## Query the counter Open a second terminal and query the current count: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query counter count ``` You should see the following output, which means the counter is starting at `0`: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {} ``` You can also query the module parameters: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query counter params ``` This shows that the fee to increment the counter is stored as a module parameter. The base coin denomination for the `exampled` chain is `stake`. ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} params: add_cost: - amount: "100" denom: stake max_add_value: "100" ``` ## Submit an add transaction Send an `Add` transaction to increment the counter. This charges a fee from the funded `alice` account you are sending the transaction from: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled tx counter add 5 --from alice --chain-id demo --yes ``` ## Query the counter again After submitting the transaction, query the counter again to see the updated module state: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query counter count ``` You should see the following: ``` count: "5" ``` Congratulations! You just ran a blockchain, submitted a transaction, and queried module state. ## Next steps In the following tutorials, you will: 1. Build a minimal version of this module from scratch to understand the core pattern 2. Walk through the full `x/counter` module example to see what it adds 3. See how modules are wired into a chain and how to run the full test suite Next: [Build a Module from Scratch →](/sdk/latest/tutorials/example/03-build-a-module) # Build a Module from Scratch Source: https://docs.cosmos.network/sdk/latest/tutorials/example/03-build-a-module Build a simple counter module from scratch in minutes In [quickstart](/sdk/latest/tutorials/example/02-quickstart), you started a chain and submitted a transaction to increase the counter. In this tutorial, you'll build a simple counter module from scratch. It follows the same overall structure as the full `x/counter`, but uses a stripped-down version so you can focus on the core steps of building and wiring a module yourself. By the end, you'll have built a working module and wired it into a running chain. For a deeper dive into how modules work in the Cosmos SDK, see [Intro to Modules](/sdk/latest/learn/concepts/modules). Before continuing, you must follow the [Prerequisites guide](/sdk/latest/tutorials/example/01-prerequisites) to make sure everything is installed. ## Making modules The Cosmos SDK makes it easy to build custom business logic directly into your chain through modules. Every module follows the same overall pattern: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} proto files → code generation → keeper → msg server → query server → module.go → app wiring ``` First, you'll define what the module does: * Define messages: users can send `Add` to increase the counter * Define queries: users can query `Count` to read the current value * Define genesis state: the module starts with a count of `0` Then you'll wire that behavior into the SDK: * Run `proto-gen` to generate the Go types and interfaces * Implement your business logic in a `keeper` to store the count and update it * Implement `MsgServer` and `QueryServer` to pass messages and queries into the keeper * Register the module in `module.go` * Wire it into the chain in `app.go` You'll build the following module structure: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} proto/example/counter/v1/ ├── tx.proto # Transaction message and Msg service definition ├── query.proto # Query message and Query service definition └── genesis.proto # Genesis state definition x/counter/ ├── keeper/ │ ├── keeper.go # Keeper struct and state methods │ ├── msg_server.go # MsgServer implementation │ └── query_server.go # QueryServer implementation ├── types/ │ ├── keys.go # Module name and store key constants │ ├── codec.go # Interface registration │ └── *.pb.go # Generated from proto — do not edit ├── module.go # AppModule wiring └── autocli.go # CLI command definitions ``` ## Step 1: Setup This tutorial uses the `tutorial/start` branch, which is a blank template for you to create the module from scratch and wire it into `app.go`. 1. Clone the repo if you haven't already: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git clone https://github.com/cosmos/example cd example ``` 2. Check out the `tutorial/start` branch and make the new module directories: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git checkout tutorial/start mkdir -p x/counter/keeper x/counter/types proto/example/counter/v1 ``` You should see empty placeholder directories at `x/counter/` and `proto/example/counter/v1/`. ## Step 2: Proto files Proto files are the source of truth for the module's public API. You define messages and services here. For a deeper look at how protobuf is used across modules, see [Encoding and Protobuf](/sdk/latest/learn/concepts/encoding#how-protobuf-is-used-in-modules). In this tutorial, the counter module stores one number, `Add` increases it by the amount the user submits, and the query returns the current value. First, create the three proto files: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} touch proto/example/counter/v1/tx.proto \ proto/example/counter/v1/query.proto \ proto/example/counter/v1/genesis.proto ``` Then add the following contents to each file. ### tx.proto This is the first module file you define. It declares the transaction message shape for `Add`: what the user sends to increment the counter, and what the module returns after handling it. To learn more about how messages are defined and routed, see [Messages](/sdk/latest/learn/concepts/transactions#messages). Add the following code to `tx.proto`. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} syntax = "proto3"; // Matches the module's protobuf namespace. package example.counter; // Provides Cosmos SDK message annotations like signer and service markers. import "cosmos/msg/v1/msg.proto"; // Generated Go types are written into x/counter/types. option go_package = "github.com/cosmos/example/x/counter/types"; service Msg { // Marks this as a transaction service, not a normal gRPC service. option (cosmos.msg.v1.service) = true; // Add is the one transaction this minimal module supports. rpc Add(MsgAddRequest) returns (MsgAddResponse); } message MsgAddRequest { // The sender signs this message. option (cosmos.msg.v1.signer) = "sender"; string sender = 1; uint64 add = 2; } message MsgAddResponse { // Return the new counter value after the add succeeds. uint64 updated_count = 1; } ``` ### query.proto This file defines the read-only gRPC query service and the response type for fetching the current count. To learn more about how queries differ from transactions, see [Queries](/sdk/latest/learn/concepts/transactions#queries). Add the following code to `query.proto`. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} syntax = "proto3"; // Matches the module's protobuf namespace. package example.counter; // Enables the REST gateway route annotation below. import "google/api/annotations.proto"; // Generated Go types are written into x/counter/types. option go_package = "github.com/cosmos/example/x/counter/types"; service Query { rpc Count(QueryCountRequest) returns (QueryCountResponse) { // Exposes this query over the HTTP API as well as gRPC. option (google.api.http).get = "/example/counter/v1/count"; } } // Empty because this query only needs the module's current state. message QueryCountRequest {} message QueryCountResponse { // The current counter value. uint64 count = 1; } ``` ### genesis.proto This file defines the data the module stores in genesis so the counter can be initialized when the chain starts. Add the following code to `genesis.proto`. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} syntax = "proto3"; // Matches the module's protobuf namespace. package example.counter; // Generated Go types are written into x/counter/types. option go_package = "github.com/cosmos/example/x/counter/types"; message GenesisState { // The counter value to load when the chain initializes. uint64 count = 1; } ``` ## Step 3: Generate Code 1. Make sure Docker is running. 2. The first time you run proto-gen you need to build the builder image. Run the following commands: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make proto-image-build make proto-gen ``` This compiles the proto files using [buf](https://buf.build) inside Docker to produce the Go interfaces you will then implement. The generated files will appear in `x/counter/types/`: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} x/counter/types/ ├── tx.pb.go # MsgAddRequest, MsgAddResponse, MsgServer interface ├── query.pb.go # QueryCountRequest, QueryCountResponse, QueryServer interface ├── query.pb.gw.go # REST gateway registration └── genesis.pb.go # GenesisState ``` > **Do not edit generated files.** Changes to public types belong in the proto files. Re-run `make proto-gen` after any proto change. The most important generated output is the `MsgServer` and `QueryServer` interfaces. In Steps 5 and 6, you'll implement them in `keeper/msg_server.go` and `keeper/query_server.go`. ## Step 4: Types Next, you'll define the module types and identifiers in `x/counter/types` that the rest of the module depends on. Create the two files for this section: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} touch x/counter/types/keys.go \ x/counter/types/codec.go ``` Then add the following contents to each file. ### keys.go This file defines the module's basic identifiers: the module name used throughout the SDK, and the store key used to claim the module's KV store namespace. For more on how modules access state through store keys, see [How modules access state](/sdk/latest/learn/concepts/store#how-modules-access-state). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/types/keys.go package types const ( // ModuleName is the name the SDK uses to refer to this module. ModuleName = "counter" // StoreKey is the key for this module's KV store. StoreKey = ModuleName ) ``` `ModuleName` identifies the module throughout the SDK (routing, events, governance). `StoreKey` is the key used to claim the module's isolated namespace in the chain's KV store (set equal to `ModuleName` by convention). ### Interface Registration This file registers your generated message types with the SDK interface registry so the application can decode and route your module's transactions correctly. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/types/codec.go package types import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" ) func RegisterInterfaces(registry codectypes.InterfaceRegistry) { // Register MsgAddRequest as an sdk.Msg so the app can decode it from transactions. registry.RegisterImplementations((*sdk.Msg)(nil), &MsgAddRequest{}, ) // Register the generated Msg service description for routing. msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } ``` `_Msg_serviceDesc` is generated by `make proto-gen` — it describes the `Msg` gRPC service defined in `tx.proto`. ## Step 5: Keeper In this step, you create the keeper, which is the part of the module that owns the counter state and provides the methods the rest of the module will call. For a conceptual overview of the keeper's role, see [Keeper](/sdk/latest/learn/concepts/modules#keeper). Create the keeper file: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} touch x/counter/keeper/keeper.go ``` Then add the following contents. This file defines the keeper struct, sets up the counter's storage item, and implements the core state methods for reading, updating, and loading the counter at genesis. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/keeper/keeper.go package keeper import ( "context" "errors" "cosmossdk.io/collections" "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/example/x/counter/types" ) type Keeper struct { Schema collections.Schema counter collections.Item[uint64] } func NewKeeper(storeService store.KVStoreService, cdc codec.Codec) *Keeper { sb := collections.NewSchemaBuilder(storeService) k := Keeper{ // Store the counter under prefix 0 in this module's KV store. counter: collections.NewItem(sb, collections.NewPrefix(0), "counter", collections.Uint64Value), } schema, err := sb.Build() if err != nil { panic(err) } k.Schema = schema return &k } func (k *Keeper) GetCount(ctx context.Context) (uint64, error) { count, err := k.counter.Get(ctx) // Treat missing state as zero so a fresh chain starts cleanly. if err != nil && !errors.Is(err, collections.ErrNotFound) { return 0, err } return count, nil } func (k *Keeper) AddCount(ctx context.Context, amount uint64) (uint64, error) { count, err := k.GetCount(ctx) if err != nil { return 0, err } // Increment the current count and write it back to state. newCount := count + amount return newCount, k.counter.Set(ctx, newCount) } func (k *Keeper) InitGenesis(ctx context.Context, gs *types.GenesisState) error { return k.counter.Set(ctx, gs.Count) } func (k *Keeper) ExportGenesis(ctx context.Context) (*types.GenesisState, error) { count, err := k.GetCount(ctx) if err != nil { return nil, err } return &types.GenesisState{Count: count}, nil } ``` `collections.Item[uint64]` is a typed KV store entry; the `collections` package handles encoding and namespacing. `GetCount` treats `ErrNotFound` as zero so the counter starts at zero without explicit initialization. > **State layout** > > * `StoreKey` (`"counter"`) is the module's isolated namespace within the chain's global KV store. No other module can read or write this namespace. > * `collections.NewPrefix(0)` is a single-byte prefix that identifies the `counter` item within the module's namespace. A module with multiple items would use `NewPrefix(0)`, `NewPrefix(1)`, etc. to keep them separate. > * `ErrNotFound` treated as zero means the keeper never needs to explicitly set an initial value — the first `GetCount` call on a fresh chain returns `0` by convention. ## Step 6: MsgServer In this step, you implement the transaction handler for the generated `MsgServer` interface. This is the code path that runs when a user submits `tx counter add`. For a conceptual overview of message execution, see [Message execution](/sdk/latest/learn/concepts/modules#message-execution-msgserver). Create the message server file: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} touch x/counter/keeper/msg_server.go ``` Then add the following contents. This file implements the generated `MsgServer` interface and forwards the `Add` transaction to the keeper's `AddCount` method. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/keeper/msg_server.go package keeper import ( "context" "github.com/cosmos/example/x/counter/types" ) type msgServer struct { *Keeper } func NewMsgServerImpl(k *Keeper) types.MsgServer { return &msgServer{k} } func (m msgServer) Add(ctx context.Context, req *types.MsgAddRequest) (*types.MsgAddResponse, error) { // Delegate the state update to the keeper. newCount, err := m.AddCount(ctx, req.GetAdd()) if err != nil { return nil, err } // Return the updated count back to the caller. return &types.MsgAddResponse{UpdatedCount: newCount}, nil } ``` `msgServer` embeds `*Keeper` and delegates directly to `AddCount`. The handler itself contains no business logic. ## Step 7: QueryServer In this step, you implement the read-only query handler for the generated `QueryServer` interface. This is the code path that runs when someone queries the current counter value. For more on how modules expose queries, see [Queries](/sdk/latest/learn/concepts/modules#queries). Create the query server file: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} touch x/counter/keeper/query_server.go ``` Then add the following contents. This file implements the generated `QueryServer` interface and returns the current counter value from the keeper. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/keeper/query_server.go package keeper import ( "context" "github.com/cosmos/example/x/counter/types" ) type queryServer struct { *Keeper } func NewQueryServer(k *Keeper) types.QueryServer { return &queryServer{k} } func (q queryServer) Count(ctx context.Context, _ *types.QueryCountRequest) (*types.QueryCountResponse, error) { // Read the current count from state and return it in the query response. count, err := q.GetCount(ctx) if err != nil { return nil, err } return &types.QueryCountResponse{Count: count}, nil } ``` ## Step 8: module.go In this step, you connect your keeper and generated services to the Cosmos SDK module framework so the application knows how to initialize the module, expose its query routes, and register its transaction handlers. Create the module file: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} touch x/counter/module.go ``` Then add the following contents. This file defines the app module types and wires your keeper into genesis handling, service registration, and gRPC gateway registration. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/module.go package counter import ( "context" "encoding/json" "cosmossdk.io/core/appmodule" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codecTypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/cosmos/example/x/counter/keeper" countertypes "github.com/cosmos/example/x/counter/types" ) var ( // Compile-time checks that AppModule implements the required module interfaces. _ appmodule.AppModule = AppModule{} _ module.HasConsensusVersion = AppModule{} _ module.HasGenesis = AppModule{} _ module.HasServices = AppModule{} ) type AppModuleBasic struct { cdc codec.Codec } func (a AppModuleBasic) Name() string { return countertypes.ModuleName } func (a AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} func (a AppModuleBasic) RegisterInterfaces(registry codecTypes.InterfaceRegistry) { countertypes.RegisterInterfaces(registry) } func (a AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { // Start the module with a zero counter by default. return cdc.MustMarshalJSON(&countertypes.GenesisState{Count: 0}) } func (a AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { gs := countertypes.GenesisState{} return cdc.UnmarshalJSON(bz, &gs) } func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { // Expose the Query service through the HTTP gateway. if err := countertypes.RegisterQueryHandlerClient(context.Background(), mux, countertypes.NewQueryClient(clientCtx)); err != nil { panic(err) } } type AppModule struct { AppModuleBasic keeper *keeper.Keeper } func NewAppModule(cdc codec.Codec, k *keeper.Keeper) AppModule { return AppModule{AppModuleBasic: AppModuleBasic{cdc: cdc}, keeper: k} } func (a AppModule) IsOnePerModuleType() {} func (a AppModule) IsAppModule() {} func (a AppModule) ConsensusVersion() uint64 { return 1 } func (a AppModule) RegisterServices(cfg module.Configurator) { // Connect the generated service interfaces to your keeper-backed implementations. countertypes.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(a.keeper)) countertypes.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServer(a.keeper)) } func (a AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, bz json.RawMessage) { gs := &countertypes.GenesisState{} cdc.MustUnmarshalJSON(bz, gs) // Load the initial counter value into state at chain start. if err := a.keeper.InitGenesis(ctx, gs); err != nil { panic(err) } } func (a AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { gs, err := a.keeper.ExportGenesis(ctx) if err != nil { panic(err) } // Write the current counter value back out for exports. return cdc.MustMarshalJSON(gs) } ``` The `var _ interface = Struct{}` block at the top is a Go compile-time check — if the struct is missing any required method, the build fails immediately. `RegisterServices` is the most important method. It connects the generated server interfaces to your implementations, making them reachable from the SDK's message and query routers. ## Step 9: AutoCLI In this step, you define the CLI metadata for your module. AutoCLI reads this configuration together with your proto services and generates the `exampled query counter` and `exampled tx counter` commands automatically. Create the AutoCLI file: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} touch x/counter/autocli.go ``` Then add the following contents. This file tells `AutoCLI` how to expose the `Count` query and `Add` transaction as simple command-line commands. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/autocli.go package counter import ( autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" ) func (a AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { return &autocliv1.ModuleOptions{ Query: &autocliv1.ServiceCommandDescriptor{ Service: "example.counter.Query", RpcCommandOptions: []*autocliv1.RpcCommandOptions{ // exampled query counter count {RpcMethod: "Count", Use: "count", Short: "Query the current counter value"}, }, }, Tx: &autocliv1.ServiceCommandDescriptor{ Service: "example.counter.Msg", RpcCommandOptions: []*autocliv1.RpcCommandOptions{ // exampled tx counter add 4 --from alice {RpcMethod: "Add", Use: "add [amount]", Short: "Add to the counter", PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "add"}}}, }, }, } } ``` `PositionalArgs` maps the first CLI argument to the `add` field in `MsgAddRequest`, so `add 4` works instead of `add --add 4`. ## Step 10: Wire into app.go In this step, you wire your new module into the application so the chain creates its store, constructs its keeper, and includes it in module startup and genesis handling. For a full explanation of what `app.go` does and why the wiring order matters, see [app.go Overview](/sdk/latest/learn/concepts/app-go). Open `app.go` and find each marker comment. Paste the code directly below it. ### 1. Imports Add the counter module, keeper, and shared types imports to `app.go`. Find the comment in `app.go` and add the code directly below it. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // counter tutorial app wiring 1: add counter imports below counter "github.com/cosmos/example/x/counter" counterkeeper "github.com/cosmos/example/x/counter/keeper" countertypes "github.com/cosmos/example/x/counter/types" ``` ### 2. Keeper Field Store the counter keeper on `ExampleApp` so the rest of the app can reference it. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // counter tutorial app wiring 2: add the counter keeper field below CounterKeeper *counterkeeper.Keeper ``` ### 3. Store Key Give the counter module its own KV store namespace. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // counter tutorial app wiring 3: add the counter store key below countertypes.StoreKey, ``` ### 4. Keeper Instantiation Construct the counter keeper using the module store and app codec. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // counter tutorial app wiring 4: create the counter keeper below app.CounterKeeper = counterkeeper.NewKeeper( runtime.NewKVStoreService(keys[countertypes.StoreKey]), appCodec, ) ``` ### 5. Module Manager Register the counter module with the app's module manager. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // counter tutorial app wiring 5: register the counter module below counter.NewAppModule(appCodec, app.CounterKeeper), ``` ### 6. Genesis Order Include the counter module when the app initializes state from genesis. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // counter tutorial app wiring 6: add the counter module to genesis order below countertypes.ModuleName, ``` ### 7. Export Order Include the counter module when the app exports state back out to genesis. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // counter tutorial app wiring 7: add the counter module to export order below countertypes.ModuleName, ``` ## Step 11: Build Run the following to compile the app and make sure the new module wiring is valid before you try to run the chain. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go build ./... ``` Fix any compilation errors before continuing. ## Step 12: Test your module Now you'll run the app locally and use one transaction plus one query to confirm the module works end-to-end. ### Start the chain First, install the binary and start the demo chain. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make install make start ``` This builds and installs `exampled` and then runs `scripts/local_node.sh`, which: * resets the local chain data * initializes genesis * creates and funds the `alice` and `bob` test accounts * creates a validator transaction * starts the chain You'll see the chain running and it should start producing blocks. ### Submit a transaction Open a second terminal and submit a transaction that adds `4` to the counter: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled tx counter add 4 --from alice --chain-id demo --yes ``` If the transaction succeeds, the response should include `code: 0`, which means the chain accepted the transaction and it passed validation without an application error: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} code: 0 codespace: "" data: "" events: [] gas_used: "0" gas_wanted: "0" height: "0" info: "" logs: [] raw_log: "" timestamp: "" tx: null txhash: 548D95784704575A347140E05A3ED84A05067DF4AD43F8E6FA20C94FAE8430E0 ``` This is the broadcast acknowledgement, returned before the transaction is in a block, so `height: "0"` and the empty fields are expected rather than a sign of failure. To see the executed result, query the transaction by its hash: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query tx ``` ### Query the chain Query the counter to confirm the stored value changed using the query command that `AutoCLI` generated earlier: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query counter count ``` You should see the following output: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} count: "4" ``` Congratulations, you've just created a Cosmos module from scratch and wired it into a real chain! If you are planning to build a production module, see [Module Design Considerations](/sdk/latest/guides/module-design/module-design-considerations) for guidance on state structure, message surface, dependencies, and upgrade planning before you ship. ## Next steps The simple counter module you built here follows the same structure as the full `x/counter` example in the `main` branch. Next, you'll see how the full module extends that foundation with features like params, fee collection, tests, and more. Next: [Full Counter Module Walkthrough →](/sdk/latest/tutorials/example/04-counter-walkthrough) # Full Counter Module Walkthrough Source: https://docs.cosmos.network/sdk/latest/tutorials/example/04-counter-walkthrough If you came here from the module building tutorial, switch back to the `main` branch of the [`cosmos/example` repo](https://github.com/cosmos/example) first: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git checkout main ``` The minimal counter you built in the previous tutorial captures the core SDK module pattern. The full `x/counter` module example in `main` follows the same pattern and adds several features on top. This walkthrough is meant to show you exactly what each feature is, what it does, and how you can add a similar feature to any module. ## Minimal vs full counter The full counter in the `main` branch adds quite a bit of functionality to the minimal tutorial counter. | Feature | minimal x/counter | full x/counter | | -------------------------------------------------- | ----------------- | ----------------------------------- | | [State](#params-and-authority) | `count` | `count` + `params` | | [Messages](#params-and-authority) | `Add` | `Add` + `UpdateParams` | | [Queries](#params-and-authority) | `Count` | `Count` + `Params` | | [Validation](#expected-keepers-and-fee-collection) | None | `MaxAddValue` limit, overflow check | | [Fees](#expected-keepers-and-fee-collection) | None | `AddCost` charged via bank module | | [Authority](#params-and-authority) | None | Governance-gated param updates | | [Errors](#sentinel-errors) | Generic | Named sentinel errors | | [Telemetry](#telemetry) | None | OpenTelemetry counter metric | | [CLI](#autocli) | AutoCLI | AutoCLI + `EnhanceCustomCommand` | | [Simulation](#simulation) | None | `simsx` weighted operations | | [Block hooks](#beginblock-and-endblock) | None | `BeginBlock` + `EndBlock` | | [Unit tests](#unit-tests) | None | Full keeper/msg/query test suite | The wiring code in [`msg_server.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/msg_server.go), [`query_server.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/query_server.go), [`module.go`](https://github.com/cosmos/example/blob/main/x/counter/module.go), and [`types/`](https://github.com/cosmos/example/tree/main/x/counter/types) is structurally similar between the two. Much of the new keeper logic lives in a single method: `AddCount` in [`keeper.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper.go). ## Params and authority A [module param](/sdk/latest/learn/concepts/modules#params) is on-chain configuration that controls how the module behaves without changing the code. The full counter adds a `Params` type that lets the chain governance configure the module's behavior at runtime. In the full module, params control how large an `Add` can be and how much it costs. ### Where the code lives * [`proto/example/counter/v1/state.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/state.proto) defines the `Params` type * [`proto/example/counter/v1/tx.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/tx.proto) adds the `UpdateParams` message * [`proto/example/counter/v1/query.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/query.proto) adds the `Params` query * [`x/counter/keeper/keeper.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper.go) stores the params and authority * [`x/counter/keeper/msg_server.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/msg_server.go) checks the authority on updates * [`x/counter/keeper/query_server.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/query_server.go) returns the current params ### Try it You can inspect the current params with: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query counter params ``` ### Add this to your module To add runtime-configurable params to your own module, make these changes: 1. Define a `Params` type in proto 2. Add a privileged `UpdateParams` message 3. Add a query to read the current params 4. Store the params and authority in your keeper 5. Check the authority in `MsgServer` before writing new params ### state.proto The relevant addition in [`state.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/state.proto) is: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Params { uint64 max_add_value = 1; repeated cosmos.base.v1beta1.Coin add_cost = 2 [ (gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (amino.dont_omitempty) = true ]; } ``` `MaxAddValue` caps how much a single `Add` call can increment the counter. `AddCost` sets an optional fee charged for each add operation. ### tx.proto - UpdateParams The relevant addition in [`tx.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/tx.proto) is: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); message MsgUpdateParams { option (cosmos.msg.v1.signer) = "authority"; string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; Params params = 2 [(gogoproto.nullable) = false]; } message MsgUpdateParamsResponse {} ``` `UpdateParams` is a privileged message. Only the `authority` address can call it. By default that address is the governance module account, so params can only be changed through a governance proposal. ### query.proto - Params [`query.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/query.proto) adds a second query to expose the current params: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} rpc Params(QueryParamsRequest) returns (QueryParamsResponse); ``` ### The authority pattern The keeper stores the authority address and checks it on every `UpdateParams` call: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { // ... // authority is the address capable of executing a MsgUpdateParams message. // Typically, this should be the x/gov module account. authority string } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // msg_server.go func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { if m.authority != msg.Authority { return nil, sdkerrors.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", m.authority, msg.Authority) } if err := m.SetParams(ctx, msg.Params); err != nil { return nil, err } return &types.MsgUpdateParamsResponse{}, nil } ``` The authority defaults to the governance module account at keeper construction: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), ``` This pattern, storing authority in the keeper and checking it in `MsgServer`, is the standard Cosmos SDK approach to governance-gated configuration. To point a module at a different authority, `NewKeeper` accepts functional options. `WithAuthority` replaces the default after the keeper is built: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/keeper/keeper.go type Options func(k *Keeper) // WithAuthority sets a custom authority on the module. This allows developers to set accounts other than the // governance module to control this module's params. func WithAuthority(authority string) Options { return func(k *Keeper) { k.authority = authority } } ``` Most chains keep the governance default, so `app.go` passes no options. ## Expected keepers and fee collection This section shows the standard Cosmos SDK pattern for [module-to-module interaction](/sdk/latest/learn/concepts/modules#inter-module-access). `x/counter` uses an expected keeper to call into the bank module and charge a fee for each add operation. ### Where the code lives * [`x/counter/types/expected_keepers.go`](https://github.com/cosmos/example/blob/main/x/counter/types/expected_keepers.go) defines the narrow bank keeper interface * [`x/counter/keeper/keeper.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper.go) stores the bank keeper dependency and charges the fee in `AddCount` * [`app.go`](https://github.com/cosmos/example/blob/main/app.go) passes `app.BankKeeper` into `counterkeeper.NewKeeper` * [`app.go`](https://github.com/cosmos/example/blob/main/app.go) adds a module account entry so the counter module can receive fees ### app.go changes This feature requires two `app.go` changes: * add `countertypes.ModuleName: nil` to `maccPerms` * pass `app.BankKeeper` into `counterkeeper.NewKeeper(...)` In [`app.go`](https://github.com/cosmos/example/blob/main/app.go), those changes look like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} maccPerms = map[string][]string{ // ... countertypes.ModuleName: nil, } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.CounterKeeper = counterkeeper.NewKeeper( runtime.NewKVStoreService(keys[countertypes.StoreKey]), appCodec, app.BankKeeper, ) ``` The full signature is `NewKeeper(storeService, cdc, bankKeeper, opts ...Options)`. The trailing options are how you override the default governance authority, covered in [the authority pattern](#the-authority-pattern) above. ### Try it Submit an add transaction and the configured `AddCost` fee will be charged from the sender: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled tx counter add 5 --from alice --chain-id demo --yes ``` ### Add this to your module To add fee collection through the bank module, make these changes: 1. Define a narrow bank keeper interface in `types/expected_keepers.go` 2. Add a `bankKeeper` field to your keeper 3. Charge the fee inside your keeper business logic 4. Add a module account entry in `maccPerms` 5. Pass `app.BankKeeper` into your keeper constructor in `app.go` ### expected\_keepers.go Rather than importing the bank module directly, the counter module defines the minimal interface it needs: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/types/expected_keepers.go type BankKeeper interface { SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } ``` This keeps the dependency explicit and narrow. The counter module cannot accidentally call any other bank method. ### Keeper struct ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { Schema collections.Schema counter collections.Item[uint64] params collections.Item[types.Params] bankKeeper types.BankKeeper authority string } ``` ### Fee charging in AddCount ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k *Keeper) AddCount(ctx context.Context, sender string, amount uint64) (uint64, error) { params, err := k.GetParams(ctx) if err != nil { return 0, err } if params.MaxAddValue > 0 && amount > params.MaxAddValue { return 0, ErrExceedsMaxAdd } count, err := k.GetCount(ctx) if err != nil { return 0, err } // Reject adds that would wrap the counter past the top of the uint64 range. // Written as a subtraction so the check itself cannot overflow. MaxAddValue // usually keeps amount small, but setting it to 0 disables that cap, so the // result has to be checked here rather than inferred from the input. if amount > math.MaxUint64-count { return 0, ErrNumTooLarge } // Charge the user if add cost is set. All validation happens above, so a // rejected add never reaches this point. if !params.AddCost.IsZero() { senderAddr, err := sdk.AccAddressFromBech32(sender) if err != nil { return 0, err } if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, senderAddr, types.ModuleName, params.AddCost); err != nil { return 0, sdkerrors.Wrap(ErrInsufficientFunds, err.Error()) } } newCount := count + amount if err := k.counter.Set(ctx, newCount); err != nil { return 0, err } sdkCtx := sdk.UnwrapSDKContext(ctx) sdkCtx.EventManager().EmitEvent( sdk.NewEvent( "count_increased", sdk.NewAttribute("count", fmt.Sprintf("%v", newCount)), ), ) countMetric.Add(ctx, int64(amount)) return newCount, nil } ``` Note the shape of the overflow guard. Go wraps silently on unsigned overflow, so `count + amount` exceeding the `uint64` range would leave the counter holding a smaller number with no error raised. Testing the input alone cannot catch that, because the value that overflows is the sum. Comparing `amount` against `math.MaxUint64 - count` tests the result while keeping the comparison itself inside the range. Any module doing unchecked arithmetic on user-supplied values needs the same treatment. All the business logic, validation, fee charging, state mutation, events, and telemetry, lives in `AddCount`. The `MsgServer` stays thin: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*types.MsgAddResponse, error) { newCount, err := m.AddCount(ctx, request.GetSender(), request.GetAdd()) if err != nil { return nil, err } return &types.MsgAddResponse{UpdatedCount: newCount}, nil } ``` Because `AddCount` is a named keeper method, it can also be called from `BeginBlock`, governance hooks, or other modules, not just from the `MsgServer`. ### Module accounts A module account is an on-chain account owned by a module instead of a user. Modules use module accounts to hold funds, receive fees, or get special permissions like minting or burning. Because `x/counter` receives fees from users, it needs a module account entry in [`app.go`](https://github.com/cosmos/example/blob/main/app.go): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} maccPerms = map[string][]string{ // ... countertypes.ModuleName: nil, } ``` This lives in the `maccPerms` map in [`app.go`](https://github.com/cosmos/example/blob/main/app.go). Here, `nil` means the module account can receive funds but does not get extra permissions like minting or burning. ## Sentinel errors Rather than returning generic errors, `x/counter` defines named sentinel errors with registered codes. That makes failures easier to understand and easier for clients to match on programmatically. ### Where the code lives * [`x/counter/keeper/errors.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/errors.go) defines the registered module errors * [`x/counter/keeper/keeper.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper.go) returns those errors from business logic checks ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // keeper/errors.go var ( // Codes start at 2: code 0 is reserved for success and code 1 for internal errors. ErrNumTooLarge = errors.Register("counter", 2, "requested integer to add is too large") ErrExceedsMaxAdd = errors.Register("counter", 3, "add value exceeds max allowed") ErrInsufficientFunds = errors.Register("counter", 4, "insufficient funds to pay add cost") ) ``` Registered errors produce structured error responses on-chain that clients can match against by code, not just by string. Each error code must be unique within the module and start at `2`: code `0` is the ABCI success code, and code `1` is reserved for internal errors. Registering an error as code `0` is accepted silently, but a transaction failing with it reports `code: 0`, which every client reads as success. To check whether an error is of a specific sentinel type, use `errors.Is(err, ErrInsufficientFunds)`. This works correctly even when the error has been wrapped with additional context via `errorsmod.Wrap` or `errorsmod.Wrapf`. All validation — both stateless field checks and stateful business logic checks — should live in the `msgServer` method or the keeper function it calls. The older `ValidateBasic` method on message types is deprecated: prefer performing all validation inside the message server. If your message type does implement `ValidateBasic`, the SDK still calls it for backward compatibility, but new modules should not rely on it. ## Telemetry [Telemetry](/sdk/latest/guides/testing/telemetry) records how often the counter is updated so you can observe module activity in an OpenTelemetry-compatible system. ### Where the code lives * [`x/counter/keeper/telemetry.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/telemetry.go) defines the meter and counter metric * [`x/counter/keeper/keeper.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper.go) records the metric from `AddCount` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/keeper/telemetry.go var ( meter = otel.Meter("github.com/cosmos/example/x/counter") countMetric metric.Int64Counter ) func init() { var err error countMetric, err = meter.Int64Counter("count") if err != nil { panic(err) } } ``` `countMetric.Add(ctx, int64(amount))` in `AddCount` increments an OpenTelemetry counter every time the module state is updated. This makes module activity visible in any OTel-compatible observability system. ## AutoCLI [AutoCLI](/sdk/latest/guides/tooling/autocli) exposes the module's queries and transactions as CLI commands. The full module example keeps the same basic AutoCLI setup as the minimal module and adds the recommended setting for custom command integration. ### Where the code lives * [`x/counter/autocli.go`](https://github.com/cosmos/example/blob/main/x/counter/autocli.go) defines the generated query and tx commands ### Try it These commands come from the AutoCLI configuration. `count` and `add` are customized explicitly in `autocli.go`, and `params` is still available from the generated query service. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query counter count exampled query counter params exampled tx counter add 5 --from alice --chain-id demo --yes ``` Both modules use AutoCLI. The only difference is that `x/counter` sets `EnhanceCustomCommand: true`, which merges any hand-written CLI commands with the auto-generated ones. Since neither module has hand-written commands, it is a no-op here, but it is a good default for fuller modules. The [`autocli.go`](https://github.com/cosmos/example/blob/main/x/counter/autocli.go) file in `x/counter`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // autocli.go func (a AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { return &autocliv1.ModuleOptions{ Query: &autocliv1.ServiceCommandDescriptor{ Service: "example.counter.Query", EnhanceCustomCommand: true, RpcCommandOptions: []*autocliv1.RpcCommandOptions{ { RpcMethod: "Count", Use: "count", Short: "Query the current counter value", }, }, }, Tx: &autocliv1.ServiceCommandDescriptor{ Service: "example.counter.Msg", EnhanceCustomCommand: true, RpcCommandOptions: []*autocliv1.RpcCommandOptions{ { RpcMethod: "Add", Use: "add [amount]", Short: "Add to the counter", PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "add"}}, }, }, }, } } ``` ## Simulation [Simulation](/sdk/latest/guides/testing/simulator) lets the SDK generate randomized transactions against the module during fuzz-style testing. ### Where the code lives * [`x/counter/simulation/msg_factory.go`](https://github.com/cosmos/example/blob/main/x/counter/simulation/msg_factory.go) defines how to generate random `Add` messages * [`x/counter/module.go`](https://github.com/cosmos/example/blob/main/x/counter/module.go) registers those weighted operations ### Test it You can exercise simulation through the repo's simulation test targets described in the running and testing tutorial. `x/counter` implements `simsx`-based simulation, which lets the SDK's simulation framework generate random `Add` transactions during fuzz testing: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/counter/simulation/msg_factory.go func MsgAddFactory() simsx.SimMsgFactoryFn[*types.MsgAddRequest] { return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgAddRequest) { sender := testData.AnyAccount(reporter) if reporter.IsSkipped() { return nil, nil } r := testData.Rand() addAmount := uint64(r.Intn(100) + 1) msg := &types.MsgAddRequest{ Sender: sender.AddressBech32, Add: addAmount, } return []simsx.SimAccount{sender}, msg } } ``` `module.go` registers this factory: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (a AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Registry) { reg.Add(weights.Get("msg_add", 100), simulation.MsgAddFactory()) } ``` ## BeginBlock and EndBlock These [hooks](/sdk/latest/learn/concepts/modules#block-hooks) let a module run code automatically at the start or end of every block. In `x/counter`, they are purposefully empty to demonstrate where and how these features can be added. ### Where the code lives * [`x/counter/module.go`](https://github.com/cosmos/example/blob/main/x/counter/module.go) implements `BeginBlock` and `EndBlock` * [`app.go`](https://github.com/cosmos/example/blob/main/app.go) adds the module to `SetOrderBeginBlockers` and `SetOrderEndBlockers` ### app.go changes Because the module advertises block hooks, [`app.go`](https://github.com/cosmos/example/blob/main/app.go) must include `countertypes.ModuleName` in both blocker order lists. ### Add this to your module To add begin and end blockers to your own module, make two changes: 1. Implement the hooks in `x//module.go` 2. Add your module name to `SetOrderBeginBlockers` and `SetOrderEndBlockers` in `app.go` `module.go` implements `HasBeginBlocker` and `HasEndBlocker`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (a AppModule) BeginBlock(ctx context.Context) error { // optional: logic to execute at the start of every block return nil } func (a AppModule) EndBlock(ctx context.Context) error { // optional: logic to execute at the end of every block return nil } ``` In [`app.go`](https://github.com/cosmos/example/blob/main/app.go), the module is added to the blocker order lists like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ModuleManager.SetOrderBeginBlockers( // ... countertypes.ModuleName, ) app.ModuleManager.SetOrderEndBlockers( // ... countertypes.ModuleName, ) ``` `x/counter` has no per-block logic, so both methods return nil. They exist to demonstrate the pattern: modules that need per-block execution (staking, distribution) implement real logic here. For example, a counter that auto-increments every block would call `k.AddCount(ctx, 1)` from `BeginBlock` instead of exposing a message type. ## Unit tests The full module example includes a real [test suite](/sdk/latest/learn/concepts/testing) for keeper logic, query behavior, message handling, and bank keeper interactions. ### Where the code lives * [`x/counter/keeper/keeper_test.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper_test.go) * [`x/counter/keeper/msg_server_test.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/msg_server_test.go) * [`x/counter/keeper/query_server_test.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/query_server_test.go) ### Run them You can run the counter module tests directly with: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go test ./x/counter/... ``` ### Add this to your module Start with keeper, message server, and query server tests. If your module depends on another keeper, use a small mock interface like `MockBankKeeper` so you can control success and failure cases in isolation. `x/counter` ships a full test suite in [`x/counter/keeper/`](https://github.com/cosmos/example/tree/main/x/counter/keeper): | File | What it tests | | ---------------------- | -------------------------------------------------------------------------------------------- | | `keeper_test.go` | `KeeperTestSuite` setup, `InitGenesis`, `ExportGenesis`, `GetCount`, `AddCount`, `SetParams` | | `msg_server_test.go` | `MsgAdd`, event emission, `MsgUpdateParams` | | `query_server_test.go` | `QueryCount`, `QueryParams` | All three files share the `KeeperTestSuite` struct defined in [`keeper_test.go`](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper_test.go), which sets up an isolated in-memory store, a mock bank keeper, and a real keeper instance: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type KeeperTestSuite struct { suite.Suite ctx sdk.Context keeper *keeper.Keeper queryClient types.QueryClient msgServer types.MsgServer bankKeeper *MockBankKeeper authority string } ``` `MockBankKeeper` lets tests control exactly what the bank keeper returns without needing a real bank module: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MockBankKeeper struct { SendCoinsFromAccountToModuleFn func(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } ``` Tests set `SendCoinsFromAccountToModuleFn` to simulate success or failure: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} s.bankKeeper.SendCoinsFromAccountToModuleFn = func(...) error { return errors.New("insufficient funds") } ``` ## Gas `minimum-gas-prices` in `app.toml` sets the minimum fee a node requires before it will accept and relay a transaction. The local dev chain started by `make start` sets this to `0stake`, so transactions are accepted with no fee beyond the `AddCost` module parameter. To require a minimum network fee, set it in `app.toml`: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} minimum-gas-prices = "0.025stake" ``` Transactions that don't meet the minimum will be rejected by the node before they reach your module. This is a per-node setting, not a chain-wide consensus rule, so validators on a live network each configure their own threshold. Next: [Running and Testing →](/sdk/latest/tutorials/example/05-run-and-test) # Run, Test, and Configure Source: https://docs.cosmos.network/sdk/latest/tutorials/example/05-run-and-test Learn how to run and test a chain Now that you've [built a module from scratch](/sdk/latest/tutorials/example/03-build-a-module) and walked through the [full counter module](/sdk/latest/tutorials/example/04-counter-walkthrough), the next step is learning the workflow for running and validating a production-ready chain. This page shows how to start the chain locally, interact with it through the CLI, and use the main layers of testing before shipping changes. ## Single-node local chain Use a single-node chain for the fastest local development loop. It gives you one validator with predictable state so you can quickly test queries and transactions. ### Start ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make start ``` This builds the binary, initializes chain data, and starts a single validator node. It handles cleanup automatically — existing chain state is reset on each run. The chain uses: * Chain ID: `demo` * Pre-funded accounts: `alice`, `bob` * Default denomination: `stake` ### Stop Press `Ctrl+C` in the terminal running `make start`. ### Reset chain state ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make start ``` Re-running `make start` resets state automatically. There is no separate reset command. ## Localnet (multi-node) Localnet runs four nodes in Docker to give you a setup closer to a real network than the single-node chain. `scripts/localnet/init.sh` creates a genesis transaction for `node0` only, so the network is **one validator plus three full nodes**, not four validators. The chain ID is `example-localnet`, and each node has a single key named `validator` rather than the `alice` and `bob` accounts used by `make start`. Before you begin, note that this section needs Docker running, and that the following host ports must be free: `26656`, `26657`, `1317`, `9090` for `node0`, then `26666`, `26667`, `1318`, `9091` for `node1`, `26676`, `26677`, `1319`, `9092` for `node2`, and `26686`, `26687`, `1320`, `9093` for `node3`. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Build the node image and initialize four node directories under build/localnet. # Takes several minutes the first time, since it compiles the chain in Docker. make localnet-init # Start all four nodes make localnet-start # Follow the logs. This does not exit on its own; press Ctrl+C to stop following make localnet-logs # Stop make localnet-stop # Delete build/localnet immediately, without confirming make localnet-clean ``` ### Confirm the network is healthy Each node exposes its own RPC port. Check that every node has found the other three and that they are advancing together: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} for port in 26657 26667 26677 26687; do curl -s http://localhost:$port/status | grep -o '"latest_block_height":"[0-9]*"' curl -s http://localhost:$port/net_info | grep -o '"n_peers":"[0-9]*"' done ``` Each node should report `"n_peers":"3"` and a block height that climbs on repeated calls. ### Send a transaction The localnet uses a different chain ID and key name than `make start`, so the commands in the CLI reference below need adjusting. Run them inside a container: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} docker exec node0 exampled tx counter add 7 \ --from validator --chain-id example-localnet \ --keyring-backend test --home /data/node0 --yes ``` Then confirm the state replicated by querying a different node: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} docker exec node2 exampled query counter count --home /data/node2 ``` ## CLI reference Once the chain is running, these are the core [CLI](/sdk/latest/learn/concepts/cli-grpc-rest#cli) commands you'll use to inspect state and submit transactions. For the gRPC and REST equivalents of every query and transaction the standard modules define, see the [API reference](/sdk/latest/api-reference/index). ### Query commands Use query commands to read module state without changing anything on-chain. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query the current counter value exampled query counter count # Query the module parameters exampled query counter params # Query with a specific node (if not using default localhost:26657) exampled query counter count --node tcp://localhost:26657 ``` ### Transaction commands Use transaction commands to submit state-changing messages to the chain. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Add to the counter exampled tx counter add 10 --from alice --chain-id demo --yes # Add with a gas limit exampled tx counter add 10 --from alice --chain-id demo --gas 200000 --yes ``` ### Updating module parameters Counter params are governance-gated. `MsgUpdateParams` accepts only the gov module address as its authority, so there is no direct CLI command for it: signing `update-params` with a user key such as `alice` always fails with `ErrInvalidSigner`. Params change through a governance proposal instead. Look up the gov module address for your chain, which is the only valid authority: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled query auth module-account gov ``` Write a `proposal.json` containing the message, using that address as `authority`. On the local `demo` chain the value is `cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn`: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "messages": [ { "@type": "/example.counter.MsgUpdateParams", "authority": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", "params": { "max_add_value": "50", "add_cost": [{"denom": "stake", "amount": "200"}] } } ], "metadata": "ipfs://CID", "deposit": "10000000stake", "title": "Update counter params", "summary": "Set max_add_value to 50 and add_cost to 200stake" } ``` The `deposit` must meet the chain's `min_deposit`, which is `10000000stake` locally. Check it with `exampled query gov params`. Then submit and vote: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} exampled tx gov submit-proposal proposal.json --from alice --chain-id demo --yes exampled tx gov vote 1 yes --from alice --chain-id demo --yes ``` Check progress with `exampled query gov proposals`. The local chain uses the default 48 hour `voting_period`, so a proposal submitted this way sits in `PROPOSAL_STATUS_VOTING_PERIOD` for two days and the params do not change during a normal dev session. To watch a param change actually take effect locally, shorten the voting period. Editing `genesis.json` before `make start` does not work, because `scripts/local_node.sh` deletes the whole home directory on every run. Let `make start` create the chain first, then stop it and edit in place: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # 1. Let make start create ~/.exampleapp, then stop it with Ctrl+C make start # 2. Lower both governance voting periods in the generated genesis. # app_state.gov.params.voting_period, for example "20s" # app_state.gov.params.expedited_voting_period must stay strictly shorter, for example "10s" vi ~/.exampleapp/config/genesis.json # 3. Wipe block history so the edited genesis is re-read, keeping keys and config exampled comet unsafe-reset-all # 4. Start the node directly. Do not use make start again, it would delete your edit exampled start ``` Submit and vote as above, wait out the shortened period, and the proposal reaches `PROPOSAL_STATUS_PASSED` and `exampled query counter params` reflects the new values. `exampled tx gov draft-proposal` can generate a skeleton, but it is an interactive terminal picker rather than a scriptable command. Its top-level list offers only `text`, `community-pool-spend`, `software-upgrade`, `cancel-software-upgrade`, and `other`, and choosing `other` opens a scroll-only list of fully qualified message type URLs that typing does not filter. Writing the JSON by hand, as above, is the more direct path. ### Useful flags These flags are the ones you'll use most often while iterating locally. | Flag | Description | | --------------- | ----------------------------------------------- | | `--from` | Key name or address to sign with | | `--chain-id` | Chain ID (use `demo` for local) | | `--yes` | Skip confirmation prompt | | `--gas` | Gas limit for the transaction | | `--node` | RPC endpoint (default: `tcp://localhost:26657`) | | `--output json` | Output response as JSON | ## Node Configuration When you run `make start`, the chain creates `~/.exampleapp/config/` automatically and initializes two config files inside it: | File | What it controls | | ------------- | -------------------------------------------------------------------------- | | `app.toml` | SDK application settings: gas prices, pruning, API/gRPC servers, telemetry | | `config.toml` | CometBFT settings: peer networking, consensus timeouts, mempool, RPC | ### app.toml The most common settings to change during development: | Setting | Default | Description | | -------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `minimum-gas-prices` | `"0stake"` | Minimum fee the node accepts before processing a transaction. Set by this chain in `exampled/cmd/commands.go`, not by the SDK, whose own default is empty | | `pruning` | `"default"` | How much historical state to keep (`default`, `nothing`, `everything`, `custom`) | | `api.enable` | `true` after `make start` | Enables the REST API on port 1317. The SDK default is `false`; `scripts/local_node.sh` turns it on for local development | | `grpc.enable` | `true` | Enables the gRPC server on port 9090 | ### config.toml The settings most likely to change during development: | Setting | Default | Description | | -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | | `moniker` | `"test"` | Human-readable name for the node | | `log_level` | `"info"` | Log verbosity (`debug`, `info`, `error`) | | `consensus.timeout_commit` | `"5s"` | How long to wait after a block is committed before starting the next one. The SDK raises CometBFT's own 1s default to 5s | | `p2p.seeds` | `""` | Seed nodes to connect to on a live network | | `p2p.persistent_peers` | `""` | Peers to maintain permanent connections to | ## Unit tests Start here when you want fast feedback on module logic without running a chain. These tests isolate the [keeper](/sdk/latest/learn/concepts/testing#keeper-unit-tests) and gRPC servers from the rest of the app. The unit test logic lives in the counter keeper package on `main`: the shared suite setup is in [x/counter/keeper/keeper\_test.go](https://github.com/cosmos/example/blob/main/x/counter/keeper/keeper_test.go), message-path tests are in [x/counter/keeper/msg\_server\_test.go](https://github.com/cosmos/example/blob/main/x/counter/keeper/msg_server_test.go), and query-path tests are in [x/counter/keeper/query\_server\_test.go](https://github.com/cosmos/example/blob/main/x/counter/keeper/query_server_test.go). The keeper test suite covers the keeper, msg server, and query server in isolation using an in-memory store and a mock bank keeper. No running chain is required. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go test ./x/counter/... ``` To run with verbose output: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go test -v ./x/counter/... ``` To run a specific test: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go test -v -run TestKeeperTestSuite/TestAddCount ./x/counter/... ``` The test suite is structured around three files: | File | Tests | | ----------------------------- | -------------------------------------------- | | `keeper/keeper_test.go` | Genesis, `GetCount`, `AddCount`, `SetParams` | | `keeper/msg_server_test.go` | `MsgAdd`, event emission, `MsgUpdateParams` | | `keeper/query_server_test.go` | `QueryCount`, `QueryParams` | ## E2E tests Run [E2E tests](/sdk/latest/learn/concepts/testing#integration-tests) when you want to verify the full request path against a real node. They give you higher confidence than unit tests, but take longer to complete. The E2E logic lives on `main` in [tests/counter\_test.go](https://github.com/cosmos/example/blob/main/tests/counter_test.go), which starts an in-process network, builds signed transactions, and verifies query results. The shared network fixture it uses is defined in [tests/test\_helpers.go](https://github.com/cosmos/example/blob/main/tests/test_helpers.go). The E2E test suite starts a real in-process validator network and submits actual transactions against it. This tests the full stack: transaction encoding, message routing, keeper logic, and query responses. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go test -v -run TestE2ETestSuite ./tests/... ``` E2E tests take longer than unit tests because they spin up a real node. Run them before merging significant changes. ## Simulation tests [Simulation tests](/sdk/latest/learn/concepts/testing#simulation-tests) stress the chain with randomized activity to catch edge cases that targeted tests can miss. In this repo, that simulation flow is built with `simsx`, the Cosmos SDK's higher-level simulation framework for defining random on-chain activity at the module level. The top-level simulation test commands on `main` run through [sim\_test.go](https://github.com/cosmos/example/blob/main/sim_test.go). The counter module's `simsx` registration lives in [x/counter/module.go](https://github.com/cosmos/example/blob/main/x/counter/module.go), the random `MsgAdd` generation lives in [x/counter/simulation/msg\_factory.go](https://github.com/cosmos/example/blob/main/x/counter/simulation/msg_factory.go), and randomized counter genesis lives in [x/counter/simulation/genesis.go](https://github.com/cosmos/example/blob/main/x/counter/simulation/genesis.go). In practice, `simsx` lets each module describe three things: how to generate random starting state, which operations can happen during simulation, and how often each operation should be chosen. For `x/counter`, that means generating a random initial counter value, registering `MsgAdd` as a simulation operation, and assigning it a weight so the simulator knows how frequently to try it relative to other module operations. When you run a simulation target, the test harness repeatedly builds app instances, creates random accounts and balances, generates random transactions from the registered module operations, and executes them over many blocks. That makes `simsx` useful for catching issues that are hard to cover with hand-written tests, like state machine bugs, unexpected panics, invariant violations, and non-deterministic behavior across runs. Simulation runs the chain with randomly generated transactions to detect non-determinism and invariant violations. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Full simulation make test-sim-full # Determinism check make test-sim-determinism # All simulation tests make test-sim ``` Simulation requires the `sims` build tag, which the Makefile targets handle automatically. Each of these runs the simulation across 38 built-in seeds, so expect roughly ten minutes per target. The Makefile deliberately uses smaller values than the SDK defaults of 500 blocks and 200 operations per block, which across 38 seeds take hours. To simulate more deeply, override them: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make test-sim-full SIM_NUM_BLOCKS=500 SIM_BLOCK_SIZE=200 SIM_TIMEOUT=4h ``` ## Lint Linting is the quickest way to catch style problems and common code-quality issues before CI or code review does. The lint commands are defined in the repo [Makefile](https://github.com/cosmos/example/blob/main/Makefile), which installs `golangci-lint` and runs it across the full module tree. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make lint ``` This installs and runs `golangci-lint` across the repository. To auto-fix issues where possible: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make lint-fix ``` ## Test summary Use this table as a quick reference for choosing the right validation command for the kind of change you made. | Command | What it validates | Typical runtime | | ------------------------------------------- | ---------------------------------------------- | ------------------ | | `go test ./x/counter/...` | Keeper, MsgServer, QueryServer in isolation | seconds | | `go test -run TestE2ETestSuite ./tests/...` | Full transaction and query flow on a live node | under a minute | | `make test-sim-full` | Non-determinism and invariant violations | around ten minutes | | `make lint` | Code style and static analysis | a few minutes | # v0.54 Upgrade Guide Source: https://docs.cosmos.network/sdk/latest/upgrade/v0.54 Reference for upgrading from v0.53 to v0.54 of Cosmos SDK This document provides a reference for upgrading from `v0.53.x` to `v0.54.x` of Cosmos SDK. However, this guide is not exhaustive for all breaking changes. For a comprehensive list of all breaking changes in v0.54.0, see the [Changelog](https://github.com/cosmos/cosmos-sdk/blob/release/v0.54.x/CHANGELOG.md). Always read the [App Wiring Changes](#app-wiring-changes) section for more information on application wiring updates. ## Table of Contents * [Upgrade Checklist](#upgrade-checklist) * [Required Changes](#required-changes) * [App Wiring Changes](#app-wiring-changes) * [x/gov](#x/gov) * [Keeper Initialization](#keeper-initialization) * [GovHooks Interface](#govhooks-interface) * [x/epochs](#x/epochs) * [x/bank](#x/bank) * [NodeService](#nodeservice) * [Removed Go Modules](#removed-go-modules) * [Renamed Go Modules](#renamed-go-modules) * [Module Version Updates](#module-version-updates) * [Log v2](#log-v2) * [Store v2](#store-v2) * [Conditional Changes](#conditional-changes) * [Module Deprecations](#module-deprecations) * [x/circuit](#x/circuit) * [x/nft](#x/nft) * [x/crisis](#x/crisis) * [Cosmos Enterprise](#cosmos-enterprise) * [Groups Module](#groups-module) * [PoA Module](#poa-module) * [New Features and Non-Breaking Changes](#new-features-and-non-breaking-changes) * [Telemetry](#telemetry) * [OpenTelemetry](#opentelemetry) * [Centralized Authority via Consensus Params](#centralized-authority-via-consensus-params) * [How AuthorityParams Works](#how-authorityparams-works) * [Upgrade Handler](#upgrade-handler) * [IBC v11 Updates](#ibc-v11-updates) * [Cosmos Performance Upgrades (Experimental)](#cosmos-performance-upgrades-experimental) * [Cosmos SDK](#cosmos-sdk) * [BlockSTM](#blockstm) * [CometBFT v0.39 Updates](#cometbft-v039-updates) * [LibP2P](#libp2p) * [`AdaptiveSync`](#adaptivesync) ## Upgrade Checklist Use this checklist first, then read the linked sections for the exact code or wiring changes. * [ ] Update `x/gov` keeper wiring, as the `x/gov` module has been decoupled from `x/staking`. See [Keeper Initialization](#keeper-initialization). * [ ] Update your governance hooks if you implement `AfterProposalSubmission`. See [GovHooks Interface](#govhooks-interface). * [ ] Update `x/epochs.NewAppModule` if your app includes `x/epochs`. See [x/epochs](#x/epochs). * [ ] Put `x/bank` first in `SetOrderEndBlockers`. See [x/bank](#x/bank). * [ ] Update your node service registration if your app exposes `NodeService`. See [NodeService](#nodeservice). * [ ] Migrate imports for removed `x/` Go modules. See [Removed Go Modules](#removed-go-modules). * [ ] Update required Cosmos SDK Go module dependencies. See [Module Version Updates](#module-version-updates). * [ ] Migrate to `contrib/` imports if you use `x/circuit`, `x/nft`, or `x/crisis`. See [Module Deprecations](#module-deprecations). * [ ] Migrate to Cosmos Enterprise if you use the `x/group` module. See [Groups Module](#groups-module). * [ ] Update imports to `cosmossdk.io/log/v2` if your app imports the log package directly. See [Log v2](#log-v2). * [ ] Migrate imports to `github.com/cosmos/cosmos-sdk/store/v2`. See [Store v2](#store-v2). * [ ] Migrate any remaining `BaseApp.NewUncachedContext()` usage. See [Store v2](#store-v2). * [ ] If using `systemtests` update import to `github.com/cosmos/cosmos-sdk/tools/systemtests`. See [Renamed Go Modules](#renamed-go-modules). * [ ] Review [IBC v11 Updates](#ibc-v11-updates) if your chain uses IBC. Several APIs have been removed. * [ ] Review [Centralized Authority via Consensus Params](#centralized-authority-via-consensus-params). No upgrade action is required to keep using per-keeper authorities. * [ ] Review [Telemetry](#telemetry). No upgrade action is required to keep existing telemetry wiring, but upgrading to OpenTelemetry is strongly encouraged. * [ ] Review [PoA Module](#poa-module) if you are interested in adopting the new Cosmos Enterprise Proof of Authority module. * [ ] Review [Cosmos Performance Upgrades (Experimental)](#cosmos-performance-upgrades-experimental) if you are interested in experimenting with BlockSTM, LibP2P, or AdaptiveSync. ## Required Changes All chains upgrading to `v0.54.x` should review and apply the changes in this section. This guide provides an overview of the major changes in v0.54.0. However, this guide is not exhaustive for all breaking changes. For a comprehensive list of all breaking changes in v0.54.0, see the [Changelog](https://github.com/cosmos/cosmos-sdk/blob/release/v0.54.x/CHANGELOG.md). ### App Wiring Changes #### x/gov ##### Keeper Initialization The `x/gov` module has been decoupled from `x/staking`. The `keeper.NewKeeper` constructor now requires a `CalculateVoteResultsAndVotingPowerFn` parameter instead of a `StakingKeeper`. **Before:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} govKeeper := govkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, // REMOVED IN v0.54 app.DistrKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` **After:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} govKeeper := govkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.DistrKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), govkeeper.NewDefaultCalculateVoteResultsAndVotingPower(app.StakingKeeper), // ADDED IN v0.54 ) ``` For applications using depinject, the governance module now accepts an optional `CalculateVoteResultsAndVotingPowerFn`. If not provided, it will use the `StakingKeeper` (also optional) to create the default function. ##### GovHooks Interface The `AfterProposalSubmission` hook now includes the proposer address as a parameter. **Before:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64) error { // implementation } ``` **After:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (h MyGovHooks) AfterProposalSubmission(ctx context.Context, proposalID uint64, proposerAddr sdk.AccAddress) error { // implementation } ``` #### x/epochs The epochs module's `NewAppModule` function now requires the epoch keeper by pointer instead of value, fixing a bug related to setting hooks via depinject. #### x/bank The bank module now contains an `EndBlock` method to support the new BlockSTM experimental package. BlockSTM requires coordinating object store access across parallel execution workers, and `x/bank`'s `EndBlock` handles the finalization step for that. **All applications must make this change**, whether or not they enable BlockSTM, because the `EndBlock` registration is now part of the module's standard lifecycle. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ModuleManager.SetOrderEndBlockers( banktypes.ModuleName, // other modules... ) ``` #### NodeService The node service has been updated to return the node's earliest store height in the `Status` query. Please update your registration with the following code (make sure you are already updated to `github.com/cosmos/cosmos-sdk/store/v2`): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *SimApp) RegisterNodeService(clientCtx client.Context, cfg config.Config) { nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg, func() int64 { return app.CommitMultiStore().EarliestVersion() }) } ``` ### Removed Go Modules Most `cosmossdk.io` vanity URLs for modules under `x/` have been removed. These separate Go modules caused dependency version management to be unpredictable; different modules could be pinned to different SDK versions, leading to compatibility issues. Consolidating everything under `github.com/cosmos/cosmos-sdk` gives developers a single, versioned dependency to manage. The following must be updated: * `cosmossdk.io/x/evidence` -> `github.com/cosmos/cosmos-sdk/x/evidence` * `cosmossdk.io/x/feegrant` -> `github.com/cosmos/cosmos-sdk/x/feegrant` * `cosmossdk.io/x/upgrade` -> `github.com/cosmos/cosmos-sdk/x/upgrade` * `cosmossdk.io/x/tx` -> `github.com/cosmos/cosmos-sdk/x/tx` ### Renamed Go Modules The `cosmossdk.io/systemtests` go module is now named `github.com/cosmos/cosmos-sdk/tools/systemtests`. ### Module Version Updates * `cosmossdk.io/client/v2` has been updated to v2.11.0 ### Log v2 The log package has been updated to `v2`. Applications using v0.54.0+ of Cosmos SDK will be required to update imports to `cosmossdk.io/log/v2`. Usage of the logger itself does not need to be updated. The v2 release of log adds contextual methods to the logger interface (InfoContext, DebugContext, etc.), allowing logs to be correlated with OpenTelemetry traces. To learn more about the new features offered in `log/v2`, as well as setting up log correlation, see the [log package documentation](https://docs.cosmos.network/sdk/latest/guides/testing/log). ### Store v2 Store v2 introduces breaking changes. For a comprehensive list of all breaking changes, see the [Changelog](https://github.com/cosmos/cosmos-sdk/blob/release/v0.54.x/CHANGELOG.md). The store package has been updated to `v2`. Applications using v0.54.0+ of Cosmos SDK will be required to update imports to `github.com/cosmos/cosmos-sdk/store/v2`. `BaseApp.NewUncachedContext()` was deprecated as part of this work. With store v2, writes must go through a cache/branch first; the SDK no longer exposes a helper that lets applications write directly against the root `CommitMultiStore`. If you previously used `BaseApp.NewUncachedContext()` in tests: * Replace `app.NewUncachedContext(false, header)` with `app.NewNextBlockContext(header)` when the test needs a writable context between `Commit()` and the next `FinalizeBlock()`. * Replace `app.NewUncachedContext(true, header)` with `app.NewContext(true)` or `app.NewContextLegacy(true, header)` when the test only needs the `CheckTx` state. Below is an example of migrating away from `NewUncachedContext`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func TestApp(t *testing.T) { db := dbm.NewMemDB() logger := log.NewTestLogger(t) app := NewSimappWithCustomOptions(t, false, SetupOptions{ Logger: logger.With("instance", "first"), DB: db, AppOpts: simtestutil.NewAppOptionsWithFlagHome(t.TempDir()), }) /* Before the updates, most code would look like: ctx := gaiaApp.NewUncachedContext(true, tmproto.Header{}) // CheckTx context app.MyKeeper.MyMethod(ctx, ...) The main thing to be aware of is when you are using checkTx state and finalizeState. NewNextBlockContext will overwrite the finalize state and return a context that writes to that state. Reading from checkTx state without committing will not reflect the changes made in finalizeBlock state UNLESS you have committed. */ ctx := app.BaseApp.NewNextBlockContext(cmtproto.Header{}) // gets finalize block state app.BankKeeper.SetSendEnabled(ctx, "foobar", true) _, err := app.Commit() // commit the out-of-band changes. require.NoError(t, err) // since we committed, we can now read the out-of-band changes via checkTx state. // If we didn't commit above, we could read this value by passing `false` to NewContext, which would give us a handle // on the finalize block state. However, if you DID commit like we did above, you MUST use `true` here. res, err := app.BankKeeper.SendEnabled(app.BaseApp.NewContext(true), &banktypes.QuerySendEnabledRequest{ Denoms: []string{"foobar"}, Pagination: nil, }) require.NoError(t, err) require.Len(t, res.SendEnabled, 1) require.Equal(t, "foobar", res.SendEnabled[0].Denom) } ``` ## Conditional Changes These changes apply if your chain uses the affected modules, packages, or integrations. ### Module Deprecations Cosmos SDK v0.54.0 drops support for the circuit, nft, and crisis modules. Developers can still use these modules, however, they will no longer be actively maintained by Cosmos Labs. #### x/circuit The circuit module is no longer being actively maintained by Cosmos Labs and was moved to `contrib/x/circuit`. #### x/nft The nft module is no longer being actively maintained by Cosmos Labs and was moved to `contrib/x/nft`. #### x/crisis The crisis module is no longer being actively maintained by Cosmos Labs and was moved to `contrib/x/crisis`. ### Cosmos Enterprise [Cosmos Enterprise modules](/sdk/latest/enterprise/overview) are hardened Cosmos SDK modules for permissioned and production networks. The module source is published under the Source Available Evaluation License, and production use requires an Enterprise License from Cosmos Labs. #### Groups Module The groups module is now maintained under the Cosmos Enterprise offering. If your application uses `x/group`, you will need to migrate your code to the Enterprise-distributed package and obtain a Cosmos Enterprise license to continue using it. Please see [Cosmos Enterprise](/sdk/latest/enterprise/overview) to learn more. #### PoA Module Cosmos SDK v0.54 includes a Proof of Authority (POA) module under the Cosmos Enterprise offering. Please see [Cosmos Enterprise](/sdk/latest/enterprise/poa/overview) to learn more about using the PoA module in your application. ## New Features and Non-Breaking Changes These changes are informational and optional to adopt during the upgrade; they are not required for a successful migration. ### Telemetry The telemetry package has been deprecated and users are encouraged to switch to OpenTelemetry. #### OpenTelemetry Previously, Cosmos SDK telemetry support was provided by `github.com/hashicorp/go-metrics` which was undermaintained and only supported metrics instrumentation. OpenTelemetry provides an integrated solution for metrics, traces, and logging which is widely adopted and actively maintained. The existing wrapper functions in the `telemetry` package required acquiring mutex locks and map lookups for every metric operation which is suboptimal. OpenTelemetry's API uses atomic concurrency wherever possible and should introduce less performance overhead during metric collection. See the [telemetry documentation](https://docs.cosmos.network/sdk/latest/guides/testing/telemetry) to learn how to set up OpenTelemetry with Cosmos SDK v0.54.0+. Below is a quick reference on setting up and using meters and traces with OpenTelemetry: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package mymodule import ( "context" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" sdk "github.com/cosmos/cosmos-sdk/types" ) // Declare package-level meter and tracer using otel.Meter() and otel.Tracer(). // Instruments should be created once at package initialization and reused. var ( tracer = otel.Tracer("cosmos-sdk/x/mymodule") meter = otel.Meter("cosmos-sdk/x/mymodule") txCounter metric.Int64Counter latencyHist metric.Float64Histogram ) func init() { var err error txCounter, err = meter.Int64Counter( "mymodule.tx.count", metric.WithDescription("Number of transactions processed"), ) if err != nil { panic(err) } latencyHist, err = meter.Float64Histogram( "mymodule.tx.latency", metric.WithDescription("Transaction processing latency"), metric.WithUnit("ms"), ) if err != nil { panic(err) } } // ExampleWithContext demonstrates tracing with a standard context.Context. // Use tracer.Start directly when you have a Go context. func ExampleWithContext(ctx context.Context) error { ctx, span := tracer.Start(ctx, "ExampleWithContext", trace.WithAttributes(attribute.String("key", "value")), ) defer span.End() // Record metrics txCounter.Add(ctx, 1) if err := doWork(ctx); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } return nil } // ExampleWithSDKContext demonstrates tracing with sdk.Context. // Use ctx.StartSpan to properly propagate the span through the SDK context. func ExampleWithSDKContext(ctx sdk.Context) error { ctx, span := ctx.StartSpan(tracer, "ExampleWithSDKContext", trace.WithAttributes(attribute.String("module", "mymodule")), ) defer span.End() // Record metrics (sdk.Context implements context.Context) txCounter.Add(ctx, 1) // Create child spans for sub-operations ctx, childSpan := ctx.StartSpan(tracer, "ExampleWithSDKContext.SubOperation") // ... do sub-operation work ... childSpan.End() return nil } ``` ### Centralized Authority via Consensus Params Authority management can now be centralized via the `x/consensus` module. A new `AuthorityParams` field in `ConsensusParams` stores the authority address on-chain. When set, it takes precedence over the per-keeper authority parameter. **This feature introduces no breaking changes**: Keeper constructors still accept the `authority` parameter. It is now used as a **fallback** when no authority is configured in consensus params. Existing code continues to work without changes. #### How AuthorityParams Works When a module validates authority (e.g., in `UpdateParams`), it checks consensus params first. If no authority is set there, it falls back to the keeper's `authority` field: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} authority := sdkCtx.Authority() // from consensus params if authority == "" { authority = k.authority // fallback to keeper field } if authority != msg.Authority { return nil, errors.Wrapf(...) } ``` To enable centralized authority, set the `AuthorityParams` in consensus params via a governance proposal targeting the `x/consensus` module's `MsgUpdateParams`. ## Upgrade Handler This section provides a reference example for implementing the on-chain upgrade itself. The following is an example upgrade handler for upgrading from **v0.53.6** to **v0.54.0**. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const UpgradeName = "v0.53.6-to-v0.54.0" func (app SimApp) RegisterUpgradeHandlers() { app.UpgradeKeeper.SetUpgradeHandler( UpgradeName, func(ctx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { return app.ModuleManager.RunMigrations(ctx, app.Configurator(), fromVM) }, ) upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() if err != nil { panic(err) } if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := storetypes.StoreUpgrades{ Added: []string{}, } // configure store loader that checks if version == upgradeHeight and applies store upgrades app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } } ``` ## IBC v11 Updates IBC v11 introduces several improvements, removes long-deprecated APIs (`ParamSubspace` from all Keeper constructors, `MsgSubmitMisbehaviour`, and `ibcwasmtypes.Checksums`), and adds custom address codec support in the transfer module to enable Cosmos EVM compatibility with IBC transfers. Read the [Changelog](https://github.com/cosmos/ibc-go/blob/main/CHANGELOG.md) and [v11 Migration Guide](https://docs.cosmos.network/ibc/latest/migrations/v10-to-v11) for more information. ## Cosmos Performance Upgrades (Experimental) For Q1 of 2026, Cosmos Labs has been focusing on greatly improving performance of Cosmos SDK applications. v0.54 of Cosmos SDK introduces support for several performance-related features across the stack. The SDK introduces [BlockSTM](#blockstm) for concurrent transactions, and CometBFT introduces [LibP2P](#libp2p) and [`AdaptiveSync`](#adaptivesync). NOTE: It is important to emphasize that the following are **experimental** features. We DO NOT recommend running chains with these features enabled in production without extensive testing. ### Cosmos SDK #### BlockSTM BlockSTM enables deterministic, concurrent execution of transactions, improving block execution speeds and throughput. Developers interested in experimenting with BlockSTM should read the [documentation](https://docs.cosmos.network/sdk/latest/experimental/blockstm). Below is an example of setting up BlockSTM: > **⚠️ Warning:** BlockSTM is experimental. Ensure thorough testing before enabling in production. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "runtime" "github.com/cosmos/cosmos-sdk/baseapp/blockstm" ) oKeys := storetypes.NewObjectStoreKeys(banktypes.ObjectStoreKey) keys := storetypes.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, // ... other store keys ) // Collect non-transient store keys var nonTransientKeys []storetypes.StoreKey for _, k := range keys { nonTransientKeys = append(nonTransientKeys, k) } for _, k := range oKeys { nonTransientKeys = append(nonTransientKeys, k) } // Enable BlockSTM runner bApp.SetBlockSTMTxRunner(blockstm.NewSTMRunner( txConfig.TxDecoder(), nonTransientKeys, min(runtime.GOMAXPROCS(0), runtime.NumCPU()), true, // debug logging sdk.DefaultBondDenom, )) // Optionally disable block gas meter for better performance bApp.SetDisableBlockGasMeter(true) // Set ObjectStoreKey on bank module app.BankKeeper = app.BankKeeper.WithObjStoreKey(oKeys[banktypes.ObjectStoreKey]) ``` ### CometBFT v0.39 Updates #### LibP2P libp2p replaces CometBFT's legacy `comet-p2p` transport layer with [go-libp2p](https://libp2p.io/). It adds native stream-oriented transport, concurrent receive pipelines, and autoscaled worker pools per reactor, reducing queue pressure and improving message flow under load. In benchmarks, libp2p has been a key contributor to reaching over 2000 TPS. Beyond raw throughput, it improves network liveness by making peer communication and block propagation more resilient under sustained congestion and sudden load spikes. Unlike other opt-in features, **to opt-in to libp2p, every validator in the network must upgrade together**. CometBFT p2p and libp2p are fundamentally incompatible and cannot interoperate. Because of this, a coordinated network-wide migration at a specific upgrade height is required. See the [libp2p page](https://docs.cosmos.network/cometbft/latest/docs/experimental/lib-p2p) in the CometBFT documentation for details. #### `AdaptiveSync` `AdaptiveSync` allows a node to run `blocksync` and consensus at the same time for faster recovery behavior. In the default flow, a node starts in `blocksync`, catches up, then switches to consensus. Under sustained load, a node can remain behind and struggle to catch up. With `adaptive_sync` enabled, consensus still works normally, but it can also ingest already available blocks from `blocksync`, allowing nodes to recover more quickly during traffic spikes. `AdaptiveSync` does not change consensus safety or finality rules. See the [`AdaptiveSync` documentation](https://docs.cosmos.network/cometbft/latest/docs/core/block-sync#adaptivesync) for details. # v0.54 Release Notes Source: https://docs.cosmos.network/sdk/latest/upgrade/v0.54-release What's new in the latest Cosmos SDK release, including performance improvements, new features, and removals. If you are upgrading to v0.54, see the [upgrade guide](/sdk/latest/upgrade/v0.54). For a full list of changes, see the [changelog](https://github.com/cosmos/cosmos-sdk/blob/release/v0.54.x/CHANGELOG.md). ## Overview This release introduces order of magnitude improvements to network stability and throughput. In testing, we are able to support sustained 1K TPS on a variety of network configurations with no degradation in block time, whereas previously block production would have slowed / halted almost immediately after 200+ TPS. This is made possible through 2 critical performance improvements targeting different layers of the stack: * **Parallel transactions (BlockSTM)**: When applied to blocks containing fully parallelizable transactions, Block STM shows between 5-10x improvements in execution time depending on the available CPUs, size of the blocks, and types of transactions being run. We have modified the underlying implementations of Cosmos bank sends and EVM native sends to ensure they are parallelizable, so you will benefit from speed ups of these transactions immediately. It is possible to do the same for other common kinds of Cosmos transactions (e.g. governance, staking, auth), but we haven’t optimized them yet. Custom transaction types and EVM smart contracts may similarly require implementation modifications to benefit from parallelization. See our guide [here](/sdk/latest/experimental/blockstm) for more information. * **Enhanced Networking (LibP2P):** The lib-p2p based reactor implementation outperforms Comet’s existing p2p implementation on latency benchmarks across a variety of workloads, reducing p99 latency metrics by a factor of 100 and up to 1000 in some cases. libp2p is industry-standard in peer-to-peer data exchange. Under the hood, it leverages QUIC, a modern low-latency UDP-based communication protocol. At this time, lib-p2p is meant for usage in centrally managed Cosmos networks, as peer exchange and upgradeability from comet’s networking stack are not supported yet. Please reach out if you are interested in testing libp2p in devnet or testnet environments and potentially contributing these improvements. We want to work closely with teams to gather feedback. See the [LibP2P guide](/cometbft/latest/docs/experimental/lib-p2p) for more information. ## Additional Features 1. **AdaptiveSync** helps nodes catchup when they fall behind by letting consensus and blocksync work simultaneously. During traffic spikes or short block times, this keeps nodes progressing with the network while preserving normal consensus safety and finality behavior. Especially valuable for RPC-heavy nodes. See the [block sync guide](/cometbft/latest/docs/core/block-sync#adaptivesync) for more information. 2. **Log/v2** supports the transition of the Cosmos SDK’s observability to OpenTelemetry, enabling automatic trace correlation across all log output (show via the logged keys `trace_id`, `span_id`, and `trace_flags`, if a span is present in the `ctx`). This is powered by four new required contextual logging methods on the `Logger` interface (`InfoContext`, `WarnContext`, etc). Additionally, a new `MultiLogger` allows fanning out to multiple logging backends simultaneously, which the server now uses automatically when OpenTelemetry is configured. See the [logging guide](/sdk/latest/guides/testing/log) and [telemetry guide](/sdk/latest/guides/testing/telemetry) for more information. 3. **IBC General Message Passing (GMP)**: General Message Passing in IBC enables calling arbitrary smart contracts on remote networks. Unlike Interchain Accounts, the caller does not need to own an account on the destination chain (though it is general enough to support this usage pattern). Instead, GMP directly calls contracts on the destination chain. This makes it especially useful for implementing mint/burn bridges (See [below](#upcoming-features-available-soon-in-minor-releases) for more details) ## Enterprise Features The following features are released as part of [Cosmos Enterprise](/sdk/latest/enterprise/overview): 1. The **Groups module** enables on-chain multisig and collective decision-making for any set of accounts. Groups are formed with weighted members and one or more configurable decision policies that define how proposals pass. Members submit proposals containing arbitrary SDK messages, vote, and any account can trigger execution once a proposal is accepted. Two built-in decision policies are included: threshold (absolute weighted vote count) and percentage (proportion of YES votes), each with configurable voting and minimum execution periods. The decision policy interface supports custom extensions. See the [Groups module docs](/sdk/latest/enterprise/group/overview) for more information. 2. The **POA module** provides an admin-managed validator set as a drop-in replacement for the staking, distribution, and slashing modules. Purpose-built for institutional deployments run by a known set of operators, it offers a streamlined validator lifecycle with no native token required. Fee distribution to validators and full governance compatibility are included out of the box. See the [POA module docs](/sdk/latest/enterprise/poa/overview) for more information. ## Upcoming Features (Available soon in Minor Releases) 1. **Krakatoa mempool (Cosmos EVM only)**: This mempool significantly improves transaction throughput and network stability by making the comet mempool stateless and introducing two new concurrent ABCI methods for transaction processing (`reapTxs` and `insertTx`). The upshot is that transaction processing is more concurrent and more lightweight, resulting in performance and stability gains. This will be available for Cosmos EVM chains at the end of April. 2. **Interchain Fungible Token Standard (IFT):** This is a more modern and flexible approach to token transfers in IBC compared to ICS20 that enables mint/burn based bridging. IFT decouples the contract or module that mints a token from the IBC channel. Importantly, this allows token issuers to establish canonical, owned deployments of their tokens on any networks they choose and manage cross-chain mints/burns with IBC, rather than using “wrapped” tokens that they cannot control. It also allows a single token to support fungibility over multiple IBC paths and to upgrade/change the IBC connection in the background without worrying about the “token path” changing. This is coming shortly to ibc-go, ibc-solidity, and ibc-sol. 3. **IBC support for any EVM network:** IBC functionality will extend directly to any EVM network as a collection of Solidity contracts that implement IBC Eureka. This will enable direct IBC connectivity without requiring any modifications to the EVM chain. This means Ethereum, Base, Arbitrum, Optimism, and other EVM networks can participate directly in IBC transfers. Combined with IFT, token issuers can manage canonical token deployments across Cosmos and any number of EVM chains from a single source of truth. 4. **IBC support for Solana:** Similar to EVM support, IBC connectivity will extend to Solana with a native program implementation. This will allow Solana to participate directly in IBC transfers with Cosmos and EVM chains, enabling cross-ecosystem token movement without wrapped tokens or intermediary chains. 5. **IBC v2 relayer:** A standalone, production-ready, request-driven relayer service for the IBC v2 protocol. This relayer will support interoperating between a Cosmos-based chain and major EVM networks (Ethereum, Base, Optimism, Arbitrum, Polygon, and more). Operators submit a source transaction hash and can track each packet's status in real time, from submission through relay completion, with full retry and failure recovery handled automatically. ## Removals The following features have been removed from this release family: * **ibc-apps/async-icq:** We have never had official support for ibc-apps/async-icq middleware. This is us just stating this explicitly. We will not be updating it as a part of this release or going forward. We will not be testing its compatibility with IBC-go v11.0.0 * **ibc-apps/pfm (packet forwarding middleware):** We have never had official support for PFM , but historically, we did update it and make a best effort to ensure compatibility with IBC in during previous release cycles. We will not be doing that as a part of this release or going forward. Instead, we are upstreaming PFM into IBC-Go to streamline our support. We will guarantee equivalent functionality and APIs as part of this migration. The upstreamed version will be available for you to migrate to in IBC-go v11.1.0, which we are planning to release towards the end of April 2026. * **ibc-apps/rate-limits:** We have never had official support for ibc-apps/rate-limits middleware, but historically, we did update it and make a best effort to ensure compatibility with IBC in during previous release cycles. We will not be doing that as a part of this release or going forward. Instead, we are upstreaming PFM into IBC-Go to streamline our support. We will guarantee equivalent functionality and APIs as part of this migration. The upstreamed version will be available for you to migrate to in IBC-go v11.2.0, which we are planning to release in the first weeks of May 2026. * **ibc-apps/ibc-hooks:** We have never had official support for ibc-apps/ibc-hooks middleware, but historically, we did update it and make a best effort to ensure compatibility with IBC in during previous release cycles. We will not be doing that as a part of this release or going forward. Instead, we are introducing and will maintain a new `callbacks` middleware that enables calling Cosmwasm contracts (like ibc-hooks) as well as Cosmos modules and EVM contracts when processing ICS20 packets. We are working to ensure the upcoming wasmd release will enable Cosmwasm contracts to adopt this without changing contract interfaces. # v0.55 Upgrade Guide Source: https://docs.cosmos.network/sdk/latest/upgrade/v0.55 Reference for upgrading to v0.55 of Cosmos SDK This document provides a reference for upgrading from `v0.54.x` to `v0.55.x` of Cosmos SDK. If you are upgrading directly from `v0.53.x`, see [Upgrading from v0.53.x](#upgrading-from-v0-53-x) after reading the breaking changes below. For a full list of changes, see the [Changelog](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/CHANGELOG.md). The headline changes in this release are the removal of three legacy surfaces (`x/params`, `x/protocolpool`, and `SIGN_MODE_TEXTUAL`), a reworked app-side mempool interface, and validator consensus key rotation in `x/staking`. Key rotation ships enabled for every chain that upgrades — see [Validator Consensus Key Rotation](#validator-consensus-key-rotation) — and requires one line of wiring in `app.go`. Everything else in the new-features list (ML-DSA-65 keys, secp256k1eth keys, config-driven Block-STM wiring) is opt-in. ## Table of Contents * [Breaking Changes](#breaking-changes) * [CometBFT Upgrade](#cometbft-upgrade) * [Removed: x/params](#removed-xparams) * [Removed: x/protocolpool](#removed-xprotocolpool) * [Removed: SIGN\_MODE\_TEXTUAL](#removed-sign_mode_textual) * [Mempool Interface Changes](#mempool-interface-changes) * [Staking: Key Rotation Wiring and Interface Changes](#staking-key-rotation-wiring-and-interface-changes) * [genutil: ExportGenesisFileWithTime Signature](#genutil-exportgenesisfilewithtime-signature) * [Upgrade Handler and Store Migrations](#upgrade-handler-and-store-migrations) * [Upgrading from v0.53.x](#upgrading-from-v053x) * [New Features and Non-Breaking Changes](#new-features-and-non-breaking-changes) * [Validator Consensus Key Rotation](#validator-consensus-key-rotation) * [ML-DSA-65 Validator Consensus Keys](#ml-dsa-65-validator-consensus-keys) * [ML-DSA-65 Account Keys](#ml-dsa-65-account-keys) * [secp256k1eth Validator Consensus Keys](#secp256k1eth-validator-consensus-keys) * [Block-STM Configuration](#block-stm-configuration) * [Behavior Changes Affecting Dapps and Indexers](#behavior-changes-affecting-dapps-and-indexers) ## Breaking Changes ### CometBFT Upgrade Cosmos SDK v0.55 requires CometBFT `v0.40.0` (the v0.54.x line shipped with `v0.39.x`, ending at `v0.39.3` in v0.54.3). Bump your app's `go.mod` to match the SDK's pin. Relevant changes in CometBFT v0.40.0: * Expanded `MaxSignatureSize` and per-validator `MaxCommitSigBytes` to accommodate post-quantum (ML-DSA-65) signatures. * A fix for the application-side mempool (`mempool.type = "app"`, supported since CometBFT v0.39.2 / SDK v0.54.3): the default socket transport was missing the `InsertTx` / `ReapTxs` cases, causing node self-kill ([cometbft#5958](https://github.com/cometbft/cometbft/pull/5958)). Chains using an app-side mempool over the socket transport need v0.40.0. * Updated `DefaultBlockParams` ([cometbft#5987](https://github.com/cometbft/cometbft/pull/5987)). This changes defaults for new chains only; existing chains keep their on-chain consensus params. See the [CometBFT changelog](https://github.com/cometbft/cometbft/blob/main/CHANGELOG.md) for the full list. ### Removed: x/params [#25546](https://github.com/cosmos/cosmos-sdk/pull/25546) removes the `x/params` module entirely (only a tombstone README remains). Module parameters have been managed by each module since v0.47; v0.55 removes the leftover machinery: 1. If your app still imports `x/params` (a `paramskeeper.Keeper`, per-module `Subspace`s, or the legacy gov proposal handler), remove that wiring. If the `params` store is still mounted, delete it in your store upgrades (see [Upgrade Handler and Store Migrations](#upgrade-handler-and-store-migrations)). Chains that have not yet migrated legacy subspace params to module-managed params must complete that migration **before** upgrading to v0.55 — the migration code is gone. 2. Drop the trailing `exported.Subspace` argument (typically passed as `nil`) from the module constructors that carried it for legacy migrations: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Before // After auth.NewAppModule(cdc, accountKeeper, randGenAccountsFn, nil) auth.NewAppModule(cdc, accountKeeper, randGenAccountsFn) bank.NewAppModule(cdc, bankKeeper, accountKeeper, nil) bank.NewAppModule(cdc, bankKeeper, accountKeeper) gov.NewAppModule(cdc, &govKeeper, accountKeeper, bankKeeper, nil) gov.NewAppModule(cdc, &govKeeper, accountKeeper, bankKeeper) mint.NewAppModule(cdc, mintKeeper, accountKeeper, nil, nil) mint.NewAppModule(cdc, mintKeeper, accountKeeper, nil) slashing.NewAppModule(cdc, keeper, ak, bk, sk, nil, registry) slashing.NewAppModule(cdc, keeper, ak, bk, sk, registry) distr.NewAppModule(cdc, keeper, ak, bk, stakingKeeper, nil) distr.NewAppModule(cdc, keeper, ak, bk, stakingKeeper) staking.NewAppModule(cdc, keeper, ak, bk, nil) staking.NewAppModule(cdc, keeper, ak, bk) ``` (`mint.NewAppModule` retains its deprecated `InflationCalculationFn` parameter; only the subspace argument is removed.) ### Removed: x/protocolpool [#26421](https://github.com/cosmos/cosmos-sdk/pull/26421) removes the `x/protocolpool` module and its proto/API surface from the SDK. The `distrkeeper.WithExternalCommunityPool` extension point is removed with it — `x/distribution` always uses its internal `FeePool` community pool again, and `MsgFundCommunityPool` / `MsgCommunityPoolSpend` operate on it directly. **Required action** if your app wired `x/protocolpool` (the v0.54 SimApp default): 1. Remove all `protocolpool` wiring from `app.go`: the imports, the `ProtocolPoolKeeper` field and its `NewKeeper` call, the `protocolpooltypes.ModuleName` and `protocolpooltypes.ProtocolPoolEscrowAccount` entries in `maccPerms`, the module manager entry, and its entries in the begin-block, end-block, init-genesis, and export orders. 2. Remove `distrkeeper.WithExternalCommunityPool(app.ProtocolPoolKeeper)` from your `distrkeeper.NewKeeper` call. 3. Delete the `protocolpool` store in your store upgrades (see [Upgrade Handler and Store Migrations](#upgrade-handler-and-store-migrations)). 4. Balances held by the protocolpool module accounts are bank state and are **not** migrated automatically. Decide where those funds go and move them in your upgrade handler — e.g. transfer them to the `x/distribution` community pool so community-pool spend proposals keep working. If your app never wired `x/protocolpool`, no action is needed beyond not being able to import it. ### Removed: SIGN\_MODE\_TEXTUAL `SIGN_MODE_TEXTUAL` (proto enum value `2`) and its entire implementation have been removed ([#26456](https://github.com/cosmos/cosmos-sdk/pull/26456)): * `x/tx/signing/textual/` — all renderers, the CBOR encoder, test data, and internal protos * `x/auth/tx/textual.go` and `ConfigOptions.TextualCoinMetadataQueryFn` * Ledger + SIGN\_MODE\_TEXTUAL integration in `client/` flags and tx factory The proto enum value `2` and string `"SIGN_MODE_TEXTUAL"` are **reserved** to prevent future reuse. ADR-050 is archived. **Required action** if your app enabled SIGN\_MODE\_TEXTUAL: 1. Remove `TextualCoinMetadataQueryFn` from your `tx.ConfigOptions`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Before txConfig, err := tx.NewTxConfigWithOptions(cdc, tx.ConfigOptions{ TextualCoinMetadataQueryFn: ..., }) // After — field removed, omit it txConfig, err := tx.NewTxConfigWithOptions(cdc, tx.ConfigOptions{...}) ``` 2. Remove any `SIGN_MODE_TEXTUAL` cases from signing mode handler switch statements. 3. Remove Ledger wiring that depended on `SIGN_MODE_TEXTUAL`. Client-side root command wiring that constructed a textual-enabled tx config for online mode (as v0.54 SimApp did in `simd/cmd/root.go`) should be deleted as well. ### Mempool Interface Changes [#25338](https://github.com/cosmos/cosmos-sdk/pull/25338) changes the `types/mempool` interfaces so the mempool stores the gas wanted reported by the ante handler at `CheckTx` time, and block selection uses that value instead of the tx-declared gas limit. **Required action** if you implement a custom mempool (chains using the SDK's built-in mempools or no app-side mempool just recompile): * `Insert` gains an `InsertOption` parameter carrying the ante-reported gas: `Insert(context.Context, sdk.Tx, InsertOption) error`. * `Iterator.Tx()` now returns a `PooledTx` (`{Tx sdk.Tx; GasWanted uint64}`) instead of `sdk.Tx`. * `ExtMempool.SelectBy`'s callback now receives a `PooledTx`: `SelectBy(context.Context, [][]byte, func(PooledTx) bool)`. * `ExtMempool.RemoveWithReason` and the `RemoveReason` type, introduced in v0.54, are unchanged. Custom `PrepareProposal` handlers that iterate the mempool should read gas from `PooledTx.GasWanted` rather than re-deriving it from the tx. ### Staking: Key Rotation Wiring and Interface Changes `x/staking` now requires a `key_rotation_fee_pool` module account with burn permissions — the staking keeper panics at construction if it is missing (`x/staking/keeper/keeper.go`). Add it to your `maccPerms`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} maccPerms = map[string][]string{ // ...existing entries... stakingtypes.KeyRotationFeePoolName: {authtypes.Burner}, } ``` This is required for **all** chains upgrading to v0.55, whether or not validators are expected to use [key rotation](#validator-consensus-key-rotation). Key rotation also touches two staking keeper surfaces that external modules may implement or consume: * The `StakingHooks` interface gains `AfterValidatorConsKeyUpdated(ctx context.Context, oldConsAddr, newConsAddr sdk.ConsAddress, valAddr sdk.ValAddress) error`, called when a rotation is applied. Custom `StakingHooks` implementations must add this method (returning `nil` is fine if you don't need the notification). * The staking keeper adds `ValidatorByHistoricalConsAddr(ctx, consAddr)`, which resolves a validator from a consensus address it used before a rotation. Modules that map consensus addresses to validators can no longer assume that mapping is immutable — see [Validator Consensus Key Rotation](#validator-consensus-key-rotation). ### genutil: ExportGenesisFileWithTime Signature [#26468](https://github.com/cosmos/cosmos-sdk/pull/26468) consolidates `ExportGenesisFileWithTime`'s arguments so the exported file preserves consensus params (previously they were rebuilt from defaults, dropping the caller's values): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Before func ExportGenesisFileWithTime(genFile, chainID string, validators []cmttypes.GenesisValidator, appState json.RawMessage, genTime time.Time) error // After — build the AppGenesis yourself; everything you set on it is preserved func ExportGenesisFileWithTime(genFile string, appGenesis *types.AppGenesis, genTime time.Time) error ``` ### Upgrade Handler and Store Migrations #### Module Migrations Two module consensus-version bumps ship in this release and run automatically via `RunMigrations` in your upgrade handler: * `x/staking` 5 → 6: adds the `key_rotation_fee` param, defaulting to `1000000` of the bond denom ([#26485](https://github.com/cosmos/cosmos-sdk/pull/26485)). `Params.Validate` requires the fee denom to equal `bond_denom` ([#26613](https://github.com/cosmos/cosmos-sdk/pull/26613)). * `x/auth` 6 → 7: adds the `SigVerifyCostMlDsa65` param with its default value ([#26472](https://github.com/cosmos/cosmos-sdk/pull/26472)). #### Reference Upgrade Handler A reference upgrade handler for this release (see `simapp/upgrades.go`): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const UpgradeName = "v054-to-v055" func (app SimApp) RegisterUpgradeHandlers() { app.UpgradeKeeper.SetUpgradeHandler( UpgradeName, func(ctx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { return app.ModuleManager.RunMigrations(ctx, app.Configurator(), fromVM) }, ) upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() if err != nil { panic(err) } if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := storetypes.StoreUpgrades{ Added: []string{}, Deleted: []string{"protocolpool"}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } } ``` Add `"params"` to `Deleted` as well if your app still had the `x/params` store mounted. ## Upgrading from v0.53.x Skipping v0.54 and upgrading directly from `v0.53.x` to `v0.55.x` is supported as a single coordinated upgrade: one binary swap, one upgrade handler, one halt height. Work through the [v0.53.x → v0.54.x upgrade reference](https://github.com/cosmos/cosmos-sdk/blob/release/v0.54.x/UPGRADING.md) first — all of its required changes still apply — then apply this guide on top. The v0.54 hop's highlights, so you know what you're signing up for: * CometBFT `v0.38.x` → `v0.39.x` (LibP2P, `AdaptiveSync`); from v0.53 you jump straight to the `v0.40.0` release v0.55 pins. * Consolidation of `cosmossdk.io/x/*` vanity modules into `github.com/cosmos/cosmos-sdk/x/*`, plus the Log v2 and Store v2 moves. * `x/gov` keeper-initialization and `GovHooks` interface changes, `x/epochs` and `x/bank` wiring updates, and the `x/circuit` / `x/nft` / `x/crisis` deprecations. * IBC v11 (if your chain uses IBC). Where the two hops interact, land directly on the v0.55 state instead of transiting through v0.54's: * **Skip transient wiring.** Don't adopt v0.54 reference-app wiring that v0.55 removes in the same hop: the SIGN\_MODE\_TEXTUAL tx-config setup, `x/protocolpool` (if your v0.53 app didn't already wire it), and `distrkeeper.WithExternalCommunityPool`. Go straight to the v0.55 forms shown in this guide. * **Module constructors.** v0.54's constructor signatures still carried the legacy `exported.Subspace` arguments; use the v0.55 signatures from [Removed: x/params](#removed-xparams) directly. * **Custom mempools.** Implement the v0.55 `Mempool` interface ([Mempool Interface Changes](#mempool-interface-changes)) directly; don't bother with the v0.54 shape. * **Module migrations are cumulative.** `RunMigrations` walks each module from its v0.53 consensus version to the v0.55 target in one pass (`x/auth` 5 → 6 → 7, `x/staking` 5 → 6). No manual intervention is needed beyond the standard upgrade handler. * **Store upgrades.** The v0.53 → v0.54 hop required no store additions or deletions, so the combined store upgrade is exactly the snippet in [Upgrade Handler and Store Migrations](#upgrade-handler-and-store-migrations): delete `protocolpool` only if your v0.53 app had wired it, and `params` if its store was still mounted (more likely on a v0.53-era app). Use a single upgrade name, e.g. `v053-to-v055`. Test the full jump on a mainnet-state export before scheduling it: the two-version migration path gets far less ecosystem mileage than the single-version one. ## New Features and Non-Breaking Changes These changes are optional to adopt during the upgrade; they are not required for a successful migration. The exception is key rotation, which is active on every v0.55 chain once the required wiring above is in place. ### Validator Consensus Key Rotation v0.55 adds consensus key rotation to `x/staking` ([#26440](https://github.com/cosmos/cosmos-sdk/pull/26440)): a validator operator can submit `MsgRotateConsPubKey` (wired into the CLI, [#26461](https://github.com/cosmos/cosmos-sdk/pull/26461)) to replace their consensus key without unbonding. Key properties: * **Fee.** Each rotation charges the `key_rotation_fee` staking param (default `1000000` of the bond denom) from the operator account; the fee is burned via the `key_rotation_fee_pool` module account. * **Rate limit.** One rotation per validator per unbonding period. * **Applied in the end blocker.** The rotation is scheduled by the msg server and applied at the end of the block; CometBFT is informed through a validator-set update. * **Evidence and slashing.** Equivocation evidence against a rotated-away (historical) consensus address remains attributable to the validator until the evidence is no longer admissible — i.e. until both `evidence.max_age_num_blocks` and `evidence.max_age_duration` have elapsed since the rotation, which can be later than the unbonding time ([#26481](https://github.com/cosmos/cosmos-sdk/pull/26481), [#26616](https://github.com/cosmos/cosmos-sdk/pull/26616)). Slashing signing info is migrated to the active consensus key. Governance changes that extend the evidence-age params after a rotation's expiry has been computed are not retroactively applied; chains should account for this when tuning evidence params. * **Genesis.** Rotation history and pending-rotation state are included in staking genesis import/export ([#26471](https://github.com/cosmos/cosmos-sdk/pull/26471)); genesis export tooling that parses staking genesis JSON should expect the new fields. * **Events.** `rotate_cons_pubkey` is emitted when a rotation is scheduled (including apply height, maturity time, evidence-expiry time/height, and the burned fee) and `apply_cons_pubkey_rotation` when it is applied (validator, old and new consensus addresses) ([#26619](https://github.com/cosmos/cosmos-sdk/pull/26619)). Indexers, exchanges, and monitoring that key validators by consensus address must handle the mapping changing over a validator's lifetime. On-chain, `keeper.ValidatorByHistoricalConsAddr` resolves a validator from a rotated-away consensus address. Chains built on the enterprise `x/poa` module have their own `MsgRotateConsPubKey` with different semantics — no fee, no rate limit, an admin override, and a same-block swap with no rotation history. Because the old consensus address is gone immediately, modules that attribute `LastCommit` signatures or vote extensions by consensus address need extra care across the swap; see the PoA guide below for the caveats and the operator runbook. For an overview of key rotation and the operator procedures, see [Key rotation](https://docs.cosmos.network/sdk/latest/keys/key-rotation), [Rotate a consensus key, Staking](https://docs.cosmos.network/sdk/latest/keys/rotate-validator-key), and [Rotate a consensus key, PoA](https://docs.cosmos.network/sdk/latest/keys/rotate-validator-key-poa). ### ML-DSA-65 Validator Consensus Keys Cosmos SDK v0.55 registers the NIST ML-DSA-65 (FIPS 204) post-quantum signature scheme as a supported validator consensus key type ([#26436](https://github.com/cosmos/cosmos-sdk/pull/26436)). The new `cosmos.crypto.mldsa65.PubKey` / `PrivKey` proto messages, Amino routes (`cometbft/PubKeyMlDsa65`, `cometbft/PrivKeyMlDsa65`), interface-registry registration, multisig amino route, and `hd.MlDsa65Type` constant are all enabled by default. **Action required:** none. Existing chains continue to accept only the consensus key types listed in `genesis.consensus_params.validator.pub_key_types` (still `["ed25519"]` by default). No state-machine-relevant behavior changes for chains that do not opt in. **To opt in (new chains):** set `genesis.consensus_params.validator.pub_key_types` to `["ml_dsa_65"]` (or a list including it). Validators must then submit `MsgCreateValidator` with a `mldsa65.PubKey`. The `init` and `testnet` commands accept `--consensus-key-algo ml_dsa_65` to generate matching validator files ([#26604](https://github.com/cosmos/cosmos-sdk/pull/26604)). Test harnesses can use the new `testutil/network.Config.ValidatorConsensusKeyType` field together with `genutil.InitializeNodeValidatorFilesFromMnemonicWithKeyType` to spin up an in-process testnet pinned to ML-DSA-65. **Operational considerations:** ML-DSA-65 keys and signatures are substantially larger than ed25519 (pubkey 1952 bytes vs 32, signature 3309 bytes vs 64). Chains enabling this key type should review `consensus_params.block.max_bytes` and gossip framing limits accordingly. The cometbft commit lift in this release expanded `MaxSignatureSize` and the per-validator `MaxCommitSigBytes` to accommodate the larger signatures; downstream applications relying on the previous fixed values may need to be re-examined. **Warning — IBC counterparties must upgrade first.** IBC light clients on counterparty chains verify your validator set's commit signatures using the counterparty's own compiled-in crypto. A counterparty running a stack that predates ML-DSA-65 support cannot verify signatures from the new key type: once validators holding sufficient voting power sign with it, your headers fail verification there, IBC packet flow with that chain stops, and the client eventually expires. Before enabling a new consensus key type on a chain with live IBC connections, coordinate so every counterparty chain is running a CometBFT/SDK stack that can verify it — the counterparty only needs the verification code on its nodes, not the key type in its own `pub_key_types`. Existing chains can combine this with [key rotation](#validator-consensus-key-rotation) to move validators to post-quantum keys: add `ml_dsa_65` to `pub_key_types` via a consensus-params update, then have validators rotate. For the concepts and operator guides, see [Post-quantum keys](https://docs.cosmos.network/sdk/latest/keys/post-quantum-keys), [Enable ML-DSA keys](https://docs.cosmos.network/sdk/latest/keys/enable-ml-dsa-keys), and [Migrate a validator to ML-DSA](https://docs.cosmos.network/sdk/latest/keys/migrate-validator-ml-dsa). ### ML-DSA-65 Account Keys [#26472](https://github.com/cosmos/cosmos-sdk/pull/26472) extends ML-DSA-65 support to user account keys: keyring creation and mnemonic recovery (`--algo ml_dsa_65`), transaction signing and verification, and a new ante-handler gas cost param `SigVerifyCostMlDsa65` (added to `x/auth` params by the automatic 6 → 7 migration). No action is required; accounts using existing key types are unaffected. See [Create an ML-DSA account](https://docs.cosmos.network/sdk/latest/keys/create-ml-dsa-account) and [Post-quantum keys](https://docs.cosmos.network/sdk/latest/keys/post-quantum-keys). ### secp256k1eth Validator Consensus Keys [#26615](https://github.com/cosmos/cosmos-sdk/pull/26615) adds `crypto/keys/secp256k1eth`, wrapping CometBFT's Ethereum-style secp256k1 consensus key implementation with SDK codec registration. Intended for EVM-compatible chains that want validator consensus addresses derived the Ethereum way; opt in via `genesis.consensus_params.validator.pub_key_types`. The IBC counterparty warning from the [ML-DSA-65 section](#ml-dsa-65-validator-consensus-keys) applies here too: counterparty chains must run a stack that can verify secp256k1eth signatures before your validators adopt the key type, or IBC connections with them will break. See [Post-quantum keys](https://docs.cosmos.network/sdk/latest/keys/post-quantum-keys) for how the consensus key types compare. ### Block-STM Configuration Block-STM parallel execution itself is not new — the engine (`baseapp/txnrunner`) and the `SetBlockSTMTxRunner` hook shipped in v0.54.x, wired programmatically per chain. v0.55 adds standard operator-facing configuration ([#26208](https://github.com/cosmos/cosmos-sdk/pull/26208)): `block-executor` (`"sequential"`, the default, or `"block-stm"`), `block-stm-workers`, and `block-stm-pre-estimate` in `app.toml`, plus a `baseapp/blockexec` helper that resolves them and installs the runner. Chains that already call `SetBlockSTMTxRunner` directly can keep that wiring or switch to the helper. To adopt the config-driven wiring, call `blockexec.Apply` after creating your store keys (see `simapp/app.go`): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} stores := make([]storetypes.StoreKey, 0, len(keys)) for _, k := range keys { stores = append(stores, k) } blockexec.Apply(bApp, appOpts, stores, txConfig.TxDecoder(), func(storetypes.MultiStore) string { return sdk.DefaultBondDenom }, ) ``` `Apply` resolves the executor from `app.toml`/flags and installs the corresponding `TxRunner`; with the default `sequential` executor it preserves today's behavior, so the wiring is safe to add unconditionally. Block-STM is incompatible with the block gas meter (disabled by default since v0.54): `Apply` disables the meter automatically when `block-stm` is selected, but chains wiring `SetBlockSTMTxRunner` directly must call `SetDisableBlockGasMeter(true)` first or the runner installation panics. Switching a running chain's executor is a per-node setting with identical state-transition results, but treat the first enablement as an operational rollout: test with your workload before flipping validators. ## Behavior Changes Affecting Dapps and Indexers Observable changes between v0.54.x and v0.55.x that don't require code changes but may affect downstream consumers: * **Block selection uses ante-reported gas.** Proposals are packed using the gas wanted returned by the ante handler at `CheckTx` time rather than the tx-declared gas limit ([#25338](https://github.com/cosmos/cosmos-sdk/pull/25338)). Block composition can differ for txs whose ante-reported gas diverges from their declared limit. * **Staking emits key-rotation events** (`rotate_cons_pubkey`, `apply_cons_pubkey_rotation`), and validator consensus addresses can change over time ([#26619](https://github.com/cosmos/cosmos-sdk/pull/26619)). * **`x/gov` `proposal_messages` event attribute** no longer has a leading comma ([#26353](https://github.com/cosmos/cosmos-sdk/pull/26353)). * **`x/authz` prunes at most 200 expired grants per begin block** ([#26588](https://github.com/cosmos/cosmos-sdk/pull/26588)); mass-expiry cleanup now spreads across blocks. * **`x/distribution` reward withdrawals to blocked addresses** during begin/end block fall back to the delegator/validator owner and then the community pool instead of failing ([#26406](https://github.com/cosmos/cosmos-sdk/pull/26406)). User-initiated withdrawals to blocked addresses still return `ErrUnauthorized`. * **`x/feegrant` `Allowances` and `AllowancesByGranter` queries** now honor `PageRequest.offset` and `count_total` correctly ([#26596](https://github.com/cosmos/cosmos-sdk/pull/26596)); clients that compensated for the old off-by-page results should re-check. # v0.55 Release Notes Source: https://docs.cosmos.network/sdk/latest/upgrade/v0.55-release What's new in the 2026.1 Ledger Security release: post-quantum keys, validator consensus key rotation, and remote signing with Cosmos-KMS. If you are upgrading to v0.55, see the [upgrade guide](/sdk/latest/upgrade/v0.55). For a full list of changes, see the [changelog](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/CHANGELOG.md). ## Overview This release is a holistic upgrade to the security of the Cosmos Stack. It adds the first native post-quantum key option in Cosmos, in-place validator consensus key rotation with no downtime, and a remote signer that keeps validator keys in your own KMS or HSM. All four artifacts ship together and join the existing 2026.1 release family. For the versions each family pins, see [Release Families](/sdk/latest/release-family). ## What ships | Artifact | Version | What changed | | ------------------------------------------------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------ | | [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) | v0.55.0 | ML-DSA account and consensus keys, consensus key rotation through `x/staking`, keyring key types | | [CometBFT](https://github.com/cometbft/cometbft) | v0.40.0 | ML-DSA consensus key support and remote signer compatibility | | [enterprise/poa](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/enterprise/poa) | v1.1.0 | Consensus key rotation for PoA validators, by the operator or the chain admin | | [cosmos-kms](https://github.com/cosmos/kms) | v1.0.0 | First release of the remote signer | ## Features ### Post-quantum keys (ML-DSA) Chains can run ML-DSA for consensus and user-account keys. ML-DSA keys use lattice-based signatures, which are considered more quantum resistant than elliptic-curve-based keys. New chains set the allowed key types through consensus params; existing chains migrate one validator at a time, and a validator-led path moves a classical key to ML-DSA in place with no hard fork. A chain reaches post-quantum security once validators holding two-thirds of voting power have rotated to ML-DSA keys, the same threshold CometBFT uses to finalize blocks. See [Post-quantum keys](/sdk/latest/keys/post-quantum-keys) for the tradeoffs, [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys) to allow the type on a chain, and [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa) for the per-validator path. ### Validator consensus key rotation Staked validators rotate a consensus key in place, keeping the validator's address, voting power, and accumulated fees, so the rotation stays invisible to delegators. Before this, a compromised or policy-expired consensus key meant standing up a new validator and rebuilding the delegator base. The operator submits `MsgRotateConsPubKey` with the new consensus public key, and CometBFT applies the change two heights later, which lets the operator bring up the new node with no downtime. Each rotation burns the `key_rotation_fee` staking parameter, a validator can rotate once per unbonding period, and a rotated-away key stays attributable for slashing until equivocation evidence for it can no longer be admitted. See [Key rotation](/sdk/latest/keys/key-rotation) for the mechanics and security implications, and [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key) for the procedure. PoA chains follow [Rotate a consensus key, PoA](/sdk/latest/keys/rotate-validator-key-poa). ### Remote signing with Cosmos-KMS `cosmos-kms` is a new remote signing solution that signs on the validator's behalf while keys stay in your own HSM or cloud KMS rather than in local files on the node. It adds AWS KMS and PKCS#11 backends and post-quantum ML-DSA signing, none of which TMKMS supported. See [Cosmos-KMS and remote signing](/sdk/latest/kms/remote-signing) for the architecture, and the [remote signing tutorial](/sdk/latest/kms/tutorial-file-backend) to run one against a local chain. ## Removals and deprecations ### TMKMS deprecation notice This release begins the deprecation of TMKMS. TMKMS reaches official deprecation six months from this release, so operators running it have that window to move to `cosmos-kms`. Validators using TMKMS should migrate. See [Migrate from TMKMS](/sdk/latest/kms/migrate-from-tmkms), which covers moving each TMKMS backend to `cosmos-kms`. ### Removed in v0.55 * `x/params`, replaced by per-module params. * `x/protocolpool`, with the community pool returning to `x/distribution`. * `SIGN_MODE_TEXTUAL`. See the [upgrade guide](/sdk/latest/upgrade/v0.55) for the wiring changes each removal requires. ## Upgrading Upgrading to Cosmos SDK v0.55.0 bumps CometBFT to v0.40.0 automatically, so you do not upgrade CometBFT separately. Coordinate the upgrade across the validator set, since it moves the SDK and CometBFT together. We document the [0.54 to 0.55 upgrade path](/sdk/latest/upgrade/v0.55), which also includes a [section on upgrading from 0.53 directly to 0.55](/sdk/latest/upgrade/v0.55#upgrading-from-v0-53-x). Module upgrades work across the last two SDK versions. ## Upcoming The following features are planned for a future release: * Enterprise HSM and key custody. AWS KMS supports ML-DSA signatures through Cosmos-KMS in this release. Other HSM and KMS solutions will be supported in a future release. * Post-quantum support for attestors and signers. * Ledger-layer confidential transactions. # Auth Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/auth gRPC queries and transaction messages defined by the auth module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### Account Account returns account details based on address. | | | | ---- | --------------------------------------------- | | gRPC | `cosmos.auth.v1beta1.Query/Account` | | REST | `GET /cosmos/auth/v1beta1/accounts/{address}` | Request `QueryAccountRequest`: | Field | Type | Description | | --------- | -------- | -------------------------------------------------------------------------- | | `address` | `string` | address defines the address to query for. Encoded as cosmos.AddressString. | Response `QueryAccountResponse`: | Field | Type | Description | | --------- | ----- | ----------------------------------------------------------------------------------------------------- | | `account` | `Any` | account defines the account of the corresponding address. One of: `/cosmos.auth.v1beta1.BaseAccount`. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address":""}' \ localhost:9090 cosmos.auth.v1beta1.Query/Account ``` ### AccountAddressByID AccountAddressByID returns account address based on account number. | | | | ---- | ---------------------------------------------------- | | gRPC | `cosmos.auth.v1beta1.Query/AccountAddressByID` | | REST | `GET /cosmos/auth/v1beta1/address_by_id/{accountId}` | Request `QueryAccountAddressByIDRequest`: | Field | Type | Description | | ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `int64` | Deprecated, use account\_id instead id is the account number of the address to be queried. This field should have been an uint64 (like all account numbers), and will be updated to uint64 in a future version of the auth query. Deprecated. | | `account_id` | `uint64` | account\_id is the account number of the address to be queried. | Response `QueryAccountAddressByIDResponse`: | Field | Type | Description | | ----------------- | -------- | -------------------------------- | | `account_address` | `string` | Encoded as cosmos.AddressString. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"account_id":"1"}' \ localhost:9090 cosmos.auth.v1beta1.Query/AccountAddressByID ``` ### AccountInfo AccountInfo queries account info which is common to all account types. | | | | ---- | ------------------------------------------------- | | gRPC | `cosmos.auth.v1beta1.Query/AccountInfo` | | REST | `GET /cosmos/auth/v1beta1/account_info/{address}` | Request `QueryAccountInfoRequest`: | Field | Type | Description | | --------- | -------- | ----------------------------------------------------------------------- | | `address` | `string` | address is the account address string. Encoded as cosmos.AddressString. | Response `QueryAccountInfoResponse`: | Field | Type | Description | | ------ | ------------------------------------------------- | ------------------------------------------------------------- | | `info` | [`BaseAccount`](#cosmos-auth-v1beta1-baseaccount) | info is the account info which is represented by BaseAccount. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address":""}' \ localhost:9090 cosmos.auth.v1beta1.Query/AccountInfo ``` ### Accounts Accounts returns all the existing accounts. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ------------------------------------ | | gRPC | `cosmos.auth.v1beta1.Query/Accounts` | | REST | `GET /cosmos/auth/v1beta1/accounts` | Request `QueryAccountsRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ---------------------------------------------------------- | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryAccountsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------ | | `accounts` | `Any`\[] | accounts are the existing accounts One of: `/cosmos.auth.v1beta1.BaseAccount`. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"pagination":{"limit":"1"}}' \ localhost:9090 cosmos.auth.v1beta1.Query/Accounts ``` ### AddressBytesToString AddressBytesToString converts Account Address bytes to string | | | | ---- | ------------------------------------------------ | | gRPC | `cosmos.auth.v1beta1.Query/AddressBytesToString` | | REST | `GET /cosmos/auth/v1beta1/bech32/{addressBytes}` | Request `AddressBytesToStringRequest`: | Field | Type | Description | | --------------- | ------- | ----------- | | `address_bytes` | `bytes` | | Response `AddressBytesToStringResponse`: | Field | Type | Description | | ---------------- | -------- | ----------- | | `address_string` | `string` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address_bytes":""}' \ localhost:9090 cosmos.auth.v1beta1.Query/AddressBytesToString ``` ### AddressStringToBytes AddressStringToBytes converts Address string to bytes | | | | ---- | ------------------------------------------------- | | gRPC | `cosmos.auth.v1beta1.Query/AddressStringToBytes` | | REST | `GET /cosmos/auth/v1beta1/bech32/{addressString}` | Request `AddressStringToBytesRequest`: | Field | Type | Description | | ---------------- | -------- | ----------- | | `address_string` | `string` | | Response `AddressStringToBytesResponse`: | Field | Type | Description | | --------------- | ------- | ----------- | | `address_bytes` | `bytes` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address_string":""}' \ localhost:9090 cosmos.auth.v1beta1.Query/AddressStringToBytes ``` ### Bech32Prefix Bech32Prefix queries bech32Prefix | | | | ---- | ---------------------------------------- | | gRPC | `cosmos.auth.v1beta1.Query/Bech32Prefix` | | REST | `GET /cosmos/auth/v1beta1/bech32` | Request `Bech32PrefixRequest`: This message has no fields. Response `Bech32PrefixResponse`: | Field | Type | Description | | --------------- | -------- | ----------- | | `bech32_prefix` | `string` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.auth.v1beta1.Query/Bech32Prefix ``` ### ModuleAccountByName ModuleAccountByName returns the module account info by module name | | | | ---- | ------------------------------------------------- | | gRPC | `cosmos.auth.v1beta1.Query/ModuleAccountByName` | | REST | `GET /cosmos/auth/v1beta1/module_accounts/{name}` | Request `QueryModuleAccountByNameRequest`: | Field | Type | Description | | ------ | -------- | ----------- | | `name` | `string` | | Response `QueryModuleAccountByNameResponse`: | Field | Type | Description | | --------- | ----- | --------------------------------------------- | | `account` | `Any` | One of: `/cosmos.auth.v1beta1.ModuleAccount`. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"name":""}' \ localhost:9090 cosmos.auth.v1beta1.Query/ModuleAccountByName ``` ### ModuleAccounts ModuleAccounts returns all the existing module accounts. | | | | ---- | ------------------------------------------ | | gRPC | `cosmos.auth.v1beta1.Query/ModuleAccounts` | | REST | `GET /cosmos/auth/v1beta1/module_accounts` | Request `QueryModuleAccountsRequest`: This message has no fields. Response `QueryModuleAccountsResponse`: | Field | Type | Description | | ---------- | -------- | --------------------------------------------- | | `accounts` | `Any`\[] | One of: `/cosmos.auth.v1beta1.ModuleAccount`. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.auth.v1beta1.Query/ModuleAccounts ``` ### Params Params queries all parameters. | | | | ---- | ---------------------------------- | | gRPC | `cosmos.auth.v1beta1.Query/Params` | | REST | `GET /cosmos/auth/v1beta1/params` | Request `QueryParamsRequest`: This message has no fields. Response `QueryParamsResponse`: | Field | Type | Description | | -------- | --------------------------------------- | -------------------------------------------- | | `params` | [`Params`](#cosmos-auth-v1beta1-params) | params defines the parameters of the module. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.auth.v1beta1.Query/Params ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### UpdateParams UpdateParams defines a (governance) operation for updating the x/auth module parameters. The authority defaults to the x/gov module account. | | | | ---------- | -------------------------------------- | | Type URL | `/cosmos.auth.v1beta1.MsgUpdateParams` | | Handler | `cosmos.auth.v1beta1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/x/auth/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `params` | [`Params`](#cosmos-auth-v1beta1-params) | params defines the x/auth parameters to update. NOTE: All parameters must be supplied. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.auth.v1beta1.MsgUpdateParams", "authority": "", "params": { "max_memo_characters": "1", "tx_sig_limit": "1", "tx_size_cost_per_byte": "1", "sig_verify_cost_ed25519": "1", "sig_verify_cost_secp256k1": "1", "sig_verify_cost_mldsa65": "1" } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.auth.v1beta1.BaseAccount BaseAccount defines a base account type. It contains all the necessary fields for basic account functionality. Any custom account type should extend this type for additional functionality (e.g. vesting). | Field | Type | Description | | ---------------- | -------- | -------------------------------- | | `address` | `string` | Encoded as cosmos.AddressString. | | `pub_key` | `Any` | | | `account_number` | `uint64` | | | `sequence` | `uint64` | | ### cosmos.auth.v1beta1.Params Params defines the parameters for the auth module. | Field | Type | Description | | --------------------------- | -------- | ----------- | | `max_memo_characters` | `uint64` | | | `tx_sig_limit` | `uint64` | | | `tx_size_cost_per_byte` | `uint64` | | | `sig_verify_cost_ed25519` | `uint64` | | | `sig_verify_cost_secp256k1` | `uint64` | | | `sig_verify_cost_mldsa65` | `uint64` | | ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | # Authz Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/authz gRPC queries and transaction messages defined by the authz module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### GranteeGrants GranteeGrants returns a list of `GrantAuthorization` by grantee. | | | | ---- | ---------------------------------------------------- | | gRPC | `cosmos.authz.v1beta1.Query/GranteeGrants` | | REST | `GET /cosmos/authz/v1beta1/grants/grantee/{grantee}` | Request `QueryGranteeGrantsRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------ | | `grantee` | `string` | Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines a pagination for the request. | Response `QueryGranteeGrantsResponse`: | Field | Type | Description | | ------------ | ------------------------------------------------------------------- | -------------------------------------------------- | | `grants` | [`GrantAuthorization`](#cosmos-authz-v1beta1-grantauthorization)\[] | grants is a list of grants granted to the grantee. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines a pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"grantee":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.authz.v1beta1.Query/GranteeGrants ``` ### GranterGrants GranterGrants returns list of `GrantAuthorization`, granted by granter. | | | | ---- | ---------------------------------------------------- | | gRPC | `cosmos.authz.v1beta1.Query/GranterGrants` | | REST | `GET /cosmos/authz/v1beta1/grants/granter/{granter}` | Request `QueryGranterGrantsRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------ | | `granter` | `string` | Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines a pagination for the request. | Response `QueryGranterGrantsResponse`: | Field | Type | Description | | ------------ | ------------------------------------------------------------------- | -------------------------------------------------- | | `grants` | [`GrantAuthorization`](#cosmos-authz-v1beta1-grantauthorization)\[] | grants is a list of grants granted by the granter. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines a pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"granter":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.authz.v1beta1.Query/GranterGrants ``` ### Grants Returns list of `Authorization`, granted to the grantee by the granter. | | | | ---- | ----------------------------------- | | gRPC | `cosmos.authz.v1beta1.Query/Grants` | | REST | `GET /cosmos/authz/v1beta1/grants` | Request `QueryGrantsRequest`: | Field | Type | Description | | -------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `granter` | `string` | Encoded as cosmos.AddressString. | | `grantee` | `string` | Encoded as cosmos.AddressString. | | `msg_type_url` | `string` | Optional, msg\_type\_url, when set, will query only grants matching given msg type. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines a pagination for the request. | Response `QueryGrantsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ------------------------------------------------------------------ | | `grants` | [`Grant`](#cosmos-authz-v1beta1-grant)\[] | authorizations is a list of grants granted for grantee by granter. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines a pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"granter":"","grantee":"","msg_type_url":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.authz.v1beta1.Query/Grants ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### Exec Exec attempts to execute the provided messages using authorizations granted to the grantee. Each message should have only one signer corresponding to the granter of the authorization. | | | | ---------- | ------------------------------- | | Type URL | `/cosmos.authz.v1beta1.MsgExec` | | Handler | `cosmos.authz.v1beta1.Msg/Exec` | | Signer | `grantee` | | Amino name | `cosmos-sdk/MsgExec` | | Field | Type | Description | | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `grantee` | `string` | Encoded as cosmos.AddressString. | | `msgs` | `Any`\[] | Execute Msg. The x/authz will try to find a grant matching (msg.signers\[0], grantee, MsgTypeURL(msg)) triple and validate it. Accepts any message implementing cosmos.base.v1beta1.Msg. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.authz.v1beta1.MsgExec", "grantee": "", "msgs": [ { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" } ] } ``` Response `MsgExecResponse`: | Field | Type | Description | | --------- | ---------- | ----------- | | `results` | `bytes`\[] | | ### Grant Grant grants the provided authorization to the grantee on the granter's account with the provided expiration time. If there is already a grant for the given (granter, grantee, Authorization) triple, then the grant will be overwritten. | | | | ---------- | -------------------------------- | | Type URL | `/cosmos.authz.v1beta1.MsgGrant` | | Handler | `cosmos.authz.v1beta1.Msg/Grant` | | Signer | `granter` | | Amino name | `cosmos-sdk/MsgGrant` | | Field | Type | Description | | --------- | -------------------------------------- | -------------------------------- | | `granter` | `string` | Encoded as cosmos.AddressString. | | `grantee` | `string` | Encoded as cosmos.AddressString. | | `grant` | [`Grant`](#cosmos-authz-v1beta1-grant) | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.authz.v1beta1.MsgGrant", "granter": "", "grantee": "", "grant": { "authorization": { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" }, "expiration": "" } } ``` Response `MsgGrantResponse`: This message has no fields. ### Revoke Revoke revokes any authorization corresponding to the provided method name on the granter's account that has been granted to the grantee. | | | | ---------- | --------------------------------- | | Type URL | `/cosmos.authz.v1beta1.MsgRevoke` | | Handler | `cosmos.authz.v1beta1.Msg/Revoke` | | Signer | `granter` | | Amino name | `cosmos-sdk/MsgRevoke` | | Field | Type | Description | | -------------- | -------- | -------------------------------- | | `granter` | `string` | Encoded as cosmos.AddressString. | | `grantee` | `string` | Encoded as cosmos.AddressString. | | `msg_type_url` | `string` | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.authz.v1beta1.MsgRevoke", "granter": "", "grantee": "", "msg_type_url": "" } ``` Response `MsgRevokeResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.authz.v1beta1.Grant Grant gives permissions to execute the provide method with expiration time. | Field | Type | Description | | --------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `authorization` | `Any` | One of: `/cosmos.authz.v1beta1.GenericAuthorization`, `/cosmos.bank.v1beta1.SendAuthorization`, `/cosmos.staking.v1beta1.StakeAuthorization`. | | `expiration` | `Timestamp` | time when the grant will expire and will be pruned. If null, then the grant doesn't have a time expiration (other conditions in `authorization` may apply to invalidate the grant) | ### cosmos.authz.v1beta1.GrantAuthorization GrantAuthorization extends a grant with both the addresses of the grantee and granter. It is used in genesis.proto and query.proto | Field | Type | Description | | --------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `granter` | `string` | Encoded as cosmos.AddressString. | | `grantee` | `string` | Encoded as cosmos.AddressString. | | `authorization` | `Any` | One of: `/cosmos.authz.v1beta1.GenericAuthorization`, `/cosmos.bank.v1beta1.SendAuthorization`, `/cosmos.staking.v1beta1.StakeAuthorization`. | | `expiration` | `Timestamp` | | ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | # Autocli Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/autocli gRPC queries and transaction messages defined by the autocli module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### AppOptions AppOptions returns the autocli options for all of the modules in an app. | | | | ---- | ------------------------------------ | | gRPC | `cosmos.autocli.v1.Query/AppOptions` | Request `AppOptionsRequest`: This message has no fields. Response `AppOptionsResponse`: | Field | Type | Description | | ---------------- | ---------------------------- | ------------------------------------------------------------------ | | `module_options` | `map` | module\_options is a map of module name to autocli module options. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.autocli.v1.Query/AppOptions ``` # Bank Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/bank gRPC queries and transaction messages defined by the bank module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### AllBalances AllBalances queries the balance of all coins for a single account. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | --------------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/AllBalances` | | REST | `GET /cosmos/bank/v1beta1/balances/{address}` | Request `QueryAllBalancesRequest`: | Field | Type | Description | | --------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `address` | `string` | address is the address to query balances for. Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | | `resolve_denom` | `bool` | resolve\_denom is the flag to resolve the denom into a human-readable form from the metadata. | Response `QueryAllBalancesResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `balances` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | balances is the balances of all the coins. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address":"","pagination":{"limit":"1"},"resolve_denom":false}' \ localhost:9090 cosmos.bank.v1beta1.Query/AllBalances ``` ### Balance Balance queries the balance of a single coin for a single account. | | | | ---- | ------------------------------------------------------ | | gRPC | `cosmos.bank.v1beta1.Query/Balance` | | REST | `GET /cosmos/bank/v1beta1/balances/{address}/by_denom` | Request `QueryBalanceRequest`: | Field | Type | Description | | --------- | -------- | ------------------------------------------------------------------------------ | | `address` | `string` | address is the address to query balances for. Encoded as cosmos.AddressString. | | `denom` | `string` | denom is the coin denom to query balances for. | Response `QueryBalanceResponse`: | Field | Type | Description | | --------- | ----------------------------------- | ----------------------------------- | | `balance` | [`Coin`](#cosmos-base-v1beta1-coin) | balance is the balance of the coin. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address":"","denom":""}' \ localhost:9090 cosmos.bank.v1beta1.Query/Balance ``` ### DenomMetadata DenomMetadata queries the client metadata of a given coin denomination. | | | | ---- | -------------------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/DenomMetadata` | | REST | `GET /cosmos/bank/v1beta1/denoms_metadata/{denom}` | Request `QueryDenomMetadataRequest`: | Field | Type | Description | | ------- | -------- | -------------------------------------------------- | | `denom` | `string` | denom is the coin denom to query the metadata for. | Response `QueryDenomMetadataResponse`: | Field | Type | Description | | ---------- | ------------------------------------------- | ----------------------------------------------------------------------------------- | | `metadata` | [`Metadata`](#cosmos-bank-v1beta1-metadata) | metadata describes and provides all the client information for the requested token. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"denom":""}' \ localhost:9090 cosmos.bank.v1beta1.Query/DenomMetadata ``` ### DenomMetadataByQueryString DenomMetadataByQueryString queries the client metadata of a given coin denomination. | | | | ---- | ---------------------------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/DenomMetadataByQueryString` | | REST | `GET /cosmos/bank/v1beta1/denoms_metadata_by_query_string` | Request `QueryDenomMetadataByQueryStringRequest`: | Field | Type | Description | | ------- | -------- | -------------------------------------------------- | | `denom` | `string` | denom is the coin denom to query the metadata for. | Response `QueryDenomMetadataByQueryStringResponse`: | Field | Type | Description | | ---------- | ------------------------------------------- | ----------------------------------------------------------------------------------- | | `metadata` | [`Metadata`](#cosmos-bank-v1beta1-metadata) | metadata describes and provides all the client information for the requested token. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"denom":""}' \ localhost:9090 cosmos.bank.v1beta1.Query/DenomMetadataByQueryString ``` ### DenomOwners DenomOwners queries for all account addresses that own a particular token denomination. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ----------------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/DenomOwners` | | REST | `GET /cosmos/bank/v1beta1/denom_owners/{denom}` | Request `QueryDenomOwnersRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | --------------------------------------------------------------------- | | `denom` | `string` | denom defines the coin denomination to query all account holders for. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDenomOwnersResponse`: | Field | Type | Description | | -------------- | --------------------------------------------------------- | -------------------------------------------------- | | `denom_owners` | [`DenomOwner`](#cosmos-bank-v1beta1-denomowner)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"denom":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.bank.v1beta1.Query/DenomOwners ``` ### DenomOwnersByQuery DenomOwnersByQuery queries for all account addresses that own a particular token denomination. | | | | ---- | ------------------------------------------------ | | gRPC | `cosmos.bank.v1beta1.Query/DenomOwnersByQuery` | | REST | `GET /cosmos/bank/v1beta1/denom_owners_by_query` | Request `QueryDenomOwnersByQueryRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | --------------------------------------------------------------------- | | `denom` | `string` | denom defines the coin denomination to query all account holders for. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDenomOwnersByQueryResponse`: | Field | Type | Description | | -------------- | --------------------------------------------------------- | -------------------------------------------------- | | `denom_owners` | [`DenomOwner`](#cosmos-bank-v1beta1-denomowner)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"denom":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.bank.v1beta1.Query/DenomOwnersByQuery ``` ### DenomsMetadata DenomsMetadata queries the client metadata for all registered coin denominations. | | | | ---- | ------------------------------------------ | | gRPC | `cosmos.bank.v1beta1.Query/DenomsMetadata` | | REST | `GET /cosmos/bank/v1beta1/denoms_metadata` | Request `QueryDenomsMetadataRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ---------------------------------------------------------- | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDenomsMetadataResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ----------------------------------------------------------------------- | | `metadatas` | [`Metadata`](#cosmos-bank-v1beta1-metadata)\[] | metadata provides the client information for all the registered tokens. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"pagination":{"limit":"1"}}' \ localhost:9090 cosmos.bank.v1beta1.Query/DenomsMetadata ``` ### Params Params queries the parameters of x/bank module. | | | | ---- | ---------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/Params` | | REST | `GET /cosmos/bank/v1beta1/params` | Request `QueryParamsRequest`: This message has no fields. Response `QueryParamsResponse`: | Field | Type | Description | | -------- | --------------------------------------- | -------------------------------------------------- | | `params` | [`Params`](#cosmos-bank-v1beta1-params) | params provides the parameters of the bank module. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.bank.v1beta1.Query/Params ``` ### SendEnabled SendEnabled queries for SendEnabled entries. This query only returns denominations that have specific SendEnabled settings. Any denomination that does not have a specific setting will use the default params.default\_send\_enabled, and will not be returned by this query. | | | | ---- | --------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/SendEnabled` | | REST | `GET /cosmos/bank/v1beta1/send_enabled` | Request `QuerySendEnabledRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `denoms` | `string`\[] | denoms is the specific denoms you want look up. Leave empty to get all entries. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. This field is only read if the denoms field is empty. | Response `QuerySendEnabledResponse`: | Field | Type | Description | | -------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `send_enabled` | [`SendEnabled`](#cosmos-bank-v1beta1-sendenabled)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. This field is only populated if the denoms field in the request is empty. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"denoms":[""],"pagination":{"limit":"1"}}' \ localhost:9090 cosmos.bank.v1beta1.Query/SendEnabled ``` ### SpendableBalanceByDenom SpendableBalanceByDenom queries the spendable balance of a single denom for a single account. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ---------------------------------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/SpendableBalanceByDenom` | | REST | `GET /cosmos/bank/v1beta1/spendable_balances/{address}/by_denom` | Request `QuerySpendableBalanceByDenomRequest`: | Field | Type | Description | | --------- | -------- | ------------------------------------------------------------------------------ | | `address` | `string` | address is the address to query balances for. Encoded as cosmos.AddressString. | | `denom` | `string` | denom is the coin denom to query balances for. | Response `QuerySpendableBalanceByDenomResponse`: | Field | Type | Description | | --------- | ----------------------------------- | ----------------------------------- | | `balance` | [`Coin`](#cosmos-base-v1beta1-coin) | balance is the balance of the coin. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address":"","denom":""}' \ localhost:9090 cosmos.bank.v1beta1.Query/SpendableBalanceByDenom ``` ### SpendableBalances SpendableBalances queries the spendable balance of all coins for a single account. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ------------------------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/SpendableBalances` | | REST | `GET /cosmos/bank/v1beta1/spendable_balances/{address}` | Request `QuerySpendableBalancesRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `address` | `string` | address is the address to query spendable balances for. Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QuerySpendableBalancesResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ---------------------------------------------------- | | `balances` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | balances is the spendable balances of all the coins. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"address":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.bank.v1beta1.Query/SpendableBalances ``` ### SupplyOf SupplyOf queries the supply of a single coin. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ------------------------------------------ | | gRPC | `cosmos.bank.v1beta1.Query/SupplyOf` | | REST | `GET /cosmos/bank/v1beta1/supply/by_denom` | Request `QuerySupplyOfRequest`: | Field | Type | Description | | ------- | -------- | ---------------------------------------------- | | `denom` | `string` | denom is the coin denom to query balances for. | Response `QuerySupplyOfResponse`: | Field | Type | Description | | -------- | ----------------------------------- | --------------------------------- | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin) | amount is the supply of the coin. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"denom":""}' \ localhost:9090 cosmos.bank.v1beta1.Query/SupplyOf ``` ### TotalSupply TotalSupply queries the total supply of all coins. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | --------------------------------------- | | gRPC | `cosmos.bank.v1beta1.Query/TotalSupply` | | REST | `GET /cosmos/bank/v1beta1/supply` | Request `QueryTotalSupplyRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ---------------------------------------------------------- | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryTotalSupplyResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `supply` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | supply is the supply of the coins | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"pagination":{"limit":"1"}}' \ localhost:9090 cosmos.bank.v1beta1.Query/TotalSupply ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### MultiSend MultiSend defines a method for sending coins from some accounts to other accounts. | | | | ---------- | ----------------------------------- | | Type URL | `/cosmos.bank.v1beta1.MsgMultiSend` | | Handler | `cosmos.bank.v1beta1.Msg/MultiSend` | | Signer | `inputs` | | Amino name | `cosmos-sdk/MsgMultiSend` | | Field | Type | Description | | --------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | | `inputs` | [`Input`](#cosmos-bank-v1beta1-input)\[] | Inputs, despite being `repeated`, only allows one sender input. This is checked in MsgMultiSend's ValidateBasic. | | `outputs` | [`Output`](#cosmos-bank-v1beta1-output)\[] | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.bank.v1beta1.MsgMultiSend", "inputs": [ { "address": "", "coins": [ { "denom": "", "amount": "" } ] } ], "outputs": [ { "address": "", "coins": [ { "denom": "", "amount": "" } ] } ] } ``` Response `MsgMultiSendResponse`: This message has no fields. ### Send Send defines a method for sending coins from one account to another account. | | | | ---------- | ------------------------------ | | Type URL | `/cosmos.bank.v1beta1.MsgSend` | | Handler | `cosmos.bank.v1beta1.Msg/Send` | | Signer | `from_address` | | Amino name | `cosmos-sdk/MsgSend` | | Field | Type | Description | | -------------- | -------------------------------------- | -------------------------------- | | `from_address` | `string` | Encoded as cosmos.AddressString. | | `to_address` | `string` | Encoded as cosmos.AddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "", "to_address": "", "amount": [ { "denom": "", "amount": "" } ] } ``` Response `MsgSendResponse`: This message has no fields. ### SetSendEnabled SetSendEnabled is a governance operation for setting the SendEnabled flag on any number of Denoms. Only the entries to add or update should be included. Entries that already exist in the store, but that aren't included in this message, will be left unchanged. | | | | ---------- | ---------------------------------------- | | Type URL | `/cosmos.bank.v1beta1.MsgSetSendEnabled` | | Handler | `cosmos.bank.v1beta1.Msg/SetSendEnabled` | | Signer | `authority` | | Amino name | `cosmos-sdk/MsgSetSendEnabled` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `authority` | `string` | authority is the address that controls the module. Encoded as cosmos.AddressString. | | `send_enabled` | [`SendEnabled`](#cosmos-bank-v1beta1-sendenabled)\[] | send\_enabled is the list of entries to add or update. | | `use_default_for` | `string`\[] | use\_default\_for is a list of denoms that should use the params.default\_send\_enabled value. Denoms listed here will have their SendEnabled entries deleted. If a denom is included that doesn't have a SendEnabled entry, it will be ignored. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.bank.v1beta1.MsgSetSendEnabled", "authority": "", "send_enabled": [ { "denom": "", "enabled": false } ], "use_default_for": [ "" ] } ``` Response `MsgSetSendEnabledResponse`: This message has no fields. ### UpdateParams UpdateParams defines a governance operation for updating the x/bank module parameters. The authority is defined in the keeper. | | | | ---------- | -------------------------------------- | | Type URL | `/cosmos.bank.v1beta1.MsgUpdateParams` | | Handler | `cosmos.bank.v1beta1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/x/bank/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `params` | [`Params`](#cosmos-bank-v1beta1-params) | params defines the x/bank parameters to update. NOTE: All parameters must be supplied. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.bank.v1beta1.MsgUpdateParams", "authority": "", "params": { "default_send_enabled": false } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.bank.v1beta1.DenomOwner DenomOwner defines structure representing an account that owns or holds a particular denominated token. It contains the account address and account balance of the denominated token. | Field | Type | Description | | --------- | ----------------------------------- | ------------------------------------------------------------------------------------------------- | | `address` | `string` | address defines the address that owns a particular denomination. Encoded as cosmos.AddressString. | | `balance` | [`Coin`](#cosmos-base-v1beta1-coin) | balance is the balance of the denominated coin for an account. | ### cosmos.bank.v1beta1.DenomUnit DenomUnit represents a struct that describes a given denomination unit of the basic token. | Field | Type | Description | | ---------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `denom` | `string` | denom represents the string name of the given denom unit (e.g uatom). | | `exponent` | `uint32` | exponent represents power of 10 exponent that one must raise the base\_denom to in order to equal the given DenomUnit's denom 1 denom = 10^exponent base\_denom (e.g. with a base\_denom of uatom, one can create a DenomUnit of 'atom' with exponent = 6, thus: 1 atom = 10^6 uatom). | | `aliases` | `string`\[] | aliases is a list of string aliases for the given denom | ### cosmos.bank.v1beta1.Input Input models transaction input. | Field | Type | Description | | --------- | -------------------------------------- | -------------------------------- | | `address` | `string` | Encoded as cosmos.AddressString. | | `coins` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | ### cosmos.bank.v1beta1.Metadata Metadata represents a struct that describes a basic token. | Field | Type | Description | | ------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `description` | `string` | | | `denom_units` | [`DenomUnit`](#cosmos-bank-v1beta1-denomunit)\[] | denom\_units represents the list of DenomUnit's for a given coin | | `base` | `string` | base represents the base denom (should be the DenomUnit with exponent = 0). | | `display` | `string` | display indicates the suggested denom that should be displayed in clients. | | `name` | `string` | name defines the name of the token (eg: Cosmos Atom) | | `symbol` | `string` | symbol is the token symbol usually shown on exchanges (eg: ATOM). This can be the same as the display. | | `uri` | `string` | URI to a document (on or off-chain) that contains additional information. Optional. | | `uri_hash` | `string` | URIHash is a sha256 hash of a document pointed by URI. It's used to verify that the document didn't change. Optional. | ### cosmos.bank.v1beta1.Output Output models transaction outputs. | Field | Type | Description | | --------- | -------------------------------------- | -------------------------------- | | `address` | `string` | Encoded as cosmos.AddressString. | | `coins` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | ### cosmos.bank.v1beta1.Params Params defines the parameters for the bank module. | Field | Type | Description | | ---------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `send_enabled` | [`SendEnabled`](#cosmos-bank-v1beta1-sendenabled)\[] | Deprecated: Use of SendEnabled in params is deprecated. For genesis, use the newly added send\_enabled field in the genesis object. Storage, lookup, and manipulation of this information is now in the keeper. As of cosmos-sdk 0.47, this only exists for backwards compatibility of genesis files. Deprecated. | | `default_send_enabled` | `bool` | | ### cosmos.bank.v1beta1.SendEnabled SendEnabled maps coin denom to a send\_enabled status (whether a denom is sendable). | Field | Type | Description | | --------- | -------- | ----------- | | `denom` | `string` | | | `enabled` | `bool` | | ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.base.v1beta1.Coin Coin defines a token with a denomination and an amount. NOTE: The amount field is an Int which implements the custom method signatures required by gogoproto. | Field | Type | Description | | -------- | -------- | ---------------------- | | `denom` | `string` | | | `amount` | `string` | Encoded as cosmos.Int. | # Base Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/base gRPC queries and transaction messages defined by the base module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ## cosmos.base.node.v1beta1.Service Service defines the gRPC querier service for node related queries. ### Config Config queries for the operator configuration. | | | | ---- | ----------------------------------------- | | gRPC | `cosmos.base.node.v1beta1.Service/Config` | | REST | `GET /cosmos/base/node/v1beta1/config` | Request `ConfigRequest`: This message has no fields. Response `ConfigResponse`: | Field | Type | Description | | --------------------- | -------- | ----------- | | `minimum_gas_price` | `string` | | | `pruning_keep_recent` | `string` | | | `pruning_interval` | `string` | | | `halt_height` | `uint64` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.node.v1beta1.Service/Config ``` ### Status Status queries for the node status. | | | | ---- | ----------------------------------------- | | gRPC | `cosmos.base.node.v1beta1.Service/Status` | | REST | `GET /cosmos/base/node/v1beta1/status` | Request `StatusRequest`: This message has no fields. Response `StatusResponse`: | Field | Type | Description | | ----------------------- | ----------- | ----------- | | `earliest_store_height` | `uint64` | | | `height` | `uint64` | | | `timestamp` | `Timestamp` | | | `app_hash` | `bytes` | | | `validator_hash` | `bytes` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.node.v1beta1.Service/Status ``` ## cosmos.base.reflection.v1beta1.ReflectionService ReflectionService defines a service for interface reflection. ### ListAllInterfaces ListAllInterfaces lists all the interfaces registered in the interface registry. | | | | ---- | -------------------------------------------------------------------- | | gRPC | `cosmos.base.reflection.v1beta1.ReflectionService/ListAllInterfaces` | | REST | `GET /cosmos/base/reflection/v1beta1/interfaces` | Request `ListAllInterfacesRequest`: This message has no fields. Response `ListAllInterfacesResponse`: | Field | Type | Description | | ----------------- | ----------- | -------------------------------------------------------------- | | `interface_names` | `string`\[] | interface\_names is an array of all the registered interfaces. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.reflection.v1beta1.ReflectionService/ListAllInterfaces ``` ### ListImplementations ListImplementations list all the concrete types that implement a given interface. | | | | ---- | -------------------------------------------------------------------------------- | | gRPC | `cosmos.base.reflection.v1beta1.ReflectionService/ListImplementations` | | REST | `GET /cosmos/base/reflection/v1beta1/interfaces/{interfaceName}/implementations` | Request `ListImplementationsRequest`: | Field | Type | Description | | ---------------- | -------- | ----------------------------------------------------------------------- | | `interface_name` | `string` | interface\_name defines the interface to query the implementations for. | Response `ListImplementationsResponse`: | Field | Type | Description | | ------------------------------ | ----------- | ----------- | | `implementation_message_names` | `string`\[] | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"interface_name":""}' \ localhost:9090 cosmos.base.reflection.v1beta1.ReflectionService/ListImplementations ``` ## cosmos.base.reflection.v2alpha1.ReflectionService ReflectionService defines a service for application reflection. ### GetAuthnDescriptor GetAuthnDescriptor returns information on how to authenticate transactions in the application NOTE: this RPC is still experimental and might be subject to breaking changes or removal in future releases of the cosmos-sdk. | | | | ---- | ---------------------------------------------------------------------- | | gRPC | `cosmos.base.reflection.v2alpha1.ReflectionService/GetAuthnDescriptor` | | REST | `GET /cosmos/base/reflection/v1beta1/app_descriptor/authn` | Request `GetAuthnDescriptorRequest`: This message has no fields. Response `GetAuthnDescriptorResponse`: | Field | Type | Description | | ------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `authn` | [`AuthnDescriptor`](#cosmos-base-reflection-v2alpha1-authndescriptor) | authn describes how to authenticate to the application when sending transactions | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.reflection.v2alpha1.ReflectionService/GetAuthnDescriptor ``` ### GetChainDescriptor GetChainDescriptor returns the description of the chain | | | | ---- | ---------------------------------------------------------------------- | | gRPC | `cosmos.base.reflection.v2alpha1.ReflectionService/GetChainDescriptor` | | REST | `GET /cosmos/base/reflection/v1beta1/app_descriptor/chain` | Request `GetChainDescriptorRequest`: This message has no fields. Response `GetChainDescriptorResponse`: | Field | Type | Description | | ------- | --------------------------------------------------------------------- | --------------------------------------------- | | `chain` | [`ChainDescriptor`](#cosmos-base-reflection-v2alpha1-chaindescriptor) | chain describes application chain information | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.reflection.v2alpha1.ReflectionService/GetChainDescriptor ``` ### GetCodecDescriptor GetCodecDescriptor returns the descriptor of the codec of the application | | | | ---- | ---------------------------------------------------------------------- | | gRPC | `cosmos.base.reflection.v2alpha1.ReflectionService/GetCodecDescriptor` | | REST | `GET /cosmos/base/reflection/v1beta1/app_descriptor/codec` | Request `GetCodecDescriptorRequest`: This message has no fields. Response `GetCodecDescriptorResponse`: | Field | Type | Description | | ------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | `codec` | [`CodecDescriptor`](#cosmos-base-reflection-v2alpha1-codecdescriptor) | codec describes the application codec such as registered interfaces and implementations | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.reflection.v2alpha1.ReflectionService/GetCodecDescriptor ``` ### GetConfigurationDescriptor GetConfigurationDescriptor returns the descriptor for the sdk.Config of the application | | | | ---- | ------------------------------------------------------------------------------ | | gRPC | `cosmos.base.reflection.v2alpha1.ReflectionService/GetConfigurationDescriptor` | | REST | `GET /cosmos/base/reflection/v1beta1/app_descriptor/configuration` | Request `GetConfigurationDescriptorRequest`: This message has no fields. Response `GetConfigurationDescriptorResponse`: | Field | Type | Description | | -------- | ------------------------------------------------------------------------------------- | --------------------------------------------- | | `config` | [`ConfigurationDescriptor`](#cosmos-base-reflection-v2alpha1-configurationdescriptor) | config describes the application's sdk.Config | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.reflection.v2alpha1.ReflectionService/GetConfigurationDescriptor ``` ### GetQueryServicesDescriptor GetQueryServicesDescriptor returns the available gRPC queryable services of the application | | | | ---- | ------------------------------------------------------------------------------ | | gRPC | `cosmos.base.reflection.v2alpha1.ReflectionService/GetQueryServicesDescriptor` | | REST | `GET /cosmos/base/reflection/v1beta1/app_descriptor/query_services` | Request `GetQueryServicesDescriptorRequest`: This message has no fields. Response `GetQueryServicesDescriptorResponse`: | Field | Type | Description | | --------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | `queries` | [`QueryServicesDescriptor`](#cosmos-base-reflection-v2alpha1-queryservicesdescriptor) | queries provides information on the available queryable services | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.reflection.v2alpha1.ReflectionService/GetQueryServicesDescriptor ``` ### GetTxDescriptor GetTxDescriptor returns information on the used transaction object and available msgs that can be used | | | | ---- | ------------------------------------------------------------------- | | gRPC | `cosmos.base.reflection.v2alpha1.ReflectionService/GetTxDescriptor` | | REST | `GET /cosmos/base/reflection/v1beta1/app_descriptor/tx_descriptor` | Request `GetTxDescriptorRequest`: This message has no fields. Response `GetTxDescriptorResponse`: | Field | Type | Description | | ----- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `tx` | [`TxDescriptor`](#cosmos-base-reflection-v2alpha1-txdescriptor) | tx provides information on msgs that can be forwarded to the application alongside the accepted transaction protobuf type | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.reflection.v2alpha1.ReflectionService/GetTxDescriptor ``` ## cosmos.base.tendermint.v1beta1.Service Service defines the gRPC querier service for tendermint queries. ### ABCIQuery ABCIQuery defines a query handler that supports ABCI queries directly to the application, bypassing Tendermint completely. The ABCI query must contain a valid and supported path, including app, custom, p2p, and store. | | | | ---- | -------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/ABCIQuery` | | REST | `GET /cosmos/base/tendermint/v1beta1/abci_query` | Request `ABCIQueryRequest`: | Field | Type | Description | | -------- | -------- | ----------- | | `data` | `bytes` | | | `path` | `string` | | | `height` | `int64` | | | `prove` | `bool` | | Response `ABCIQueryResponse`: | Field | Type | Description | | ----------- | ------------------------------------------------------ | ----------- | | `code` | `uint32` | | | `log` | `string` | | | `info` | `string` | | | `index` | `int64` | | | `key` | `bytes` | | | `value` | `bytes` | | | `proof_ops` | [`ProofOps`](#cosmos-base-tendermint-v1beta1-proofops) | | | `height` | `int64` | | | `codespace` | `string` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"data":"","path":"","height":"1","prove":false}' \ localhost:9090 cosmos.base.tendermint.v1beta1.Service/ABCIQuery ``` ### GetBlockByHeight GetBlockByHeight queries block for given height. | | | | ---- | --------------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetBlockByHeight` | | REST | `GET /cosmos/base/tendermint/v1beta1/blocks/{height}` | Request `GetBlockByHeightRequest`: | Field | Type | Description | | -------- | ------- | ----------- | | `height` | `int64` | | Response `GetBlockByHeightResponse`: | Field | Type | Description | | ----------- | ------------------------------------------------ | ------------------------------------------ | | `block_id` | `BlockID` | | | `block` | `Block` | Deprecated: please use `sdk_block` instead | | `sdk_block` | [`Block`](#cosmos-base-tendermint-v1beta1-block) | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"height":"1"}' \ localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetBlockByHeight ``` ### GetBlockResults GetBlockResults queries block results for given height. | | | | ---- | ------------------------------------------------------------ | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetBlockResults` | | REST | `GET /cosmos/base/tendermint/v1beta1/block_results/{height}` | Request `GetBlockResultsRequest`: | Field | Type | Description | | -------- | ------- | ----------- | | `height` | `int64` | | Response `GetBlockResultsResponse`: | Field | Type | Description | | ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `height` | `int64` | height is the block height. | | `txs_results` | `ExecTxResult`\[] | txs\_results contains the results of each transaction execution. | | `finalize_block_events` | `Event`\[] | finalize\_block\_events contains consensus-level events emitted during block finalization, including slashing, jailing, and validator set updates. | | `validator_updates` | `ValidatorUpdate`\[] | validator\_updates contains the validator updates for this block. | | `consensus_param_updates` | `ConsensusParams` | consensus\_param\_updates contains any consensus parameter updates for this block. | | `app_hash` | `bytes` | app\_hash is the app hash after processing this block. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"height":"1"}' \ localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetBlockResults ``` ### GetLatestBlock GetLatestBlock returns the latest block. | | | | ---- | ------------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetLatestBlock` | | REST | `GET /cosmos/base/tendermint/v1beta1/blocks/latest` | Request `GetLatestBlockRequest`: This message has no fields. Response `GetLatestBlockResponse`: | Field | Type | Description | | ----------- | ------------------------------------------------ | ------------------------------------------ | | `block_id` | `BlockID` | | | `block` | `Block` | Deprecated: please use `sdk_block` instead | | `sdk_block` | [`Block`](#cosmos-base-tendermint-v1beta1-block) | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetLatestBlock ``` ### GetLatestBlockResults GetLatestBlockResults returns the block results for the latest block. Block results contain finalize\_block\_events which include consensus-level events like slashing, jailing, and validator set updates. | | | | ---- | -------------------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetLatestBlockResults` | | REST | `GET /cosmos/base/tendermint/v1beta1/block_results/latest` | Request `GetLatestBlockResultsRequest`: This message has no fields. Response `GetLatestBlockResultsResponse`: | Field | Type | Description | | ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `height` | `int64` | height is the block height. | | `txs_results` | `ExecTxResult`\[] | txs\_results contains the results of each transaction execution. | | `finalize_block_events` | `Event`\[] | finalize\_block\_events contains consensus-level events emitted during block finalization, including slashing, jailing, and validator set updates. | | `validator_updates` | `ValidatorUpdate`\[] | validator\_updates contains the validator updates for this block. | | `consensus_param_updates` | `ConsensusParams` | consensus\_param\_updates contains any consensus parameter updates for this block. | | `app_hash` | `bytes` | app\_hash is the app hash after processing this block. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetLatestBlockResults ``` ### GetLatestValidatorSet GetLatestValidatorSet queries latest validator-set. | | | | ---- | -------------------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetLatestValidatorSet` | | REST | `GET /cosmos/base/tendermint/v1beta1/validatorsets/latest` | Request `GetLatestValidatorSetRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------- | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an pagination for the request. | Response `GetLatestValidatorSetResponse`: | Field | Type | Description | | -------------- | ----------------------------------------------------------- | -------------------------------------------------- | | `block_height` | `int64` | | | `validators` | [`Validator`](#cosmos-base-tendermint-v1beta1-validator)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines an pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"pagination":{"limit":"1"}}' \ localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetLatestValidatorSet ``` ### GetNodeInfo GetNodeInfo queries the current node info. | | | | ---- | ---------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetNodeInfo` | | REST | `GET /cosmos/base/tendermint/v1beta1/node_info` | Request `GetNodeInfoRequest`: This message has no fields. Response `GetNodeInfoResponse`: | Field | Type | Description | | --------------------- | ------------------------------------------------------------ | ----------- | | `default_node_info` | `DefaultNodeInfo` | | | `application_version` | [`VersionInfo`](#cosmos-base-tendermint-v1beta1-versioninfo) | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetNodeInfo ``` ### GetSyncing GetSyncing queries node syncing. | | | | ---- | --------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetSyncing` | | REST | `GET /cosmos/base/tendermint/v1beta1/syncing` | Request `GetSyncingRequest`: This message has no fields. Response `GetSyncingResponse`: | Field | Type | Description | | ----------------------- | ------- | ---------------------------------------------------------------------------- | | `syncing` | `bool` | | | `earliest_block_height` | `int64` | earliest\_block\_height is the earliest block height available on this node. | | `latest_block_height` | `int64` | latest\_block\_height is the latest block height available on this node. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetSyncing ``` ### GetValidatorSetByHeight GetValidatorSetByHeight queries validator-set at a given height. | | | | ---- | ---------------------------------------------------------------- | | gRPC | `cosmos.base.tendermint.v1beta1.Service/GetValidatorSetByHeight` | | REST | `GET /cosmos/base/tendermint/v1beta1/validatorsets/{height}` | Request `GetValidatorSetByHeightRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------- | | `height` | `int64` | | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an pagination for the request. | Response `GetValidatorSetByHeightResponse`: | Field | Type | Description | | -------------- | ----------------------------------------------------------- | -------------------------------------------------- | | `block_height` | `int64` | | | `validators` | [`Validator`](#cosmos-base-tendermint-v1beta1-validator)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines an pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"height":"1","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.base.tendermint.v1beta1.Service/GetValidatorSetByHeight ``` ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.base.reflection.v2alpha1.AuthnDescriptor AuthnDescriptor provides information on how to sign transactions without relying on the online RPCs GetTxMetadata and CombineUnsignedTxAndSignatures | Field | Type | Description | | ------------ | ------------------------------------------------------------------------------------ | ----------------------------------------------------- | | `sign_modes` | [`SigningModeDescriptor`](#cosmos-base-reflection-v2alpha1-signingmodedescriptor)\[] | sign\_modes defines the supported signature algorithm | ### cosmos.base.reflection.v2alpha1.ChainDescriptor ChainDescriptor describes chain information of the application | Field | Type | Description | | ----- | -------- | ------------------ | | `id` | `string` | id is the chain id | ### cosmos.base.reflection.v2alpha1.CodecDescriptor CodecDescriptor describes the registered interfaces and provides metadata information on the types | Field | Type | Description | | ------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------- | | `interfaces` | [`InterfaceDescriptor`](#cosmos-base-reflection-v2alpha1-interfacedescriptor)\[] | interfaces is a list of the registered interfaces descriptors | ### cosmos.base.reflection.v2alpha1.ConfigurationDescriptor ConfigurationDescriptor contains metadata information on the sdk.Config | Field | Type | Description | | ------------------------------- | -------- | -------------------------------------------------------------- | | `bech32_account_address_prefix` | `string` | bech32\_account\_address\_prefix is the account address prefix | ### cosmos.base.reflection.v2alpha1.InterfaceAcceptingMessageDescriptor InterfaceAcceptingMessageDescriptor describes a protobuf message which contains an interface represented as a google.protobuf.Any | Field | Type | Description | | ------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fullname` | `string` | fullname is the protobuf fullname of the type containing the interface | | `field_descriptor_names` | `string`\[] | field\_descriptor\_names is a list of the protobuf name (not fullname) of the field which contains the interface as google.protobuf.Any (the interface is the same, but it can be in multiple fields of the same proto message) | ### cosmos.base.reflection.v2alpha1.InterfaceDescriptor InterfaceDescriptor describes the implementation of an interface | Field | Type | Description | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `fullname` | `string` | fullname is the name of the interface | | `interface_accepting_messages` | [`InterfaceAcceptingMessageDescriptor`](#cosmos-base-reflection-v2alpha1-interfaceacceptingmessagedescriptor)\[] | interface\_accepting\_messages contains information regarding the proto messages which contain the interface as google.protobuf.Any field | | `interface_implementers` | [`InterfaceImplementerDescriptor`](#cosmos-base-reflection-v2alpha1-interfaceimplementerdescriptor)\[] | interface\_implementers is a list of the descriptors of the interface implementers | ### cosmos.base.reflection.v2alpha1.InterfaceImplementerDescriptor InterfaceImplementerDescriptor describes an interface implementer | Field | Type | Description | | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fullname` | `string` | fullname is the protobuf queryable name of the interface implementer | | `type_url` | `string` | type\_url defines the type URL used when marshalling the type as any this is required so we can provide type safe google.protobuf.Any marshalling and unmarshalling, making sure that we don't accept just 'any' type in our interface fields | ### cosmos.base.reflection.v2alpha1.MsgDescriptor MsgDescriptor describes a cosmos-sdk message that can be delivered with a transaction | Field | Type | Description | | -------------- | -------- | ------------------------------------------------- | | `msg_type_url` | `string` | msg\_type\_url contains the TypeURL of a sdk.Msg. | ### cosmos.base.reflection.v2alpha1.QueryMethodDescriptor QueryMethodDescriptor describes a queryable method of a query service no other info is provided beside method name and tendermint queryable path because it would be redundant with the grpc reflection service | Field | Type | Description | | ----------------- | -------- | --------------------------------------------------------------------------------------------- | | `name` | `string` | name is the protobuf name (not fullname) of the method | | `full_query_path` | `string` | full\_query\_path is the path that can be used to query this method via tendermint abci.Query | ### cosmos.base.reflection.v2alpha1.QueryServiceDescriptor QueryServiceDescriptor describes a cosmos-sdk queryable service | Field | Type | Description | | ----------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | | `fullname` | `string` | fullname is the protobuf fullname of the service descriptor | | `is_module` | `bool` | is\_module describes if this service is actually exposed by an application's module | | `methods` | [`QueryMethodDescriptor`](#cosmos-base-reflection-v2alpha1-querymethoddescriptor)\[] | methods provides a list of query service methods | ### cosmos.base.reflection.v2alpha1.QueryServicesDescriptor QueryServicesDescriptor contains the list of cosmos-sdk queryable services | Field | Type | Description | | ---------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------- | | `query_services` | [`QueryServiceDescriptor`](#cosmos-base-reflection-v2alpha1-queryservicedescriptor)\[] | query\_services is a list of cosmos-sdk QueryServiceDescriptor | ### cosmos.base.reflection.v2alpha1.SigningModeDescriptor SigningModeDescriptor provides information on a signing flow of the application NOTE(fdymylja): here we could go as far as providing an entire flow on how to sign a message given a SigningModeDescriptor, but it's better to think about this another time | Field | Type | Description | | ------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | name defines the unique name of the signing mode | | `number` | `int32` | number is the unique int32 identifier for the sign\_mode enum | | `authn_info_provider_method_fullname` | `string` | authn\_info\_provider\_method\_fullname defines the fullname of the method to call to get the metadata required to authenticate using the provided sign\_modes | ### cosmos.base.reflection.v2alpha1.TxDescriptor TxDescriptor describes the accepted transaction type | Field | Type | Description | | ---------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fullname` | `string` | fullname is the protobuf fullname of the raw transaction type (for instance the tx.Tx type) it is not meant to support polymorphism of transaction types, it is supposed to be used by reflection clients to understand if they can handle a specific transaction type in an application. | | `msgs` | [`MsgDescriptor`](#cosmos-base-reflection-v2alpha1-msgdescriptor)\[] | msgs lists the accepted application messages (sdk.Msg) | ### cosmos.base.tendermint.v1beta1.Block Block is tendermint type Block, with the Header proposer address field converted to bech32 string. | Field | Type | Description | | ------------- | -------------------------------------------------- | ----------- | | `header` | [`Header`](#cosmos-base-tendermint-v1beta1-header) | | | `data` | `Data` | | | `evidence` | `EvidenceList` | | | `last_commit` | `Commit` | | ### cosmos.base.tendermint.v1beta1.Header Header defines the structure of a Tendermint block header. | Field | Type | Description | | ---------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `version` | `Consensus` | basic block info | | `chain_id` | `string` | | | `height` | `int64` | | | `time` | `Timestamp` | | | `last_block_id` | `BlockID` | prev block info | | `last_commit_hash` | `bytes` | hashes of block data | | `data_hash` | `bytes` | | | `validators_hash` | `bytes` | hashes from the app output from the prev block | | `next_validators_hash` | `bytes` | | | `consensus_hash` | `bytes` | | | `app_hash` | `bytes` | | | `last_results_hash` | `bytes` | | | `evidence_hash` | `bytes` | consensus info | | `proposer_address` | `string` | proposer\_address is the original block proposer address, formatted as a Bech32 string. In Tendermint, this type is `bytes`, but in the SDK, we convert it to a Bech32 string for better UX. | ### cosmos.base.tendermint.v1beta1.Module Module is the type for VersionInfo | Field | Type | Description | | --------- | -------- | -------------- | | `path` | `string` | module path | | `version` | `string` | module version | | `sum` | `string` | checksum | ### cosmos.base.tendermint.v1beta1.ProofOp ProofOp defines an operation used for calculating Merkle root. The data could be arbitrary format, providing necessary data for example neighbouring node hash. Note: This type is a duplicate of the ProofOp proto type defined in Tendermint. | Field | Type | Description | | ------ | -------- | ----------- | | `type` | `string` | | | `key` | `bytes` | | | `data` | `bytes` | | ### cosmos.base.tendermint.v1beta1.ProofOps ProofOps is Merkle proof defined by the list of ProofOps. Note: This type is a duplicate of the ProofOps proto type defined in Tendermint. | Field | Type | Description | | ----- | ------------------------------------------------------- | ----------- | | `ops` | [`ProofOp`](#cosmos-base-tendermint-v1beta1-proofop)\[] | | ### cosmos.base.tendermint.v1beta1.Validator Validator is the type for the validator-set. | Field | Type | Description | | ------------------- | -------- | -------------------------------- | | `address` | `string` | Encoded as cosmos.AddressString. | | `pub_key` | `Any` | | | `voting_power` | `int64` | | | `proposer_priority` | `int64` | | ### cosmos.base.tendermint.v1beta1.VersionInfo VersionInfo is the type for the GetNodeInfoResponse message. | Field | Type | Description | | -------------------- | ----------------------------------------------------- | ----------- | | `name` | `string` | | | `app_name` | `string` | | | `version` | `string` | | | `git_commit` | `string` | | | `build_tags` | `string` | | | `go_version` | `string` | | | `build_deps` | [`Module`](#cosmos-base-tendermint-v1beta1-module)\[] | | | `cosmos_sdk_version` | `string` | | # Consensus Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/consensus gRPC queries and transaction messages defined by the consensus module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### Params Params queries the parameters of x/consensus module. | | | | ---- | ---------------------------------- | | gRPC | `cosmos.consensus.v1.Query/Params` | | REST | `GET /cosmos/consensus/v1/params` | Request `QueryParamsRequest`: This message has no fields. Response `QueryParamsResponse`: | Field | Type | Description | | -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `params` | `ConsensusParams` | params are the tendermint consensus params stored in the consensus module. Please note that `params.version` is not populated in this response, it is tracked separately in the x/upgrade module. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.consensus.v1.Query/Params ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### UpdateParams UpdateParams defines a governance operation for updating the x/consensus module parameters. The authority is defined in the keeper. | | | | ---------- | ---------------------------------------- | | Type URL | `/cosmos.consensus.v1.MsgUpdateParams` | | Handler | `cosmos.consensus.v1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/x/consensus/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `block` | `BlockParams` | params defines the x/consensus parameters to update. VersionsParams is not included in this Msg because it is tracked separarately in x/upgrade. NOTE: All parameters must be supplied. | | `evidence` | `EvidenceParams` | | | `validator` | `ValidatorParams` | | | `abci` | `ABCIParams` | | | `auth` | `AuthorityParams` | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.consensus.v1.MsgUpdateParams", "authority": "", "block": { "max_bytes": "1", "max_gas": "1" }, "evidence": { "max_age_num_blocks": "1", "max_age_duration": "0s", "max_bytes": "1" }, "validator": { "pub_key_types": [ "" ] }, "abci": { "vote_extensions_enable_height": "1" }, "auth": { "authority": "" } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. # Counter Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/counter gRPC queries and transaction messages defined by the counter module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### GetCount GetCount queries the parameters of x/Counter module. | | | | ---- | ---------------------------------- | | gRPC | `cosmos.counter.v1.Query/GetCount` | Request `QueryGetCountRequest`: This message has no fields. Response `QueryGetCountResponse`: | Field | Type | Description | | ------------- | ------- | ----------- | | `total_count` | `int64` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.counter.v1.Query/GetCount ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### IncreaseCount IncreaseCount increments the counter by the specified amount. | | | | ---------- | --------------------------------------- | | Type URL | `/cosmos.counter.v1.MsgIncreaseCounter` | | Handler | `cosmos.counter.v1.Msg/IncreaseCount` | | Signer | `signer` | | Amino name | `cosmos-sdk/increase_counter` | | Field | Type | Description | | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `signer` | `string` | signer is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `count` | `int64` | count is the number of times to increment the counter. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.counter.v1.MsgIncreaseCounter", "signer": "", "count": "1" } ``` Response `MsgIncreaseCountResponse`: | Field | Type | Description | | ----------- | ------- | -------------------------------------------------------------- | | `new_count` | `int64` | new\_count is the number of times the counter was incremented. | # Distribution Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/distribution gRPC queries and transaction messages defined by the distribution module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### CommunityPool CommunityPool queries the community pool coins. | | | | ---- | ------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/CommunityPool` | | REST | `GET /cosmos/distribution/v1beta1/community_pool` | Request `QueryCommunityPoolRequest`: This message has no fields. Response `QueryCommunityPoolResponse`: | Field | Type | Description | | ------ | -------------------------------------------- | ------------------------------------ | | `pool` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | pool defines community pool's coins. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.distribution.v1beta1.Query/CommunityPool ``` ### DelegationRewards DelegationRewards queries the total rewards accrued by a delegation. | | | | ---- | ------------------------------------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/DelegationRewards` | | REST | `GET /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/rewards/{validatorAddress}` | Request `QueryDelegationRewardsRequest`: | Field | Type | Description | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `delegator_address` | `string` | delegator\_address defines the delegator address to query for. Encoded as cosmos.AddressString. | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryDelegationRewardsResponse`: | Field | Type | Description | | --------- | -------------------------------------------- | ---------------------------------------------------- | | `rewards` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | rewards defines the rewards accrued by a delegation. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_address":"","validator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/DelegationRewards ``` ### DelegationTotalRewards DelegationTotalRewards queries the total rewards accrued by each validator. | | | | ---- | ------------------------------------------------------------------------ | | gRPC | `cosmos.distribution.v1beta1.Query/DelegationTotalRewards` | | REST | `GET /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/rewards` | Request `QueryDelegationTotalRewardsRequest`: | Field | Type | Description | | ------------------- | -------- | ----------------------------------------------------------------------------------------------- | | `delegator_address` | `string` | delegator\_address defines the delegator address to query for. Encoded as cosmos.AddressString. | Response `QueryDelegationTotalRewardsResponse`: | Field | Type | Description | | --------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `rewards` | [`DelegationDelegatorReward`](#cosmos-distribution-v1beta1-delegationdelegatorreward)\[] | rewards defines all the rewards accrued by a delegator. | | `total` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | total defines the sum of all the rewards. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/DelegationTotalRewards ``` ### DelegatorStartingInfo DelegatorStartingInfo queries the starting info for a delegator. | | | | ---- | ------------------------------------------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/DelegatorStartingInfo` | | REST | `GET /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/starting_info/{validatorAddress}` | Request `QueryDelegatorStartingInfoRequest`: | Field | Type | Description | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `delegator_address` | `string` | delegator\_address defines the delegator address to query for. Encoded as cosmos.AddressString. | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryDelegatorStartingInfoResponse`: | Field | Type | Description | | --------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------- | | `starting_info` | [`DelegatorStartingInfo`](#cosmos-distribution-v1beta1-delegatorstartinginfo) | starting\_info defines the starting info of a delegator. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_address":"","validator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/DelegatorStartingInfo ``` ### DelegatorValidators DelegatorValidators queries the validators of a delegator. | | | | ---- | --------------------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/DelegatorValidators` | | REST | `GET /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/validators` | Request `QueryDelegatorValidatorsRequest`: | Field | Type | Description | | ------------------- | -------- | ----------------------------------------------------------------------------------------------- | | `delegator_address` | `string` | delegator\_address defines the delegator address to query for. Encoded as cosmos.AddressString. | Response `QueryDelegatorValidatorsResponse`: | Field | Type | Description | | ------------ | ----------- | ---------------------------------------------------------------- | | `validators` | `string`\[] | validators defines the validators a delegator is delegating for. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/DelegatorValidators ``` ### DelegatorWithdrawAddress DelegatorWithdrawAddress queries withdraw address of a delegator. | | | | ---- | --------------------------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/DelegatorWithdrawAddress` | | REST | `GET /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/withdraw_address` | Request `QueryDelegatorWithdrawAddressRequest`: | Field | Type | Description | | ------------------- | -------- | ----------------------------------------------------------------------------------------------- | | `delegator_address` | `string` | delegator\_address defines the delegator address to query for. Encoded as cosmos.AddressString. | Response `QueryDelegatorWithdrawAddressResponse`: | Field | Type | Description | | ------------------ | -------- | ---------------------------------------------------------------------------------------------- | | `withdraw_address` | `string` | withdraw\_address defines the delegator address to query for. Encoded as cosmos.AddressString. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/DelegatorWithdrawAddress ``` ### Params Params queries params of the distribution module. | | | | ---- | ------------------------------------------ | | gRPC | `cosmos.distribution.v1beta1.Query/Params` | | REST | `GET /cosmos/distribution/v1beta1/params` | Request `QueryParamsRequest`: This message has no fields. Response `QueryParamsResponse`: | Field | Type | Description | | -------- | ----------------------------------------------- | -------------------------------------------- | | `params` | [`Params`](#cosmos-distribution-v1beta1-params) | params defines the parameters of the module. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.distribution.v1beta1.Query/Params ``` ### ValidatorCommission ValidatorCommission queries accumulated commission for a validator. | | | | ---- | --------------------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/ValidatorCommission` | | REST | `GET /cosmos/distribution/v1beta1/validators/{validatorAddress}/commission` | Request `QueryValidatorCommissionRequest`: | Field | Type | Description | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryValidatorCommissionResponse`: | Field | Type | Description | | ------------ | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | `commission` | [`ValidatorAccumulatedCommission`](#cosmos-distribution-v1beta1-validatoraccumulatedcommission) | commission defines the commission the validator received. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/ValidatorCommission ``` ### ValidatorCurrentRewards ValidatorCurrentRewards queries current rewards for a validator. | | | | ---- | -------------------------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/ValidatorCurrentRewards` | | REST | `GET /cosmos/distribution/v1beta1/validators/{validatorAddress}/current_rewards` | Request `QueryValidatorCurrentRewardsRequest`: | Field | Type | Description | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryValidatorCurrentRewardsResponse`: | Field | Type | Description | | --------- | --------------------------------------------------------------------------------- | --------------------------------------------------- | | `rewards` | [`ValidatorCurrentRewards`](#cosmos-distribution-v1beta1-validatorcurrentrewards) | rewards defines the current rewards of a validator. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/ValidatorCurrentRewards ``` ### ValidatorDistributionInfo ValidatorDistributionInfo queries validator commission and self-delegation rewards for validator | | | | ---- | ---------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/ValidatorDistributionInfo` | | REST | `GET /cosmos/distribution/v1beta1/validators/{validatorAddress}` | Request `QueryValidatorDistributionInfoRequest`: | Field | Type | Description | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryValidatorDistributionInfoResponse`: | Field | Type | Description | | ------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `operator_address` | `string` | operator\_address defines the validator operator address. Encoded as cosmos.ValidatorAddressString. | | `self_bond_rewards` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | self\_bond\_rewards defines the self delegations rewards. | | `commission` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | commission defines the commission the validator received. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/ValidatorDistributionInfo ``` ### ValidatorHistoricalRewards ValidatorHistoricalRewards queries historical rewards for a validator at a specific period. | | | | ---- | -------------------------------------------------------------------------------------------- | | gRPC | `cosmos.distribution.v1beta1.Query/ValidatorHistoricalRewards` | | REST | `GET /cosmos/distribution/v1beta1/validators/{validatorAddress}/historical_rewards/{period}` | Request `QueryValidatorHistoricalRewardsRequest`: | Field | Type | Description | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | | `period` | `uint64` | period defines the period to query historical rewards for. | Response `QueryValidatorHistoricalRewardsResponse`: | Field | Type | Description | | --------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------ | | `rewards` | [`ValidatorHistoricalRewards`](#cosmos-distribution-v1beta1-validatorhistoricalrewards) | rewards defines the historical rewards of a validator. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_address":"","period":"1"}' \ localhost:9090 cosmos.distribution.v1beta1.Query/ValidatorHistoricalRewards ``` ### ValidatorOutstandingRewards ValidatorOutstandingRewards queries rewards of a validator address. | | | | ---- | ------------------------------------------------------------------------------------ | | gRPC | `cosmos.distribution.v1beta1.Query/ValidatorOutstandingRewards` | | REST | `GET /cosmos/distribution/v1beta1/validators/{validatorAddress}/outstanding_rewards` | Request `QueryValidatorOutstandingRewardsRequest`: | Field | Type | Description | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------- | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryValidatorOutstandingRewardsResponse`: | Field | Type | Description | | --------- | ----------------------------------------------------------------------------------------- | ----------- | | `rewards` | [`ValidatorOutstandingRewards`](#cosmos-distribution-v1beta1-validatoroutstandingrewards) | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_address":""}' \ localhost:9090 cosmos.distribution.v1beta1.Query/ValidatorOutstandingRewards ``` ### ValidatorSlashes ValidatorSlashes queries slash events of a validator. | | | | ---- | ------------------------------------------------------------------------ | | gRPC | `cosmos.distribution.v1beta1.Query/ValidatorSlashes` | | REST | `GET /cosmos/distribution/v1beta1/validators/{validatorAddress}/slashes` | Request `QueryValidatorSlashesRequest`: | Field | Type | Description | | ------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `validator_address` | `string` | validator\_address defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | | `starting_height` | `uint64` | starting\_height defines the optional starting height to query the slashes. | | `ending_height` | `uint64` | starting\_height defines the optional ending height to query the slashes. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryValidatorSlashesResponse`: | Field | Type | Description | | ------------ | ---------------------------------------------------------------------------- | --------------------------------------------------- | | `slashes` | [`ValidatorSlashEvent`](#cosmos-distribution-v1beta1-validatorslashevent)\[] | slashes defines the slashes the validator received. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_address":"","starting_height":"1","ending_height":"1","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.distribution.v1beta1.Query/ValidatorSlashes ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### CommunityPoolSpend CommunityPoolSpend defines a governance operation for sending tokens from the community pool in the x/distribution module to another account, which could be the governance module itself. The authority is defined in the keeper. | | | | ---------- | ---------------------------------------------------- | | Type URL | `/cosmos.distribution.v1beta1.MsgCommunityPoolSpend` | | Handler | `cosmos.distribution.v1beta1.Msg/CommunityPoolSpend` | | Signer | `authority` | | Amino name | `cosmos-sdk/distr/MsgCommunityPoolSpend` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `recipient` | `string` | | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.distribution.v1beta1.MsgCommunityPoolSpend", "authority": "", "recipient": "", "amount": [ { "denom": "", "amount": "" } ] } ``` Response `MsgCommunityPoolSpendResponse`: This message has no fields. ### DepositValidatorRewardsPool DepositValidatorRewardsPool defines a method to provide additional rewards to delegators to a specific validator. | | | | ---------- | ------------------------------------------------------------- | | Type URL | `/cosmos.distribution.v1beta1.MsgDepositValidatorRewardsPool` | | Handler | `cosmos.distribution.v1beta1.Msg/DepositValidatorRewardsPool` | | Signer | `depositor` | | Amino name | `cosmos-sdk/distr/MsgDepositValRewards` | | Field | Type | Description | | ------------------- | -------------------------------------- | ----------------------------------------- | | `depositor` | `string` | Encoded as cosmos.AddressString. | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.distribution.v1beta1.MsgDepositValidatorRewardsPool", "depositor": "", "validator_address": "", "amount": [ { "denom": "", "amount": "" } ] } ``` Response `MsgDepositValidatorRewardsPoolResponse`: This message has no fields. ### FundCommunityPool FundCommunityPool defines a method to allow an account to directly fund the community pool. | | | | ---------- | --------------------------------------------------- | | Type URL | `/cosmos.distribution.v1beta1.MsgFundCommunityPool` | | Handler | `cosmos.distribution.v1beta1.Msg/FundCommunityPool` | | Signer | `depositor` | | Amino name | `cosmos-sdk/MsgFundCommunityPool` | | Field | Type | Description | | ----------- | -------------------------------------- | -------------------------------- | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | | `depositor` | `string` | Encoded as cosmos.AddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.distribution.v1beta1.MsgFundCommunityPool", "amount": [ { "denom": "", "amount": "" } ], "depositor": "" } ``` Response `MsgFundCommunityPoolResponse`: This message has no fields. ### SetWithdrawAddress SetWithdrawAddress defines a method to change the withdraw address for a delegator (or validator self-delegation). | | | | ---------- | ---------------------------------------------------- | | Type URL | `/cosmos.distribution.v1beta1.MsgSetWithdrawAddress` | | Handler | `cosmos.distribution.v1beta1.Msg/SetWithdrawAddress` | | Signer | `delegator_address` | | Amino name | `cosmos-sdk/MsgModifyWithdrawAddress` | | Field | Type | Description | | ------------------- | -------- | -------------------------------- | | `delegator_address` | `string` | Encoded as cosmos.AddressString. | | `withdraw_address` | `string` | Encoded as cosmos.AddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", "delegator_address": "", "withdraw_address": "" } ``` Response `MsgSetWithdrawAddressResponse`: This message has no fields. ### UpdateParams UpdateParams defines a governance operation for updating the x/distribution module parameters. The authority is defined in the keeper. | | | | ---------- | ---------------------------------------------- | | Type URL | `/cosmos.distribution.v1beta1.MsgUpdateParams` | | Handler | `cosmos.distribution.v1beta1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/distribution/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `params` | [`Params`](#cosmos-distribution-v1beta1-params) | params defines the x/distribution parameters to update. NOTE: All parameters must be supplied. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.distribution.v1beta1.MsgUpdateParams", "authority": "", "params": { "community_tax": "", "withdraw_addr_enabled": false } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. ### WithdrawDelegatorReward WithdrawDelegatorReward defines a method to withdraw rewards of delegator from a single validator. | | | | ---------- | --------------------------------------------------------- | | Type URL | `/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward` | | Handler | `cosmos.distribution.v1beta1.Msg/WithdrawDelegatorReward` | | Signer | `delegator_address` | | Amino name | `cosmos-sdk/MsgWithdrawDelegationReward` | | Field | Type | Description | | ------------------- | -------- | ----------------------------------------- | | `delegator_address` | `string` | Encoded as cosmos.AddressString. | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", "delegator_address": "", "validator_address": "" } ``` Response `MsgWithdrawDelegatorRewardResponse`: | Field | Type | Description | | -------- | -------------------------------------- | ----------- | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | ### WithdrawValidatorCommission WithdrawValidatorCommission defines a method to withdraw the full commission to the validator address. | | | | ---------- | ------------------------------------------------------------- | | Type URL | `/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission` | | Handler | `cosmos.distribution.v1beta1.Msg/WithdrawValidatorCommission` | | Signer | `validator_address` | | Amino name | `cosmos-sdk/MsgWithdrawValCommission` | | Field | Type | Description | | ------------------- | -------- | ----------------------------------------- | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission", "validator_address": "" } ``` Response `MsgWithdrawValidatorCommissionResponse`: | Field | Type | Description | | -------- | -------------------------------------- | ----------- | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.base.v1beta1.Coin Coin defines a token with a denomination and an amount. NOTE: The amount field is an Int which implements the custom method signatures required by gogoproto. | Field | Type | Description | | -------- | -------- | ---------------------- | | `denom` | `string` | | | `amount` | `string` | Encoded as cosmos.Int. | ### cosmos.base.v1beta1.DecCoin DecCoin defines a token with a denomination and a decimal amount. NOTE: The amount field is a Dec which implements the custom method signatures required by gogoproto. | Field | Type | Description | | -------- | -------- | -------------------------------------------------------------------------------- | | `denom` | `string` | | | `amount` | `string` | Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | ### cosmos.distribution.v1beta1.DelegationDelegatorReward DelegationDelegatorReward represents the properties of a delegator's delegation reward. | Field | Type | Description | | ------------------- | -------------------------------------------- | ----------------------------------------- | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `reward` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | | ### cosmos.distribution.v1beta1.DelegatorStartingInfo DelegatorStartingInfo represents the starting info for a delegator reward period. It tracks the previous validator period, the delegation's amount of staking token, and the creation height (to check later on if any slashes have occurred). NOTE: Even though validators are slashed to whole staking tokens, the delegators within the validator may be left with less than a full token, thus sdk.Dec is used. | Field | Type | Description | | ----------------- | -------- | -------------------------------------------------------------------------------- | | `previous_period` | `uint64` | | | `stake` | `string` | Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `height` | `uint64` | | ### cosmos.distribution.v1beta1.Params Params defines the set of params for the distribution module. | Field | Type | Description | | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `community_tax` | `string` | Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `base_proposer_reward` | `string` | Deprecated: The base\_proposer\_reward field is deprecated and is no longer used in the x/distribution module's reward mechanism. Deprecated. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `bonus_proposer_reward` | `string` | Deprecated: The bonus\_proposer\_reward field is deprecated and is no longer used in the x/distribution module's reward mechanism. Deprecated. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `withdraw_addr_enabled` | `bool` | | ### cosmos.distribution.v1beta1.ValidatorAccumulatedCommission ValidatorAccumulatedCommission represents accumulated commission for a validator kept as a running counter, can be withdrawn at any time. | Field | Type | Description | | ------------ | -------------------------------------------- | ----------- | | `commission` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | | ### cosmos.distribution.v1beta1.ValidatorCurrentRewards ValidatorCurrentRewards represents current rewards and current period for a validator kept as a running counter and incremented each block as long as the validator's tokens remain constant. | Field | Type | Description | | --------- | -------------------------------------------- | ----------- | | `rewards` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | | | `period` | `uint64` | | ### cosmos.distribution.v1beta1.ValidatorHistoricalRewards ValidatorHistoricalRewards represents historical rewards for a validator. Height is implicit within the store key. Cumulative reward ratio is the sum from the zeroeth period until this period of rewards / tokens, per the spec. The reference count indicates the number of objects which might need to reference this historical entry at any point. ReferenceCount = number of outstanding delegations which ended the associated period (and might need to read that record) + number of slashes which ended the associated period (and might need to read that record) + one per validator for the zeroeth period, set on initialization | Field | Type | Description | | ------------------------- | -------------------------------------------- | ----------- | | `cumulative_reward_ratio` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | | | `reference_count` | `uint32` | | ### cosmos.distribution.v1beta1.ValidatorOutstandingRewards ValidatorOutstandingRewards represents outstanding (un-withdrawn) rewards for a validator inexpensive to track, allows simple sanity checks. | Field | Type | Description | | --------- | -------------------------------------------- | ----------- | | `rewards` | [`DecCoin`](#cosmos-base-v1beta1-deccoin)\[] | | ### cosmos.distribution.v1beta1.ValidatorSlashEvent ValidatorSlashEvent represents a validator slash event. Height is implicit within the store key. This is needed to calculate appropriate amount of staking tokens for delegations which are withdrawn after a slash has occurred. | Field | Type | Description | | ------------------ | -------- | -------------------------------------------------------------------------------- | | `validator_period` | `uint64` | | | `fraction` | `string` | Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | # Epochs Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/epochs gRPC queries and transaction messages defined by the epochs module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### CurrentEpoch CurrentEpoch provide current epoch of specified identifier | | | | ---- | ------------------------------------------ | | gRPC | `cosmos.epochs.v1beta1.Query/CurrentEpoch` | | REST | `GET /cosmos/epochs/v1beta1/current_epoch` | Request `QueryCurrentEpochRequest`: | Field | Type | Description | | ------------ | -------- | ----------- | | `identifier` | `string` | | Response `QueryCurrentEpochResponse`: | Field | Type | Description | | --------------- | ------- | ----------- | | `current_epoch` | `int64` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"identifier":""}' \ localhost:9090 cosmos.epochs.v1beta1.Query/CurrentEpoch ``` ### EpochInfos EpochInfos provide running epochInfos | | | | ---- | ---------------------------------------- | | gRPC | `cosmos.epochs.v1beta1.Query/EpochInfos` | | REST | `GET /cosmos/epochs/v1beta1/epochs` | Request `QueryEpochInfosRequest`: This message has no fields. Response `QueryEpochInfosResponse`: | Field | Type | Description | | -------- | -------------------------------------------------- | ----------- | | `epochs` | [`EpochInfo`](#cosmos-epochs-v1beta1-epochinfo)\[] | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.epochs.v1beta1.Query/EpochInfos ``` ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.epochs.v1beta1.EpochInfo EpochInfo is a struct that describes the data going into a timer defined by the x/epochs module. | Field | Type | Description | | ---------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `identifier` | `string` | identifier is a unique reference to this particular timer. | | `start_time` | `Timestamp` | start\_time is the time at which the timer first ever ticks. If start\_time is in the future, the epoch will not begin until the start time. | | `duration` | `Duration` | duration is the time in between epoch ticks. In order for intended behavior to be met, duration should be greater than the chains expected block time. Duration must be non-zero. | | `current_epoch` | `int64` | current\_epoch is the current epoch number, or in other words, how many times has the timer 'ticked'. The first tick (current\_epoch=1) is defined as the first block whose blocktime is greater than the EpochInfo start\_time. | | `current_epoch_start_time` | `Timestamp` | current\_epoch\_start\_time describes the start time of the current timer interval. The interval is (current\_epoch\_start\_time, current\_epoch\_start\_time + duration] When the timer ticks, this is set to current\_epoch\_start\_time = last\_epoch\_start\_time + duration only one timer tick for a given identifier can occur per block. NOTE! The current\_epoch\_start\_time may diverge significantly from the wall-clock time the epoch began at. Wall-clock time of epoch start may be >> current\_epoch\_start\_time. Suppose current\_epoch\_start\_time = 10, duration = 5. Suppose the chain goes offline at t=14, and comes back online at t=30, and produces blocks at every successive time. (t=31, 32, etc.) \* The t=30 block will start the epoch for (10, 15] \* The t=31 block will start the epoch for (15, 20] \* The t=32 block will start the epoch for (20, 25] \* The t=33 block will start the epoch for (25, 30] \* The t=34 block will start the epoch for (30, 35] \* The **t=36** block will start the epoch for (35, 40] | | `epoch_counting_started` | `bool` | epoch\_counting\_started is a boolean, that indicates whether this epoch timer has began yet. | | `current_epoch_start_height` | `int64` | current\_epoch\_start\_height is the block height at which the current epoch started. (The block height at which the timer last ticked) | # Evidence Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/evidence gRPC queries and transaction messages defined by the evidence module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### AllEvidence AllEvidence queries all evidence. | | | | ---- | ------------------------------------------- | | gRPC | `cosmos.evidence.v1beta1.Query/AllEvidence` | | REST | `GET /cosmos/evidence/v1beta1/evidence` | Request `QueryAllEvidenceRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ---------------------------------------------------------- | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryAllEvidenceResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `evidence` | `Any`\[] | evidence returns all evidences. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"pagination":{"limit":"1"}}' \ localhost:9090 cosmos.evidence.v1beta1.Query/AllEvidence ``` ### Evidence Evidence queries evidence based on evidence hash. | | | | ---- | ---------------------------------------------- | | gRPC | `cosmos.evidence.v1beta1.Query/Evidence` | | REST | `GET /cosmos/evidence/v1beta1/evidence/{hash}` | Request `QueryEvidenceRequest`: | Field | Type | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `evidence_hash` | `bytes` | evidence\_hash defines the hash of the requested evidence. Deprecated: Use hash, a HEX encoded string, instead. Deprecated. | | `hash` | `string` | hash defines the evidence hash of the requested evidence. | Response `QueryEvidenceResponse`: | Field | Type | Description | | ---------- | ----- | ---------------------------------------- | | `evidence` | `Any` | evidence returns the requested evidence. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"hash":""}' \ localhost:9090 cosmos.evidence.v1beta1.Query/Evidence ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### SubmitEvidence SubmitEvidence submits an arbitrary Evidence of misbehavior such as equivocation or counterfactual signing. | | | | ---------- | -------------------------------------------- | | Type URL | `/cosmos.evidence.v1beta1.MsgSubmitEvidence` | | Handler | `cosmos.evidence.v1beta1.Msg/SubmitEvidence` | | Signer | `submitter` | | Amino name | `cosmos-sdk/MsgSubmitEvidence` | | Field | Type | Description | | ----------- | -------- | ---------------------------------------------------------------------------------------------------------------- | | `submitter` | `string` | submitter is the signer account address of evidence. Encoded as cosmos.AddressString. | | `evidence` | `Any` | evidence defines the evidence of misbehavior. Accepts any message implementing cosmos.evidence.v1beta1.Evidence. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.evidence.v1beta1.MsgSubmitEvidence", "submitter": "", "evidence": { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" } } ``` Response `MsgSubmitEvidenceResponse`: | Field | Type | Description | | ------ | ------- | -------------------------------------- | | `hash` | `bytes` | hash defines the hash of the evidence. | ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | # Feegrant Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/feegrant gRPC queries and transaction messages defined by the feegrant module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### Allowance Allowance returns granted allowance to the grantee by the granter. | | | | ---- | ------------------------------------------------------------ | | gRPC | `cosmos.feegrant.v1beta1.Query/Allowance` | | REST | `GET /cosmos/feegrant/v1beta1/allowance/{granter}/{grantee}` | Request `QueryAllowanceRequest`: | Field | Type | Description | | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `granter` | `string` | granter is the address of the user granting an allowance of their funds. Encoded as cosmos.AddressString. | | `grantee` | `string` | grantee is the address of the user being granted an allowance of another user's funds. Encoded as cosmos.AddressString. | Response `QueryAllowanceResponse`: | Field | Type | Description | | ----------- | ----------------------------------------- | --------------------------------------------------------- | | `allowance` | [`Grant`](#cosmos-feegrant-v1beta1-grant) | allowance is an allowance granted for grantee by granter. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"granter":"","grantee":""}' \ localhost:9090 cosmos.feegrant.v1beta1.Query/Allowance ``` ### Allowances Allowances returns all the grants for the given grantee address. | | | | ---- | --------------------------------------------------- | | gRPC | `cosmos.feegrant.v1beta1.Query/Allowances` | | REST | `GET /cosmos/feegrant/v1beta1/allowances/{grantee}` | Request `QueryAllowancesRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------ | | `grantee` | `string` | Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines a pagination for the request. | Response `QueryAllowancesResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ---------------------------------------------------------- | | `allowances` | [`Grant`](#cosmos-feegrant-v1beta1-grant)\[] | allowances are allowance's granted for grantee by granter. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines a pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"grantee":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.feegrant.v1beta1.Query/Allowances ``` ### AllowancesByGranter AllowancesByGranter returns all the grants given by an address | | | | ---- | --------------------------------------------------- | | gRPC | `cosmos.feegrant.v1beta1.Query/AllowancesByGranter` | | REST | `GET /cosmos/feegrant/v1beta1/issued/{granter}` | Request `QueryAllowancesByGranterRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------ | | `granter` | `string` | Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines a pagination for the request. | Response `QueryAllowancesByGranterResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ------------------------------------------------- | | `allowances` | [`Grant`](#cosmos-feegrant-v1beta1-grant)\[] | allowances that have been issued by the granter. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines a pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"granter":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.feegrant.v1beta1.Query/AllowancesByGranter ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### GrantAllowance GrantAllowance grants fee allowance to the grantee on the granter's account with the provided expiration time. | | | | ---------- | -------------------------------------------- | | Type URL | `/cosmos.feegrant.v1beta1.MsgGrantAllowance` | | Handler | `cosmos.feegrant.v1beta1.Msg/GrantAllowance` | | Signer | `granter` | | Amino name | `cosmos-sdk/MsgGrantAllowance` | | Field | Type | Description | | ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `granter` | `string` | granter is the address of the user granting an allowance of their funds. Encoded as cosmos.AddressString. | | `grantee` | `string` | grantee is the address of the user being granted an allowance of another user's funds. Encoded as cosmos.AddressString. | | `allowance` | `Any` | allowance can be any of basic, periodic, allowed fee allowance. One of: `/cosmos.feegrant.v1beta1.AllowedMsgAllowance`, `/cosmos.feegrant.v1beta1.BasicAllowance`, `/cosmos.feegrant.v1beta1.PeriodicAllowance`. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.feegrant.v1beta1.MsgGrantAllowance", "granter": "", "grantee": "", "allowance": { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" } } ``` Response `MsgGrantAllowanceResponse`: This message has no fields. ### PruneAllowances PruneAllowances prunes expired fee allowances, currently up to 75 at a time. | | | | ---------- | --------------------------------------------- | | Type URL | `/cosmos.feegrant.v1beta1.MsgPruneAllowances` | | Handler | `cosmos.feegrant.v1beta1.Msg/PruneAllowances` | | Signer | `pruner` | | Amino name | none registered | | Field | Type | Description | | -------- | -------- | ---------------------------------------------------------------------------------------------- | | `pruner` | `string` | pruner is the address of the user pruning expired allowances. Encoded as cosmos.AddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.feegrant.v1beta1.MsgPruneAllowances", "pruner": "" } ``` Response `MsgPruneAllowancesResponse`: This message has no fields. ### RevokeAllowance RevokeAllowance revokes any fee allowance of granter's account that has been granted to the grantee. | | | | ---------- | --------------------------------------------- | | Type URL | `/cosmos.feegrant.v1beta1.MsgRevokeAllowance` | | Handler | `cosmos.feegrant.v1beta1.Msg/RevokeAllowance` | | Signer | `granter` | | Amino name | `cosmos-sdk/MsgRevokeAllowance` | | Field | Type | Description | | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `granter` | `string` | granter is the address of the user granting an allowance of their funds. Encoded as cosmos.AddressString. | | `grantee` | `string` | grantee is the address of the user being granted an allowance of another user's funds. Encoded as cosmos.AddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.feegrant.v1beta1.MsgRevokeAllowance", "granter": "", "grantee": "" } ``` Response `MsgRevokeAllowanceResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.feegrant.v1beta1.Grant Grant is stored in the KVStore to record a grant with full context | Field | Type | Description | | ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `granter` | `string` | granter is the address of the user granting an allowance of their funds. Encoded as cosmos.AddressString. | | `grantee` | `string` | grantee is the address of the user being granted an allowance of another user's funds. Encoded as cosmos.AddressString. | | `allowance` | `Any` | allowance can be any of basic, periodic, allowed fee allowance. One of: `/cosmos.feegrant.v1beta1.AllowedMsgAllowance`, `/cosmos.feegrant.v1beta1.BasicAllowance`, `/cosmos.feegrant.v1beta1.PeriodicAllowance`. | # Gov Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/gov gRPC queries and transaction messages defined by the gov module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ## cosmos.gov.v1.Query Query defines the gRPC querier service for gov module ### Constitution Constitution queries the chain's constitution. | | | | ---- | ---------------------------------- | | gRPC | `cosmos.gov.v1.Query/Constitution` | | REST | `GET /cosmos/gov/v1/constitution` | Request `QueryConstitutionRequest`: This message has no fields. Response `QueryConstitutionResponse`: | Field | Type | Description | | -------------- | -------- | ----------- | | `constitution` | `string` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.gov.v1.Query/Constitution ``` ### Deposit (Query, v1) Deposit queries single deposit information based on proposalID, depositAddr. | | | | ---- | ---------------------------------------------------------------- | | gRPC | `cosmos.gov.v1.Query/Deposit` | | REST | `GET /cosmos/gov/v1/proposals/{proposalId}/deposits/{depositor}` | Request `QueryDepositRequest`: | Field | Type | Description | | ------------- | -------- | -------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | Response `QueryDepositResponse`: | Field | Type | Description | | --------- | ----------------------------------- | -------------------------------------- | | `deposit` | [`Deposit`](#cosmos-gov-v1-deposit) | deposit defines the requested deposit. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","depositor":""}' \ localhost:9090 cosmos.gov.v1.Query/Deposit ``` ### Deposits (v1) Deposits queries all deposits of a single proposal. | | | | ---- | ---------------------------------------------------- | | gRPC | `cosmos.gov.v1.Query/Deposits` | | REST | `GET /cosmos/gov/v1/proposals/{proposalId}/deposits` | Request `QueryDepositsRequest`: | Field | Type | Description | | ------------- | ------------------------------------------------------- | ---------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDepositsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `deposits` | [`Deposit`](#cosmos-gov-v1-deposit)\[] | deposits defines the requested deposits. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.gov.v1.Query/Deposits ``` ### Params (v1) Params queries all parameters of the gov module. | | | | ---- | ---------------------------------------- | | gRPC | `cosmos.gov.v1.Query/Params` | | REST | `GET /cosmos/gov/v1/params/{paramsType}` | Request `QueryParamsRequest`: | Field | Type | Description | | ------------- | -------- | ---------------------------------------------------------------------------------------------------- | | `params_type` | `string` | params\_type defines which parameters to query for, can be one of "voting", "tallying" or "deposit". | Response `QueryParamsResponse`: | Field | Type | Description | | ---------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `voting_params` | [`VotingParams`](#cosmos-gov-v1-votingparams) | Deprecated: Prefer to use `params` instead. voting\_params defines the parameters related to voting. Deprecated. | | `deposit_params` | [`DepositParams`](#cosmos-gov-v1-depositparams) | Deprecated: Prefer to use `params` instead. deposit\_params defines the parameters related to deposit. Deprecated. | | `tally_params` | [`TallyParams`](#cosmos-gov-v1-tallyparams) | Deprecated: Prefer to use `params` instead. tally\_params defines the parameters related to tally. Deprecated. | | `params` | [`Params`](#cosmos-gov-v1-params) | params defines all the parameters of x/gov module. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"params_type":"voting"}' \ localhost:9090 cosmos.gov.v1.Query/Params ``` ### Proposal (v1) Proposal queries proposal details based on ProposalID. | | | | ---- | ------------------------------------------- | | gRPC | `cosmos.gov.v1.Query/Proposal` | | REST | `GET /cosmos/gov/v1/proposals/{proposalId}` | Request `QueryProposalRequest`: | Field | Type | Description | | ------------- | -------- | --------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | Response `QueryProposalResponse`: | Field | Type | Description | | ---------- | ------------------------------------- | ---------------------------------------------- | | `proposal` | [`Proposal`](#cosmos-gov-v1-proposal) | proposal is the requested governance proposal. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1"}' \ localhost:9090 cosmos.gov.v1.Query/Proposal ``` ### Proposals (v1) Proposals queries all proposals based on given status. | | | | ---- | ------------------------------- | | gRPC | `cosmos.gov.v1.Query/Proposals` | | REST | `GET /cosmos/gov/v1/proposals` | Request `QueryProposalsRequest`: | Field | Type | Description | | ----------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `proposal_status` | [`ProposalStatus`](#cosmos-gov-v1-proposalstatus) | proposal\_status defines the status of the proposals. | | `voter` | `string` | voter defines the voter address for the proposals. Encoded as cosmos.AddressString. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryProposalsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | --------------------------------------------------------- | | `proposals` | [`Proposal`](#cosmos-gov-v1-proposal)\[] | proposals defines all the requested governance proposals. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_status":"PROPOSAL_STATUS_DEPOSIT_PERIOD","voter":"","depositor":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.gov.v1.Query/Proposals ``` ### TallyResult (v1) TallyResult queries the tally of a proposal vote. | | | | ---- | ------------------------------------------------- | | gRPC | `cosmos.gov.v1.Query/TallyResult` | | REST | `GET /cosmos/gov/v1/proposals/{proposalId}/tally` | Request `QueryTallyResultRequest`: | Field | Type | Description | | ------------- | -------- | --------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | Response `QueryTallyResultResponse`: | Field | Type | Description | | ------- | ------------------------------------------- | ---------------------------------- | | `tally` | [`TallyResult`](#cosmos-gov-v1-tallyresult) | tally defines the requested tally. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1"}' \ localhost:9090 cosmos.gov.v1.Query/TallyResult ``` ### Vote (Query, v1) Vote queries voted information based on proposalID, voterAddr. | | | | ---- | --------------------------------------------------------- | | gRPC | `cosmos.gov.v1.Query/Vote` | | REST | `GET /cosmos/gov/v1/proposals/{proposalId}/votes/{voter}` | Request `QueryVoteRequest`: | Field | Type | Description | | ------------- | -------- | ----------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter defines the voter address for the proposals. Encoded as cosmos.AddressString. | Response `QueryVoteResponse`: | Field | Type | Description | | ------ | ----------------------------- | ------------------------------ | | `vote` | [`Vote`](#cosmos-gov-v1-vote) | vote defines the queried vote. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","voter":""}' \ localhost:9090 cosmos.gov.v1.Query/Vote ``` ### Votes (v1) Votes queries votes of a given proposal. | | | | ---- | ------------------------------------------------- | | gRPC | `cosmos.gov.v1.Query/Votes` | | REST | `GET /cosmos/gov/v1/proposals/{proposalId}/votes` | Request `QueryVotesRequest`: | Field | Type | Description | | ------------- | ------------------------------------------------------- | ---------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryVotesResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `votes` | [`Vote`](#cosmos-gov-v1-vote)\[] | votes defines the queried votes. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.gov.v1.Query/Votes ``` ## cosmos.gov.v1beta1.Query Query defines the gRPC querier service for gov module ### Deposit (Query, v1beta1) Deposit queries single deposit information based on proposalID, depositor address. | | | | ---- | --------------------------------------------------------------------- | | gRPC | `cosmos.gov.v1beta1.Query/Deposit` | | REST | `GET /cosmos/gov/v1beta1/proposals/{proposalId}/deposits/{depositor}` | Request `QueryDepositRequest`: | Field | Type | Description | | ------------- | -------- | -------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | Response `QueryDepositResponse`: | Field | Type | Description | | --------- | ---------------------------------------- | -------------------------------------- | | `deposit` | [`Deposit`](#cosmos-gov-v1beta1-deposit) | deposit defines the requested deposit. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","depositor":""}' \ localhost:9090 cosmos.gov.v1beta1.Query/Deposit ``` ### Deposits (v1beta1) Deposits queries all deposits of a single proposal. | | | | ---- | --------------------------------------------------------- | | gRPC | `cosmos.gov.v1beta1.Query/Deposits` | | REST | `GET /cosmos/gov/v1beta1/proposals/{proposalId}/deposits` | Request `QueryDepositsRequest`: | Field | Type | Description | | ------------- | ------------------------------------------------------- | ---------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDepositsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `deposits` | [`Deposit`](#cosmos-gov-v1beta1-deposit)\[] | deposits defines the requested deposits. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.gov.v1beta1.Query/Deposits ``` ### Params (v1beta1) Params queries all parameters of the gov module. | | | | ---- | --------------------------------------------- | | gRPC | `cosmos.gov.v1beta1.Query/Params` | | REST | `GET /cosmos/gov/v1beta1/params/{paramsType}` | Request `QueryParamsRequest`: | Field | Type | Description | | ------------- | -------- | ---------------------------------------------------------------------------------------------------- | | `params_type` | `string` | params\_type defines which parameters to query for, can be one of "voting", "tallying" or "deposit". | Response `QueryParamsResponse`: | Field | Type | Description | | ---------------- | ---------------------------------------------------- | ---------------------------------------------------------- | | `voting_params` | [`VotingParams`](#cosmos-gov-v1beta1-votingparams) | voting\_params defines the parameters related to voting. | | `deposit_params` | [`DepositParams`](#cosmos-gov-v1beta1-depositparams) | deposit\_params defines the parameters related to deposit. | | `tally_params` | [`TallyParams`](#cosmos-gov-v1beta1-tallyparams) | tally\_params defines the parameters related to tally. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"params_type":"voting"}' \ localhost:9090 cosmos.gov.v1beta1.Query/Params ``` ### Proposal (v1beta1) Proposal queries proposal details based on ProposalID. | | | | ---- | ------------------------------------------------ | | gRPC | `cosmos.gov.v1beta1.Query/Proposal` | | REST | `GET /cosmos/gov/v1beta1/proposals/{proposalId}` | Request `QueryProposalRequest`: | Field | Type | Description | | ------------- | -------- | --------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | Response `QueryProposalResponse`: | Field | Type | Description | | ---------- | ------------------------------------------ | ----------- | | `proposal` | [`Proposal`](#cosmos-gov-v1beta1-proposal) | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1"}' \ localhost:9090 cosmos.gov.v1beta1.Query/Proposal ``` ### Proposals (v1beta1) Proposals queries all proposals based on given status. | | | | ---- | ------------------------------------ | | gRPC | `cosmos.gov.v1beta1.Query/Proposals` | | REST | `GET /cosmos/gov/v1beta1/proposals` | Request `QueryProposalsRequest`: | Field | Type | Description | | ----------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `proposal_status` | [`ProposalStatus`](#cosmos-gov-v1beta1-proposalstatus) | proposal\_status defines the status of the proposals. | | `voter` | `string` | voter defines the voter address for the proposals. Encoded as cosmos.AddressString. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryProposalsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | --------------------------------------------------------- | | `proposals` | [`Proposal`](#cosmos-gov-v1beta1-proposal)\[] | proposals defines all the requested governance proposals. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_status":"PROPOSAL_STATUS_DEPOSIT_PERIOD","voter":"","depositor":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.gov.v1beta1.Query/Proposals ``` ### TallyResult (v1beta1) TallyResult queries the tally of a proposal vote. | | | | ---- | ------------------------------------------------------ | | gRPC | `cosmos.gov.v1beta1.Query/TallyResult` | | REST | `GET /cosmos/gov/v1beta1/proposals/{proposalId}/tally` | Request `QueryTallyResultRequest`: | Field | Type | Description | | ------------- | -------- | --------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | Response `QueryTallyResultResponse`: | Field | Type | Description | | ------- | ------------------------------------------------ | ---------------------------------- | | `tally` | [`TallyResult`](#cosmos-gov-v1beta1-tallyresult) | tally defines the requested tally. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1"}' \ localhost:9090 cosmos.gov.v1beta1.Query/TallyResult ``` ### Vote (Query, v1beta1) Vote queries voted information based on proposalID, voterAddr. | | | | ---- | -------------------------------------------------------------- | | gRPC | `cosmos.gov.v1beta1.Query/Vote` | | REST | `GET /cosmos/gov/v1beta1/proposals/{proposalId}/votes/{voter}` | Request `QueryVoteRequest`: | Field | Type | Description | | ------------- | -------- | ----------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter defines the voter address for the proposals. Encoded as cosmos.AddressString. | Response `QueryVoteResponse`: | Field | Type | Description | | ------ | ---------------------------------- | ------------------------------ | | `vote` | [`Vote`](#cosmos-gov-v1beta1-vote) | vote defines the queried vote. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","voter":""}' \ localhost:9090 cosmos.gov.v1beta1.Query/Vote ``` ### Votes (v1beta1) Votes queries votes of a given proposal. | | | | ---- | ------------------------------------------------------ | | gRPC | `cosmos.gov.v1beta1.Query/Votes` | | REST | `GET /cosmos/gov/v1beta1/proposals/{proposalId}/votes` | Request `QueryVotesRequest`: | Field | Type | Description | | ------------- | ------------------------------------------------------- | ---------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryVotesResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `votes` | [`Vote`](#cosmos-gov-v1beta1-vote)\[] | votes defines the queried votes. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"proposal_id":"1","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.gov.v1beta1.Query/Votes ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### CancelProposal CancelProposal defines a method to cancel governance proposal | | | | ---------- | ---------------------------------- | | Type URL | `/cosmos.gov.v1.MsgCancelProposal` | | Handler | `cosmos.gov.v1.Msg/CancelProposal` | | Signer | `proposer` | | Amino name | none registered | | Field | Type | Description | | ------------- | -------- | --------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `proposer` | `string` | proposer is the account address of the proposer. Encoded as cosmos.AddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1.MsgCancelProposal", "proposal_id": "1", "proposer": "" } ``` Response `MsgCancelProposalResponse`: | Field | Type | Description | | ----------------- | ----------- | ---------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `canceled_time` | `Timestamp` | canceled\_time is the time when proposal is canceled. | | `canceled_height` | `uint64` | canceled\_height defines the block height at which the proposal is canceled. | ### Deposit (Msg, v1) Deposit defines a method to add deposit on a specific proposal. | | | | ---------- | --------------------------- | | Type URL | `/cosmos.gov.v1.MsgDeposit` | | Handler | `cosmos.gov.v1.Msg/Deposit` | | Signer | `depositor` | | Amino name | `cosmos-sdk/v1/MsgDeposit` | | Field | Type | Description | | ------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | amount to be deposited by depositor. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1.MsgDeposit", "proposal_id": "1", "depositor": "", "amount": [ { "denom": "", "amount": "" } ] } ``` Response `MsgDepositResponse`: This message has no fields. ### ExecLegacyContent ExecLegacyContent defines a Msg to be in included in a MsgSubmitProposal to execute a legacy content-based proposal. | | | | ---------- | ------------------------------------- | | Type URL | `/cosmos.gov.v1.MsgExecLegacyContent` | | Handler | `cosmos.gov.v1.Msg/ExecLegacyContent` | | Signer | `authority` | | Amino name | `cosmos-sdk/v1/MsgExecLegacyContent` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `content` | `Any` | content is the proposal's content. One of: `/cosmos.distribution.v1beta1.CommunityPoolSpendProposal`, `/cosmos.distribution.v1beta1.CommunityPoolSpendProposalWithDeposit`, `/cosmos.gov.v1beta1.TextProposal`, `/cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal`, `/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal`. | | `authority` | `string` | authority must be the gov module address. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1.MsgExecLegacyContent", "content": { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" }, "authority": "" } ``` Response `MsgExecLegacyContentResponse`: This message has no fields. ### SubmitProposal (v1) SubmitProposal defines a method to create new proposal given the messages. | | | | ---------- | ---------------------------------- | | Type URL | `/cosmos.gov.v1.MsgSubmitProposal` | | Handler | `cosmos.gov.v1.Msg/SubmitProposal` | | Signer | `proposer` | | Amino name | `cosmos-sdk/v1/MsgSubmitProposal` | | Field | Type | Description | | ----------------- | -------------------------------------- | --------------------------------------------------------------------------------- | | `messages` | `Any`\[] | messages are the arbitrary messages to be executed if proposal passes. | | `initial_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | initial\_deposit is the deposit value that must be paid at proposal submission. | | `proposer` | `string` | proposer is the account address of the proposer. Encoded as cosmos.AddressString. | | `metadata` | `string` | metadata is any arbitrary metadata attached to the proposal. | | `title` | `string` | title is the title of the proposal. | | `summary` | `string` | summary is the summary of the proposal | | `expedited` | `bool` | expedited defines if the proposal is expedited or not | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1.MsgSubmitProposal", "messages": [ { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" } ], "initial_deposit": [ { "denom": "", "amount": "" } ], "proposer": "", "metadata": "", "title": "", "summary": "", "expedited": false } ``` Response `MsgSubmitProposalResponse`: | Field | Type | Description | | ------------- | -------- | --------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | ### UpdateParams UpdateParams defines a governance operation for updating the x/gov module parameters. The authority is defined in the keeper. | | | | ---------- | ------------------------------------- | | Type URL | `/cosmos.gov.v1.MsgUpdateParams` | | Handler | `cosmos.gov.v1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/x/gov/v1/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `params` | [`Params`](#cosmos-gov-v1-params) | params defines the x/gov parameters to update. NOTE: All parameters must be supplied. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1.MsgUpdateParams", "authority": "", "params": { "min_deposit": [ { "denom": "", "amount": "" } ], "max_deposit_period": "0s", "voting_period": "0s", "quorum": "", "threshold": "", "veto_threshold": "", "min_initial_deposit_ratio": "", "proposal_cancel_ratio": "", "proposal_cancel_dest": "", "expedited_voting_period": "0s", "expedited_threshold": "", "expedited_min_deposit": [ { "denom": "", "amount": "" } ], "burn_vote_quorum": false, "burn_proposal_deposit_prevote": false, "burn_vote_veto": false, "min_deposit_ratio": "" } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. ### Vote (Msg, v1) Vote defines a method to add a vote on a specific proposal. | | | | ---------- | ------------------------ | | Type URL | `/cosmos.gov.v1.MsgVote` | | Handler | `cosmos.gov.v1.Msg/Vote` | | Signer | `voter` | | Amino name | `cosmos-sdk/v1/MsgVote` | | Field | Type | Description | | ------------- | ----------------------------------------- | ----------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter is the voter address for the proposal. Encoded as cosmos.AddressString. | | `option` | [`VoteOption`](#cosmos-gov-v1-voteoption) | option defines the vote option. | | `metadata` | `string` | metadata is any arbitrary metadata attached to the Vote. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1.MsgVote", "proposal_id": "1", "voter": "", "option": "VOTE_OPTION_YES", "metadata": "" } ``` Response `MsgVoteResponse`: This message has no fields. ### VoteWeighted (v1) VoteWeighted defines a method to add a weighted vote on a specific proposal. | | | | ---------- | -------------------------------- | | Type URL | `/cosmos.gov.v1.MsgVoteWeighted` | | Handler | `cosmos.gov.v1.Msg/VoteWeighted` | | Signer | `voter` | | Amino name | `cosmos-sdk/v1/MsgVoteWeighted` | | Field | Type | Description | | ------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter is the voter address for the proposal. Encoded as cosmos.AddressString. | | `options` | [`WeightedVoteOption`](#cosmos-gov-v1-weightedvoteoption)\[] | options defines the weighted vote options. | | `metadata` | `string` | metadata is any arbitrary metadata attached to the VoteWeighted. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1.MsgVoteWeighted", "proposal_id": "1", "voter": "", "options": [ { "option": "VOTE_OPTION_YES", "weight": "" } ], "metadata": "" } ``` Response `MsgVoteWeightedResponse`: This message has no fields. ### Deposit (Msg, v1beta1) Deposit defines a method to add deposit on a specific proposal. | | | | ---------- | -------------------------------- | | Type URL | `/cosmos.gov.v1beta1.MsgDeposit` | | Handler | `cosmos.gov.v1beta1.Msg/Deposit` | | Signer | `depositor` | | Amino name | `cosmos-sdk/MsgDeposit` | | Field | Type | Description | | ------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | amount to be deposited by depositor. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1beta1.MsgDeposit", "proposal_id": "1", "depositor": "", "amount": [ { "denom": "", "amount": "" } ] } ``` Response `MsgDepositResponse`: This message has no fields. ### SubmitProposal (v1beta1) SubmitProposal defines a method to create new proposal given a content. | | | | ---------- | --------------------------------------- | | Type URL | `/cosmos.gov.v1beta1.MsgSubmitProposal` | | Handler | `cosmos.gov.v1beta1.Msg/SubmitProposal` | | Signer | `proposer` | | Amino name | `cosmos-sdk/MsgSubmitProposal` | | Field | Type | Description | | ----------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `content` | `Any` | content is the proposal's content. One of: `/cosmos.distribution.v1beta1.CommunityPoolSpendProposal`, `/cosmos.distribution.v1beta1.CommunityPoolSpendProposalWithDeposit`, `/cosmos.gov.v1beta1.TextProposal`, `/cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal`, `/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal`. | | `initial_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | initial\_deposit is the deposit value that must be paid at proposal submission. | | `proposer` | `string` | proposer is the account address of the proposer. Encoded as cosmos.AddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1beta1.MsgSubmitProposal", "content": { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" }, "initial_deposit": [ { "denom": "", "amount": "" } ], "proposer": "" } ``` Response `MsgSubmitProposalResponse`: | Field | Type | Description | | ------------- | -------- | --------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | ### Vote (Msg, v1beta1) Vote defines a method to add a vote on a specific proposal. | | | | ---------- | ----------------------------- | | Type URL | `/cosmos.gov.v1beta1.MsgVote` | | Handler | `cosmos.gov.v1beta1.Msg/Vote` | | Signer | `voter` | | Amino name | `cosmos-sdk/MsgVote` | | Field | Type | Description | | ------------- | ---------------------------------------------- | ----------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter is the voter address for the proposal. Encoded as cosmos.AddressString. | | `option` | [`VoteOption`](#cosmos-gov-v1beta1-voteoption) | option defines the vote option. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1beta1.MsgVote", "proposal_id": "1", "voter": "", "option": "VOTE_OPTION_YES" } ``` Response `MsgVoteResponse`: This message has no fields. ### VoteWeighted (v1beta1) VoteWeighted defines a method to add a weighted vote on a specific proposal. | | | | ---------- | ------------------------------------- | | Type URL | `/cosmos.gov.v1beta1.MsgVoteWeighted` | | Handler | `cosmos.gov.v1beta1.Msg/VoteWeighted` | | Signer | `voter` | | Amino name | `cosmos-sdk/MsgVoteWeighted` | | Field | Type | Description | | ------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter is the voter address for the proposal. Encoded as cosmos.AddressString. | | `options` | [`WeightedVoteOption`](#cosmos-gov-v1beta1-weightedvoteoption)\[] | options defines the weighted vote options. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.gov.v1beta1.MsgVoteWeighted", "proposal_id": "1", "voter": "", "options": [ { "option": "VOTE_OPTION_YES", "weight": "" } ] } ``` Response `MsgVoteWeightedResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.base.v1beta1.Coin Coin defines a token with a denomination and an amount. NOTE: The amount field is an Int which implements the custom method signatures required by gogoproto. | Field | Type | Description | | -------- | -------- | ---------------------- | | `denom` | `string` | | | `amount` | `string` | Encoded as cosmos.Int. | ### cosmos.gov.v1.Deposit Deposit defines an amount deposited by an account address to an active proposal. | Field | Type | Description | | ------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | amount to be deposited by depositor. | ### cosmos.gov.v1.DepositParams DepositParams defines the params for deposits on governance proposals. | Field | Type | Description | | -------------------- | -------------------------------------- | ---------------------------------------------------------------------------------- | | `min_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | Minimum deposit for a proposal to enter voting period. | | `max_deposit_period` | `Duration` | Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months. | ### cosmos.gov.v1.Params Params defines the parameters for the x/gov module. | Field | Type | Description | | ------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `min_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | Minimum deposit for a proposal to enter voting period. | | `max_deposit_period` | `Duration` | Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months. | | `voting_period` | `Duration` | Duration of the voting period. | | `quorum` | `string` | Minimum percentage of total stake needed to vote for a result to be considered valid. Encoded as cosmos.Dec, a decimal string. | | `threshold` | `string` | Minimum proportion of Yes votes for proposal to pass. Default value: 0.5. Encoded as cosmos.Dec, a decimal string. | | `veto_threshold` | `string` | Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Default value: 1/3. Encoded as cosmos.Dec, a decimal string. | | `min_initial_deposit_ratio` | `string` | The ratio representing the proportion of the deposit value that must be paid at proposal submission. Encoded as cosmos.Dec, a decimal string. | | `proposal_cancel_ratio` | `string` | The cancel ratio which will not be returned back to the depositors when a proposal is cancelled. Encoded as cosmos.Dec, a decimal string. | | `proposal_cancel_dest` | `string` | The address which will receive (proposal\_cancel\_ratio \* deposit) proposal deposits. If empty, the (proposal\_cancel\_ratio \* deposit) proposal deposits will be burned. Encoded as cosmos.AddressString. | | `expedited_voting_period` | `Duration` | Duration of the voting period of an expedited proposal. | | `expedited_threshold` | `string` | Minimum proportion of Yes votes for proposal to pass. Default value: 0.67. Encoded as cosmos.Dec, a decimal string. | | `expedited_min_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | Minimum expedited deposit for a proposal to enter voting period. | | `burn_vote_quorum` | `bool` | burn deposits if a proposal does not meet quorum | | `burn_proposal_deposit_prevote` | `bool` | burn deposits if the proposal does not enter voting period | | `burn_vote_veto` | `bool` | burn deposits if quorum with vote type no\_veto is met | | `min_deposit_ratio` | `string` | The ratio representing the proportion of the deposit value minimum that must be met when making a deposit. Default value: 0.01. Meaning that for a chain with a min\_deposit of 100stake, a deposit of 1stake would be required. Encoded as cosmos.Dec, a decimal string. | ### cosmos.gov.v1.Proposal Proposal defines the core field members of a governance proposal. | Field | Type | Description | | -------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `uint64` | id defines the unique id of the proposal. | | `messages` | `Any`\[] | messages are the arbitrary messages to be executed if the proposal passes. | | `status` | [`ProposalStatus`](#cosmos-gov-v1-proposalstatus) | status defines the proposal status. | | `final_tally_result` | [`TallyResult`](#cosmos-gov-v1-tallyresult) | final\_tally\_result is the final tally result of the proposal. When querying a proposal via gRPC, this field is not populated until the proposal's voting period has ended. | | `submit_time` | `Timestamp` | submit\_time is the time of proposal submission. | | `deposit_end_time` | `Timestamp` | deposit\_end\_time is the end time for deposition. | | `total_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | total\_deposit is the total deposit on the proposal. | | `voting_start_time` | `Timestamp` | voting\_start\_time is the starting time to vote on a proposal. | | `voting_end_time` | `Timestamp` | voting\_end\_time is the end time of voting on a proposal. | | `metadata` | `string` | metadata is any arbitrary metadata attached to the proposal. the recommended format of the metadata is to be found here: [https://docs.cosmos.network/v0.47/modules/gov#proposal-3](https://docs.cosmos.network/v0.47/modules/gov#proposal-3) | | `title` | `string` | title is the title of the proposal | | `summary` | `string` | summary is a short summary of the proposal | | `proposer` | `string` | proposer is the address of the proposal submitter Encoded as cosmos.AddressString. | | `expedited` | `bool` | expedited defines if the proposal is expedited | | `failed_reason` | `string` | failed\_reason defines the reason why the proposal failed | ### cosmos.gov.v1.ProposalStatus Written as a quoted string in JSON. | Value | | | -------------------------------- | ------------------------------- | | `PROPOSAL_STATUS_UNSPECIFIED` | Zero value. Handlers reject it. | | `PROPOSAL_STATUS_DEPOSIT_PERIOD` | | | `PROPOSAL_STATUS_VOTING_PERIOD` | | | `PROPOSAL_STATUS_PASSED` | | | `PROPOSAL_STATUS_REJECTED` | | | `PROPOSAL_STATUS_FAILED` | | ### cosmos.gov.v1.TallyParams TallyParams defines the params for tallying votes on governance proposals. | Field | Type | Description | | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `quorum` | `string` | Minimum percentage of total stake needed to vote for a result to be considered valid. Encoded as cosmos.Dec, a decimal string. | | `threshold` | `string` | Minimum proportion of Yes votes for proposal to pass. Default value: 0.5. Encoded as cosmos.Dec, a decimal string. | | `veto_threshold` | `string` | Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Default value: 1/3. Encoded as cosmos.Dec, a decimal string. | ### cosmos.gov.v1.TallyResult TallyResult defines a standard tally for a governance proposal. | Field | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------- | | `yes_count` | `string` | yes\_count is the number of yes votes on a proposal. Encoded as cosmos.Int. | | `abstain_count` | `string` | abstain\_count is the number of abstain votes on a proposal. Encoded as cosmos.Int. | | `no_count` | `string` | no\_count is the number of no votes on a proposal. Encoded as cosmos.Int. | | `no_with_veto_count` | `string` | no\_with\_veto\_count is the number of no with veto votes on a proposal. Encoded as cosmos.Int. | ### cosmos.gov.v1.Vote Vote defines a vote on a governance proposal. A Vote consists of a proposal ID, the voter, and the vote option. | Field | Type | Description | | ------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter is the voter address of the proposal. Encoded as cosmos.AddressString. | | `options` | [`WeightedVoteOption`](#cosmos-gov-v1-weightedvoteoption)\[] | options is the weighted vote options. | | `metadata` | `string` | metadata is any arbitrary metadata attached to the vote. the recommended format of the metadata is to be found here: [https://docs.cosmos.network/v0.47/modules/gov#vote-5](https://docs.cosmos.network/v0.47/modules/gov#vote-5) | ### cosmos.gov.v1.VoteOption Written as a quoted string in JSON. | Value | | | -------------------------- | ------------------------------- | | `VOTE_OPTION_UNSPECIFIED` | Zero value. Handlers reject it. | | `VOTE_OPTION_YES` | | | `VOTE_OPTION_ABSTAIN` | | | `VOTE_OPTION_NO` | | | `VOTE_OPTION_NO_WITH_VETO` | | ### cosmos.gov.v1.VotingParams VotingParams defines the params for voting on governance proposals. | Field | Type | Description | | --------------- | ---------- | ------------------------------ | | `voting_period` | `Duration` | Duration of the voting period. | ### cosmos.gov.v1.WeightedVoteOption WeightedVoteOption defines a unit of vote for vote split. | Field | Type | Description | | -------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------- | | `option` | [`VoteOption`](#cosmos-gov-v1-voteoption) | option defines the valid vote options, it must not contain duplicate vote options. | | `weight` | `string` | weight is the vote weight associated with the vote option. Encoded as cosmos.Dec, a decimal string. | ### cosmos.gov.v1beta1.Deposit Deposit defines an amount deposited by an account address to an active proposal. | Field | Type | Description | | ------------- | -------------------------------------- | -------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `depositor` | `string` | depositor defines the deposit addresses from the proposals. Encoded as cosmos.AddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | amount to be deposited by depositor. | ### cosmos.gov.v1beta1.DepositParams DepositParams defines the params for deposits on governance proposals. | Field | Type | Description | | -------------------- | -------------------------------------- | ---------------------------------------------------------------------------------- | | `min_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | Minimum deposit for a proposal to enter voting period. | | `max_deposit_period` | `Duration` | Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months. | ### cosmos.gov.v1beta1.Proposal Proposal defines the core field members of a governance proposal. | Field | Type | Description | | -------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `content` | `Any` | content is the proposal's content. One of: `/cosmos.distribution.v1beta1.CommunityPoolSpendProposal`, `/cosmos.distribution.v1beta1.CommunityPoolSpendProposalWithDeposit`, `/cosmos.gov.v1beta1.TextProposal`, `/cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal`, `/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal`. | | `status` | [`ProposalStatus`](#cosmos-gov-v1beta1-proposalstatus) | status defines the proposal status. | | `final_tally_result` | [`TallyResult`](#cosmos-gov-v1beta1-tallyresult) | final\_tally\_result is the final tally result of the proposal. When querying a proposal via gRPC, this field is not populated until the proposal's voting period has ended. | | `submit_time` | `Timestamp` | submit\_time is the time of proposal submission. | | `deposit_end_time` | `Timestamp` | deposit\_end\_time is the end time for deposition. | | `total_deposit` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | total\_deposit is the total deposit on the proposal. | | `voting_start_time` | `Timestamp` | voting\_start\_time is the starting time to vote on a proposal. | | `voting_end_time` | `Timestamp` | voting\_end\_time is the end time of voting on a proposal. | ### cosmos.gov.v1beta1.ProposalStatus Written as a quoted string in JSON. | Value | | | -------------------------------- | ------------------------------- | | `PROPOSAL_STATUS_UNSPECIFIED` | Zero value. Handlers reject it. | | `PROPOSAL_STATUS_DEPOSIT_PERIOD` | | | `PROPOSAL_STATUS_VOTING_PERIOD` | | | `PROPOSAL_STATUS_PASSED` | | | `PROPOSAL_STATUS_REJECTED` | | | `PROPOSAL_STATUS_FAILED` | | ### cosmos.gov.v1beta1.TallyParams TallyParams defines the params for tallying votes on governance proposals. | Field | Type | Description | | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `quorum` | `bytes` | Minimum percentage of total stake needed to vote for a result to be considered valid. Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | | `threshold` | `bytes` | Minimum proportion of Yes votes for proposal to pass. Default value: 0.5. Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | | `veto_threshold` | `bytes` | Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Default value: 1/3. Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | ### cosmos.gov.v1beta1.TallyResult TallyResult defines a standard tally for a governance proposal. | Field | Type | Description | | -------------- | -------- | ---------------------------------------------------------------------------------------- | | `yes` | `string` | yes is the number of yes votes on a proposal. Encoded as cosmos.Int. | | `abstain` | `string` | abstain is the number of abstain votes on a proposal. Encoded as cosmos.Int. | | `no` | `string` | no is the number of no votes on a proposal. Encoded as cosmos.Int. | | `no_with_veto` | `string` | no\_with\_veto is the number of no with veto votes on a proposal. Encoded as cosmos.Int. | ### cosmos.gov.v1beta1.Vote Vote defines a vote on a governance proposal. A Vote consists of a proposal ID, the voter, and the vote option. | Field | Type | Description | | ------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `proposal_id` | `uint64` | proposal\_id defines the unique id of the proposal. | | `voter` | `string` | voter is the voter address of the proposal. Encoded as cosmos.AddressString. | | `option` | [`VoteOption`](#cosmos-gov-v1beta1-voteoption) | Deprecated: Prefer to use `options` instead. This field is set in queries if and only if `len(options) == 1` and that option has weight 1. In all other cases, this field will default to VOTE\_OPTION\_UNSPECIFIED. Deprecated. | | `options` | [`WeightedVoteOption`](#cosmos-gov-v1beta1-weightedvoteoption)\[] | options is the weighted vote options. | ### cosmos.gov.v1beta1.VoteOption Written as a quoted string in JSON. | Value | | | -------------------------- | ------------------------------- | | `VOTE_OPTION_UNSPECIFIED` | Zero value. Handlers reject it. | | `VOTE_OPTION_YES` | | | `VOTE_OPTION_ABSTAIN` | | | `VOTE_OPTION_NO` | | | `VOTE_OPTION_NO_WITH_VETO` | | ### cosmos.gov.v1beta1.VotingParams VotingParams defines the params for voting on governance proposals. | Field | Type | Description | | --------------- | ---------- | ------------------------------ | | `voting_period` | `Duration` | Duration of the voting period. | ### cosmos.gov.v1beta1.WeightedVoteOption WeightedVoteOption defines a unit of vote for vote split. | Field | Type | Description | | -------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `option` | [`VoteOption`](#cosmos-gov-v1beta1-voteoption) | option defines the valid vote options, it must not contain duplicate vote options. | | `weight` | `string` | weight is the vote weight associated with the vote option. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | # gRPC Services Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/index How Cosmos SDK gRPC services are named, and how to list and call them against a running node. Each module defines its API in protobuf. A `service` block groups related methods, and each `rpc` inside it declares one method with exactly one request message and one response message. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package cosmos.bank.v1beta1; service Query { rpc AllBalances(QueryAllBalancesRequest) returns (QueryAllBalancesResponse) { option (google.api.http).get = "/cosmos/bank/v1beta1/balances/{address}"; } } ``` Three parts combine into the name a node answers to: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1 . Query / AllBalances package service method ``` The `google.api.http` option on the method is what produces the REST route on port 1317. Methods without that option are reachable over gRPC only. Modules define `Query` services and `Msg` services: * `Query` services are registered into the gRPC query router and served on port 9090. * `Msg` services are registered into the message service router, which is consulted only while a transaction is being delivered. ## Scalar encodings Some fields use SDK encoding conventions layered on protobuf `string` or `bytes` types. | Annotation | Meaning | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `cosmos.Int` | Arbitrary-precision integer encoded as a base-10 string. `"8821829649"` means 8,821,829,649. | | `cosmos.Dec` | Fixed-point decimal. Its encoding depends on the codec. | | `cosmos.AddressString` | Bech32 account address, such as `cosmos1...` on the Cosmos Hub. | | `cosmos.ValidatorAddressString` | Bech32 validator operator address, such as `cosmosvaloper1...`. | | `cosmos.ConsensusAddressString` | Bech32 consensus address, such as `cosmosvalcons1...`. It is derived from the consensus key and differs from the operator address. | Account, operator, and consensus addresses belong to separate address spaces. Using the wrong type fails with `hrp does not match bech32 prefix`. Prefixes are chain-specific; query `cosmos.auth.v1beta1.Query/Bech32Prefix` to find them. ### `cosmos.Dec` encodings The value 0.05 appears differently depending on where it is read or written: | Where | Form | Example | | -------------------------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------- | | Write through the CLI or `POST /cosmos/tx/v1beta1/encode` | Decimal string | `"0.05"` | | Write with `grpcurl`, generated clients, or `Service/TxEncode` | Integer scaled by 10^18 | `"50000000000000000"` | | Read through gRPC | Scaled integer, or its base64 encoding for `bytes` fields | `"50000000000000000"` or `"MTAwMDAwMDAwMDAwMDAwMDAw"` | | Read through REST | Decimal string | `"0.050000000000000000"` | Field tables identify each field’s read encoding. When writing, transaction JSON uses decimal strings and protobuf JSON uses scaled integers. The wrong encoding may produce a valid but incorrect value. For example, `"50000000000000000"` in transaction JSON means fifty quadrillion, while the same value returned by `staking` as a scaled integer means 5%. ## Other encodings JSON encodes `bytes` fields as base64. Hex input may decode successfully as base64 and produce the wrong value. A response’s `pagination.next_key` is already base64. Pass it back unchanged over gRPC. In a REST query string, percent-encode it so `+` is not interpreted as a space. An `Any` field contains an `@type` discriminator and the concrete message’s fields. For example, `cosmos.auth.v1beta1.Query/Account` may return a `BaseAccount`, `ModuleAccount`, or vesting account. Protobuf JSON usually omits fields with default values. A successful query with no matches may therefore return `{}` instead of an empty list. ## List available services These pages cover the standard modules for one SDK version. Each chain registers its own services. When enabled, gRPC reflection gives the authoritative list: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # List every service grpcurl -plaintext localhost:9090 list # Describe one method grpcurl -plaintext localhost:9090 describe \ cosmos.bank.v1beta1.Query.AllBalances ``` If reflection is disabled, provide local proto files with `-import-path` and `-proto`. ## Call a method Pass the request as JSON with `-d`. Fields accept either protobuf names such as `resolve_denom` or JSON names such as `resolveDenom`. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address": "cosmos1..."}' \ localhost:9090 cosmos.bank.v1beta1.Query/AllBalances ``` ## Pagination List queries accept and return a `pagination` field. Use either an offset or a key: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"address": "cosmos1...", "pagination": {"limit": 10, "count_total": true}}' \ localhost:9090 cosmos.bank.v1beta1.Query/AllBalances ``` Pass the returned `pagination.next_key` as `pagination.key` to request the next page. Key-based pagination is more efficient for large result sets. # Mint Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/mint gRPC queries and transaction messages defined by the mint module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### AnnualProvisions AnnualProvisions current minting annual provisions value. | | | | ---- | -------------------------------------------- | | gRPC | `cosmos.mint.v1beta1.Query/AnnualProvisions` | | REST | `GET /cosmos/mint/v1beta1/annual_provisions` | Request `QueryAnnualProvisionsRequest`: This message has no fields. Response `QueryAnnualProvisionsResponse`: | Field | Type | Description | | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `annual_provisions` | `bytes` | annual\_provisions is the current minting annual provisions value. Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/AnnualProvisions ``` ### Inflation Inflation returns the current minting inflation value. | | | | ---- | ------------------------------------- | | gRPC | `cosmos.mint.v1beta1.Query/Inflation` | | REST | `GET /cosmos/mint/v1beta1/inflation` | Request `QueryInflationRequest`: This message has no fields. Response `QueryInflationResponse`: | Field | Type | Description | | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `inflation` | `bytes` | inflation is the current minting inflation value. Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/Inflation ``` ### Params Params returns the total set of minting parameters. | | | | ---- | ---------------------------------- | | gRPC | `cosmos.mint.v1beta1.Query/Params` | | REST | `GET /cosmos/mint/v1beta1/params` | Request `QueryParamsRequest`: This message has no fields. Response `QueryParamsResponse`: | Field | Type | Description | | -------- | --------------------------------------- | -------------------------------------------- | | `params` | [`Params`](#cosmos-mint-v1beta1-params) | params defines the parameters of the module. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.mint.v1beta1.Query/Params ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### UpdateParams UpdateParams defines a governance operation for updating the x/mint module parameters. The authority is defaults to the x/gov module account. | | | | ---------- | -------------------------------------- | | Type URL | `/cosmos.mint.v1beta1.MsgUpdateParams` | | Handler | `cosmos.mint.v1beta1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/x/mint/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `params` | [`Params`](#cosmos-mint-v1beta1-params) | params defines the x/mint parameters to update. NOTE: All parameters must be supplied. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.mint.v1beta1.MsgUpdateParams", "authority": "", "params": { "mint_denom": "", "inflation_rate_change": "", "inflation_max": "", "inflation_min": "", "goal_bonded": "", "blocks_per_year": "1", "max_supply": "" } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.mint.v1beta1.Params Params defines the parameters for the x/mint module. | Field | Type | Description | | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | | `mint_denom` | `string` | type of coin to mint | | `inflation_rate_change` | `string` | maximum annual change in inflation rate Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `inflation_max` | `string` | maximum inflation rate Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `inflation_min` | `string` | minimum inflation rate Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `goal_bonded` | `string` | goal of percent bonded atoms Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `blocks_per_year` | `uint64` | expected blocks per year | | `max_supply` | `string` | maximum supply for the token. A value of "0" indicates an unlimited (infinite) maximum supply. Encoded as cosmos.Int. | # Reflection Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/reflection gRPC queries and transaction messages defined by the reflection module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### FileDescriptors FileDescriptors queries all the file descriptors in the app in order to enable easier generation of dynamic clients. | | | | ---- | -------------------------------------------------------- | | gRPC | `cosmos.reflection.v1.ReflectionService/FileDescriptors` | Request `FileDescriptorsRequest`: This message has no fields. Response `FileDescriptorsResponse`: | Field | Type | Description | | ------- | ------------------------ | ------------------------------ | | `files` | `FileDescriptorProto`\[] | files is the file descriptors. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.reflection.v1.ReflectionService/FileDescriptors ``` # Slashing Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/slashing gRPC queries and transaction messages defined by the slashing module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### Params Params queries the parameters of slashing module | | | | ---- | -------------------------------------- | | gRPC | `cosmos.slashing.v1beta1.Query/Params` | | REST | `GET /cosmos/slashing/v1beta1/params` | Request `QueryParamsRequest`: This message has no fields. Response `QueryParamsResponse`: | Field | Type | Description | | -------- | ------------------------------------------- | ----------- | | `params` | [`Params`](#cosmos-slashing-v1beta1-params) | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.slashing.v1beta1.Query/Params ``` ### SigningInfo SigningInfo queries the signing info of given cons address | | | | ---- | ---------------------------------------------------------- | | gRPC | `cosmos.slashing.v1beta1.Query/SigningInfo` | | REST | `GET /cosmos/slashing/v1beta1/signing_infos/{consAddress}` | Request `QuerySigningInfoRequest`: | Field | Type | Description | | -------------- | -------- | ----------------------------------------------------------------------------------------------- | | `cons_address` | `string` | cons\_address is the address to query signing info of Encoded as cosmos.ConsensusAddressString. | Response `QuerySigningInfoResponse`: | Field | Type | Description | | ------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------- | | `val_signing_info` | [`ValidatorSigningInfo`](#cosmos-slashing-v1beta1-validatorsigninginfo) | val\_signing\_info is the signing info of requested val cons address | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"cons_address":""}' \ localhost:9090 cosmos.slashing.v1beta1.Query/SigningInfo ``` ### SigningInfos SigningInfos queries signing info of all validators | | | | ---- | -------------------------------------------- | | gRPC | `cosmos.slashing.v1beta1.Query/SigningInfos` | | REST | `GET /cosmos/slashing/v1beta1/signing_infos` | Request `QuerySigningInfosRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ----------- | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | | Response `QuerySigningInfosResponse`: | Field | Type | Description | | ------------ | -------------------------------------------------------------------------- | ------------------------------------------ | | `info` | [`ValidatorSigningInfo`](#cosmos-slashing-v1beta1-validatorsigninginfo)\[] | info is the signing info of all validators | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"pagination":{"limit":"1"}}' \ localhost:9090 cosmos.slashing.v1beta1.Query/SigningInfos ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### Unjail Unjail defines a method for unjailing a jailed validator, thus returning them into the bonded validator set, so they can begin receiving provisions and rewards again. | | | | ---------- | ------------------------------------ | | Type URL | `/cosmos.slashing.v1beta1.MsgUnjail` | | Handler | `cosmos.slashing.v1beta1.Msg/Unjail` | | Signer | `validator_addr` | | Amino name | `cosmos-sdk/MsgUnjail` | | Field | Type | Description | | ---------------- | -------- | ----------------------------------------- | | `validator_addr` | `string` | Encoded as cosmos.ValidatorAddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.slashing.v1beta1.MsgUnjail", "validator_addr": "" } ``` Response `MsgUnjailResponse`: This message has no fields. ### UpdateParams UpdateParams defines a governance operation for updating the x/slashing module parameters. The authority defaults to the x/gov module account. | | | | ---------- | ------------------------------------------ | | Type URL | `/cosmos.slashing.v1beta1.MsgUpdateParams` | | Handler | `cosmos.slashing.v1beta1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/x/slashing/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `params` | [`Params`](#cosmos-slashing-v1beta1-params) | params defines the x/slashing parameters to update. NOTE: All parameters must be supplied. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.slashing.v1beta1.MsgUpdateParams", "authority": "", "params": { "signed_blocks_window": "1", "min_signed_per_window": "0.05", "downtime_jail_duration": "0s", "slash_fraction_double_sign": "0.05", "slash_fraction_downtime": "0.05" } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.slashing.v1beta1.Params Params represents the parameters used for by the slashing module. | Field | Type | Description | | ---------------------------- | ---------- | ------------------------------------------------------------------------------------------ | | `signed_blocks_window` | `int64` | | | `min_signed_per_window` | `bytes` | Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | | `downtime_jail_duration` | `Duration` | | | `slash_fraction_double_sign` | `bytes` | Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | | `slash_fraction_downtime` | `bytes` | Encoded as cosmos.Dec, read back as base64 of an integer string scaled by 10^18 over gRPC. | ### cosmos.slashing.v1beta1.ValidatorSigningInfo ValidatorSigningInfo defines a validator's signing info for monitoring their liveness activity. | Field | Type | Description | | ----------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | `string` | Encoded as cosmos.ConsensusAddressString. | | `start_height` | `int64` | Height at which validator was first a candidate OR was un-jailed | | `index_offset` | `int64` | Index which is incremented every time a validator is bonded in a block and *may* have signed a pre-commit or not. This in conjunction with the signed\_blocks\_window param determines the index in the missed block bitmap. | | `jailed_until` | `Timestamp` | Timestamp until which the validator is jailed due to liveness downtime. | | `tombstoned` | `bool` | Whether or not a validator has been tombstoned (killed out of validator set). It is set once the validator commits an equivocation or for any other configured misbehavior. | | `missed_blocks_counter` | `int64` | A counter of missed (unsigned) blocks. It is used to avoid unnecessary reads in the missed block bitmap. | # API reference Source: https://docs.cosmos.network/sdk/latest/api-reference/index The interfaces exposed by a Cosmos SDK node, how they relate, and what this reference covers. Cosmos SDK modules define their queries and transaction messages in protobuf. A node exposes them through gRPC and REST, while the CLI provides commands for using them. CometBFT exposes a separate API for consensus and node data. This section documents these interfaces. For how applications register them, see [CLI, gRPC, and REST API](/sdk/latest/learn/concepts/cli-grpc-rest). ## Interfaces | Interface | Default address | Default | Purpose | | -------------------------------------------------------- | ----------------- | -------- | -------------------------------------------------------------------------------------- | | [gRPC](/sdk/latest/api-reference/grpc/index) | `localhost:9090` | Enabled | Query application state and access supporting services | | [REST](/sdk/latest/api-reference/rest/bank/allbalances) | `localhost:1317` | Disabled | Call gRPC methods through HTTP and JSON | | [CometBFT RPC](/cometbft/latest/api-reference/rpc/index) | `127.0.0.1:26657` | Enabled | Query blocks, validators, and the mempool; broadcast transactions; subscribe to events | | [CLI](/sdk/latest/learn/concepts/cli-grpc-rest#cli) | n/a | n/a | Query state and build, sign, and broadcast transactions | ## How they relate Modules usually define two protobuf services: * A `Query` service for reading application state * A `Msg` service describing the state changes transactions can request ### Queries Query methods are callable through gRPC on port 9090. Methods with a `google.api.http` binding are also available through REST on port 1317. For example, these calls reach the same query handler: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.bank.v1beta1.Query/AllBalances GET /cosmos/bank/v1beta1/balances/{address} ``` Without an HTTP binding, a method is available only through gRPC. ### Transaction messages `Msg` methods are not callable endpoints. They define messages that are encoded into transactions, signed, and broadcast through a transaction service: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos.tx.v1beta1.Service/BroadcastTx gRPC POST /cosmos/tx/v1beta1/txs REST broadcast_tx_sync CometBFT RPC ``` ### CLI The CLI is a client. Query commands call the application’s query services. Transaction commands construct and sign module messages, then broadcast the resulting transaction. See [Using the CLI](/sdk/latest/node/interact-node#using-the-cli) for worked examples, and [CLI](/sdk/latest/learn/concepts/cli-grpc-rest#cli) for how it fits with the other interfaces. Most commands are not written by hand: [`autocli`](/sdk/latest/guides/tooling/autocli) generates one per gRPC service method, which is why a command and a `grpcurl` call usually take the same arguments. Examples in this reference use `simd`, but each chain normally provides its own application-specific binary. ### CometBFT RPC CometBFT RPC is separate from the application APIs. It belongs to the consensus engine beneath the Cosmos SDK and exposes blocks, validators, consensus data, the mempool, transaction broadcasting, and event subscriptions. See the [CometBFT RPC reference](/cometbft/latest/api-reference/rpc/index). ## What this section covers | Page | Contents | | -------------------------------------------------------------- | ---------------------------------------------------------------- | | [gRPC services](/sdk/latest/api-reference/grpc/index) | Service names, field encodings, reflection, and pagination | | REST | Generated OpenAPI documentation with a playground for each route | | [Sending transactions](/sdk/latest/api-reference/transactions) | How to build, sign, and broadcast transactions | Each module page includes both kinds of declaration. For example, the `bank` page documents `Query/AllBalances` and `MsgSend`. To list the gRPC services registered by a running node: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 list ``` ## Enable the interfaces gRPC is enabled by default. Enable REST in `app.toml`: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [api] enable = true address = "tcp://localhost:1317" # Serve the generated OpenAPI document at /swagger. swagger = true [grpc] enable = true address = "localhost:9090" ``` Configure CometBFT RPC in `config.toml`: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [rpc] laddr = "tcp://127.0.0.1:26657" ``` # Sending Transactions Source: https://docs.cosmos.network/sdk/latest/api-reference/transactions The envelope around a transaction message, and the three steps that put it on chain. The [gRPC Services](/sdk/latest/api-reference/grpc/index) module pages give each transaction message its fields, its signer, and its JSON body. This page covers the envelope those bodies go into. For the model behind messages and transactions, see [Transactions, Messages, and Queries](/sdk/latest/learn/concepts/transactions). ## The envelope A transaction is a wrapper around one or more messages and includes the information needed to authorize and pay for them: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "body": { "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1...", "to_address": "cosmos1...", "amount": [{ "denom": "uatom", "amount": "1000000" }] } ], "memo": "", "timeout_height": "0", "unordered": false, "timeout_timestamp": null, "extension_options": [], "non_critical_extension_options": [] }, "auth_info": { "signer_infos": [], "fee": { "amount": [{ "denom": "uatom", "amount": "5000" }], "gas_limit": "200000", "payer": "", "granter": "" }, "tip": null }, "signatures": [] } ``` The `messages` array holds exactly what a module page shows under In a transaction. The `@type` field is the type URL, and it selects the handler. Everything else is envelope, and defaults are correct unless stated otherwise: `payer` and `granter` apply to fee grants, `unordered` and `timeout_timestamp` to unordered transactions. Several messages can go in one transaction. They execute in order and atomically. ## The three steps Building, signing, and broadcasting are separate operations. Separating them is what allows offline signing. The commands below are the shortest path; [Generating, Signing and Broadcasting Transactions](/sdk/latest/node/txs) covers multisig, offline signing, and the same flow in Go, gRPC, REST, and CosmJS. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # 1. Build simd tx bank send mykey cosmos1recipient... 1000000uatom \ --chain-id cosmoshub-4 --node https://your-rpc-endpoint:443 \ --gas auto --gas-adjustment 1.5 --gas-prices 0.005uatom \ --generate-only > unsigned.json # 2. Sign simd tx sign unsigned.json --from mykey \ --chain-id cosmoshub-4 --node https://your-rpc-endpoint:443 \ --output-document signed.json # 3. Broadcast simd tx broadcast signed.json --broadcast-mode sync ``` Signing covers the chain ID, account number, and sequence, which is what binds a signature to one chain and one use. Given a node, `sign` fetches the account number and sequence itself; offline signing supplies them with `--offline --account-number --sequence`. The key must control the address in the message's signer field. The module pages name that field for every message. See [Setting up the keyring](/sdk/latest/node/keyring) for managing the keys these commands sign with. Broadcasting returns a transaction hash, not a result. Query for it: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query tx ``` A `code` of 0 is success. For what gas measures and how the limit and price above are applied, see [Execution Context, Gas, and Events](/sdk/latest/learn/concepts/context-gas-events). The API surfaces broadcast directly through `cosmos.tx.v1beta1.Service/BroadcastTx` on gRPC or `POST /cosmos/tx/v1beta1/txs` on REST. Both take the signed transaction as bytes, so building and signing still happen first. ## Governance-gated messages Some messages take `authority` as their signer, meaning the governance module account, which no one holds a key for. They execute only through a passed governance proposal, wrapped in `MsgSubmitProposal`. See [Proposal submission](/sdk/latest/modules/gov/README#proposal-submission) for the deposit and voting periods a proposal has to clear. The module pages flag every one. `MsgUpdateParams` on each module is the common case. The address the message needs: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"name":"gov"}' localhost:9090 \ cosmos.auth.v1beta1.Query/ModuleAccountByName ``` ## Related The module pages under [gRPC Services](/sdk/latest/api-reference/grpc/index) carry the message list, field tables, signer, and JSON body for every transaction message. # API Reference Source: https://docs.cosmos.network/sdk/latest/enterprise/group/api Complete API reference for Group module queries and messages # Group Module API Reference ## Overview The Group module provides a comprehensive API for managing on-chain multisig groups and collective decision-making. **Package:** `cosmos.group.v1` **Go Import:** `github.com/cosmos/cosmos-sdk/enterprise/group/x/group` *** ## Data Types ### GroupInfo Represents a group on-chain. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GroupInfo { uint64 id = 1; string admin = 2; bytes metadata = 3; uint64 version = 4; string total_weight = 5; google.protobuf.Timestamp created_at = 6; } ``` **Fields:** * `id` (uint64): Unique group identifier, auto-assigned on creation * `admin` (string): Cosmos SDK address of the group administrator * `metadata` (bytes): Optional group metadata * `version` (uint64): Incremented on every group update; used to detect stale proposals * `total_weight` (string): Sum of all member weights * `created_at` (Timestamp): Block time when the group was created *** ### GroupMember Represents a member's relationship to a group. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GroupMember { uint64 group_id = 1; Member member = 2; } message Member { string address = 1; string weight = 2; bytes metadata = 3; google.protobuf.Timestamp added_at = 4; } ``` **Fields:** * `address` (string): Cosmos SDK address of the member * `weight` (string): Voting weight. Set to `"0"` to remove a member. * `metadata` (bytes): Optional member metadata * `added_at` (Timestamp): Block time when the member was added *** ### GroupPolicyInfo Represents a group policy account. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GroupPolicyInfo { string address = 1; uint64 group_id = 2; string admin = 3; bytes metadata = 4; uint64 version = 5; google.protobuf.Any decision_policy = 6; google.protobuf.Timestamp created_at = 7; } ``` **Fields:** * `address` (string): The group policy's account address (auto-generated) * `group_id` (uint64): The group this policy is associated with * `admin` (string): Address with authority to update the policy * `decision_policy` (Any): The policy's decision logic (threshold or percentage) * `version` (uint64): Incremented on every update; used to detect aborted proposals *** ### Proposal Represents an on-chain proposal submitted to a group policy. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Proposal { uint64 id = 1; string group_policy_address = 2; bytes metadata = 3; repeated string proposers = 4; google.protobuf.Timestamp submit_time = 5; uint64 group_version = 6; uint64 group_policy_version = 7; ProposalStatus status = 8; TallyResult final_tally_result = 9; google.protobuf.Timestamp voting_period_end = 10; ProposalExecutorResult executor_result = 11; repeated google.protobuf.Any messages = 12; string title = 13; string summary = 14; } ``` **ProposalStatus values:** * `PROPOSAL_STATUS_SUBMITTED` - Open for voting * `PROPOSAL_STATUS_ACCEPTED` - Passed; ready for execution * `PROPOSAL_STATUS_REJECTED` - Failed tally * `PROPOSAL_STATUS_ABORTED` - Group or policy updated during voting * `PROPOSAL_STATUS_WITHDRAWN` - Withdrawn by proposer or policy admin **ProposalExecutorResult values:** * `PROPOSAL_EXECUTOR_RESULT_NOT_RUN` * `PROPOSAL_EXECUTOR_RESULT_SUCCESS` * `PROPOSAL_EXECUTOR_RESULT_FAILURE` *** ### TallyResult The accumulated vote counts for a proposal. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message TallyResult { string yes_count = 1; string abstain_count = 2; string no_count = 3; string no_with_veto_count = 4; } ``` *** ## Query API The Query service provides read-only access to Group module state. ### GroupInfo Get information about a group by ID. **gRPC:** `cosmos.group.v1.Query/GroupInfo` **REST:** `GET /cosmos/group/v1/groups/{group_id}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group group-info [group-id] ``` **Example:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group group-info 1 ``` *** ### GroupPolicyInfo Get information about a group policy account. **gRPC:** `cosmos.group.v1.Query/GroupPolicyInfo` **REST:** `GET /cosmos/group/v1/group_policies/{address}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group group-policy-info [group-policy-account] ``` *** ### GroupMembers List all members of a group. **gRPC:** `cosmos.group.v1.Query/GroupMembers` **REST:** `GET /cosmos/group/v1/groups/{group_id}/members` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group group-members [group-id] ``` *** ### GroupsByAdmin List all groups administered by a given address. **gRPC:** `cosmos.group.v1.Query/GroupsByAdmin` **REST:** `GET /cosmos/group/v1/groups/by_admin/{admin}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group groups-by-admin [admin] ``` *** ### GroupPoliciesByGroup List all group policies associated with a group. **gRPC:** `cosmos.group.v1.Query/GroupPoliciesByGroup` **REST:** `GET /cosmos/group/v1/groups/{group_id}/group_policies` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group group-policies-by-group [group-id] ``` *** ### GroupPoliciesByAdmin List all group policies administered by a given address. **gRPC:** `cosmos.group.v1.Query/GroupPoliciesByAdmin` **REST:** `GET /cosmos/group/v1/group_policies/by_admin/{admin}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group group-policies-by-admin [admin] ``` *** ### Proposal Get a proposal by ID. **gRPC:** `cosmos.group.v1.Query/Proposal` **REST:** `GET /cosmos/group/v1/proposals/{proposal_id}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group proposal [proposal-id] ``` *** ### ProposalsByGroupPolicy List all proposals for a given group policy account. **gRPC:** `cosmos.group.v1.Query/ProposalsByGroupPolicy` **REST:** `GET /cosmos/group/v1/proposals/by_group_policy/{address}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group proposals-by-group-policy [group-policy-account] ``` *** ### VoteByProposalVoter Get a specific vote on a proposal. **gRPC:** `cosmos.group.v1.Query/VoteByProposalVoter` **REST:** `GET /cosmos/group/v1/votes/{proposal_id}/{voter}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group vote [proposal-id] [voter] ``` *** ### VotesByProposal List all votes on a proposal. **gRPC:** `cosmos.group.v1.Query/VotesByProposal` **REST:** `GET /cosmos/group/v1/votes/by_proposal/{proposal_id}` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group votes-by-proposal [proposal-id] ``` *** ### TallyResult Get the current tally for a proposal. **gRPC:** `cosmos.group.v1.Query/TallyResult` **REST:** `GET /cosmos/group/v1/proposals/{proposal_id}/tally` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group tally-result [proposal-id] ``` **Example Response:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "tally": { "yes_count": "2", "abstain_count": "0", "no_count": "1", "no_with_veto_count": "0" } } ``` *** ### Groups List all groups on chain. **gRPC:** `cosmos.group.v1.Query/Groups` **REST:** `GET /cosmos/group/v1/groups` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q group groups ``` *** ## Transaction Messages (Msg Service) ### CreateGroup Create a new group with an admin and initial members. **Msg:** `MsgCreateGroup` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group [admin] [metadata] [members-json-file] ``` **Members JSON format:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "members": [ { "address": "cosmos1...", "weight": "1", "metadata": "member description" } ] } ``` **Authorization:** Any address can create a group. **Failure conditions:** * Metadata length exceeds `MaxMetadataLen` * Members have invalid addresses, duplicate entries, or zero weight *** ### UpdateGroupMembers Add, remove, or reweight members in a group. **Msg:** `MsgUpdateGroupMembers` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-members [admin] [group-id] [members-json-file] ``` **Note:** Set a member's weight to `"0"` to remove them from the group. **Authorization:** Must be signed by the group admin. **Failure conditions:** * Signer is not the group admin * Any associated group policy's `Validate()` method fails against the updated member set *** ### UpdateGroupAdmin Transfer group administration to a new address. **Msg:** `MsgUpdateGroupAdmin` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-admin [admin] [group-id] [new-admin] ``` **Authorization:** Must be signed by the current group admin. *** ### UpdateGroupMetadata Update a group's metadata. **Msg:** `MsgUpdateGroupMetadata` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-metadata [admin] [group-id] [metadata] ``` **Authorization:** Must be signed by the group admin. *** ### CreateGroupPolicy Create a new group policy account with a decision policy. **Msg:** `MsgCreateGroupPolicy` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group-policy [admin] [group-id] [metadata] [decision-policy-json] ``` **Threshold policy example:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "2", "windows": { "voting_period": "24h", "min_execution_period": "0s" } } ``` **Percentage policy example:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.group.v1.PercentageDecisionPolicy", "percentage": "0.5", "windows": { "voting_period": "48h", "min_execution_period": "0s" } } ``` **Authorization:** Must be signed by the group admin. **Failure conditions:** * Signer is not the group admin * Metadata length exceeds `MaxMetadataLen` * Decision policy's `Validate()` method fails against the group *** ### CreateGroupWithPolicy Create a group and a group policy in a single transaction. **Msg:** `MsgCreateGroupWithPolicy` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group create-group-with-policy [admin] [group-metadata] [group-policy-metadata] [members-json-file] [decision-policy-json] ``` Set `--group-policy-as-admin` to make the group policy account the group admin (enabling a self-governed group). *** ### UpdateGroupPolicyAdmin Transfer group policy administration to a new address. **Msg:** `MsgUpdateGroupPolicyAdmin` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-admin [admin] [group-policy-account] [new-admin] ``` **Authorization:** Must be signed by the group policy admin. *** ### UpdateGroupPolicyDecisionPolicy Update the decision policy for a group policy account. **Msg:** `MsgUpdateGroupPolicyDecisionPolicy` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-decision-policy [admin] [group-policy-account] [decision-policy-json] ``` **Authorization:** Must be signed by the group policy admin. **Note:** Updating the decision policy aborts any in-flight proposals for that policy. *** ### UpdateGroupPolicyMetadata Update a group policy's metadata. **Msg:** `MsgUpdateGroupPolicyMetadata` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group update-group-policy-metadata [admin] [group-policy-account] [metadata] ``` **Authorization:** Must be signed by the group policy admin. *** ### SubmitProposal Submit a proposal to a group policy account. **Msg:** `MsgSubmitProposal` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group submit-proposal [proposal-json-file] \ --from proposer \ --keyring-backend test ``` **Proposal JSON format:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "group_policy_address": "cosmos1...", "proposers": ["cosmos1..."], "metadata": "proposal description", "title": "My Proposal", "summary": "A brief description of the proposal", "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1...", "to_address": "cosmos1...", "amount": [{"denom": "uatom", "amount": "1000"}] } ], "exec": 0 } ``` Set `"exec": 1` (`EXEC_TRY`) to attempt immediate execution. When using `EXEC_TRY`, proposers are automatically counted as yes votes. **Authorization:** Must be signed by at least one group member. **Failure conditions:** * Metadata, title, or summary length exceeds `MaxMetadataLen` * Proposer is not a group member *** ### WithdrawProposal Withdraw a pending proposal. **Msg:** `MsgWithdrawProposal` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group withdraw-proposal [proposal-id] [group-policy-admin-or-proposer] ``` **Authorization:** Must be signed by a proposer or the group policy admin. **Failure conditions:** * Signer is neither a proposer nor the group policy admin * Proposal is already closed or aborted *** ### Vote Cast a vote on an open proposal. **Msg:** `MsgVote` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group vote [proposal-id] [voter] [vote-option] [metadata] ``` **Vote options:** * `VOTE_OPTION_YES` * `VOTE_OPTION_NO` * `VOTE_OPTION_ABSTAIN` * `VOTE_OPTION_NO_WITH_VETO` Set `--exec 1` to attempt immediate execution after voting. **Authorization:** Must be signed by a group member. **Failure conditions:** * Metadata length exceeds `MaxMetadataLen` * Proposal is no longer in the voting period *** ### Exec Execute an accepted proposal. **Msg:** `MsgExec` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group exec [proposal-id] \ --from executor \ --keyring-backend test ``` **Authorization:** Any address can execute an accepted proposal. **Notes:** * Proposal must be in `ACCEPTED` status * Execution must occur before `MaxExecutionPeriod` after the voting period ends * A failed execution (`PROPOSAL_EXECUTOR_RESULT_FAILURE`) can be retried until expiry *** ### LeaveGroup Remove yourself from a group. **Msg:** `MsgLeaveGroup` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx group leave-group [member-address] [group-id] ``` **Authorization:** Must be signed by the member leaving. **Failure conditions:** * Signer is not a group member * Any associated group policy's `Validate()` method fails against the updated member set *** ## Events The Group module emits the following events: | Event Type | Key | Value | | ---------------------------------------- | --------------------------------------- | --------------------------- | | `cosmos.group.v1.EventCreateGroup` | `group_id` | `{groupId}` | | `cosmos.group.v1.EventUpdateGroup` | `group_id` | `{groupId}` | | `cosmos.group.v1.EventCreateGroupPolicy` | `address` | `{groupPolicyAddress}` | | `cosmos.group.v1.EventUpdateGroupPolicy` | `address` | `{groupPolicyAddress}` | | `cosmos.group.v1.EventCreateProposal` | `proposal_id` | `{proposalId}` | | `cosmos.group.v1.EventWithdrawProposal` | `proposal_id` | `{proposalId}` | | `cosmos.group.v1.EventVote` | `proposal_id` | `{proposalId}` | | `cosmos.group.v1.EventExec` | `proposal_id`, `logs` | `{proposalId}`, `{logs}` | | `cosmos.group.v1.EventLeaveGroup` | `proposal_id`, `address` | `{proposalId}`, `{address}` | | `cosmos.group.v1.EventProposalPruned` | `proposal_id`, `status`, `tally_result` | pruning details | *** ## REST API Endpoints | Method | Endpoint | Description | | ------ | ------------------------------------------------------ | --------------------------- | | GET | `/cosmos/group/v1/groups/{group_id}` | Get group info | | GET | `/cosmos/group/v1/groups/by_admin/{admin}` | List groups by admin | | GET | `/cosmos/group/v1/groups` | List all groups | | GET | `/cosmos/group/v1/groups/{group_id}/members` | List group members | | GET | `/cosmos/group/v1/group_policies/{address}` | Get group policy info | | GET | `/cosmos/group/v1/groups/{group_id}/group_policies` | List policies for a group | | GET | `/cosmos/group/v1/group_policies/by_admin/{admin}` | List policies by admin | | GET | `/cosmos/group/v1/proposals/{proposal_id}` | Get proposal | | GET | `/cosmos/group/v1/proposals/by_group_policy/{address}` | List proposals for a policy | | GET | `/cosmos/group/v1/proposals/{proposal_id}/tally` | Get tally result | | GET | `/cosmos/group/v1/votes/{proposal_id}/{voter}` | Get a specific vote | | GET | `/cosmos/group/v1/votes/by_proposal/{proposal_id}` | List votes for a proposal | *** ## Common Use Cases ### 1. Create a 2-of-3 Multisig Group ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Create the group with 3 members of equal weight simd tx group create-group cosmos1admin "" members.json --from admin # members.json { "members": [ {"address": "cosmos1alice...", "weight": "1"}, {"address": "cosmos1bob...", "weight": "1"}, {"address": "cosmos1carol...", "weight": "1"} ] } # Create a policy requiring 2 of 3 yes votes simd tx group create-group-policy cosmos1admin 1 "" policy.json --from admin # policy.json (threshold = 2) { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "2", "windows": {"voting_period": "72h", "min_execution_period": "0s"} } ``` ### 2. Submit and Execute a Proposal ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Alice submits a proposal simd tx group submit-proposal proposal.json --from alice # Bob and Carol vote yes simd tx group vote 1 cosmos1bob YES "" --from bob simd tx group vote 1 cosmos1carol YES "" --from carol # Anyone executes the accepted proposal simd tx group exec 1 --from alice ``` ### 3. Self-Governing Group (Policy as Admin) ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Create group with policy as its own admin simd tx group create-group-with-policy cosmos1admin "" "" members.json policy.json \ --group-policy-as-admin \ --from admin ``` ### 4. Multiple Policies for Different Actions ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Low-threshold policy for routine actions (1-of-3) simd tx group create-group-policy cosmos1admin 1 "routine" low_policy.json --from admin # High-threshold policy for critical actions (3-of-3) simd tx group create-group-policy cosmos1admin 1 "critical" high_policy.json --from admin ``` *** # Architecture Source: https://docs.cosmos.network/sdk/latest/enterprise/group/architecture System architecture, core concepts, and module integration details for the Group module # Group Module Architecture ## Overview The Group module enables collective decision-making through a proposal-and-vote system. Groups are collections of accounts with associated voting weights. Each group can have one or more policy accounts, each with its own decision policy governing how proposals are accepted or rejected. You can think of it like a dynamic multi-signature account. ## Architecture Diagram Group Module Architecture *The diagram above shows the Group module's actor model, data structures, and proposal lifecycle — from submission through voting to execution.* ## Core Concepts ### Group A group is an aggregation of accounts with associated voting weights. It is not itself an account and does not hold a balance. A group has an **administrator** who can add, remove, and update members. Key points: * The administrator does not need to be a member of the group * A group policy account can itself be the administrator of a group, enabling self-governed groups * Members have weights that determine their relative voting power within proposals ### Group Policy A group policy is an account associated with a group and a decision policy. Group policies are abstracted from groups so that a single group can have **multiple decision policies** for different types of actions. This separation keeps membership consistent across policies while allowing different authorization thresholds for different operations. The recommended pattern is: 1. Create a **master group policy** for a given group 2. Create additional group policies with different decision policies for specific action types 3. Delegate permissions from the master account to sub-accounts using the `x/authz` module ### Decision Policy A decision policy is the mechanism by which group members vote on proposals and the rules that determine whether a proposal passes based on its tally outcome. All decision policies have: * **Minimum Execution Period**: The minimum time after submission before a proposal can be executed. Can be set to `0` to allow immediate execution. * **Maximum Voting Window**: The maximum time after submission during which members can vote. The chain developer also defines an **app-wide maximum execution period** — the window after a proposal's voting period ends during which execution is permitted. #### Threshold Decision Policy A threshold decision policy defines a minimum total weight of yes votes required for a proposal to pass. Abstain and veto votes are treated as no votes. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.group.v1.ThresholdDecisionPolicy", "threshold": "2", "windows": { "voting_period": "24h", "min_execution_period": "0s" } } ``` #### Percentage Decision Policy A percentage decision policy defines acceptance as a minimum percentage of total group weight voting yes. This policy is better suited for groups with dynamic membership, since the percentage threshold remains meaningful as member weights change. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.group.v1.PercentageDecisionPolicy", "percentage": "0.5", "windows": { "voting_period": "24h", "min_execution_period": "0s" } } ``` #### Custom Decision Policies Chain developers can implement custom decision policies by implementing the `DecisionPolicy` interface. This enables encoding arbitrary acceptance logic into a group policy. ### Proposal Any group member can submit a proposal to a group policy account. A proposal consists of: * A list of messages to execute if the proposal is accepted * Optional metadata, title, and summary * An optional `Exec` field to attempt immediate execution on submission #### Voting Members vote with one of four options: * `VOTE_OPTION_YES` * `VOTE_OPTION_NO` * `VOTE_OPTION_ABSTAIN` * `VOTE_OPTION_NO_WITH_VETO` The voting window opens immediately on proposal submission and closes at the time defined by the group policy's decision policy. #### Tallying Tallying occurs when either: 1. A `Msg/Exec`, `Msg/SubmitProposal` (with `TRY_EXEC`), or `Msg/Vote` (with `TRY_EXEC`) triggers an execution attempt 2. The proposal's voting period end is reached during `EndBlock` If the tally passes the decision policy's rules, the proposal is marked `PROPOSAL_STATUS_ACCEPTED`. Otherwise it is marked `PROPOSAL_STATUS_REJECTED`. No further voting is permitted after tallying. #### Executing Proposals Accepted proposals must be executed before `MaxExecutionPeriod` after the voting period ends. Any account (not just group members) can submit a `Msg/Exec` transaction to execute an accepted proposal. When `Exec` is set to `EXEC_TRY` on a submit or vote message, the chain attempts immediate execution. If the proposal doesn't yet pass, it remains open for further votes. #### Withdrawn and Aborted Proposals * **Withdrawn**: Any proposer or the group policy admin can withdraw a proposal before the voting period ends. Withdrawn proposals cannot be executed. A proposal can be withdrawn using `MsgWithdrawProposal` which has an `address` (can be either a proposer or the group policy admin) and a `proposal_id` (which has to be withdrawn). * **Aborted**: If the group or group policy is updated during the voting period, the proposal is automatically marked as `PROPOSAL_STATUS_ABORTED` since the rules it was created under no longer apply. ### Pruning Proposals and votes are automatically pruned to prevent unbounded state growth. **Votes are pruned:** * After a successful tally triggered by `Msg/Exec` or a submit/vote with `TRY_EXEC` * On `EndBlock` immediately after the proposal's voting period ends (including aborted and withdrawn proposals) **Proposals are pruned:** * On `EndBlock` when a withdrawn or aborted proposal's voting period ends * After a successful proposal execution * On `EndBlock` after `voting_period_end + max_execution_period` has passed # Overview Source: https://docs.cosmos.network/sdk/latest/enterprise/group/overview On-Chain Multisig Accounts and Collective Decision-Making The Group module is a Cosmos SDK module that enables on-chain multisig accounts and collective decision-making through configurable voting policies. Any set of accounts can form a named group, attach one or more decision policies to it, and collectively authorize the execution of arbitrary messages through a proposal-and-vote workflow. Unlike chain-wide governance proposals, group proposals are scoped to a specific group policy account — enabling organizations, DAOs, and consortiums to manage their on-chain operations with flexible, programmable authorization rules. The Group module is designed for networks that require: 1. **Multi-Party Authorization:** Groups aggregate accounts with weighted voting power, enabling multiple parties to collectively authorize on-chain actions without relying on a single key. 2. **Flexible Decision Policies:** Each group can have multiple policy accounts with independent threshold or percentage-based rules, allowing different authorization requirements for different types of actions. 3. **Permissioned Execution:** Proposals are only executed when they meet the policy's acceptance criteria, ensuring on-chain actions reflect genuine collective agreement. 4. **DAO and Consortium Support:** Ideal for coordinating on-chain operations across organizations, multisig signers, and governance participants. ## Source Code The source code for the Group module can be found [here](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/enterprise/group). ## Available Documentation This section contains detailed documentation for the Group module. * **[API Reference](/sdk/latest/enterprise/group/api)** - Complete API reference for queries and messages * **[Architecture](/sdk/latest/enterprise/group/architecture)** - System architecture, core concepts, and module integration details ## Licensing The Group module source is published under the [Source Available Evaluation License](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/group/LICENSE), which permits evaluation and testing in non-production environments only. Production or commercial use requires an Enterprise License from Cosmos Labs. To use the Group module in production, contact [sales@cosmoslabs.io](mailto:sales@cosmoslabs.io). # API Reference Source: https://docs.cosmos.network/sdk/latest/enterprise/poa/api Complete API reference for PoA module gRPC queries and transactions # PoA Module API Documentation ## Overview The Proof of Authority (PoA) permissioned consensus module provides a governance mechanism for managing validators in a Cosmos SDK blockchain. Unlike traditional Proof of Stake, PoA allows a designated admin to control validator set membership and voting power distribution. **Package:** `cosmos.poa.v1` **Go Import:** `github.com/cosmos/cosmos-sdk/enterprise/poa/types` *** ## Core Concepts * **Admin Control:** A single admin address has exclusive authority to manage validators and module parameters * **Validator Management:** Create validators, update voting power, and manage the active validator set * **Fee Distribution:** Validators accumulate fees that can be withdrawn by their operators * **Dynamic Updates:** Changes to the validator set are applied without stopping the chain *** ## Data Types ### Validator Represents a validator in the PoA system. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Validator { google.protobuf.Any pub_key = 1; int64 power = 2; ValidatorMetadata metadata = 3; repeated cosmos.base.v1beta1.DecCoin allocated_fees = 4; } ``` **Fields:** * `pub_key` (Any): The validator's consensus public key (typically `/cosmos.crypto.ed25519.PubKey`) * `power` (int64): Voting power for this validator (use `0` to remove a validator) * `metadata` (ValidatorMetadata): Additional validator information * `allocated_fees` (DecCoin\[]): Accumulated fees allocated to this validator **Example:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "pub_key": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "YUzyiqZzKN8BmLbl75gdXfbxQ2QtSYpPSwA85bZ3xuE=" }, "power": "10000", "metadata": { "moniker": "validator-1", "description": "First validator node", "operator_address": "cosmos1x0mm8rws8lm46xay3zyyznzr6lvu5um3kht0x7" }, "allocated_fees": [] } ``` ### ValidatorMetadata Metadata information about a validator. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message ValidatorMetadata { string moniker = 3; string description = 4; string operator_address = 5; } ``` **Fields:** * `moniker` (string): Human-readable name for the validator * `description` (string): Optional description of the validator * `operator_address` (string): Cosmos SDK address that operates this validator ### Params Module parameters. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Params { string admin = 1; } ``` **Fields:** * `admin` (string): Cosmos SDK address with administrative privileges ### ValidatorFees Represents fee allocations for a validator operator. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message ValidatorFees { repeated cosmos.base.v1beta1.DecCoin fees = 1; } ``` **Fields:** * `fees` (DecCoin\[]): List of coins representing allocated fees *** ## Query API The Query service provides read-only access to PoA module state. ### Params Get module parameters. **gRPC:** `cosmos.poa.v1.Query/Params` **REST:** `GET /cosmos/poa/v1/params` **Request:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryParamsRequest {} ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryParamsResponse { Params params = 1; } ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa params ``` **Example Response:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "params": { "admin": "cosmos1x0mm8rws8lm46xay3zyyznzr6lvu5um3kht0x7" } } ``` *** ### Validator Query a single validator by address. **gRPC:** `cosmos.poa.v1.Query/Validator` **REST:** `GET /cosmos/poa/v1/validator/{address}` **Request:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryValidatorRequest { string address = 1; // Consensus or operator address } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryValidatorResponse { Validator validator = 1; } ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa validator
``` **Notes:** * `address` can be either a consensus address or operator address *** ### Validators List all validators in the system. **gRPC:** `cosmos.poa.v1.Query/Validators` **REST:** `GET /cosmos/poa/v1/validators` **Request:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryValidatorsRequest { cosmos.base.query.v1beta1.PageRequest pagination = 2; } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryValidatorsResponse { repeated Validator validators = 1; cosmos.base.query.v1beta1.PageResponse pagination = 2; } ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa validators ``` **Notes:** * Results are always returned in descending order by voting power * Supports pagination for large validator sets **Example Response:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "validators": [ { "pub_key": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "YUzyiqZzKN8BmLbl75gdXfbxQ2QtSYpPSwA85bZ3xuE=" }, "power": "10000", "metadata": { "moniker": "validator-1", "operator_address": "cosmos1..." } } ] } ``` *** ### WithdrawableFees Query fees available for withdrawal by a validator operator. **gRPC:** `cosmos.poa.v1.Query/WithdrawableFees` **REST:** `GET /cosmos/poa/v1/allocated_fees/{operator_address}` **Request:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryWithdrawableFeesRequest { string operator_address = 1; } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryWithdrawableFeesResponse { ValidatorFees fees = 1; } ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa allocated-fees ``` **Example Response:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "fees": { "fees": [ { "denom": "token", "amount": "1000.500000000000000000" } ] } } ``` *** ### TotalPower Get the total voting power across all validators. **gRPC:** `cosmos.poa.v1.Query/TotalPower` **REST:** `GET /cosmos/poa/v1/total_power` **Request:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryTotalPowerRequest {} ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message QueryTotalPowerResponse { int64 total_power = 1; } ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa total-power ``` **Example Response:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "total_power": "50000" } ``` *** ## Transaction Messages (Msg Service) The Msg service handles state-changing operations. ### UpdateParams Update module parameters (admin only). **gRPC:** `cosmos.poa.v1.Msg/UpdateParams` **Message:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgUpdateParams { Params params = 1; string admin = 2; // Signer must be current admin } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgUpdateParamsResponse {} ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa update-params \ --admin \ --from \ --keyring-backend test \ --chain-id \ -y ``` **Authorization:** Only the current admin can execute this transaction. *** ### CreateValidator Create a new validator with zero voting power (operator initiates, admin must activate). **gRPC:** `cosmos.poa.v1.Msg/CreateValidator` **Message:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgCreateValidator { google.protobuf.Any pub_key = 1; string moniker = 2; string description = 3; string operator_address = 4; // Signer } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgCreateValidatorResponse {} ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa create-validator \ --pubkey \ --moniker "my-validator" \ --description "Validator description" \ --from \ --keyring-backend test \ --chain-id \ -y ``` **Authorization:** Any account can create a validator, but it starts with power=0. **Notes:** * The validator will not participate in consensus until the admin updates its power to a non-zero value * Public key must be a valid consensus public key (typically Ed25519) *** ### UpdateValidators Update validator set (admin only). This is the primary mechanism for managing validators. **gRPC:** `cosmos.poa.v1.Msg/UpdateValidators` **Message:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgUpdateValidators { repeated Validator validators = 1; string admin = 2; // Signer must be admin } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgUpdateValidatorsResponse {} ``` **CLI (inline):** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa update-validators \ --validator '{ "pub_key": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "YUzyiqZzKN8BmLbl75gdXfbxQ2QtSYpPSwA85bZ3xuE=" }, "power": 10000 }' \ --validator '{ "pub_key": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "lSR1GEByJtzgiuCevrWgcyBWjhQXjycsuzzIdf56Oa4=" }, "power": 0 }' \ --from account \ --keyring-backend test \ --chain-id \ -y ``` **CLI (from file):** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa update-validators validators.json \ --from account \ --keyring-backend test \ --chain-id \ -y ``` **File Format (validators.json):** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [ { "pub_key": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "YUzyiqZzKN8BmLbl75gdXfbxQ2QtSYpPSwA85bZ3xuE=" }, "power": 10000, "metadata": { "moniker": "validator-1", "description": "First validator", "operator_address": "cosmos1x0mm8rws8lm46xay3zyyznzr6lvu5um3kht0x7" } }, { "pub_key": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "lSR1GEByJtzgiuCevrWgcyBWjhQXjycsuzzIdf56Oa4=" }, "power": 0 } ] ``` **Authorization:** Only the admin can execute this transaction. **Notes:** * Can update multiple validators in a single transaction * Setting `power: 0` removes a validator from the active set * Changes propagate to CometBFT consensus in the next block * Missing fields in metadata are preserved from existing state *** ### WithdrawFees Withdraw accumulated fees to the operator's account. **gRPC:** `cosmos.poa.v1.Msg/WithdrawFees` **Message:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgWithdrawFees { string operator = 1; // Signer } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgWithdrawFeesResponse {} ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa withdraw-fees \ --from \ --keyring-backend test \ --chain-id \ -y ``` **Authorization:** Must be signed by the validator's operator address. **Notes:** * Transfers all accumulated fees to the operator's account * Fees are denominated in the chain's native token(s) *** ### RotateConsPubKey Replace a validator's consensus public key in place. **gRPC:** `cosmos.poa.v1.Msg/RotateConsPubKey` **Message:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgRotateConsPubKey { string sender = 1; // Signer: the validator's operator or the chain admin string validator_address = 2; // Operator address identifying the validator google.protobuf.Any new_pub_key = 3; // New consensus public key } ``` **Response:** ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgRotateConsPubKeyResponse {} ``` **CLI:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa rotate-cons-pub-key \ --operator-address \ --from \ -y ``` **Authorization:** Must be signed by the validator's operator address or the chain admin. **Notes:** * Re-keys the validator and migrates its accrued fees in the same block * Power, metadata, and the operator address are unchanged * No fee, no rate limit, and no rotation history, unlike `x/staking` rotation * The new key's type must be in the chain's consensus params, must not equal the current key, and must not belong to another validator For the operational procedure, see [Rotate a consensus key, PoA](/sdk/latest/keys/rotate-validator-key-poa). *** ## Common Use Cases ### 1. Query Current Admin ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa params ``` ### 2. List All Active Validators ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd q poa validators ``` ### 3. Add a New Validator **Step 1:** Operator creates the validator: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa create-validator \ --pubkey \ --moniker "new-validator" \ --from operator-account \ --keyring-backend test \ -y ``` **Step 2:** Admin activates with voting power: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa update-validators \ --validator '{ "pub_key": {"@type": "/cosmos.crypto.ed25519.PubKey", "key": "..."}, "power": 10000 }' \ --from admin \ --keyring-backend test \ -y ``` ### 4. Change Validator Voting Power ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa update-validators \ --validator '{ "pub_key": {"@type": "/cosmos.crypto.ed25519.PubKey", "key": "..."}, "power": 20000 }' \ --from admin \ --keyring-backend test \ -y ``` ### 5. Remove a Validator ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa update-validators \ --validator '{ "pub_key": {"@type": "/cosmos.crypto.ed25519.PubKey", "key": "..."}, "power": 0 }' \ --from admin \ --keyring-backend test \ -y ``` ### 6. Withdraw Validator Fees ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa withdraw-fees \ --from validator-operator \ --keyring-backend test \ -y ``` ### 7. Transfer Admin Rights ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx poa update-params \ --admin cosmos1newadminaddress... \ --from current-admin \ --keyring-backend test \ -y ``` *** ## REST API Endpoints All query endpoints are available via REST: | Method | Endpoint | Description | | ------ | -------------------------------------------------- | ---------------------- | | GET | `/cosmos/poa/v1/params` | Get module parameters | | GET | `/cosmos/poa/v1/validator/{address}` | Get single validator | | GET | `/cosmos/poa/v1/validators` | List all validators | | GET | `/cosmos/poa/v1/allocated_fees/{operator_address}` | Get withdrawable fees | | GET | `/cosmos/poa/v1/total_power` | Get total voting power | **Example REST Query:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl http://localhost:1317/cosmos/poa/v1/validators ``` *** ## Error Handling Common error scenarios: ### Unauthorized Admin Action **Error:** Transaction rejected **Cause:** Non-admin attempted to call admin-only function **Solution:** Ensure transaction is signed by the admin account ### Invalid Public Key **Error:** Invalid validator public key **Cause:** Malformed or wrong type of public key **Solution:** Use Ed25519 public key in correct format ### Validator Not Found **Error:** Validator does not exist **Cause:** Querying non-existent validator **Solution:** Verify validator address/public key ### Insufficient Fees **Error:** No fees to withdraw **Cause:** Validator has no accumulated fees **Solution:** Wait for fees to accumulate from block rewards *** ## Integration Examples ### JavaScript/TypeScript (CosmJS) ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { SigningStargateClient } from "@cosmjs/stargate"; // Query validators const client = await StargateClient.connect("http://localhost:26657"); const response = await client.queryContractSmart( "cosmos.poa.v1.Query/Validators", {} ); // Update validators (requires signing) const signingClient = await SigningStargateClient.connectWithSigner( "http://localhost:26657", wallet ); const msg = { typeUrl: "/cosmos.poa.v1.MsgUpdateValidators", value: { validators: [{ pubKey: { typeUrl: "/cosmos.crypto.ed25519.PubKey", value: ... }, power: 10000, metadata: { moniker: "validator-1", operatorAddress: "cosmos1..." } }], admin: "cosmos1adminaddress..." } }; const result = await signingClient.signAndBroadcast( adminAddress, [msg], "auto" ); ``` ### Python (cosmpy) ```python theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} from cosmpy.aerial.client import LedgerClient, NetworkConfig from cosmpy.aerial.wallet import LocalWallet # Create client client = LedgerClient(NetworkConfig.fetchai_mainnet()) # Query validators response = client.query_contract( "cosmos.poa.v1.Query/Validators", {} ) print(response) ``` ### Go ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "context" poatypes "github.com/cosmos/cosmos-sdk/enterprise/poa/types" "google.golang.org/grpc" ) // Query client conn, _ := grpc.Dial("localhost:9090", grpc.WithInsecure()) queryClient := poatypes.NewQueryClient(conn) // Get validators resp, err := queryClient.Validators(context.Background(), &poatypes.QueryValidatorsRequest{}) if err != nil { panic(err) } for _, val := range resp.Validators { fmt.Printf("Validator: %s, Power: %d\n", val.Metadata.Moniker, val.Power) } ``` *** ## Security Considerations 1. **Admin Key Security:** The admin private key has complete control over the validator set. Use hardware wallets or secure key management systems. 2. **Validator Public Keys:** Ensure validator public keys are correctly generated and stored securely. 3. **Power Distribution:** Consider the security implications of power concentration. Avoid giving a single validator >67% of total power. 4. **Operator Separation:** Use separate accounts for operator and admin roles to limit exposure. 5. **Fee Withdrawal:** Operators should regularly withdraw fees to prevent accumulation in the module. *** ## Appendix ### Public Key Formats Ed25519 public keys should be base64-encoded: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "YUzyiqZzKN8BmLbl75gdXfbxQ2QtSYpPSwA85bZ3xuE=" } ``` ### Address Formats * **Operator Address:** Standard Cosmos SDK bech32 address (e.g., `cosmos1...`) * **Consensus Address:** Can be derived from public key or use operator address for queries ### Power Units * Voting power is represented as `int64` * Total power affects block signing requirements (typically need >2/3 of total power for consensus) * Zero power effectively removes a validator from the active set # Architecture Source: https://docs.cosmos.network/sdk/latest/enterprise/poa/architecture System architecture and module integration details for the PoA module # PoA Module Architecture ## Overview The Proof of Authority (PoA) permissioned consensus module is a Cosmos SDK module that implements a permissioned consensus mechanism where a designated admin controls the validator set. Unlike traditional Proof of Stake systems, PoA validators are explicitly authorized and managed by an administrative authority rather than being selected based on staked tokens. Vote extensions are not supported on PoA chains. A consensus key rotation leaves the SDK-side consensus address out of sync with CometBFT for two heights, and during that window the chain cannot verify a rotating validator's vote-extension signatures. If more than 2/3 of voting power rotates within one window, the chain halts. See [Key rotation](/sdk/latest/keys/key-rotation) for the mechanism. ## Table of Contents * [Architecture](#architecture) * [SDK Integration Points](#sdk-integration-points) * [Architectural Decisions](#architectural-decisions) * [Admin Control Flow](#admin-control-flow) * [Setting Admin Authority](#setting-admin-authority) * [Managing Validator Set](#managing-validator-set) * [Updating Parameters](#updating-parameters) * [Validator Lifecycle](#validator-lifecycle) * [Validator Registration](#validator-registration) * [Gaining Consensus Power](#gaining-consensus-power) * [Removing Validators](#removing-validators) * [Fee Distribution](#fee-distribution) → See [the Distribution page](/sdk/latest/enterprise/poa/distribution) * [Governance](#governance) → See [the Governance page](/sdk/latest/enterprise/poa/governance) * [Technical Implementation](#technical-implementation) * [Storage Design](#storage-design) * [ABCI Integration](#abci-integration) * [Dependencies](#dependencies) * [Security Considerations](#security-considerations) ## Architecture ### SDK Integration Points The PoA module plugs into the Cosmos SDK as a replacement for the standard staking module, providing an alternative consensus mechanism: PoA Module Architecture *The diagram above shows how the PoA module integrates with Cosmos SDK modules (x/auth, x/bank, x/gov), the fee\_collector account, and CometBFT consensus engine.* **Key Integration Points:** 1. **Replaces [x/staking](/sdk/latest/modules/staking/README)**: PoA provides validator management without token delegation or bonding 2. **Integrates with [x/gov](/sdk/latest/modules/gov/README)**: Custom governance hooks ensure only active validators can participate and tally function override allocates vote weight to validator power ([details](#governance)) 3. **Uses [x/auth](/sdk/latest/modules/auth/auth) & [x/bank](/sdk/latest/modules/bank/README)**: Standard account and token management for fee distribution ([details](#fee-distribution)) 4. **ABCI Lifecycle**: Implements `EndBlocker` to communicate validator updates to CometBFT ([details](#abci-integration)) ### Architectural Decisions **Admin-Controlled Validator Set** Unlike proof-of-stake where validators are determined by token weight, PoA uses a single admin address to authorize validators. This design choice: * Enables permissioned networks with known validator identities * Removes token requirement from validator participation (no token bonding required) * Centralizes trust in the admin address (see [Security Considerations](#security-considerations)) **Custom Fee Distribution** Rather than using the standard [x/distribution](/sdk/latest/modules/distribution/README) module, PoA implements its own fee mechanism: * Fees are routed to the PoA module account via a custom ante handler (see [Fee Routing Setup](/sdk/latest/enterprise/poa/distribution#fee-routing-setup) for complete details). * Fees allocated proportionally to validator power (not delegated stake) * Validators withdraw fees on-demand * See [Fee Distribution](#fee-distribution) for complete details **Governance Without Staking** Standard SDK [governance](/sdk/latest/modules/gov/README) uses bonded tokens for voting weight. PoA replaces this with validator power: * Only active validators (power > 0) can submit, deposit, or vote on proposals * Voting weight determined by validator power, not token holdings * Prevents non-validator governance participation * See [Governance](#governance) for implementation details **Storage Design Philosophy** The module uses `cosmossdk.io/collections` with an indexed map: * Primary key: consensus address * Secondary indexes on operator address and power, for fast lookups and power-sorted iteration * See [Storage Design](#storage-design) for technical details ## Admin Control Flow ### Setting Admin Authority The PoA module is controlled by a single admin address configured at genesis. This admin has exclusive authority to: * Update validator power (grant/revoke consensus participation) * Modify module parameters * Batch update the entire validator set The admin could be set to any authority that has an address. This includes a group from x/groups, the governance module account, and multisigs. **Location**: Admin address stored in [`x/poa/types/keys.go:26`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/types/keys.go#L26) (params prefix) Only the admin can update itself with a parameter change. ### Managing Validator Set **MsgUpdateValidators** ([`x/poa/keeper/msg_server.go:142`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/msg_server.go#L142)) The admin can batch update validators through a single transaction: 1. **Authentication**: Transaction must be signed by the admin address 2. **Validation**: Each validator update is validated for: * Valid public key * Non-negative power * Valid metadata (operator address, moniker, description) * No duplicate operator addresses 3. **Power Changes**: Any power change triggers: * Fee checkpoint (allocates pending fees before power changes) * Total power recalculation * ABCI validator update queue 4. **Consensus Update**: Changes take effect in the next block ### Updating Parameters **MsgUpdateParams** ([`x/poa/keeper/msg_server.go:45`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/msg_server.go#L45)) The admin can update module parameters (currently only the admin address itself). This requires: * Transaction signed by current admin * Validation of new parameters ## Validator Lifecycle ### Validator Registration **MsgCreateValidator** ([`x/poa/keeper/msg_server.go:79`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/msg_server.go#L79)) **Admin-Only Creation**: Only the admin can register a validator: 1. **Submit Registration**: Provide public key and metadata * **PubKey**: Ed25519 * **Operator Address**: Account that will receive fees and manage the validator * **Moniker**: Human-readable name (max 256 chars) * **Description**: Additional details (max 256 chars) 2. **Initial State**: The admin sets the validator's initial power. A validator with **power = 0** is inactive: * Not participating in consensus * Not earning fees * Cannot vote in governance **Location**: [`x/poa/keeper/validator.go:121`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/validator.go#L121) ### Gaining Consensus Power Validators can only gain consensus power through admin action: 1. **Admin Updates Power**: Via [`MsgUpdateValidators`](#managing-validator-set) 2. **Power > 0**: Validator becomes active 3. **ABCI Update**: CometBFT adds validator to active set at next block 4. **Fee Eligibility**: Validator starts accumulating fees proportionally 5. **Governance Rights**: Validator can submit proposals, deposit, and vote **Power Mechanics**: * Power is an integer representing voting weight * Higher power = more consensus influence and fee share * Power can be adjusted up or down by admin * Setting power = 0 removes validator from consensus without deleting **Location**: [`x/poa/keeper/validator.go:34`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/validator.go#L34) ### Removing Validators **Soft Removal** (Removing power): * Admin sets validator power to 0 * Validator remains registered but inactive * Can be reactivated by admin later * Validator entry is preserved in the map of validators ## Fee Distribution The PoA module implements a custom checkpoint-based fee distribution system that allocates block fees proportionally to validator power. **Key Features**: * Fees accumulate in [the PoA module account](/sdk/latest/enterprise/poa/distribution#fee-routing-setup) * Allocated proportionally to validator power at checkpoints * Checkpoints triggered by power changes or withdrawals * Validators withdraw accumulated fees on-demand * Uses DecCoins for precision to prevent dust accumulation **Why Checkpointing?**: Allows for efficient, lazy distribution rather than actively moving funds every block. **See [Fee Distribution Documentation](/sdk/latest/enterprise/poa/distribution)** for complete details. **Location**: [`x/poa/keeper/distribution.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/distribution.go) ## Governance The PoA module restricts governance participation to active validators only, using validator power as voting weight instead of bonded tokens. **Key Features**: * Uses existing x/gov module * Only active validators (power > 0) can submit, deposit, or vote on proposals * Voting weight equals validator power * Custom tally function replaces standard governance tallying * Admin indirectly controls governance through power distribution **Power-Based Voting**: Each validator's vote is weighted by their validator power set in the x/poa module. **See [Governance Documentation](/sdk/latest/enterprise/poa/governance)** for complete details. **Location**: [`x/poa/keeper/governance.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/governance.go) and [`x/poa/keeper/hooks.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/hooks.go) ## Technical Implementation ### Storage Design **Collections Schema** ([`x/poa/types/keys.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/types/keys.go)) The module uses `cosmossdk.io/collections` for type-safe state management: | Prefix | Collection | Key Type | Value Type | Purpose | | ------ | ----------------------- | ---------------------- | --------------- | --------------------------------------- | | 0 | `params` | - | `Params` | Admin address and module config | | 1 | `validators` | `ConsAddress` | `Validator` | Primary map, keyed by consensus address | | 2 | `validator_by_operator` | `string` | `ConsAddress` | Index: operator addr → consensus addr | | 3 | `validator_by_power` | `(int64, ConsAddress)` | - | Index: power-sorted iteration | | 4 | `total_power` | - | `int64` | Sum of all validator power | | 5 | `total_allocated` | - | `ValidatorFees` | Sum of allocated fees | **Location**: [`x/poa/keeper/keeper.go:38`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/keeper.go#L38) ### ABCI Integration **EndBlocker** ([`x/poa/keeper/abci.go:30`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/abci.go#L30)) The module integrates with CometBFT consensus through ABCI: 1. **Power Changes**: When validator power changes, create `ValidatorUpdate` 2. **Queue Updates**: Store updates in memory queue 3. **EndBlock**: At end of block, return all queued updates 4. **CometBFT Processing**: Consensus engine applies updates for next block 5. **Clear Queue**: After returning, clear the queue **ValidatorUpdate Format**: ``` ValidatorUpdate { PubKey: PublicKey // Consensus public key Power: int64 // New power (0 = remove) } ``` **Location**: [`x/poa/module.go:266`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/module.go#L266) ## Security Considerations 1. **Single Point of Control**: * Admin address controls entire validator set 2. **Validator Registration**: * Only the admin can register a validator * Only the admin can grant consensus power 3. **Total Power Invariant**: * Total power must remain > 0 * Prevents zero-power chain halts * Validated on every power adjustment via a checkpoint trigger 4. **Governance Restrictions**: * Only active validators (power > 0) can participate * Prevents governance spam from unauthorized users * Ensures governance represents actual consensus participants 5. **Validator Indexing**: * Unique consensus address prevents duplicate validators * Unique operator address prevents fee confusion # Fee Distribution Source: https://docs.cosmos.network/sdk/latest/enterprise/poa/distribution Fee distribution mechanics and algorithms in the PoA module # Fee Distribution ## Overview The PoA module implements a custom fee distribution mechanism based on validator power. Unlike the standard Cosmos SDK x/distribution module, PoA uses a checkpoint-based system to allocate fees proportionally to validators without automatic distribution. ## How Fees Accumulate Fees flow through the PoA system differently than standard Cosmos SDK: 1. **Block Fees**: Transaction fees collected in each block go to the PoA module account (see [Fee Routing Setup](#fee-routing-setup)) 2. **Checkpoint System**: Allocated fees are updated for validators when: * Any validator power changes * Any validator withdraws fees **Why Checkpointing?**: Ensures fair distribution when power changes. If power changes mid-period, fees are allocated based on old power distribution before the change takes effect. **Location**: [`x/poa/keeper/distribution.go:32`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/distribution.go#L32) ## Distribution Algorithm ### Checkpoint-Based Allocation The PoA module uses a checkpoint system to allocate fees fairly when validator power changes. Rather than distributing fees actively at every block, allocation efficiently happens at discrete checkpoints. **Checkpoint Triggers**: * Any validator power change (via `MsgUpdateValidators`) * Any fee withdrawal (via `MsgWithdrawFees`) **Unallocated Fees Calculation**: At checkpoint time $t$, calculate unallocated fees: $$ U_t = B_{collector}(t) - A_{total}(t) $$ Where: * $U_t$ = unallocated fees at checkpoint $t$ * $B_{collector}(t)$ = current balance in the PoA module account * $A_{total}(t) = \sum_{i=1}^{n} F_i(t)$ = sum of all previously- allocated fees across all validators (0 if no checkpoints have been done) **Proportional Share Allocation**: For each active validator $i$ (where $P_i(t) > 0$), allocate a share proportional to their power: $$ S_i(t) = U_t \times \frac{P_i(t)}{P_{total}(t)} $$ Where: * $S_i(t)$ = share allocated to validator $i$ at checkpoint $t$ * $P_i(t)$ = voting power of validator $i$ at checkpoint $t$ * $P_{total}(t) = \sum_{j=1}^{n} P_j(t)$ = sum of all validator powers **Accumulated Fees Update**: After allocation, update each validator's accumulated fees: $$ F_i(t+1) = F_i(t) + S_i(t) $$ Where: * $F_i(t)$ = validator $i$'s accumulated fees before checkpoint * $F_i(t+1)$ = validator $i$'s accumulated fees after checkpoint * $S_i(t)$ = share allocated in this checkpoint **Total Allocated Tracking**: Update the global allocated tracker: $$ A_{total}(t+1) = A_{total}(t) + U_t $$ After this checkpoint, $A_{total}(t+1) = B_{collector}(t)$ (all fees are now allocated). ### Example Checkpoint Sequence **Initial State** (before checkpoint): * PoA module account balance: $B_{collector} = 1000$ tokens * Total allocated: $A_{total} = 400$ tokens (from previous checkpoints) * Validator A: $P_A = 50$, $F_A = 200$ tokens allocated * Validator B: $P_B = 50$, $F_B = 200$ tokens allocated * Total power: $P_{total} = 100$ **Admin Action**: Admin submits `MsgUpdateValidators` to change power distribution to 30/70 **Checkpoint Triggered** (before power change takes effect): 1. Calculate unallocated: $U = 1000 - 400 = 600$ tokens 2. Allocate shares based on **current power** (50/50): * Validator A: $S_A = 600 \times \frac{50}{100} = 300$ tokens * Validator B: $S_B = 600 \times \frac{50}{100} = 300$ tokens 3. Update accumulated fees: * Validator A: $F_A = 200 + 300 = 500$ tokens * Validator B: $F_B = 200 + 300 = 500$ tokens 4. Update total allocated: $A_{total} = 400 + 600 = 1000$ tokens **After Checkpoint** - Power Change Applied: * Validator A: $P_A = 30$ (new power for future blocks) * Validator B: $P_B = 70$ (new power for future blocks) * All 1000 tokens now allocated ($A_{total} = B_{collector}$) * Each validator has updated $F_i$ available for withdrawal **Why This Matters**: Validator A earned 300 tokens (50% share) based on their power during the period when those fees were collected. After the checkpoint, their power drops to 30%, so future fees will be split 30/70. Checkpointing ensures validators are rewarded based on the work they actually performed. **Precision**: Uses `DecCoins` (decimal coins) to prevent rounding dust accumulation. Each validator tracks fractional amounts that are too small to withdraw. ## Withdrawing Fees **MsgWithdrawFees** ([`x/poa/keeper/msg_server.go:182`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/msg_server.go#L182)) Any validator operator can withdraw accumulated fees: 1. **Submit Withdrawal**: Signed by operator address 2. **Checkpoint**: System checkpoints all validators first (allocates any pending fees) 3. **Truncate**: Decimal coins truncated to whole coins 4. **Transfer**: Coins transferred from the PoA module account to operator address 5. **Update Tracking**: Total allocated decreases by withdrawn amount 6. **Remainder**: Decimal remainder stays in validator's allocated balance **Example**: ``` Validator has: 100.7543 utokens allocated Withdrawal: 100 utokens transferred to operator Remainder: 0.7543 utokens remain allocated (less than least significant utoken digit) ``` **Location**: [`x/poa/keeper/distribution.go:135`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/distribution.go#L135) ## Withdrawal Formula When validator $i$ withdraws fees: $$ W_i = \lfloor F_i \rfloor $$ $$ F_i' = F_i - W_i $$ $$ A_{total}' = A_{total} - W_i $$ Where: * $W_i$ = amount withdrawn (truncated to integer coins) * $F_i$ = validator's allocated fees before withdrawal * $F_i'$ = validator's allocated fees after withdrawal (decimal remainder) * $\lfloor F_i \rfloor$ = floor function (truncate decimals) * $A_{total}'$ = updated total allocated across all validators ## Fee Routing Setup PoA has its own module account for collecting fees. Enabling the PoA module account is required to keep fee accounting isolated and accurate. The chain panics at block 1 if the ante handler's fee recipient is not the PoA module. To enable the PoA module account, two wiring changes are required: ### 1. Register the PoA Module Account Register `poatypes.ModuleName` in the `maccPerms` map passed to `authkeeper.NewAccountKeeper`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.AccountKeeper = authkeeper.NewAccountKeeper( appCodec, runtime.NewKVStoreService(storeKeys[authtypes.StoreKey]), authtypes.ProtoBaseAccount, map[string][]string{ authtypes.FeeCollectorName: nil, govtypes.ModuleName: {authtypes.Burner, authtypes.Staking}, poatypes.ModuleName: nil, // register PoA module account }, // ... ) ``` **Source**: [`simapp/app.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/simapp/app.go#L182-L194) ### 2. Configure the Ante Handler Use `WithFeeRecipientModule` on `NewDeductFeeDecorator` to route fees to the PoA module account: ```go {9} theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} anteDecorators := []sdk.AnteDecorator{ ante.NewSetUpContextDecorator(), ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), ante.NewValidateBasicDecorator(), ante.NewTxTimeoutHeightDecorator(), ante.NewValidateMemoDecorator(options.AccountKeeper), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker). WithFeeRecipientModule(poatypes.ModuleName), // redirect fees to PoA module account ante.NewSetPubKeyDecorator(options.AccountKeeper), ante.NewValidateSigCountDecorator(options.AccountKeeper), ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler, options.SigVerifyOptions...), ante.NewIncrementSequenceDecorator(options.AccountKeeper), } ``` **Source**: [`simapp/ante.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/simapp/ante.go#L41-L56) ## Security Considerations 1. **Decimal Precision**: * Uses DecCoins to prevent dust accumulation * Validators track fractional amounts * Remainders preserved across withdrawals * Prevents rounding errors from accumulating # Governance Integration Source: https://docs.cosmos.network/sdk/latest/enterprise/poa/governance Governance integration and power-based voting in the PoA module # Governance ## Overview The PoA module integrates with Cosmos SDK governance to restrict participation to authorized validators only. Unlike standard governance that uses bonded tokens for voting weight, PoA governance uses validator power as the basis for voting. ## Validator-Only Governance The PoA module restricts governance participation to authorized validators only through governance hooks. **Governance Hooks** ([`x/poa/keeper/hooks.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/hooks.go)) The module implements `govtypes.GovHooks`: 1. **AfterProposalSubmission**: Only authorized validators can submit proposals 2. **AfterProposalDeposit**: Only authorized validators can deposit on proposals 3. **AfterProposalVote**: Only authorized validators can vote **Authorized Validator Definition**: * Registered in PoA module * Power > 0 * Has valid operator address **Rejected Actions**: * If non-validator attempts governance action → transaction fails * If validator has power = 0 → transaction fails * Error: "voter X is not an active POA validator" **Location**: [`x/poa/keeper/governance.go:115`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/governance.go#L115) ## Voting Power **Custom Vote Tallying** ([`x/poa/keeper/governance.go:38`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/governance.go#L38)). An example of the wiring can be found in the [SimApp](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/simapp/app.go#L214-L224). Standard governance uses staked tokens as voting weight. PoA governance uses validator power: 1. **Vote Collection**: System iterates all votes on a proposal 2. **Validator Check**: For each vote, verify voter is active PoA validator 3. **Weight Calculation**: Use validator's power as voting weight 4. **Weighted Options**: Supports split votes, exactly like x/staking in traditional POS governance (e.g., 70% Yes, 30% Abstain) 5. **Tally Results**: Sum weighted votes by option ### Vote Tallying Algorithm **Voting Power Formula**: $$ V_i = P_i $$ Where: * $V_i$ = voting power of validator $i$ * $P_i$ = validator power (consensus weight) **Weighted Vote Calculation**: For a validator casting a split vote across multiple options: $$ W_{i,o} = V_i \times w_{i,o} $$ Where: * $W_{i,o}$ = vote weight from validator $i$ for option $o$ * $w_{i,o}$ = weight assigned to option $o$ by validator $i$ (where $\sum_{o} w_{i,o} = 1$) **Total Tally per Option**: $$ T_o = \sum_{i \in voters} W_{i,o} $$ Where: * $T_o$ = total votes for option $o$ * Sum over all validators who voted ### Example **Validator A**: $P_A = 100$, votes 100% Yes * $W_{A,Yes} = 100 \times 1.0 = 100$ **Validator B**: $P_B = 50$, votes 60% Yes, 40% No * $W_{B,Yes} = 50 \times 0.6 = 30$ * $W_{B,No} = 50 \times 0.4 = 20$ **Totals**: * $T_{Yes} = 130$ * $T_{No} = 20$ ## Proposal Lifecycle ### 1. Proposal Submission **[MsgSubmitProposal](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/tx.proto#L57-L88)** (standard x/gov module) When a proposal is submitted: 1. Standard governance validates the proposal content 2. `AfterProposalSubmission` hook is called 3. PoA module checks if proposer is authorized validator: * Look up proposer by operator address * Verify validator exists and has $P > 0$ * If not active, reject with error 4. If valid, proposal enters deposit period **Restriction**: Only authorized validators can submit proposals, preventing spam from non-consensus participants. ### 2. Deposit Period **[MsgDeposit](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/tx.proto#L153-L166)** (standard x/gov module) When a deposit is made: 1. Standard governance processes the deposit 2. `AfterProposalDeposit` hook is called 3. PoA module checks if depositor is authorized validator 4. If deposit threshold reached, proposal moves to voting period **Restriction**: Only authorized validators can deposit, ensuring only consensus participants can advance proposals. ### 3. Voting Period **[MsgVote](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/tx.proto#L111-L127)** or **[MsgVoteWeighted](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/proto/cosmos/gov/v1/tx.proto#L132-L148)** (standard x/gov module) When a vote is cast: 1. Standard governance records the vote 2. `AfterProposalVote` hook is called 3. PoA module validates voter is authorized validator 4. If invalid, transaction fails **Vote Options**: * `Yes`: Support the proposal * `No`: Oppose the proposal * `NoWithVeto`: Oppose and veto (can burn deposits if threshold met) * `Abstain`: Participate in quorum without taking a position **Weighted Voting**: Validators can split their vote across multiple options, with weights summing to 1. ### 4. Vote Tallying At the end of the voting period, the [custom tally function](#vote-tallying-algorithm) is called: **NewPOACalculateVoteResultsAndVotingPowerFn** ([`x/poa/keeper/governance.go:38`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/governance.go#L38)) 1. Iterate all votes on the proposal 2. For each vote, look up the validator by voter address 3. If validator is not active ($P \leq 0$), skip the vote 4. Otherwise, use validator power as voting weight 5. For weighted votes, distribute power across options 6. Sum all weighted votes by option 7. Apply standard governance thresholds: * Quorum: Minimum participation percentage * Threshold: Minimum "Yes" percentage to pass * Veto: Maximum "NoWithVeto" percentage before rejection **Result**: Proposal passes, fails, or is vetoed based on power-weighted votes. ## Implementation Details ### Governance Hooks **Location**: [`x/poa/keeper/hooks.go`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/hooks.go) The module implements the `govtypes.GovHooks` interface: ``` type GovHooks interface { AfterProposalSubmission(ctx, proposalID, proposerAddr) AfterProposalDeposit(ctx, proposalID, depositorAddr) AfterProposalVote(ctx, proposalID, voterAddr) // ... other hooks } ``` Each hook implementation: 1. Receives the operator address from the governance hook 2. Looks up the validator by operator address 3. Checks if validator exists and has power > 0 4. Returns error if validation fails ### Custom Tally Function **Location**: [`x/poa/keeper/governance.go:38`](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/enterprise/poa/x/poa/keeper/governance.go#L38) The tally function replaces the standard governance tally: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewPOACalculateVoteResultsAndVotingPowerFn(keeper) TallyFn { return func(ctx, proposal) (totalVotingPower, results) { // Iterate votes for vote in votes(proposal) { validator = keeper.GetValidatorByOperatorAddress(vote.voter) if validator == nil || validator.Power <= 0 { continue // Skip non-authorized validators } // Add validator power to total totalVotingPower += validator.Power // Apply vote weights for option, weight in vote.options { results[option] += validator.Power * weight } } return totalVotingPower, results } } ``` ## Governance Parameters The standard governance module parameters still apply: * **MinDeposit**: Minimum tokens required to enter voting period * **MaxDepositPeriod**: Time limit for reaching minimum deposit * **VotingPeriod**: Duration of the voting period * **Quorum**: Minimum participation rate (fraction of total power) * **Threshold**: Minimum "Yes" rate to pass (fraction of non-abstain votes) * **VetoThreshold**: Maximum "NoWithVeto" rate before rejection **Key Difference**: Quorum is calculated as a percentage of total validator power, not total bonded tokens. ## Security Considerations 1. **Validator Exclusivity**: * Only authorized validators (power > 0) can participate * Prevents sybil attacks through unauthorized validator spam * Ensures governance represents actual consensus participants 2. **Power-Based Voting**: * Voting weight tied to consensus power * Admin controls power distribution, thus controls governance indirectly 3. **Admin Governance Control**: * Admin can change validator power at any time * Admin can rotate any validator's consensus key on the operator's behalf * Admin can effectively control governance by adjusting power * Consider multi-sig admin or governance-controlled admin changes 4. **Proposal Spam Prevention**: * Restricting submissions to authorized validators reduces spam * Deposit requirements still apply * Validators have reputational stake in proposal quality ## Comparison to Standard Governance | Aspect | Standard Cosmos Governance | PoA Governance | | ------------------ | --------------------------------------- | ------------------------------------- | | Who can vote | Token holders (delegators + validators) | Authorized validators only | | Voting weight | Bonded tokens | Validator power | | Who can propose | Anyone with min deposit | Authorized validators only | | Who can deposit | Anyone | Authorized validators only | | Vote tallying | Sum of bonded tokens | Sum of validator power | | Quorum calculation | % of bonded tokens | % of total validator power | | Admin control | No direct control | Admin controls power → controls votes | ## Example Governance Flow **Scenario**: Validator A wants to propose a parameter change 1. **Submit Proposal**: * Validator A (power = 40) submits `MsgSubmitProposal` * Hook verifies A is authorized validator * Proposal enters deposit period 2. **Reach Deposit**: * Validator B (power = 30) deposits * Validator C (power = 30) deposits * Deposit threshold reached → voting period starts 3. **Voting**: * Validator A: 100% Yes (40 power → 40 Yes votes) * Validator B: 60% Yes, 40% No (30 power → 18 Yes, 12 No) * Validator C: 100% Abstain (30 power → 30 Abstain) * Total power: 100 (all authorized validators) 4. **Tally**: * Total voting power: 100 (all voted) * Quorum: 100/100 = 100% ✓ (assuming 33% quorum) * Results: 58 Yes, 12 No, 30 Abstain (out of 70 non-abstain) * Threshold: 58/70 = 82.9% Yes ✓ (assuming 50% threshold) * **Proposal passes** # ADR Creation Process Source: https://docs.cosmos.network/sdk/latest/reference/architecture/PROCESS 1. Copy the `adr-template.md` file. Use the following filename pattern: `adr-next_number-title.md` 2. Create a draft Pull Request if you want to get early feedback. 3. Make sure the context and solution are clear and well documented. 4. Add an entry to the list in the [README](/sdk/latest/reference/architecture/README) file. 5. Create a Pull Request to propose a new ADR. ## What is an ADR? An ADR is a document to document an implementation and design that may or may not have been discussed in an RFC. While an RFC is meant to replace synchronous communication in a distributed environment, an ADR is meant to document an already made decision. An ADR won't come with much of a communication overhead because the discussion was recorded in an RFC or a synchronous discussion. If the consensus came from a synchronous discussion, then a short excerpt should be added to the ADR to explain the goals. ## ADR life cycle ADR creation is an **iterative** process. Instead of having a high amount of communication overhead, an ADR is used when there is already a decision made and implementation details need to be added. The ADR should document what the collective consensus for the specific issue is and how to solve it. 1. Every ADR should start with either an RFC or a discussion where consensus has been met. 2. Once consensus is met, a GitHub Pull Request (PR) is created with a new document based on the `adr-template.md`. 3. If a *proposed* ADR is merged, then it should clearly document outstanding issues either in ADR document notes or in a GitHub Issue. 4. The PR SHOULD always be merged. In the case of a faulty ADR, we still prefer to merge it with a *rejected* status. The only time the ADR SHOULD NOT be merged is if the author abandons it. 5. Merged ADRs SHOULD NOT be pruned. ### ADR status Status has two components: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {CONSENSUS STATUS} {IMPLEMENTATION STATUS} ``` IMPLEMENTATION STATUS is either `Implemented` or `Not Implemented`. #### Consensus Status ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DRAFT -> PROPOSED -> LAST CALL yyyy-mm-dd -> ACCEPTED | REJECTED -> SUPERSEDED by ADR-xxx \ | \ | v v ABANDONED ``` * `DRAFT`: \[optional] an ADR which is a work in progress, not being ready for a general review. This is to present an early work and get early feedback in a Draft Pull Request form. * `PROPOSED`: an ADR covering a full solution architecture and still in the review - project stakeholders haven't reached an agreement yet. * `LAST CALL `: \[optional] Notifies that we are close to accepting updates. Changing a status to `LAST CALL` means that social consensus (of Cosmos SDK maintainers) has been reached, and we still want to give it a time to let the community react or analyze. * `ACCEPTED`: ADR which will represent a currently implemented or to be implemented architecture design. * `REJECTED`: ADR can go from PROPOSED or ACCEPTED to rejected if the consensus among project stakeholders will decide so. * `SUPERSEDED by ADR-xxx`: ADR which has been superseded by a new ADR. * `ABANDONED`: the ADR is no longer pursued by the original authors. ## Language used in ADR * The context/background should be written in the present tense. * Avoid using a first, personal form. # Architecture Decision Records (ADR) Source: https://docs.cosmos.network/sdk/latest/reference/architecture/README This is a location to record all high-level architecture decisions in the Cosmos-SDK. This is a location to record all high-level architecture decisions in the Cosmos-SDK. An Architectural Decision (**AD**) is a software design choice that addresses a functional or non-functional requirement that is architecturally significant. An Architecturally Significant Requirement (**ASR**) is a requirement that has a measurable effect on a software system’s architecture and quality. An Architectural Decision Record (**ADR**) captures a single AD, such as often done when writing personal notes or meeting minutes; the collection of ADRs created and maintained in a project constitute its decision log. All these are within the topic of Architectural Knowledge Management (AKM). You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t). ## Rationale ADRs are intended to be the primary mechanism for proposing new feature designs and new processes, for collecting community input on an issue, and for documenting the design decisions. An ADR should provide: * Context on the relevant goals and the current state * Proposed changes to achieve the goals * Summary of pros and cons * References * Changelog Note the distinction between an ADR and a spec. The ADR provides the context, intuition, reasoning, and justification for a change in architecture, or for the architecture of something new. The spec is a much more compressed and streamlined summary of everything as it stands today. If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match. ## Creating new ADR Read about the [PROCESS](/sdk/latest/reference/architecture/PROCESS). ### Use RFC 2119 Keywords When writing ADRs, follow the same best practices for writing RFCs. When writing RFCs, key words are used to signify the requirements in the specification. These words are often capitalized: "MUST," "MUST NOT," "REQUIRED," "SHALL," "SHALL NOT," "SHOULD," "SHOULD NOT," "RECOMMENDED," "MAY," and "OPTIONAL." They are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). ## ADR Table of Contents ### Accepted * [ADR 002: SDK Documentation Structure](/sdk/latest/reference/architecture/adr-002-docs-structure) * [ADR 004: Split Denomination Keys](/sdk/latest/reference/architecture/adr-004-split-denomination-keys) * [ADR 006: Secret Store Replacement](/sdk/latest/reference/architecture/adr-006-secret-store-replacement) * [ADR 009: Evidence Module](/sdk/latest/reference/architecture/adr-009-evidence-module) * [ADR 010: Modular AnteHandler](/sdk/latest/reference/architecture/adr-010-modular-antehandler) * [ADR 019: Protocol Buffer State Encoding](/sdk/latest/reference/architecture/adr-019-protobuf-state-encoding) * [ADR 020: Protocol Buffer Transaction Encoding](/sdk/latest/reference/architecture/adr-020-protobuf-transaction-encoding) * [ADR 021: Protocol Buffer Query Encoding](/sdk/latest/reference/architecture/adr-021-protobuf-query-encoding) * [ADR 023: Protocol Buffer Naming and Versioning](/sdk/latest/reference/architecture/adr-023-protobuf-naming) * [ADR 029: Fee Grant Module](/sdk/latest/reference/architecture/adr-029-fee-grant-module) * [ADR 030: Message Authorization Module](/sdk/latest/reference/architecture/adr-030-authz-module) * [ADR 031: Protobuf Msg Services](/sdk/latest/reference/architecture/adr-031-msg-service) * [ADR 055: ORM](/sdk/latest/reference/architecture/adr-055-orm) * [ADR 058: Auto-Generated CLI](/sdk/latest/reference/architecture/adr-058-auto-generated-cli) * [ADR 060: ABCI 1.0 (Phase I)](/sdk/latest/reference/architecture/adr-060-abci-1.0) * [ADR 061: Liquid Staking](/sdk/latest/reference/architecture/adr-061-liquid-staking) ### Proposed * [ADR 003: Dynamic Capability Store](/sdk/latest/reference/architecture/adr-003-dynamic-capability-store) * [ADR 011: Generalize Genesis Accounts](/sdk/latest/reference/architecture/adr-011-generalize-genesis-accounts) * [ADR 012: State Accessors](/sdk/latest/reference/architecture/adr-012-state-accessors) * [ADR 013: Metrics](/sdk/latest/reference/architecture/adr-013-metrics) * [ADR 016: Validator Consensus Key Rotation](/sdk/latest/reference/architecture/adr-016-validator-consensus-key-rotation) * [ADR 017: Historical Header Module](/sdk/latest/reference/architecture/adr-017-historical-header-module) * [ADR 018: Extendable Voting Periods](/sdk/latest/reference/architecture/adr-018-extendable-voting-period) * [ADR 022: Custom baseapp panic handling](/sdk/latest/reference/architecture/adr-022-custom-panic-handling) * [ADR 024: Coin Metadata](/sdk/latest/reference/architecture/adr-024-coin-metadata) * [ADR 027: Deterministic Protobuf Serialization](/sdk/latest/reference/architecture/adr-027-deterministic-protobuf-serialization) * [ADR 028: Public Key Addresses](/sdk/latest/reference/architecture/adr-028-public-key-addresses) * [ADR 032: Typed Events](/sdk/latest/reference/architecture/adr-032-typed-events) * [ADR 033: Inter-module RPC](/sdk/latest/reference/architecture/adr-033-protobuf-inter-module-comm) * [ADR 035: Rosetta API Support](/sdk/latest/reference/architecture/adr-035-rosetta-api-support) * [ADR 037: Governance Split Votes](/sdk/latest/reference/architecture/adr-037-gov-split-vote) * [ADR 038: State Listening](/sdk/latest/reference/architecture/adr-038-state-listening) * [ADR 039: Epoched Staking](/sdk/latest/reference/architecture/adr-039-epoched-staking) * [ADR 040: Storage and SMT State Commitments](/sdk/latest/reference/architecture/adr-040-storage-and-smt-state-commitments) * [ADR 046: Module Params](/sdk/latest/reference/architecture/adr-046-module-params) * [ADR 054: Semver Compatible SDK Modules](/sdk/latest/reference/architecture/adr-054-semver-compatible-modules) * [ADR 057: App Wiring](/sdk/latest/reference/architecture/adr-057-app-wiring) * [ADR 059: Test Scopes](/sdk/latest/reference/architecture/adr-059-test-scopes) * [ADR 062: Collections State Layer](/sdk/latest/reference/architecture/adr-062-collections-state-layer) * [ADR 063: Core Module API](/sdk/latest/reference/architecture/adr-063-core-module-api) * [ADR 065: Store V2](/sdk/latest/reference/architecture/adr-065-store-v2) * [ADR 076: Transaction Malleability Risk Review and Recommendations](/sdk/latest/reference/architecture/adr-076-tx-malleability) ### Draft * [ADR 044: Guidelines for Updating Protobuf Definitions](/sdk/latest/reference/architecture/adr-044-protobuf-updates-guidelines) * [ADR 047: Extend Upgrade Plan](/sdk/latest/reference/architecture/adr-047-extend-upgrade-plan) * [ADR 053: Go Module Refactoring](/sdk/latest/reference/architecture/adr-053-go-module-refactoring) * [ADR 068: Preblock](/sdk/latest/reference/architecture/adr-068-preblock) # ADR 002: SDK Documentation Structure Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-002-docs-structure There is a need for a scalable structure of the Cosmos SDK documentation. Current documentation includes a lot of non-related Cosmos SDK material, is difficult to maintain and hard to follow as a user. ## Context There is a need for a scalable structure of the Cosmos SDK documentation. Current documentation includes a lot of non-related Cosmos SDK material, is difficult to maintain and hard to follow as a user. Ideally, we would have: * All docs related to dev frameworks or tools live in their respective github repos (sdk repo would contain sdk docs, hub repo would contain hub docs, lotion repo would contain lotion docs, etc.) * All other docs (faqs, whitepaper, high-level material about Cosmos) would live on the website. ## Decision Re-structure the `/docs` folder of the Cosmos SDK github repo as follows: ```text expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} docs/ ├── README ├── intro/ ├── concepts/ │ ├── baseapp │ ├── types │ ├── store │ ├── server │ ├── modules/ │ │ ├── keeper │ │ ├── handler │ │ ├── cli │ ├── gas │ └── commands ├── clients/ │ ├── lite/ │ ├── service-providers ├── modules/ ├── spec/ ├── translations/ └── architecture/ ``` The files in each sub-folder do not matter and will likely change. What matters is the sectioning: * `README`: Landing page of the docs. * `intro`: Introductory material. Goal is to have a short explainer of the Cosmos SDK and then channel people to the resource they need. The [Cosmos SDK tutorial](https://github.com/cosmos/sdk-application-tutorial/) will be highlighted, as well as the `godocs`. * `concepts`: Contains high-level explanations of the abstractions of the Cosmos SDK. It does not contain specific code implementation and does not need to be updated often. **It is not an API specification of the interfaces**. API spec is the `godoc`. * `clients`: Contains specs and info about the various Cosmos SDK clients. * `spec`: Contains specs of modules, and others. * `modules`: Contains links to `godocs` and the spec of the modules. * `architecture`: Contains architecture-related docs like the present one. * `translations`: Contains different translations of the documentation. Website docs sidebar will only include the following sections: * `README` * `intro` * `concepts` * `clients` `architecture` need not be displayed on the website. ## Status Accepted ## Consequences ### Positive * Much clearer organization of the Cosmos SDK docs. * The `/docs` folder now only contains Cosmos SDK and gaia related material. Later, it will only contain Cosmos SDK related material. * Developers only have to update `/docs` folder when they open a PR (and not `/examples` for example). * Easier for developers to find what they need to update in the docs thanks to reworked architecture. * Cleaner vuepress build for website docs. * Will help build an executable doc (cf [Link](https://github.com/cosmos/cosmos-sdk/issues/2611)) ### Neutral * We need to move a bunch of deprecated stuff to `/_attic` folder. * We need to integrate content in `sdk/docs/core` in `concepts`. * We need to move all the content that currently lives in `docs` and does not fit in new structure (like `lotion`, intro material, whitepaper) to the website repository. * Update `DOCS_README.md` ## References * [Link](https://github.com/cosmos/cosmos-sdk/issues/1460) * [Link](https://github.com/cosmos/cosmos-sdk/pull/2695) * [Link](https://github.com/cosmos/cosmos-sdk/issues/2611) # ADR 003: Dynamic Capability Store Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-003-dynamic-capability-store 12 December 2019: Initial version 02 April 2020: Memory Store Revisions ## Changelog * 12 December 2019: Initial version * 02 April 2020: Memory Store Revisions ## Context Full implementation of the [IBC specification](https://github.com/cosmos/ibc) requires the ability to create and authenticate object-capability keys at runtime (i.e., during transaction execution), as described in [ICS 5](https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#technical-specification). In the IBC specification, capability keys are created for each newly initialized port & channel, and are used to authenticate future usage of the port or channel. Since channels and potentially ports can be initialized during transaction execution, the state machine must be able to create object-capability keys at this time. At present, the Cosmos SDK does not have the ability to do this. Object-capability keys are currently pointers (memory addresses) of `StoreKey` structs created at application initialisation in `app.go` ([example](https://github.com/cosmos/gaia/blob/dcbddd9f04b3086c0ad07ee65de16e7adedc7da4/app/app.go#L132)) and passed to Keepers as fixed arguments ([example](https://github.com/cosmos/gaia/blob/dcbddd9f04b3086c0ad07ee65de16e7adedc7da4/app/app.go#L160)). Keepers cannot create or store capability keys during transaction execution — although they could call `NewKVStoreKey` and take the memory address of the returned struct, storing this in the Merklised store would result in a consensus fault, since the memory address will be different on each machine (this is intentional — were this not the case, the keys would be predictable and couldn't serve as object capabilities). Keepers need a way to keep a private map of store keys which can be altered during transaction execution, along with a suitable mechanism for regenerating the unique memory addresses (capability keys) in this map whenever the application is started or restarted, along with a mechanism to revert capability creation on tx failure. This ADR proposes such an interface & mechanism. ## Decision The Cosmos SDK will include a new `CapabilityKeeper` abstraction, which is responsible for provisioning, tracking, and authenticating capabilities at runtime. During application initialisation in `app.go`, the `CapabilityKeeper` will be hooked up to modules through unique function references (by calling `ScopeToModule`, defined below) so that it can identify the calling module when later invoked. When the initial state is loaded from disk, the `CapabilityKeeper`'s `Initialize` function will create new capability keys for all previously allocated capability identifiers (allocated during execution of past transactions and assigned to particular modes), and keep them in a memory-only store while the chain is running. The `CapabilityKeeper` will include a persistent `KVStore`, a `MemoryStore`, and an in-memory map. The persistent `KVStore` tracks which capability is owned by which modules. The `MemoryStore` stores a forward mapping that map from module name, capability tuples to capability names and a reverse mapping that map from module name, capability name to the capability index. Since we cannot marshal the capability into a `KVStore` and unmarshal without changing the memory location of the capability, the reverse mapping in the KVStore will simply map to an index. This index can then be used as a key in the ephemeral go-map to retrieve the capability at the original memory location. The `CapabilityKeeper` will define the following types & functions: The `Capability` is similar to `StoreKey`, but has a globally unique `Index()` instead of a name. A `String()` method is provided for debugging. A `Capability` is simply a struct, the address of which is taken for the actual capability. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Capability struct { index uint64 } ``` A `CapabilityKeeper` contains a persistent store key, memory store key, and mapping of allocated module names. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type CapabilityKeeper struct { persistentKey StoreKey memKey StoreKey capMap map[uint64]*Capability moduleNames map[string]interface{ } sealed bool } ``` The `CapabilityKeeper` provides the ability to create *scoped* sub-keepers which are tied to a particular module name. These `ScopedCapabilityKeeper`s must be created at application initialisation and passed to modules, which can then use them to claim capabilities they receive and retrieve capabilities which they own by name, in addition to creating new capabilities & authenticating capabilities passed by other modules. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ScopedCapabilityKeeper struct { persistentKey StoreKey memKey StoreKey capMap map[uint64]*Capability moduleName string } ``` `ScopeToModule` is used to create a scoped sub-keeper with a particular name, which must be unique. It MUST be called before `InitializeAndSeal`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (ck CapabilityKeeper) ScopeToModule(moduleName string) ScopedCapabilityKeeper { if k.sealed { panic("cannot scope to module via a sealed capability keeper") } if _, ok := k.scopedModules[moduleName]; ok { panic(fmt.Sprintf("cannot create multiple scoped keepers for the same module name: %s", moduleName)) } k.scopedModules[moduleName] = struct{ }{ } return ScopedKeeper{ cdc: k.cdc, storeKey: k.storeKey, memKey: k.memKey, capMap: k.capMap, module: moduleName, } } ``` `InitializeAndSeal` MUST be called exactly once, after loading the initial state and creating all necessary `ScopedCapabilityKeeper`s, in order to populate the memory store with newly-created capability keys in accordance with the keys previously claimed by particular modules and prevent the creation of any new `ScopedCapabilityKeeper`s. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (ck CapabilityKeeper) InitializeAndSeal(ctx Context) { if ck.sealed { panic("capability keeper is sealed") } persistentStore := ctx.KVStore(ck.persistentKey) map := ctx.KVStore(ck.memKey) // initialise memory store for all names in persistent store for index, value := range persistentStore.Iter() { capability = &CapabilityKey{ index: index } for moduleAndCapability := range value { moduleName, capabilityName := moduleAndCapability.Split("/") memStore.Set(moduleName + "/fwd/" + capability, capabilityName) memStore.Set(moduleName + "/rev/" + capabilityName, index) ck.capMap[index] = capability } } ck.sealed = true } ``` `NewCapability` can be called by any module to create a new unique, unforgeable object-capability reference. The newly created capability is automatically persisted; the calling module need not call `ClaimCapability`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (sck ScopedCapabilityKeeper) NewCapability(ctx Context, name string) (Capability, error) { // check name not taken in memory store if capStore.Get("rev/" + name) != nil { return nil, errors.New("name already taken") } // fetch the current index index := persistentStore.Get("index") // create a new capability capability := &CapabilityKey{ index: index } // set persistent store persistentStore.Set(index, Set.singleton(sck.moduleName + "/" + name)) // update the index index++ persistentStore.Set("index", index) // set forward mapping in memory store from capability to name memStore.Set(sck.moduleName + "/fwd/" + capability, name) // set reverse mapping in memory store from name to index memStore.Set(sck.moduleName + "/rev/" + name, index) // set the in-memory mapping from index to capability pointer capMap[index] = capability // return the newly created capability return capability } ``` `AuthenticateCapability` can be called by any module to check that a capability does in fact correspond to a particular name (the name can be untrusted user input) with which the calling module previously associated it. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (sck ScopedCapabilityKeeper) AuthenticateCapability(name string, capability Capability) bool { // return whether forward mapping in memory store matches name return memStore.Get(sck.moduleName + "/fwd/" + capability) === name } ``` `ClaimCapability` allows a module to claim a capability key which it has received from another module so that future `GetCapability` calls will succeed. `ClaimCapability` MUST be called if a module which receives a capability wishes to access it by name in the future. Capabilities are multi-owner, so if multiple modules have a single `Capability` reference, they will all own it. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (sck ScopedCapabilityKeeper) ClaimCapability(ctx Context, capability Capability, name string) error { persistentStore := ctx.KVStore(sck.persistentKey) // set forward mapping in memory store from capability to name memStore.Set(sck.moduleName + "/fwd/" + capability, name) // set reverse mapping in memory store from name to capability memStore.Set(sck.moduleName + "/rev/" + name, capability) // update owner set in persistent store owners := persistentStore.Get(capability.Index()) owners.add(sck.moduleName + "/" + name) persistentStore.Set(capability.Index(), owners) } ``` `GetCapability` allows a module to fetch a capability which it has previously claimed by name. The module is not allowed to retrieve capabilities which it does not own. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (sck ScopedCapabilityKeeper) GetCapability(ctx Context, name string) (Capability, error) { // fetch the index of capability using reverse mapping in memstore index := memStore.Get(sck.moduleName + "/rev/" + name) // fetch capability from go-map using index capability := capMap[index] // return the capability return capability } ``` `ReleaseCapability` allows a module to release a capability which it had previously claimed. If no more owners exist, the capability will be deleted globally. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (sck ScopedCapabilityKeeper) ReleaseCapability(ctx Context, capability Capability) err { persistentStore := ctx.KVStore(sck.persistentKey) name := capStore.Get(sck.moduleName + "/fwd/" + capability) if name == nil { return error("capability not owned by module") } // delete forward mapping in memory store memoryStore.Delete(sck.moduleName + "/fwd/" + capability, name) // delete reverse mapping in memory store memoryStore.Delete(sck.moduleName + "/rev/" + name, capability) // update owner set in persistent store owners := persistentStore.Get(capability.Index()) owners.remove(sck.moduleName + "/" + name) if owners.size() > 0 { // there are still other owners, keep the capability around persistentStore.Set(capability.Index(), owners) } else { // no more owners, delete the capability persistentStore.Delete(capability.Index()) delete(capMap[capability.Index()]) } } ``` ### Usage patterns #### Initialisation Any modules which use dynamic capabilities must be provided a `ScopedCapabilityKeeper` in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ck := NewCapabilityKeeper(persistentKey, memoryKey) mod1Keeper := NewMod1Keeper(ck.ScopeToModule("mod1"), ....) mod2Keeper := NewMod2Keeper(ck.ScopeToModule("mod2"), ....) // other initialisation logic ... // load initial state... ck.InitializeAndSeal(initialContext) ``` #### Creating, passing, claiming and using capabilities Consider the case where `mod1` wants to create a capability, associate it with a resource (e.g. an IBC channel) by name, then pass it to `mod2` which will use it later: Module 1 would have the following code: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} capability := scopedCapabilityKeeper.NewCapability(ctx, "resourceABC") mod2Keeper.SomeFunction(ctx, capability, args...) ``` `SomeFunction`, running in module 2, could then claim the capability: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Mod2Keeper) SomeFunction(ctx Context, capability Capability) { k.sck.ClaimCapability(ctx, capability, "resourceABC") // other logic... } ``` Later on, module 2 can retrieve that capability by name and pass it to module 1, which will authenticate it against the resource: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Mod2Keeper) SomeOtherFunction(ctx Context, name string) { capability := k.sck.GetCapability(ctx, name) mod1.UseResource(ctx, capability, "resourceABC") } ``` Module 1 will then check that this capability key is authenticated to use the resource before allowing module 2 to use it: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Mod1Keeper) UseResource(ctx Context, capability Capability, resource string) { if !k.sck.AuthenticateCapability(name, capability) { return errors.New("unauthenticated") } // do something with the resource } ``` If module 2 passed the capability key to module 3, module 3 could then claim it and call module 1 just like module 2 did (in which case module 1, module 2, and module 3 would all be able to use this capability). ## Status Proposed. ## Consequences ### Positive * Dynamic capability support. * Allows CapabilityKeeper to return same capability pointer from go-map while reverting any writes to the persistent `KVStore` and in-memory `MemoryStore` on tx failure. ### Negative * Requires an additional keeper. * Some overlap with existing `StoreKey` system (in the future they could be combined, since this is a superset functionality-wise). * Requires an extra level of indirection in the reverse mapping, since MemoryStore must map to index which must then be used as key in a go map to retrieve the actual capability ### Neutral (none known) ## References * [Original discussion](https://github.com/cosmos/cosmos-sdk/pull/5230#discussion_r343978513) # ADR 004: Split Denomination Keys Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-004-split-denomination-keys 2020-01-08: Initial version 2020-01-09: Alterations to handle vesting accounts 2020-01-14: Updates from review feedback 2020-01-30: Updates from implementation ## Changelog * 2020-01-08: Initial version * 2020-01-09: Alterations to handle vesting accounts * 2020-01-14: Updates from review feedback * 2020-01-30: Updates from implementation ### Glossary * denom / denomination key -- unique token identifier. ## Context With permissionless IBC, anyone will be able to send arbitrary denominations to any other account. Currently, all non-zero balances are stored along with the account in an `sdk.Coins` struct, which creates a potential denial-of-service concern, as too many denominations will become expensive to load & store each time the account is modified. See issues [5467](https://github.com/cosmos/cosmos-sdk/issues/5467) and [4982](https://github.com/cosmos/cosmos-sdk/issues/4982) for additional context. Simply rejecting incoming deposits after a denomination count limit doesn't work, since it opens up a griefing vector: someone could send a user lots of nonsensical coins over IBC, and then prevent the user from receiving real denominations (such as staking rewards). ## Decision Balances shall be stored per-account & per-denomination under a denomination- and account-unique key, thus enabling O(1) read & write access to the balance of a particular account in a particular denomination. ### Account interface (x/auth) `GetCoins()` and `SetCoins()` will be removed from the account interface, since coin balances will now be stored in & managed by the bank module. The vesting account interface will replace `SpendableCoins` in favor of `LockedCoins` which does not require the account balance anymore. In addition, `TrackDelegation()` will now accept the account balance of all tokens denominated in the vesting balance instead of loading the entire account balance. Vesting accounts will continue to store original vesting, delegated free, and delegated vesting coins (which is safe since these cannot contain arbitrary denominations). ### Bank keeper (x/bank) The following APIs will be added to the `x/bank` keeper: * `GetAllBalances(ctx Context, addr AccAddress) Coins` * `GetBalance(ctx Context, addr AccAddress, denom string) Coin` * `SetBalance(ctx Context, addr AccAddress, coin Coin)` * `LockedCoins(ctx Context, addr AccAddress) Coins` * `SpendableCoins(ctx Context, addr AccAddress) Coins` Additional APIs may be added to facilitate iteration and auxiliary functionality not essential to core functionality or persistence. Balances will be stored first by the address, then by the denomination (the reverse is also possible, but retrieval of all balances for a single account is presumed to be more frequent): ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var BalancesPrefix = []byte("balances") func (k Keeper) SetBalance(ctx Context, addr AccAddress, balance Coin) error { if !balance.IsValid() { return err } store := ctx.KVStore(k.storeKey) balancesStore := prefix.NewStore(store, BalancesPrefix) accountStore := prefix.NewStore(balancesStore, addr.Bytes()) bz := Marshal(balance) accountStore.Set([]byte(balance.Denom), bz) return nil } ``` This will result in the balances being indexed by the byte representation of `balances/{address}/{denom}`. `DelegateCoins()` and `UndelegateCoins()` will be altered to only load each individual account balance by denomination found in the (un)delegation amount. As a result, any mutations to the account balance by will made by denomination. `SubtractCoins()` and `AddCoins()` will be altered to read & write the balances directly instead of calling `GetCoins()` / `SetCoins()` (which no longer exist). `trackDelegation()` and `trackUndelegation()` will be altered to no longer update account balances. External APIs will need to scan all balances under an account to retain backwards-compatibility. It is advised that these APIs use `GetBalance` and `SetBalance` instead of `GetAllBalances` when possible as to not load the entire account balance. ### Supply module The supply module, in order to implement the total supply invariant, will now need to scan all accounts & call `GetAllBalances` using the `x/bank` Keeper, then sum the balances and check that they match the expected total supply. ## Status Accepted. ## Consequences ### Positive * O(1) reads & writes of balances (with respect to the number of denominations for which an account has non-zero balances). Note, this does not relate to the actual I/O cost, rather the total number of direct reads needed. ### Negative * Slightly less efficient reads/writes when reading & writing all balances of a single account in a transaction. ### Neutral None in particular. ## References * Ref: [Link](https://github.com/cosmos/cosmos-sdk/issues/4982) * Ref: [Link](https://github.com/cosmos/cosmos-sdk/issues/5467) * Ref: [Link](https://github.com/cosmos/cosmos-sdk/issues/5492) # ADR 006: Secret Store Replacement Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-006-secret-store-replacement July 29th, 2019: Initial draft September 11th, 2019: Work has started November 4th: Cosmos SDK changes merged in November 18th: Gaia changes merged in ## Changelog * July 29th, 2019: Initial draft * September 11th, 2019: Work has started * November 4th: Cosmos SDK changes merged in * November 18th: Gaia changes merged in ## Context Currently, a Cosmos SDK application's CLI directory stores key material and metadata in a plain text database in the user's home directory. Key material is encrypted by a passphrase, protected by the bcrypt hashing algorithm. Metadata (e.g. addresses, public keys, key storage details) is available in plain text. This is not desirable for a number of reasons. Perhaps the biggest reason is insufficient security protection of key material and metadata. Leaking the plain text allows an attacker to surveil what keys a given computer controls via a number of techniques, like compromised dependencies without any privileged execution. This could be followed by a more targeted attack on a particular user/computer. All modern desktop operating systems (Ubuntu, Debian, macOS, Windows) provide a built-in secret store that is designed to allow applications to store information that is isolated from all other applications and requires passphrase entry to access the data. We are seeking a solution that provides a common abstraction layer to the many different backends and reasonable fallback for minimal platforms that don't provide a native secret store. ## Decision We recommend replacing the current Keybase backend based on LevelDB with [Keyring](https://github.com/99designs/keyring) by 99designs. This application is designed to provide a common abstraction and uniform interface between many secret stores and is used by the AWS Vault application by 99designs. This appears to fulfill the requirement of protecting both key material and metadata from rogue software on a user's machine. ## Status Accepted ## Consequences ### Positive Increased safety for users. ### Negative Users must manually migrate. Testing against all supported backends is difficult. Running tests locally on a Mac requires numerous repetitive password entries. ### Neutral No neutral consequences identified. ## References * \#4754 Switch secret store to the keyring secret store (original PR by @poldsam) \[**CLOSED**] * \#5029 Add support for github.com/99designs/keyring-backed keybases \[**MERGED**] * \#5097 Add keys migrate command \[**MERGED**] * \#5180 Drop on-disk keybase in favor of keyring \[*PENDING\_REVIEW*] * cosmos/gaia#164 Drop on-disk keybase in favor of keyring (gaia's changes) \[*PENDING\_REVIEW*] # ADR 007: Specialization Groups Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-007-specialization-groups 2019 Jul 31: Initial Draft ## Changelog * 2019 Jul 31: Initial Draft ## Context This idea was first conceived of in order to fulfill the use case of the creation of a decentralized Computer Emergency Response Team (dCERT), whose members would be elected by a governing community and would fulfill the role of coordinating the community under emergency situations. This thinking can be further abstracted into the conception of "blockchain specialization groups". The creation of these groups are the beginning of specialization capabilities within a wider blockchain community which could be used to enable a certain level of delegated responsibilities. Examples of specialization which could be beneficial to a blockchain community include: code auditing, emergency response, code development etc. This type of community organization paves the way for individual stakeholders to delegate votes by issue type, if in the future governance proposals include a field for issue type. ## Decision A specialization group can be broadly broken down into the following functions (herein containing examples): * Membership Admittance * Membership Acceptance * Membership Revocation * (probably) Without Penalty * member steps down (self-Revocation) * replaced by new member from governance * (probably) With Penalty * due to breach of soft-agreement (determined through governance) * due to breach of hard-agreement (determined by code) * Execution of Duties * Special transactions which only execute for members of a specialization group (for example, dCERT members voting to turn off transaction routes in an emergency scenario) * Compensation * Group compensation (further distribution decided by the specialization group) * Individual compensation for all constituents of a group from the greater community Membership admittance to a specialization group could take place over a wide variety of mechanisms. The most obvious example is through a general vote among the entire community, however in certain systems a community may want to allow the members already in a specialization group to internally elect new members, or maybe the community may assign a permission to a particular specialization group to appoint members to other 3rd party groups. The sky is really the limit as to how membership admittance can be structured. We attempt to capture some of these possiblities in a common interface dubbed the `Electionator`. For its initial implementation as a part of this ADR we recommend that the general election abstraction (`Electionator`) is provided as well as a basic implementation of that abstraction which allows for a continuous election of members of a specialization group. ```golang expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // The Electionator abstraction covers the concept space for // a wide variety of election kinds. type Electionator interface { // is the election object accepting votes. Active() bool // functionality to execute for when a vote is cast in this election, here // the vote field is anticipated to be marshalled into a vote type used // by an election. // // NOTE There are no explicit ids here. Just votes which pertain specifically // to one electionator. Anyone can create and send a vote to the electionator item // which will presumably attempt to marshal those bytes into a particular struct // and apply the vote information in some arbitrary way. There can be multiple // Electionators within the Cosmos-Hub for multiple specialization groups, votes // would need to be routed to the Electionator upstream of here. Vote(addr sdk.AccAddress, vote []byte) // here lies all functionality to authenticate and execute changes for // when a member accepts being elected AcceptElection(sdk.AccAddress) // Register a revoker object RegisterRevoker(Revoker) // No more revokers may be registered after this function is called SealRevokers() // register hooks to call when an election actions occur RegisterHooks(ElectionatorHooks) // query for the current winner(s) of this election based on arbitrary // election ruleset QueryElected() []sdk.AccAddress // query metadata for an address in the election this // could include for example position that an address // is being elected for within a group // // this metadata may be directly related to // voting information and/or privileges enabled // to members within a group. QueryMetadata(sdk.AccAddress) []byte } // ElectionatorHooks, once registered with an Electionator, // trigger execution of relevant interface functions when // Electionator events occur. type ElectionatorHooks interface { AfterVoteCast(addr sdk.AccAddress, vote []byte) AfterMemberAccepted(addr sdk.AccAddress) AfterMemberRevoked(addr sdk.AccAddress, cause []byte) } // Revoker defines the function required for a membership revocation rule-set // used by a specialization group. This could be used to create self revoking, // and evidence based revoking, etc. Revokers types may be created and // reused for different election types. // // When revoking the "cause" bytes may be arbitrarily marshalled into evidence, // memos, etc. type Revoker interface { RevokeName() string // identifier for this revoker type RevokeMember(addr sdk.AccAddress, cause []byte) error } ``` Certain level of commonality likely exists between the existing code within `x/governance` and required functionality of elections. This common functionality should be abstracted during implementation. Similarly for each vote implementation client CLI/REST functionality should be abstracted to be reused for multiple elections. The specialization group abstraction firstly extends the `Electionator` but also further defines traits of the group. ```golang expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type SpecializationGroup interface { Electionator GetName() string GetDescription() string // general soft contract the group is expected // to fulfill with the greater community GetContract() string // messages which can be executed by the members of the group Handler(ctx sdk.Context, msg sdk.Msg) sdk.Result // logic to be executed at endblock, this may for instance // include payment of a stipend to the group members // for participation in the security group. EndBlocker(ctx sdk.Context) } ``` ## Status > Proposed ## Consequences ### Positive * increases specialization capabilities of a blockchain * improve abstractions in `x/gov/` such that they can be used with specialization groups ### Negative * could be used to increase centralization within a community ### Neutral ## References * [dCERT ADR](/sdk/v0.50/build/architecture/adr-008-dCERT-group) # ADR 008: Decentralized Computer Emergency Response Team (dCERT) Group Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-008-dCERT-group 2019 Jul 31: Initial Draft ## Changelog * 2019 Jul 31: Initial Draft ## Context In order to reduce the number of parties involved with handling sensitive information in an emergency scenario, we propose the creation of a specialization group named The Decentralized Computer Emergency Response Team (dCERT). Initially this group's role is intended to serve as coordinators between various actors within a blockchain community such as validators, bug-hunters, and developers. During a time of crisis, the dCERT group would aggregate and relay input from a variety of stakeholders to the developers who are actively devising a patch to the software, this way sensitive information does not need to be publicly disclosed while some input from the community can still be gained. Additionally, a special privilege is proposed for the dCERT group: the capacity to "circuit-break" (aka. temporarily disable) a particular message path. Note that this privilege should be enabled/disabled globally with a governance parameter such that this privilege could start disabled and later be enabled through a parameter change proposal, once a dCERT group has been established. In the future it is foreseeable that the community may wish to expand the roles of dCERT with further responsibilities such as the capacity to "pre-approve" a security update on behalf of the community prior to a full community wide vote whereby the sensitive information would be revealed prior to a vulnerability being patched on the live network. ## Decision The dCERT group is proposed to include an implementation of a `SpecializationGroup` as defined in [ADR 007](/sdk/v0.50/build/architecture/adr-007-specialization-groups). This will include the implementation of: * continuous voting * slashing due to breach of soft contract * revoking a member due to breach of soft contract * emergency disband of the entire dCERT group (ex. for colluding maliciously) * compensation stipend from the community pool or other means decided by governance This system necessitates the following new parameters: * blockly stipend allowance per dCERT member * maximum number of dCERT members * required staked slashable tokens for each dCERT member * quorum for suspending a particular member * proposal wager for disbanding the dCERT group * stabilization period for dCERT member transition * circuit break dCERT privileges enabled These parameters are expected to be implemented through the param keeper such that governance may change them at any given point. ### Continuous Voting Electionator An `Electionator` object is to be implemented as continuous voting and with the following specifications: * All delegation addresses may submit votes at any point which updates their preferred representation on the dCERT group. * Preferred representation may be arbitrarily split between addresses (ex. 50% to John, 25% to Sally, 25% to Carol) * In order for a new member to be added to the dCERT group they must send a transaction accepting their admission at which point the validity of their admission is to be confirmed. * A sequence number is assigned when a member is added to dCERT group. If a member leaves the dCERT group and then enters back, a new sequence number is assigned. * Addresses which control the greatest amount of preferred-representation are eligible to join the dCERT group (up the *maximum number of dCERT members*). If the dCERT group is already full and new member is admitted, the existing dCERT member with the lowest amount of votes is kicked from the dCERT group. * In the split situation where the dCERT group is full but a vying candidate has the same amount of vote as an existing dCERT member, the existing member should maintain its position. * In the split situation where somebody must be kicked out but the two addresses with the smallest number of votes have the same number of votes, the address with the smallest sequence number maintains its position. * A stabilization period can be optionally included to reduce the "flip-flopping" of the dCERT membership tail members. If a stabilization period is provided which is greater than 0, when members are kicked due to insufficient support, a queue entry is created which documents which member is to replace which other member. While this entry is in the queue, no new entries to kick that same dCERT member can be made. When the entry matures at the duration of the stabilization period, the new member is instantiated, and old member kicked. ### Staking/Slashing All members of the dCERT group must stake tokens *specifically* to maintain eligibility as a dCERT member. These tokens can be staked directly by the vying dCERT member or out of the good will of a 3rd party (who shall gain no on-chain benefits for doing so). This staking mechanism should use the existing global unbonding time of tokens staked for network validator security. A dCERT member can *only be* a member if it has the required tokens staked under this mechanism. If those tokens are unbonded then the dCERT member must be automatically kicked from the group. Slashing of a particular dCERT member due to soft-contract breach should be performed by governance on a per member basis based on the magnitude of the breach. The process flow is anticipated to be that a dCERT member is suspended by the dCERT group prior to being slashed by governance. Membership suspension by the dCERT group takes place through a voting procedure by the dCERT group members. After this suspension has taken place, a governance proposal to slash the dCERT member must be submitted, if the proposal is not approved by the time the rescinding member has completed unbonding their tokens, then the tokens are no longer staked and unable to be slashed. Additionally in the case of an emergency situation of a colluding and malicious dCERT group, the community needs the capability to disband the entire dCERT group and likely fully slash them. This could be achieved though a special new proposal type (implemented as a general governance proposal) which would halt the functionality of the dCERT group until the proposal was concluded. This special proposal type would likely need to also have a fairly large wager which could be slashed if the proposal creator was malicious. The reason a large wager should be required is because as soon as the proposal is made, the capability of the dCERT group to halt message routes is put on temporarily suspended, meaning that a malicious actor who created such a proposal could then potentially exploit a bug during this period of time, with no dCERT group capable of shutting down the exploitable message routes. ### dCERT membership transactions Active dCERT members * change of the description of the dCERT group * circuit break a message route * vote to suspend a dCERT member. Here circuit-breaking refers to the capability to disable a groups of messages, This could for instance mean: "disable all staking-delegation messages", or "disable all distribution messages". This could be accomplished by verifying that the message route has not been "circuit-broken" at CheckTx time (in `baseapp/baseapp.go`). "unbreaking" a circuit is anticipated only to occur during a hard fork upgrade meaning that no capability to unbreak a message route on a live chain is required. Note also, that if there was a problem with governance voting (for instance a capability to vote many times) then governance would be broken and should be halted with this mechanism, it would be then up to the validator set to coordinate and hard-fork upgrade to a patched version of the software where governance is re-enabled (and fixed). If the dCERT group abuses this privilege they should all be severely slashed. ## Status > Proposed ## Consequences ### Positive * Potential to reduces the number of parties to coordinate with during an emergency * Reduction in possibility of disclosing sensitive information to malicious parties ### Negative * Centralization risks ### Neutral ## References [Specialization Groups ADR](/sdk/v0.50/build/architecture/adr-007-specialization-groups) # ADR 009: Evidence Module Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-009-evidence-module 2019 July 31: Initial draft 2019 October 24: Initial implementation ## Changelog * 2019 July 31: Initial draft * 2019 October 24: Initial implementation ## Status Accepted ## Context In order to support building highly secure, robust and interoperable blockchain applications, it is vital for the Cosmos SDK to expose a mechanism in which arbitrary evidence can be submitted, evaluated and verified resulting in some agreed upon penalty for any misbehavior committed by a validator, such as equivocation (double-voting), signing when unbonded, signing an incorrect state transition (in the future), etc. Furthermore, such a mechanism is paramount for any IBC (`https://github.com/cosmos/ics/blob/master/ibc/2_IBC_ARCHITECTURE.md`) or cross-chain validation protocol implementation in order to support the ability for any misbehavior to be relayed back from a collateralized chain to a primary chain so that the equivocating validator(s) can be slashed. ## Decision We will implement an evidence module in the Cosmos SDK supporting the following functionality: * Provide developers with the abstractions and interfaces necessary to define custom evidence messages, message handlers, and methods to slash and penalize accordingly for misbehavior. * Support the ability to route evidence messages to handlers in any module to determine the validity of submitted misbehavior. * Support the ability, through governance, to modify slashing penalties of any evidence type. * Querier implementation to support querying params, evidence types, params, and all submitted valid misbehavior. ### Types First, we define the `Evidence` interface type. The `x/evidence` module may implement its own types that can be used by many chains (e.g. `CounterFactualEvidence`). In addition, other modules may implement their own `Evidence` types in a similar manner in which governance is extensible. It is important to note any concrete type implementing the `Evidence` interface may include arbitrary fields such as an infraction time. We want the `Evidence` type to remain as flexible as possible. When submitting evidence to the `x/evidence` module, the concrete type must provide the validator's consensus address, which should be known by the `x/slashing` module (assuming the infraction is valid), the height at which the infraction occurred and the validator's power at same height in which the infraction occurred. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Evidence interface { Route() string Type() string String() string Hash() HexBytes ValidateBasic() error // The consensus address of the malicious validator at time of infraction GetConsensusAddress() ConsAddress // Height at which the infraction occurred GetHeight() int64 // The total power of the malicious validator at time of infraction GetValidatorPower() int64 // The total validator set power at time of infraction GetTotalPower() int64 } ``` ### Routing & Handling Each `Evidence` type must map to a specific unique route and be registered with the `x/evidence` module. It accomplishes this through the `Router` implementation. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Router interface { AddRoute(r string, h Handler) Router HasRoute(r string) bool GetRoute(path string) Handler Seal() } ``` Upon successful routing through the `x/evidence` module, the `Evidence` type is passed through a `Handler`. This `Handler` is responsible for executing all corresponding business logic necessary for verifying the evidence as valid. In addition, the `Handler` may execute any necessary slashing and potential jailing. Since slashing fractions will typically result from some form of static functions, allow the `Handler` to do this provides the greatest flexibility. An example could be `k * evidence.GetValidatorPower()` where `k` is an on-chain parameter controlled by governance. The `Evidence` type should provide all the external information necessary in order for the `Handler` to make the necessary state transitions. If no error is returned, the `Evidence` is considered valid. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Handler func(Context, Evidence) error ``` ### Submission `Evidence` is submitted through a `MsgSubmitEvidence` message type which is internally handled by the `x/evidence` module's `SubmitEvidence`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgSubmitEvidence struct { Evidence } func handleMsgSubmitEvidence(ctx Context, keeper Keeper, msg MsgSubmitEvidence) Result { if err := keeper.SubmitEvidence(ctx, msg.Evidence); err != nil { return err.Result() } // emit events... return Result{ // ... } } ``` The `x/evidence` module's keeper is responsible for matching the `Evidence` against the module's router and invoking the corresponding `Handler` which may include slashing and jailing the validator. Upon success, the submitted evidence is persisted. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) SubmitEvidence(ctx Context, evidence Evidence) error { handler := keeper.router.GetRoute(evidence.Route()) if err := handler(ctx, evidence); err != nil { return ErrInvalidEvidence(keeper.codespace, err) } keeper.setEvidence(ctx, evidence) return nil } ``` ### Genesis Finally, we need to represent the genesis state of the `x/evidence` module. The module only needs a list of all submitted valid infractions and any necessary params for which the module needs in order to handle submitted evidence. The `x/evidence` module will naturally define and route native evidence types for which it'll most likely need slashing penalty constants for. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type GenesisState struct { Params Params Infractions []Evidence } ``` ## Consequences ### Positive * Allows the state machine to process misbehavior submitted on-chain and penalize validators based on agreed upon slashing parameters. * Allows evidence types to be defined and handled by any module. This further allows slashing and jailing to be defined by more complex mechanisms. * Does not solely rely on Tendermint to submit evidence. ### Negative * No easy way to introduce new evidence types through governance on a live chain due to the inability to introduce the new evidence type's corresponding handler ### Neutral * Should we persist infractions indefinitely? Or should we rather rely on events? ## References * [ICS](https://github.com/cosmos/ics) * IBC Architecture: `https://github.com/cosmos/ics/blob/master/ibc/1_IBC_ARCHITECTURE.md` * [Tendermint Fork Accountability](https://github.com/tendermint/spec/blob/7b3138e69490f410768d9b1ffc7a17abc23ea397/spec/consensus/fork-accountability.md) # ADR 010: Modular AnteHandler Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-010-modular-antehandler 2019 Aug 31: Initial draft 2021 Sep 14: Superseded by ADR-045 ## Changelog * 2019 Aug 31: Initial draft * 2021 Sep 14: Superseded by ADR-045 ## Status SUPERSEDED by ADR-045 ## Context The current AnteHandler design allows users to either use the default AnteHandler provided in `x/auth` or to build their own AnteHandler from scratch. Ideally AnteHandler functionality is split into multiple, modular functions that can be chained together along with custom ante-functions so that users do not have to rewrite common antehandler logic when they want to implement custom behavior. For example, let's say a user wants to implement some custom signature verification logic. In the current codebase, the user would have to write their own Antehandler from scratch largely reimplementing much of the same code and then set their own custom, monolithic antehandler in the baseapp. Instead, we would like to allow users to specify custom behavior when necessary and combine them with default ante-handler functionality in a way that is as modular and flexible as possible. ## Proposals ### Per-Module AnteHandler One approach is to use the [ModuleManager](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/types/module) and have each module implement its own antehandler if it requires custom antehandler logic. The ModuleManager can then be passed in an AnteHandler order in the same way it has an order for BeginBlockers and EndBlockers. The ModuleManager returns a single AnteHandler function that will take in a tx and run each module's `AnteHandle` in the specified order. The module manager's AnteHandler is set as the baseapp's AnteHandler. Pros: 1. Simple to implement 2. Utilizes the existing ModuleManager architecture Cons: 1. Improves granularity but still cannot get more granular than a per-module basis. e.g. If auth's `AnteHandle` function is in charge of validating memo and signatures, users cannot swap the signature-checking functionality while keeping the rest of auth's `AnteHandle` functionality. 2. Module AnteHandler are run one after the other. There is no way for one AnteHandler to wrap or "decorate" another. ### Decorator Pattern The [weave project](https://github.com/iov-one/weave) achieves AnteHandler modularity through the use of a decorator pattern. The interface is designed as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Decorator wraps a Handler to provide common functionality // like authentication, or fee-handling, to many Handlers type Decorator interface { Check(ctx Context, store KVStore, tx Tx, next Checker) (*CheckResult, error) Deliver(ctx Context, store KVStore, tx Tx, next Deliverer) (*DeliverResult, error) } ``` Each decorator works like a modularized Cosmos SDK antehandler function, but it can take in a `next` argument that may be another decorator or a Handler (which does not take in a next argument). These decorators can be chained together, one decorator being passed in as the `next` argument of the previous decorator in the chain. The chain ends in a Router which can take a tx and route to the appropriate msg handler. A key benefit of this approach is that one Decorator can wrap its internal logic around the next Checker/Deliverer. A weave Decorator may do the following: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Example Decorator's Deliver function func (example Decorator) Deliver(ctx Context, store KVStore, tx Tx, next Deliverer) { // Do some pre-processing logic res, err := next.Deliver(ctx, store, tx) // Do some post-processing logic given the result and error } ``` Pros: 1. Weave Decorators can wrap over the next decorator/handler in the chain. The ability to both pre-process and post-process may be useful in certain settings. 2. Provides a nested modular structure that isn't possible in the solution above, while also allowing for a linear one-after-the-other structure like the solution above. Cons: 1. It is hard to understand at first glance the state updates that would occur after a Decorator runs given the `ctx`, `store`, and `tx`. A Decorator can have an arbitrary number of nested Decorators being called within its function body, each possibly doing some pre- and post-processing before calling the next decorator on the chain. Thus to understand what a Decorator is doing, one must also understand what every other decorator further along the chain is also doing. This can get quite complicated to understand. A linear, one-after-the-other approach while less powerful, may be much easier to reason about. ### Chained Micro-Functions The benefit of Weave's approach is that the Decorators can be very concise, which when chained together allows for maximum customizability. However, the nested structure can get quite complex and thus hard to reason about. Another approach is to split the AnteHandler functionality into tightly scoped "micro-functions", while preserving the one-after-the-other ordering that would come from the ModuleManager approach. We can then have a way to chain these micro-functions so that they run one after the other. Modules may define multiple ante micro-functions and then also provide a default per-module AnteHandler that implements a default, suggested order for these micro-functions. Users can order the AnteHandlers easily by simply using the ModuleManager. The ModuleManager will take in a list of AnteHandlers and return a single AnteHandler that runs each AnteHandler in the order of the list provided. If the user is comfortable with the default ordering of each module, this is as simple as providing a list with each module's antehandler (exactly the same as BeginBlocker and EndBlocker). If however, users wish to change the order or add, modify, or delete ante micro-functions in anyway; they can always define their own ante micro-functions and add them explicitly to the list that gets passed into module manager. #### Default Workflow This is an example of a user's AnteHandler if they choose not to make any custom micro-functions. ##### Cosmos SDK code ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Chains together a list of AnteHandler micro-functions that get run one after the other. // Returned AnteHandler will abort on first error. func Chainer(order []AnteHandler) AnteHandler { return func(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) { for _, ante := range order { ctx, err := ante(ctx, tx, simulate) if err != nil { return ctx, err } } return ctx, err } } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // AnteHandler micro-function to verify signatures func VerifySignatures(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) { // verify signatures // Returns InvalidSignature Result and abort=true if sigs invalid // Return OK result and abort=false if sigs are valid } // AnteHandler micro-function to validate memo func ValidateMemo(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) { // validate memo } // Auth defines its own default ante-handler by chaining its micro-functions in a recommended order AuthModuleAnteHandler := Chainer([]AnteHandler{ VerifySignatures, ValidateMemo }) ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Distribution micro-function to deduct fees from tx func DeductFees(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) { // Deduct fees from tx // Abort if insufficient funds in account to pay for fees } // Distribution micro-function to check if fees > mempool parameter func CheckMempoolFees(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) { // If CheckTx: Abort if the fees are less than the mempool's minFee parameter } // Distribution defines its own default ante-handler by chaining its micro-functions in a recommended order DistrModuleAnteHandler := Chainer([]AnteHandler{ CheckMempoolFees, DeductFees }) ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ModuleManager struct { // other fields AnteHandlerOrder []AnteHandler } func (mm ModuleManager) GetAnteHandler() AnteHandler { retun Chainer(mm.AnteHandlerOrder) } ``` ##### User Code ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Note: Since user is not making any custom modifications, we can just SetAnteHandlerOrder with the default AnteHandlers provided by each module in our preferred order moduleManager.SetAnteHandlerOrder([]AnteHandler(AuthModuleAnteHandler, DistrModuleAnteHandler)) app.SetAnteHandler(mm.GetAnteHandler()) ``` #### Custom Workflow This is an example workflow for a user that wants to implement custom antehandler logic. In this example, the user wants to implement custom signature verification and change the order of antehandler so that validate memo runs before signature verification. ##### User Code ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // User can implement their own custom signature verification antehandler micro-function func CustomSigVerify(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) { // do some custom signature verification logic } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Micro-functions allow users to change order of when they get executed, and swap out default ante-functionality with their own custom logic. // Note that users can still chain the default distribution module handler, and auth micro-function along with their custom ante function moduleManager.SetAnteHandlerOrder([]AnteHandler(ValidateMemo, CustomSigVerify, DistrModuleAnteHandler)) ``` Pros: 1. Allows for ante functionality to be as modular as possible. 2. For users that do not need custom ante-functionality, there is little difference between how antehandlers work and how BeginBlock and EndBlock work in ModuleManager. 3. Still easy to understand Cons: 1. Cannot wrap antehandlers with decorators like you can with Weave. ### Simple Decorators This approach takes inspiration from Weave's decorator design while trying to minimize the number of breaking changes to the Cosmos SDK and maximizing simplicity. Like Weave decorators, this approach allows one `AnteDecorator` to wrap the next AnteHandler to do pre- and post-processing on the result. This is useful since decorators can do defer/cleanups after an AnteHandler returns as well as perform some setup beforehand. Unlike Weave decorators, these `AnteDecorator` functions can only wrap over the AnteHandler rather than the entire handler execution path. This is deliberate as we want decorators from different modules to perform authentication/validation on a `tx`. However, we do not want decorators being capable of wrapping and modifying the results of a `MsgHandler`. In addition, this approach will not break any core Cosmos SDK API's. Since we preserve the notion of an AnteHandler and still set a single AnteHandler in baseapp, the decorator is simply an additional approach available for users that desire more customization. The API of modules (namely `x/auth`) may break with this approach, but the core API remains untouched. Allow Decorator interface that can be chained together to create a Cosmos SDK AnteHandler. This allows users to choose between implementing an AnteHandler by themselves and setting it in the baseapp, or use the decorator pattern to chain their custom decorators with the Cosmos SDK provided decorators in the order they wish. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // An AnteDecorator wraps an AnteHandler, and can do pre- and post-processing on the next AnteHandler type AnteDecorator interface { AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (newCtx Context, err error) } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ChainAnteDecorators will recursively link all of the AnteDecorators in the chain and return a final AnteHandler function // This is done to preserve the ability to set a single AnteHandler function in the baseapp. func ChainAnteDecorators(chain ...AnteDecorator) AnteHandler { if len(chain) == 1 { return func(ctx Context, tx Tx, simulate bool) { chain[0].AnteHandle(ctx, tx, simulate, nil) } } return func(ctx Context, tx Tx, simulate bool) { chain[0].AnteHandle(ctx, tx, simulate, ChainAnteDecorators(chain[1:])) } } ``` #### Example Code Define AnteDecorator functions ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Setup GasMeter, catch OutOfGasPanic and handle appropriately type SetUpContextDecorator struct{ } func (sud SetUpContextDecorator) AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (newCtx Context, err error) { ctx.GasMeter = NewGasMeter(tx.Gas) defer func() { // recover from OutOfGas panic and handle appropriately } return next(ctx, tx, simulate) } // Signature Verification decorator. Verify Signatures and move on type SigVerifyDecorator struct{ } func (svd SigVerifyDecorator) AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (newCtx Context, err error) { // verify sigs. Return error if invalid // call next antehandler if sigs ok return next(ctx, tx, simulate) } // User-defined Decorator. Can choose to pre- and post-process on AnteHandler type UserDefinedDecorator struct{ // custom fields } func (udd UserDefinedDecorator) AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (newCtx Context, err error) { // pre-processing logic ctx, err = next(ctx, tx, simulate) // post-processing logic } ``` Link AnteDecorators to create a final AnteHandler. Set this AnteHandler in baseapp. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Create final antehandler by chaining the decorators together antehandler := ChainAnteDecorators(NewSetUpContextDecorator(), NewSigVerifyDecorator(), NewUserDefinedDecorator()) // Set chained Antehandler in the baseapp bapp.SetAnteHandler(antehandler) ``` Pros: 1. Allows one decorator to pre- and post-process the next AnteHandler, similar to the Weave design. 2. Do not need to break baseapp API. Users can still set a single AnteHandler if they choose. Cons: 1. Decorator pattern may have a deeply nested structure that is hard to understand, this is mitigated by having the decorator order explicitly listed in the `ChainAnteDecorators` function. 2. Does not make use of the ModuleManager design. Since this is already being used for BeginBlocker/EndBlocker, this proposal seems unaligned with that design pattern. ## Consequences Since pros and cons are written for each approach, it is omitted from this section ## References * [#4572](https://github.com/cosmos/cosmos-sdk/issues/4572): Modular AnteHandler Issue * [#4582](https://github.com/cosmos/cosmos-sdk/pull/4583): Initial Implementation of Per-Module AnteHandler Approach * [Weave Decorator Code](https://github.com/iov-one/weave/blob/master/handler.go#L35) * [Weave Design Videos](https://vimeo.com/showcase/6189877) # ADR 011: Generalize Genesis Accounts Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-011-generalize-genesis-accounts 2019-08-30: initial draft ## Changelog * 2019-08-30: initial draft ## Context Currently, the Cosmos SDK allows for custom account types; the `auth` keeper stores any type fulfilling its `Account` interface. However `auth` does not handle exporting or loading accounts to/from a genesis file, this is done by `genaccounts`, which only handles one of 4 concrete account types (`BaseAccount`, `ContinuousVestingAccount`, `DelayedVestingAccount` and `ModuleAccount`). Projects desiring to use custom accounts (say custom vesting accounts) need to fork and modify `genaccounts`. ## Decision In summary, we will (un)marshal all accounts (interface types) directly using amino, rather than converting to `genaccounts`’s `GenesisAccount` type. Since doing this removes the majority of `genaccounts`'s code, we will merge `genaccounts` into `auth`. Marshalled accounts will be stored in `auth`'s genesis state. Detailed changes: ### 1) (Un)Marshal accounts directly using amino The `auth` module's `GenesisState` gains a new field `Accounts`. Note these aren't of type `exported.Account` for reasons outlined in section 3. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // GenesisState - all auth state that must be provided at genesis type GenesisState struct { Params Params `json:"params" yaml:"params"` Accounts []GenesisAccount `json:"accounts" yaml:"accounts"` } ``` Now `auth`'s `InitGenesis` and `ExportGenesis` (un)marshal accounts as well as the defined params. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // InitGenesis - Init store state from genesis data func InitGenesis(ctx sdk.Context, ak AccountKeeper, data GenesisState) { ak.SetParams(ctx, data.Params) // load the accounts for _, a := range data.Accounts { acc := ak.NewAccount(ctx, a) // set account number ak.SetAccount(ctx, acc) } } // ExportGenesis returns a GenesisState for a given context and keeper func ExportGenesis(ctx sdk.Context, ak AccountKeeper) GenesisState { params := ak.GetParams(ctx) var genAccounts []exported.GenesisAccount ak.IterateAccounts(ctx, func(account exported.Account) bool { genAccount := account.(exported.GenesisAccount) genAccounts = append(genAccounts, genAccount) return false }) return NewGenesisState(params, genAccounts) } ``` ### 2) Register custom account types on the `auth` codec The `auth` codec must have all custom account types registered to marshal them. We will follow the pattern established in `gov` for proposals. An example custom account definition: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" // Register the module account type with the auth module codec so it can decode module accounts stored in a genesis file func init() { authtypes.RegisterAccountTypeCodec(ModuleAccount{ }, "cosmos-sdk/ModuleAccount") } type ModuleAccount struct { ... ``` The `auth` codec definition: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ModuleCdc *codec.LegacyAmino func init() { ModuleCdc = codec.NewLegacyAmino() // register module msg's and Account interface ... // leave the codec unsealed } // RegisterAccountTypeCodec registers an external account type defined in another module for the internal ModuleCdc. func RegisterAccountTypeCodec(o interface{ }, name string) { ModuleCdc.RegisterConcrete(o, name, nil) } ``` ### 3) Genesis validation for custom account types Modules implement a `ValidateGenesis` method. As `auth` does not know of account implementations, accounts will need to validate themselves. We will unmarshal accounts into a `GenesisAccount` interface that includes a `Validate` method. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type GenesisAccount interface { exported.Account Validate() error } ``` Then the `auth` `ValidateGenesis` function becomes: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ValidateGenesis performs basic validation of auth genesis data returning an // error for any failed validation criteria. func ValidateGenesis(data GenesisState) error { // Validate params ... // Validate accounts addrMap := make(map[string]bool, len(data.Accounts)) for _, acc := range data.Accounts { // check for duplicated accounts addrStr := acc.GetAddress().String() if _, ok := addrMap[addrStr]; ok { return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr) } addrMap[addrStr] = true // check account specific validation if err := acc.Validate(); err != nil { return fmt.Errorf("invalid account found in genesis state; address: %s, error: %s", addrStr, err.Error()) } } return nil } ``` ### 4) Move add-genesis-account cli to `auth` The `genaccounts` module contains a cli command to add base or vesting accounts to a genesis file. This will be moved to `auth`. We will leave it to projects to write their own commands to add custom accounts. An extensible cli handler, similar to `gov`, could be created but it is not worth the complexity for this minor use case. ### 5) Update module and vesting accounts Under the new scheme, module and vesting account types need some minor updates: * Type registration on `auth`'s codec (shown above) * A `Validate` method for each `Account` concrete type ## Status Proposed ## Consequences ### Positive * custom accounts can be used without needing to fork `genaccounts` * reduction in lines of code ### Negative ### Neutral * `genaccounts` module no longer exists * accounts in genesis files are stored under `accounts` in `auth` rather than in the `genaccounts` module. -`add-genesis-account` cli command now in `auth` ## References # ADR 012: State Accessors Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-012-state-accessors 2019 Sep 04: Initial draft ## Changelog * 2019 Sep 04: Initial draft ## Context Cosmos SDK modules currently use the `KVStore` interface and `Codec` to access their respective state. While this provides a large degree of freedom to module developers, it is hard to modularize and the UX is mediocre. First, each time a module tries to access the state, it has to marshal the value and set or get the value and finally unmarshal. Usually this is done by declaring `Keeper.GetXXX` and `Keeper.SetXXX` functions, which are repetitive and hard to maintain. Second, this makes it harder to align with the object capability theorem: the right to access the state is defined as a `StoreKey`, which gives full access on the entire Merkle tree, so a module cannot send the access right to a specific key-value pair (or a set of key-value pairs) to another module safely. Finally, because the getter/setter functions are defined as methods of a module's `Keeper`, the reviewers have to consider the whole Merkle tree space when they reviewing a function accessing any part of the state. There is no static way to know which part of the state that the function is accessing (and which is not). ## Decision We will define a type named `Value`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Value struct { m Mapping key []byte } ``` The `Value` works as a reference for a key-value pair in the state, where `Value.m` defines the key-value space it will access and `Value.key` defines the exact key for the reference. We will define a type named `Mapping`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Mapping struct { storeKey sdk.StoreKey cdc *codec.LegacyAmino prefix []byte } ``` The `Mapping` works as a reference for a key-value space in the state, where `Mapping.storeKey` defines the IAVL (sub-)tree and `Mapping.prefix` defines the optional subspace prefix. We will define the following core methods for the `Value` type: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Get and unmarshal stored data, noop if not exists, panic if cannot unmarshal func (Value) Get(ctx Context, ptr interface{ }) { } // Get and unmarshal stored data, return error if not exists or cannot unmarshal func (Value) GetSafe(ctx Context, ptr interface{ }) { } // Get stored data as raw byte slice func (Value) GetRaw(ctx Context) []byte { } // Marshal and set a raw value func (Value) Set(ctx Context, o interface{ }) { } // Check if a raw value exists func (Value) Exists(ctx Context) bool { } // Delete a raw value value func (Value) Delete(ctx Context) { } ``` We will define the following core methods for the `Mapping` type: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Constructs key-value pair reference corresponding to the key argument in the Mapping space func (Mapping) Value(key []byte) Value { } // Get and unmarshal stored data, noop if not exists, panic if cannot unmarshal func (Mapping) Get(ctx Context, key []byte, ptr interface{ }) { } // Get and unmarshal stored data, return error if not exists or cannot unmarshal func (Mapping) GetSafe(ctx Context, key []byte, ptr interface{ }) // Get stored data as raw byte slice func (Mapping) GetRaw(ctx Context, key []byte) []byte { } // Marshal and set a raw value func (Mapping) Set(ctx Context, key []byte, o interface{ }) { } // Check if a raw value exists func (Mapping) Has(ctx Context, key []byte) bool { } // Delete a raw value value func (Mapping) Delete(ctx Context, key []byte) { } ``` Each method of the `Mapping` type that is passed the arguments `ctx`, `key`, and `args...` will proxy the call to `Mapping.Value(key)` with arguments `ctx` and `args...`. In addition, we will define and provide a common set of types derived from the `Value` type: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Boolean struct { Value } type Enum struct { Value } type Integer struct { Value; enc IntEncoding } type String struct { Value } // ... ``` Where the encoding schemes can be different, `o` arguments in core methods are typed, and `ptr` arguments in core methods are replaced by explicit return types. Finally, we will define a family of types derived from the `Mapping` type: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Indexer struct { m Mapping enc IntEncoding } ``` Where the `key` argument in core method is typed. Some of the properties of the accessor types are: * State access happens only when a function which takes a `Context` as an argument is invoked * Accessor type structs give rights to access the state only that the struct is referring, no other * Marshalling/Unmarshalling happens implicitly within the core methods ## Status Proposed ## Consequences ### Positive * Serialization will be done automatically * Shorter code size, less boilerplate, better UX * References to the state can be transferred safely * Explicit scope of accessing ### Negative * Serialization format will be hidden * Different architecture from the current, but the use of accessor types can be opt-in * Type-specific types (e.g. `Boolean` and `Integer`) have to be defined manually ### Neutral ## References * [#4554](https://github.com/cosmos/cosmos-sdk/issues/4554) # ADR 013: Observability Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-013-metrics 20-01-2020: Initial Draft ## Changelog * 20-01-2020: Initial Draft ## Status Proposed ## Context Telemetry is paramount into debugging and understanding what the application is doing and how it is performing. We aim to expose metrics from modules and other core parts of the Cosmos SDK. In addition, we should aim to support multiple configurable sinks that an operator may choose from. By default, when telemetry is enabled, the application should track and expose metrics that are stored in-memory. The operator may choose to enable additional sinks, where we support only [Prometheus](https://prometheus.io/) for now, as it's battle-tested, simple to setup, open source, and is rich with ecosystem tooling. We must also aim to integrate metrics into the Cosmos SDK in the most seamless way possible such that metrics may be added or removed at will and without much friction. To do this, we will use the [go-metrics](https://github.com/hashicorp/go-metrics) library. Finally, operators may enable telemetry along with specific configuration options. If enabled, metrics will be exposed via `/metrics?format={text|prometheus}` via the API server. ## Decision We will add an additional configuration block to `app.toml` that defines telemetry settings: ```toml expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ############################################################################### ### Telemetry Configuration ### ############################################################################### [telemetry] # Prefixed with keys to separate services service-name = {{ .Telemetry.ServiceName }} # Enabled enables the application telemetry functionality. When enabled, # an in-memory sink is also enabled by default. Operators may also enabled # other sinks such as Prometheus. enabled = {{ .Telemetry.Enabled }} # Enable prefixing gauge values with hostname enable-hostname = {{ .Telemetry.EnableHostname }} # Enable adding hostname to labels enable-hostname-label = {{ .Telemetry.EnableHostnameLabel }} # Enable adding service to labels enable-service-label = {{ .Telemetry.EnableServiceLabel }} # PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. prometheus-retention-time = {{ .Telemetry.PrometheusRetentionTime }} ``` The given configuration allows for two sinks -- in-memory and Prometheus. We create a `Metrics` type that performs all the bootstrapping for the operator, so capturing metrics becomes seamless. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Metrics defines a wrapper around application telemetry functionality. It allows // metrics to be gathered at any point in time. When creating a Metrics object, // internally, a global metrics is registered with a set of sinks as configured // by the operator. In addition to the sinks, when a process gets a SIGUSR1, a // dump of formatted recent metrics will be sent to STDERR. type Metrics struct { memSink *metrics.InmemSink prometheusEnabled bool } // Gather collects all registered metrics and returns a GatherResponse where the // metrics are encoded depending on the type. Metrics are either encoded via // Prometheus or JSON if in-memory. func (m *Metrics) Gather(format string) (GatherResponse, error) { switch format { case FormatPrometheus: return m.gatherPrometheus() case FormatText: return m.gatherGeneric() case FormatDefault: return m.gatherGeneric() default: return GatherResponse{ }, fmt.Errorf("unsupported metrics format: %s", format) } } ``` In addition, `Metrics` allows us to gather the current set of metrics at any given point in time. An operator may also choose to send a signal, SIGUSR1, to dump and print formatted metrics to STDERR. During an application's bootstrapping and construction phase, if `Telemetry.Enabled` is `true`, the API server will create an instance of a reference to `Metrics` object and will register a metrics handler accordingly. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (s *Server) Start(cfg config.Config) error { // ... if cfg.Telemetry.Enabled { m, err := telemetry.New(cfg.Telemetry) if err != nil { return err } s.metrics = m s.registerMetrics() } // ... } func (s *Server) registerMetrics() { metricsHandler := func(w http.ResponseWriter, r *http.Request) { format := strings.TrimSpace(r.FormValue("format")) gr, err := s.metrics.Gather(format) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err)) return } w.Header().Set("Content-Type", gr.ContentType) _, _ = w.Write(gr.Metrics) } s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET") } ``` Application developers may track counters, gauges, summaries, and key/value metrics. There is no additional lifting required by modules to leverage profiling metrics. To do so, it's as simple as: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error { defer metrics.MeasureSince(time.Now(), "MintCoins") // ... } ``` ## Consequences ### Positive * Exposure into the performance and behavior of an application ### Negative ### Neutral ## References # ADR 14: Proportional Slashing Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-014-proportional-slashing 2019-10-15: Initial draft 2020-05-25: Removed correlation root slashing 2020-07-01: Updated to include S-curve function instead of linear ## Changelog * 2019-10-15: Initial draft * 2020-05-25: Removed correlation root slashing * 2020-07-01: Updated to include S-curve function instead of linear ## Context In Proof of Stake-based chains, centralization of consensus power amongst a small set of validators can cause harm to the network due to increased risk of censorship, liveness failure, fork attacks, etc. However, while this centralization causes a negative externality to the network, it is not directly felt by the delegators contributing towards delegating towards already large validators. We would like a way to pass on the negative externality cost of centralization onto those large validators and their delegators. ## Decision ### Design To solve this problem, we will implement a procedure called Proportional Slashing. The desire is that the larger a validator is, the more they should be slashed. The first naive attempt is to make a validator's slash percent proportional to their share of consensus voting power. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} slash_amount = k * power // power is the faulting validator's voting power and k is some on-chain constant ``` However, this will incentivize validators with large amounts of stake to split up their voting power amongst accounts (sybil attack), so that if they fault, they all get slashed at a lower percent. The solution to this is to take into account not just a validator's own voting percentage, but also the voting percentage of all the other validators who get slashed in a specified time frame. ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} slash_amount = k * (power_1 + power_2 + ... + power_n) // where power_i is the voting power of the ith validator faulting in the specified time frame and k is some on-chain constant ``` Now, if someone splits a validator of 10% into two validators of 5% each which both fault, then they both fault in the same time frame, they both will get slashed at the sum 10% amount. However in practice, we likely don't want a linear relation between amount of stake at fault, and the percentage of stake to slash. In particular, solely 5% of stake double signing effectively did nothing to majorly threaten security, whereas 30% of stake being at fault clearly merits a large slashing factor, due to being very close to the point at which Tendermint security is threatened. A linear relation would require a factor of 6 gap between these two, whereas the difference in risk posed to the network is much larger. We propose using S-curves (formally [logistic functions](https://en.wikipedia.org/wiki/Logistic_function) to solve this). S-Curves capture the desired criterion quite well. They allow the slashing factor to be minimal for small values, and then grow very rapidly near some threshold point where the risk posed becomes notable. #### Parameterization This requires parameterizing a logistic function. It is very well understood how to parameterize this. It has four parameters: 1. A minimum slashing factor 2. A maximum slashing factor 3. The inflection point of the S-curve (essentially where do you want to center the S) 4. The rate of growth of the S-curve (How elongated is the S) #### Correlation across non-sybil validators One will note, that this model doesn't differentiate between multiple validators run by the same operators vs validators run by different operators. This can be seen as an additional benefit in fact. It incentivizes validators to differentiate their setups from other validators, to avoid having correlated faults with them or else they risk a higher slash. So for example, operators should avoid using the same popular cloud hosting platforms or using the same Staking as a Service providers. This will lead to a more resilient and decentralized network. #### Griefing Griefing, the act of intentionally getting oneself slashed in order to make another's slash worse, could be a concern here. However, using the protocol described here, the attacker also gets equally impacted by the grief as the victim, so it would not provide much benefit to the griefer. ### Implementation In the slashing module, we will add two queues that will track all of the recent slash events. For double sign faults, we will define "recent slashes" as ones that have occurred within the last `unbonding period`. For liveness faults, we will define "recent slashes" as ones that have occurred withing the last `jail period`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type SlashEvent struct { Address sdk.ValAddress ValidatorVotingPercent sdk.Dec SlashedSoFar sdk.Dec } ``` These slash events will be pruned from the queue once they are older than their respective "recent slash period". Whenever a new slash occurs, a `SlashEvent` struct is created with the faulting validator's voting percent and a `SlashedSoFar` of 0. Because recent slash events are pruned before the unbonding period and unjail period expires, it should not be possible for the same validator to have multiple SlashEvents in the same Queue at the same time. We then will iterate over all the SlashEvents in the queue, adding their `ValidatorVotingPercent` to calculate the new percent to slash all the validators in the queue at, using the "Square of Sum of Roots" formula introduced above. Once we have the `NewSlashPercent`, we then iterate over all the `SlashEvent`s in the queue once again, and if `NewSlashPercent > SlashedSoFar` for that SlashEvent, we call the `staking.Slash(slashEvent.Address, slashEvent.Power, Math.Min(Math.Max(minSlashPercent, NewSlashPercent - SlashedSoFar), maxSlashPercent)` (we pass in the power of the validator before any slashes occurred, so that we slash the right amount of tokens). We then set `SlashEvent.SlashedSoFar` amount to `NewSlashPercent`. ## Status Proposed ## Consequences ### Positive * Increases decentralization by disincentivizing delegating to large validators * Incentivizes Decorrelation of Validators * More severely punishes attacks than accidental faults * More flexibility in slashing rates parameterization ### Negative * More computationally expensive than current implementation. Will require more data about "recent slashing events" to be stored on chain. # ADR 016: Validator Consensus Key Rotation Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-016-validator-consensus-key-rotation 2019 Oct 23: Initial draft 2019 Nov 28: Add key rotation fee ## Changelog * 2019 Oct 23: Initial draft * 2019 Nov 28: Add key rotation fee ## Context Validator consensus key rotation feature has been discussed and requested for a long time, for the sake of safer validator key management policy (e.g. [Link](https://github.com/tendermint/tendermint/issues/1136)). So, we suggest one of the simplest form of validator consensus key rotation implementation mostly onto Cosmos SDK. We don't need to make any update on consensus logic in Tendermint because Tendermint does not have any mapping information of consensus key and validator operator key, meaning that from Tendermint point of view, a consensus key rotation of a validator is simply a replacement of a consensus key to another. Also, it should be noted that this ADR includes only the simplest form of consensus key rotation without considering multiple consensus keys concept. Such multiple consensus keys concept shall remain a long term goal of Tendermint and Cosmos SDK. ## Decision ### Pseudo procedure for consensus key rotation * create new random consensus key. * create and broadcast a transaction with a `MsgRotateConsPubKey` that states the new consensus key is now coupled with the validator operator with signature from the validator's operator key. * old consensus key becomes unable to participate on consensus immediately after the update of key mapping state on-chain. * start validating with new consensus key. * validators using HSM and KMS should update the consensus key in HSM to use the new rotated key after the height `h` when `MsgRotateConsPubKey` committed to the blockchain. ### Considerations * consensus key mapping information management strategy * store history of each key mapping changes in the kvstore. * the state machine can search corresponding consensus key paired with given validator operator for any arbitrary height in a recent unbonding period. * the state machine does not need any historical mapping information which is past more than unbonding period. * key rotation costs related to LCD and IBC * LCD and IBC will have traffic/computation burden when there exists frequent power changes * In current Tendermint design, consensus key rotations are seen as power changes from LCD or IBC perspective * Therefore, to minimize unnecessary frequent key rotation behavior, we limited maximum number of rotation in recent unbonding period and also applied exponentially increasing rotation fee * limits * a validator cannot rotate its consensus key more than `MaxConsPubKeyRotations` time for any unbonding period, to prevent spam. * parameters can be decided by governance and stored in genesis file. * key rotation fee * a validator should pay `KeyRotationFee` to rotate the consensus key which is calculated as below * `KeyRotationFee` = (max(`VotingPowerPercentage` *100, 1)* `InitialKeyRotationFee`) \* 2^(number of rotations in `ConsPubKeyRotationHistory` in recent unbonding period) * evidence module * evidence module can search corresponding consensus key for any height from slashing keeper so that it can decide which consensus key is supposed to be used for given height. * abci.ValidatorUpdate * tendermint already has ability to change a consensus key by ABCI communication(`ValidatorUpdate`). * validator consensus key update can be done via creating new + delete old by change the power to zero. * therefore, we expect we even do not need to change tendermint codebase at all to implement this feature. * new genesis parameters in `staking` module * `MaxConsPubKeyRotations` : maximum number of rotation can be executed by a validator in recent unbonding period. default value 10 is suggested(11th key rotation will be rejected) * `InitialKeyRotationFee` : the initial key rotation fee when no key rotation has happened in recent unbonding period. default value 1atom is suggested(1atom fee for the first key rotation in recent unbonding period) ### Workflow 1. The validator generates a new consensus keypair. 2. The validator generates and signs a `MsgRotateConsPubKey` tx with their operator key and new ConsPubKey ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgRotateConsPubKey struct { ValidatorAddress sdk.ValAddress NewPubKey crypto.PubKey } ``` 3. `handleMsgRotateConsPubKey` gets `MsgRotateConsPubKey`, calls `RotateConsPubKey` with emits event 4. `RotateConsPubKey` * checks if `NewPubKey` is not duplicated on `ValidatorsByConsAddr` * checks if the validator is does not exceed parameter `MaxConsPubKeyRotations` by iterating `ConsPubKeyRotationHistory` * checks if the signing account has enough balance to pay `KeyRotationFee` * pays `KeyRotationFee` to community fund * overwrites `NewPubKey` in `validator.ConsPubKey` * deletes old `ValidatorByConsAddr` * `SetValidatorByConsAddr` for `NewPubKey` * Add `ConsPubKeyRotationHistory` for tracking rotation ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ConsPubKeyRotationHistory struct { OperatorAddress sdk.ValAddress OldConsPubKey crypto.PubKey NewConsPubKey crypto.PubKey RotatedHeight int64 } ``` 5. `ApplyAndReturnValidatorSetUpdates` checks if there is `ConsPubKeyRotationHistory` with `ConsPubKeyRotationHistory.RotatedHeight == ctx.BlockHeight()` and if so, generates 2 `ValidatorUpdate` , one for a remove validator and one for create new validator ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} abci.ValidatorUpdate{ PubKey: cmttypes.TM2PB.PubKey(OldConsPubKey), Power: 0, } abci.ValidatorUpdate{ PubKey: cmttypes.TM2PB.PubKey(NewConsPubKey), Power: v.ConsensusPower(), } ``` 6. at `previousVotes` Iteration logic of `AllocateTokens`, `previousVote` using `OldConsPubKey` match up with `ConsPubKeyRotationHistory`, and replace validator for token allocation 7. Migrate `ValidatorSigningInfo` and `ValidatorMissedBlockBitArray` from `OldConsPubKey` to `NewConsPubKey` * Note : All above features shall be implemented in `staking` module. ## Status Proposed ## Consequences ### Positive * Validators can immediately or periodically rotate their consensus key to have better security policy * improved security against Long-Range attacks given a validator throws away the old consensus key(s) ### Negative * Slash module needs more computation because it needs to lookup corresponding consensus key of validators for each height * frequent key rotations will make light client bisection less efficient ### Neutral ## References * on tendermint repo : [Link](https://github.com/tendermint/tendermint/issues/1136) * on cosmos-sdk repo : [Link](https://github.com/cosmos/cosmos-sdk/issues/5231) * about multiple consensus keys : [Link](https://github.com/tendermint/tendermint/issues/1758#issuecomment-545291698) # ADR 17: Historical Header Module Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-017-historical-header-module 26 November 2019: Start of first version 2 December 2019: Final draft of first version ## Changelog * 26 November 2019: Start of first version * 2 December 2019: Final draft of first version ## Context In order for the Cosmos SDK to implement the [IBC specification](https://github.com/cosmos/ics), modules within the Cosmos SDK must have the ability to introspect recent consensus states (validator sets & commitment roots) as proofs of these values on other chains must be checked during the handshakes. ## Decision The application MUST store the most recent `n` headers in a persistent store. At first, this store MAY be the current Merklised store. A non-Merklised store MAY be used later as no proofs are necessary. The application MUST store this information by storing new headers immediately when handling `abci.RequestBeginBlock`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func BeginBlock(ctx sdk.Context, keeper HistoricalHeaderKeeper, req abci.RequestBeginBlock) abci.ResponseBeginBlock { info := HistoricalInfo{ Header: ctx.BlockHeader(), ValSet: keeper.StakingKeeper.GetAllValidators(ctx), // note that this must be stored in a canonical order } keeper.SetHistoricalInfo(ctx, ctx.BlockHeight(), info) n := keeper.GetParamRecentHeadersToStore() keeper.PruneHistoricalInfo(ctx, ctx.BlockHeight() - n) // continue handling request } ``` Alternatively, the application MAY store only the hash of the validator set. The application MUST make these past `n` committed headers available for querying by Cosmos SDK modules through the `Keeper`'s `GetHistoricalInfo` function. This MAY be implemented in a new module, or it MAY also be integrated into an existing one (likely `x/staking` or `x/ibc`). `n` MAY be configured as a parameter store parameter, in which case it could be changed by `ParameterChangeProposal`s, although it will take some blocks for the stored information to catch up if `n` is increased. ## Status Proposed. ## Consequences Implementation of this ADR will require changes to the Cosmos SDK. It will not require changes to Tendermint. ### Positive * Easy retrieval of headers & state roots for recent past heights by modules anywhere in the Cosmos SDK. * No RPC calls to Tendermint required. * No ABCI alterations required. ### Negative * Duplicates `n` headers data in Tendermint & the application (additional disk usage) - in the long term, an approach such as [this](https://github.com/tendermint/tendermint/issues/4210) might be preferable. ### Neutral (none known) ## References * [ICS 2: "Consensus state introspection"](https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics#consensus-state-introspection) # ADR 18: Extendable Voting Periods Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-018-extendable-voting-period 1 January 2020: Start of first version ## Changelog * 1 January 2020: Start of first version ## Context Currently the voting period for all governance proposals is the same. However, this is suboptimal as all governance proposals do not require the same time period. For more non-contentious proposals, they can be dealt with more efficiently with a faster period, while more contentious or complex proposals may need a longer period for extended discussion/consideration. ## Decision We would like to design a mechanism for making the voting period of a governance proposal variable based on the demand of voters. We would like it to be based on the view of the governance participants, rather than just the proposer of a governance proposal (thus, allowing the proposer to select the voting period length is not sufficient). However, we would like to avoid the creation of an entire second voting process to determine the length of the voting period, as it just pushed the problem to determining the length of that first voting period. Thus, we propose the following mechanism: ### Params * The current gov param `VotingPeriod` is to be replaced by a `MinVotingPeriod` param. This is the default voting period that all governance proposal voting periods start with. * There is a new gov param called `MaxVotingPeriodExtension`. ### Mechanism There is a new `Msg` type called `MsgExtendVotingPeriod`, which can be sent by any staked account during a proposal's voting period. It allows the sender to unilaterally extend the length of the voting period by `MaxVotingPeriodExtension * sender's share of voting power`. Every address can only call `MsgExtendVotingPeriod` once per proposal. So for example, if the `MaxVotingPeriodExtension` is set to 100 Days, then anyone with 1% of voting power can extend the voting power by 1 day. If 33% of voting power has sent the message, the voting period will be extended by 33 days. Thus, if absolutely everyone chooses to extend the voting period, the absolute maximum voting period will be `MinVotingPeriod + MaxVotingPeriodExtension`. This system acts as a sort of distributed coordination, where individual stakers choosing to extend or not, allows the system the guage the conentiousness/complexity of the proposal. It is extremely unlikely that many stakers will choose to extend at the exact same time, it allows stakers to view how long others have already extended thus far, to decide whether or not to extend further. ### Dealing with Unbonding/Redelegation There is one thing that needs to be addressed. How to deal with redelegation/unbonding during the voting period. If a staker of 5% calls `MsgExtendVotingPeriod` and then unbonds, does the voting period then decrease by 5 days again? This is not good as it can give people a false sense of how long they have to make their decision. For this reason, we want to design it such that the voting period length can only be extended, not shortened. To do this, the current extension amount is based on the highest percent that voted extension at any time. This is best explained by example: 1. Let's say 2 stakers of voting power 4% and 3% respectively vote to extend. The voting period will be extended by 7 days. 2. Now the staker of 3% decides to unbond before the end of the voting period. The voting period extension remains 7 days. 3. Now, let's say another staker of 2% voting power decides to extend voting period. There is now 6% of active voting power choosing the extend. The voting power remains 7 days. 4. If a fourth staker of 10% chooses to extend now, there is a total of 16% of active voting power wishing to extend. The voting period will be extended to 16 days. ### Delegators Just like votes in the actual voting period, delegators automatically inherit the extension of their validators. If their validator chooses to extend, their voting power will be used in the validator's extension. However, the delegator is unable to override their validator and "unextend" as that would contradict the "voting power length can only be ratcheted up" principle described in the previous section. However, a delegator may choose the extend using their personal voting power, if their validator has not done so. ## Status Proposed ## Consequences ### Positive * More complex/contentious governance proposals will have more time to properly digest and deliberate ### Negative * Governance process becomes more complex and requires more understanding to interact with effectively * Can no longer predict when a governance proposal will end. Can't assume order in which governance proposals will end. ### Neutral * The minimum voting period can be made shorter ## References * [Cosmos Forum post where idea first originated](https://forum.cosmos.network/t/proposal-draft-reduce-governance-voting-period-to-7-days/3032/9) # ADR 019: Protocol Buffer State Encoding Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-019-protobuf-state-encoding ## Changelog * 2020 Feb 15: Initial Draft * 2020 Feb 24: Updates to handle messages with interface fields * 2020 Apr 27: Convert usages of `oneof` for interfaces to `Any` * 2020 May 15: Describe `cosmos_proto` extensions and amino compatibility * 2020 Dec 4: Move and rename `MarshalAny` and `UnmarshalAny` into the `codec.Codec` interface. * 2021 Feb 24: Remove mentions of `HybridCodec`, which has been abandoned in [#6843](https://github.com/cosmos/cosmos-sdk/pull/6843). ## Status Accepted ## Context Currently, the Cosmos SDK utilizes [go-amino](https://github.com/tendermint/go-amino/) for binary and JSON object encoding over the wire bringing parity between logical objects and persistence objects. From the Amino docs: > Amino is an object encoding specification. It is a subset of Proto3 with an extension for interface > support. See the [Proto3 spec](https://developers.google.com/protocol-buffers/docs/proto3) for more > information on Proto3, which Amino is largely compatible with (but not with Proto2). > > The goal of the Amino encoding protocol is to bring parity into logic objects and persistence objects. Amino also aims to have the following goals (not a complete list): * Binary bytes must be decode-able with a schema. * Schema must be upgradeable. * The encoder and decoder logic must be reasonably simple. However, we believe that Amino does not fulfill these goals completely and does not fully meet the needs of a truly flexible cross-language and multi-client compatible encoding protocol in the Cosmos SDK. Namely, Amino has proven to be a big pain-point in regards to supporting object serialization across clients written in various languages while providing virtually little in the way of true backwards compatibility and upgradeability. Furthermore, through profiling and various benchmarks, Amino has been shown to be an extremely large performance bottleneck in the Cosmos SDK 1. This is largely reflected in the performance of simulations and application transaction throughput. Thus, we need to adopt an encoding protocol that meets the following criteria for state serialization: * Language agnostic * Platform agnostic * Rich client support and thriving ecosystem * High performance * Minimal encoded message size * Codegen-based over reflection-based * Supports backward and forward compatibility Note, migrating away from Amino should be viewed as a two-pronged approach, state and client encoding. This ADR focuses on state serialization in the Cosmos SDK state machine. A corresponding ADR will be made to address client-side encoding. ## Decision We will adopt [Protocol Buffers](https://developers.google.com/protocol-buffers) for serializing persisted structured data in the Cosmos SDK while providing a clean mechanism and developer UX for applications wishing to continue to use Amino. We will provide this mechanism by updating modules to accept a codec interface, `Marshaler`, instead of a concrete Amino codec. Furthermore, the Cosmos SDK will provide two concrete implementations of the `Marshaler` interface: `AminoCodec` and `ProtoCodec`. * `AminoCodec`: Uses Amino for both binary and JSON encoding. * `ProtoCodec`: Uses Protobuf for both binary and JSON encoding. Modules will use whichever codec that is instantiated in the app. By default, the Cosmos SDK's `simapp` instantiates a `ProtoCodec` as the concrete implementation of `Marshaler`, inside the `MakeTestEncodingConfig` function. This can be easily overwritten by app developers if they so desire. The ultimate goal will be to replace Amino JSON encoding with Protobuf encoding and thus have modules accept and/or extend `ProtoCodec`. Until then, Amino JSON is still provided for legacy use-cases. A handful of places in the Cosmos SDK still have Amino JSON hardcoded, such as the Legacy API REST endpoints and the `x/params` store. They are planned to be converted to Protobuf in a gradual manner. ### Module Codecs Modules that do not require the ability to work with and serialize interfaces, the path to Protobuf migration is pretty straightforward. These modules are to simply migrate any existing types that are encoded and persisted via their concrete Amino codec to Protobuf and have their keeper accept a `Marshaler` that will be a `ProtoCodec`. This migration is simple as things will just work as-is. Note, any business logic that needs to encode primitive types like `bool` or `int64` should use [gogoprotobuf](https://github.com/cosmos/gogoproto) Value types. Example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ts, err := gogotypes.TimestampProto(completionTime) if err != nil { // ... } bz := cdc.MustMarshal(ts) ``` However, modules can vary greatly in purpose and design and so we must support the ability for modules to be able to encode and work with interfaces (e.g. `Account` or `Content`). For these modules, they must define their own codec interface that extends `Marshaler`. These specific interfaces are unique to the module and will contain method contracts that know how to serialize the needed interfaces. Example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/auth/types/codec.go type Codec interface { codec.Codec MarshalAccount(acc exported.Account) ([]byte, error) UnmarshalAccount(bz []byte) (exported.Account, error) MarshalAccountJSON(acc exported.Account) ([]byte, error) UnmarshalAccountJSON(bz []byte) (exported.Account, error) } ``` ### Usage of `Any` to encode interfaces In general, module-level .proto files should define messages which encode interfaces using [`google.protobuf.Any`](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). After [extension discussion](https://github.com/cosmos/cosmos-sdk/issues/6030), this was chosen as the preferred alternative to application-level `oneof`s as in our original protobuf design. The arguments in favor of `Any` can be summarized as follows: * `Any` provides a simpler, more consistent client UX for dealing with interfaces than app-level `oneof`s that will need to be coordinated more carefully across applications. Creating a generic transaction signing library using `oneof`s may be cumbersome and critical logic may need to be reimplemented for each chain * `Any` provides more resistance against human error than `oneof` * `Any` is generally simpler to implement for both modules and apps The main counter-argument to using `Any` centers around its additional space and possibly performance overhead. The space overhead could be dealt with using compression at the persistence layer in the future and the performance impact is likely to be small. Thus, not using `Any` is seem as a pre-mature optimization, with user experience as the higher order concern. Note, that given the Cosmos SDK's decision to adopt the `Codec` interfaces described above, apps can still choose to use `oneof` to encode state and transactions but it is not the recommended approach. If apps do choose to use `oneof`s instead of `Any` they will likely lose compatibility with client apps that support multiple chains. Thus developers should think carefully about whether they care more about what is possibly a pre-mature optimization or end-user and client developer UX. ### Safe usage of `Any` By default, the [gogo protobuf implementation of `Any`](https://pkg.go.dev/github.com/cosmos/gogoproto/types) uses [global type registration](https://github.com/cosmos/gogoproto/blob/master/proto/properties.go#L540) to decode values packed in `Any` into concrete go types. This introduces a vulnerability where any malicious module in the dependency tree could register a type with the global protobuf registry and cause it to be loaded and unmarshaled by a transaction that referenced it in the `type_url` field. To prevent this, we introduce a type registration mechanism for decoding `Any` values into concrete types through the `InterfaceRegistry` interface which bears some similarity to type registration with Amino: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type InterfaceRegistry interface { // RegisterInterface associates protoName as the public name for the // interface passed in as iface // Ex: // registry.RegisterInterface("cosmos_sdk.Msg", (*sdk.Msg)(nil)) RegisterInterface(protoName string, iface interface{ }) // RegisterImplementations registers impls as a concrete implementations of // the interface iface // Ex: // registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{ }, &MsgMultiSend{ }) RegisterImplementations(iface interface{ }, impls ...proto.Message) } ``` In addition to serving as a whitelist, `InterfaceRegistry` can also serve to communicate the list of concrete types that satisfy an interface to clients. In .proto files: * fields which accept interfaces should be annotated with `cosmos_proto.accepts_interface` using the same full-qualified name passed as `protoName` to `InterfaceRegistry.RegisterInterface` * interface implementations should be annotated with `cosmos_proto.implements_interface` using the same full-qualified name passed as `protoName` to `InterfaceRegistry.RegisterInterface` In the future, `protoName`, `cosmos_proto.accepts_interface`, `cosmos_proto.implements_interface` may be used via code generation, reflection &/or static linting. The same struct that implements `InterfaceRegistry` will also implement an interface `InterfaceUnpacker` to be used for unpacking `Any`s: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type InterfaceUnpacker interface { // UnpackAny unpacks the value in any to the interface pointer passed in as // iface. Note that the type in any must have been registered with // RegisterImplementations as a concrete type for that interface // Ex: // var msg sdk.Msg // err := ctx.UnpackAny(any, &msg) // ... UnpackAny(any *Any, iface interface{ }) error } ``` Note that `InterfaceRegistry` usage does not deviate from standard protobuf usage of `Any`, it just introduces a security and introspection layer for golang usage. `InterfaceRegistry` will be a member of `ProtoCodec` described above. In order for modules to register interface types, app modules can optionally implement the following interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type InterfaceModule interface { RegisterInterfaceTypes(InterfaceRegistry) } ``` The module manager will include a method to call `RegisterInterfaceTypes` on every module that implements it in order to populate the `InterfaceRegistry`. ### Using `Any` to encode state The Cosmos SDK will provide support methods `MarshalInterface` and `UnmarshalInterface` to hide a complexity of wrapping interface types into `Any` and allow easy serialization. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import "github.com/cosmos/cosmos-sdk/codec" // note: eviexported.Evidence is an interface type func MarshalEvidence(cdc codec.BinaryCodec, e eviexported.Evidence) ([]byte, error) { return cdc.MarshalInterface(e) } func UnmarshalEvidence(cdc codec.BinaryCodec, bz []byte) (eviexported.Evidence, error) { var evi eviexported.Evidence err := cdc.UnmarshalInterface(&evi, bz) return err, nil } ``` ### Using `Any` in `sdk.Msg`s A similar concept is to be applied for messages that contain interfaces fields. For example, we can define `MsgSubmitEvidence` as follows where `Evidence` is an interface: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/evidence/types/types.proto message MsgSubmitEvidence { bytes submitter = 1 [ (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; google.protobuf.Any evidence = 2; } ``` Note that in order to unpack the evidence from `Any` we do need a reference to `InterfaceRegistry`. In order to reference evidence in methods like `ValidateBasic` which shouldn't have to know about the `InterfaceRegistry`, we introduce an `UnpackInterfaces` phase to deserialization which unpacks interfaces before they're needed. ### Unpacking Interfaces To implement the `UnpackInterfaces` phase of deserialization which unpacks interfaces wrapped in `Any` before they're needed, we create an interface that `sdk.Msg`s and other types can implement: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type UnpackInterfacesMessage interface { UnpackInterfaces(InterfaceUnpacker) error } ``` We also introduce a private `cachedValue interface{}` field onto the `Any` struct itself with a public getter `GetCachedValue() interface{}`. The `UnpackInterfaces` method is to be invoked during message deserialization right after `Unmarshal` and any interface values packed in `Any`s will be decoded and stored in `cachedValue` for reference later. Then unpacked interface values can safely be used in any code afterwards without knowledge of the `InterfaceRegistry` and messages can introduce a simple getter to cast the cached value to the correct interface type. This has the added benefit that unmarshaling of `Any` values only happens once during initial deserialization rather than every time the value is read. Also, when `Any` values are first packed (for instance in a call to `NewMsgSubmitEvidence`), the original interface value is cached so that unmarshaling isn't needed to read it again. `MsgSubmitEvidence` could implement `UnpackInterfaces`, plus a convenience getter `GetEvidence` as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (msg MsgSubmitEvidence) UnpackInterfaces(ctx sdk.InterfaceRegistry) error { var evi eviexported.Evidence return ctx.UnpackAny(msg.Evidence, *evi) } func (msg MsgSubmitEvidence) GetEvidence() eviexported.Evidence { return msg.Evidence.GetCachedValue().(eviexported.Evidence) } ``` ### Amino Compatibility Our custom implementation of `Any` can be used transparently with Amino if used with the proper codec instance. What this means is that interfaces packed within `Any`s will be amino marshaled like regular Amino interfaces (assuming they have been registered properly with Amino). In order for this functionality to work: * **all legacy code must use `*codec.LegacyAmino` instead of `*amino.Codec` which is now a wrapper which properly handles `Any`** * **all new code should use `Marshaler` which is compatible with both amino and protobuf** * Also, before v0.39, `codec.LegacyAmino` will be renamed to `codec.LegacyAmino`. ### Why Wasn't X Chosen Instead For a more complete comparison to alternative protocols, see [here](https://codeburst.io/json-vs-protocol-buffers-vs-flatbuffers-a4247f8bda6f). ### Cap'n Proto While [Cap’n Proto](https://capnproto.org/) does seem like an advantageous alternative to Protobuf due to it's native support for interfaces/generics and built in canonicalization, it does lack the rich client ecosystem compared to Protobuf and is a bit less mature. ### FlatBuffers [FlatBuffers](https://google.github.io/flatbuffers/) is also a potentially viable alternative, with the primary difference being that FlatBuffers does not need a parsing/unpacking step to a secondary representation before you can access data, often coupled with per-object memory allocation. However, it would require great efforts into research and full understanding the scope of the migration and path forward -- which isn't immediately clear. In addition, FlatBuffers aren't designed for untrusted inputs. ## Future Improvements & Roadmap In the future we may consider a compression layer right above the persistence layer which doesn't change tx or merkle tree hashes, but reduces the storage overhead of `Any`. In addition, we may adopt protobuf naming conventions which make type URLs a bit more concise while remaining descriptive. Additional code generation support around the usage of `Any` is something that could also be explored in the future to make the UX for go developers more seamless. ## Consequences ### Positive * Significant performance gains. * Supports backward and forward type compatibility. * Better support for cross-language clients. ### Negative * Learning curve required to understand and implement Protobuf messages. * Slightly larger message size due to use of `Any`, although this could be offset by a compression layer in the future ### Neutral ## References 1. [Link](https://github.com/cosmos/cosmos-sdk/issues/4977) 2. [Link](https://github.com/cosmos/cosmos-sdk/issues/5444) # ADR 020: Protocol Buffer Transaction Encoding Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-020-protobuf-transaction-encoding ## Changelog * 2020 March 06: Initial Draft * 2020 March 12: API Updates * 2020 April 13: Added details on interface `oneof` handling * 2020 April 30: Switch to `Any` * 2020 May 14: Describe public key encoding * 2020 June 08: Store `TxBody` and `AuthInfo` as bytes in `SignDoc`; Document `TxRaw` as broadcast and storage type. * 2020 August 07: Use ADR 027 for serializing `SignDoc`. * 2020 August 19: Move sequence field from `SignDoc` to `SignerInfo`, as discussed in [#6966](https://github.com/cosmos/cosmos-sdk/issues/6966). * 2020 September 25: Remove `PublicKey` type in favor of `secp256k1.PubKey`, `ed25519.PubKey` and `multisig.LegacyAminoPubKey`. * 2020 October 15: Add `GetAccount` and `GetAccountWithHeight` methods to the `AccountRetriever` interface. * 2021 Feb 24: The Cosmos SDK does not use Tendermint's `PubKey` interface anymore, but its own `cryptotypes.PubKey`. Updates to reflect this. * 2021 May 3: Rename `clientCtx.JSONMarshaler` to `clientCtx.JSONCodec`. * 2021 June 10: Add `clientCtx.Codec: codec.Codec`. ## Status Accepted ## Context This ADR is a continuation of the motivation, design, and context established in [ADR 019](/sdk/v0.50/build/architecture/adr-019-protobuf-state-encoding), namely, we aim to design the Protocol Buffer migration path for the client-side of the Cosmos SDK. Specifically, the client-side migration path primarily includes tx generation and signing, message construction and routing, in addition to CLI & REST handlers and business logic (i.e. queriers). With this in mind, we will tackle the migration path via two main areas, txs and querying. However, this ADR solely focuses on transactions. Querying should be addressed in a future ADR, but it should build off of these proposals. Based on detailed discussions ([#6030](https://github.com/cosmos/cosmos-sdk/issues/6030) and [#6078](https://github.com/cosmos/cosmos-sdk/issues/6078)), the original design for transactions was changed substantially from an `oneof` /JSON-signing approach to the approach described below. ## Decision ### Transactions Since interface values are encoded with `google.protobuf.Any` in state (see [ADR 019](/sdk/latest/reference/architecture/adr-019-protobuf-state-encoding)), `sdk.Msg`s are encoding with `Any` in transactions. One of the main goals of using `Any` to encode interface values is to have a core set of types which is reused by apps so that clients can safely be compatible with as many chains as possible. It is one of the goals of this specification to provide a flexible cross-chain transaction format that can serve a wide variety of use cases without breaking client compatibility. In order to facilitate signing, transactions are separated into `TxBody`, which will be re-used by `SignDoc` below, and `signatures`: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // types/types.proto package cosmos_sdk.v1; message Tx { TxBody body = 1; AuthInfo auth_info = 2; // A list of signatures that matches the length and order of AuthInfo's signer_infos to // allow connecting signature meta information like public key and signing mode by position. repeated bytes signatures = 3; } // A variant of Tx that pins the signer's exact binary represenation of body and // auth_info. This is used for signing, broadcasting and verification. The binary // `serialize(tx: TxRaw)` is stored in Tendermint and the hash `sha256(serialize(tx: TxRaw))` // becomes the "txhash", commonly used as the transaction ID. message TxRaw { // A protobuf serialization of a TxBody that matches the representation in SignDoc. bytes body = 1; // A protobuf serialization of an AuthInfo that matches the representation in SignDoc. bytes auth_info = 2; // A list of signatures that matches the length and order of AuthInfo's signer_infos to // allow connecting signature meta information like public key and signing mode by position. repeated bytes signatures = 3; } message TxBody { // A list of messages to be executed. The required signers of those messages define // the number and order of elements in AuthInfo's signer_infos and Tx's signatures. // Each required signer address is added to the list only the first time it occurs. // // By convention, the first required signer (usually from the first message) is referred // to as the primary signer and pays the fee for the whole transaction. repeated google.protobuf.Any messages = 1; string memo = 2; int64 timeout_height = 3; repeated google.protobuf.Any extension_options = 1023; } message AuthInfo { // This list defines the signing modes for the required signers. The number // and order of elements must match the required signers from TxBody's messages. // The first element is the primary signer and the one which pays the fee. repeated SignerInfo signer_infos = 1; // The fee can be calculated based on the cost of evaluating the body and doing signature verification of the signers. This can be estimated via simulation. Fee fee = 2; } message SignerInfo { // The public key is optional for accounts that already exist in state. If unset, the // verifier can use the required signer address for this position and lookup the public key. google.protobuf.Any public_key = 1; // ModeInfo describes the signing mode of the signer and is a nested // structure to support nested multisig pubkey's ModeInfo mode_info = 2; // sequence is the sequence of the account, which describes the // number of committed transactions signed by a given address. It is used to prevent // replay attacks. uint64 sequence = 3; } message ModeInfo { oneof sum { Single single = 1; Multi multi = 2; } // Single is the mode info for a single signer. It is structured as a message // to allow for additional fields such as locale for SIGN_MODE_TEXTUAL in the future message Single { SignMode mode = 1; } // Multi is the mode info for a multisig public key message Multi { // bitarray specifies which keys within the multisig are signing CompactBitArray bitarray = 1; // mode_infos is the corresponding modes of the signers of the multisig // which could include nested multisig public keys repeated ModeInfo mode_infos = 2; } } enum SignMode { SIGN_MODE_UNSPECIFIED = 0; SIGN_MODE_DIRECT = 1; SIGN_MODE_TEXTUAL = 2; SIGN_MODE_LEGACY_AMINO_JSON = 127; } ``` As will be discussed below, in order to include as much of the `Tx` as possible in the `SignDoc`, `SignerInfo` is separated from signatures so that only the raw signatures themselves live outside of what is signed over. Because we are aiming for a flexible, extensible cross-chain transaction format, new transaction processing options should be added to `TxBody` as soon those use cases are discovered, even if they can't be implemented yet. Because there is coordination overhead in this, `TxBody` includes an `extension_options` field which can be used for any transaction processing options that are not already covered. App developers should, nevertheless, attempt to upstream important improvements to `Tx`. ### Signing All of the signing modes below aim to provide the following guarantees: * **No Malleability**: `TxBody` and `AuthInfo` cannot change once the transaction is signed * **Predictable Gas**: if I am signing a transaction where I am paying a fee, the final gas is fully dependent on what I am signing These guarantees give the maximum amount confidence to message signers that manipulation of `Tx`s by intermediaries can't result in any meaningful changes. #### `SIGN_MODE_DIRECT` The "direct" signing behavior is to sign the raw `TxBody` bytes as broadcast over the wire. This has the advantages of: * requiring the minimum additional client capabilities beyond a standard protocol buffers implementation * leaving effectively zero holes for transaction malleability (i.e. there are no subtle differences between the signing and encoding formats which could potentially be exploited by an attacker) Signatures are structured using the `SignDoc` below which reuses the serialization of `TxBody` and `AuthInfo` and only adds the fields which are needed for signatures: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // types/types.proto message SignDoc { // A protobuf serialization of a TxBody that matches the representation in TxRaw. bytes body = 1; // A protobuf serialization of an AuthInfo that matches the representation in TxRaw. bytes auth_info = 2; string chain_id = 3; uint64 account_number = 4; } ``` In order to sign in the default mode, clients take the following steps: 1. Serialize `TxBody` and `AuthInfo` using any valid protobuf implementation. 2. Create a `SignDoc` and serialize it using [ADR 027](/sdk/v0.50/build/architecture/adr-027-deterministic-protobuf-serialization). 3. Sign the encoded `SignDoc` bytes. 4. Build a `TxRaw` and serialize it for broadcasting. Signature verification is based on comparing the raw `TxBody` and `AuthInfo` bytes encoded in `TxRaw` not based on any ["canonicalization"](https://github.com/regen-network/canonical-proto3) algorithm which creates added complexity for clients in addition to preventing some forms of upgradeability (to be addressed later in this document). Signature verifiers do: 1. Deserialize a `TxRaw` and pull out `body` and `auth_info`. 2. Create a list of required signer addresses from the messages. 3. For each required signer: * Pull account number and sequence from the state. * Obtain the public key either from state or `AuthInfo`'s `signer_infos`. * Create a `SignDoc` and serialize it using [ADR 027](/sdk/v0.50/build/architecture/adr-027-deterministic-protobuf-serialization). * Verify the signature at the same list position against the serialized `SignDoc`. #### `SIGN_MODE_LEGACY_AMINO` In order to support legacy wallets and exchanges, Amino JSON will be temporarily supported transaction signing. Once wallets and exchanges have had a chance to upgrade to protobuf based signing, this option will be disabled. In the meantime, it is foreseen that disabling the current Amino signing would cause too much breakage to be feasible. Note that this is mainly a requirement of the Cosmos Hub and other chains may choose to disable Amino signing immediately. Legacy clients will be able to sign a transaction using the current Amino JSON format and have it encoded to protobuf using the REST `/tx/encode` endpoint before broadcasting. #### `SIGN_MODE_TEXTUAL` As was discussed extensively in [#6078](https://github.com/cosmos/cosmos-sdk/issues/6078), there is a desire for a human-readable signing encoding, especially for hardware wallets like the [Ledger](https://www.ledger.com) which display transaction contents to users before signing. JSON was an attempt at this but falls short of the ideal. `SIGN_MODE_TEXTUAL` is intended as a placeholder for a human-readable encoding which will replace Amino JSON. This new encoding should be even more focused on readability than JSON, possibly based on formatting strings like [MessageFormat](http://userguide.icu-project.org/formatparse/messages). In order to ensure that the new human-readable format does not suffer from transaction malleability issues, `SIGN_MODE_TEXTUAL` requires that the *human-readable bytes are concatenated with the raw `SignDoc`* to generate sign bytes. Multiple human-readable formats (maybe even localized messages) may be supported by `SIGN_MODE_TEXTUAL` when it is implemented. ### Unknown Field Filtering Unknown fields in protobuf messages should generally be rejected by transaction processors because: * important data may be present in the unknown fields, that if ignored, will cause unexpected behavior for clients * they present a malleability vulnerability where attackers can bloat tx size by adding random uninterpreted data to unsigned content (i.e. the master `Tx`, not `TxBody`) There are also scenarios where we may choose to safely ignore unknown fields ([Link](https://github.com/cosmos/cosmos-sdk/issues/6078#issuecomment-624400188)) to provide graceful forwards compatibility with newer clients. We propose that field numbers with bit 11 set (for most use cases this is the range of 1024-2047) be considered non-critical fields that can safely be ignored if unknown. To handle this we will need an unknown field filter that: * always rejects unknown fields in unsigned content (i.e. top-level `Tx` and unsigned parts of `AuthInfo` if present based on the signing mode) * rejects unknown fields in all messages (including nested `Any`s) other than fields with bit 11 set This will likely need to be a custom protobuf parser pass that takes message bytes and `FileDescriptor`s and returns a boolean result. ### Public Key Encoding Public keys in the Cosmos SDK implement the `cryptotypes.PubKey` interface. We propose to use `Any` for protobuf encoding as we are doing with other interfaces (for example, in `BaseAccount.PubKey` and `SignerInfo.PublicKey`). The following public keys are implemented: secp256k1, secp256r1, ed25519 and legacy-multisignature. Ex: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message PubKey { bytes key = 1; } ``` `multisig.LegacyAminoPubKey` has an array of `Any`'s member to support any protobuf public key type. Apps should only attempt to handle a registered set of public keys that they have tested. The provided signature verification ante handler decorators will enforce this. ### CLI & REST Currently, the REST and CLI handlers encode and decode types and txs via Amino JSON encoding using a concrete Amino codec. Being that some of the types dealt with in the client can be interfaces, similar to how we described in [ADR 019](/sdk/v0.50/build/architecture/adr-019-protobuf-state-encoding), the client logic will now need to take a codec interface that knows not only how to handle all the types, but also knows how to generate transactions, signatures, and messages. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type AccountRetriever interface { GetAccount(clientCtx Context, addr sdk.AccAddress) (client.Account, error) GetAccountWithHeight(clientCtx Context, addr sdk.AccAddress) (client.Account, int64, error) EnsureExists(clientCtx client.Context, addr sdk.AccAddress) error GetAccountNumberSequence(clientCtx client.Context, addr sdk.AccAddress) (uint64, uint64, error) } type Generator interface { NewTx() TxBuilder NewFee() ClientFee NewSignature() ClientSignature MarshalTx(tx types.Tx) ([]byte, error) } type TxBuilder interface { GetTx() sdk.Tx SetMsgs(...sdk.Msg) error GetSignatures() []sdk.Signature SetSignatures(...sdk.Signature) GetFee() sdk.Fee SetFee(sdk.Fee) GetMemo() string SetMemo(string) } ``` We then update `Context` to have new fields: `Codec`, `TxGenerator`, and `AccountRetriever`, and we update `AppModuleBasic.GetTxCmd` to take a `Context` which should have all of these fields pre-populated. Each client method should then use one of the `Init` methods to re-initialize the pre-populated `Context`. `tx.GenerateOrBroadcastTx` can be used to generate or broadcast a transaction. For example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import "github.com/spf13/cobra" import "github.com/cosmos/cosmos-sdk/client" import "github.com/cosmos/cosmos-sdk/client/tx" func NewCmdDoSomething(clientCtx client.Context) *cobra.Command { return &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { clientCtx := ctx.InitWithInput(cmd.InOrStdin()) msg := NewSomeMsg{... } tx.GenerateOrBroadcastTx(clientCtx, msg) }, } } ``` ## Future Improvements ### `SIGN_MODE_TEXTUAL` specification A concrete specification and implementation of `SIGN_MODE_TEXTUAL` is intended as a near-term future improvement so that the ledger app and other wallets can gracefully transition away from Amino JSON. ### `SIGN_MODE_DIRECT_AUX` (\*Documented as option (3) in [Link](https://github.com/cosmos/cosmos-sdk/issues/6078#issuecomment-628026933)) We could add a mode `SIGN_MODE_DIRECT_AUX` to support scenarios where multiple signatures are being gathered into a single transaction but the message composer does not yet know which signatures will be included in the final transaction. For instance, I may have a 3/5 multisig wallet and want to send a `TxBody` to all 5 signers to see who signs first. As soon as I have 3 signatures then I will go ahead and build the full transaction. With `SIGN_MODE_DIRECT`, each signer needs to sign the full `AuthInfo` which includes the full list of all signers and their signing modes, making the above scenario very hard. `SIGN_MODE_DIRECT_AUX` would allow "auxiliary" signers to create their signature using only `TxBody` and their own `PublicKey`. This allows the full list of signers in `AuthInfo` to be delayed until signatures have been collected. An "auxiliary" signer is any signer besides the primary signer who is paying the fee. For the primary signer, the full `AuthInfo` is actually needed to calculate gas and fees because that is dependent on how many signers and which key types and signing modes they are using. Auxiliary signers, however, do not need to worry about fees or gas and thus can just sign `TxBody`. To generate a signature in `SIGN_MODE_DIRECT_AUX` these steps would be followed: 1. Encode `SignDocAux` (with the same requirement that fields must be serialized in order): ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // types/types.proto message SignDocAux { bytes body_bytes = 1; // PublicKey is included in SignDocAux : // 1. as a special case for multisig public keys. For multisig public keys, // the signer should use the top-level multisig public key they are signing // against, not their own public key. This is to prevent against a form // of malleability where a signature could be taken out of context of the // multisig key that was intended to be signed for // 2. to guard against scenario where configuration information is encoded // in public keys (it has been proposed) such that two keys can generate // the same signature but have different security properties // // By including it here, the composer of AuthInfo cannot reference the // a public key variant the signer did not intend to use PublicKey public_key = 2; string chain_id = 3; uint64 account_number = 4; } ``` 2. Sign the encoded `SignDocAux` bytes 3. Send their signature and `SignerInfo` to primary signer who will then sign and broadcast the final transaction (with `SIGN_MODE_DIRECT` and `AuthInfo` added) once enough signatures have been collected ### `SIGN_MODE_DIRECT_RELAXED` (*Documented as option (1)(a) in [Link](https://github.com/cosmos/cosmos-sdk/issues/6078#issuecomment-628026933)*) This is a variation of `SIGN_MODE_DIRECT` where multiple signers wouldn't need to coordinate public keys and signing modes in advance. It would involve an alternate `SignDoc` similar to `SignDocAux` above with fee. This could be added in the future if client developers found the burden of collecting public keys and modes in advance too burdensome. ## Consequences ### Positive * Significant performance gains. * Supports backward and forward type compatibility. * Better support for cross-language clients. * Multiple signing modes allow for greater protocol evolution ### Negative * `google.protobuf.Any` type URLs increase transaction size although the effect may be negligible or compression may be able to mitigate it. ### Neutral ## References # ADR 021: Protocol Buffer Query Encoding Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-021-protobuf-query-encoding 2020 March 27: Initial Draft ## Changelog * 2020 March 27: Initial Draft ## Status Accepted ## Context This ADR is a continuation of the motivation, design, and context established in [ADR 019](/sdk/v0.50/build/architecture/adr-019-protobuf-state-encoding) and [ADR 020](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding), namely, we aim to design the Protocol Buffer migration path for the client-side of the Cosmos SDK. This ADR continues from [ADD 020](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding) to specify the encoding of queries. ## Decision ### Custom Query Definition Modules define custom queries through a protocol buffers `service` definition. These `service` definitions are generally associated with and used by the GRPC protocol. However, the protocol buffers specification indicates that they can be used more generically by any request/response protocol that uses protocol buffer encoding. Thus, we can use `service` definitions for specifying custom ABCI queries and even reuse a substantial amount of the GRPC infrastructure. Each module with custom queries should define a service canonically named `Query`: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/bank/types/types.proto service Query { rpc QueryBalance(QueryBalanceParams) returns (cosmos_sdk.v1.Coin) { } rpc QueryAllBalances(QueryAllBalancesParams) returns (QueryAllBalancesResponse) { } } ``` #### Handling of Interface Types Modules that use interface types and need true polymorphism generally force a `oneof` up to the app-level that provides the set of concrete implementations of that interface that the app supports. While app's are welcome to do the same for queries and implement an app-level query service, it is recommended that modules provide query methods that expose these interfaces via `google.protobuf.Any`. There is a concern on the transaction level that the overhead of `Any` is too high to justify its usage. However for queries this is not a concern, and providing generic module-level queries that use `Any` does not preclude apps from also providing app-level queries that return use the app-level `oneof`s. A hypothetical example for the `gov` module would look something like: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/gov/types/types.proto import "google/protobuf/any.proto"; service Query { rpc GetProposal(GetProposalParams) returns (AnyProposal) { } } message AnyProposal { ProposalBase base = 1; google.protobuf.Any content = 2; } ``` ### Custom Query Implementation In order to implement the query service, we can reuse the existing [gogo protobuf](https://github.com/cosmos/gogoproto) grpc plugin, which for a service named `Query` generates an interface named `QueryServer` as below: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type QueryServer interface { QueryBalance(context.Context, *QueryBalanceParams) (*types.Coin, error) QueryAllBalances(context.Context, *QueryAllBalancesParams) (*QueryAllBalancesResponse, error) } ``` The custom queries for our module are implemented by implementing this interface. The first parameter in this generated interface is a generic `context.Context`, whereas querier methods generally need an instance of `sdk.Context` to read from the store. Since arbitrary values can be attached to `context.Context` using the `WithValue` and `Value` methods, the Cosmos SDK should provide a function `sdk.UnwrapSDKContext` to retrieve the `sdk.Context` from the provided `context.Context`. An example implementation of `QueryBalance` for the bank module as above would look something like: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Querier struct { Keeper } func (q Querier) QueryBalance(ctx context.Context, params *types.QueryBalanceParams) (*sdk.Coin, error) { balance := q.GetBalance(sdk.UnwrapSDKContext(ctx), params.Address, params.Denom) return &balance, nil } ``` ### Custom Query Registration and Routing Query server implementations as above would be registered with `AppModule`s using a new method `RegisterQueryService(grpc.Server)` which could be implemented simply as below: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/bank/module.go func (am AppModule) RegisterQueryService(server grpc.Server) { types.RegisterQueryServer(server, keeper.Querier{ am.keeper }) } ``` Underneath the hood, a new method `RegisterService(sd *grpc.ServiceDesc, handler interface{})` will be added to the existing `baseapp.QueryRouter` to add the queries to the custom query routing table (with the routing method being described below). The signature for this method matches the existing `RegisterServer` method on the GRPC `Server` type where `handler` is the custom query server implementation described above. GRPC-like requests are routed by the service name (ex. `cosmos_sdk.x.bank.v1.Query`) and method name (ex. `QueryBalance`) combined with `/`s to form a full method name (ex. `/cosmos_sdk.x.bank.v1.Query/QueryBalance`). This gets translated into an ABCI query as `custom/cosmos_sdk.x.bank.v1.Query/QueryBalance`. Service handlers registered with `QueryRouter.RegisterService` will be routed this way. Beyond the method name, GRPC requests carry a protobuf encoded payload, which maps naturally to `RequestQuery.Data`, and receive a protobuf encoded response or error. Thus there is a quite natural mapping of GRPC-like rpc methods to the existing `sdk.Query` and `QueryRouter` infrastructure. This basic specification allows us to reuse protocol buffer `service` definitions for ABCI custom queries substantially reducing the need for manual decoding and encoding in query methods. ### GRPC Protocol Support In addition to providing an ABCI query pathway, we can easily provide a GRPC proxy server that routes requests in the GRPC protocol to ABCI query requests under the hood. In this way, clients could use their host languages' existing GRPC implementations to make direct queries against Cosmos SDK app's using these `service` definitions. In order for this server to work, the `QueryRouter` on `BaseApp` will need to expose the service handlers registered with `QueryRouter.RegisterService` to the proxy server implementation. Nodes could launch the proxy server on a separate port in the same process as the ABCI app with a command-line flag. ### REST Queries and Swagger Generation [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) is a project that translates REST calls into GRPC calls using special annotations on service methods. Modules that want to expose REST queries should add `google.api.http` annotations to their `rpc` methods as in this example below. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/bank/types/types.proto service Query { rpc QueryBalance(QueryBalanceParams) returns (cosmos_sdk.v1.Coin) { option (google.api.http) = { get: "/x/bank/v1/balance/{address}/{denom}" }; } rpc QueryAllBalances(QueryAllBalancesParams) returns (QueryAllBalancesResponse) { option (google.api.http) = { get: "/x/bank/v1/balances/{address}" }; } } ``` grpc-gateway will work direcly against the GRPC proxy described above which will translate requests to ABCI queries under the hood. grpc-gateway can also generate Swagger definitions automatically. In the current implementation of REST queries, each module needs to implement REST queries manually in addition to ABCI querier methods. Using the grpc-gateway approach, there will be no need to generate separate REST query handlers, just query servers as described above as grpc-gateway handles the translation of protobuf to REST as well as Swagger definitions. The Cosmos SDK should provide CLI commands for apps to start GRPC gateway either in a separate process or the same process as the ABCI app, as well as provide a command for generating grpc-gateway proxy `.proto` files and the `swagger.json` file. ### Client Usage The gogo protobuf grpc plugin generates client interfaces in addition to server interfaces. For the `Query` service defined above we would get a `QueryClient` interface like: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type QueryClient interface { QueryBalance(ctx context.Context, in *QueryBalanceParams, opts ...grpc.CallOption) (*types.Coin, error) QueryAllBalances(ctx context.Context, in *QueryAllBalancesParams, opts ...grpc.CallOption) (*QueryAllBalancesResponse, error) } ``` Via a small patch to gogo protobuf ([gogo/protobuf#675](https://github.com/gogo/protobuf/pull/675)) we have tweaked the grpc codegen to use an interface rather than concrete type for the generated client struct. This allows us to also reuse the GRPC infrastructure for ABCI client queries. 1Context`will receive a new method`QueryConn`that returns a`ClientConn\` that routes calls to ABCI queries Clients (such as CLI methods) will then be able to call query methods like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} clientCtx := client.NewContext() queryClient := types.NewQueryClient(clientCtx.QueryConn()) params := &types.QueryBalanceParams{ addr, denom } result, err := queryClient.QueryBalance(gocontext.Background(), params) ``` ### Testing Tests would be able to create a query client directly from keeper and `sdk.Context` references using a `QueryServerTestHelper` as below: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} queryHelper := baseapp.NewQueryServerTestHelper(ctx) types.RegisterQueryServer(queryHelper, keeper.Querier{ app.BankKeeper }) queryClient := types.NewQueryClient(queryHelper) ``` ## Future Improvements ## Consequences ### Positive * greatly simplified querier implementation (no manual encoding/decoding) * easy query client generation (can use existing grpc and swagger tools) * no need for REST query implementations * type safe query methods (generated via grpc plugin) * going forward, there will be less breakage of query methods because of the backwards compatibility guarantees provided by buf ### Negative * all clients using the existing ABCI/REST queries will need to be refactored for both the new GRPC/REST query paths as well as protobuf/proto-json encoded data, but this is more or less unavoidable in the protobuf refactoring ### Neutral ## References # ADR 022: Custom BaseApp panic handling Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-022-custom-panic-handling 2020 Apr 24: Initial Draft 2021 Sep 14: Superseded by ADR-045 ## Changelog * 2020 Apr 24: Initial Draft * 2021 Sep 14: Superseded by ADR-045 ## Status SUPERSEDED by ADR-045 ## Context The current implementation of BaseApp does not allow developers to write custom error handlers during panic recovery [runTx()](https://github.com/cosmos/cosmos-sdk/blob/bad4ca75f58b182f600396ca350ad844c18fc80b/baseapp/baseapp.go#L539) method. We think that this method can be more flexible and can give Cosmos SDK users more options for customizations without the need to rewrite whole BaseApp. Also there's one special case for `sdk.ErrorOutOfGas` error handling, that case might be handled in a "standard" way (middleware) alongside the others. We propose middleware-solution, which could help developers implement the following cases: * add external logging (let's say sending reports to external services like [Sentry](https://sentry.io)); * call panic for specific error cases; It will also make `OutOfGas` case and `default` case one of the middlewares. `Default` case wraps recovery object to an error and logs it ([example middleware implementation](#Recovery-middleware)). Our project has a sidecar service running alongside the blockchain node (smart contracts virtual machine). It is essential that node `<->` sidecar connectivity stays stable for TXs processing. So when the communication breaks we need to crash the node and reboot it once the problem is solved. That behavior makes node's state machine execution deterministic. As all keeper panics are caught by runTx's `defer()` handler, we have to adjust the BaseApp code in order to customize it. ## Decision ### Design #### Overview Instead of hardcoding custom error handling into BaseApp we suggest using set of middlewares which can be customized externally and will allow developers use as many custom error handlers as they want. Implementation with tests can be found [here](https://github.com/cosmos/cosmos-sdk/pull/6053). #### Implementation details ##### Recovery handler New `RecoveryHandler` type added. `recoveryObj` input argument is an object returned by the standard Go function `recover()` from the `builtin` package. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type RecoveryHandler func(recoveryObj interface{ }) error ``` Handler should type assert (or other methods) an object to define if object should be handled. `nil` should be returned if input object can't be handled by that `RecoveryHandler` (not a handler's target type). Not `nil` error should be returned if input object was handled and middleware chain execution should be stopped. An example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func exampleErrHandler(recoveryObj interface{ }) error { err, ok := recoveryObj.(error) if !ok { return nil } if someSpecificError.Is(err) { panic(customPanicMsg) } else { return nil } } ``` This example breaks the application execution, but it also might enrich the error's context like the `OutOfGas` handler. ##### Recovery middleware We also add a middleware type (decorator). That function type wraps `RecoveryHandler` and returns the next middleware in execution chain and handler's `error`. Type is used to separate actual `recovery()` object handling from middleware chain processing. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type recoveryMiddleware func(recoveryObj interface{ }) (recoveryMiddleware, error) func newRecoveryMiddleware(handler RecoveryHandler, next recoveryMiddleware) recoveryMiddleware { return func(recoveryObj interface{ }) (recoveryMiddleware, error) { if err := handler(recoveryObj); err != nil { return nil, err } return next, nil } } ``` Function receives a `recoveryObj` object and returns: * (next `recoveryMiddleware`, `nil`) if object wasn't handled (not a target type) by `RecoveryHandler`; * (`nil`, not nil `error`) if input object was handled and other middlewares in the chain should not be executed; * (`nil`, `nil`) in case of invalid behavior. Panic recovery might not have been properly handled; this can be avoided by always using a `default` as a rightmost middleware in the chain (always returns an `error`'); `OutOfGas` middleware example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func newOutOfGasRecoveryMiddleware(gasWanted uint64, ctx sdk.Context, next recoveryMiddleware) recoveryMiddleware { handler := func(recoveryObj interface{ }) error { err, ok := recoveryObj.(sdk.ErrorOutOfGas) if !ok { return nil } return errorsmod.Wrap( sdkerrors.ErrOutOfGas, fmt.Sprintf( "out of gas in location: %v; gasWanted: %d, gasUsed: %d", err.Descriptor, gasWanted, ctx.GasMeter().GasConsumed(), ), ) } return newRecoveryMiddleware(handler, next) } ``` `Default` middleware example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func newDefaultRecoveryMiddleware() recoveryMiddleware { handler := func(recoveryObj interface{ }) error { return errorsmod.Wrap( sdkerrors.ErrPanic, fmt.Sprintf("recovered: %v\nstack:\n%v", recoveryObj, string(debug.Stack())), ) } return newRecoveryMiddleware(handler, nil) } ``` ##### Recovery processing Basic chain of middlewares processing would look like: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func processRecovery(recoveryObj interface{ }, middleware recoveryMiddleware) error { if middleware == nil { return nil } next, err := middleware(recoveryObj) if err != nil { return err } if next == nil { return nil } return processRecovery(recoveryObj, next) } ``` That way we can create a middleware chain which is executed from left to right, the rightmost middleware is a `default` handler which must return an `error`. ##### BaseApp changes The `default` middleware chain must exist in a `BaseApp` object. `Baseapp` modifications: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type BaseApp struct { // ... runTxRecoveryMiddleware recoveryMiddleware } func NewBaseApp(...) { // ... app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware() } func (app *BaseApp) runTx(...) { // ... defer func() { if r := recover(); r != nil { recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, app.runTxRecoveryMiddleware) err, result = processRecovery(r, recoveryMW), nil } gInfo = sdk.GasInfo{ GasWanted: gasWanted, GasUsed: ctx.GasMeter().GasConsumed() } }() // ... } ``` Developers can add their custom `RecoveryHandler`s by providing `AddRunTxRecoveryHandler` as a BaseApp option parameter to the `NewBaseapp` constructor: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *BaseApp) AddRunTxRecoveryHandler(handlers ...RecoveryHandler) { for _, h := range handlers { app.runTxRecoveryMiddleware = newRecoveryMiddleware(h, app.runTxRecoveryMiddleware) } } ``` This method would prepend handlers to an existing chain. ## Consequences ### Positive * Developers of Cosmos SDK based projects can add custom panic handlers to: * add error context for custom panic sources (panic inside of custom keepers); * emit `panic()`: passthrough recovery object to the Tendermint core; * other necessary handling; * Developers can use standard Cosmos SDK `BaseApp` implementation, rather that rewriting it in their projects; * Proposed solution doesn't break the current "standard" `runTx()` flow; ### Negative * Introduces changes to the execution model design. ### Neutral * `OutOfGas` error handler becomes one of the middlewares; * Default panic handler becomes one of the middlewares; ## References * [PR-6053 with proposed solution](https://github.com/cosmos/cosmos-sdk/pull/6053) * [Similar solution. ADR-010 Modular AnteHandler](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-010-modular-antehandler.md) # ADR 023: Protocol Buffer Naming and Versioning Conventions Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-023-protobuf-naming 2020 April 27: Initial Draft 2020 August 5: Update guidelines ## Changelog * 2020 April 27: Initial Draft * 2020 August 5: Update guidelines ## Status Accepted ## Context Protocol Buffers provide a basic [style guide](https://developers.google.com/protocol-buffers/docs/style) and [Buf](https://buf.build/docs/style-guide) builds upon that. To the extent possible, we want to follow industry accepted guidelines and wisdom for the effective usage of protobuf, deviating from those only when there is clear rationale for our use case. ### Adoption of `Any` The adoption of `google.protobuf.Any` as the recommended approach for encoding interface types (as opposed to `oneof`) makes package naming a central part of the encoding as fully-qualified message names now appear in encoded messages. ### Current Directory Organization Thus far we have mostly followed [Buf's](https://buf.build) [DEFAULT](https://buf.build/docs/lint-checkers#default) recommendations, with the minor deviation of disabling [`PACKAGE_DIRECTORY_MATCH`](https://buf.build/docs/lint-checkers#file_layout) which although being convenient for developing code comes with the warning from Buf that: > you will have a very bad time with many Protobuf plugins across various languages if you do not do this ### Adoption of gRPC Queries In [ADR 021](/sdk/latest/reference/architecture/adr-021-protobuf-query-encoding), gRPC was adopted for Protobuf native queries. The full gRPC service path thus becomes a key part of ABCI query path. In the future, gRPC queries may be allowed from within persistent scripts by technologies such as CosmWasm and these query routes would be stored within script binaries. ## Decision The goal of this ADR is to provide thoughtful naming conventions that: * encourage a good user experience for when users interact directly with .proto files and fully-qualified protobuf names * balance conciseness against the possibility of either over-optimizing (making names too short and cryptic) or under-optimizing (just accepting bloated names with lots of redundant information) These guidelines are meant to act as a style guide for both the Cosmos SDK and third-party modules. As a starting point, we should adopt all of the [DEFAULT](https://buf.build/docs/lint-checkers#default) checkers in [Buf's](https://buf.build) including [`PACKAGE_DIRECTORY_MATCH`](https://buf.build/docs/lint-checkers#file_layout), except: * [PACKAGE\_VERSION\_SUFFIX](https://buf.build/docs/lint-checkers#package_version_suffix) * [SERVICE\_SUFFIX](https://buf.build/docs/lint-checkers#service_suffix) Further guidelines to be described below. ### Principles #### Concise and Descriptive Names Names should be descriptive enough to convey their meaning and distinguish them from other names. Given that we are using fully-qualifed names within `google.protobuf.Any` as well as within gRPC query routes, we should aim to keep names concise, without going overboard. The general rule of thumb should be if a shorter name would convey more or else the same thing, pick the shorter name. For instance, `cosmos.bank.MsgSend` (19 bytes) conveys roughly the same information as `cosmos_sdk.x.bank.v1.MsgSend` (28 bytes) but is more concise. Such conciseness makes names both more pleasant to work with and take up less space within transactions and on the wire. We should also resist the temptation to over-optimize, by making names cryptically short with abbreviations. For instance, we shouldn't try to reduce `cosmos.bank.MsgSend` to `csm.bk.MSnd` just to save a few bytes. The goal is to make names ***concise but not cryptic***. #### Names are for Clients First Package and type names should be chosen for the benefit of users, not necessarily because of legacy concerns related to the go code-base. #### Plan for Longevity In the interests of long-term support, we should plan on the names we do choose to be in usage for a long time, so now is the opportunity to make the best choices for the future. ### Versioning #### Guidelines on Stable Package Versions In general, schema evolution is the way to update protobuf schemas. That means that new fields, messages, and RPC methods are *added* to existing schemas and old fields, messages and RPC methods are maintained as long as possible. Breaking things is often unacceptable in a blockchain scenario. For instance, immutable smart contracts may depend on certain data schemas on the host chain. If the host chain breaks those schemas, the smart contract may be irreparably broken. Even when things can be fixed (for instance in client software), this often comes at a high cost. Instead of breaking things, we should make every effort to evolve schemas rather than just breaking them. [Buf](https://buf.build) breaking change detection should be used on all stable (non-alpha or beta) packages to prevent such breakage. With that in mind, different stable versions (i.e. `v1` or `v2`) of a package should more or less be considered different packages and this should be last resort approach for upgrading protobuf schemas. Scenarios where creating a `v2` may make sense are: * we want to create a new module with similar functionality to an existing module and adding `v2` is the most natural way to do this. In that case, there are really just two different, but similar modules with different APIs. * we want to add a new revamped API for an existing module and it's just too cumbersome to add it to the existing package, so putting it in `v2` is cleaner for users. In this case, care should be made to not deprecate support for `v1` if it is actively used in immutable smart contracts. #### Guidelines on unstable (alpha and beta) package versions The following guidelines are recommended for marking packages as alpha or beta: * marking something as `alpha` or `beta` should be a last resort and just putting something in the stable package (i.e. `v1` or `v2`) should be preferred * a package *should* be marked as `alpha` *if and only if* there are active discussions to remove or significantly alter the package in the near future * a package *should* be marked as `beta` *if and only if* there is an active discussion to significantly refactor/rework the functionality in the near future but not remove it * modules *can and should* have types in both stable (i.e. `v1` or `v2`) and unstable (`alpha` or `beta`) packages. *`alpha` and `beta` should not be used to avoid responsibility for maintaining compatibility.* Whenever code is released into the wild, especially on a blockchain, there is a high cost to changing things. In some cases, for instance with immutable smart contracts, a breaking change may be impossible to fix. When marking something as `alpha` or `beta`, maintainers should ask the questions: * what is the cost of asking others to change their code vs the benefit of us maintaining the optionality to change it? * what is the plan for moving this to `v1` and how will that affect users? `alpha` or `beta` should really be used to communicate "changes are planned". As a case study, gRPC reflection is in the package `grpc.reflection.v1alpha`. It hasn't been changed since 2017 and it is now used in other widely used software like gRPCurl. Some folks probably use it in production services and so if they actually went and changed the package to `grpc.reflection.v1`, some software would break and they probably don't want to do that... So now the `v1alpha` package is more or less the de-facto `v1`. Let's not do that. The following are guidelines for working with non-stable packages: * [Buf's recommended version suffix](https://buf.build/docs/lint-checkers#package_version_suffix) (ex. `v1alpha1`) *should* be used for non-stable packages * non-stable packages should generally be excluded from breaking change detection * immutable smart contract modules (i.e. CosmWasm) *should* block smart contracts/persistent scripts from interacting with `alpha`/`beta` packages #### Omit v1 suffix Instead of using [Buf's recommended version suffix](https://buf.build/docs/lint-checkers#package_version_suffix), we can omit `v1` for packages that don't actually have a second version. This allows for more concise names for common use cases like `cosmos.bank.Send`. Packages that do have a second or third version can indicate that with `.v2` or `.v3`. ### Package Naming #### Adopt a short, unique top-level package name Top-level packages should adopt a short name that is known to not collide with other names in common usage within the Cosmos ecosystem. In the near future, a registry should be created to reserve and index top-level package names used within the Cosmos ecosystem. Because the Cosmos SDK is intended to provide the top-level types for the Cosmos project, the top-level package name `cosmos` is recommended for usage within the Cosmos SDK instead of the longer `cosmos_sdk`. [ICS](https://github.com/cosmos/ics) specifications could consider a short top-level package like `ics23` based upon the standard number. #### Limit sub-package depth Sub-package depth should be increased with caution. Generally a single sub-package is needed for a module or a library. Even though `x` or `modules` is used in source code to denote modules, this is often unnecessary for .proto files as modules are the primary thing sub-packages are used for. Only items which are known to be used infrequently should have deep sub-package depths. For the Cosmos SDK, it is recommended that we simply write `cosmos.bank`, `cosmos.gov`, etc. rather than `cosmos.x.bank`. In practice, most non-module types can go straight in the `cosmos` package or we can introduce a `cosmos.base` package if needed. Note that this naming *will not* change go package names, i.e. the `cosmos.bank` protobuf package will still live in `x/bank`. ### Message Naming Message type names should be as concise possible without losing clarity. `sdk.Msg` types which are used in transactions will retain the `Msg` prefix as that provides helpful context. ### Service and RPC Naming [ADR 021](/sdk/latest/reference/architecture/adr-021-protobuf-query-encoding) specifies that modules should implement a gRPC query service. We should consider the principle of conciseness for query service and RPC names as these may be called from persistent script modules such as CosmWasm. Also, users may use these query paths from tools like [gRPCurl](https://github.com/fullstorydev/grpcurl). As an example, we can shorten `/cosmos_sdk.x.bank.v1.QueryService/QueryBalance` to `/cosmos.bank.Query/Balance` without losing much useful information. RPC request and response types *should* follow the `ServiceNameMethodNameRequest`/ `ServiceNameMethodNameResponse` naming convention. i.e. for an RPC method named `Balance` on the `Query` service, the request and response types would be `QueryBalanceRequest` and `QueryBalanceResponse`. This will be more self-explanatory than `BalanceRequest` and `BalanceResponse`. #### Use just `Query` for the query service Instead of [Buf's default service suffix recommendation](https://github.com/cosmos/cosmos-sdk/pull/6033), we should simply use the shorter `Query` for query services. For other types of gRPC services, we should consider sticking with Buf's default recommendation. #### Omit `Get` and `Query` from query service RPC names `Get` and `Query` should be omitted from `Query` service names because they are redundant in the fully-qualified name. For instance, `/cosmos.bank.Query/QueryBalance` just says `Query` twice without any new information. ## Future Improvements A registry of top-level package names should be created to coordinate naming across the ecosystem, prevent collisions, and also help developers discover useful schemas. A simple starting point would be a git repository with community-based governance. ## Consequences ### Positive * names will be more concise and easier to read and type * all transactions using `Any` will be at shorter (`_sdk.x` and `.v1` will be removed) * `.proto` file imports will be more standard (without `"third_party/proto"` in the path) * code generation will be easier for clients because .proto files will be in a single `proto/` directory which can be copied rather than scattered throughout the Cosmos SDK ### Negative ### Neutral * `.proto` files will need to be reorganized and refactored * some modules may need to be marked as alpha or beta ## References # ADR 024: Coin Metadata Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-024-coin-metadata 05/19/2020: Initial draft ## Changelog * 05/19/2020: Initial draft ## Status Proposed ## Context Assets in the Cosmos SDK are represented via a `Coins` type that consists of an `amount` and a `denom`, where the `amount` can be any arbitrarily large or small value. In addition, the Cosmos SDK uses an account-based model where there are two types of primary accounts -- basic accounts and module accounts. All account types have a set of balances that are composed of `Coins`. The `x/bank` module keeps track of all balances for all accounts and also keeps track of the total supply of balances in an application. With regards to a balance `amount`, the Cosmos SDK assumes a static and fixed unit of denomination, regardless of the denomination itself. In other words, clients and apps built atop a Cosmos-SDK-based chain may choose to define and use arbitrary units of denomination to provide a richer UX, however, by the time a tx or operation reaches the Cosmos SDK state machine, the `amount` is treated as a single unit. For example, for the Cosmos Hub (Gaia), clients assume 1 ATOM = 10^6 uatom, and so all txs and operations in the Cosmos SDK work off of units of 10^6. This clearly provides a poor and limited UX especially as interoperability of networks increases and as a result the total amount of asset types increases. We propose to have `x/bank` additionally keep track of metadata per `denom` in order to help clients, wallet providers, and explorers improve their UX and remove the requirement for making any assumptions on the unit of denomination. ## Decision The `x/bank` module will be updated to store and index metadata by `denom`, specifically the "base" or smallest unit -- the unit the Cosmos SDK state-machine works with. Metadata may also include a non-zero length list of denominations. Each entry contains the name of the denomination `denom`, the exponent to the base and a list of aliases. An entry is to be interpreted as `1 denom = 10^exponent base_denom` (e.g. `1 ETH = 10^18 wei` and `1 uatom = 10^0 uatom`). There are two denominations that are of high importance for clients: the `base`, which is the smallest possible unit and the `display`, which is the unit that is commonly referred to in human communication and on exchanges. The values in those fields link to an entry in the list of denominations. The list in `denom_units` and the `display` entry may be changed via governance. As a result, we can define the type as follows: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message DenomUnit { string denom = 1; uint32 exponent = 2; repeated string aliases = 3; } message Metadata { string description = 1; repeated DenomUnit denom_units = 2; string base = 3; string display = 4; } ``` As an example, the ATOM's metadata can be defined as follows: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "name": "atom", "description": "The native staking token of the Cosmos Hub.", "denom_units": [ { "denom": "uatom", "exponent": 0, "aliases": [ "microatom" ], }, { "denom": "matom", "exponent": 3, "aliases": [ "milliatom" ] }, { "denom": "atom", "exponent": 6, } ], "base": "uatom", "display": "atom", } ``` Given the above metadata, a client may infer the following things: * 4.3atom = 4.3 \* (10^6) = 4,300,000uatom * The string "atom" can be used as a display name in a list of tokens. * The balance 4300000 can be displayed as 4,300,000uatom or 4,300matom or 4.3atom. The `display` denomination 4.3atom is a good default if the authors of the client don't make an explicit decision to choose a different representation. A client should be able to query for metadata by denom both via the CLI and REST interfaces. In addition, we will add handlers to these interfaces to convert from any unit to another given unit, as the base framework for this already exists in the Cosmos SDK. Finally, we need to ensure metadata exists in the `GenesisState` of the `x/bank` module which is also indexed by the base `denom`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type GenesisState struct { SendEnabled bool `json:"send_enabled" yaml:"send_enabled"` Balances []Balance `json:"balances" yaml:"balances"` Supply sdk.Coins `json:"supply" yaml:"supply"` DenomMetadata []Metadata `json:"denom_metadata" yaml:"denom_metadata"` } ``` ## Future Work In order for clients to avoid having to convert assets to the base denomination -- either manually or via an endpoint, we may consider supporting automatic conversion of a given unit input. ## Consequences ### Positive * Provides clients, wallet providers and block explorers with additional data on asset denomination to improve UX and remove any need to make assumptions on denomination units. ### Negative * A small amount of required additional storage in the `x/bank` module. The amount of additional storage should be minimal as the amount of total assets should not be large. ### Neutral ## References # ADR 027: Deterministic Protobuf Serialization Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-027-deterministic-protobuf-serialization 2020-08-07: Initial Draft 2020-09-01: Further clarify rules ## Changelog * 2020-08-07: Initial Draft * 2020-09-01: Further clarify rules ## Status Proposed ## Abstract Fully deterministic structure serialization, which works across many languages and clients, is needed when signing messages. We need to be sure that whenever we serialize a data structure, no matter in which supported language, the raw bytes will stay the same. [Protobuf](https://developers.google.com/protocol-buffers/docs/proto3) serialization is not bijective (i.e. there exist a practically unlimited number of valid binary representations for a given protobuf document)1. This document describes a deterministic serialization scheme for a subset of protobuf documents, that covers this use case but can be reused in other cases as well. ### Context For signature verification in Cosmos SDK, the signer and verifier need to agree on the same serialization of a `SignDoc` as defined in [ADR-020](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding) without transmitting the serialization. Currently, for block signatures we are using a workaround: we create a new [TxRaw](https://github.com/cosmos/cosmos-sdk/blob/9e85e81e0e8140067dd893421290c191529c148c/proto/cosmos/tx/v1beta1/tx.proto#L30) instance (as defined in [adr-020-protobuf-transaction-encoding](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-020-protobuf-transaction-encoding.md#transactions)) by converting all [Tx](https://github.com/cosmos/cosmos-sdk/blob/9e85e81e0e8140067dd893421290c191529c148c/proto/cosmos/tx/v1beta1/tx.proto#L13) fields to bytes on the client side. This adds an additional manual step when sending and signing transactions. ### Decision The following encoding scheme is to be used by other ADRs, and in particular for `SignDoc` serialization. ## Specification ### Scope This ADR defines a protobuf3 serializer. The output is a valid protobuf serialization, such that every protobuf parser can parse it. No maps are supported in version 1 due to the complexity of defining a deterministic serialization. This might change in future. Implementations must reject documents containing maps as invalid input. ### Background - Protobuf3 Encoding Most numeric types in protobuf3 are encoded as [varints](https://developers.google.com/protocol-buffers/docs/encoding#varints). Varints are at most 10 bytes, and since each varint byte has 7 bits of data, varints are a representation of `uint70` (70-bit unsigned integer). When encoding, numeric values are casted from their base type to `uint70`, and when decoding, the parsed `uint70` is casted to the appropriate numeric type. The maximum valid value for a varint that complies with protobuf3 is `FF FF FF FF FF FF FF FF FF 7F` (i.e. `2**70 -1`). If the field type is `{,u,s}int64`, the highest 6 bits of the 70 are dropped during decoding, introducing 6 bits of malleability. If the field type is `{,u,s}int32`, the highest 38 bits of the 70 are dropped during decoding, introducing 38 bits of malleability. Among other sources of non-determinism, this ADR eliminates the possibility of encoding malleability. ### Serialization rules The serialization is based on the [protobuf3 encoding](https://developers.google.com/protocol-buffers/docs/encoding) with the following additions: 1. Fields must be serialized only once in ascending order 2. Extra fields or any extra data must not be added 3. [Default values](https://developers.google.com/protocol-buffers/docs/proto3#default) must be omitted 4. `repeated` fields of scalar numeric types must use [packed encoding](https://developers.google.com/protocol-buffers/docs/encoding#packed) 5. Varint encoding must not be longer than needed: * No trailing zero bytes (in little endian, i.e. no leading zeroes in big endian). Per rule 3 above, the default value of `0` must be omitted, so this rule does not apply in such cases. * The maximum value for a varint must be `FF FF FF FF FF FF FF FF FF 01`. In other words, when decoded, the highest 6 bits of the 70-bit unsigned integer must be `0`. (10-byte varints are 10 groups of 7 bits, i.e. 70 bits, of which only the lowest 70-6=64 are useful.) * The maximum value for 32-bit values in varint encoding must be `FF FF FF FF 0F` with one exception (below). In other words, when decoded, the highest 38 bits of the 70-bit unsigned integer must be `0`. * The one exception to the above is *negative* `int32`, which must be encoded using the full 10 bytes for sign extension2. * The maximum value for Boolean values in varint encoding must be `01` (i.e. it must be `0` or `1`). Per rule 3 above, the default value of `0` must be omitted, so if a Boolean is included it must have a value of `1`. While rule number 1. and 2. should be pretty straight forward and describe the default behavior of all protobuf encoders the author is aware of, the 3rd rule is more interesting. After a protobuf3 deserialization you cannot differentiate between unset fields and fields set to the default value3. At serialization level however, it is possible to set the fields with an empty value or omitting them entirely. This is a significant difference to e.g. JSON where a property can be empty (`""`, `0`), `null` or undefined, leading to 3 different documents. Omitting fields set to default values is valid because the parser must assign the default value to fields missing in the serialization4. For scalar types, omitting defaults is required by the spec5. For `repeated` fields, not serializing them is the only way to express empty lists. Enums must have a first element of numeric value 0, which is the default6. And message fields default to unset7. Omitting defaults allows for some amount of forward compatibility: users of newer versions of a protobuf schema produce the same serialization as users of older versions as long as newly added fields are not used (i.e. set to their default value). ### Implementation There are three main implementation strategies, ordered from the least to the most custom development: * **Use a protobuf serializer that follows the above rules by default.** E.g. [gogoproto](https://pkg.go.dev/github.com/cosmos/gogoproto/gogoproto) is known to be compliant by in most cases, but not when certain annotations such as `nullable = false` are used. It might also be an option to configure an existing serializer accordingly. * **Normalize default values before encoding them.** If your serializer follows rule 1. and 2. and allows you to explicitly unset fields for serialization, you can normalize default values to unset. This can be done when working with [protobuf.js](https://www.npmjs.com/package/protobufjs): ```js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const bytes = SignDoc.encode({ bodyBytes: body.length > 0 ? body : null, // normalize empty bytes to unset authInfoBytes: authInfo.length > 0 ? authInfo : null, // normalize empty bytes to unset chainId: chainId || null, // normalize "" to unset accountNumber: accountNumber || null, // normalize 0 to unset accountSequence: accountSequence || null, // normalize 0 to unset }).finish(); ``` * **Use a hand-written serializer for the types you need.** If none of the above ways works for you, you can write a serializer yourself. For SignDoc this would look something like this in Go, building on existing protobuf utilities: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if !signDoc.body_bytes.empty() { buf.WriteUVarInt64(0xA) // wire type and field number for body_bytes buf.WriteUVarInt64(signDoc.body_bytes.length()) buf.WriteBytes(signDoc.body_bytes) } if !signDoc.auth_info.empty() { buf.WriteUVarInt64(0x12) // wire type and field number for auth_info buf.WriteUVarInt64(signDoc.auth_info.length()) buf.WriteBytes(signDoc.auth_info) } if !signDoc.chain_id.empty() { buf.WriteUVarInt64(0x1a) // wire type and field number for chain_id buf.WriteUVarInt64(signDoc.chain_id.length()) buf.WriteBytes(signDoc.chain_id) } if signDoc.account_number != 0 { buf.WriteUVarInt64(0x20) // wire type and field number for account_number buf.WriteUVarInt(signDoc.account_number) } if signDoc.account_sequence != 0 { buf.WriteUVarInt64(0x28) // wire type and field number for account_sequence buf.WriteUVarInt(signDoc.account_sequence) } ``` ### Test vectors Given the protobuf definition `Article.proto` ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package blog; syntax = "proto3"; enum Type { UNSPECIFIED = 0; IMAGES = 1; NEWS = 2; }; enum Review { UNSPECIFIED = 0; ACCEPTED = 1; REJECTED = 2; }; message Article { string title = 1; string description = 2; uint64 created = 3; uint64 updated = 4; bool public = 5; bool promoted = 6; Type type = 7; Review review = 8; repeated string comments = 9; repeated string backlinks = 10; }; ``` serializing the values ```yaml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} title: "The world needs change 🌳" description: "" created: 1596806111080 updated: 0 public: true promoted: false type: Type.NEWS review: Review.UNSPECIFIED comments: ["Nice one", "Thank you"] backlinks: [] ``` must result in the serialization ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} 0a1b54686520776f726c64206e65656473206368616e676520f09f8cb318e8bebec8bc2e280138024a084e696365206f6e654a095468616e6b20796f75 ``` When inspecting the serialized document, you see that every second field is omitted: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} $ echo 0a1b54686520776f726c64206e65656473206368616e676520f09f8cb318e8bebec8bc2e280138024a084e696365206f6e654a095468616e6b20796f75 | xxd -r -p | protoc --decode_raw 1: "The world needs change \360\237\214\263" 3: 1596806111080 5: 1 7: 2 9: "Nice one" 9: "Thank you" ``` ## Consequences Having such an encoding available allows us to get deterministic serialization for all protobuf documents we need in the context of Cosmos SDK signing. ### Positive * Well defined rules that can be verified independent of a reference implementation * Simple enough to keep the barrier to implement transaction signing low * It allows us to continue to use 0 and other empty values in SignDoc, avoiding the need to work around 0 sequences. This does not imply the change from [Link](https://github.com/cosmos/cosmos-sdk/pull/6949) should not be merged, but not too important anymore. ### Negative * When implementing transaction signing, the encoding rules above must be understood and implemented. * The need for rule number 3. adds some complexity to implementations. * Some data structures may require custom code for serialization. Thus the code is not very portable - it will require additional work for each client implementing serialization to properly handle custom data structures. ### Neutral ### Usage in Cosmos SDK For the reasons mentioned above ("Negative" section) we prefer to keep workarounds for shared data structure. Example: the aforementioned `TxRaw` is using raw bytes as a workaround. This allows them to use any valid Protobuf library without the need of implementing a custom serializer that adheres to this standard (and related risks of bugs). ## References * 1 *When a message is serialized, there is no guaranteed order for how its known or unknown fields should be written. Serialization order is an implementation detail and the details of any particular implementation may change in the future. Therefore, protocol buffer parsers must be able to parse fields in any order.* from [Link](https://developers.google.com/protocol-buffers/docs/encoding#order) * 2 [Link](https://developers.google.com/protocol-buffers/docs/encoding#signed_integers) * 3 *Note that for scalar message fields, once a message is parsed there's no way of telling whether a field was explicitly set to the default value (for example whether a boolean was set to false) or just not set at all: you should bear this in mind when defining your message types. For example, don't have a boolean that switches on some behavior when set to false if you don't want that behavior to also happen by default.* from [Link](https://developers.google.com/protocol-buffers/docs/proto3#default) * 4 *When a message is parsed, if the encoded message does not contain a particular singular element, the corresponding field in the parsed object is set to the default value for that field.* from [Link](https://developers.google.com/protocol-buffers/docs/proto3#default) * 5 *Also note that if a scalar message field is set to its default, the value will not be serialized on the wire.* from [Link](https://developers.google.com/protocol-buffers/docs/proto3#default) * 6 *For enums, the default value is the first defined enum value, which must be 0.* from [Link](https://developers.google.com/protocol-buffers/docs/proto3#default) * 7 *For message fields, the field is not set. Its exact value is language-dependent.* from [Link](https://developers.google.com/protocol-buffers/docs/proto3#default) * Encoding rules and parts of the reasoning taken from [canonical-proto3 Aaron Craelius](https://github.com/regen-network/canonical-proto3) # ADR 028: Public Key Addresses Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-028-public-key-addresses 2020/08/18: Initial version 2021/01/15: Analysis and algorithm update ## Changelog * 2020/08/18: Initial version * 2021/01/15: Analysis and algorithm update ## Status Proposed ## Abstract This ADR defines an address format for all addressable Cosmos SDK accounts. That includes: new public key algorithms, multisig public keys, and module accounts. ## Context Issue [#3685](https://github.com/cosmos/cosmos-sdk/issues/3685) identified that public key address spaces are currently overlapping. We confirmed that it significantly decreases security of Cosmos SDK. ### Problem An attacker can control an input for an address generation function. This leads to a birthday attack, which significantly decreases the security space. To overcome this, we need to separate the inputs for different kind of account types: a security break of one account type shouldn't impact the security of other account types. ### Initial proposals One initial proposal was extending the address length and adding prefixes for different types of addresses. @ethanfrey explained an alternate approach originally used in [Link](https://github.com/iov-one/weave): > I spent quite a bit of time thinking about this issue while building weave... The other cosmos Sdk. > Basically I define a condition to be a type and format as human readable string with some binary data appended. This condition is hashed into an Address (again at 20 bytes). The use of this prefix makes it impossible to find a preimage for a given address with a different condition (eg ed25519 vs secp256k1). > This is explained in depth here [Link](https://weave.readthedocs.io/en/latest/design/permissions.html) > And the code is here, look mainly at the top where we process conditions. [Link](https://github.com/iov-one/weave/blob/master/conditions.go) And explained how this approach should be sufficiently collision resistant: > Yeah, AFAIK, 20 bytes should be collision resistance when the preimages are unique and not malleable. A space of 2^160 would expect some collision to be likely around 2^80 elements (birthday paradox). And if you want to find a collision for some existing element in the database, it is still 2^160. 2^80 only is if all these elements are written to state. > The good example you brought up was eg. a public key bytes being a valid public key on two algorithms supported by the codec. Meaning if either was broken, you would break accounts even if they were secured with the safer variant. This is only as the issue when no differentiating type info is present in the preimage (before hashing into an address). > I would like to hear an argument if the 20 bytes space is an actual issue for security, as I would be happy to increase my address sizes in weave. I just figured cosmos and ethereum and bitcoin all use 20 bytes, it should be good enough. And the arguments above which made me feel it was secure. But I have not done a deeper analysis. This led to the first proposal (which we proved to be not good enough): we concatenate a key type with a public key, hash it and take the first 20 bytes of that hash, summarized as `sha256(keyTypePrefix || keybytes)[:20]`. ### Review and Discussions In [#5694](https://github.com/cosmos/cosmos-sdk/issues/5694) we discussed various solutions. We agreed that 20 bytes it's not future proof, and extending the address length is the only way to allow addresses of different types, various signature types, etc. This disqualifies the initial proposal. In the issue we discussed various modifications: * Choice of the hash function. * Move the prefix out of the hash function: `keyTypePrefix + sha256(keybytes)[:20]` \[post-hash-prefix-proposal]. * Use double hashing: `sha256(keyTypePrefix + sha256(keybytes)[:20])`. * Increase to keybytes hash slice from 20 byte to 32 or 40 bytes. We concluded that 32 bytes, produced by a good hash functions is future secure. ### Requirements * Support currently used tools - we don't want to break an ecosystem, or add a long adaptation period. Ref: [Link](https://github.com/cosmos/cosmos-sdk/issues/8041) * Try to keep the address length small - addresses are widely used in state, both as part of a key and object value. ### Scope This ADR only defines a process for the generation of address bytes. For end-user interactions with addresses (through the API, or CLI, etc.), we still use bech32 to format these addresses as strings. This ADR doesn't change that. Using Bech32 for string encoding gives us support for checksum error codes and handling of user typos. ## Decision We define the following account types, for which we define the address function: 1. simple accounts: represented by a regular public key (ie: secp256k1, sr25519) 2. naive multisig: accounts composed by other addressable objects (ie: naive multisig) 3. composed accounts with a native address key (ie: bls, group module accounts) 4. module accounts: basically any accounts which cannot sign transactions and which are managed internally by modules ### Legacy Public Key Addresses Don't Change Currently (Jan 2021), the only officially supported Cosmos SDK user accounts are `secp256k1` basic accounts and legacy amino multisig. They are used in existing Cosmos SDK zones. They use the following address formats: * secp256k1: `ripemd160(sha256(pk_bytes))[:20]` * legacy amino multisig: `sha256(aminoCdc.Marshal(pk))[:20]` We don't want to change existing addresses. So the addresses for these two key types will remain the same. The current multisig public keys use amino serialization to generate the address. We will retain those public keys and their address formatting, and call them "legacy amino" multisig public keys in protobuf. We will also create multisig public keys without amino addresses to be described below. ### Hash Function Choice As in other parts of the Cosmos SDK, we will use `sha256`. ### Basic Address We start with defining a base algorithm for generating addresses which we will call `Hash`. Notably, it's used for accounts represented by a single key pair. For each public key schema we have to have an associated `typ` string, explained in the next section. `hash` is the cryptographic hash function defined in the previous section. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const A_LEN = 32 func Hash(typ string, key []byte) []byte { return hash(hash(typ) + key)[:A_LEN] } ``` The `+` is bytes concatenation, which doesn't use any separator. This algorithm is the outcome of a consultation session with a professional cryptographer. Motivation: this algorithm keeps the address relatively small (length of the `typ` doesn't impact the length of the final address) and it's more secure than \[post-hash-prefix-proposal] (which uses the first 20 bytes of a pubkey hash, significantly reducing the address space). Moreover the cryptographer motivated the choice of adding `typ` in the hash to protect against a switch table attack. `address.Hash` is a low level function to generate *base* addresses for new key types. Example: * BLS: `address.Hash("bls", pubkey)` ### Composed Addresses For simple composed accounts (like a new naive multisig) we generalize the `address.Hash`. The address is constructed by recursively creating addresses for the sub accounts, sorting the addresses and composing them into a single address. It ensures that the ordering of keys doesn't impact the resulting address. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // We don't need a PubKey interface - we need anything which is addressable. type Addressable interface { Address() []byte } func Composed(typ string, subaccounts []Addressable) []byte { addresses = map(subaccounts, \a -> LengthPrefix(a.Address())) addresses = sort(addresses) return address.Hash(typ, addresses[0] + ... + addresses[n]) } ``` The `typ` parameter should be a schema descriptor, containing all significant attributes with deterministic serialization (eg: utf8 string). `LengthPrefix` is a function which prepends 1 byte to the address. The value of that byte is the length of the address bits before prepending. The address must be at most 255 bits long. We are using `LengthPrefix` to eliminate conflicts - it assures, that for 2 lists of addresses: `as = {a1, a2, ..., an}` and `bs = {b1, b2, ..., bm}` such that every `bi` and `ai` is at most 255 long, `concatenate(map(as, (a) => LengthPrefix(a))) = map(bs, (b) => LengthPrefix(b))` if `as = bs`. Implementation Tip: account implementations should cache addresses. #### Multisig Addresses For a new multisig public keys, we define the `typ` parameter not based on any encoding scheme (amino or protobuf). This avoids issues with non-determinism in the encoding scheme. Example: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package cosmos.crypto.multisig; message PubKey { uint32 threshold = 1; repeated google.protobuf.Any pubkeys = 2; } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (multisig PubKey) Address() { // first gather all nested pub keys var keys []address.Addressable // cryptotypes.PubKey implements Addressable for _, _key := range multisig.Pubkeys { keys = append(keys, key.GetCachedValue().(cryptotypes.PubKey)) } // form the type from the message name (cosmos.crypto.multisig.PubKey) and the threshold joined together prefix := fmt.Sprintf("%s/%d", proto.MessageName(multisig), multisig.Threshold) // use the Composed function defined above return address.Composed(prefix, keys) } ``` ### Derived Addresses We must be able to cryptographically derive one address from another one. The derivation process must guarantee hash properties, hence we use the already defined `Hash` function: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func Derive(address, derivationKey []byte) []byte { return Hash(addres, derivationKey) } ``` ### Module Account Addresses A module account will have `"module"` type. Module accounts can have sub accounts. The submodule account will be created based on module name, and sequence of derivation keys. Typically, the first derivation key should be a class of the derived accounts. The derivation process has a defined order: module name, submodule key, subsubmodule key... An example module account is created using: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} address.Module(moduleName, key) ``` An example sub-module account is created using: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} groupPolicyAddresses := []byte{1 } address.Module(moduleName, groupPolicyAddresses, policyID) ``` The `address.Module` function is using `address.Hash` with `"module"` as the type argument, and byte representation of the module name concatenated with submodule key. The two last component must be uniquely separated to avoid potential clashes (example: modulename="ab" & submodulekey="bc" will have the same derivation key as modulename="a" & submodulekey="bbc"). We use a null byte (`'\x00'`) to separate module name from the submodule key. This works, because null byte is not a part of a valid module name. Finally, the sub-submodule accounts are created by applying the `Derive` function recursively. We could use `Derive` function also in the first step (rather than concatenating module name with zero byte and the submodule key). We decided to do concatenation to avoid one level of derivation and speed up computation. For backward compatibility with the existing `authtypes.NewModuleAddress`, we add a special case in `Module` function: when no derivation key is provided, we fallback to the "legacy" implementation. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func Module(moduleName string, derivationKeys ...[]byte) []byte{ if len(derivationKeys) == 0 { return authtypes.NewModuleAddress(modulenName) // legacy case } submoduleAddress := Hash("module", []byte(moduleName) + 0 + key) return fold((a, k) => Derive(a, k), subsubKeys, submoduleAddress) } ``` **Example 1** A lending BTC pool address would be: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} btcPool := address.Module("lending", btc.Address() }) ``` If we want to create an address for a module account depending on more than one key, we can concatenate them: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} btcAtomAMM := address.Module("amm", btc.Address() + atom.Address() }) ``` **Example 2** a smart-contract address could be constructed by: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} smartContractAddr = Module("mySmartContractVM", smartContractsNamespace, smartContractKey }) // which equals to: smartContractAddr = Derived( Module("mySmartContractVM", smartContractsNamespace), []{ smartContractKey }) ``` ### Schema Types A `typ` parameter used in `Hash` function SHOULD be unique for each account type. Since all Cosmos SDK account types are serialized in the state, we propose to use the protobuf message name string. Example: all public key types have a unique protobuf message type similar to: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package cosmos.crypto.sr25519; message PubKey { bytes key = 1; } ``` All protobuf messages have unique fully qualified names, in this example `cosmos.crypto.sr25519.PubKey`. These names are derived directly from .proto files in a standardized way and used in other places such as the type URL in `Any`s. We can easily obtain the name using `proto.MessageName(msg)`. ## Consequences ### Backwards Compatibility This ADR is compatible with what was committed and directly supported in the Cosmos SDK repository. ### Positive * a simple algorithm for generating addresses for new public keys, complex accounts and modules * the algorithm generalizes *native composed keys* * increased security and collision resistance of addresses * the approach is extensible for future use-cases - one can use other address types, as long as they don't conflict with the address length specified here (20 or 32 bytes). * support new account types. ### Negative * addresses do not communicate key type, a prefixed approach would have done this * addresses are 60% longer and will consume more storage space * requires a refactor of KVStore store keys to handle variable length addresses ### Neutral * protobuf message names are used as key type prefixes ## Further Discussions Some accounts can have a fixed name or may be constructed in other way (eg: modules). We were discussing an idea of an account with a predefined name (eg: `me.regen`), which could be used by institutions. Without going into details, these kinds of addresses are compatible with the hash based addresses described here as long as they don't have the same length. More specifically, any special account address must not have a length equal to 20 or 32 bytes. ## Appendix: Consulting session End of Dec 2020 we had a session with [Alan Szepieniec](https://scholar.google.be/citations?user=4LyZn8oAAAAJ\&hl=en) to consult the approach presented above. Alan general observations: * we don’t need 2-preimage resistance * we need 32bytes address space for collision resistance * when an attacker can control an input for object with an address then we have a problem with birthday attack * there is an issue with smart-contracts for hashing * sha2 mining can be use to breaking address pre-image Hashing algorithm * any attack breaking blake3 will break blake2 * Alan is pretty confident about the current security analysis of the blake hash algorithm. It was a finalist, and the author is well known in security analysis. Algorithm: * Alan recommends to hash the prefix: `address(pub_key) = hash(hash(key_type) + pub_key)[:32]`, main benefits: * we are free to user arbitrary long prefix names * we still don’t risk collisions * switch tables * discussion about penalization -> about adding prefix post hash * Aaron asked about post hash prefixes (`address(pub_key) = key_type + hash(pub_key)`) and differences. Alan noted that this approach has longer address space and it’s stronger. Algorithm for complex / composed keys: * merging tree like addresses with same algorithm are fine Module addresses: Should module addresses have different size to differentiate it? * we will need to set a pre-image prefix for module addresse to keept them in 32-byte space: `hash(hash('module') + module_key)` * Aaron observation: we already need to deal with variable length (to not break secp256k1 keys). Discssion about arithmetic hash function for ZKP * Posseidon / Rescue * Problem: much bigger risk because we don’t know much techniques and history of crypto-analysis of arithmetic constructions. It’s still a new ground and area of active research. Post quantum signature size * Alan suggestion: Falcon: speed / size ration - very good. * Aaron - should we think about it? Alan: based on early extrapolation this thing will get able to break EC cryptography in 2050 . But that’s a lot of uncertainty. But there is magic happening with recurions / linking / simulation and that can speedup the progress. Other ideas * Let’s say we use same key and two different address algorithms for 2 different use cases. Is it still safe to use it? Alan: if we want to hide the public key (which is not our use case), then it’s less secure but there are fixes. ### References * [Notes](https://hackmd.io/_NGWI4xZSbKzj1BkCqyZMw) # ADR 029: Fee Grant Module Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-029-fee-grant-module 2020/08/18: Initial Draft 2021/05/05: Removed height based expiration support and simplified naming. ## Changelog * 2020/08/18: Initial Draft * 2021/05/05: Removed height based expiration support and simplified naming. ## Status Accepted ## Context In order to make blockchain transactions, the signing account must possess a sufficient balance of the right denomination in order to pay fees. There are classes of transactions where needing to maintain a wallet with sufficient fees is a barrier to adoption. For instance, when proper permissions are setup, someone may temporarily delegate the ability to vote on proposals to a "burner" account that is stored on a mobile phone with only minimal security. Other use cases include workers tracking items in a supply chain or farmers submitting field data for analytics or compliance purposes. For all of these use cases, UX would be significantly enhanced by obviating the need for these accounts to always maintain the appropriate fee balance. This is especially true if we wanted to achieve enterprise adoption for something like supply chain tracking. While one solution would be to have a service that fills up these accounts automatically with the appropriate fees, a better UX would be provided by allowing these accounts to pull from a common fee pool account with proper spending limits. A single pool would reduce the churn of making lots of small "fill up" transactions and also more effectively leverages the resources of the organization setting up the pool. ## Decision As a solution we propose a module, `x/feegrant` which allows one account, the "granter" to grant another account, the "grantee" an allowance to spend the granter's account balance for fees within certain well-defined limits. Fee allowances are defined by the extensible `FeeAllowanceI` interface: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type FeeAllowanceI { // Accept can use fee payment requested as well as timestamp of the current block // to determine whether or not to process this. This is checked in // Keeper.UseGrantedFees and the return values should match how it is handled there. // // If it returns an error, the fee payment is rejected, otherwise it is accepted. // The FeeAllowance implementation is expected to update it's internal state // and will be saved again after an acceptance. // // If remove is true (regardless of the error), the FeeAllowance will be deleted from storage // (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees) Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg) (remove bool, err error) // ValidateBasic should evaluate this FeeAllowance for internal consistency. // Don't allow negative amounts, or negative periods for example. ValidateBasic() error } ``` Two basic fee allowance types, `BasicAllowance` and `PeriodicAllowance` are defined to support known use cases: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // BasicAllowance implements FeeAllowanceI with a one-time grant of tokens // that optionally expires. The delegatee can use up to SpendLimit to cover fees. message BasicAllowance { // spend_limit specifies the maximum amount of tokens that can be spent // by this allowance and will be updated as tokens are spent. If it is // empty, there is no spend limit and any amount of coins can be spent. repeated cosmos_sdk.v1.Coin spend_limit = 1; // expiration specifies an optional time when this allowance expires google.protobuf.Timestamp expiration = 2; } // PeriodicAllowance extends FeeAllowanceI to allow for both a maximum cap, // as well as a limit per time period. message PeriodicAllowance { BasicAllowance basic = 1; // period specifies the time duration in which period_spend_limit coins can // be spent before that allowance is reset google.protobuf.Duration period = 2; // period_spend_limit specifies the maximum number of coins that can be spent // in the period repeated cosmos_sdk.v1.Coin period_spend_limit = 3; // period_can_spend is the number of coins left to be spent before the period_reset time repeated cosmos_sdk.v1.Coin period_can_spend = 4; // period_reset is the time at which this period resets and a new one begins, // it is calculated from the start time of the first transaction after the // last period ended google.protobuf.Timestamp period_reset = 5; } ``` Allowances can be granted and revoked using `MsgGrantAllowance` and `MsgRevokeAllowance`: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MsgGrantAllowance adds permission for Grantee to spend up to Allowance // of fees from the account of Granter. message MsgGrantAllowance { string granter = 1; string grantee = 2; google.protobuf.Any allowance = 3; } // MsgRevokeAllowance removes any existing FeeAllowance from Granter to Grantee. message MsgRevokeAllowance { string granter = 1; string grantee = 2; } ``` In order to use allowances in transactions, we add a new field `granter` to the transaction `Fee` type: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package cosmos.tx.v1beta1; message Fee { repeated cosmos.base.v1beta1.Coin amount = 1; uint64 gas_limit = 2; string payer = 3; string granter = 4; } ``` `granter` must either be left empty or must correspond to an account which has granted a fee allowance to fee payer (either the first signer or the value of the `payer` field). A new `AnteDecorator` named `DeductGrantedFeeDecorator` will be created in order to process transactions with `fee_payer` set and correctly deduct fees based on fee allowances. ## Consequences ### Positive * improved UX for use cases where it is cumbersome to maintain an account balance just for fees ### Negative ### Neutral * a new field must be added to the transaction `Fee` message and a new `AnteDecorator` must be created to use it ## References * Blog article describing initial work: [Link](https://medium.com/regen-network/hacking-the-cosmos-cosmwasm-and-key-management-a08b9f561d1b) * Initial public specification: [Link](https://gist.github.com/aaronc/b60628017352df5983791cad30babe56) * Original subkeys proposal from B-harvest which influenced this design: [Link](https://github.com/cosmos/cosmos-sdk/issues/4480) # ADR 030: Authorization Module Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-030-authz-module ## Changelog * 2019-11-06: Initial Draft * 2020-10-12: Updated Draft * 2020-11-13: Accepted * 2020-05-06: proto API updates, use `sdk.Msg` instead of `sdk.ServiceMsg` (the latter concept was removed from Cosmos SDK) * 2022-04-20: Updated the `SendAuthorization` proto docs to clarify the `SpendLimit` is a required field. (Generic authorization can be used with bank msg type url to create limit less bank authorization) ## Status Accepted ## Abstract This ADR defines the `x/authz` module which allows accounts to grant authorizations to perform actions on behalf of that account to other accounts. ## Context The concrete use cases which motivated this module include: * the desire to delegate the ability to vote on proposals to other accounts besides the account which one has delegated stake * "sub-keys" functionality, as originally proposed in [#4480](https://github.com/cosmos/cosmos-sdk/issues/4480) which is a term used to describe the functionality provided by this module together with the `fee_grant` module from [ADR 029](/sdk/v0.50/build/architecture/adr-029-fee-grant-module) and the [group module](https://github.com/cosmos/cosmos-sdk/tree/release/v0.53.x/x/group). The "sub-keys" functionality roughly refers to the ability for one account to grant some subset of its capabilities to other accounts with possibly less robust, but easier to use security measures. For instance, a master account representing an organization could grant the ability to spend small amounts of the organization's funds to individual employee accounts. Or an individual (or group) with a multisig wallet could grant the ability to vote on proposals to any one of the member keys. The current implementation is based on work done by the [Gaian's team at Hackatom Berlin 2019](https://github.com/cosmos-gaians/cosmos-sdk/tree/hackatom/x/delegation). ## Decision We will create a module named `authz` which provides functionality for granting arbitrary privileges from one account (the *granter*) to another account (the *grantee*). Authorizations must be granted for a particular `Msg` service methods one by one using an implementation of `Authorization` interface. ### Types Authorizations determine exactly what privileges are granted. They are extensible and can be defined for any `Msg` service method even outside of the module where the `Msg` method is defined. `Authorization`s reference `Msg`s using their TypeURL. #### Authorization ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Authorization interface { proto.Message // MsgTypeURL returns the fully-qualified Msg TypeURL (as described in ADR 020), // which will process and accept or reject a request. MsgTypeURL() string // Accept determines whether this grant permits the provided sdk.Msg to be performed, and if // so provides an upgraded authorization instance. Accept(ctx sdk.Context, msg sdk.Msg) (AcceptResponse, error) // ValidateBasic does a simple validation check that // doesn't require access to any other information. ValidateBasic() error } // AcceptResponse instruments the controller of an authz message if the request is accepted // and if it should be updated or deleted. type AcceptResponse struct { // If Accept=true, the controller can accept and authorization and handle the update. Accept bool // If Delete=true, the controller must delete the authorization object and release // storage resources. Delete bool // Controller, who is calling Authorization.Accept must check if `Updated != nil`. If yes, // it must use the updated version and handle the update on the storage level. Updated Authorization } ``` For example a `SendAuthorization` like this is defined for `MsgSend` that takes a `SpendLimit` and updates it down to zero: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type SendAuthorization struct { // SpendLimit specifies the maximum amount of tokens that can be spent // by this authorization and will be updated as tokens are spent. This field is required. (Generic authorization // can be used with bank msg type url to create limit less bank authorization). SpendLimit sdk.Coins } func (a SendAuthorization) MsgTypeURL() string { return sdk.MsgTypeURL(&MsgSend{ }) } func (a SendAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptResponse, error) { mSend, ok := msg.(*MsgSend) if !ok { return authz.AcceptResponse{ }, sdkerrors.ErrInvalidType.Wrap("type mismatch") } limitLeft, isNegative := a.SpendLimit.SafeSub(mSend.Amount) if isNegative { return authz.AcceptResponse{ }, sdkerrors.ErrInsufficientFunds.Wrapf("requested amount is more than spend limit") } if limitLeft.IsZero() { return authz.AcceptResponse{ Accept: true, Delete: true }, nil } return authz.AcceptResponse{ Accept: true, Delete: false, Updated: &SendAuthorization{ SpendLimit: limitLeft }}, nil } ``` A different type of capability for `MsgSend` could be implemented using the `Authorization` interface with no need to change the underlying `bank` module. ##### Small notes on `AcceptResponse` * The `AcceptResponse.Accept` field will be set to `true` if the authorization is accepted. However, if it is rejected, the function `Accept` will raise an error (without setting `AcceptResponse.Accept` to `false`). * The `AcceptResponse.Updated` field will be set to a non-nil value only if there is a real change to the authorization. If authorization remains the same (as is, for instance, always the case for a [`GenericAuthorization`](#genericauthorization)), the field will be `nil`. ### `Msg` Service ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service Msg { // Grant grants the provided authorization to the grantee on the granter's // account with the provided expiration time. rpc Grant(MsgGrant) returns (MsgGrantResponse); // Exec attempts to execute the provided messages using // authorizations granted to the grantee. Each message should have only // one signer corresponding to the granter of the authorization. rpc Exec(MsgExec) returns (MsgExecResponse); // Revoke revokes any authorization corresponding to the provided method name on the // granter's account that has been granted to the grantee. rpc Revoke(MsgRevoke) returns (MsgRevokeResponse); } // Grant gives permissions to execute // the provided method with expiration time. message Grant { google.protobuf.Any authorization = 1 [(cosmos_proto.accepts_interface) = "cosmos.authz.v1beta1.Authorization"]; google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; } message MsgGrant { string granter = 1; string grantee = 2; Grant grant = 3 [(gogoproto.nullable) = false]; } message MsgExecResponse { cosmos.base.abci.v1beta1.Result result = 1; } message MsgExec { string grantee = 1; // Authorization Msg requests to execute. Each msg must implement Authorization interface repeated google.protobuf.Any msgs = 2 [(cosmos_proto.accepts_interface) = "cosmos.base.v1beta1.Msg"];; } ``` ### Router Middleware The `authz` `Keeper` will expose a `DispatchActions` method which allows other modules to send `Msg`s to the router based on `Authorization` grants: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper interface { // DispatchActions routes the provided msgs to their respective handlers if the grantee was granted an authorization // to send those messages by the first (and only) signer of each msg. DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs []sdk.Msg) sdk.Result` } ``` ### CLI #### `tx exec` Method When a CLI user wants to run a transaction on behalf of another account using `MsgExec`, they can use the `exec` method. For instance `gaiacli tx gov vote 1 yes --from --generate-only | gaiacli tx authz exec --send-as --from ` would send a transaction like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} MsgExec { Grantee: mykey, Msgs: []sdk.Msg{ MsgVote { ProposalID: 1, Voter: cosmos3thsdgh983egh823 Option: Yes } } } ``` #### `tx grant --from ` This CLI command will send a `MsgGrant` transaction. `authorization` should be encoded as JSON on the CLI. #### `tx revoke --from ` This CLI command will send a `MsgRevoke` transaction. ### Built-in Authorizations #### `SendAuthorization` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SendAuthorization allows the grantee to spend up to spend_limit coins from // the granter's account. message SendAuthorization { repeated cosmos.base.v1beta1.Coin spend_limit = 1; } ``` #### `GenericAuthorization` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // GenericAuthorization gives the grantee unrestricted permissions to execute // the provided method on behalf of the granter's account. message GenericAuthorization { option (cosmos_proto.implements_interface) = "Authorization"; // Msg, identified by it's type URL, to grant unrestricted permissions to execute string msg = 1; } ``` ## Consequences ### Positive * Users will be able to authorize arbitrary actions on behalf of their accounts to other users, improving key management for many use cases * The solution is more generic than previously considered approaches and the `Authorization` interface approach can be extended to cover other use cases by SDK users ### Negative ### Neutral ## References * Initial Hackatom implementation: [Link](https://github.com/cosmos-gaians/cosmos-sdk/tree/hackatom/x/delegation) * Post-Hackatom spec: [Link](https://gist.github.com/aaronc/b60628017352df5983791cad30babe56#delegation-module) * B-Harvest subkeys spec: [Link](https://github.com/cosmos/cosmos-sdk/issues/4480) # ADR 031: Protobuf Msg Services Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-031-msg-service 2020-10-05: Initial Draft 2021-04-21: Remove ServiceMsgs to follow Protobuf Any's spec, see #9063. ## Changelog * 2020-10-05: Initial Draft * 2021-04-21: Remove `ServiceMsg`s to follow Protobuf `Any`'s spec, see [#9063](https://github.com/cosmos/cosmos-sdk/issues/9063). ## Status Accepted ## Abstract We want to leverage protobuf `service` definitions for defining `Msg`s which will give us significant developer UX improvements in terms of the code that is generated and the fact that return types will now be well defined. ## Context Currently `Msg` handlers in the Cosmos SDK do have return values that are placed in the `data` field of the response. These return values, however, are not specified anywhere except in the golang handler code. In early conversations it was proposed that `Msg` return types be captured using a protobuf extension field, ex: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package cosmos.gov; message MsgSubmitProposal option (cosmos_proto.msg_return) = “uint64”; string delegator_address = 1; string validator_address = 2; repeated sdk.Coin amount = 3; } ``` This was never adopted, however. Having a well-specified return value for `Msg`s would improve client UX. For instance, in `x/gov`, `MsgSubmitProposal` returns the proposal ID as a big-endian `uint64`. This isn’t really documented anywhere and clients would need to know the internals of the Cosmos SDK to parse that value and return it to users. Also, there may be cases where we want to use these return values programatically. For instance, [Link](https://github.com/cosmos/cosmos-sdk/issues/7093) proposes a method for doing inter-module Ocaps using the `Msg` router. A well-defined return type would improve the developer UX for this approach. In addition, handler registration of `Msg` types tends to add a bit of boilerplate on top of keepers and is usually done through manual type switches. This isn't necessarily bad, but it does add overhead to creating modules. ## Decision We decide to use protobuf `service` definitions for defining `Msg`s as well as the code generated by them as a replacement for `Msg` handlers. Below we define how this will look for the `SubmitProposal` message from `x/gov` module. We start with a `Msg` `service` definition: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package cosmos.gov; service Msg { rpc SubmitProposal(MsgSubmitProposal) returns (MsgSubmitProposalResponse); } // Note that for backwards compatibility this uses MsgSubmitProposal as the request // type instead of the more canonical MsgSubmitProposalRequest message MsgSubmitProposal { google.protobuf.Any content = 1; string proposer = 2; } message MsgSubmitProposalResponse { uint64 proposal_id; } ``` While this is most commonly used for gRPC, overloading protobuf `service` definitions like this does not violate the intent of the [protobuf spec](https://developers.google.com/protocol-buffers/docs/proto3#services) which says: > If you don’t want to use gRPC, it’s also possible to use protocol buffers with your own RPC implementation. > With this approach, we would get an auto-generated `MsgServer` interface: In addition to clearly specifying return types, this has the benefit of generating client and server code. On the server side, this is almost like an automatically generated keeper method and could maybe be used intead of keepers eventually (see [#7093](https://github.com/cosmos/cosmos-sdk/issues/7093)): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package gov type MsgServer interface { SubmitProposal(context.Context, *MsgSubmitProposal) (*MsgSubmitProposalResponse, error) } ``` On the client side, developers could take advantage of this by creating RPC implementations that encapsulate transaction logic. Protobuf libraries that use asynchronous callbacks, like [protobuf.js](https://github.com/protobufjs/protobuf.js#using-services) could use this to register callbacks for specific messages even for transactions that include multiple `Msg`s. Each `Msg` service method should have exactly one request parameter: its corresponding `Msg` type. For example, the `Msg` service method `/cosmos.gov.v1beta1.Msg/SubmitProposal` above has exactly one request parameter, namely the `Msg` type `/cosmos.gov.v1beta1.MsgSubmitProposal`. It is important the reader understands clearly the nomenclature difference between a `Msg` service (a Protobuf service) and a `Msg` type (a Protobuf message), and the differences in their fully-qualified name. This convention has been decided over the more canonical `Msg...Request` names mainly for backwards compatibility, but also for better readability in `TxBody.messages` (see [Encoding section](#encoding) below): transactions containing `/cosmos.gov.MsgSubmitProposal` read better than those containing `/cosmos.gov.v1beta1.MsgSubmitProposalRequest`. One consequence of this convention is that each `Msg` type can be the request parameter of only one `Msg` service method. However, we consider this limitation a good practice in explicitness. ### Encoding Encoding of transactions generated with `Msg` services do not differ from current Protobuf transaction encoding as defined in [ADR-020](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding). We are encoding `Msg` types (which are exactly `Msg` service methods' request parameters) as `Any` in `Tx`s which involves packing the binary-encoded `Msg` with its type URL. ### Decoding Since `Msg` types are packed into `Any`, decoding transactions messages are done by unpacking `Any`s into `Msg` types. For more information, please refer to [ADR-020](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#transactions). ### Routing We propose to add a `msg_service_router` in BaseApp. This router is a key/value map which maps `Msg` types' `type_url`s to their corresponding `Msg` service method handler. Since there is a 1-to-1 mapping between `Msg` types and `Msg` service method, the `msg_service_router` has exactly one entry per `Msg` service method. When a transaction is processed by BaseApp (in CheckTx or in DeliverTx), its `TxBody.messages` are decoded as `Msg`s. Each `Msg`'s `type_url` is matched against an entry in the `msg_service_router`, and the respective `Msg` service method handler is called. For backward compatibility, the old handlers are not removed yet. If BaseApp receives a legacy `Msg` with no corresponding entry in the `msg_service_router`, it will be routed via its legacy `Route()` method into the legacy handler. ### Module Configuration In [ADR 021](/sdk/v0.50/build/architecture/adr-021-protobuf-query-encoding), we introduced a method `RegisterQueryService` to `AppModule` which allows for modules to register gRPC queriers. To register `Msg` services, we attempt a more extensible approach by converting `RegisterQueryService` to a more generic `RegisterServices` method: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type AppModule interface { RegisterServices(Configurator) ... } type Configurator interface { QueryServer() grpc.Server MsgServer() grpc.Server } // example module: func (am AppModule) RegisterServices(cfg Configurator) { types.RegisterQueryServer(cfg.QueryServer(), keeper) types.RegisterMsgServer(cfg.MsgServer(), keeper) } ``` The `RegisterServices` method and the `Configurator` interface are intended to evolve to satisfy the use cases discussed in [#7093](https://github.com/cosmos/cosmos-sdk/issues/7093) and [#7122](https://github.com/cosmos/cosmos-sdk/issues/7421). When `Msg` services are registered, the framework *should* verify that all `Msg` types implement the `sdk.Msg` interface and throw an error during initialization rather than later when transactions are processed. ### `Msg` Service Implementation Just like query services, `Msg` service methods can retrieve the `sdk.Context` from the `context.Context` parameter method using the `sdk.UnwrapSDKContext` method: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package gov func (k Keeper) SubmitProposal(goCtx context.Context, params *types.MsgSubmitProposal) (*MsgSubmitProposalResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) ... } ``` The `sdk.Context` should have an `EventManager` already attached by BaseApp's `msg_service_router`. Separate handler definition is no longer needed with this approach. ## Consequences This design changes how a module functionality is exposed and accessed. It deprecates the existing `Handler` interface and `AppModule.Route` in favor of [Protocol Buffer Services](https://developers.google.com/protocol-buffers/docs/proto3#services) and Service Routing described above. This dramatically simplifies the code. We don't need to create handlers and keepers any more. Use of Protocol Buffer auto-generated clients clearly separates the communication interfaces between the module and a modules user. The control logic (aka handlers and keepers) is not exposed any more. A module interface can be seen as a black box accessible through a client API. It's worth to note that the client interfaces are also generated by Protocol Buffers. This also allows us to change how we perform functional tests. Instead of mocking AppModules and Router, we will mock a client (server will stay hidden). More specifically: we will never mock `moduleA.MsgServer` in `moduleB`, but rather `moduleA.MsgClient`. One can think about it as working with external services (eg DBs, or online servers...). We assume that the transmission between clients and servers is correctly handled by generated Protocol Buffers. Finally, closing a module to client API opens desirable OCAP patterns discussed in ADR-033. Since server implementation and interface is hidden, nobody can hold "keepers"/servers and will be forced to relay on the client interface, which will drive developers for correct encapsulation and software engineering patterns. ### Pros * communicates return type clearly * manual handler registration and return type marshaling is no longer needed, just implement the interface and register it * communication interface is automatically generated, the developer can now focus only on the state transition methods - this would improve the UX of [#7093](https://github.com/cosmos/cosmos-sdk/issues/7093) approach (1) if we chose to adopt that * generated client code could be useful for clients and tests * dramatically reduces and simplifies the code ### Cons * using `service` definitions outside the context of gRPC could be confusing (but doesn’t violate the proto3 spec) ## References * [Initial Github Issue #7122](https://github.com/cosmos/cosmos-sdk/issues/7122) * [proto 3 Language Guide: Defining Services](https://developers.google.com/protocol-buffers/docs/proto3#services) * [ADR 020](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding) * [ADR 021](/sdk/v0.50/build/architecture/adr-021-protobuf-query-encoding) # ADR 032: Typed Events Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-032-typed-events 28-Sept-2020: Initial Draft ## Changelog * 28-Sept-2020: Initial Draft ## Authors * Anil Kumar (@anilcse) * Jack Zampolin (@jackzampolin) * Adam Bozanich (@boz) ## Status Proposed ## Abstract Currently in the Cosmos SDK, events are defined in the handlers for each message as well as `BeginBlock` and `EndBlock`. Each module doesn't have types defined for each event, they are implemented as `map[string]string`. Above all else this makes these events difficult to consume as it requires a great deal of raw string matching and parsing. This proposal focuses on updating the events to use **typed events** defined in each module such that emiting and subscribing to events will be much easier. This workflow comes from the experience of the Akash Network team. ## Context Currently in the Cosmos SDK, events are defined in the handlers for each message, meaning each module doesn't have a cannonical set of types for each event. Above all else this makes these events difficult to consume as it requires a great deal of raw string matching and parsing. This proposal focuses on updating the events to use **typed events** defined in each module such that emiting and subscribing to events will be much easier. This workflow comes from the experience of the Akash Network team. [Our platform](http://github.com/ovrclk/akash) requires a number of programatic on chain interactions both on the provider (datacenter - to bid on new orders and listen for leases created) and user (application developer - to send the app manifest to the provider) side. In addition the Akash team is now maintaining the IBC [`relayer`](https://github.com/ovrclk/relayer), another very event driven process. In working on these core pieces of infrastructure, and integrating lessons learned from Kubernetes developement, our team has developed a standard method for defining and consuming typed events in Cosmos SDK modules. We have found that it is extremely useful in building this type of event driven application. As the Cosmos SDK gets used more extensively for apps like `peggy`, other peg zones, IBC, DeFi, etc... there will be an exploding demand for event driven applications to support new features desired by users. We propose upstreaming our findings into the Cosmos SDK to enable all Cosmos SDK applications to quickly and easily build event driven apps to aid their core application. Wallets, exchanges, explorers, and defi protocols all stand to benefit from this work. If this proposal is accepted, users will be able to build event driven Cosmos SDK apps in go by just writing `EventHandler`s for their specific event types and passing them to `EventEmitters` that are defined in the Cosmos SDK. The end of this proposal contains a detailed example of how to consume events after this refactor. This proposal is specifically about how to consume these events as a client of the blockchain, not for intermodule communication. ## Decision **Step-1**: Implement additional functionality in the `types` package: `EmitTypedEvent` and `ParseTypedEvent` functions ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // types/events.go // EmitTypedEvent takes typed event and emits converting it into sdk.Event func (em *EventManager) EmitTypedEvent(event proto.Message) error { evtType := proto.MessageName(event) evtJSON, err := codec.ProtoMarshalJSON(event) if err != nil { return err } var attrMap map[string]json.RawMessage err = json.Unmarshal(evtJSON, &attrMap) if err != nil { return err } var attrs []abci.EventAttribute for k, v := range attrMap { attrs = append(attrs, abci.EventAttribute{ Key: []byte(k), Value: v, }) } em.EmitEvent(Event{ Type: evtType, Attributes: attrs, }) return nil } // ParseTypedEvent converts abci.Event back to typed event func ParseTypedEvent(event abci.Event) (proto.Message, error) { concreteGoType := proto.MessageType(event.Type) if concreteGoType == nil { return nil, fmt.Errorf("failed to retrieve the message of type %q", event.Type) } var value reflect.Value if concreteGoType.Kind() == reflect.Ptr { value = reflect.New(concreteGoType.Elem()) } else { value = reflect.Zero(concreteGoType) } protoMsg, ok := value.Interface().(proto.Message) if !ok { return nil, fmt.Errorf("%q does not implement proto.Message", event.Type) } attrMap := make(map[string]json.RawMessage) for _, attr := range event.Attributes { attrMap[string(attr.Key)] = attr.Value } attrBytes, err := json.Marshal(attrMap) if err != nil { return nil, err } err = jsonpb.Unmarshal(strings.NewReader(string(attrBytes)), protoMsg) if err != nil { return nil, err } return protoMsg, nil } ``` Here, the `EmitTypedEvent` is a method on `EventManager` which takes typed event as input and apply json serialization on it. Then it maps the JSON key/value pairs to `event.Attributes` and emits it in form of `sdk.Event`. `Event.Type` will be the type URL of the proto message. When we subscribe to emitted events on the CometBFT websocket, they are emitted in the form of an `abci.Event`. `ParseTypedEvent` parses the event back to it's original proto message. **Step-2**: Add proto definitions for typed events for msgs in each module: For example, let's take `MsgSubmitProposal` of `gov` module and implement this event's type. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // proto/cosmos/gov/v1beta1/gov.proto // Add typed event definition package cosmos.gov.v1beta1; message EventSubmitProposal { string from_address = 1; uint64 proposal_id = 2; TextProposal proposal = 3; } ``` **Step-3**: Refactor event emission to use the typed event created and emit using `sdk.EmitTypedEvent`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // x/gov/handler.go func handleMsgSubmitProposal(ctx sdk.Context, keeper keeper.Keeper, msg types.MsgSubmitProposalI) (*sdk.Result, error) { ... types.Context.EventManager().EmitTypedEvent( &EventSubmitProposal{ FromAddress: fromAddress, ProposalId: id, Proposal: proposal, }, ) ... } ``` ### How to subscribe to these typed events in `Client` > NOTE: Full code example below Users will be able to subscribe using `client.Context.Client.Subscribe` and consume events which are emitted using `EventHandler`s. Akash Network has built a simple [`pubsub`](https://github.com/ovrclk/akash/blob/90d258caeb933b611d575355b8df281208a214f8/pubsub/bus.go#L20). This can be used to subscribe to `abci.Events` and [publish](https://github.com/ovrclk/akash/blob/90d258caeb933b611d575355b8df281208a214f8/events/publish.go#L21) them as typed events. Please see the below code sample for more detail on this flow looks for clients. ## Consequences ### Positive * Improves consistency of implementation for the events currently in the Cosmos SDK * Provides a much more ergonomic way to handle events and facilitates writing event driven applications * This implementation will support a middleware ecosystem of `EventHandler`s ### Negative ## Detailed code example of publishing events This ADR also proposes adding affordances to emit and consume these events. This way developers will only need to write `EventHandler`s which define the actions they desire to take. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // EventEmitter is a type that describes event emitter functions // This should be defined in `types/events.go` type EventEmitter func(context.Context, client.Context, ...EventHandler) error // EventHandler is a type of function that handles events coming out of the event bus // This should be defined in `types/events.go` type EventHandler func(proto.Message) error // Sample use of the functions below func main() { ctx, cancel := context.WithCancel(context.Background()) if err := TxEmitter(ctx, client.Context{ }.WithNodeURI("tcp://localhost:26657"), SubmitProposalEventHandler); err != nil { cancel() panic(err) } return } // SubmitProposalEventHandler is an example of an event handler that prints proposal details // when any EventSubmitProposal is emitted. func SubmitProposalEventHandler(ev proto.Message) (err error) { switch event := ev.(type) { // Handle governance proposal events creation events case govtypes.EventSubmitProposal: // Users define business logic here e.g. fmt.Println(ev.FromAddress, ev.ProposalId, ev.Proposal) return nil default: return nil } } // TxEmitter is an example of an event emitter that emits just transaction events. This can and // should be implemented somewhere in the Cosmos SDK. The Cosmos SDK can include an EventEmitters for tm.event='Tx' // and/or tm.event='NewBlock' (the new block events may contain typed events) func TxEmitter(ctx context.Context, cliCtx client.Context, ehs ...EventHandler) (err error) { // Instantiate and start CometBFT RPC client client, err := cliCtx.GetNode() if err != nil { return err } if err = client.Start(); err != nil { return err } // Start the pubsub bus bus := pubsub.NewBus() defer bus.Close() // Initialize a new error group eg, ctx := errgroup.WithContext(ctx) // Publish chain events to the pubsub bus eg.Go(func() error { return PublishChainTxEvents(ctx, client, bus, simapp.ModuleBasics) }) // Subscribe to the bus events subscriber, err := bus.Subscribe() if err != nil { return err } // Handle all the events coming out of the bus eg.Go(func() error { var err error for { select { case <-ctx.Done(): return nil case <-subscriber.Done(): return nil case ev := <-subscriber.Events(): for _, eh := range ehs { if err = eh(ev); err != nil { break } } } } return nil }) return group.Wait() } // PublishChainTxEvents events using cmtclient. Waits on context shutdown signals to exit. func PublishChainTxEvents(ctx context.Context, client cmtclient.EventsClient, bus pubsub.Bus, mb module.BasicManager) (err error) { // Subscribe to transaction events txch, err := client.Subscribe(ctx, "txevents", "tm.event='Tx'", 100) if err != nil { return err } // Unsubscribe from transaction events on function exit defer func() { err = client.UnsubscribeAll(ctx, "txevents") }() // Use errgroup to manage concurrency g, ctx := errgroup.WithContext(ctx) // Publish transaction events in a goroutine g.Go(func() error { var err error for { select { case <-ctx.Done(): break case ed := <-ch: switch evt := ed.Data.(type) { case cmttypes.EventDataTx: if !evt.Result.IsOK() { continue } // range over events, parse them using the basic manager and // send them to the pubsub bus for _, abciEv := range events { typedEvent, err := sdk.ParseTypedEvent(abciEv) if err != nil { return er } if err := bus.Publish(typedEvent); err != nil { bus.Close() return } continue } } } } return err }) // Exit on error or context cancelation return g.Wait() } ``` ## References * [Publish Custom Events via a bus](https://github.com/ovrclk/akash/blob/90d258caeb933b611d575355b8df281208a214f8/events/publish.go#L19-L58) * [Consuming the events in `Client`](https://github.com/ovrclk/deploy/blob/bf6c633ab6c68f3026df59efd9982d6ca1bf0561/cmd/event-handlers.go#L57) # ADR 033: Protobuf-based Inter-Module Communication Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-033-protobuf-inter-module-comm 2020-10-05: Initial Draft ## Changelog * 2020-10-05: Initial Draft ## Status Proposed ## Abstract This ADR introduces a system for permissioned inter-module communication leveraging the protobuf `Query` and `Msg` service definitions defined in [ADR 021](/sdk/v0.50/build/architecture/adr-021-protobuf-query-encoding) and [ADR 031](/sdk/v0.50/build/architecture/adr-031-msg-service) which provides: * stable protobuf based module interfaces to potentially later replace the keeper paradigm * stronger inter-module object capabilities (OCAPs) guarantees * module accounts and sub-account authorization ## Context In the current Cosmos SDK documentation on the [Object-Capability Model](/sdk/latest/guides/module-design/ocap), it is stated that: > We assume that a thriving ecosystem of Cosmos SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules. There is currently not a thriving ecosystem of Cosmos SDK modules. We hypothesize that this is in part due to: 1. lack of a stable v1.0 Cosmos SDK to build modules off of. Module interfaces are changing, sometimes dramatically, from point release to point release, often for good reasons, but this does not create a stable foundation to build on. 2. lack of a properly implemented object capability or even object-oriented encapsulation system which makes refactors of module keeper interfaces inevitable because the current interfaces are poorly constrained. ### `x/bank` Case Study Currently the `x/bank` keeper gives pretty much unrestricted access to any module which references it. For instance, the `SetBalance` method allows the caller to set the balance of any account to anything, bypassing even proper tracking of supply. There appears to have been some later attempts to implement some semblance of OCAPs using module-level minting, staking and burning permissions. These permissions allow a module to mint, burn or delegate tokens with reference to the module’s own account. These permissions are actually stored as a `[]string` array on the `ModuleAccount` type in state. However, these permissions don’t really do much. They control what modules can be referenced in the `MintCoins`, `BurnCoins` and `DelegateCoins***` methods, but for one there is no unique object capability token that controls access — just a simple string. So the `x/upgrade` module could mint tokens for the `x/staking` module simple by calling `MintCoins(“staking”)`. Furthermore, all modules which have access to these keeper methods, also have access to `SetBalance` negating any other attempt at OCAPs and breaking even basic object-oriented encapsulation. ## Decision Based on [ADR-021](/sdk/v0.50/build/architecture/adr-021-protobuf-query-encoding) and [ADR-031](/sdk/v0.50/build/architecture/adr-031-msg-service), we introduce the Inter-Module Communication framework for secure module authorization and OCAPs. When implemented, this could also serve as an alternative to the existing paradigm of passing keepers between modules. The approach outlined here-in is intended to form the basis of a Cosmos SDK v1.0 that provides the necessary stability and encapsulation guarantees that allow a thriving module ecosystem to emerge. Of particular note — the decision is to *enable* this functionality for modules to adopt at their own discretion. Proposals to migrate existing modules to this new paradigm will have to be a separate conversation, potentially addressed as amendments to this ADR. ### New "Keeper" Paradigm In [ADR 021](/sdk/v0.50/build/architecture/adr-021-protobuf-query-encoding), a mechanism for using protobuf service definitions to define queriers was introduced and in [ADR 31](/sdk/v0.50/build/architecture/adr-031-msg-service), a mechanism for using protobuf service to define `Msg`s was added. Protobuf service definitions generate two golang interfaces representing the client and server sides of a service plus some helper code. Here is a minimal example for the bank `cosmos.bank.Msg/Send` message type: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package bank type MsgClient interface { Send(context.Context, *MsgSend, opts ...grpc.CallOption) (*MsgSendResponse, error) } type MsgServer interface { Send(context.Context, *MsgSend) (*MsgSendResponse, error) } ``` [ADR 021](/sdk/v0.50/build/architecture/adr-021-protobuf-query-encoding) and [ADR 31](/sdk/v0.50/build/architecture/adr-031-msg-service) specifies how modules can implement the generated `QueryServer` and `MsgServer` interfaces as replacements for the legacy queriers and `Msg` handlers respectively. In this ADR we explain how modules can make queries and send `Msg`s to other modules using the generated `QueryClient` and `MsgClient` interfaces and propose this mechanism as a replacement for the existing `Keeper` paradigm. To be clear, this ADR does not necessitate the creation of new protobuf definitions or services. Rather, it leverages the same proto based service interfaces already used by clients for inter-module communication. Using this `QueryClient`/`MsgClient` approach has the following key benefits over exposing keepers to external modules: 1. Protobuf types are checked for breaking changes using [buf](https://buf.build/docs/breaking-overview) and because of the way protobuf is designed this will give us strong backwards compatibility guarantees while allowing for forward evolution. 2. The separation between the client and server interfaces will allow us to insert permission checking code in between the two which checks if one module is authorized to send the specified `Msg` to the other module providing a proper object capability system (see below). 3. The router for inter-module communication gives us a convenient place to handle rollback of transactions, enabling atomicy of operations ([currently a problem](https://github.com/cosmos/cosmos-sdk/issues/8030)). Any failure within a module-to-module call would result in a failure of the entire transaction This mechanism has the added benefits of: * reducing boilerplate through code generation, and * allowing for modules in other languages either via a VM like CosmWasm or sub-processes using gRPC ### Inter-module Communication To use the `Client` generated by the protobuf compiler we need a `grpc.ClientConn` [interface](https://github.com/grpc/grpc-go/blob/v1.49.x/clientconn.go#L441-L450) implementation. For this we introduce a new type, `ModuleKey`, which implements the `grpc.ClientConn` interface. `ModuleKey` can be thought of as the "private key" corresponding to a module account, where authentication is provided through use of a special `Invoker()` function, described in more detail below. Blockchain users (external clients) use their account's private key to sign transactions containing `Msg`s where they are listed as signers (each message specifies required signers with `Msg.GetSigner`). The authentication checks is performed by `AnteHandler`. Here, we extend this process, by allowing modules to be identified in `Msg.GetSigners`. When a module wants to trigger the execution a `Msg` in another module, its `ModuleKey` acts as the sender (through the `ClientConn` interface we describe below) and is set as a sole "signer". It's worth to note that we don't use any cryptographic signature in this case. For example, module `A` could use its `A.ModuleKey` to create `MsgSend` object for `/cosmos.bank.Msg/Send` transaction. `MsgSend` validation will assure that the `from` account (`A.ModuleKey` in this case) is the signer. Here's an example of a hypothetical module `foo` interacting with `x/bank`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package foo type FooMsgServer { // ... bankQuery bank.QueryClient bankMsg bank.MsgClient } func NewFooMsgServer(moduleKey RootModuleKey, ...) FooMsgServer { // ... return FooMsgServer { // ... modouleKey: moduleKey, bankQuery: bank.NewQueryClient(moduleKey), bankMsg: bank.NewMsgClient(moduleKey), } } func (foo *FooMsgServer) Bar(ctx context.Context, req *MsgBarRequest) (*MsgBarResponse, error) { balance, err := foo.bankQuery.Balance(&bank.QueryBalanceRequest{ Address: fooMsgServer.moduleKey.Address(), Denom: "foo" }) ... res, err := foo.bankMsg.Send(ctx, &bank.MsgSendRequest{ FromAddress: fooMsgServer.moduleKey.Address(), ... }) ... } ``` This design is also intended to be extensible to cover use cases of more fine grained permissioning like minting by denom prefix being restricted to certain modules (as discussed in [#7459](https://github.com/cosmos/cosmos-sdk/pull/7459#discussion_r529545528)). ### `ModuleKey`s and `ModuleID`s A `ModuleKey` can be thought of as a "private key" for a module account and a `ModuleID` can be thought of as the corresponding "public key". From the [ADR 028](/sdk/v0.50/build/architecture/adr-028-public-key-addresses), modules can have both a root module account and any number of sub-accounts or derived accounts that can be used for different pools (ex. staking pools) or managed accounts (ex. group accounts). We can also think of module sub-accounts as similar to derived keys - there is a root key and then some derivation path. `ModuleID` is a simple struct which contains the module name and optional "derivation" path, and forms its address based on the `AddressHash` method from [the ADR-028](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-028-public-key-addresses.md): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ModuleID struct { ModuleName string Path []byte } func (key ModuleID) Address() []byte { return AddressHash(key.ModuleName, key.Path) } ``` In addition to being able to generate a `ModuleID` and address, a `ModuleKey` contains a special function called `Invoker` which is the key to safe inter-module access. The `Invoker` creates an `InvokeFn` closure which is used as an `Invoke` method in the `grpc.ClientConn` interface and under the hood is able to route messages to the appropriate `Msg` and `Query` handlers performing appropriate security checks on `Msg`s. This allows for even safer inter-module access than keeper's whose private member variables could be manipulated through reflection. Golang does not support reflection on a function closure's captured variables and direct manipulation of memory would be needed for a truly malicious module to bypass the `ModuleKey` security. The two `ModuleKey` types are `RootModuleKey` and `DerivedModuleKey`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Invoker func(callInfo CallInfo) func(ctx context.Context, request, response interface{ }, opts ...interface{ }) error type CallInfo { Method string Caller ModuleID } type RootModuleKey struct { moduleName string invoker Invoker } func (rm RootModuleKey) Derive(path []byte) DerivedModuleKey { /* ... */ } type DerivedModuleKey struct { moduleName string path []byte invoker Invoker } ``` A module can get access to a `DerivedModuleKey`, using the `Derive(path []byte)` method on `RootModuleKey` and then would use this key to authenticate `Msg`s from a sub-account. Ex: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package foo func (fooMsgServer *MsgServer) Bar(ctx context.Context, req *MsgBar) (*MsgBarResponse, error) { derivedKey := fooMsgServer.moduleKey.Derive(req.SomePath) bankMsgClient := bank.NewMsgClient(derivedKey) res, err := bankMsgClient.Balance(ctx, &bank.MsgSend{ FromAddress: derivedKey.Address(), ... }) ... } ``` In this way, a module can gain permissioned access to a root account and any number of sub-accounts and send authenticated `Msg`s from these accounts. The `Invoker` `callInfo.Caller` parameter is used under the hood to distinguish between different module accounts, but either way the function returned by `Invoker` only allows `Msg`s from either the root or a derived module account to pass through. Note that `Invoker` itself returns a function closure based on the `CallInfo` passed in. This will allow client implementations in the future that cache the invoke function for each method type avoiding the overhead of hash table lookup. This would reduce the performance overhead of this inter-module communication method to the bare minimum required for checking permissions. To re-iterate, the closure only allows access to authorized calls. There is no access to anything else regardless of any name impersonation. Below is a rough sketch of the implementation of `grpc.ClientConn.Invoke` for `RootModuleKey`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (key RootModuleKey) Invoke(ctx context.Context, method string, args, reply interface{ }, opts ...grpc.CallOption) error { f := key.invoker(CallInfo { Method: method, Caller: ModuleID { ModuleName: key.moduleName }}) return f(ctx, args, reply) } ``` ### `AppModule` Wiring and Requirements In [ADR 031](/sdk/v0.50/build/architecture/adr-031-msg-service), the `AppModule.RegisterService(Configurator)` method was introduced. To support inter-module communication, we extend the `Configurator` interface to pass in the `ModuleKey` and to allow modules to specify their dependencies on other modules using `RequireServer()`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Configurator interface { MsgServer() grpc.Server QueryServer() grpc.Server ModuleKey() ModuleKey RequireServer(msgServer interface{ }) } ``` The `ModuleKey` is passed to modules in the `RegisterService` method itself so that `RegisterServices` serves as a single entry point for configuring module services. This is intended to also have the side-effect of greatly reducing boilerplate in `app.go`. For now, `ModuleKey`s will be created based on `AppModuleBasic.Name()`, but a more flexible system may be introduced in the future. The `ModuleManager` will handle creation of module accounts behind the scenes. Because modules do not get direct access to each other anymore, modules may have unfulfilled dependencies. To make sure that module dependencies are resolved at startup, the `Configurator.RequireServer` method should be added. The `ModuleManager` will make sure that all dependencies declared with `RequireServer` can be resolved before the app starts. An example module `foo` could declare it's dependency on `x/bank` like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package foo func (am AppModule) RegisterServices(cfg Configurator) { cfg.RequireServer((*bank.QueryServer)(nil)) cfg.RequireServer((*bank.MsgServer)(nil)) } ``` ### Security Considerations In addition to checking for `ModuleKey` permissions, a few additional security precautions will need to be taken by the underlying router infrastructure. #### Recursion and Re-entry Recursive or re-entrant method invocations pose a potential security threat. This can be a problem if Module A calls Module B and Module B calls module A again in the same call. One basic way for the router system to deal with this is to maintain a call stack which prevents a module from being referenced more than once in the call stack so that there is no re-entry. A `map[string]interface{}` table in the router could be used to perform this security check. #### Queries Queries in Cosmos SDK are generally un-permissioned so allowing one module to query another module should not pose any major security threats assuming basic precautions are taken. The basic precaution that the router system will need to take is making sure that the `sdk.Context` passed to query methods does not allow writing to the store. This can be done for now with a `CacheMultiStore` as is currently done for `BaseApp` queries. ### Internal Methods In many cases, we may wish for modules to call methods on other modules which are not exposed to clients at all. For this purpose, we add the `InternalServer` method to `Configurator`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Configurator interface { MsgServer() grpc.Server QueryServer() grpc.Server InternalServer() grpc.Server } ``` As an example, x/slashing's Slash must call x/staking's Slash, but we don't want to expose x/staking's Slash to end users and clients. Internal protobuf services will be defined in a corresponding `internal.proto` file in the given module's proto package. Services registered against `InternalServer` will be callable from other modules but not by external clients. An alternative solution to internal-only methods could involve hooks / plugins as discussed [here](https://github.com/cosmos/cosmos-sdk/pull/7459#issuecomment-733807753). A more detailed evaluation of a hooks / plugin system will be addressed later in follow-ups to this ADR or as a separate ADR. ### Authorization By default, the inter-module router requires that messages are sent by the first signer returned by `GetSigners`. The inter-module router should also accept authorization middleware such as that provided by [ADR 030](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-030-authz-module.md). This middleware will allow accounts to otherwise specific module accounts to perform actions on their behalf. Authorization middleware should take into account the need to grant certain modules effectively "admin" privileges to other modules. This will be addressed in separate ADRs or updates to this ADR. ### Future Work Other future improvements may include: * custom code generation that: * simplifies interfaces (ex. generates code with `sdk.Context` instead of `context.Context`) * optimizes inter-module calls - for instance caching resolved methods after first invocation * combining `StoreKey`s and `ModuleKey`s into a single interface so that modules have a single OCAPs handle * code generation which makes inter-module communication more performant * decoupling `ModuleKey` creation from `AppModuleBasic.Name()` so that app's can override root module account names * inter-module hooks and plugins ## Alternatives ### MsgServices vs `x/capability` The `x/capability` module does provide a proper object-capability implementation that can be used by any module in the Cosmos SDK and could even be used for inter-module OCAPs as described in [#5931](https://github.com/cosmos/cosmos-sdk/issues/5931). The advantages of the approach described in this ADR are mostly around how it integrates with other parts of the Cosmos SDK, specifically: * protobuf so that: * code generation of interfaces can be leveraged for a better dev UX * module interfaces are versioned and checked for breakage using [buf](https://docs.buf.build/breaking-overview) * sub-module accounts as per ADR 028 * the general `Msg` passing paradigm and the way signers are specified by `GetSigners` Also, this is a complete replacement for keepers and could be applied to *all* inter-module communication whereas the `x/capability` approach in #5931 would need to be applied method by method. ## Consequences ### Backwards Compatibility This ADR is intended to provide a pathway to a scenario where there is greater long term compatibility between modules. In the short-term, this will likely result in breaking certain `Keeper` interfaces which are too permissive and/or replacing `Keeper` interfaces altogether. ### Positive * an alternative to keepers which can more easily lead to stable inter-module interfaces * proper inter-module OCAPs * improved module developer DevX, as commented on by several particpants on [Architecture Review Call, Dec 3](https://hackmd.io/E0wxxOvRQ5qVmTf6N_k84Q) * lays the groundwork for what can be a greatly simplified `app.go` * router can be setup to enforce atomic transactions for module-to-module calls ### Negative * modules which adopt this will need significant refactoring ### Neutral ## Test Cases \[optional] ## References * [ADR 021](/sdk/v0.50/build/architecture/adr-021-protobuf-query-encoding) * [ADR 031](/sdk/v0.50/build/architecture/adr-031-msg-service) * [ADR 028](/sdk/v0.50/build/architecture/adr-028-public-key-addresses) * [ADR 030 draft](https://github.com/cosmos/cosmos-sdk/pull/7105) * [Object-Capability Model](/sdk/latest/guides/module-design/ocap) # ADR 034: Account Rekeying Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-034-account-rekeying 30-09-2020: Initial Draft ## Changelog * 30-09-2020: Initial Draft ## Status PROPOSED ## Abstract Account rekeying is a process hat allows an account to replace its authentication pubkey with a new one. ## Context Currently, in the Cosmos SDK, the address of an auth `BaseAccount` is based on the hash of the public key. Once an account is created, the public key for the account is set in stone, and cannot be changed. This can be a problem for users, as key rotation is a useful security practice, but is not possible currently. Furthermore, as multisigs are a type of pubkey, once a multisig for an account is set, it can not be updated. This is problematic, as multisigs are often used by organizations or companies, who may need to change their set of multisig signers for internal reasons. Transferring all the assets of an account to a new account with the updated pubkey is not sufficient, because some "engagements" of an account are not easily transferable. For example, in staking, to transfer bonded Atoms, an account would have to unbond all delegations and wait the three week unbonding period. Even more significantly, for validator operators, ownership over a validator is not transferrable at all, meaning that the operator key for a validator can never be updated, leading to poor operational security for validators. ## Decision We propose the addition of a new feature to `x/auth` that allows accounts to update the public key associated with their account, while keeping the address the same. This is possible because the Cosmos SDK `BaseAccount` stores the public key for an account in state, instead of making the assumption that the public key is included in the transaction (whether explicitly or implicitly through the signature) as in other blockchains such as Bitcoin and Ethereum. Because the public key is stored on chain, it is okay for the public key to not hash to the address of an account, as the address is not pertinent to the signature checking process. To build this system, we design a new Msg type as follows: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service Msg { rpc ChangePubKey(MsgChangePubKey) returns (MsgChangePubKeyResponse); } message MsgChangePubKey { string address = 1; google.protobuf.Any pub_key = 2; } message MsgChangePubKeyResponse {} ``` The MsgChangePubKey transaction needs to be signed by the existing pubkey in state. Once, approved, the handler for this message type, which takes in the AccountKeeper, will update the in-state pubkey for the account and replace it with the pubkey from the Msg. An account that has had its pubkey changed cannot be automatically pruned from state. This is because if pruned, the original pubkey of the account would be needed to recreate the same address, but the owner of the address may not have the original pubkey anymore. Currently, we do not automatically prune any accounts anyways, but we would like to keep this option open the road (this is the purpose of account numbers). To resolve this, we charge an additional gas fee for this operation to compensate for this this externality (this bound gas amount is configured as parameter `PubKeyChangeCost`). The bonus gas is charged inside the handler, using the `ConsumeGas` function. Furthermore, in the future, we can allow accounts that have rekeyed manually prune themselves using a new Msg type such as `MsgDeleteAccount`. Manually pruning accounts can give a gas refund as an incentive for performing the action. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} amount := ak.GetParams(ctx).PubKeyChangeCost ctx.GasMeter().ConsumeGas(amount, "pubkey change fee") ``` Every time a key for an address is changed, we will store a log of this change in the state of the chain, thus creating a stack of all previous keys for an address and the time intervals for which they were active. This allows dapps and clients to easily query past keys for an account which may be useful for features such as verifying timestamped off-chain signed messages. ## Consequences ### Positive * Will allow users and validator operators to employ better operational security practices with key rotation. * Will allow organizations or groups to easily change and add/remove multisig signers. ### Negative Breaks the current assumed relationship between address and pubkeys as H(pubkey) = address. This has a couple of consequences. * This makes wallets that support this feature more complicated. For example, if an address on chain was updated, the corresponding key in the CLI wallet also needs to be updated. * Cannot automatically prune accounts with 0 balance that have had their pubkey changed. ### Neutral * While the purpose of this is intended to allow the owner of an account to update to a new pubkey they own, this could technically also be used to transfer ownership of an account to a new owner. For example, this could be use used to sell a staked position without unbonding or an account that has vesting tokens. However, the friction of this is very high as this would essentially have to be done as a very specific OTC trade. Furthermore, additional constraints could be added to prevent accouns with Vesting tokens to use this feature. * Will require that PubKeys for an account are included in the genesis exports. ## References * [Link](https://www.algorand.com/resources/blog/announcing-rekeying) # ADR 035: Rosetta API Support Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-035-rosetta-api-support Jonathan Gimeno (@jgimeno) David Grierson (@senormonito) Alessio Treglia (@alessio) Frojdy Dymylja (@fdymylja) ## Authors * Jonathan Gimeno (@jgimeno) * David Grierson (@senormonito) * Alessio Treglia (@alessio) * Frojdy Dymylja (@fdymylja) ## Changelog * 2021-05-12: the external library [cosmos-rosetta-gateway](https://github.com/tendermint/cosmos-rosetta-gateway) has been moved within the Cosmos SDK. ## Context [Rosetta API](https://www.rosetta-api.org/) is an open-source specification and set of tools developed by Coinbase to standardise blockchain interactions. Through the use of a standard API for integrating blockchain applications it will * Be easier for a user to interact with a given blockchain * Allow exchanges to integrate new blockchains quickly and easily * Enable application developers to build cross-blockchain applications such as block explorers, wallets and dApps at considerably lower cost and effort. ## Decision It is clear that adding Rosetta API support to the Cosmos SDK will bring value to all the developers and Cosmos SDK based chains in the ecosystem. How it is implemented is key. The driving principles of the proposed design are: 1. **Extensibility:** it must be as riskless and painless as possible for application developers to set-up network configurations to expose Rosetta API-compliant services. 2. **Long term support:** This proposal aims to provide support for all the supported Cosmos SDK release series. 3. **Cost-efficiency:** Backporting changes to Rosetta API specifications from `master` to the various stable branches of Cosmos SDK is a cost that needs to be reduced. We will achieve these delivering on these principles by the following: 1. There will be a package `rosetta/lib` for the implementation of the core Rosetta API features, particularly: a. The types and interfaces (`Client`, `OfflineClient`...), this separates design from implementation detail. b. The `Server` functionality as this is independent of the Cosmos SDK version. c. The `Online/OfflineNetwork`, which is not exported, and implements the rosetta API using the `Client` interface to query the node, build tx and so on. d. The `errors` package to extend rosetta errors. 2. Due to differences between the Cosmos release series, each series will have its own specific implementation of `Client` interface. 3. There will be two options for starting an API service in applications: a. API shares the application process b. API-specific process. ## Architecture ### The External Repo As section will describe the proposed external library, including the service implementation, plus the defined types and interfaces. #### Server `Server` is a simple `struct` that is started and listens to the port specified in the settings. This is meant to be used across all the Cosmos SDK versions that are actively supported. The constructor follows: `func NewServer(settings Settings) (Server, error)` `Settings`, which are used to construct a new server, are the following: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Settings define the rosetta server settings type Settings struct { // Network contains the information regarding the network Network *types.NetworkIdentifier // Client is the online API handler Client crgtypes.Client // Listen is the address the handler will listen at Listen string // Offline defines if the rosetta service should be exposed in offline mode Offline bool // Retries is the number of readiness checks that will be attempted when instantiating the handler // valid only for online API Retries int // RetryWait is the time that will be waited between retries RetryWait time.Duration } ``` #### Types Package types uses a mixture of rosetta types and custom defined type wrappers, that the client must parse and return while executing operations. ##### Interfaces Every SDK version uses a different format to connect (rpc, gRPC, etc), query and build transactions, we have abstracted this in what is the `Client` interface. The client uses rosetta types, while the `Online/OfflineNetwork` takes care of returning correctly parsed rosetta responses and errors. Each Cosmos SDK release series will have their own `Client` implementations. Developers can implement their own custom `Client`s as required. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Client defines the API the client implementation should provide. type Client interface { // Needed if the client needs to perform some action before connecting. Bootstrap() error // Ready checks if the servicer constraints for queries are satisfied // for example the node might still not be ready, it's useful in process // when the rosetta instance might come up before the node itself // the servicer must return nil if the node is ready Ready() error // Data API // Balances fetches the balance of the given address // if height is not nil, then the balance will be displayed // at the provided height, otherwise last block balance will be returned Balances(ctx context.Context, addr string, height *int64) ([]*types.Amount, error) // BlockByHashAlt gets a block and its transaction at the provided height BlockByHash(ctx context.Context, hash string) (BlockResponse, error) // BlockByHeightAlt gets a block given its height, if height is nil then last block is returned BlockByHeight(ctx context.Context, height *int64) (BlockResponse, error) // BlockTransactionsByHash gets the block, parent block and transactions // given the block hash. BlockTransactionsByHash(ctx context.Context, hash string) (BlockTransactionsResponse, error) // BlockTransactionsByHash gets the block, parent block and transactions // given the block hash. BlockTransactionsByHeight(ctx context.Context, height *int64) (BlockTransactionsResponse, error) // GetTx gets a transaction given its hash GetTx(ctx context.Context, hash string) (*types.Transaction, error) // GetUnconfirmedTx gets an unconfirmed Tx given its hash // NOTE(fdymylja): NOT IMPLEMENTED YET! GetUnconfirmedTx(ctx context.Context, hash string) (*types.Transaction, error) // Mempool returns the list of the current non confirmed transactions Mempool(ctx context.Context) ([]*types.TransactionIdentifier, error) // Peers gets the peers currently connected to the node Peers(ctx context.Context) ([]*types.Peer, error) // Status returns the node status, such as sync data, version etc Status(ctx context.Context) (*types.SyncStatus, error) // Construction API // PostTx posts txBytes to the node and returns the transaction identifier plus metadata related // to the transaction itself. PostTx(txBytes []byte) (res *types.TransactionIdentifier, meta map[string]interface{ }, err error) // ConstructionMetadataFromOptions ConstructionMetadataFromOptions(ctx context.Context, options map[string]interface{ }) (meta map[string]interface{ }, err error) OfflineClient } // OfflineClient defines the functionalities supported without having access to the node type OfflineClient interface { NetworkInformationProvider // SignedTx returns the signed transaction given the tx bytes (msgs) plus the signatures SignedTx(ctx context.Context, txBytes []byte, sigs []*types.Signature) (signedTxBytes []byte, err error) // TxOperationsAndSignersAccountIdentifiers returns the operations related to a transaction and the account // identifiers if the transaction is signed TxOperationsAndSignersAccountIdentifiers(signed bool, hexBytes []byte) (ops []*types.Operation, signers []*types.AccountIdentifier, err error) // ConstructionPayload returns the construction payload given the request ConstructionPayload(ctx context.Context, req *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error) // PreprocessOperationsToOptions returns the options given the preprocess operations PreprocessOperationsToOptions(ctx context.Context, req *types.ConstructionPreprocessRequest) (options map[string]interface{ }, err error) // AccountIdentifierFromPublicKey returns the account identifier given the public key AccountIdentifierFromPublicKey(pubKey *types.PublicKey) (*types.AccountIdentifier, error) } ``` ### 2. Cosmos SDK Implementation The Cosmos SDK implementation, based on version, takes care of satisfying the `Client` interface. In Stargate, Launchpad and 0.37, we have introduced the concept of rosetta.Msg, this message is not in the shared repository as the sdk.Msg type differs between Cosmos SDK versions. The rosetta.Msg interface follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Msg represents a cosmos-sdk message that can be converted from and to a rosetta operation. type Msg interface { sdk.Msg ToOperations(withStatus, hasError bool) []*types.Operation FromOperations(ops []*types.Operation) (sdk.Msg, error) } ``` Hence developers who want to extend the rosetta set of supported operations just need to extend their module's sdk.Msgs with the `ToOperations` and `FromOperations` methods. ### 3. API service invocation As stated at the start, application developers will have two methods for invocation of the Rosetta API service: 1. Shared process for both application and API 2. Standalone API service #### Shared Process (Only Stargate) Rosetta API service could run within the same execution process as the application. This would be enabled via app.toml settings, and if gRPC is not enabled the rosetta instance would be spinned in offline mode (tx building capabilities only). #### Separate API service Client application developers can write a new command to launch a Rosetta API server as a separate process too, using the rosetta command contained in the `/server/rosetta` package. Construction of the command depends on Cosmos SDK version. Examples can be found inside `simd` for stargate, and `contrib/rosetta/simapp` for other release series. ## Status Proposed ## Consequences ### Positive * Out-of-the-box Rosetta API support within Cosmos SDK. * Blockchain interface standardisation ## References * [Link](https://www.rosetta-api.org/) # ADR 036: Arbitrary Message Signature Specification Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-036-arbitrary-signature 28/10/2020 - Initial draft ## Changelog * 28/10/2020 - Initial draft ## Authors * Antoine Herzog (@antoineherzog) * Zaki Manian (@zmanian) * Aleksandr Bezobchuk (alexanderbez) \[1] * Frojdi Dymylja (@fdymylja) ## Status Draft ## Abstract Currently, in the Cosmos SDK, there is no convention to sign arbitrary message like on Ethereum. We propose with this specification, for Cosmos SDK ecosystem, a way to sign and validate off-chain arbitrary messages. This specification serves the purpose of covering every use case, this means that cosmos-sdk applications developers decide how to serialize and represent `Data` to users. ## Context Having the ability to sign messages off-chain has proven to be a fundamental aspect of nearly any blockchain. The notion of signing messages off-chain has many added benefits such as saving on computational costs and reducing transaction throughput and overhead. Within the context of the Cosmos, some of the major applications of signing such data includes, but is not limited to, providing a cryptographic secure and verifiable means of proving validator identity and possibly associating it with some other framework or organization. In addition, having the ability to sign Cosmos messages with a Ledger or similar HSM device. Further context and use cases can be found in the references links. ## Decision The aim is being able to sign arbitrary messages, even using Ledger or similar HSM devices. As a result signed messages should look roughly like Cosmos SDK messages but **must not** be a valid on-chain transaction. `chain-id`, `account_number` and `sequence` can all be assigned invalid values. Cosmos SDK 0.40 also introduces a concept of “auth\_info” this can specify SIGN\_MODES. A spec should include an `auth_info` that supports SIGN\_MODE\_DIRECT and SIGN\_MODE\_LEGACY\_AMINO. Create the `offchain` proto definitions, we extend the auth module with `offchain` package to offer functionalities to verify and sign offline messages. An offchain transaction follows these rules: * the memo must be empty * nonce, sequence number must be equal to 0 * chain-id must be equal to “” * fee gas must be equal to 0 * fee amount must be an empty array Verification of an offchain transaction follows the same rules as an onchain one, except for the spec differences highlighted above. The first message added to the `offchain` package is `MsgSignData`. `MsgSignData` allows developers to sign arbitrary bytes valid offchain only. Where `Signer` is the account address of the signer. `Data` is arbitrary bytes which can represent `text`, `files`, `object`s. It's applications developers decision how `Data` should be deserialized, serialized and the object it can represent in their context. It's applications developers decision how `Data` should be treated, by treated we mean the serialization and deserialization process and the Object `Data` should represent. Proto definition: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MsgSignData defines an arbitrary, general-purpose, off-chain message message MsgSignData { // Signer is the sdk.AccAddress of the message signer bytes Signer = 1 [(gogoproto.jsontag) = "signer", (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; // Data represents the raw bytes of the content that is signed (text, json, etc) bytes Data = 2 [(gogoproto.jsontag) = "data"]; } ``` Signed MsgSignData json example: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "cosmos-sdk/StdTx", "value": { "msg": [ { "type": "sign/MsgSignData", "value": { "signer": "cosmos1hftz5ugqmpg9243xeegsqqav62f8hnywsjr4xr", "data": "cmFuZG9t" } } ], "fee": { "amount": [], "gas": "0" }, "signatures": [ { "pub_key": { "type": "tendermint/PubKeySecp256k1", "value": "AqnDSiRoFmTPfq97xxEb2VkQ/Hm28cPsqsZm9jEVsYK9" }, "signature": "8y8i34qJakkjse9pOD2De+dnlc4KvFgh0wQpes4eydN66D9kv7cmCEouRrkka9tlW9cAkIL52ErB+6ye7X5aEg==" } ], "memo": "" } } ``` ## Consequences There is a specification on how messages, that are not meant to be broadcast to a live chain, should be formed. ### Backwards Compatibility Backwards compatibility is maintained as this is a new message spec definition. ### Positive * A common format that can be used by multiple applications to sign and verify off-chain messages. * The specification is primitive which means it can cover every use case without limiting what is possible to fit inside it. * It gives room for other off-chain messages specifications that aim to target more specific and common use cases such as off-chain-based authN/authZ layers \[2]. ### Negative * Current proposal requires a fixed relationship between an account address and a public key. * Doesn't work with multisig accounts. ## Further discussion * Regarding security in `MsgSignData`, the developer using `MsgSignData` is in charge of making the content laying in `Data` non-replayable when, and if, needed. * the offchain package will be further extended with extra messages that target specific use cases such as, but not limited to, authentication in applications, payment channels, L2 solutions in general. ## References 1. [Link](https://github.com/cosmos/ics/pull/33) 2. [Link](https://github.com/cosmos/cosmos-sdk/pull/7727#discussion_r515668204) 3. [Link](https://github.com/cosmos/cosmos-sdk/pull/7727#issuecomment-722478477) 4. [Link](https://github.com/cosmos/cosmos-sdk/pull/7727#issuecomment-721062923) # ADR 037: Governance split votes Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-037-gov-split-vote 2020/10/28: Intial draft ## Changelog * 2020/10/28: Intial draft ## Status Accepted ## Abstract This ADR defines a modification to the governance module that would allow a staker to split their votes into several voting options. For example, it could use 70% of its voting power to vote Yes and 30% of its voting power to vote No. ## Context Currently, an address can cast a vote with only one options (Yes/No/Abstain/NoWithVeto) and use their full voting power behind that choice. However, often times the entity owning that address might not be a single individual. For example, a company might have different stakeholders who want to vote differently, and so it makes sense to allow them to split their voting power. Another example use case is exchanges. Many centralized exchanges often stake a portion of their users' tokens in their custody. Currently, it is not possible for them to do "passthrough voting" and giving their users voting rights over their tokens. However, with this system, exchanges can poll their users for voting preferences, and then vote on-chain proportionally to the results of the poll. ## Decision We modify the vote structs to be ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type WeightedVoteOption struct { Option string Weight sdk.Dec } type Vote struct { ProposalID int64 Voter sdk.Address Options []WeightedVoteOption } ``` And for backwards compatibility, we introduce `MsgVoteWeighted` while keeping `MsgVote`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgVote struct { ProposalID int64 Voter sdk.Address Option Option } type MsgVoteWeighted struct { ProposalID int64 Voter sdk.Address Options []WeightedVoteOption } ``` The `ValidateBasic` of a `MsgVoteWeighted` struct would require that 1. The sum of all the Rates is equal to 1.0 2. No Option is repeated The governance tally function will iterate over all the options in a vote and add to the tally the result of the voter's voting power \* the rate for that option. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} tally() { results := map[types.VoteOption]sdk.Dec for _, vote := range votes { for i, weightedOption := range vote.Options { results[weightedOption.Option] += getVotingPower(vote.voter) * weightedOption.Weight } } } ``` The CLI command for creating a multi-option vote would be as such: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov vote 1 "yes=0.6,no=0.3,abstain=0.05,no_with_veto=0.05" --from mykey ``` To create a single-option vote a user can do either ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov vote 1 "yes=1" --from mykey ``` or ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov vote 1 yes --from mykey ``` to maintain backwards compatibility. ## Consequences ### Backwards Compatibility * Previous VoteMsg types will remain the same and so clients will not have to update their procedure unless they want to support the WeightedVoteMsg feature. * When querying a Vote struct from state, its structure will be different, and so clients wanting to display all voters and their respective votes will have to handle the new format and the fact that a single voter can have split votes. * The result of querying the tally function should have the same API for clients. ### Positive * Can make the voting process more accurate for addresses representing multiple stakeholders, often some of the largest addresses. ### Negative * Is more complex than simple voting, and so may be harder to explain to users. However, this is mostly mitigated because the feature is opt-in. ### Neutral * Relatively minor change to governance tally function. # ADR 038: KVStore state listening Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-038-state-listening ## Changelog * 11/23/2020: Initial draft * 10/06/2022: Introduce plugin system based on hashicorp/go-plugin * 10/14/2022: * Add `ListenCommit`, flatten the state writes in a block to a single batch. * Remove listeners from cache stores, should only listen to `rootmulti.Store`. * Remove `HaltAppOnDeliveryError()`, the errors are propagated by default, the implementations should return nil if don't want to propogate errors. * 26/05/2023: Update with ABCI 2.0 ## Status Proposed ## Abstract This ADR defines a set of changes to enable listening to state changes of individual KVStores and exposing these data to consumers. ## Context Currently, KVStore data can be remotely accessed through [Queries](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/docs/building-modules/messages-and-queries.md#queries) which proceed either through Tendermint and the ABCI, or through the gRPC server. In addition to these request/response queries, it would be beneficial to have a means of listening to state changes as they occur in real time. ## Decision We will modify the `CommitMultiStore` interface and its concrete (`rootmulti`) implementations and introduce a new `listenkv.Store` to allow listening to state changes in underlying KVStores. We don't need to listen to cache stores, because we can't be sure that the writes will be committed eventually, and the writes are duplicated in `rootmulti.Store` eventually, so we should only listen to `rootmulti.Store`. We will introduce a plugin system for configuring and running streaming services that write these state changes and their surrounding ABCI message context to different destinations. ### Listening In a new file, `store/types/listening.go`, we will create a `MemoryListener` struct for streaming out protobuf encoded KV pairs state changes from a KVStore. The `MemoryListener` will be used internally by the concrete `rootmulti` implementation to collect state changes from KVStores. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MemoryListener listens to the state writes and accumulate the records in memory. type MemoryListener struct { stateCache []StoreKVPair } // NewMemoryListener creates a listener that accumulate the state writes in memory. func NewMemoryListener() *MemoryListener { return &MemoryListener{ } } // OnWrite writes state change events to the internal cache func (fl *MemoryListener) OnWrite(storeKey StoreKey, key []byte, value []byte, delete bool) { fl.stateCache = append(fl.stateCache, StoreKVPair{ StoreKey: storeKey.Name(), Delete: delete, Key: key, Value: value, }) } // PopStateCache returns the current state caches and set to nil func (fl *MemoryListener) PopStateCache() []StoreKVPair { res := fl.stateCache fl.stateCache = nil return res } ``` We will also define a protobuf type for the KV pairs. In addition to the key and value fields this message will include the StoreKey for the originating KVStore so that we can collect information from separate KVStores and determine the source of each KV pair. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message StoreKVPair { optional string store_key = 1; // the store key for the KVStore this pair originates from required bool set = 2; // true indicates a set operation, false indicates a delete operation required bytes key = 3; required bytes value = 4; } ``` ### ListenKVStore We will create a new `Store` type `listenkv.Store` that the `rootmulti` store will use to wrap a `KVStore` to enable state listening. We will configure the `Store` with a `MemoryListener` which will collect state changes for output to specific destinations. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Store implements the KVStore interface with listening enabled. // Operations are traced on each core KVStore call and written to any of the // underlying listeners with the proper key and operation permissions type Store struct { parent types.KVStore listener *types.MemoryListener parentStoreKey types.StoreKey } // NewStore returns a reference to a new traceKVStore given a parent // KVStore implementation and a buffered writer. func NewStore(parent types.KVStore, psk types.StoreKey, listener *types.MemoryListener) *Store { return &Store{ parent: parent, listener: listener, parentStoreKey: psk } } // Set implements the KVStore interface. It traces a write operation and // delegates the Set call to the parent KVStore. func (s *Store) Set(key []byte, value []byte) { types.AssertValidKey(key) s.parent.Set(key, value) s.listener.OnWrite(s.parentStoreKey, key, value, false) } // Delete implements the KVStore interface. It traces a write operation and // delegates the Delete call to the parent KVStore. func (s *Store) Delete(key []byte) { s.parent.Delete(key) s.listener.OnWrite(s.parentStoreKey, key, nil, true) } ``` ### MultiStore interface updates We will update the `CommitMultiStore` interface to allow us to wrap a `Memorylistener` to a specific `KVStore`. Note that the `MemoryListener` will be attached internally by the concrete `rootmulti` implementation. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type CommitMultiStore interface { ... // AddListeners adds a listener for the KVStore belonging to the provided StoreKey AddListeners(keys []StoreKey) // PopStateCache returns the accumulated state change messages from MemoryListener PopStateCache() []StoreKVPair } ``` ### MultiStore implementation updates We will adjust the `rootmulti` `GetKVStore` method to wrap the returned `KVStore` with a `listenkv.Store` if listening is turned on for that `Store`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore { store := rs.stores[key].(types.KVStore) if rs.TracingEnabled() { store = tracekv.NewStore(store, rs.traceWriter, rs.traceContext) } if rs.ListeningEnabled(key) { store = listenkv.NewStore(store, key, rs.listeners[key]) } return store } ``` We will implement `AddListeners` to manage KVStore listeners internally and implement `PopStateCache` for a means of retrieving the current state. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // AddListeners adds state change listener for a specific KVStore func (rs *Store) AddListeners(keys []types.StoreKey) { listener := types.NewMemoryListener() for i := range keys { rs.listeners[keys[i]] = listener } } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (rs *Store) PopStateCache() []types.StoreKVPair { var cache []types.StoreKVPair for _, ls := range rs.listeners { cache = append(cache, ls.PopStateCache()...) } sort.SliceStable(cache, func(i, j int) bool { return cache[i].StoreKey < cache[j].StoreKey }) return cache } ``` We will also adjust the `rootmulti` `CacheMultiStore` and `CacheMultiStoreWithVersion` methods to enable listening in the cache layer. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (rs *Store) CacheMultiStore() types.CacheMultiStore { stores := make(map[types.StoreKey]types.CacheWrapper) for k, v := range rs.stores { store := v.(types.KVStore) // Wire the listenkv.Store to allow listeners to observe the writes from the cache store, // set same listeners on cache store will observe duplicated writes. if rs.ListeningEnabled(k) { store = listenkv.NewStore(store, k, rs.listeners[k]) } stores[k] = store } return cachemulti.NewStore(rs.db, stores, rs.keysByName, rs.traceWriter, rs.getTracingContext()) } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (rs *Store) CacheMultiStoreWithVersion(version int64) (types.CacheMultiStore, error) { // ... // Wire the listenkv.Store to allow listeners to observe the writes from the cache store, // set same listeners on cache store will observe duplicated writes. if rs.ListeningEnabled(key) { cacheStore = listenkv.NewStore(cacheStore, key, rs.listeners[key]) } cachedStores[key] = cacheStore } return cachemulti.NewStore(rs.db, cachedStores, rs.keysByName, rs.traceWriter, rs.getTracingContext()), nil } ``` ### Exposing the data #### Streaming Service We will introduce a new `ABCIListener` interface that plugs into the BaseApp and relays ABCI requests and responses so that the service can group the state changes with the ABCI requests. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // baseapp/streaming.go // ABCIListener is the interface that we're exposing as a streaming service. type ABCIListener interface { // ListenFinalizeBlock updates the streaming service with the latest FinalizeBlock messages ListenFinalizeBlock(ctx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error // ListenCommit updates the steaming service with the latest Commit messages and state changes ListenCommit(ctx context.Context, res abci.ResponseCommit, changeSet []*StoreKVPair) error } ``` #### BaseApp Registration We will add a new method to the `BaseApp` to enable the registration of `StreamingService`s: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SetStreamingService is used to set a streaming service into the BaseApp hooks and load the listeners into the multistore func (app *BaseApp) SetStreamingService(s ABCIListener) { // register the StreamingService within the BaseApp // BaseApp will pass BeginBlock, DeliverTx, and EndBlock requests and responses to the streaming services to update their ABCI context app.abciListeners = append(app.abciListeners, s) } ``` We will add two new fields to the `BaseApp` struct: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type BaseApp struct { ... // abciListenersAsync for determining if abciListeners will run asynchronously. // When abciListenersAsync=false and stopNodeOnABCIListenerErr=false listeners will run synchronized but will not stop the node. // When abciListenersAsync=true stopNodeOnABCIListenerErr will be ignored. abciListenersAsync bool // stopNodeOnABCIListenerErr halts the node when ABCI streaming service listening results in an error. // stopNodeOnABCIListenerErr=true must be paired with abciListenersAsync=false. stopNodeOnABCIListenerErr bool } ``` #### ABCI Event Hooks We will modify the `FinalizeBlock` and `Commit` methods to pass ABCI requests and responses to any streaming service hooks registered with the `BaseApp`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *BaseApp) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock { var abciRes abci.ResponseFinalizeBlock defer func() { // call the streaming service hook with the FinalizeBlock messages for _, abciListener := range app.abciListeners { ctx := app.finalizeState.ctx blockHeight := ctx.BlockHeight() if app.abciListenersAsync { go func(req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) { if err := app.abciListener.FinalizeBlock(blockHeight, req, res); err != nil { app.logger.Error("FinalizeBlock listening hook failed", "height", blockHeight, "err", err) } }(req, abciRes) } else { if err := app.abciListener.ListenFinalizeBlock(blockHeight, req, res); err != nil { app.logger.Error("FinalizeBlock listening hook failed", "height", blockHeight, "err", err) if app.stopNodeOnABCIListenerErr { os.Exit(1) } } } } }() ... return abciRes } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *BaseApp) Commit() abci.ResponseCommit { ... res := abci.ResponseCommit{ Data: commitID.Hash, RetainHeight: retainHeight, } // call the streaming service hook with the Commit messages for _, abciListener := range app.abciListeners { ctx := app.deliverState.ctx blockHeight := ctx.BlockHeight() changeSet := app.cms.PopStateCache() if app.abciListenersAsync { go func(res abci.ResponseCommit, changeSet []store.StoreKVPair) { if err := app.abciListener.ListenCommit(ctx, res, changeSet); err != nil { app.logger.Error("ListenCommit listening hook failed", "height", blockHeight, "err", err) } }(res, changeSet) } else { if err := app.abciListener.ListenCommit(ctx, res, changeSet); err != nil { app.logger.Error("ListenCommit listening hook failed", "height", blockHeight, "err", err) if app.stopNodeOnABCIListenerErr { os.Exit(1) } } } } ... return res } ``` #### Go Plugin System We propose a plugin architecture to load and run `Streaming` plugins and other types of implementations. We will introduce a plugin system over gRPC that is used to load and run Cosmos-SDK plugins. The plugin system uses [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin). Each plugin must have a struct that implements the `plugin.Plugin` interface and an `Impl` interface for processing messages over gRPC. Each plugin must also have a message protocol defined for the gRPC service: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // streaming/plugins/abci/{ plugin_version }/interface.go // Handshake is a common handshake that is shared by streaming and host. // This prevents users from executing bad plugins or executing a plugin // directory. It is a UX feature, not a security feature. var Handshake = plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "ABCI_LISTENER_PLUGIN", MagicCookieValue: "ef78114d-7bdf-411c-868f-347c99a78345", } // ListenerPlugin is the base struc for all kinds of go-plugin implementations // It will be included in interfaces of different Plugins type ABCIListenerPlugin struct { // GRPCPlugin must still implement the Plugin interface plugin.Plugin // Concrete implementation, written in Go. This is only used for plugins // that are written in Go. Impl baseapp.ABCIListener } func (p *ListenerGRPCPlugin) GRPCServer(_ *plugin.GRPCBroker, s *grpc.Server) error { RegisterABCIListenerServiceServer(s, &GRPCServer{ Impl: p.Impl }) return nil } func (p *ListenerGRPCPlugin) GRPCClient( _ context.Context, _ *plugin.GRPCBroker, c *grpc.ClientConn, ) (interface{ }, error) { return &GRPCClient{ client: NewABCIListenerServiceClient(c) }, nil } ``` The `plugin.Plugin` interface has two methods `Client` and `Server`. For our GRPC service these are `GRPCClient` and `GRPCServer` The `Impl` field holds the concrete implementation of our `baseapp.ABCIListener` interface written in Go. Note: this is only used for plugin implementations written in Go. The advantage of having such a plugin system is that within each plugin authors can define the message protocol in a way that fits their use case. For example, when state change listening is desired, the `ABCIListener` message protocol can be defined as below (*for illustrative purposes only*). When state change listening is not desired than `ListenCommit` can be omitted from the protocol. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} syntax = "proto3"; ... message Empty {} message ListenFinalizeBlockRequest { RequestFinalizeBlock req = 1; ResponseFinalizeBlock res = 2; } message ListenCommitRequest { int64 block_height = 1; ResponseCommit res = 2; repeated StoreKVPair changeSet = 3; } // plugin that listens to state changes service ABCIListenerService { rpc ListenFinalizeBlock(ListenFinalizeBlockRequest) returns (Empty); rpc ListenCommit(ListenCommitRequest) returns (Empty); } ``` ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ... // plugin that doesn't listen to state changes service ABCIListenerService { rpc ListenFinalizeBlock(ListenFinalizeBlockRequest) returns (Empty); rpc ListenCommit(ListenCommitRequest) returns (Empty); } ``` Implementing the service above: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // streaming/plugins/abci/{ plugin_version }/grpc.go var ( _ baseapp.ABCIListener = (*GRPCClient)(nil) ) // GRPCClient is an implementation of the ABCIListener and ABCIListenerPlugin interfaces that talks over RPC. type GRPCClient struct { client ABCIListenerServiceClient } func (m *GRPCClient) ListenFinalizeBlock(goCtx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error { ctx := sdk.UnwrapSDKContext(goCtx) _, err := m.client.ListenDeliverTx(ctx, &ListenDeliverTxRequest{ BlockHeight: ctx.BlockHeight(), Req: req, Res: res }) return err } func (m *GRPCClient) ListenCommit(goCtx context.Context, res abci.ResponseCommit, changeSet []store.StoreKVPair) error { ctx := sdk.UnwrapSDKContext(goCtx) _, err := m.client.ListenCommit(ctx, &ListenCommitRequest{ BlockHeight: ctx.BlockHeight(), Res: res, ChangeSet: changeSet }) return err } // GRPCServer is the gRPC server that GRPCClient talks to. type GRPCServer struct { // This is the real implementation Impl baseapp.ABCIListener } func (m *GRPCServer) ListenFinalizeBlock(ctx context.Context, req *ListenFinalizeBlockRequest) (*Empty, error) { return &Empty{ }, m.Impl.ListenFinalizeBlock(ctx, req.Req, req.Res) } func (m *GRPCServer) ListenCommit(ctx context.Context, req *ListenCommitRequest) (*Empty, error) { return &Empty{ }, m.Impl.ListenCommit(ctx, req.Res, req.ChangeSet) } ``` And the pre-compiled Go plugin `Impl`(*this is only used for plugins that are written in Go*): ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // streaming/plugins/abci/{ plugin_version }/impl/plugin.go // Plugins are pre-compiled and loaded by the plugin system // ABCIListener is the implementation of the baseapp.ABCIListener interface type ABCIListener struct{ } func (m *ABCIListenerPlugin) ListenFinalizeBlock(ctx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error { // send data to external system } func (m *ABCIListenerPlugin) ListenCommit(ctx context.Context, res abci.ResponseCommit, changeSet []store.StoreKVPair) error { // send data to external system } func main() { plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: grpc_abci_v1.Handshake, Plugins: map[string]plugin.Plugin{ "grpc_plugin_v1": &grpc_abci_v1.ABCIListenerGRPCPlugin{ Impl: &ABCIListenerPlugin{ }}, }, // A non-nil value here enables gRPC serving for this streaming... GRPCServer: plugin.DefaultGRPCServer, }) } ``` We will introduce a plugin loading system that will return `(interface{}, error)`. This provides the advantage of using versioned plugins where the plugin interface and gRPC protocol change over time. In addition, it allows for building independent plugin that can expose different parts of the system over gRPC. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewStreamingPlugin(name string, logLevel string) (interface{ }, error) { logger := hclog.New(&hclog.LoggerOptions{ Output: hclog.DefaultOutput, Level: toHclogLevel(logLevel), Name: fmt.Sprintf("plugin.%s", name), }) // We're a host. Start by launching the streaming process. env := os.Getenv(GetPluginEnvKey(name)) client := plugin.NewClient(&plugin.ClientConfig{ HandshakeConfig: HandshakeMap[name], Plugins: PluginMap, Cmd: exec.Command("sh", "-c", env), Logger: logger, AllowedProtocols: []plugin.Protocol{ plugin.ProtocolNetRPC, plugin.ProtocolGRPC }, }) // Connect via RPC rpcClient, err := client.Client() if err != nil { return nil, err } // Request streaming plugin return rpcClient.Dispense(name) } ``` We propose a `RegisterStreamingPlugin` function for the App to register `NewStreamingPlugin`s with the App's BaseApp. Streaming plugins can be of `Any` type; therefore, the function takes in an interface vs a concrete type. For example, we could have plugins of `ABCIListener`, `WasmListener` or `IBCListener`. Note that `RegisterStreamingPluing` function is helper function and not a requirement. Plugin registration can easily be moved from the App to the BaseApp directly. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // baseapp/streaming.go // RegisterStreamingPlugin registers streaming plugins with the App. // This method returns an error if a plugin is not supported. func RegisterStreamingPlugin( bApp *BaseApp, appOpts servertypes.AppOptions, keys map[string]*types.KVStoreKey, streamingPlugin interface{ }, ) error { switch t := streamingPlugin.(type) { case ABCIListener: registerABCIListenerPlugin(bApp, appOpts, keys, t) default: return fmt.Errorf("unexpected plugin type %T", t) } return nil } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func registerABCIListenerPlugin( bApp *BaseApp, appOpts servertypes.AppOptions, keys map[string]*store.KVStoreKey, abciListener ABCIListener, ) { asyncKey := fmt.Sprintf("%s.%s.%s", StreamingTomlKey, StreamingABCITomlKey, StreamingABCIAsync) async := cast.ToBool(appOpts.Get(asyncKey)) stopNodeOnErrKey := fmt.Sprintf("%s.%s.%s", StreamingTomlKey, StreamingABCITomlKey, StreamingABCIStopNodeOnErrTomlKey) stopNodeOnErr := cast.ToBool(appOpts.Get(stopNodeOnErrKey)) keysKey := fmt.Sprintf("%s.%s.%s", StreamingTomlKey, StreamingABCITomlKey, StreamingABCIKeysTomlKey) exposeKeysStr := cast.ToStringSlice(appOpts.Get(keysKey)) exposedKeys := exposeStoreKeysSorted(exposeKeysStr, keys) bApp.cms.AddListeners(exposedKeys) app.SetStreamingManager( storetypes.StreamingManager{ ABCIListeners: []storetypes.ABCIListener{ abciListener }, StopNodeOnErr: stopNodeOnErr, }, ) } ``` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func exposeAll(list []string) bool { for _, ele := range list { if ele == "*" { return true } } return false } func exposeStoreKeys(keysStr []string, keys map[string]*types.KVStoreKey) []types.StoreKey { var exposeStoreKeys []types.StoreKey if exposeAll(keysStr) { exposeStoreKeys = make([]types.StoreKey, 0, len(keys)) for _, storeKey := range keys { exposeStoreKeys = append(exposeStoreKeys, storeKey) } } else { exposeStoreKeys = make([]types.StoreKey, 0, len(keysStr)) for _, keyStr := range keysStr { if storeKey, ok := keys[keyStr]; ok { exposeStoreKeys = append(exposeStoreKeys, storeKey) } } } // sort storeKeys for deterministic output sort.SliceStable(exposeStoreKeys, func(i, j int) bool { return exposeStoreKeys[i].Name() < exposeStoreKeys[j].Name() }) return exposeStoreKeys } ``` The `NewStreamingPlugin` and `RegisterStreamingPlugin` functions are used to register a plugin with the App's BaseApp. e.g. in `NewSimApp`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewSimApp( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { ... keys := sdk.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, ) ... // register streaming services streamingCfg := cast.ToStringMap(appOpts.Get(baseapp.StreamingTomlKey)) for service := range streamingCfg { pluginKey := fmt.Sprintf("%s.%s.%s", baseapp.StreamingTomlKey, service, baseapp.StreamingPluginTomlKey) pluginName := strings.TrimSpace(cast.ToString(appOpts.Get(pluginKey))) if len(pluginName) > 0 { logLevel := cast.ToString(appOpts.Get(flags.FlagLogLevel)) plugin, err := streaming.NewStreamingPlugin(pluginName, logLevel) if err != nil { tmos.Exit(err.Error()) } if err := baseapp.RegisterStreamingPlugin(bApp, appOpts, keys, plugin); err != nil { tmos.Exit(err.Error()) } } } return app ``` #### Configuration The plugin system will be configured within an App's TOML configuration files. ```toml expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # gRPC streaming [streaming] # ABCI streaming service [streaming.abci] # The plugin version to use for ABCI listening plugin = "abci_v1" # List of kv store keys to listen to for state changes. # Set to ["*"] to expose all keys. keys = ["*"] # Enable abciListeners to run asynchronously. # When abciListenersAsync=false and stopNodeOnABCIListenerErr=false listeners will run synchronized but will not stop the node. # When abciListenersAsync=true stopNodeOnABCIListenerErr will be ignored. async = false # Whether to stop the node on message deliver error. stop-node-on-err = true ``` There will be four parameters for configuring `ABCIListener` plugin: `streaming.abci.plugin`, `streaming.abci.keys`, `streaming.abci.async` and `streaming.abci.stop-node-on-err`. `streaming.abci.plugin` is the name of the plugin we want to use for streaming, `streaming.abci.keys` is a set of store keys for stores it listens to, `streaming.abci.async` is bool enabling asynchronous listening and `streaming.abci.stop-node-on-err` is a bool that stops the node when true and when operating on synchronized mode `streaming.abci.async=false`. Note that `streaming.abci.stop-node-on-err=true` will be ignored if `streaming.abci.async=true`. The configuration above support additional streaming plugins by adding the plugin to the `[streaming]` configuration section and registering the plugin with `RegisterStreamingPlugin` helper function. Note the that each plugin must include `streaming.{service}.plugin` property as it is a requirement for doing the lookup and registration of the plugin with the App. All other properties are unique to the individual services. #### Encoding and decoding streams ADR-038 introduces the interfaces and types for streaming state changes out from KVStores, associating this data with their related ABCI requests and responses, and registering a service for consuming this data and streaming it to some destination in a final format. Instead of prescribing a final data format in this ADR, it is left to a specific plugin implementation to define and document this format. We take this approach because flexibility in the final format is necessary to support a wide range of streaming service plugins. For example, the data format for a streaming service that writes the data out to a set of files will differ from the data format that is written to a Kafka topic. ## Consequences These changes will provide a means of subscribing to KVStore state changes in real time. ### Backwards Compatibility * This ADR changes the `CommitMultiStore` interface, implementations supporting the previous version of this interface will not support the new one ### Positive * Ability to listen to KVStore state changes in real time and expose these events to external consumers ### Negative * Changes `CommitMultiStore` interface and its implementations ### Neutral * Introduces additional- but optional- complexity to configuring and running a cosmos application * If an application developer opts to use these features to expose data, they need to be aware of the ramifications/risks of that data exposure as it pertains to the specifics of their application # ADR 039: Epoched Staking Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-039-epoched-staking 10-Feb-2021: Initial Draft ## Changelog * 10-Feb-2021: Initial Draft ## Authors * Dev Ojha (@valardragon) * Sunny Aggarwal (@sunnya97) ## Status Proposed ## Abstract This ADR updates the proof of stake module to buffer the staking weight updates for a number of blocks before updating the consensus' staking weights. The length of the buffer is dubbed an epoch. The prior functionality of the staking module is then a special case of the abstracted module, with the epoch being set to 1 block. ## Context The current proof of stake module takes the design decision to apply staking weight changes to the consensus engine immediately. This means that delegations and unbonds get applied immediately to the validator set. This decision was primarily done as it was implementationally simplest, and because we at the time believed that this would lead to better UX for clients. An alternative design choice is to allow buffering staking updates (delegations, unbonds, validators joining) for a number of blocks. This 'epoch'd proof of stake consensus provides the guarantee that the consensus weights for validators will not change mid-epoch, except in the event of a slash condition. Additionally, the UX hurdle may not be as significant as was previously thought. This is because it is possible to provide users immediate acknowledgement that their bond was recorded and will be executed. Furthermore, it has become clearer over time that immediate execution of staking events comes with limitations, such as: * Threshold based cryptography. One of the main limitations is that because the validator set can change so regularly, it makes the running of multiparty computation by a fixed validator set difficult. Many threshold-based cryptographic features for blockchains such as randomness beacons and threshold decryption require a computationally-expensive DKG process (will take much longer than 1 block to create). To productively use these, we need to guarantee that the result of the DKG will be used for a reasonably long time. It wouldn't be feasible to rerun the DKG every block. By epoching staking, it guarantees we'll only need to run a new DKG once every epoch. * Light client efficiency. This would lessen the overhead for IBC when there is high churn in the validator set. In the Tendermint light client bisection algorithm, the number of headers you need to verify is related to bounding the difference in validator sets between a trusted header and the latest header. If the difference is too great, you verify more header in between the two. By limiting the frequency of validator set changes, we can reduce the worst case size of IBC lite client proofs, which occurs when a validator set has high churn. * Fairness of deterministic leader election. Currently we have no ways of reasoning of fairness of deterministic leader election in the presence of staking changes without epochs (tendermint/spec#217). Breaking fairness of leader election is profitable for validators, as they earn additional rewards from being the proposer. Adding epochs at least makes it easier for our deterministic leader election to match something we can prove secure. (Albeit, we still haven’t proven if our current algorithm is fair with > 2 validators in the presence of stake changes) * Staking derivative design. Currently, reward distribution is done lazily using the F1 fee distribution. While saving computational complexity, lazy accounting requires a more stateful staking implementation. Right now, each delegation entry has to track the time of last withdrawal. Handling this can be a challenge for some staking derivatives designs that seek to provide fungibility for all tokens staked to a single validator. Force-withdrawing rewards to users can help solve this, however it is infeasible to force-withdraw rewards to users on a per block basis. With epochs, a chain could more easily alter the design to have rewards be forcefully withdrawn (iterating over delegator accounts only once per-epoch), and can thus remove delegation timing from state. This may be useful for certain staking derivative designs. ## Design considerations ### Slashing There is a design consideration for whether to apply a slash immediately or at the end of an epoch. A slash event should apply to only members who are actually staked during the time of the infraction, namely during the epoch the slash event occurred. Applying it immediately can be viewed as offering greater consensus layer security, at potential costs to the aforementioned usecases. The benefits of immediate slashing for consensus layer security can be all be obtained by executing the validator jailing immediately (thus removing it from the validator set), and delaying the actual slash change to the validator's weight until the epoch boundary. For the use cases mentioned above, workarounds can be integrated to avoid problems, as follows: * For threshold based cryptography, this setting will have the threshold cryptography use the original epoch weights, while consensus has an update that lets it more rapidly benefit from additional security. If the threshold based cryptography blocks liveness of the chain, then we have effectively raised the liveness threshold of the remaining validators for the rest of the epoch. (Alternatively, jailed nodes could still contribute shares) This plan will fail in the extreme case that more than 1/3rd of the validators have been jailed within a single epoch. For such an extreme scenario, the chain already have its own custom incident response plan, and defining how to handle the threshold cryptography should be a part of that. * For light client efficiency, there can be a bit included in the header indicating an intra-epoch slash (ala [Link](https://github.com/tendermint/spec/issues/199)). * For fairness of deterministic leader election, applying a slash or jailing within an epoch would break the guarantee we were seeking to provide. This then re-introduces a new (but significantly simpler) problem for trying to provide fairness guarantees. Namely, that validators can adversarially elect to remove themself from the set of proposers. From a security perspective, this could potentially be handled by two different mechanisms (or prove to still be too difficult to achieve). One is making a security statement acknowledging the ability for an adversary to force an ahead-of-time fixed threshold of users to drop out of the proposer set within an epoch. The second method would be to parameterize such that the cost of a slash within the epoch far outweights benefits due to being a proposer. However, this latter criterion is quite dubious, since being a proposer can have many advantageous side-effects in chains with complex state machines. (Namely, DeFi games such as Fomo3D) * For staking derivative design, there is no issue introduced. This does not increase the state size of staking records, since whether a slash has occurred is fully queryable given the validator address. ### Token lockup When someone makes a transaction to delegate, even though they are not immediately staked, their tokens should be moved into a pool managed by the staking module which will then be used at the end of an epoch. This prevents concerns where they stake, and then spend those tokens not realizing they were already allocated for staking, and thus having their staking tx fail. ### Pipelining the epochs For threshold based cryptography in particular, we need a pipeline for epoch changes. This is because when we are in epoch N, we want the epoch N+1 weights to be fixed so that the validator set can do the DKG accordingly. So if we are currently in epoch N, the stake weights for epoch N+1 should already be fixed, and new stake changes should be getting applied to epoch N + 2. This can be handled by making a parameter for the epoch pipeline length. This parameter should not be alterable except during hard forks, to mitigate implementation complexity of switching the pipeline length. With pipeline length 1, if I redelegate during epoch N, then my redelegation is applied prior to the beginning of epoch N+1. With pipeline length 2, if I redelegate during epoch N, then my redelegation is applied prior to the beginning of epoch N+2. ### Rewards Even though all staking updates are applied at epoch boundaries, rewards can still be distributed immediately when they are claimed. This is because they do not affect the current stake weights, as we do not implement auto-bonding of rewards. If such a feature were to be implemented, it would have to be setup so that rewards are auto-bonded at the epoch boundary. ### Parameterizing the epoch length When choosing the epoch length, there is a trade-off queued state/computation buildup, and countering the previously discussed limitations of immediate execution if they apply to a given chain. Until an ABCI mechanism for variable block times is introduced, it is ill-advised to be using high epoch lengths due to the computation buildup. This is because when a block's execution time is greater than the expected block time from Tendermint, rounds may increment. ## Decision **Step-1**: Implement buffering of all staking and slashing messages. First we create a pool for storing tokens that are being bonded, but should be applied at the epoch boundary called the `EpochDelegationPool`. Then, we have two separate queues, one for staking, one for slashing. We describe what happens on each message being delivered below: ### Staking messages * **MsgCreateValidator**: Move user's self-bond to `EpochDelegationPool` immediately. Queue a message for the epoch boundary to handle the self-bond, taking the funds from the `EpochDelegationPool`. If Epoch execution fail, return back funds from `EpochDelegationPool` to user's account. * **MsgEditValidator**: Validate message and if valid queue the message for execution at the end of the Epoch. * **MsgDelegate**: Move user's funds to `EpochDelegationPool` immediately. Queue a message for the epoch boundary to handle the delegation, taking the funds from the `EpochDelegationPool`. If Epoch execution fail, return back funds from `EpochDelegationPool` to user's account. * **MsgBeginRedelegate**: Validate message and if valid queue the message for execution at the end of the Epoch. * **MsgUndelegate**: Validate message and if valid queue the message for execution at the end of the Epoch. ### Slashing messages * **MsgUnjail**: Validate message and if valid queue the message for execution at the end of the Epoch. * **Slash Event**: Whenever a slash event is created, it gets queued in the slashing module to apply at the end of the epoch. The queues should be setup such that this slash applies immediately. ### Evidence Messages * **MsgSubmitEvidence**: This gets executed immediately, and the validator gets jailed immediately. However in slashing, the actual slash event gets queued. Then we add methods to the end blockers, to ensure that at the epoch boundary the queues are cleared and delegation updates are applied. **Step-2**: Implement querying of queued staking txs. When querying the staking activity of a given address, the status should return not only the amount of tokens staked, but also if there are any queued stake events for that address. This will require more work to be done in the querying logic, to trace the queued upcoming staking events. As an initial implementation, this can be implemented as a linear search over all queued staking events. However, for chains that need long epochs, they should eventually build additional support for nodes that support querying to be able to produce results in constant time. (This is do-able by maintaining an auxilliary hashmap for indexing upcoming staking events by address) **Step-3**: Adjust gas Currently gas represents the cost of executing a transaction when its done immediately. (Merging together costs of p2p overhead, state access overhead, and computational overhead) However, now a transaction can cause computation in a future block, namely at the epoch boundary. To handle this, we should initially include parameters for estimating the amount of future computation (denominated in gas), and add that as a flat charge needed for the message. We leave it as out of scope for how to weight future computation versus current computation in gas pricing, and have it set such that the are weighted equally for now. ## Consequences ### Positive * Abstracts the proof of stake module that allows retaining the existing functionality * Enables new features such as validator-set based threshold cryptography ### Negative * Increases complexity of integrating more complex gas pricing mechanisms, as they now have to consider future execution costs as well. * When epoch > 1, validators can no longer leave the network immediately, and must wait until an epoch boundary. # ADR 040: Storage and SMT State Commitments Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-040-storage-and-smt-state-commitments 2020-01-15: Draft ## Changelog * 2020-01-15: Draft ## Status DRAFT Not Implemented ## Abstract Sparse Merkle Tree ([SMT](https://osf.io/8mcnh/)) is a version of a Merkle Tree with various storage and performance optimizations. This ADR defines a separation of state commitments from data storage and the Cosmos SDK transition from IAVL to SMT. ## Context Currently, Cosmos SDK uses IAVL for both state [commitments](https://cryptography.fandom.com/wiki/Commitment_scheme) and data storage. IAVL has effectively become an orphaned project within the Cosmos ecosystem and it's proven to be an inefficient state commitment data structure. In the current design, IAVL is used for both data storage and as a Merkle Tree for state commitments. IAVL is meant to be a standalone Merkelized key/value database, however it's using a KV DB engine to store all tree nodes. So, each node is stored in a separate record in the KV DB. This causes many inefficiencies and problems: * Each object query requires a tree traversal from the root. Subsequent queries for the same object are cached on the Cosmos SDK level. * Each edge traversal requires a DB query. * Creating snapshots is [expensive](https://github.com/cosmos/cosmos-sdk/issues/7215#issuecomment-684804950). It takes about 30 seconds to export less than 100 MB of state (as of March 2020). * Updates in IAVL may trigger tree reorganization and possible O(log(n)) hashes re-computation, which can become a CPU bottleneck. * The node structure is pretty expensive - it contains a standard tree node elements (key, value, left and right element) and additional metadata such as height, version (which is not required by the Cosmos SDK). The entire node is hashed, and that hash is used as the key in the underlying database, [ref](https://github.com/cosmos/iavl/blob/master/docs/node/node.md). Moreover, the IAVL project lacks support and a maintainer and we already see better and well-established alternatives. Instead of optimizing the IAVL, we are looking into other solutions for both storage and state commitments. ## Decision We propose to separate the concerns of state commitment (**SC**), needed for consensus, and state storage (**SS**), needed for state machine. Finally we replace IAVL with [Celestia's SMT](https://github.com/lazyledger/smt). Celestia SMT is based on Diem (called jellyfish) design \[\*] - it uses a compute-optimized SMT by replacing subtrees with only default values with a single node (same approach is used by Ethereum2) and implements compact proofs. The storage model presented here doesn't deal with data structure nor serialization. It's a Key-Value database, where both key and value are binaries. The storage user is responsible for data serialization. ### Decouple state commitment from storage Separation of storage and commitment (by the SMT) will allow the optimization of different components according to their usage and access patterns. `SC` (SMT) is used to commit to a data and compute Merkle proofs. `SS` is used to directly access data. To avoid collisions, both `SS` and `SC` will use a separate storage namespace (they could use the same database underneath). `SS` will store each record directly (mapping `(key, value)` as `key → value`). SMT is a merkle tree structure: we don't store keys directly. For every `(key, value)` pair, `hash(key)` is used as leaf path (we hash a key to uniformly distribute leaves in the tree) and `hash(value)` as the leaf contents. The tree structure is specified in more depth [below](#smt-for-state-commitment). For data access we propose 2 additional KV buckets (implemented as namespaces for the key-value pairs, sometimes called [column family](https://github.com/facebook/rocksdb/wiki/Terminology)): 1. B1: `key → value`: the principal object storage, used by a state machine, behind the Cosmos SDK `KVStore` interface: provides direct access by key and allows prefix iteration (KV DB backend must support it). 2. B2: `hash(key) → key`: a reverse index to get a key from an SMT path. Internally the SMT will store `(key, value)` as `prefix || hash(key) || hash(value)`. So, we can get an object value by composing `hash(key) → B2 → B1`. 3. We could use more buckets to optimize the app usage if needed. We propose to use a KV database for both `SS` and `SC`. The store interface will allow to use the same physical DB backend for both `SS` and `SC` as well two separate DBs. The latter option allows for the separation of `SS` and `SC` into different hardware units, providing support for more complex setup scenarios and improving overall performance: one can use different backends (eg RocksDB and Badger) as well as independently tuning the underlying DB configuration. ### Requirements State Storage requirements: * range queries * quick (key, value) access * creating a snapshot * historical versioning * pruning (garbage collection) State Commitment requirements: * fast updates * tree path should be short * query historical commitment proofs using ICS-23 standard * pruning (garbage collection) ### SMT for State Commitment A Sparse Merkle tree is based on the idea of a complete Merkle tree of an intractable size. The assumption here is that as the size of the tree is intractable, there would only be a few leaf nodes with valid data blocks relative to the tree size, rendering a sparse tree. The full specification can be found at [Celestia](https://github.com/celestiaorg/celestia-specs/blob/ec98170398dfc6394423ee79b00b71038879e211/src/specs/data_structures.md#sparse-merkle-tree). In summary: * The SMT consists of a binary Merkle tree, constructed in the same fashion as described in [Certificate Transparency (RFC-6962)](https://tools.ietf.org/html/rfc6962), but using as the hashing function SHA-2-256 as defined in [FIPS 180-4](https://doi.org/10.6028/NIST.FIPS.180-4). * Leaves and internal nodes are hashed differently: the one-byte `0x00` is prepended for leaf nodes while `0x01` is prepended for internal nodes. * Default values are given to leaf nodes with empty leaves. * While the above rule is sufficient to pre-compute the values of intermediate nodes that are roots of empty subtrees, a further simplification is to extend this default value to all nodes that are roots of empty subtrees. The 32-byte zero is used as the default value. This rule takes precedence over the above one. * An internal node that is the root of a subtree that contains exactly one non-empty leaf is replaced by that leaf's leaf node. ### Snapshots for storage sync and state versioning Below, with simple *snapshot* we refer to a database snapshot mechanism, not to a *ABCI snapshot sync*. The latter will be referred as *snapshot sync* (which will directly use DB snapshot as described below). Database snapshot is a view of DB state at a certain time or transaction. It's not a full copy of a database (it would be too big). Usually a snapshot mechanism is based on a *copy on write* and it allows DB state to be efficiently delivered at a certain stage. Some DB engines support snapshotting. Hence, we propose to reuse that functionality for the state sync and versioning (described below). We limit the supported DB engines to ones which efficiently implement snapshots. In a final section we discuss the evaluated DBs. One of the Stargate core features is a *snapshot sync* delivered in the `/snapshot` package. It provides a way to trustlessly sync a blockchain without repeating all transactions from the genesis. This feature is implemented in Cosmos SDK and requires storage support. Currently IAVL is the only supported backend. It works by streaming to a client a snapshot of a `SS` at a certain version together with a header chain. A new database snapshot will be created in every `EndBlocker` and identified by a block height. The `root` store keeps track of the available snapshots to offer `SS` at a certain version. The `root` store implements the `RootStore` interface described below. In essence, `RootStore` encapsulates a `Committer` interface. `Committer` has a `Commit`, `SetPruning`, `GetPruning` functions which will be used for creating and removing snapshots. The `rootStore.Commit` function creates a new snapshot and increments the version on each call, and checks if it needs to remove old versions. We will need to update the SMT interface to implement the `Committer` interface. NOTE: `Commit` must be called exactly once per block. Otherwise we risk going out of sync for the version number and block height. NOTE: For the Cosmos SDK storage, we may consider splitting that interface into `Committer` and `PruningCommitter` - only the multiroot should implement `PruningCommitter` (cache and prefix store don't need pruning). Number of historical versions for `abci.RequestQuery` and state sync snapshots is part of a node configuration, not a chain configuration (configuration implied by the blockchain consensus). A configuration should allow to specify number of past blocks and number of past blocks modulo some number (eg: 100 past blocks and one snapshot every 100 blocks for past 2000 blocks). Archival nodes can keep all past versions. Pruning old snapshots is effectively done by a database. Whenever we update a record in `SC`, SMT won't update nodes - instead it creates new nodes on the update path, without removing the old one. Since we are snapshotting each block, we need to change that mechanism to immediately remove orphaned nodes from the database. This is a safe operation - snapshots will keep track of the records and make it available when accessing past versions. To manage the active snapshots we will either use a DB *max number of snapshots* option (if available), or we will remove DB snapshots in the `EndBlocker`. The latter option can be done efficiently by identifying snapshots with block height and calling a store function to remove past versions. #### Accessing old state versions One of the functional requirements is to access old state. This is done through `abci.RequestQuery` structure. The version is specified by a block height (so we query for an object by a key `K` at block height `H`). The number of old versions supported for `abci.RequestQuery` is configurable. Accessing an old state is done by using available snapshots. `abci.RequestQuery` doesn't need old state of `SC` unless the `prove=true` parameter is set. The SMT merkle proof must be included in the `abci.ResponseQuery` only if both `SC` and `SS` have a snapshot for requested version. Moreover, Cosmos SDK could provide a way to directly access a historical state. However, a state machine shouldn't do that - since the number of snapshots is configurable, it would lead to nondeterministic execution. We positively [validated](https://github.com/cosmos/cosmos-sdk/discussions/8297) a versioning and snapshot mechanism for querying old state with regards to the database we evaluated. ### State Proofs For any object stored in State Store (SS), we have corresponding object in `SC`. A proof for object `V` identified by a key `K` is a branch of `SC`, where the path corresponds to the key `hash(K)`, and the leaf is `hash(K, V)`. ### Rollbacks We need to be able to process transactions and roll-back state updates if a transaction fails. This can be done in the following way: during transaction processing, we keep all state change requests (writes) in a `CacheWrapper` abstraction (as it's done today). Once we finish the block processing, in the `Endblocker`, we commit a root store - at that time, all changes are written to the SMT and to the `SS` and a snapshot is created. ### Committing to an object without saving it We identified use-cases, where modules will need to save an object commitment without storing an object itself. Sometimes clients are receiving complex objects, and they have no way to prove a correctness of that object without knowing the storage layout. For those use cases it would be easier to commit to the object without storing it directly. ### Refactor MultiStore The Stargate `/store` implementation (store/v1) adds an additional layer in the SDK store construction - the `MultiStore` structure. The multistore exists to support the modularity of the Cosmos SDK - each module is using its own instance of IAVL, but in the current implementation, all instances share the same database. The latter indicates, however, that the implementation doesn't provide true modularity. Instead it causes problems related to race condition and atomic DB commits (see: [#6370](https://github.com/cosmos/cosmos-sdk/issues/6370) and [discussion](https://github.com/cosmos/cosmos-sdk/discussions/8297#discussioncomment-757043)). We propose to reduce the multistore concept from the SDK, and to use a single instance of `SC` and `SS` in a `RootStore` object. To avoid confusion, we should rename the `MultiStore` interface to `RootStore`. The `RootStore` will have the following interface; the methods for configuring tracing and listeners are omitted for brevity. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Used where read-only access to versions is needed. type BasicRootStore interface { Store GetKVStore(StoreKey) KVStore CacheRootStore() CacheRootStore } // Used as the main app state, replacing CommitMultiStore. type CommitRootStore interface { BasicRootStore Committer Snapshotter GetVersion(uint64) (BasicRootStore, error) SetInitialVersion(uint64) error ... // Trace and Listen methods } // Replaces CacheMultiStore for branched state. type CacheRootStore interface { BasicRootStore Write() ... // Trace and Listen methods } // Example of constructor parameters for the concrete type. type RootStoreConfig struct { Upgrades *StoreUpgrades InitialVersion uint64 ReservePrefix(StoreKey, StoreType) } ``` In contrast to `MultiStore`, `RootStore` doesn't allow to dynamically mount sub-stores or provide an arbitrary backing DB for individual sub-stores. NOTE: modules will be able to use a special commitment and their own DBs. For example: a module which will use ZK proofs for state can store and commit this proof in the `RootStore` (usually as a single record) and manage the specialized store privately or using the `SC` low level interface. #### Compatibility support To ease the transition to this new interface for users, we can create a shim which wraps a `CommitMultiStore` but provides a `CommitRootStore` interface, and expose functions to safely create and access the underlying `CommitMultiStore`. The new `RootStore` and supporting types can be implemented in a `store/v2alpha1` package to avoid breaking existing code. #### Merkle Proofs and IBC Currently, an IBC (v1.0) Merkle proof path consists of two elements (`["", ""]`), with each key corresponding to a separate proof. These are each verified according to individual [ICS-23 specs](https://github.com/cosmos/ibc-go/blob/f7051429e1cf833a6f65d51e6c3df1609290a549/modules/core/23-commitment/types/merkle.go#L17), and the result hash of each step is used as the committed value of the next step, until a root commitment hash is obtained. The root hash of the proof for `""` is hashed with the `""` to validate against the App Hash. This is not compatible with the `RootStore`, which stores all records in a single Merkle tree structure, and won't produce separate proofs for the store- and record-key. Ideally, the store-key component of the proof could just be omitted, and updated to use a "no-op" spec, so only the record-key is used. However, because the IBC verification code hardcodes the `"ibc"` prefix and applies it to the SDK proof as a separate element of the proof path, this isn't possible without a breaking change. Breaking this behavior would severely impact the Cosmos ecosystem which already widely adopts the IBC module. Requesting an update of the IBC module across the chains is a time consuming effort and not easily feasible. As a workaround, the `RootStore` will have to use two separate SMTs (they could use the same underlying DB): one for IBC state and one for everything else. A simple Merkle map that reference these SMTs will act as a Merkle Tree to create a final App hash. The Merkle map is not stored in a DBs - it's constructed in the runtime. The IBC substore key must be `"ibc"`. The workaround can still guarantee atomic syncs: the [proposed DB backends](#evaluated-kv-databases) support atomic transactions and efficient rollbacks, which will be used in the commit phase. The presented workaround can be used until the IBC module is fully upgraded to supports single-element commitment proofs. ### Optimization: compress module key prefixes We consider a compression of prefix keys by creating a mapping from module key to an integer, and serializing the integer using varint coding. Varint coding assures that different values don't have common byte prefix. For Merkle Proofs we can't use prefix compression - so it should only apply for the `SS` keys. Moreover, the prefix compression should be only applied for the module namespace. More precisely: * each module has its own namespace; * when accessing a module namespace we create a KVStore with embedded prefix; * that prefix will be compressed only when accessing and managing `SS`. We need to assure that the codes won't change. We can fix the mapping in a static variable (provided by an app) or SS state under a special key. TODO: need to make decision about the key compression. ## Optimization: SS key compression Some objects may be saved with key, which contains a Protobuf message type. Such keys are long. We could save a lot of space if we can map Protobuf message types in varints. TODO: finalize this or move to another ADR. ## Migration Using the new store will require a migration. 2 Migrations are proposed: 1. Genesis export -- it will reset the blockchain history. 2. In place migration: we can reuse `UpgradeKeeper.SetUpgradeHandler` to provide the migration logic: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("adr-40", func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { storev2.Migrate(iavlstore, v2.store) // RunMigrations returns the VersionMap // with the updated module ConsensusVersions return app.mm.RunMigrations(ctx, vm) }) ``` The `Migrate` function will read all entries from a store/v1 DB and save them to the AD-40 combined KV store. Cache layer should not be used and the operation must finish with a single Commit call. Inserting records to the `SC` (SMT) component is the bottleneck. Unfortunately SMT doesn't support batch transactions. Adding batch transactions to `SC` layer is considered as a feature after the main release. ## Consequences ### Backwards Compatibility This ADR doesn't introduce any Cosmos SDK level API changes. We change the storage layout of the state machine, a storage hard fork and network upgrade is required to incorporate these changes. SMT provides a merkle proof functionality, however it is not compatible with ICS23. Updating the proofs for ICS23 compatibility is required. ### Positive * Decoupling state from state commitment introduce better engineering opportunities for further optimizations and better storage patterns. * Performance improvements. * Joining SMT based camp which has wider and proven adoption than IAVL. Example projects which decided on SMT: Ethereum2, Diem (Libra), Trillan, Tezos, Celestia. * Multistore removal fixes a longstanding issue with the current MultiStore design. * Simplifies merkle proofs - all modules, except IBC, have only one pass for merkle proof. ### Negative * Storage migration * LL SMT doesn't support pruning - we will need to add and test that functionality. * `SS` keys will have an overhead of a key prefix. This doesn't impact `SC` because all keys in `SC` have same size (they are hashed). ### Neutral * Deprecating IAVL, which is one of the core proposals of Cosmos Whitepaper. ## Alternative designs Most of the alternative designs were evaluated in a prior state commitments and storage report. Ethereum research published [Verkle Trie](https://dankradfeist.de/ethereum/2021/06/18/verkle-trie-for-eth1.html) - an idea of combining polynomial commitments with merkle tree in order to reduce the tree height. This concept has a very good potential, but we think it's too early to implement it. The current, SMT based design could be easily updated to the Verkle Trie once other research implement all necessary libraries. The main advantage of the design described in this ADR is the separation of state commitments from the data storage and designing a more powerful interface. ## Further Discussions ### Evaluated KV Databases We verified existing databases KV databases for evaluating snapshot support. The following databases provide efficient snapshot mechanism: Badger, RocksDB, [Pebble](https://github.com/cockroachdb/pebble). Databases which don't provide such support or are not production ready: boltdb, leveldb, goleveldb, membdb, lmdb. ### RDBMS Use of RDBMS instead of simple KV store for state. Use of RDBMS will require a Cosmos SDK API breaking change (`KVStore` interface) and will allow better data extraction and indexing solutions. Instead of saving an object as a single blob of bytes, we could save it as record in a table in the state storage layer, and as a `hash(key, protobuf(object))` in the SMT as outlined above. To verify that an object registered in RDBMS is same as the one committed to SMT, one will need to load it from RDBMS, marshal using protobuf, hash and do SMT search. ### Off Chain Store We were discussing use case where modules can use a support database, which is not automatically committed. Module will responsible for having a sound storage model and can optionally use the feature discussed in \_*Committing to an object without saving it* section. ## References * [IAVL What's Next?](https://github.com/cosmos/cosmos-sdk/issues/7100) * [IAVL overview](https://docs.google.com/document/d/16Z_hW2rSAmoyMENO-RlAhQjAG3mSNKsQueMnKpmcBv0/edit#heading=h.yd2th7x3o1iv) of it's state v0.15 * [Celestia (LazyLedger) SMT](https://github.com/lazyledger/smt) * Facebook Diem (Libra) SMT [design](https://developers.diem.com/papers/jellyfish-merkle-tree/2021-01-14.pdf) * [Trillian Revocation Transparency](https://github.com/google/trillian/blob/master/docs/papers/RevocationTransparency.pdf), [Trillian Verifiable Data Structures](https://github.com/google/trillian/blob/master/docs/papers/VerifiableDataStructures.pdf). * Design and implementation [discussion](https://github.com/cosmos/cosmos-sdk/discussions/8297). * [How to Upgrade IBC Chains and their Clients](https://github.com/cosmos/ibc-go/blob/main/docs/docs/01-ibc/05-upgrades/01-quick-guide.md) * [ADR-40 Effect on IBC](https://github.com/cosmos/ibc-go/discussions/256) # ADR 041: In-Place Store Migrations Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-041-in-place-store-migrations 17.02.2021: Initial Draft ## Changelog * 17.02.2021: Initial Draft ## Status Accepted ## Abstract This ADR introduces a mechanism to perform in-place state store migrations during chain software upgrades. ## Context When a chain upgrade introduces state-breaking changes inside modules, the current procedure consists of exporting the whole state into a JSON file (via the `simd export` command), running migration scripts on the JSON file (`simd genesis migrate` command), clearing the stores (`simd unsafe-reset-all` command), and starting a new chain with the migrated JSON file as new genesis (optionally with a custom initial block height). An example of such a procedure can be seen [in the Cosmos Hub 3->4 migration guide](https://github.com/cosmos/gaia/blob/v4.0.3/docs/migration/cosmoshub-3.md#upgrade-procedure). This procedure is cumbersome for multiple reasons: * The procedure takes time. It can take hours to run the `export` command, plus some additional hours to run `InitChain` on the fresh chain using the migrated JSON. * The exported JSON file can be heavy (\~100MB-1GB), making it difficult to view, edit and transfer, which in turn introduces additional work to solve these problems (such as [streaming genesis](https://github.com/cosmos/cosmos-sdk/issues/6936)). ## Decision We propose a migration procedure based on modifying the KV store in-place without involving the JSON export-process-import flow described above. ### Module `ConsensusVersion` We introduce a new method on the `AppModule` interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type AppModule interface { // --snip-- ConsensusVersion() uint64 } ``` This methods returns an `uint64` which serves as state-breaking version of the module. It MUST be incremented on each consensus-breaking change introduced by the module. To avoid potential errors with default values, the initial version of a module MUST be set to 1. In the Cosmos SDK, version 1 corresponds to the modules in the v0.41 series. ### Module-Specific Migration Functions For each consensus-breaking change introduced by the module, a migration script from ConsensusVersion `N` to version `N+1` MUST be registered in the `Configurator` using its newly-added `RegisterMigration` method. All modules receive a reference to the configurator in their `RegisterServices` method on `AppModule`, and this is where the migration functions should be registered. The migration functions should be registered in increasing order. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (am AppModule) RegisterServices(cfg module.Configurator) { // --snip-- cfg.RegisterMigration(types.ModuleName, 1, func(ctx sdk.Context) error { // Perform in-place store migrations from ConsensusVersion 1 to 2. }) cfg.RegisterMigration(types.ModuleName, 2, func(ctx sdk.Context) error { // Perform in-place store migrations from ConsensusVersion 2 to 3. }) // etc. } ``` For example, if the new ConsensusVersion of a module is `N` , then `N-1` migration functions MUST be registered in the configurator. In the Cosmos SDK, the migration functions are handled by each module's keeper, because the keeper holds the `sdk.StoreKey` used to perform in-place store migrations. To not overload the keeper, a `Migrator` wrapper is used by each module to handle the migration functions: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Migrator is a struct for handling in-place store migrations. type Migrator struct { BaseKeeper } ``` Migration functions should live inside the `migrations/` folder of each module, and be called by the Migrator's methods. We propose the format `Migrate{M}to{N}` for method names. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Migrate1to2 migrates from version 1 to 2. func (m Migrator) Migrate1to2(ctx sdk.Context) error { return v2bank.MigrateStore(ctx, m.keeper.storeKey) // v043bank is package `x/bank/migrations/v2`. } ``` Each module's migration functions are specific to the module's store evolutions, and are not described in this ADR. An example of x/bank store key migrations after the introduction of ADR-028 length-prefixed addresses can be seen in this [store.go code](https://github.com/cosmos/cosmos-sdk/blob/36f68eb9e041e20a5bb47e216ac5eb8b91f95471/x/bank/legacy/v043/store.go#L41-L62). ### Tracking Module Versions in `x/upgrade` We introduce a new prefix store in `x/upgrade`'s store. This store will track each module's current version, it can be modelized as a `map[string]uint64` of module name to module ConsensusVersion, and will be used when running the migrations (see next section for details). The key prefix used is `0x1`, and the key/value format is: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} 0x2 | {bytes(module_name)} => BigEndian(module_consensus_version) ``` The initial state of the store is set from `app.go`'s `InitChainer` method. The UpgradeHandler signature needs to be updated to take a `VersionMap`, as well as return an upgraded `VersionMap` and an error: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - type UpgradeHandler func(ctx sdk.Context, plan Plan) + type UpgradeHandler func(ctx sdk.Context, plan Plan, versionMap VersionMap) (VersionMap, error) ``` To apply an upgrade, we query the `VersionMap` from the `x/upgrade` store and pass it into the handler. The handler runs the actual migration functions (see next section), and if successful, returns an updated `VersionMap` to be stored in state. ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k UpgradeKeeper) ApplyUpgrade(ctx sdk.Context, plan types.Plan) { // --snip-- - handler(ctx, plan) + updatedVM, err := handler(ctx, plan, k.GetModuleVersionMap(ctx)) // k.GetModuleVersionMap() fetches the VersionMap stored in state. + if err != nil { + return err + } + + // Set the updated consensus versions to state + k.SetModuleVersionMap(ctx, updatedVM) } ``` A gRPC query endpoint to query the `VersionMap` stored in `x/upgrade`'s state will also be added, so that app developers can double-check the `VersionMap` before the upgrade handler runs. ### Running Migrations Once all the migration handlers are registered inside the configurator (which happens at startup), running migrations can happen by calling the `RunMigrations` method on `module.Manager`. This function will loop through all modules, and for each module: * Get the old ConsensusVersion of the module from its `VersionMap` argument (let's call it `M`). * Fetch the new ConsensusVersion of the module from the `ConsensusVersion()` method on `AppModule` (call it `N`). * If `N>M`, run all registered migrations for the module sequentially `M -> M+1 -> M+2...` until `N`. * There is a special case where there is no ConsensusVersion for the module, as this means that the module has been newly added during the upgrade. In this case, no migration function is run, and the module's current ConsensusVersion is saved to `x/upgrade`'s store. If a required migration is missing (e.g. if it has not been registered in the `Configurator`), then the `RunMigrations` function will error. In practice, the `RunMigrations` method should be called from inside an `UpgradeHandler`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("my-plan", func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { return app.mm.RunMigrations(ctx, vm) }) ``` Assuming a chain upgrades at block `n`, the procedure should run as follows: * the old binary will halt in `BeginBlock` when starting block `N`. In its store, the ConsensusVersions of the old binary's modules are stored. * the new binary will start at block `N`. The UpgradeHandler is set in the new binary, so will run at `BeginBlock` of the new binary. Inside `x/upgrade`'s `ApplyUpgrade`, the `VersionMap` will be retrieved from the (old binary's) store, and passed into the `RunMigrations` functon, migrating all module stores in-place before the modules' own `BeginBlock`s. ## Consequences ### Backwards Compatibility This ADR introduces a new method `ConsensusVersion()` on `AppModule`, which all modules need to implement. It also alters the UpgradeHandler function signature. As such, it is not backwards-compatible. While modules MUST register their migration functions when bumping ConsensusVersions, running those scripts using an upgrade handler is optional. An application may perfectly well decide to not call the `RunMigrations` inside its upgrade handler, and continue using the legacy JSON migration path. ### Positive * Perform chain upgrades without manipulating JSON files. * While no benchmark has been made yet, it is probable that in-place store migrations will take less time than JSON migrations. The main reason supporting this claim is that both the `simd export` command on the old binary and the `InitChain` function on the new binary will be skipped. ### Negative * Module developers MUST correctly track consensus-breaking changes in their modules. If a consensus-breaking change is introduced in a module without its corresponding `ConsensusVersion()` bump, then the `RunMigrations` function won't detect the migration, and the chain upgrade might be unsuccessful. Documentation should clearly reflect this. ### Neutral * The Cosmos SDK will continue to support JSON migrations via the existing `simd export` and `simd genesis migrate` commands. * The current ADR does not allow creating, renaming or deleting stores, only modifying existing store keys and values. The Cosmos SDK already has the `StoreLoader` for those operations. ## Further Discussions ## References * Initial discussion: [Link](https://github.com/cosmos/cosmos-sdk/discussions/8429) * Implementation of `ConsensusVersion` and `RunMigrations`: [Link](https://github.com/cosmos/cosmos-sdk/pull/8485) * Issue discussing `x/upgrade` design: [Link](https://github.com/cosmos/cosmos-sdk/issues/8514) # ADR 042: Group Module Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-042-group-module 2020/04/09: Initial Draft ## Changelog * 2020/04/09: Initial Draft ## Status Draft ## Abstract This ADR defines the `x/group` module which allows the creation and management of on-chain multi-signature accounts and enables voting for message execution based on configurable decision policies. ## Context The legacy amino multi-signature mechanism of the Cosmos SDK has certain limitations: * Key rotation is not possible, although this can be solved with [account rekeying](/sdk/latest/reference/architecture/adr-034-account-rekeying). * Thresholds can't be changed. * UX is cumbersome for non-technical users ([#5661](https://github.com/cosmos/cosmos-sdk/issues/5661)). * It requires `legacy_amino` sign mode ([#8141](https://github.com/cosmos/cosmos-sdk/issues/8141)). While the group module is not meant to be a total replacement for the current multi-signature accounts, it provides a solution to the limitations described above, with a more flexible key management system where keys can be added, updated or removed, as well as configurable thresholds. It's meant to be used with other access control modules such as [`x/feegrant`](/sdk/v0.50/build/architecture/adr-029-fee-grant-module) ans [`x/authz`](/sdk/latest/reference/architecture/adr-030-authz-module) to simplify key management for individuals and organizations. The proof of concept of the group module can be found in `https://github.com/regen-network/regen-ledger/tree/master/proto/regen/group/v1alpha1` and `https://github.com/regen-network/regen-ledger/tree/master/x/group`. ## Decision We propose merging the `x/group` module with its supporting ORM/Table Store package (`https://github.com/regen-network/regen-ledger/tree/master/orm`) ([#7098](https://github.com/cosmos/cosmos-sdk/issues/7098)) into the Cosmos SDK and continuing development here. There will be a dedicated ADR for the ORM package. ### Group A group is a composition of accounts with associated weights. It is not an account and doesn't have a balance. It doesn't in and of itself have any sort of voting or decision weight. Group members can create proposals and vote on them through group accounts using different decision policies. It has an `admin` account which can manage members in the group, update the group metadata and set a new admin. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GroupInfo { // group_id is the unique ID of this group. uint64 group_id = 1; // admin is the account address of the group's admin. string admin = 2; // metadata is any arbitrary metadata to attached to the group. bytes metadata = 3; // version is used to track changes to a group's membership structure that // would break existing proposals. Whenever a member weight has changed, // or any member is added or removed, the version is incremented and will // invalidate all proposals from older versions. uint64 version = 4; // total_weight is the sum of the group members' weights. string total_weight = 5; } ``` ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GroupMember { // group_id is the unique ID of the group. uint64 group_id = 1; // member is the member data. Member member = 2; } // Member represents a group member with an account address, // non-zero weight and metadata. message Member { // address is the member's account address. string address = 1; // weight is the member's voting weight that should be greater than 0. string weight = 2; // metadata is any arbitrary metadata to attached to the member. bytes metadata = 3; } ``` ### Group Account A group account is an account associated with a group and a decision policy. A group account does have a balance. Group accounts are abstracted from groups because a single group may have multiple decision policies for different types of actions. Managing group membership separately from decision policies results in the least overhead and keeps membership consistent across different policies. The pattern that is recommended is to have a single master group account for a given group, and then to create separate group accounts with different decision policies and delegate the desired permissions from the master account to those "sub-accounts" using the [`x/authz` module](/sdk/latest/reference/architecture/adr-030-authz-module). ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GroupAccountInfo { // address is the group account address. string address = 1; // group_id is the ID of the Group the GroupAccount belongs to. uint64 group_id = 2; // admin is the account address of the group admin. string admin = 3; // metadata is any arbitrary metadata of this group account. bytes metadata = 4; // version is used to track changes to a group's GroupAccountInfo structure that // invalidates active proposal from old versions. uint64 version = 5; // decision_policy specifies the group account's decision policy. google.protobuf.Any decision_policy = 6 [(cosmos_proto.accepts_interface) = "cosmos.group.v1.DecisionPolicy"]; } ``` Similarly to a group admin, a group account admin can update its metadata, decision policy or set a new group account admin. A group account can also be an admin or a member of a group. For instance, a group admin could be another group account which could "elects" the members or it could be the same group that elects itself. ### Decision Policy A decision policy is the mechanism by which members of a group can vote on proposals. All decision policies should have a minimum and maximum voting window. The minimum voting window is the minimum duration that must pass in order for a proposal to potentially pass, and it may be set to 0. The maximum voting window is the maximum time that a proposal may be voted on and executed if it reached enough support before it is closed. Both of these values must be less than a chain-wide max voting window parameter. We define the `DecisionPolicy` interface that all decision policies must implement: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type DecisionPolicy interface { codec.ProtoMarshaler ValidateBasic() error GetTimeout() types.Duration Allow(tally Tally, totalPower string, votingDuration time.Duration) (DecisionPolicyResult, error) Validate(g GroupInfo) error } type DecisionPolicyResult struct { Allow bool Final bool } ``` #### Threshold decision policy A threshold decision policy defines a minimum support votes (*yes*), based on a tally of voter weights, for a proposal to pass. For this decision policy, abstain and veto are treated as no support (*no*). ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message ThresholdDecisionPolicy { // threshold is the minimum weighted sum of support votes for a proposal to succeed. string threshold = 1; // voting_period is the duration from submission of a proposal to the end of voting period // Within this period, votes and exec messages can be submitted. google.protobuf.Duration voting_period = 2 [(gogoproto.nullable) = false]; } ``` ### Proposal Any member of a group can submit a proposal for a group account to decide upon. A proposal consists of a set of `sdk.Msg`s that will be executed if the proposal passes as well as any metadata associated with the proposal. These `sdk.Msg`s get validated as part of the `Msg/CreateProposal` request validation. They should also have their signer set as the group account. Internally, a proposal also tracks: * its current `Status`: submitted, closed or aborted * its `Result`: unfinalized, accepted or rejected * its `VoteState` in the form of a `Tally`, which is calculated on new votes and when executing the proposal. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Tally represents the sum of weighted votes. message Tally { option (gogoproto.goproto_getters) = false; // yes_count is the weighted sum of yes votes. string yes_count = 1; // no_count is the weighted sum of no votes. string no_count = 2; // abstain_count is the weighted sum of abstainers. string abstain_count = 3; // veto_count is the weighted sum of vetoes. string veto_count = 4; } ``` ### Voting Members of a group can vote on proposals. There are four choices to choose while voting - yes, no, abstain and veto. Not all decision policies will support them. Votes can contain some optional metadata. In the current implementation, the voting window begins as soon as a proposal is submitted. Voting internally updates the proposal `VoteState` as well as `Status` and `Result` if needed. ### Executing Proposals Proposals will not be automatically executed by the chain in this current design, but rather a user must submit a `Msg/Exec` transaction to attempt to execute the proposal based on the current votes and decision policy. A future upgrade could automate this and have the group account (or a fee granter) pay. #### Changing Group Membership In the current implementation, updating a group or a group account after submitting a proposal will make it invalid. It will simply fail if someone calls `Msg/Exec` and will eventually be garbage collected. ### Notes on current implementation This section outlines the current implementation used in the proof of concept of the group module but this could be subject to changes and iterated on. #### ORM The [ORM package](https://github.com/cosmos/cosmos-sdk/discussions/9156) defines tables, sequences and secondary indexes which are used in the group module. Groups are stored in state as part of a `groupTable`, the `group_id` being an auto-increment integer. Group members are stored in a `groupMemberTable`. Group accounts are stored in a `groupAccountTable`. The group account address is generated based on an auto-increment integer which is used to derive the group module `RootModuleKey` into a `DerivedModuleKey`, as stated in [ADR-033](/sdk/latest/reference/architecture/adr-033-protobuf-inter-module-comm#modulekeys-and-moduleids). The group account is added as a new `ModuleAccount` through `x/auth`. Proposals are stored as part of the `proposalTable` using the `Proposal` type. The `proposal_id` is an auto-increment integer. Votes are stored in the `voteTable`. The primary key is based on the vote's `proposal_id` and `voter` account address. #### ADR-033 to route proposal messages Inter-module communication introduced by [ADR-033](/sdk/latest/reference/architecture/adr-033-protobuf-inter-module-comm) can be used to route a proposal's messages using the `DerivedModuleKey` corresponding to the proposal's group account. ## Consequences ### Positive * Improved UX for multi-signature accounts allowing key rotation and custom decision policies. ### Negative ### Neutral * It uses ADR 033 so it will need to be implemented within the Cosmos SDK, but this doesn't imply necessarily any large refactoring of existing Cosmos SDK modules. * The current implementation of the group module uses the ORM package. ## Further Discussions * Convergence of `/group` and `x/gov` as both support proposals and voting: [Link](https://github.com/cosmos/cosmos-sdk/discussions/9066) * `x/group` possible future improvements: * Execute proposals on submission ([Link](https://github.com/regen-network/regen-ledger/issues/288)) * Withdraw a proposal ([Link](https://github.com/regen-network/cosmos-modules/issues/41)) * Make `Tally` more flexible and support non-binary choices ## References * Initial specification: * [Link](https://gist.github.com/aaronc/b60628017352df5983791cad30babe56#group-module) * [#5236](https://github.com/cosmos/cosmos-sdk/pull/5236) * Proposal to add `x/group` into the Cosmos SDK: [#7633](https://github.com/cosmos/cosmos-sdk/issues/7633) # ADR 43: NFT Module Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-043-nft-module 2021-05-01: Initial Draft 2021-07-02: Review updates 2022-06-15: Add batch operation 2022-11-11: Remove strict validation of classID and tokenID ## Changelog * 2021-05-01: Initial Draft * 2021-07-02: Review updates * 2022-06-15: Add batch operation * 2022-11-11: Remove strict validation of classID and tokenID ## Status PROPOSED ## Abstract This ADR defines the `x/nft` module which is a generic implementation of NFTs, roughly "compatible" with ERC721. **Applications using the `x/nft` module must implement the following functions**: * `MsgNewClass` - Receive the user's request to create a class, and call the `NewClass` of the `x/nft` module. * `MsgUpdateClass` - Receive the user's request to update a class, and call the `UpdateClass` of the `x/nft` module. * `MsgMintNFT` - Receive the user's request to mint a nft, and call the `MintNFT` of the `x/nft` module. * `BurnNFT` - Receive the user's request to burn a nft, and call the `BurnNFT` of the `x/nft` module. * `UpdateNFT` - Receive the user's request to update a nft, and call the `UpdateNFT` of the `x/nft` module. ## Context NFTs are more than just crypto art, which is very helpful for accruing value to the Cosmos ecosystem. As a result, Cosmos Hub should implement NFT functions and enable a unified mechanism for storing and sending the ownership representative of NFTs as discussed in [Link](https://github.com/cosmos/cosmos-sdk/discussions/9065). As discussed in [#9065](https://github.com/cosmos/cosmos-sdk/discussions/9065), several potential solutions can be considered: * irismod/nft and modules/incubator/nft * CW721 * DID NFTs * interNFT Since functions/use cases of NFTs are tightly connected with their logic, it is almost impossible to support all the NFTs' use cases in one Cosmos SDK module by defining and implementing different transaction types. Considering generic usage and compatibility of interchain protocols including IBC and Gravity Bridge, it is preferred to have a generic NFT module design which handles the generic NFTs logic. This design idea can enable composability that application-specific functions should be managed by other modules on Cosmos Hub or on other Zones by importing the NFT module. The current design is based on the work done by [IRISnet team](https://github.com/irisnet/irismod/tree/master/modules/nft) and an older implementation in the [Cosmos repository](https://github.com/cosmos/modules/tree/master/incubator/nft). ## Decision We create a `x/nft` module, which contains the following functionality: * Store NFTs and track their ownership. * Expose `Keeper` interface for composing modules to transfer, mint and burn NFTs. * Expose external `Message` interface for users to transfer ownership of their NFTs. * Query NFTs and their supply information. The proposed module is a base module for NFT app logic. It's goal it to provide a common layer for storage, basic transfer functionality and IBC. The module should not be used as a standalone. Instead an app should create a specialized module to handle app specific logic (eg: NFT ID construction, royalty), user level minting and burning. Moreover an app specialized module should handle auxiliary data to support the app logic (eg indexes, ORM, business data). All data carried over IBC must be part of the `NFT` or `Class` type described below. The app specific NFT data should be encoded in `NFT.data` for cross-chain integrity. Other objects related to NFT, which are not important for integrity can be part of the app specific module. ### Types We propose two main types: * `Class` -- describes NFT class. We can think about it as a smart contract address. * `NFT` -- object representing unique, non fungible asset. Each NFT is associated with a Class. #### Class NFT **Class** is comparable to an ERC-721 smart contract (provides description of a smart contract), under which a collection of NFTs can be created and managed. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Class { string id = 1; string name = 2; string symbol = 3; string description = 4; string uri = 5; string uri_hash = 6; google.protobuf.Any data = 7; } ``` * `id` is used as the primary index for storing the class; *required* * `name` is a descriptive name of the NFT class; *optional* * `symbol` is the symbol usually shown on exchanges for the NFT class; *optional* * `description` is a detailed description of the NFT class; *optional* * `uri` is a URI for the class metadata stored off chain. It should be a JSON file that contains metadata about the NFT class and NFT data schema ([OpenSea example](https://docs.opensea.io/docs/contract-level-metadata)); *optional* * `uri_hash` is a hash of the document pointed by uri; *optional* * `data` is app specific metadata of the class; *optional* #### NFT We define a general model for `NFT` as follows. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message NFT { string class_id = 1; string id = 2; string uri = 3; string uri_hash = 4; google.protobuf.Any data = 10; } ``` * `class_id` is the identifier of the NFT class where the NFT belongs; *required* * `id` is an identifier of the NFT, unique within the scope of its class. It is specified by the creator of the NFT and may be expanded to use DID in the future. `class_id` combined with `id` uniquely identifies an NFT and is used as the primary index for storing the NFT; *required* ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {class_id}/{id} --> NFT (bytes) ``` * `uri` is a URI for the NFT metadata stored off chain. Should point to a JSON file that contains metadata about this NFT (Ref: [ERC721 standard and OpenSea extension](https://docs.opensea.io/docs/metadata-standards)); *required* * `uri_hash` is a hash of the document pointed by uri; *optional* * `data` is an app specific data of the NFT. CAN be used by composing modules to specify additional properties of the NFT; *optional* This ADR doesn't specify values that `data` can take; however, best practices recommend upper-level NFT modules clearly specify their contents. Although the value of this field doesn't provide the additional context required to manage NFT records, which means that the field can technically be removed from the specification, the field's existence allows basic informational/UI functionality. ### `Keeper` Interface ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper interface { NewClass(ctx sdk.Context,class Class) UpdateClass(ctx sdk.Context,class Class) Mint(ctx sdk.Context,nft NFT,receiver sdk.AccAddress) // updates totalSupply BatchMint(ctx sdk.Context, tokens []NFT,receiver sdk.AccAddress) error Burn(ctx sdk.Context, classId string, nftId string) // updates totalSupply BatchBurn(ctx sdk.Context, classID string, nftIDs []string) error Update(ctx sdk.Context, nft NFT) BatchUpdate(ctx sdk.Context, tokens []NFT) error Transfer(ctx sdk.Context, classId string, nftId string, receiver sdk.AccAddress) BatchTransfer(ctx sdk.Context, classID string, nftIDs []string, receiver sdk.AccAddress) error GetClass(ctx sdk.Context, classId string) Class GetClasses(ctx sdk.Context) []Class GetNFT(ctx sdk.Context, classId string, nftId string) NFT GetNFTsOfClassByOwner(ctx sdk.Context, classId string, owner sdk.AccAddress) []NFT GetNFTsOfClass(ctx sdk.Context, classId string) []NFT GetOwner(ctx sdk.Context, classId string, nftId string) sdk.AccAddress GetBalance(ctx sdk.Context, classId string, owner sdk.AccAddress) uint64 GetTotalSupply(ctx sdk.Context, classId string) uint64 } ``` Other business logic implementations should be defined in composing modules that import `x/nft` and use its `Keeper`. ### `Msg` Service ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service Msg { rpc Send(MsgSend) returns (MsgSendResponse); } message MsgSend { string class_id = 1; string id = 2; string sender = 3; string reveiver = 4; } message MsgSendResponse {} ``` `MsgSend` can be used to transfer the ownership of an NFT to another address. The implementation outline of the server is as follows: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type msgServer struct{ k Keeper } func (m msgServer) Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { // check current ownership assertEqual(msg.Sender, m.k.GetOwner(msg.ClassId, msg.Id)) // transfer ownership m.k.Transfer(msg.ClassId, msg.Id, msg.Receiver) return &types.MsgSendResponse{ }, nil } ``` The query service methods for the `x/nft` module are: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service Query { // Balance queries the number of NFTs of a given class owned by the owner, same as balanceOf in ERC721 rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) { option (google.api.http).get = "/cosmos/nft/v1beta1/balance/{owner}/{class_id}"; } // Owner queries the owner of the NFT based on its class and id, same as ownerOf in ERC721 rpc Owner(QueryOwnerRequest) returns (QueryOwnerResponse) { option (google.api.http).get = "/cosmos/nft/v1beta1/owner/{class_id}/{id}"; } // Supply queries the number of NFTs from the given class, same as totalSupply of ERC721. rpc Supply(QuerySupplyRequest) returns (QuerySupplyResponse) { option (google.api.http).get = "/cosmos/nft/v1beta1/supply/{class_id}"; } // NFTs queries all NFTs of a given class or owner,choose at least one of the two, similar to tokenByIndex in ERC721Enumerable rpc NFTs(QueryNFTsRequest) returns (QueryNFTsResponse) { option (google.api.http).get = "/cosmos/nft/v1beta1/nfts"; } // NFT queries an NFT based on its class and id. rpc NFT(QueryNFTRequest) returns (QueryNFTResponse) { option (google.api.http).get = "/cosmos/nft/v1beta1/nfts/{class_id}/{id}"; } // Class queries an NFT class based on its id rpc Class(QueryClassRequest) returns (QueryClassResponse) { option (google.api.http).get = "/cosmos/nft/v1beta1/classes/{class_id}"; } // Classes queries all NFT classes rpc Classes(QueryClassesRequest) returns (QueryClassesResponse) { option (google.api.http).get = "/cosmos/nft/v1beta1/classes"; } } // QueryBalanceRequest is the request type for the Query/Balance RPC method message QueryBalanceRequest { string class_id = 1; string owner = 2; } // QueryBalanceResponse is the response type for the Query/Balance RPC method message QueryBalanceResponse { uint64 amount = 1; } // QueryOwnerRequest is the request type for the Query/Owner RPC method message QueryOwnerRequest { string class_id = 1; string id = 2; } // QueryOwnerResponse is the response type for the Query/Owner RPC method message QueryOwnerResponse { string owner = 1; } // QuerySupplyRequest is the request type for the Query/Supply RPC method message QuerySupplyRequest { string class_id = 1; } // QuerySupplyResponse is the response type for the Query/Supply RPC method message QuerySupplyResponse { uint64 amount = 1; } // QueryNFTstRequest is the request type for the Query/NFTs RPC method message QueryNFTsRequest { string class_id = 1; string owner = 2; cosmos.base.query.v1beta1.PageRequest pagination = 3; } // QueryNFTsResponse is the response type for the Query/NFTs RPC methods message QueryNFTsResponse { repeated cosmos.nft.v1beta1.NFT nfts = 1; cosmos.base.query.v1beta1.PageResponse pagination = 2; } // QueryNFTRequest is the request type for the Query/NFT RPC method message QueryNFTRequest { string class_id = 1; string id = 2; } // QueryNFTResponse is the response type for the Query/NFT RPC method message QueryNFTResponse { cosmos.nft.v1beta1.NFT nft = 1; } // QueryClassRequest is the request type for the Query/Class RPC method message QueryClassRequest { string class_id = 1; } // QueryClassResponse is the response type for the Query/Class RPC method message QueryClassResponse { cosmos.nft.v1beta1.Class class = 1; } // QueryClassesRequest is the request type for the Query/Classes RPC method message QueryClassesRequest { // pagination defines an optional pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 1; } // QueryClassesResponse is the response type for the Query/Classes RPC method message QueryClassesResponse { repeated cosmos.nft.v1beta1.Class classes = 1; cosmos.base.query.v1beta1.PageResponse pagination = 2; } ``` ### Interoperability Interoperability is all about reusing assets between modules and chains. The former one is achieved by ADR-33: Protobuf client - server communication. At the time of writing ADR-33 is not finalized. The latter is achieved by IBC. Here we will focus on the IBC side. IBC is implemented per module. Here, we aligned that NFTs will be recorded and managed in the x/nft. This requires creation of a new IBC standard and implementation of it. For IBC interoperability, NFT custom modules MUST use the NFT object type understood by the IBC client. So, for x/nft interoperability, custom NFT implementations (example: x/cryptokitty) should use the canonical x/nft module and proxy all NFT balance keeping functionality to x/nft or else re-implement all functionality using the NFT object type understood by the IBC client. In other words: x/nft becomes the standard NFT registry for all Cosmos NFTs (example: x/cryptokitty will register a kitty NFT in x/nft and use x/nft for book keeping). This was [discussed](https://github.com/cosmos/cosmos-sdk/discussions/9065#discussioncomment-873206) in the context of using x/bank as a general asset balance book. Not using x/nft will require implementing another module for IBC. ## Consequences ### Backward Compatibility No backward incompatibilities. ### Forward Compatibility This specification conforms to the ERC-721 smart contract specification for NFT identifiers. Note that ERC-721 defines uniqueness based on (contract address, uint256 tokenId), and we conform to this implicitly because a single module is currently aimed to track NFT identifiers. Note: use of the (mutable) data field to determine uniqueness is not safe.s ### Positive * NFT identifiers available on Cosmos Hub. * Ability to build different NFT modules for the Cosmos Hub, e.g., ERC-721. * NFT module which supports interoperability with IBC and other cross-chain infrastructures like Gravity Bridge ### Negative * New IBC app is required for x/nft * CW721 adapter is required ### Neutral * Other functions need more modules. For example, a custody module is needed for NFT trading function, a collectible module is needed for defining NFT properties. ## Further Discussions For other kinds of applications on the Hub, more app-specific modules can be developed in the future: * `x/nft/custody`: custody of NFTs to support trading functionality. * `x/nft/marketplace`: selling and buying NFTs using sdk.Coins. * `x/fractional`: a module to split an ownership of an asset (NFT or other assets) for multiple stakeholder. `x/group` should work for most of the cases. Other networks in the Cosmos ecosystem could design and implement their own NFT modules for specific NFT applications and use cases. ## References * Initial discussion: [Link](https://github.com/cosmos/cosmos-sdk/discussions/9065) * x/nft: initialize module: [Link](https://github.com/cosmos/cosmos-sdk/pull/9174) * [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md) # ADR 044: Guidelines for Updating Protobuf Definitions Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-044-protobuf-updates-guidelines 28.06.2021: Initial Draft 02.12.2021: Add Since: comment for new fields 21.07.2022: Remove the rule of no new Msg in the same proto version. ## Changelog * 28.06.2021: Initial Draft * 02.12.2021: Add `Since:` comment for new fields * 21.07.2022: Remove the rule of no new `Msg` in the same proto version. ## Status Draft ## Abstract This ADR provides guidelines and recommended practices when updating Protobuf definitions. These guidelines are targeting module developers. ## Context The Cosmos SDK maintains a set of [Protobuf definitions](https://github.com/cosmos/cosmos-sdk/tree/main/proto/cosmos). It is important to correctly design Protobuf definitions to avoid any breaking changes within the same version. The reasons are to not break tooling (including indexers and explorers), wallets and other third-party integrations. When making changes to these Protobuf definitions, the Cosmos SDK currently only follows [Buf's](https://docs.buf.build/) recommendations. We noticed however that Buf's recommendations might still result in breaking changes in the SDK in some cases. For example: * Adding fields to `Msg`s. Adding fields is a not a Protobuf spec-breaking operation. However, when adding new fields to `Msg`s, the unknown field rejection will throw an error when sending the new `Msg` to an older node. * Marking fields as `reserved`. Protobuf proposes the `reserved` keyword for removing fields without the need to bump the package version. However, by doing so, client backwards compatibility is broken as Protobuf doesn't generate anything for `reserved` fields. See [#9446](https://github.com/cosmos/cosmos-sdk/issues/9446) for more details on this issue. Moreover, module developers often face other questions around Protobuf definitions such as "Can I rename a field?" or "Can I deprecate a field?" This ADR aims to answer all these questions by providing clear guidelines about allowed updates for Protobuf definitions. ## Decision We decide to keep [Buf's](https://docs.buf.build/) recommendations with the following exceptions: * `UNARY_RPC`: the Cosmos SDK currently does not support streaming RPCs. * `COMMENT_FIELD`: the Cosmos SDK allows fields with no comments. * `SERVICE_SUFFIX`: we use the `Query` and `Msg` service naming convention, which doesn't use the `-Service` suffix. * `PACKAGE_VERSION_SUFFIX`: some packages, such as `cosmos.crypto.ed25519`, don't use a version suffix. * `RPC_REQUEST_STANDARD_NAME`: Requests for the `Msg` service don't have the `-Request` suffix to keep backwards compatibility. On top of Buf's recommendations we add the following guidelines that are specific to the Cosmos SDK. ### Updating Protobuf Definition Without Bumping Version #### 1. Module developers MAY add new Protobuf definitions Module developers MAY add new `message`s, new `Service`s, new `rpc` endpoints, and new fields to existing messages. This recommendation follows the Protobuf specification, but is added in this document for clarity, as the SDK requires one additional change. The SDK requires the Protobuf comment of the new addition to contain one line with the following format: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Since: cosmos-sdk {, ...} ``` Where each `version` denotes a minor ("0.45") or patch ("0.44.5") version from which the field is available. This will greatly help client libraries, who can optionally use reflection or custom code generation to show/hide these fields depending on the targetted node version. As examples, the following comments are valid: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Since: cosmos-sdk 0.44 // Since: cosmos-sdk 0.42.11, 0.44.5 ``` and the following ones are NOT valid: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Since cosmos-sdk v0.44 // since: cosmos-sdk 0.44 // Since: cosmos-sdk 0.42.11 0.44.5 // Since: Cosmos SDK 0.42.11, 0.44.5 ``` #### 2. Fields MAY be marked as `deprecated`, and nodes MAY implement a protocol-breaking change for handling these fields Protobuf supports the [`deprecated` field option](https://developers.google.com/protocol-buffers/docs/proto#options), and this option MAY be used on any field, including `Msg` fields. If a node handles a Protobuf message with a non-empty deprecated field, the node MAY change its behavior upon processing it, even in a protocol-breaking way. When possible, the node MUST handle backwards compatibility without breaking the consensus (unless we increment the proto version). As an example, the Cosmos SDK v0.42 to v0.43 update contained two Protobuf-breaking changes, listed below. Instead of bumping the package versions from `v1beta1` to `v1`, the SDK team decided to follow this guideline, by reverting the breaking changes, marking those changes as deprecated, and modifying the node implementation when processing messages with deprecated fields. More specifically: * The Cosmos SDK recently removed support for [time-based software upgrades](https://github.com/cosmos/cosmos-sdk/pull/8849). As such, the `time` field has been marked as deprecated in `cosmos.upgrade.v1beta1.Plan`. Moreover, the node will reject any proposal containing an upgrade Plan whose `time` field is non-empty. * The Cosmos SDK now supports [governance split votes](/sdk/v0.50/build/architecture/adr-037-gov-split-vote). When querying for votes, the returned `cosmos.gov.v1beta1.Vote` message has its `option` field (used for 1 vote option) deprecated in favor of its `options` field (allowing multiple vote options). Whenever possible, the SDK still populates the deprecated `option` field, that is, if and only if the `len(options) == 1` and `options[0].Weight == 1.0`. #### 3. Fields MUST NOT be renamed Whereas the official Protobuf recommendations do not prohibit renaming fields, as it does not break the Protobuf binary representation, the SDK explicitly forbids renaming fields in Protobuf structs. The main reason for this choice is to avoid introducing breaking changes for clients, which often rely on hard-coded fields from generated types. Moreover, renaming fields will lead to client-breaking JSON representations of Protobuf definitions, used in REST endpoints and in the CLI. ### Incrementing Protobuf Package Version TODO, needs architecture review. Some topics: * Bumping versions frequency * When bumping versions, should the Cosmos SDK support both versions? * i.e. v1beta1 -> v1, should we have two folders in the Cosmos SDK, and handlers for both versions? * mention ADR-023 Protobuf naming ## Consequences > This section describes the resulting context, after applying the decision. All consequences should be listed here, not just the "positive" ones. A particular decision may have positive, negative, and neutral consequences, but all of them affect the team and project in the future. ### Backwards Compatibility > All ADRs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The ADR must explain how the author proposes to deal with these incompatibilities. ADR submissions without a sufficient backwards compatibility treatise may be rejected outright. ### Positive * less pain to tool developers * more compatibility in the ecosystem * ... ### Negative `{negative consequences}` ### Neutral * more rigor in Protobuf review ## Further Discussions This ADR is still in the DRAFT stage, and the "Incrementing Protobuf Package Version" will be filled in once we make a decision on how to correctly do it. ## Test Cases \[optional] Test cases for an implementation are mandatory for ADRs that are affecting consensus changes. Other ADRs can choose to include links to test cases if applicable. ## References * [#9445](https://github.com/cosmos/cosmos-sdk/issues/9445) Release proto definitions v1 * [#9446](https://github.com/cosmos/cosmos-sdk/issues/9446) Address v1beta1 proto breaking changes # Adr 045 check delivertx middlewares Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-045-check-delivertx-middlewares 20.08.2021: Initial draft. 07.12.2021: Update tx.Handler interface (\#10693). 17.05.2022: ADR is abandoned, as middlewares are deemed too hard to reason about. ## Changelog * 20.08.2021: Initial draft. * 07.12.2021: Update `tx.Handler` interface ([#10693](https://github.com/cosmos/cosmos-sdk/pull/10693)). * 17.05.2022: ADR is abandoned, as middlewares are deemed too hard to reason about. ## Status ABANDONED. Replacement is being discussed in [#11955](https://github.com/cosmos/cosmos-sdk/issues/11955). ## Abstract This ADR replaces the current BaseApp `runTx` and antehandlers design with a middleware-based design. ## Context BaseApp's implementation of ABCI `{Check,Deliver}Tx()` and its own `Simulate()` method call the `runTx` method under the hood, which first runs antehandlers, then executes `Msg`s. However, the [transaction Tips](https://github.com/cosmos/cosmos-sdk/issues/9406) and [refunding unused gas](https://github.com/cosmos/cosmos-sdk/issues/2150) use cases require custom logic to be run after the `Msg`s execution. There is currently no way to achieve this. An naive solution would be to add post-`Msg` hooks to BaseApp. However, the Cosmos SDK team thinks in parallel about the bigger picture of making app wiring simpler ([#9181](https://github.com/cosmos/cosmos-sdk/discussions/9182)), which includes making BaseApp more lightweight and modular. ## Decision We decide to transform Baseapp's implementation of ABCI `{Check,Deliver}Tx` and its own `Simulate` methods to use a middleware-based design. The two following interfaces are the base of the middleware design, and are defined in `types/tx`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Handler interface { CheckTx(ctx context.Context, req Request, checkReq RequestCheckTx) (Response, ResponseCheckTx, error) DeliverTx(ctx context.Context, req Request) (Response, error) SimulateTx(ctx context.Context, req Request (Response, error) } type Middleware func(Handler) Handler ``` where we define the following arguments and return types: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Request struct { Tx sdk.Tx TxBytes []byte } type Response struct { GasWanted uint64 GasUsed uint64 // MsgResponses is an array containing each Msg service handler's response // type, packed in an Any. This will get proto-serialized into the `Data` field // in the ABCI Check/DeliverTx responses. MsgResponses []*codectypes.Any Log string Events []abci.Event } type RequestCheckTx struct { Type abci.CheckTxType } type ResponseCheckTx struct { Priority int64 } ``` Please note that because CheckTx handles separate logic related to mempool priotization, its signature is different than DeliverTx and SimulateTx. BaseApp holds a reference to a `tx.Handler`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type BaseApp struct { // other fields txHandler tx.Handler } ``` Baseapp's ABCI `{Check,Deliver}Tx()` and `Simulate()` methods simply call `app.txHandler.{Check,Deliver,Simulate}Tx()` with the relevant arguments. For example, for `DeliverTx`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { var abciRes abci.ResponseDeliverTx ctx := app.getContextForTx(runTxModeDeliver, req.Tx) res, err := app.txHandler.DeliverTx(ctx, tx.Request{ TxBytes: req.Tx }) if err != nil { abciRes = sdkerrors.ResponseDeliverTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) return abciRes } abciRes, err = convertTxResponseToDeliverTx(res) if err != nil { return sdkerrors.ResponseDeliverTx(err, uint64(res.GasUsed), uint64(res.GasWanted), app.trace) } return abciRes } // convertTxResponseToDeliverTx converts a tx.Response into a abci.ResponseDeliverTx. func convertTxResponseToDeliverTx(txRes tx.Response) (abci.ResponseDeliverTx, error) { data, err := makeABCIData(txRes) if err != nil { return abci.ResponseDeliverTx{ }, nil } return abci.ResponseDeliverTx{ Data: data, Log: txRes.Log, Events: txRes.Events, }, nil } // makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx. func makeABCIData(txRes tx.Response) ([]byte, error) { return proto.Marshal(&sdk.TxMsgData{ MsgResponses: txRes.MsgResponses }) } ``` The implementations are similar for `BaseApp.CheckTx` and `BaseApp.Simulate`. `baseapp.txHandler`'s three methods' implementations can obviously be monolithic functions, but for modularity we propose a middleware composition design, where a middleware is simply a function that takes a `tx.Handler`, and returns another `tx.Handler` wrapped around the previous one. ### Implementing a Middleware In practice, middlewares are created by Go function that takes as arguments some parameters needed for the middleware, and returns a `tx.Middleware`. For example, for creating an arbitrary `MyMiddleware`, we can implement: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // myTxHandler is the tx.Handler of this middleware. Note that it holds a // reference to the next tx.Handler in the stack. type myTxHandler struct { // next is the next tx.Handler in the middleware stack. next tx.Handler // some other fields that are relevant to the middleware can be added here } // NewMyMiddleware returns a middleware that does this and that. func NewMyMiddleware(arg1, arg2) tx.Middleware { return func (txh tx.Handler) tx.Handler { return myTxHandler{ next: txh, // optionally, set arg1, arg2... if they are needed in the middleware } } } // Assert myTxHandler is a tx.Handler. var _ tx.Handler = myTxHandler{ } func (h myTxHandler) CheckTx(ctx context.Context, req Request, checkReq RequestcheckTx) (Response, ResponseCheckTx, error) { // CheckTx specific pre-processing logic // run the next middleware res, checkRes, err := txh.next.CheckTx(ctx, req, checkReq) // CheckTx specific post-processing logic return res, checkRes, err } func (h myTxHandler) DeliverTx(ctx context.Context, req Request) (Response, error) { // DeliverTx specific pre-processing logic // run the next middleware res, err := txh.next.DeliverTx(ctx, tx, req) // DeliverTx specific post-processing logic return res, err } func (h myTxHandler) SimulateTx(ctx context.Context, req Request) (Response, error) { // SimulateTx specific pre-processing logic // run the next middleware res, err := txh.next.SimulateTx(ctx, tx, req) // SimulateTx specific post-processing logic return res, err } ``` ### Composing Middlewares While BaseApp simply holds a reference to a `tx.Handler`, this `tx.Handler` itself is defined using a middleware stack. The Cosmos SDK exposes a base (i.e. innermost) `tx.Handler` called `RunMsgsTxHandler`, which executes messages. Then, the app developer can compose multiple middlewares on top on the base `tx.Handler`. Each middleware can run pre-and-post-processing logic around its next middleware, as described in the section above. Conceptually, as an example, given the middlewares `A`, `B`, and `C` and the base `tx.Handler` `H` the stack looks like: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} A.pre B.pre C.pre H # The base tx.handler, for example `RunMsgsTxHandler` C.post B.post A.post ``` We define a `ComposeMiddlewares` function for composing middlewares. It takes the base handler as first argument, and middlewares in the "outer to inner" order. For the above stack, the final `tx.Handler` is: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txHandler := middleware.ComposeMiddlewares(H, A, B, C) ``` The middleware is set in BaseApp via its `SetTxHandler` setter: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // simapp/app.go txHandler := middleware.ComposeMiddlewares(...) app.SetTxHandler(txHandler) ``` The app developer can define their own middlewares, or use the Cosmos SDK's pre-defined middlewares from `middleware.NewDefaultTxHandler()`. ### Middlewares Maintained by the Cosmos SDK While the app developer can define and compose the middlewares of their choice, the Cosmos SDK provides a set of middlewares that caters for the ecosystem's most common use cases. These middlewares are: | Middleware | Description | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | RunMsgsTxHandler | This is the base `tx.Handler`. It replaces the old baseapp's `runMsgs`, and executes a transaction's `Msg`s. | | TxDecoderMiddleware | This middleware takes in transaction raw bytes, and decodes them into a `sdk.Tx`. It replaces the `baseapp.txDecoder` field, so that BaseApp stays as thin as possible. Since most middlewares read the contents of the `sdk.Tx`, the TxDecoderMiddleware should be run first in the middleware stack. | | `{Antehandlers}` | Each antehandler is converted to its own middleware. These middlewares perform signature verification, fee deductions and other validations on the incoming transaction. | | IndexEventsTxMiddleware | This is a simple middleware that chooses which events to index in Tendermint. Replaces `baseapp.indexEvents` (which unfortunately still exists in baseapp too, because it's used to index Begin/EndBlock events) | | RecoveryTxMiddleware | This index recovers from panics. It replaces baseapp.runTx's panic recovery described in [ADR-022](/sdk/v0.50/build/architecture/adr-022-custom-panic-handling). | | GasTxMiddleware | This replaces the [`Setup`](https://github.com/cosmos/cosmos-sdk/blob/v0.43.0/x/auth/ante/setup.go) Antehandler. It sets a GasMeter on sdk.Context. Note that before, GasMeter was set on sdk.Context inside the antehandlers, and there was some mess around the fact that antehandlers had their own panic recovery system so that the GasMeter could be read by baseapp's recovery system. Now, this mess is all removed: one middleware sets GasMeter, another one handles recovery. | ### Similarities and Differences between Antehandlers and Middlewares The middleware-based design builds upon the existing antehandlers design described in [ADR-010](/sdk/v0.50/build/architecture/adr-010-modular-antehandler). Even though the final decision of ADR-010 was to go with the "Simple Decorators" approach, the middleware design is actually very similar to the other [Decorator Pattern](/sdk/v0.50/build/architecture/adr-010-modular-antehandler#decorator-pattern) proposal, also used in [weave](https://github.com/iov-one/weave). #### Similarities with Antehandlers * Designed as chaining/composing small modular pieces. * Allow code reuse for `{Check,Deliver}Tx` and for `Simulate`. * Set up in `app.go`, and easily customizable by app developers. * Order is important. #### Differences with Antehandlers * The Antehandlers are run before `Msg` execution, whereas middlewares can run before and after. * The middleware approach uses separate methods for `{Check,Deliver,Simulate}Tx`, whereas the antehandlers pass a `simulate bool` flag and uses the `sdkCtx.Is{Check,Recheck}Tx()` flags to determine in which transaction mode we are. * The middleware design lets each middleware hold a reference to the next middleware, whereas the antehandlers pass a `next` argument in the `AnteHandle` method. * The middleware design use Go's standard `context.Context`, whereas the antehandlers use `sdk.Context`. ## Consequences ### Backwards Compatibility Since this refactor removes some logic away from BaseApp and into middlewares, it introduces API-breaking changes for app developers. Most notably, instead of creating an antehandler chain in `app.go`, app developers need to create a middleware stack: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - anteHandler, err := ante.NewAnteHandler( - ante.HandlerOptions{ - AccountKeeper: app.AccountKeeper, - BankKeeper: app.BankKeeper, - SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - FeegrantKeeper: app.FeeGrantKeeper, - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, - }, -) +txHandler, err := authmiddleware.NewDefaultTxHandler(authmiddleware.TxHandlerOptions{ + Debug: app.Trace(), + IndexEvents: indexEvents, + LegacyRouter: app.legacyRouter, + MsgServiceRouter: app.msgSvcRouter, + LegacyAnteHandler: anteHandler, + TxDecoder: encodingConfig.TxConfig.TxDecoder, +}) if err != nil { panic(err) } - app.SetAnteHandler(anteHandler) + app.SetTxHandler(txHandler) ``` Other more minor API breaking changes will also be provided in the CHANGELOG. As usual, the Cosmos SDK will provide a release migration document for app developers. This ADR does not introduce any state-machine-, client- or CLI-breaking changes. ### Positive * Allow custom logic to be run before an after `Msg` execution. This enables the [tips](https://github.com/cosmos/cosmos-sdk/issues/9406) and [gas refund](https://github.com/cosmos/cosmos-sdk/issues/2150) uses cases, and possibly other ones. * Make BaseApp more lightweight, and defer complex logic to small modular components. * Separate paths for `{Check,Deliver,Simulate}Tx` with different returns types. This allows for improved readability (replace `if sdkCtx.IsRecheckTx() && !simulate {...}` with separate methods) and more flexibility (e.g. returning a `priority` in `ResponseCheckTx`). ### Negative * It is hard to understand at first glance the state updates that would occur after a middleware runs given the `sdk.Context` and `tx`. A middleware can have an arbitrary number of nested middleware being called within its function body, each possibly doing some pre- and post-processing before calling the next middleware on the chain. Thus to understand what a middleware is doing, one must also understand what every other middleware further along the chain is also doing, and the order of middlewares matters. This can get quite complicated to understand. * API-breaking changes for app developers. ### Neutral No neutral consequences. ## Further Discussions * [#9934](https://github.com/cosmos/cosmos-sdk/discussions/9934) Decomposing BaseApp's other ABCI methods into middlewares. * Replace `sdk.Tx` interface with the concrete protobuf Tx type in the `tx.Handler` methods signature. ## Test Cases We update the existing baseapp and antehandlers tests to use the new middleware API, but keep the same test cases and logic, to avoid introducing regressions. Existing CLI tests will also be left untouched. For new middlewares, we introduce unit tests. Since middlewares are purposefully small, unit tests suit well. ## References * Initial discussion: [Link](https://github.com/cosmos/cosmos-sdk/issues/9585) * Implementation: [#9920 BaseApp refactor](https://github.com/cosmos/cosmos-sdk/pull/9920) and [#10028 Antehandlers migration](https://github.com/cosmos/cosmos-sdk/pull/10028) # ADR 046: Module Params Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-046-module-params Sep 22, 2021: Initial Draft ## Changelog * Sep 22, 2021: Initial Draft ## Status Proposed ## Abstract This ADR describes an alternative approach to how Cosmos SDK modules use, interact, and store their respective parameters. ## Context Currently, in the Cosmos SDK, modules that require the use of parameters use the `x/params` module. The `x/params` works by having modules define parameters, typically via a simple `Params` structure, and registering that structure in the `x/params` module via a unique `Subspace` that belongs to the respective registering module. The registering module then has unique access to its respective `Subspace`. Through this `Subspace`, the module can get and set its `Params` structure. In addition, the Cosmos SDK's `x/gov` module has direct support for changing parameters on-chain via a `ParamChangeProposal` governance proposal type, where stakeholders can vote on suggested parameter changes. There are various tradeoffs to using the `x/params` module to manage individual module parameters. Namely, managing parameters essentially comes for "free" in that developers only need to define the `Params` struct, the `Subspace`, and the various auxiliary functions, e.g. `ParamSetPairs`, on the `Params` type. However, there are some notable drawbacks. These drawbacks include the fact that parameters are serialized in state via JSON which is extremely slow. In addition, parameter changes via `ParamChangeProposal` governance proposals have no way of reading from or writing to state. In other words, it is currently not possible to have any state transitions in the application during an attempt to change param(s). ## Decision We will build off of the alignment of `x/gov` and `x/authz` work per [#9810](https://github.com/cosmos/cosmos-sdk/pull/9810). Namely, module developers will create one or more unique parameter data structures that must be serialized to state. The Param data structures must implement `sdk.Msg` interface with respective Protobuf Msg service method which will validate and update the parameters with all necessary changes. The `x/gov` module via the work done in [#9810](https://github.com/cosmos/cosmos-sdk/pull/9810), will dispatch Param messages, which will be handled by Protobuf Msg services. Note, it is up to developers to decide how to structure their parameters and the respective `sdk.Msg` messages. Consider the parameters currently defined in `x/auth` using the `x/params` module for parameter management: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Params { uint64 max_memo_characters = 1; uint64 tx_sig_limit = 2; uint64 tx_size_cost_per_byte = 3; uint64 sig_verify_cost_ed25519 = 4; uint64 sig_verify_cost_secp256k1 = 5; } ``` Developers can choose to either create a unique data structure for every field in `Params` or they can create a single `Params` structure as outlined above in the case of `x/auth`. In the former, `x/params`, approach, a `sdk.Msg` would need to be created for every single field along with a handler. This can become burdensome if there are a lot of parameter fields. In the latter case, there is only a single data structure and thus only a single message handler, however, the message handler might have to be more sophisticated in that it might need to understand what parameters are being changed vs what parameters are untouched. Params change proposals are made using the `x/gov` module. Execution is done through `x/authz` authorization to the root `x/gov` module's account. Continuing to use `x/auth`, we demonstrate a more complete example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Params struct { MaxMemoCharacters uint64 TxSigLimit uint64 TxSizeCostPerByte uint64 SigVerifyCostED25519 uint64 SigVerifyCostSecp256k1 uint64 } type MsgUpdateParams struct { MaxMemoCharacters uint64 TxSigLimit uint64 TxSizeCostPerByte uint64 SigVerifyCostED25519 uint64 SigVerifyCostSecp256k1 uint64 } type MsgUpdateParamsResponse struct { } func (ms msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // verification logic... // persist params params := ParamsFromMsg(msg) ms.SaveParams(ctx, params) return &types.MsgUpdateParamsResponse{ }, nil } func ParamsFromMsg(msg *types.MsgUpdateParams) Params { // ... } ``` A gRPC `Service` query should also be provided, for example: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} service Query { // ... rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/cosmos//v1beta1/params"; } } message QueryParamsResponse { Params params = 1 [(gogoproto.nullable) = false]; } ``` ## Consequences As a result of implementing the module parameter methodology, we gain the ability for module parameter changes to be stateful and extensible to fit nearly every application's use case. We will be able to emit events (and trigger hooks registered to that events using the work proposed in [event hooks](https://github.com/cosmos/cosmos-sdk/discussions/9656)), call other Msg service methods or perform migration. In addition, there will be significant gains in performance when it comes to reading and writing parameters from and to state, especially if a specific set of parameters are read on a consistent basis. However, this methodology will require developers to implement more types and Msg service methods which can become burdensome if many parameters exist. In addition, developers are required to implement persistence logic for module parameters. However, this should be trivial. ### Backwards Compatibility The new method for working with module parameters is naturally not backwards compatible with the existing `x/params` module. However, the `x/params` will remain in the Cosmos SDK and will be marked as deprecated with no additional functionality being added apart from potential bug fixes. Note, the `x/params` module may be removed entirely in a future release. ### Positive * Module parameters are serialized more efficiently * Modules are able to react on parameters changes and perform additional actions. * Special events can be emitted, allowing hooks to be triggered. ### Negative * Module parameters becomes slightly more burdensome for module developers: * Modules are now responsible for persisting and retrieving parameter state * Modules are now required to have unique message handlers to handle parameter changes per unique parameter data structure. ### Neutral * Requires [#9810](https://github.com/cosmos/cosmos-sdk/pull/9810) to be reviewed and merged. ## References * [Link](https://github.com/cosmos/cosmos-sdk/pull/9810) * [Link](https://github.com/cosmos/cosmos-sdk/issues/9438) * [Link](https://github.com/cosmos/cosmos-sdk/discussions/9913) # ADR 047: Extend Upgrade Plan Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-047-extend-upgrade-plan Nov, 23, 2021: Initial Draft May, 16, 2023: Proposal ABANDONED. prerun and postrun are not necessary anymore and adding the artifacts brings minor benefits. ## Changelog * Nov, 23, 2021: Initial Draft * May, 16, 2023: Proposal ABANDONED. `pre_run` and `post_run` are not necessary anymore and adding the `artifacts` brings minor benefits. ## Status ABANDONED ## Abstract This ADR expands the existing x/upgrade `Plan` proto message to include new fields for defining pre-run and post-run processes within upgrade tooling. It also defines a structure for providing downloadable artifacts involved in an upgrade. ## Context The `upgrade` module in conjunction with Cosmovisor are designed to facilitate and automate a blockchain's transition from one version to another. Users submit a software upgrade governance proposal containing an upgrade `Plan`. The [Plan](https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/proto/cosmos/upgrade/v1beta1/upgrade.proto#L12) currently contains the following fields: * `name`: A short string identifying the new version. * `height`: The chain height at which the upgrade is to be performed. * `info`: A string containing information about the upgrade. The `info` string can be anything. However, Cosmovisor will try to use the `info` field to automatically download a new version of the blockchain executable. For the auto-download to work, Cosmovisor expects it to be either a stringified JSON object (with a specific structure defined through documentation), or a URL that will return such JSON. The JSON object identifies URLs used to download the new blockchain executable for different platforms (OS and Architecture, e.g. "linux/amd64"). Such a URL can either return the executable file directly or can return an archive containing the executable and possibly other assets. If the URL returns an archive, it is decompressed into `{DAEMON_HOME}/cosmovisor/{upgrade name}`. Then, if `{DAEMON_HOME}/cosmovisor/{upgrade name}/bin/{DAEMON_NAME}` does not exist, but `{DAEMON_HOME}/cosmovisor/{upgrade name}/{DAEMON_NAME}` does, the latter is copied to the former. If the URL returns something other than an archive, it is downloaded to `{DAEMON_HOME}/cosmovisor/{upgrade name}/bin/{DAEMON_NAME}`. If an upgrade height is reached and the new version of the executable version isn't available, Cosmovisor will stop running. Both `DAEMON_HOME` and `DAEMON_NAME` are [environment variables used to configure Cosmovisor](https://github.com/cosmos/cosmos-sdk/blob/cosmovisor/v1.0.0/cosmovisor/README.md#command-line-arguments-and-environment-variables). Currently, there is no mechanism that makes Cosmovisor run a command after the upgraded chain has been restarted. The current upgrade process has this timeline: 1. An upgrade governance proposal is submitted and approved. 2. The upgrade height is reached. 3. The `x/upgrade` module writes the `upgrade_info.json` file. 4. The chain halts. 5. Cosmovisor backs up the data directory (if set up to do so). 6. Cosmovisor downloads the new executable (if not already in place). 7. Cosmovisor executes the `${DAEMON_NAME} pre-upgrade`. 8. Cosmovisor restarts the app using the new version and same args originally provided. ## Decision ### Protobuf Updates We will update the `x/upgrade.Plan` message for providing upgrade instructions. The upgrade instructions will contain a list of artifacts available for each platform. It allows for the definition of a pre-run and post-run commands. These commands are not consensus guaranteed; they will be executed by Cosmosvisor (or other) during its upgrade handling. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Plan { // ... (existing fields) UpgradeInstructions instructions = 6; } ``` The new `UpgradeInstructions instructions` field MUST be optional. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message UpgradeInstructions { string pre_run = 1; string post_run = 2; repeated Artifact artifacts = 3; string description = 4; } ``` All fields in the `UpgradeInstructions` are optional. * `pre_run` is a command to run prior to the upgraded chain restarting. If defined, it will be executed after halting and downloading the new artifact but before restarting the upgraded chain. The working directory this command runs from MUST be `{DAEMON_HOME}/cosmovisor/{upgrade name}`. This command MUST behave the same as the current [pre-upgrade](https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/docs/migrations/pre-upgrade.md) command. It does not take in any command-line arguments and is expected to terminate with the following exit codes: | Exit status code | How it is handled in Cosmosvisor | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `0` | Assumes `pre-upgrade` command executed successfully and continues the upgrade. | | `1` | Default exit code when `pre-upgrade` command has not been implemented. | | `30` | `pre-upgrade` command was executed but failed. This fails the entire upgrade. | | `31` | `pre-upgrade` command was executed but failed. But the command is retried until exit code `1` or `30` are returned. | | If defined, then the app supervisors (e.g. Cosmovisor) MUST NOT run `app pre-run`. | | * `post_run` is a command to run after the upgraded chain has been started. If defined, this command MUST be only executed at most once by an upgrading node. The output and exit code SHOULD be logged but SHOULD NOT affect the running of the upgraded chain. The working directory this command runs from MUST be `{DAEMON_HOME}/cosmovisor/{upgrade name}`. * `artifacts` define items to be downloaded. It SHOULD have only one entry per platform. * `description` contains human-readable information about the upgrade and might contain references to external resources. It SHOULD NOT be used for structured processing information. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Artifact { string platform = 1; string url = 2; string checksum = 3; string checksum_algo = 4; } ``` * `platform` is a required string that SHOULD be in the format `{OS}/{CPU}`, e.g. `"linux/amd64"`. The string `"any"` SHOULD also be allowed. An `Artifact` with a `platform` of `"any"` SHOULD be used as a fallback when a specific `{OS}/{CPU}` entry is not found. That is, if an `Artifact` exists with a `platform` that matches the system's OS and CPU, that should be used; otherwise, if an `Artifact` exists with a `platform` of `any`, that should be used; otherwise no artifact should be downloaded. * `url` is a required URL string that MUST conform to [RFC 1738: Uniform Resource Locators](https://www.ietf.org/rfc/rfc1738.txt). A request to this `url` MUST return either an executable file or an archive containing either `bin/{DAEMON_NAME}` or `{DAEMON_NAME}`. The URL should not contain checksum - it should be specified by the `checksum` attribute. * `checksum` is a checksum of the expected result of a request to the `url`. It is not required, but is recommended. If provided, it MUST be a hex encoded checksum string. Tools utilizing these `UpgradeInstructions` MUST fail if a `checksum` is provided but is different from the checksum of the result returned by the `url`. * `checksum_algo` is a string identify the algorithm used to generate the `checksum`. Recommended algorithms: `sha256`, `sha512`. Algorithms also supported (but not recommended): `sha1`, `md5`. If a `checksum` is provided, a `checksum_algo` MUST also be provided. A `url` is not required to contain a `checksum` query parameter. If the `url` does contain a `checksum` query parameter, the `checksum` and `checksum_algo` fields MUST also be populated, and their values MUST match the value of the query parameter. For example, if the `url` is `"https://example.com?checksum=md5:d41d8cd98f00b204e9800998ecf8427e"`, then the `checksum` field must be `"d41d8cd98f00b204e9800998ecf8427e"` and the `checksum_algo` field must be `"md5"`. ### Upgrade Module Updates If an upgrade `Plan` does not use the new `UpgradeInstructions` field, existing functionality will be maintained. The parsing of the `info` field as either a URL or `binaries` JSON will be deprecated. During validation, if the `info` field is used as such, a warning will be issued, but not an error. We will update the creation of the `upgrade-info.json` file to include the `UpgradeInstructions`. We will update the optional validation available via CLI to account for the new `Plan` structure. We will add the following validation: 1. If `UpgradeInstructions` are provided: 1. There MUST be at least one entry in `artifacts`. 2. All of the `artifacts` MUST have a unique `platform`. 3. For each `Artifact`, if the `url` contains a `checksum` query parameter: 1. The `checksum` query parameter value MUST be in the format of `{checksum_algo}:{checksum}`. 2. The `{checksum}` from the query parameter MUST equal the `checksum` provided in the `Artifact`. 3. The `{checksum_algo}` from the query parameter MUST equal the `checksum_algo` provided in the `Artifact`. 2. The following validation is currently done using the `info` field. We will apply similar validation to the `UpgradeInstructions`. For each `Artifact`: 1. The `platform` MUST have the format `{OS}/{CPU}` or be `"any"`. 2. The `url` field MUST NOT be empty. 3. The `url` field MUST be a proper URL. 4. A `checksum` MUST be provided either in the `checksum` field or as a query parameter in the `url`. 5. If the `checksum` field has a value and the `url` also has a `checksum` query parameter, the two values MUST be equal. 6. The `url` MUST return either a file or an archive containing either `bin/{DAEMON_NAME}` or `{DAEMON_NAME}`. 7. If a `checksum` is provided (in the field or as a query param), the checksum of the result of the `url` MUST equal the provided checksum. Downloading of an `Artifact` will happen the same way that URLs from `info` are currently downloaded. ### Cosmovisor Updates If the `upgrade-info.json` file does not contain any `UpgradeInstructions`, existing functionality will be maintained. We will update Cosmovisor to look for and handle the new `UpgradeInstructions` in `upgrade-info.json`. If the `UpgradeInstructions` are provided, we will do the following: 1. The `info` field will be ignored. 2. The `artifacts` field will be used to identify the artifact to download based on the `platform` that Cosmovisor is running in. 3. If a `checksum` is provided (either in the field or as a query param in the `url`), and the downloaded artifact has a different checksum, the upgrade process will be interrupted and Cosmovisor will exit with an error. 4. If a `pre_run` command is defined, it will be executed at the same point in the process where the `app pre-upgrade` command would have been executed. It will be executed using the same environment as other commands run by Cosmovisor. 5. If a `post_run` command is defined, it will be executed after executing the command that restarts the chain. It will be executed in a background process using the same environment as the other commands. Any output generated by the command will be logged. Once complete, the exit code will be logged. We will deprecate the use of the `info` field for anything other than human readable information. A warning will be logged if the `info` field is used to define the assets (either by URL or JSON). The new upgrade timeline is very similar to the current one. Changes are in bold: 1. An upgrade governance proposal is submitted and approved. 2. The upgrade height is reached. 3. The `x/upgrade` module writes the `upgrade_info.json` file **(now possibly with `UpgradeInstructions`)**. 4. The chain halts. 5. Cosmovisor backs up the data directory (if set up to do so). 6. Cosmovisor downloads the new executable (if not already in place). 7. Cosmovisor executes **the `pre_run` command if provided**, or else the `${DAEMON_NAME} pre-upgrade` command. 8. Cosmovisor restarts the app using the new version and same args originally provided. 9. **Cosmovisor immediately runs the `post_run` command in a detached process.** ## Consequences ### Backwards Compatibility Since the only change to existing definitions is the addition of the `instructions` field to the `Plan` message, and that field is optional, there are no backwards incompatibilities with respects to the proto messages. Additionally, current behavior will be maintained when no `UpgradeInstructions` are provided, so there are no backwards incompatibilities with respects to either the upgrade module or Cosmovisor. ### Forwards Compatibility In order to utilize the `UpgradeInstructions` as part of a software upgrade, both of the following must be true: 1. The chain must already be using a sufficiently advanced version of the Cosmos SDK. 2. The chain's nodes must be using a sufficiently advanced version of Cosmovisor. ### Positive 1. The structure for defining artifacts is clearer since it is now defined in the proto instead of in documentation. 2. Availability of a pre-run command becomes more obvious. 3. A post-run command becomes possible. ### Negative 1. The `Plan` message becomes larger. This is negligible because A) the `x/upgrades` module only stores at most one upgrade plan, and B) upgrades are rare enough that the increased gas cost isn't a concern. 2. There is no option for providing a URL that will return the `UpgradeInstructions`. 3. The only way to provide multiple assets (executables and other files) for a platform is to use an archive as the platform's artifact. ### Neutral 1. Existing functionality of the `info` field is maintained when the `UpgradeInstructions` aren't provided. ## Further Discussions 1. [Draft PR #10032 Comment](https://github.com/cosmos/cosmos-sdk/pull/10032/files?authenticity_token=pLtzpnXJJB%2Fif2UWiTp9Td3MvRrBF04DvjSuEjf1azoWdLF%2BSNymVYw9Ic7VkqHgNLhNj6iq9bHQYnVLzMXd4g%3D%3D\&file-filters%5B%5D=.go\&file-filters%5B%5D=.proto#r698708349): Consider different names for `UpgradeInstructions instructions` (either the message type or field name). 2. [Draft PR #10032 Comment](https://github.com/cosmos/cosmos-sdk/pull/10032/files?authenticity_token=pLtzpnXJJB%2Fif2UWiTp9Td3MvRrBF04DvjSuEjf1azoWdLF%2BSNymVYw9Ic7VkqHgNLhNj6iq9bHQYnVLzMXd4g%3D%3D\&file-filters%5B%5D=.go\&file-filters%5B%5D=.proto#r754655072): 1. Consider putting the `string platform` field inside `UpgradeInstructions` and make `UpgradeInstructions` a repeated field in `Plan`. 2. Consider using a `oneof` field in the `Plan` which could either be `UpgradeInstructions` or else a URL that should return the `UpgradeInstructions`. 3. Consider allowing `info` to either be a JSON serialized version of `UpgradeInstructions` or else a URL that returns that. 3. [Draft PR #10032 Comment](https://github.com/cosmos/cosmos-sdk/pull/10032/files?authenticity_token=pLtzpnXJJB%2Fif2UWiTp9Td3MvRrBF04DvjSuEjf1azoWdLF%2BSNymVYw9Ic7VkqHgNLhNj6iq9bHQYnVLzMXd4g%3D%3D\&file-filters%5B%5D=.go\&file-filters%5B%5D=.proto#r755462876): Consider not including the `UpgradeInstructions.description` field, using the `info` field for that purpose instead. 4. [Draft PR #10032 Comment](https://github.com/cosmos/cosmos-sdk/pull/10032/files?authenticity_token=pLtzpnXJJB%2Fif2UWiTp9Td3MvRrBF04DvjSuEjf1azoWdLF%2BSNymVYw9Ic7VkqHgNLhNj6iq9bHQYnVLzMXd4g%3D%3D\&file-filters%5B%5D=.go\&file-filters%5B%5D=.proto#r754643691): Consider allowing multiple artifacts to be downloaded for any given `platform` by adding a `name` field to the `Artifact` message. 5. [PR #10502 Comment](https://github.com/cosmos/cosmos-sdk/pull/10602#discussion_r781438288) Allow the new `UpgradeInstructions` to be provided via URL. 6. [PR #10502 Comment](https://github.com/cosmos/cosmos-sdk/pull/10602#discussion_r781438288) Allow definition of a `signer` for assets (as an alternative to using a `checksum`). ## References * [Current upgrade.proto](https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/proto/cosmos/upgrade/v1beta1/upgrade.proto) * [Upgrade Module README](https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/x/upgrade/spec/README.md) * [Cosmovisor README](https://github.com/cosmos/cosmos-sdk/blob/cosmovisor/v1.0.0/cosmovisor/README.md) * [Pre-upgrade README](https://github.com/cosmos/cosmos-sdk/blob/v0.44.5/docs/migrations/pre-upgrade.md) * [Draft/POC PR #10032](https://github.com/cosmos/cosmos-sdk/pull/10032) * [RFC 1738: Uniform Resource Locators](https://www.ietf.org/rfc/rfc1738.txt) # ADR 048: Multi Tire Gas Price System Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-048-consensus-fees Dec 1, 2021: Initial Draft ## Changelog * Dec 1, 2021: Initial Draft ## Status Rejected ## Abstract This ADR describes a flexible mechanism to maintain a consensus level gas prices, in which one can choose a multi-tier gas price system or EIP-1559 like one through configuration. ## Context Currently, each validator configures its own `minimal-gas-prices` in `app.yaml`. But setting a proper minimal gas price is critical to protect network from DDoS attack, and it's hard for all the validators to pick a sensible value, so we propose to maintain a gas price in consensus level. Since tendermint 0.34.20 has supported mempool prioritization, we can take advantage of that to implement more sophisticated gas fee system. ## Multi-Tier Price System We propose a multi-tier price system on consensus to provide maximum flexibility: * Tier 1: a constant gas price, which could only be modified occasionally through governance proposal. * Tier 2: a dynamic gas price which is adjusted according to previous block load. * Tier 3: a dynamic gas price which is adjusted according to previous block load at a higher speed. The gas price of higher tier should bigger than the lower tier. The transaction fees are charged with the exact gas price calculated on consensus. The parameter schema is like this: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message TierParams { uint32 priority = 1 // priority in tendermint mempool Coin initial_gas_price = 2 // uint32 parent_gas_target = 3 // the target saturation of block uint32 change_denominator = 4 // decides the change speed Coin min_gas_price = 5 // optional lower bound of the price adjustment Coin max_gas_price = 6 // optional upper bound of the price adjustment } message Params { repeated TierParams tiers = 1; } ``` ### Extension Options We need to allow user to specify the tier of service for the transaction, to support it in an extensible way, we add an extension option in `AuthInfo`: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message ExtensionOptionsTieredTx { uint32 fee_tier = 1 } ``` The value of `fee_tier` is just the index to the `tiers` parameter list. We also change the semantic of existing `fee` field of `Tx`, instead of charging user the exact `fee` amount, we treat it as a fee cap, while the actual amount of fee charged is decided dynamically. If the `fee` is smaller than dynamic one, the transaction won't be included in current block and ideally should stay in the mempool until the consensus gas price drop. The mempool can eventually prune old transactions. ### Tx Prioritization Transactions are prioritized based on the tier, the higher the tier, the higher the priority. Within the same tier, follow the default Tendermint order (currently FIFO). Be aware of that the mempool tx ordering logic is not part of consensus and can be modified by malicious validator. This mechanism can be easily composed with prioritization mechanisms: * we can add extra tiers out of a user control: * Example 1: user can set tier 0, 10 or 20, but the protocol will create tiers 0, 1, 2 ... 29. For example IBC transactions will go to tier `user_tier + 5`: if user selected tier 1, then the transaction will go to tier 15. * Example 2: we can reserve tier 4, 5, ... only for special transaction types. For example, tier 5 is reserved for evidence tx. So if submits a bank.Send transaction and set tier 5, it will be delegated to tier 3 (the max tier level available for any transaction). * Example 3: we can enforce that all transactions of a sepecific type will go to specific tier. For example, tier 100 will be reserved for evidence transactions and all evidence transactions will always go to that tier. ### `min-gas-prices` Deprecate the current per-validator `min-gas-prices` configuration, since it would confusing for it to work together with the consensus gas price. ### Adjust For Block Load For tier 2 and tier 3 transactions, the gas price is adjusted according to previous block load, the logic could be similar to EIP-1559: ```python expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} def adjust_gas_price(gas_price, parent_gas_used, tier): if parent_gas_used == tier.parent_gas_target: return gas_price elif parent_gas_used > tier.parent_gas_target: gas_used_delta = parent_gas_used - tier.parent_gas_target gas_price_delta = max(gas_price * gas_used_delta // tier.parent_gas_target // tier.change_speed, 1) return gas_price + gas_price_delta else: gas_used_delta = parent_gas_target - parent_gas_used gas_price_delta = gas_price * gas_used_delta // parent_gas_target // tier.change_speed return gas_price - gas_price_delta ``` ### Block Segment Reservation Ideally we should reserve block segments for each tier, so the lower tiered transactions won't be completely squeezed out by higher tier transactions, which will force user to use higher tier, and the system degraded to a single tier. We need help from tendermint to implement this. ## Implementation We can make each tier's gas price strategy fully configurable in protocol parameters, while providing a sensible default one. Pseudocode in python-like syntax: ```python expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface TieredTx: def tier(self) -> int: pass def tx_tier(tx): if isinstance(tx, TieredTx): return tx.tier() else: # default tier for custom transactions return 0 # NOTE: we can add more rules here per "Tx Prioritization" section class TierParams: 'gas price strategy parameters of one tier' priority: int # priority in tendermint mempool initial_gas_price: Coin parent_gas_target: int change_speed: Decimal # 0 means don't adjust for block load. class Params: 'protocol parameters' tiers: List[TierParams] class State: 'consensus state' # total gas used in last block, None when it's the first block parent_gas_used: Optional[int] # gas prices of last block for all tiers gas_prices: List[Coin] def begin_block(): 'Adjust gas prices' for i, tier in enumerate(Params.tiers): if State.parent_gas_used is None: # initialized gas price for the first block State.gas_prices[i] = tier.initial_gas_price else: # adjust gas price according to gas used in previous block State.gas_prices[i] = adjust_gas_price(State.gas_prices[i], State.parent_gas_used, tier) def mempoolFeeTxHandler_checkTx(ctx, tx): # the minimal-gas-price configured by validator, zero in deliver_tx context validator_price = ctx.MinGasPrice() consensus_price = State.gas_prices[tx_tier(tx)] min_price = max(validator_price, consensus_price) # zero means infinity for gas price cap if tx.gas_price() > 0 and tx.gas_price() < min_price: return 'insufficient fees' return next_CheckTx(ctx, tx) def txPriorityHandler_checkTx(ctx, tx): res, err := next_CheckTx(ctx, tx) # pass priority to tendermint res.Priority = Params.tiers[tx_tier(tx)].priority return res, err def end_block(): 'Update block gas used' State.parent_gas_used = block_gas_meter.consumed() ``` ### DDoS attack protection To fully saturate the blocks and prevent other transactions from executing, attacker need to use transactions of highest tier, the cost would be significantly higher than the default tier. If attacker spam with lower tier transactions, user can mitigate by sending higher tier transactions. ## Consequences ### Backwards Compatibility * New protocol parameters. * New consensus states. * New/changed fields in transaction body. ### Positive * The default tier keeps the same predictable gas price experience for client. * The higher tier's gas price can adapt to block load. * No priority conflict with custom priority based on transaction types, since this proposal only occupy three priority levels. * Possibility to compose different priority rules with tiers ### Negative * Wallets & tools need to update to support the new `tier` parameter, and semantic of `fee` field is changed. ### Neutral ## References * [Link](https://eips.ethereum.org/EIPS/eip-1559) * [Link](https://iohk.io/en/blog/posts/2021/11/26/network-traffic-and-tiered-pricing/) # ADR 049: State Sync Hooks Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-049-state-sync-hooks Jan 19, 2022: Initial Draft Apr 29, 2022: Safer extension snapshotter interface ## Changelog * Jan 19, 2022: Initial Draft * Apr 29, 2022: Safer extension snapshotter interface ## Status Implemented ## Abstract This ADR outlines a hooks-based mechanism for application modules to provide additional state (outside of the IAVL tree) to be used during state sync. ## Context New clients use state-sync to download snapshots of module state from peers. Currently, the snapshot consists of a stream of `SnapshotStoreItem` and `SnapshotIAVLItem`, which means that application modules that define their state outside of the IAVL tree cannot include their state as part of the state-sync process. Note, Even though the module state data is outside of the tree, for determinism we require that the hash of the external data should be posted in the IAVL tree. ## Decision A simple proposal based on our existing implementation is that, we can add two new message types: `SnapshotExtensionMeta` and `SnapshotExtensionPayload`, and they are appended to the existing multi-store stream with `SnapshotExtensionMeta` acting as a delimiter between extensions. As the chunk hashes should be able to ensure data integrity, we don't need a delimiter to mark the end of the snapshot stream. Besides, we provide `Snapshotter` and `ExtensionSnapshotter` interface for modules to implement snapshotters, which will handle both taking snapshot and the restoration. Each module could have mutiple snapshotters, and for modules with additional state, they should implement `ExtensionSnapshotter` as extension snapshotters. When setting up the application, the snapshot `Manager` should call `RegisterExtensions([]ExtensionSnapshotter…)` to register all the extension snapshotters. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SnapshotItem is an item contained in a rootmulti.Store snapshot. // On top of the exsiting SnapshotStoreItem and SnapshotIAVLItem, we add two new options for the item. message SnapshotItem { // item is the specific type of snapshot item. oneof item { SnapshotStoreItem store = 1; SnapshotIAVLItem iavl = 2 [(gogoproto.customname) = "IAVL"]; SnapshotExtensionMeta extension = 3; SnapshotExtensionPayload extension_payload = 4; } } // SnapshotExtensionMeta contains metadata about an external snapshotter. // One module may need multiple snapshotters, so each module may have multiple SnapshotExtensionMeta. message SnapshotExtensionMeta { // the name of the ExtensionSnapshotter, and it is registered to snapshotter manager when setting up the application // name should be unique for each ExtensionSnapshotter as we need to alphabetically order their snapshots to get // deterministic snapshot stream. string name = 1; // this is used by each ExtensionSnapshotter to decide the format of payloads included in SnapshotExtensionPayload message // it is used within the snapshotter/namespace, not global one for all modules uint32 format = 2; } // SnapshotExtensionPayload contains payloads of an external snapshotter. message SnapshotExtensionPayload { bytes payload = 1; } ``` When we create a snapshot stream, the `multistore` snapshot is always placed at the beginning of the binary stream, and other extension snapshots are alphabetically ordered by the name of the corresponding `ExtensionSnapshotter`. The snapshot stream would look like as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // multi-store snapshot { SnapshotStoreItem | SnapshotIAVLItem, ... } // extension1 snapshot SnapshotExtensionMeta { SnapshotExtensionPayload, ... } // extension2 snapshot SnapshotExtensionMeta { SnapshotExtensionPayload, ... } ``` We add an `extensions` field to snapshot `Manager` for extension snapshotters. The `multistore` snapshotter is a special one and it doesn't need a name because it is always placed at the beginning of the binary stream. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Manager struct { store *Store multistore types.Snapshotter extensions map[string]types.ExtensionSnapshotter mtx sync.Mutex operation operation chRestore chan<- io.ReadCloser chRestoreDone <-chan restoreDone restoreChunkHashes [][]byte restoreChunkIndex uint32 } ``` For extension snapshotters that implement the `ExtensionSnapshotter` interface, their names should be registered to the snapshot `Manager` by calling `RegisterExtensions` when setting up the application. The snapshotters will handle both taking snapshot and restoration. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // RegisterExtensions register extension snapshotters to manager func (m *Manager) RegisterExtensions(extensions ...types.ExtensionSnapshotter) error ``` On top of the existing `Snapshotter` interface for the `multistore`, we add `ExtensionSnapshotter` interface for the extension snapshotters. Three more function signatures: `SnapshotFormat()`, `SupportedFormats()` and `SnapshotName()` are added to `ExtensionSnapshotter`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ExtensionPayloadReader read extension payloads, // it returns io.EOF when reached either end of stream or the extension boundaries. type ExtensionPayloadReader = func() ([]byte, error) // ExtensionPayloadWriter is a helper to write extension payloads to underlying stream. type ExtensionPayloadWriter = func([]byte) error // ExtensionSnapshotter is an extension Snapshotter that is appended to the snapshot stream. // ExtensionSnapshotter has a unique name and manages its own internal formats. type ExtensionSnapshotter interface { // SnapshotName returns the name of snapshotter, it should be unique in the manager. SnapshotName() string // SnapshotFormat returns the default format used to take a snapshot. SnapshotFormat() uint32 // SupportedFormats returns a list of formats it can restore from. SupportedFormats() []uint32 // SnapshotExtension writes extension payloads into the underlying protobuf stream. SnapshotExtension(height uint64, payloadWriter ExtensionPayloadWriter) error // RestoreExtension restores an extension state snapshot, // the payload reader returns `io.EOF` when reached the extension boundaries. RestoreExtension(height uint64, format uint32, payloadReader ExtensionPayloadReader) error } ``` ## Consequences As a result of this implementation, we are able to create snapshots of binary chunk stream for the state that we maintain outside of the IAVL Tree, CosmWasm blobs for example. And new clients are able to fetch sanpshots of state for all modules that have implemented the corresponding interface from peer nodes. ### Backwards Compatibility This ADR introduces new proto message types, add an `extensions` field in snapshot `Manager`, and add new `ExtensionSnapshotter` interface, so this is not backwards compatible if we have extensions. But for applications that does not have the state data outside of the IAVL tree for any module, the snapshot stream is backwards-compatible. ### Positive * State maintained outside of IAVL tree like CosmWasm blobs can create snapshots by implementing extension snapshotters, and being fetched by new clients via state-sync. ### Negative ### Neutral * All modules that maintain state outside of IAVL tree need to implement `ExtensionSnapshotter` and the snapshot `Manager` need to call `RegisterExtensions` when setting up the application. ## Further Discussions While an ADR is in the DRAFT or PROPOSED stage, this section should contain a summary of issues to be solved in future iterations (usually referencing comments from a pull-request discussion). Later, this section can optionally list ideas or improvements the author or reviewers found during the analysis of this ADR. ## Test Cases \[optional] Test cases for an implementation are mandatory for ADRs that are affecting consensus changes. Other ADRs can choose to include links to test cases if applicable. ## References * [Link](https://github.com/cosmos/cosmos-sdk/pull/10961) * [Link](https://github.com/cosmos/cosmos-sdk/issues/7340) # ADR 050: SIGN_MODE_TEXTUAL Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-050-sign-mode-textual ## Changelog * Dec 06, 2021: Initial Draft. * Feb 07, 2022: Draft read and concept-ACKed by the Ledger team. * May 16, 2022: Change status to Accepted. * Aug 11, 2022: Require signing over tx raw bytes. * Sep 07, 2022: Add custom `Msg`-renderers. * Sep 18, 2022: Structured format instead of lines of text * Nov 23, 2022: Specify CBOR encoding. * Dec 01, 2022: Link to examples in separate JSON file. * Dec 06, 2022: Re-ordering of envelope screens. * Dec 14, 2022: Mention exceptions for invertability. * Jan 23, 2023: Switch Screen.Text to Title+Content. * Mar 07, 2023: Change SignDoc from array to struct containing array. * Mar 20, 2023: Introduce a spec version initialized to 0. ## Status Archived. `SIGN_MODE_TEXTUAL` was removed in Cosmos SDK v0.55, and the proto enum value is reserved. This ADR is retained for historical reference. Spec version: 0. ## Abstract This ADR specifies SIGN\_MODE\_TEXTUAL, a new string-based sign mode that is targetted at signing with hardware devices. ## Context Protobuf-based SIGN\_MODE\_DIRECT was introduced in [ADR-020](/sdk/latest/reference/architecture/adr-020-protobuf-transaction-encoding) and is intended to replace SIGN\_MODE\_LEGACY\_AMINO\_JSON in most situations, such as mobile wallets and CLI keyrings. However, the [Ledger](https://www.ledger.com/) hardware wallet is still using SIGN\_MODE\_LEGACY\_AMINO\_JSON for displaying the sign bytes to the user. Hardware wallets cannot transition to SIGN\_MODE\_DIRECT as: * SIGN\_MODE\_DIRECT is binary-based and thus not suitable for display to end-users. Technically, hardware wallets could simply display the sign bytes to the user. But this would be considered as blind signing, and is a security concern. * hardware cannot decode the protobuf sign bytes due to memory constraints, as the Protobuf definitions would need to be embedded on the hardware device. In an effort to remove Amino from the SDK, a new sign mode needs to be created for hardware devices. [Initial discussions](https://github.com/cosmos/cosmos-sdk/issues/6513) propose a text-based sign mode, which this ADR formally specifies. ## Decision In SIGN\_MODE\_TEXTUAL, a transaction is rendered into a textual representation, which is then sent to a secure device or subsystem for the user to review and sign. Unlike `SIGN_MODE_DIRECT`, the transmitted data can be simply decoded into legible text even on devices with limited processing and display. The textual representation is a sequence of *screens*. Each screen is meant to be displayed in its entirety (if possible) even on a small device like a Ledger. A screen is roughly equivalent to a short line of text. Large screens can be displayed in several pieces, much as long lines of text are wrapped, so no hard guidance is given, though 40 characters is a good target. A screen is used to display a single key/value pair for scalar values (or composite values with a compact notation, such as `Coins`) or to introduce or conclude a larger grouping. The text can contain the full range of Unicode code points, including control characters and nul. The device is responsible for deciding how to display characters it cannot render natively. See [annex 2](/sdk/latest/reference/architecture/adr-050-sign-mode-textual-annex2) for guidance. Screens have a non-negative indentation level to signal composite or nested structures. Indentation level zero is the top level. Indentation is displayed via some device-specific mechanism. Message quotation notation is an appropriate model, such as leading `>` characters or vertical bars on more capable displays. Some screens are marked as *expert* screens, meant to be displayed only if the viewer chooses to opt in for the extra detail. Expert screens are meant for information that is rarely useful, or needs to be present only for signature integrity (see below). ### Invertible Rendering We require that the rendering of the transaction be invertible: there must be a parsing function such that for every transaction, when rendered to the textual representation, parsing that representation yeilds a proto message equivalent to the original under proto equality. Note that this inverse function does not need to perform correct parsing or error signaling for the whole domain of textual data. Merely that the range of valid transactions be invertible under the composition of rendering and parsing. Note that the existence of an inverse function ensures that the rendered text contains the full information of the original transaction, not a hash or subset. We make an exception for invertibility for data which are too large to meaningfully display, such as byte strings longer than 32 bytes. We may then selectively render them with a cryptographically-strong hash. In these cases, it is still computationally infeasible to find a different transaction which has the same rendering. However, we must ensure that the hash computation is simple enough to be reliably executed independently, so at least the hash is itself reasonably verifiable when the raw byte string is not. ### Chain State The rendering function (and parsing function) may depend on the current chain state. This is useful for reading parameters, such as coin display metadata, or for reading user-specific preferences such as language or address aliases. Note that if the observed state changes between signature generation and the transaction's inclusion in a block, the delivery-time rendering might differ. If so, the signature will be invalid and the transaction will be rejected. ### Signature and Security For security, transaction signatures should have three properties: 1. Given the transaction, signatures, and chain state, it must be possible to validate that the signatures matches the transaction, to verify that the signers must have known their respective secret keys. 2. It must be computationally infeasible to find a substantially different transaction for which the given signatures are valid, given the same chain state. 3. The user should be able to give informed consent to the signed data via a simple, secure device with limited display capabilities. The correctness and security of `SIGN_MODE_TEXTUAL` is guaranteed by demonstrating an inverse function from the rendering to transaction protos. This means that it is impossible for a different protocol buffer message to render to the same text. ### Transaction Hash Malleability When client software forms a transaction, the "raw" transaction (`TxRaw`) is serialized as a proto and a hash of the resulting byte sequence is computed. This is the `TxHash`, and is used by various services to track the submitted transaction through its lifecycle. Various misbehavior is possible if one can generate a modified transaction with a different TxHash but for which the signature still checks out. SIGN\_MODE\_TEXTUAL prevents this transaction malleability by including the TxHash as an expert screen in the rendering. ### SignDoc The SignDoc for `SIGN_MODE_TEXTUAL` is formed from a data structure like: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Screen struct { Title string // possibly size limited to, advised to 64 characters Content string // possibly size limited to, advised to 255 characters Indent uint8 // size limited to something small like 16 or 32 Expert bool } type SignDocTextual struct { Screens []Screen } ``` We do not plan to use protobuf serialization to form the sequence of bytes that will be tranmitted and signed, in order to keep the decoder simple. We will use [CBOR](https://cbor.io) ([RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html)) instead. The encoding is defined by the following CDDL ([RFC 8610](https://www.rfc-editor.org/rfc/rfc8610)): ``` ;;; CDDL (RFC 8610) Specification of SignDoc for SIGN_MODE_TEXTUAL. ;;; Must be encoded using CBOR deterministic encoding (RFC 8949, section 4.2.1). ;; A Textual document is a struct containing one field: an array of screens. sign_doc = { screens_key: [* screen], } ;; The key is an integer to keep the encoding small. screens_key = 1 ;; A screen consists of a text string, an indentation, and the expert flag, ;; represented as an integer-keyed map. All entries are optional ;; and MUST be omitted from the encoding if empty, zero, or false. ;; Text defaults to the empty string, indent defaults to zero, ;; and expert defaults to false. screen = { ? title_key: tstr, ? content_key: tstr, ? indent_key: uint, ? expert_key: bool, } ;; Keys are small integers to keep the encoding small. title_key = 1 content_key = 2 indent_key = 3 expert_key = 4 ``` Defining the sign\_doc as directly an array of screens has also been considered. However, given the possibility of future iterations of this specification, using a single-keyed struct has been chosen over the former proposal, as structs allow for easier backwards-compatibility. ## Details In the examples that follow, screens will be shown as lines of text, indentation is indicated with a leading '>', and expert screens are marked with a leading `*`. ### Encoding of the Transaction Envelope We define "transaction envelope" as all data in a transaction that is not in the `TxBody.Messages` field. Transaction envelope includes fee, signer infos and memo, but don't include `Msg`s. `//` denotes comments and are not shown on the Ledger device. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Chain ID: Account number: Sequence: Address: *Public Key: This transaction has Message(s) // Pluralize "Message" only when int>1 > Message (/): // See value renderers for Any rendering. End of Message Memo: // Skipped if no memo set. Fee: // See value renderers for coins rendering. *Fee payer: // Skipped if no fee_payer set. *Fee granter: // Skipped if no fee_granter set. Tip: // Skippted if no tip. Tipper: *Gas Limit: *Timeout Height: // Skipped if no timeout_height set. *Other signer: SignerInfo // Skipped if the transaction only has 1 signer. *> Other signer (/): *End of other signers *Extension options: Any: // Skipped if no body extension options *> Extension options (/): *End of extension options *Non critical extension options: Any: // Skipped if no body non critical extension options *> Non critical extension options (/): *End of Non critical extension options *Hash of raw bytes: // Hex encoding of bytes defined, to prevent tx hash malleability. ``` ### Encoding of the Transaction Body Transaction Body is the `Tx.TxBody.Messages` field, which is an array of `Any`s, where each `Any` packs a `sdk.Msg`. Since `sdk.Msg`s are widely used, they have a slightly different encoding than usual array of `Any`s (Protobuf: `repeated google.protobuf.Any`) described in Annex 1. ``` This transaction has message: // Optional 's' for "message" if there's is >1 sdk.Msgs. // For each Msg, print the following 2 lines: Msg (/): // E.g. Msg (1/2): bank v1beta1 send coins End of transaction messages ``` #### Example Given the following Protobuf message: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Grant { google.protobuf.Any authorization = 1 [(cosmos_proto.accepts_interface) = "cosmos.authz.v1beta1.Authorization"]; google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; } message MsgGrant { option (cosmos.msg.v1.signer) = "granter"; string granter = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; string grantee = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } ``` and a transaction containing 1 such `sdk.Msg`, we get the following encoding: ``` This transaction has 1 message: Msg (1/1): authz v1beta1 grant Granter: cosmos1abc...def Grantee: cosmos1ghi...jkl End of transaction messages ``` ### Custom `Msg` Renderers Application developers may choose to not follow default renderer value output for their own `Msg`s. In this case, they can implement their own custom `Msg` renderer. This is similar to [EIP4430](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4430.md), where the smart contract developer chooses the description string to be shown to the end user. This is done by setting the `cosmos.msg.textual.v1.expert_custom_renderer` Protobuf option to a non-empty string. This option CAN ONLY be set on a Protobuf message representing transaction message object (implementing `sdk.Msg` interface). ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgFooBar { // Optional comments to describe in human-readable language the formatting // rules of the custom renderer. option (cosmos.msg.textual.v1.expert_custom_renderer) = ""; // proto fields } ``` When this option is set on a `Msg`, a registered function will transform the `Msg` into an array of one or more strings, which MAY use the key/value format (described in point #3) with the expert field prefix (described in point #5) and arbitrary indentation (point #6). These strings MAY be rendered from a `Msg` field using a default value renderer, or they may be generated from several fields using custom logic. The `` is a string convention chosen by the application developer and is used to identify the custom `Msg` renderer. For example, the documentation or specification of this custom algorithm can reference this identifier. This identifier CAN have a versioned suffix (e.g. `_v1`) to adapt for future changes (which would be consensus-breaking). We also recommend adding Protobuf comments to describe in human language the custom logic used. Moreover, the renderer must provide 2 functions: one for formatting from Protobuf to string, and one for parsing string to Protobuf. These 2 functions are provided by the application developer. To satisfy point #1, the parse function MUST be the inverse of the formatting function. This property will not be checked by the SDK at runtime. However, we strongly recommend the application developer to include a comprehensive suite in their app repo to test invertibility, as to not introduce security bugs. ### Require signing over the `TxBody` and `AuthInfo` raw bytes Recall that the transaction bytes merklelized on chain are the Protobuf binary serialization of [TxRaw](https://buf.build/cosmos/cosmos-sdk/sdk/v0.50/main:cosmos.tx.v1beta1#cosmos.tx.v1beta1.TxRaw), which contains the `body_bytes` and `auth_info_bytes`. Moreover, the transaction hash is defined as the SHA256 hash of the `TxRaw` bytes. We require that the user signs over these bytes in SIGN\_MODE\_TEXTUAL, more specifically over the following string: ``` *Hash of raw bytes: ``` where: * `++` denotes concatenation, * `HEX` is the hexadecimal representation of the bytes, all in capital letters, no `0x` prefix, * and `len()` is encoded as a Big-Endian uint64. This is to prevent transaction hash malleability. The point #1 about invertiblity assures that transaction `body` and `auth_info` values are not malleable, but the transaction hash still might be malleable with point #1 only, because the SIGN\_MODE\_TEXTUAL strings don't follow the byte ordering defined in `body_bytes` and `auth_info_bytes`. Without this hash, a malicious validator or exchange could intercept a transaction, modify its transaction hash *after* the user signed it using SIGN\_MODE\_TEXTUAL (by tweaking the byte ordering inside `body_bytes` or `auth_info_bytes`), and then submit it to Tendermint. By including this hash in the SIGN\_MODE\_TEXTUAL signing payload, we keep the same level of guarantees as [SIGN\_MODE\_DIRECT](/sdk/latest/reference/architecture/adr-020-protobuf-transaction-encoding). These bytes are only shown in expert mode, hence the leading `*`. ## Updates to the current specification The current specification is not set in stone, and future iterations are to be expected. We distinguish two categories of updates to this specification: 1. Updates that require changes of the hardware device embedded application. 2. Updates that only modify the envelope and the value renderers. Updates in the 1st category include changes of the `Screen` struct or its corresponding CBOR encoding. This type of updates require a modification of the hardware signer application, to be able to decode and parse the new types. Backwards-compatibility must also be guaranteed, so that the new hardware application works with existing versions of the SDK. These updates require the coordination of multiple parties: SDK developers, hardware application developers (currently: Zondax), and client-side developers (e.g. CosmJS). Furthermore, a new submission of the hardware device application may be necessary, which, dependending on the vendor, can take some time. As such, we recommend to avoid this type of updates as much as possible. Updates in the 2nd category include changes to any of the value renderers or to the transaction envelope. For example, the ordering of fields in the envelope can be swapped, or the timestamp formatting can be modified. Since SIGN\_MODE\_TEXTUAL sends `Screen`s to the hardware device, this type of change do not need a hardware wallet application update. They are however state-machine-breaking, and must be documented as such. They require the coordination of SDK developers with client-side developers (e.g. CosmJS), so that the updates are released on both sides close to each other in time. We define a spec version, which is an integer that must be incremented on each update of either category. This spec version will be exposed by the SDK's implementation, and can be communicated to clients. For example, SDK v0.50 might use the spec version 1, and SDK v0.51 might use 2; thanks to this versioning, clients can know how to craft SIGN\_MODE\_TEXTUAL transactions based on the target SDK version. The current spec version is defined in the "Status" section, on the top of this document. It is initialized to `0` to allow flexibility in choosing how to define future versions, as it would allow adding a field either in the SignDoc Go struct or in Protobuf in a backwards-compatible way. ## Additional Formatting by the Hardware Device See [annex 2](/sdk/latest/reference/architecture/adr-050-sign-mode-textual-annex2). ## Examples 1. A minimal MsgSend: [see transaction](https://github.com/cosmos/cosmos-sdk/blob/094abcd393379acbbd043996024d66cd65246fb1/tx/textual/internal/testdata/e2e.json#L2-L70). 2. A transaction with a bit of everything: [see transaction](https://github.com/cosmos/cosmos-sdk/blob/094abcd393379acbbd043996024d66cd65246fb1/tx/textual/internal/testdata/e2e.json#L71-L270). The examples below are stored in a JSON file with the following fields: * `proto`: the representation of the transaction in ProtoJSON, * `screens`: the transaction rendered into SIGN\_MODE\_TEXTUAL screens, * `cbor`: the sign bytes of the transaction, which is the CBOR encoding of the screens. ## Consequences ### Backwards Compatibility SIGN\_MODE\_TEXTUAL is purely additive, and doesn't break any backwards compatibility with other sign modes. ### Positive * Human-friendly way of signing in hardware devices. * Once SIGN\_MODE\_TEXTUAL is shipped, SIGN\_MODE\_LEGACY\_AMINO\_JSON can be deprecated and removed. On the longer term, once the ecosystem has totally migrated, Amino can be totally removed. ### Negative * Some fields are still encoded in non-human-readable ways, such as public keys in hexadecimal. * New ledger app needs to be released, still unclear ### Neutral * If the transaction is complex, the string array can be arbitrarily long, and some users might just skip some screens and blind sign. ## Further Discussions * Some details on value renderers need to be polished, see [Annex 1](/sdk/latest/reference/architecture/adr-050-sign-mode-textual-annex1). * Are ledger apps able to support both SIGN\_MODE\_LEGACY\_AMINO\_JSON and SIGN\_MODE\_TEXTUAL at the same time? * Open question: should we add a Protobuf field option to allow app developers to overwrite the textual representation of certain Protobuf fields and message? This would be similar to Ethereum's [EIP4430](https://github.com/ethereum/EIPs/pull/4430), where the contract developer decides on the textual representation. * Internationalization. ## References * [Annex 1](/sdk/latest/reference/architecture/adr-050-sign-mode-textual-annex1) * Initial discussion: [Link](https://github.com/cosmos/cosmos-sdk/issues/6513) * Living document used in the working group: [Link](https://hackmd.io/fsZAO-TfT0CKmLDtfMcKeA?both) * Working group meeting notes: [Link](https://hackmd.io/7RkGfv_rQAaZzEigUYhcXw) * Ethereum's "Described Transactions" [Link](https://github.com/ethereum/EIPs/pull/4430) # ADR 050: SIGN_MODE_TEXTUAL: Annex 1 Value Renderers Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-050-sign-mode-textual-annex1 ## Changelog * Dec 06, 2021: Initial Draft * Feb 07, 2022: Draft read and concept-ACKed by the Ledger team. * Dec 01, 2022: Remove `Object: ` prefix on Any header screen. * Dec 13, 2022: Sign over bytes hash when bytes length > 32. * Mar 27, 2023: Update `Any` value renderer to omit message header screen. ## Status Archived. `SIGN_MODE_TEXTUAL` was removed in Cosmos SDK v0.55, and the proto enum value is reserved. This ADR is retained for historical reference. ## Abstract This Annex describes value renderers, which are used for displaying Protobuf values in a human-friendly way using a string array. ## Value Renderers Value Renderers describe how values of different Protobuf types should be encoded as a string array. Value renderers can be formalized as a set of bijective functions `func renderT(value T) []string`, where `T` is one of the below Protobuf types for which this spec is defined. ### Protobuf `number` * Applies to: * protobuf numeric integer types (`int{32,64}`, `uint{32,64}`, `sint{32,64}`, `fixed{32,64}`, `sfixed{32,64}`) * strings whose `customtype` is `github.com/cosmos/cosmos-sdk/types.Int` or `github.com/cosmos/cosmos-sdk/types.Dec` * bytes whose `customtype` is `github.com/cosmos/cosmos-sdk/types.Int` or `github.com/cosmos/cosmos-sdk/types.Dec` * Trailing decimal zeroes are always removed * Formatting with `'`s for every three integral digits. * Usage of `.` to denote the decimal delimiter. #### Examples * `1000` (uint64) -> `1'000` * `"1000000.00"` (string representing a Dec) -> `1'000'000` * `"1000000.10"` (string representing a Dec) -> `1'000'000.1` ### `coin` * Applies to `cosmos.base.v1beta1.Coin`. * Denoms are converted to `display` denoms using `Metadata` (if available). **This requires a state query**. The definition of `Metadata` can be found in the [bank protobuf definition](https://buf.build/cosmos/cosmos-sdk/docs/main:cosmos.bank.v1beta1#cosmos.bank.v1beta1.Metadata). If the `display` field is empty or nil, then we do not perform any denom conversion. * Amounts are converted to `display` denom amounts and rendered as `number`s above * We do not change the capitalization of the denom. In practice, `display` denoms are stored in lowercase in state (e.g. `10 atom`), however they are often showed in UPPERCASE in everyday life (e.g. `10 ATOM`). Value renderers keep the case used in state, but we may recommend chains changing the denom metadata to be uppercase for better user display. * One space between the denom and amount (e.g. `10 atom`). * In the future, IBC denoms could maybe be converted to DID/IIDs, if we can find a robust way for doing this (ex. `cosmos:cosmos:hub:bank:denom:atom`) #### Examples * `1000000000uatom` -> `["1'000 atom"]`, because atom is the metadata's display denom. ### `coins` * an array of `coin` is display as the concatenation of each `coin` encoded as the specification above, the joined together with the delimiter `", "` (a comma and a space, no quotes around). * the list of coins is ordered by unicode code point of the display denom: `A-Z` \< `a-z`. For example, the string `aAbBcC` would be sorted `ABCabc`. * if the coins list had 0 items in it then it'll be rendered as `zero` ### Example * `["3cosm", "2000000uatom"]` -> `2 atom, 3 COSM` (assuming the display denoms are `atom` and `COSM`) * `["10atom", "20Acoin"]` -> `20 Acoin, 10 atom` (assuming the display denoms are `atom` and `Acoin`) * `[]` -> `zero` ### `repeated` * Applies to all `repeated` fields, except `cosmos.tx.v1beta1.TxBody#Messages`, which has a particular encoding (see [ADR-050](/sdk/latest/reference/architecture/adr-050-sign-mode-textual)). * A repeated type has the following template: ``` : (/): (/): End of . ``` where: * `field_name` is the Protobuf field name of the repeated field * `field_kind`: * if the type of the repeated field is a message, `field_kind` is the message name * if the type of the repeated field is an enum, `field_kind` is the enum name * in any other case, `field_kind` is the protobuf primitive type (e.g. "string" or "bytes") * `int` is the length of the array * `index` is one based index of the repeated field #### Examples Given the proto definition: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message AllowedMsgAllowance { repeated string allowed_messages = 1; } ``` and initializing with: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} x := []AllowedMsgAllowance{"cosmos.bank.v1beta1.MsgSend", "cosmos.gov.v1.MsgVote" } ``` we have the following value-rendered encoding: ``` Allowed messages: 2 strings Allowed messages (1/2): cosmos.bank.v1beta1.MsgSend Allowed messages (2/2): cosmos.gov.v1.MsgVote End of Allowed messages ``` ### `message` * Applies to all Protobuf messages that do not have a custom encoding. * Field names follow [sentence case](https://en.wiktionary.org/wiki/sentence_case) * replace each `_` with a space * capitalize first letter of the sentence * Field names are ordered by their Protobuf field number * Screen title is the field name, and screen content is the value. * Nesting: * if a field contains a nested message, we value-render the underlying message using the template: ``` : <1st line of value-rendered message> > // Notice the `>` prefix. ``` * `>` character is used to denote nesting. For each additional level of nesting, add `>`. #### Examples Given the following Protobuf messages: ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} enum VoteOption { VOTE_OPTION_UNSPECIFIED = 0; VOTE_OPTION_YES = 1; VOTE_OPTION_ABSTAIN = 2; VOTE_OPTION_NO = 3; VOTE_OPTION_NO_WITH_VETO = 4; } message WeightedVoteOption { VoteOption option = 1; string weight = 2 [(cosmos_proto.scalar) = "cosmos.Dec"]; } message Vote { uint64 proposal_id = 1; string voter = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; reserved 3; repeated WeightedVoteOption options = 4; } ``` we get the following encoding for the `Vote` message: ``` Vote object > Proposal id: 4 > Voter: cosmos1abc...def > Options: 2 WeightedVoteOptions > Options (1/2): WeightedVoteOption object >> Option: VOTE_OPTION_YES >> Weight: 0.7 > Options (2/2): WeightedVoteOption object >> Option: VOTE_OPTION_NO >> Weight: 0.3 > End of Options ``` ### Enums * Show the enum variant name as string. #### Examples See example above with `message Vote{}`. ### `google.protobuf.Any` * Applies to `google.protobuf.Any` * Rendered as: ``` > ``` There is however one exception: when the underlying message is a Protobuf message that does not have a custom encoding, then the message header screen is omitted, and one level of indentation is removed. Messages that have a custom encoding, including `google.protobuf.Timestamp`, `google.protobuf.Duration`, `google.protobuf.Any`, `cosmos.base.v1beta1.Coin`, and messages that have an app-defined custom encoding, will preserve their header and indentation level. #### Examples Message header screen is stripped, one-level of indentation removed: ``` /cosmos.gov.v1.Vote > Proposal id: 4 > Vote: cosmos1abc...def > Options: 2 WeightedVoteOptions > Options (1/2): WeightedVoteOption object >> Option: Yes >> Weight: 0.7 > Options (2/2): WeightedVoteOption object >> Option: No >> Weight: 0.3 > End of Options ``` Message with custom encoding: ``` /cosmos.base.v1beta1.Coin > 10uatom ``` ### `google.protobuf.Timestamp` Rendered using [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) (a simplification of ISO 8601), which is the current recommendation for portable time values. The rendering always uses "Z" (UTC) as the timezone. It uses only the necessary fractional digits of a second, omitting the fractional part entirely if the timestamp has no fractional seconds. (The resulting timestamps are not automatically sortable by standard lexicographic order, but we favor the legibility of the shorter string.) #### Examples The timestamp with 1136214245 seconds and 700000000 nanoseconds is rendered as `2006-01-02T15:04:05.7Z`. The timestamp with 1136214245 seconds and zero nanoseconds is rendered as `2006-01-02T15:04:05Z`. ### `google.protobuf.Duration` The duration proto expresses a raw number of seconds and nanoseconds. This will be rendered as longer time units of days, hours, and minutes, plus any remaining seconds, in that order. Leading and trailing zero-quantity units will be omitted, but all units in between nonzero units will be shown, e.g. ` 3 days, 0 hours, 0 minutes, 5 seconds`. Even longer time units such as months or years are imprecise. Weeks are precise, but not commonly used - `91 days` is more immediately legible than `13 weeks`. Although `days` can be problematic, e.g. noon to noon on subsequent days can be 23 or 25 hours depending on daylight savings transitions, there is significant advantage in using strict 24-hour days over using only hours (e.g. `91 days` vs `2184 hours`). When nanoseconds are nonzero, they will be shown as fractional seconds, with only the minimum number of digits, e.g `0.5 seconds`. A duration of exactly zero is shown as `0 seconds`. Units will be given as singular (no trailing `s`) when the quantity is exactly one, and will be shown in plural otherwise. Negative durations will be indicated with a leading minus sign (`-`). Examples: * `1 day` * `30 days` * `-1 day, 12 hours` * `3 hours, 0 minutes, 53.025 seconds` ### bytes * Bytes of length shorter or equal to 35 are rendered in hexadecimal, all capital letters, without the `0x` prefix. * Bytes of length greater than 35 are hashed using SHA256. The rendered text is `SHA-256=`, followed by the 32-byte hash, in hexadecimal, all capital letters, without the `0x` prefix. * The hexadecimal string is finally separated into groups of 4 digits, with a space `' '` as separator. If the bytes length is odd, the 2 remaining hexadecimal characters are at the end. The number 35 was chosen because it is the longest length where the hashed-and-prefixed representation is longer than the original data directly formatted, using the 3 rules above. More specifically: * a 35-byte array will have 70 hex characters, plus 17 space characters, resulting in 87 characters. * byte arrays starting from length 36 will be be hashed to 32 bytes, which is 64 hex characters plus 15 spaces, and with the `SHA-256=` prefix, it takes 87 characters. Also, secp256k1 public keys have length 33, so their Textual representation is not their hashed value, which we would like to avoid. Note: Data longer than 35 bytes are not rendered in a way that can be inverted. See ADR-050's [section about invertibility](/sdk/latest/reference/architecture/adr-050-sign-mode-textual#invertible-rendering) for a discussion. #### Examples Inputs are displayed as byte arrays. * `[0]`: `00` * `[0,1,2]`: `0001 02` * `[0,1,2,..,34]`: `0001 0203 0405 0607 0809 0A0B 0C0D 0E0F 1011 1213 1415 1617 1819 1A1B 1C1D 1E1F 2021 22` * `[0,1,2,..,35]`: `SHA-256=5D7E 2D9B 1DCB C85E 7C89 0036 A2CF 2F9F E7B6 6554 F2DF 08CE C6AA 9C0A 25C9 9C21` ### address bytes We currently use `string` types in protobuf for addresses so this may not be needed, but if any address bytes are used in sign mode textual they should be rendered with bech32 formatting ### strings Strings are rendered as-is. ### Default Values * Default Protobuf values for each field are skipped. #### Example ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message TestData { string signer = 1; string metadata = 2; } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} myTestData := TestData{ Signer: "cosmos1abc" } ``` We get the following encoding for the `TestData` message: ``` TestData object > Signer: cosmos1abc ``` ### bool Boolean values are rendered as `True` or `False`. ### \[ABANDONED] Custom `msg_title` instead of Msg `type_url` *This paragraph is in the Annex for informational purposes only, and will be removed in a next update of the ADR.* * all protobuf messages to be used with `SIGN_MODE_TEXTUAL` CAN have a short title associated with them that can be used in format strings whenever the type URL is explicitly referenced via the `cosmos.msg.v1.textual.msg_title` Protobuf message option. * if this option is not specified for a Msg, then the Protobuf fully qualified name will be used. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message MsgSend { option (cosmos.msg.v1.textual.msg_title) = "bank send coins"; } ``` * they MUST be unique per message, per chain #### Examples * `cosmos.gov.v1.MsgVote` -> `governance v1 vote` #### Best Pratices We recommend to use this option only for `Msg`s whose Protobuf fully qualified name can be hard to understand. As such, the two examples above (`MsgSend` and `MsgVote`) are not good examples to be used with `msg_title`. We still allow `msg_title` for chains who might have `Msg`s with complex or non-obvious names. In those cases, we recommend to drop the version (e.g. `v1`) in the string if there's only one version of the module on chain. This way, the bijective mapping can figure out which message each string corresponds to. If multiple Protobuf versions of the same module exist on the same chain, we recommend keeping the first `msg_title` with version, and the second `msg_title` with version (e.g. `v2`): * `mychain.mymodule.v1.MsgDo` -> `mymodule do something` * `mychain.mymodule.v2.MsgDo` -> `mymodule v2 do something` # ADR 050: SIGN_MODE_TEXTUAL: Annex 2 Device Rendering Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-050-sign-mode-textual-annex2 Normative guidance on how hardware devices should render a SIGN_MODE_TEXTUAL document. ## Changelog * Oct 3, 2022: Initial Draft ## Status Archived. `SIGN_MODE_TEXTUAL` was removed in Cosmos SDK v0.55, and the proto enum value is reserved. This ADR is retained for historical reference. ## Abstract This annex provides normative guidance on how devices should render a `SIGN_MODE_TEXTUAL` document. ## Context `SIGN_MODE_TEXTUAL` allows a legible version of a transaction to be signed on a hardware security device, such as a Ledger. Early versions of the design rendered transactions directly to lines of ASCII text, but this proved awkward from its in-band signaling, and for the need to display Unicode text within the transaction. ## Decision `SIGN_MODE_TEXTUAL` renders to an abstract representation, leaving it up to device-specific software how to present this representation given the capabilities, limitations, and conventions of the deivce. We offer the following normative guidance: 1. The presentation should be as legible as possible to the user, given the capabilities of the device. If legibility could be sacrificed for other properties, we would recommend just using some other signing mode. Legibility should focus on the common case - it is okay for unusual cases to be less legible. 2. The presentation should be invertible if possible without substantial sacrifice of legibility. Any change to the rendered data should result in a visible change to the presentation. This extends the integrity of the signing to user-visible presentation. 3. The presentation should follow normal conventions of the device, without sacrificing legibility or invertibility. As an illustration of these principles, here is an example algorithm for presentation on a device which can display a single 80-character line of printable ASCII characters: * The presentation is broken into lines, and each line is presented in sequence, with user controls for going forward or backward a line. * Expert mode screens are only presented if the device is in expert mode. * Each line of the screen starts with a number of `>` characters equal to the screen's indentation level, followed by a `+` character if this isn't the first line of the screen, followed by a space if either a `>` or a `+` has been emitted, or if this header is followed by a `>`, `+`, or space. * If the line ends with whitespace or an `@` character, an additional `@` character is appended to the line. * The following ASCII control characters or backslash (`\`) are converted to a backslash followed by a letter code, in the manner of string literals in many languages: * a: U+0007 alert or bell * b: U+0008 backspace * f: U+000C form feed * n: U+000A line feed * r: U+000D carriage return * t: U+0009 horizontal tab * v: U+000B vertical tab * `\`: U+005C backslash * All other ASCII control characters, plus non-ASCII Unicode code points, are shown as either: * `\u` followed by 4 uppercase hex chacters for code points in the basic multilingual plane (BMP). * `\U` followed by 8 uppercase hex characters for other code points. * The screen will be broken into multiple lines to fit the 80-character limit, considering the above transformations in a way that attempts to minimize the number of lines generated. Expanded control or Unicode characters are never split across lines. Example output: ``` An introductory line. key1: 123456 key2: a string that ends in whitespace @ key3: a string that ends in a single ampersand - @@ >tricky key4<: note the leading space in the presentation introducing an aggregate > key5: false > key6: a very long line of text, please co\u00F6perate and break into >+ multiple lines. > Can we do further nesting? >> You bet we can! ``` The inverse mapping gives us the only input which could have generated this output (JSON notation for string data): ``` Indent Text ------ ---- 0 "An introductory line." 0 "key1: 123456" 0 "key2: a string that ends in whitespace " 0 "key3: a string that ends in a single ampersand - @" 0 ">tricky key4<: note the leading space in the presentation" 0 "introducing an aggregate" 1 "key5: false" 1 "key6: a very long line of text, please coöperate and break into multiple lines." 1 "Can we do further nesting?" 2 "You bet we can!" ``` # ADR 053: Go Module Refactoring Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-053-go-module-refactoring 2022-04-27: First Draft ## Changelog * 2022-04-27: First Draft ## Status PROPOSED ## Abstract The current SDK is built as a single monolithic go module. This ADR describes how we refactor the SDK into smaller independently versioned go modules for ease of maintenance. ## Context Go modules impose certain requirements on software projects with respect to stable version numbers (anything above 0.x) in that [any API breaking changes necessitate a major version](https://go.dev/doc/modules/release-workflow#breaking) increase which technically creates a new go module (with a v2, v3, etc. suffix). [Keeping modules API compatible](https://go.dev/blog/module-compatibility) in this way requires a fair amount of fair thought and discipline. The Cosmos SDK is a fairly large project which originated before go modules came into existence and has always been under a v0.x release even though it has been used in production for years now, not because it isn't production quality software, but rather because the API compatibility guarantees required by go modules are fairly complex to adhere to with such a large project. Up to now, it has generally been deemed more important to be able to break the API if needed rather than require all users update all package import paths to accommodate breaking changes causing v2, v3, etc. releases. This is in addition to the other complexities related to protobuf generated code that will be addressed in a separate ADR. Nevertheless, the desire for semantic versioning has been [strong in the community](https://github.com/cosmos/cosmos-sdk/discussions/10162) and the single go module release process has made it very hard to release small changes to isolated features in a timely manner. Release cycles often exceed six months which means small improvements done in a day or two get bottle-necked by everything else in the monolithic release cycle. ## Decision To improve the current situation, the SDK is being refactored into multiple go modules within the current repository. There has been a [fair amount of debate](https://github.com/cosmos/cosmos-sdk/discussions/10582#discussioncomment-1813377) as to how to do this, with some developers arguing for larger vs smaller module scopes. There are pros and cons to both approaches (which will be discussed below in the [Consequences](#consequences) section), but the approach being adopted is the following: * a go module should generally be scoped to a specific coherent set of functionality (such as math, errors, store, etc.) * when code is removed from the core SDK and moved to a new module path, every effort should be made to avoid API breaking changes in the existing code using aliases and wrapper types (as done in [Link](https://github.com/cosmos/cosmos-sdk/pull/10779) and [Link](https://github.com/cosmos/cosmos-sdk/pull/11788)) * new go modules should be moved to a standalone domain (`cosmossdk.io`) before being tagged as `v1.0.0` to accommodate the possibility that they may be better served by a standalone repository in the future * all go modules should follow the guidelines in [Link](https://go.dev/blog/module-compatibility) before `v1.0.0` is tagged and should make use of `internal` packages to limit the exposed API surface * the new go module's API may deviate from the existing code where there are clear improvements to be made or to remove legacy dependencies (for instance on amino or gogo proto), as long the old package attempts to avoid API breakage with aliases and wrappers * care should be taken when simply trying to turn an existing package into a new go module: [Link](https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository). In general, it seems safer to just create a new module path (appending v2, v3, etc. if necessary), rather than trying to make an old package a new module. ## Consequences ### Backwards Compatibility If the above guidelines are followed to use aliases or wrapper types pointing in existing APIs that point back to the new go modules, there should be no or very limited breaking changes to existing APIs. ### Positive * standalone pieces of software will reach `v1.0.0` sooner * new features to specific functionality will be released sooner ### Negative * there will be more go module versions to update in the SDK itself and per-project, although most of these will hopefully be indirect ### Neutral ## Further Discussions Further discussions are occurring in primarily in [Link](https://github.com/cosmos/cosmos-sdk/discussions/10582) and within the Cosmos SDK Framework Working Group. ## References * [Link](https://go.dev/doc/modules/release-workflow) * [Link](https://go.dev/blog/module-compatibility) * [Link](https://github.com/cosmos/cosmos-sdk/discussions/10162) * [Link](https://github.com/cosmos/cosmos-sdk/discussions/10582) * [Link](https://github.com/cosmos/cosmos-sdk/pull/10779) * [Link](https://github.com/cosmos/cosmos-sdk/pull/11788) # ADR 054: Semver Compatible SDK Modules Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-054-semver-compatible-modules 2022-04-27: First draft ## Changelog * 2022-04-27: First draft ## Status DRAFT ## Abstract In order to move the Cosmos SDK to a system of decoupled semantically versioned modules which can be composed in different combinations (ex. staking v3 with bank v1 and distribution v2), we need to reassess how we organize the API surface of modules to avoid problems with go semantic import versioning and circular dependencies. This ADR explores various approaches we can take to addressing these issues. ## Context There has been [a fair amount of desire](https://github.com/cosmos/cosmos-sdk/discussions/10162) in the community for semantic versioning in the SDK and there has been significant movement to splitting SDK modules into [standalone go modules](https://github.com/cosmos/cosmos-sdk/issues/11899). Both of these will ideally allow the ecosystem to move faster because we won't be waiting for all dependencies to update synchronously. For instance, we could have 3 versions of the core SDK compatible with the latest 2 releases of CosmWasm as well as 4 different versions of staking . This sort of setup would allow early adopters to aggressively integrate new versions, while allowing more conservative users to be selective about which versions they're ready for. In order to achieve this, we need to solve the following problems: 1. because of the way [go semantic import versioning](https://research.swtch.com/vgo-import) (SIV) works, moving to SIV naively will actually make it harder to achieve these goals 2. circular dependencies between modules need to be broken to actually release many modules in the SDK independently 3. pernicious minor version incompatibilities introduced through correctly [evolving protobuf schemas](https://developers.google.com/protocol-buffers/docs/proto3#updating) without correct [unknown field filtering](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#unknown-field-filtering) Note that all the following discussion assumes that the proto file versioning and state machine versioning of a module are distinct in that: * proto files are maintained in a non-breaking way (using something like [buf breaking](https://docs.buf.build/breaking/overview) to ensure all changes are backwards compatible) * proto file versions get bumped much less frequently, i.e. we might maintain `cosmos.bank.v1` through many versions of the bank module state machine * state machine breaking changes are more common and ideally this is what we'd want to semantically version with go modules, ex. `x/bank/v2`, `x/bank/v3`, etc. ### Problem 1: Semantic Import Versioning Compatibility Consider we have a module `foo` which defines the following `MsgDoSomething` and that we've released its state machine in go module `example.com/foo`: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package foo.v1; message MsgDoSomething { string sender = 1; uint64 amount = 2; } service Msg { DoSomething(MsgDoSomething) returns (MsgDoSomethingResponse); } ``` Now consider that we make a revision to this module and add a new `condition` field to `MsgDoSomething` and also add a new validation rule on `amount` requiring it to be non-zero, and that following go semantic versioning we release the next state machine version of `foo` as `example.com/foo/v2`. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Revision 1 package foo.v1; message MsgDoSomething { string sender = 1; // amount must be a non-zero integer. uint64 amount = 2; // condition is an optional condition on doing the thing. // // Since: Revision 1 Condition condition = 3; } ``` Approaching this naively, we would generate the protobuf types for the initial version of `foo` in `example.com/foo/types` and we would generate the protobuf types for the second version in `example.com/foo/v2/types`. Now let's say we have a module `bar` which talks to `foo` using this keeper interface which `foo` provides: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type FooKeeper interface { DoSomething(MsgDoSomething) error } ``` #### Scenario A: Backward Compatibility: Newer Foo, Older Bar Imagine we have a chain which uses both `foo` and `bar` and wants to upgrade to `foo/v2`, but the `bar` module has not upgraded to `foo/v2`. In this case, the chain will not be able to upgrade to `foo/v2` until `bar` has upgraded its references to `example.com/foo/types.MsgDoSomething` to `example.com/foo/v2/types.MsgDoSomething`. Even if `bar`'s usage of `MsgDoSomething` has not changed at all, the upgrade will be impossible without this change because `example.com/foo/types.MsgDoSomething` and `example.com/foo/v2/types.MsgDoSomething` are fundamentally different incompatible structs in the go type system. #### Scenario B: Forward Compatibility: Older Foo, Newer Bar Now let's consider the reverse scenario, where `bar` upgrades to `foo/v2` by changing the `MsgDoSomething` reference to `example.com/foo/v2/types.MsgDoSomething` and releases that as `bar/v2` with some other changes that a chain wants. The chain, however, has decided that it thinks the changes in `foo/v2` are too risky and that it'd prefer to stay on the initial version of `foo`. In this scenario, it is impossible to upgrade to `bar/v2` without upgrading to `foo/v2` even if `bar/v2` would have worked 100% fine with `foo` other than changing the import path to `MsgDoSomething` (meaning that `bar/v2` doesn't actually use any new features of `foo/v2`). Now because of the way go semantic import versioning works, we are locked into either using `foo` and `bar` OR `foo/v2` and `bar/v2`. We cannot have `foo` + `bar/v2` OR `foo/v2` + `bar`. The go type system doesn't allow this even if both versions of these modules are otherwise compatible with each other. #### Naive Mitigation A naive approach to fixing this would be to not regenerate the protobuf types in `example.com/foo/v2/types` but instead just update `example.com/foo/types` to reflect the changes needed for `v2` (adding `condition` and requiring `amount` to be non-zero). Then we could release a patch of `example.com/foo/types` with this update and use that for `foo/v2`. But this change is state machine breaking for `v1`. It requires changing the `ValidateBasic` method to reject the case where `amount` is zero, and it adds the `condition` field which should be rejected based on [ADR 020 unknown field filtering](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#unknown-field-filtering). So adding these changes as a patch on `v1` is actually incorrect based on semantic versioning. Chains that want to stay on `v1` of `foo` should not be importing these changes because they are incorrect for `v1.` ### Problem 2: Circular dependencies None of the above approaches allow `foo` and `bar` to be separate modules if for some reason `foo` and `bar` depend on each other in different ways. For instance, we can't have `foo` import `bar/types` while `bar` imports `foo/types`. We have several cases of circular module dependencies in the SDK (ex. staking, distribution and slashing) that are legitimate from a state machine perspective. Without separating the API types out somehow, there would be no way to independently semantically version these modules without some other mitigation. ### Problem 3: Handling Minor Version Incompatibilities Imagine that we solve the first two problems but now have a scenario where `bar/v2` wants the option to use `MsgDoSomething.condition` which only `foo/v2` supports. If `bar/v2` works with `foo` `v1` and sets `condition` to some non-nil value, then `foo` will silently ignore this field resulting in a silent logic possibly dangerous logic error. If `bar/v2` were able to check whether `foo` was on `v1` or `v2` and dynamically, it could choose to only use `condition` when `foo/v2` is available. Even if `bar/v2` were able to perform this check, however, how do we know that it is always performing the check properly. Without some sort of framework-level [unknown field filtering](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#unknown-field-filtering), it is hard to know whether these pernicious hard to detect bugs are getting into our app and a client-server layer such as [ADR 033: Inter-Module Communication](/sdk/v0.50/build/architecture/adr-033-protobuf-inter-module-comm) may be needed to do this. ## Solutions ### Approach A) Separate API and State Machine Modules One solution (first proposed in [Link](https://github.com/cosmos/cosmos-sdk/discussions/10582)) is to isolate all protobuf generated code into a separate module from the state machine module. This would mean that we could have state machine go modules `foo` and `foo/v2` which could use a types or API go module say `foo/api`. This `foo/api` go module would be perpetually on `v1.x` and only accept non-breaking changes. This would then allow other modules to be compatible with either `foo` or `foo/v2` as long as the inter-module API only depends on the types in `foo/api`. It would also allow modules `foo` and `bar` to depend on each other in that both of them could depend on `foo/api` and `bar/api` without `foo` directly depending on `bar` and vice versa. This is similar to the naive mitigation described above except that it separates the types into separate go modules which in and of itself could be used to break circular module dependencies. It has the same problems as the naive solution, otherwise, which we could rectify by: 1. removing all state machine breaking code from the API module (ex. `ValidateBasic` and any other interface methods) 2. embedding the correct file descriptors for unknown field filtering in the binary #### Migrate all interface methods on API types to handlers To solve 1), we need to remove all interface implementations from generated types and instead use a handler approach which essentially means that given a type `X`, we have some sort of resolver which allows us to resolve interface implementations for that type (ex. `sdk.Msg` or `authz.Authorization`). For example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) DoSomething(msg MsgDoSomething) error { var validateBasicHandler ValidateBasicHandler err := k.resolver.Resolve(&validateBasic, msg) if err != nil { return err } err = validateBasicHandler.ValidateBasic() ... } ``` In the case of some methods on `sdk.Msg`, we could replace them with declarative annotations. For instance, `GetSigners` can already be replaced by the protobuf annotation `cosmos.msg.v1.signer`. In the future, we may consider some sort of protobuf validation framework (like [Link](https://github.com/bufbuild/protoc-gen-validate) but more Cosmos-specific) to replace `ValidateBasic`. #### Pinned FileDescriptor's To solve 2), state machine modules must be able to specify what the version of the protobuf files was that they were built against. For instance if the API module for `foo` upgrades to `foo/v2`, the original `foo` module still needs a copy of the original protobuf files it was built with so that ADR 020 unknown field filtering will reject `MsgDoSomething` when `condition` is set. The simplest way to do this may be to embed the protobuf `FileDescriptor`s into the module itself so that these `FileDescriptor`s are used at runtime rather than the ones that are built into the `foo/api` which may be different. Using [buf build](https://docs.buf.build/build/usage#output-format), [go embed](https://pkg.go.dev/embed), and a build script we can probably come up with a solution for embedding `FileDescriptor`s into modules that is fairly straightforward. #### Potential limitations to generated code One challenge with this approach is that it places heavy restrictions on what can go in API modules and requires that most of this is state machine breaking. All or most of the code in the API module would be generated from protobuf files, so we can probably control this with how code generation is done, but it is a risk to be aware of. For instance, we do code generation for the ORM that in the future could contain optimizations that are state machine breaking. We would either need to ensure very carefully that the optimizations aren't actually state machine breaking in generated code or separate this generated code out from the API module into the state machine module. Both of these mitigations are potentially viable but the API module approach does require an extra level of care to avoid these sorts of issues. #### Minor Version Incompatibilities This approach in and of itself does little to address any potential minor version incompatibilities and the requisite [unknown field filtering](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#unknown-field-filtering). Likely some sort of client-server routing layer which does this check such as [ADR 033: Inter-Module communication](/sdk/v0.50/build/architecture/adr-033-protobuf-inter-module-comm) is required to make sure that this is done properly. We could then allow modules to perform a runtime check given a `MsgClient`, ex: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) CallFoo() error { if k.interModuleClient.MinorRevision(k.fooMsgClient) >= 2 { k.fooMsgClient.DoSomething(&MsgDoSomething{ Condition: ... }) } else { ... } } ``` To do the unknown field filtering itself, the ADR 033 router would need to use the [protoreflect API](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect) to ensure that no fields unknown to the receiving module are set. This could result in an undesirable performance hit depending on how complex this logic is. ### Approach B) Changes to Generated Code An alternate approach to solving the versioning problem is to change how protobuf code is generated and move modules mostly or completely in the direction of inter-module communication as described in [ADR 033](/sdk/v0.50/build/architecture/adr-033-protobuf-inter-module-comm). In this paradigm, a module could generate all the types it needs internally - including the API types of other modules - and talk to other modules via a client-server boundary. For instance, if `bar` needs to talk to `foo`, it could generate its own version of `MsgDoSomething` as `bar/internal/foo/v1.MsgDoSomething` and just pass this to the inter-module router which would somehow convert it to the version which foo needs (ex. `foo/internal.MsgDoSomething`). Currently, two generated structs for the same protobuf type cannot exist in the same go binary without special build flags (see [Link](https://developers.google.com/protocol-buffers/docs/reference/go/faq#fix-namespace-conflict)). A relatively simple mitigation to this issue would be to set up the protobuf code to not register protobuf types globally if they are generated in an `internal/` package. This will require modules to register their types manually with the app-level level protobuf registry, this is similar to what modules already do with the `InterfaceRegistry` and amino codec. If modules *only* do ADR 033 message passing then a naive and non-performant solution for converting `bar/internal/foo/v1.MsgDoSomething` to `foo/internal.MsgDoSomething` would be marshaling and unmarshaling in the ADR 033 router. This would break down if we needed to expose protobuf types in `Keeper` interfaces because the whole point is to try to keep these types `internal/` so that we don't end up with all the import version incompatibilities we've described above. However, because of the issue with minor version incompatibilities and the need for [unknown field filtering](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#unknown-field-filtering), sticking with the `Keeper` paradigm instead of ADR 033 may be unviable to begin with. A more performant solution (that could maybe be adapted to work with `Keeper` interfaces) would be to only expose getters and setters for generated types and internally store data in memory buffers which could be passed from one implementation to another in a zero-copy way. For example, imagine this protobuf API with only getters and setters is exposed for `MsgSend`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgSend interface { proto.Message GetFromAddress() string GetToAddress() string GetAmount() []v1beta1.Coin SetFromAddress(string) SetToAddress(string) SetAmount([]v1beta1.Coin) } func NewMsgSend() MsgSend { return &msgSendImpl{ memoryBuffers: ... } } ``` Under the hood, `MsgSend` could be implemented based on some raw memory buffer in the same way that [Cap'n Proto](https://capnproto.org) and [FlatBuffers](https://google.github.io/flatbuffers/) so that we could convert between one version of `MsgSend` and another without serialization (i.e. zero-copy). This approach would have the added benefits of allowing zero-copy message passing to modules written in other languages such as Rust and accessed through a VM or FFI. It could also make unknown field filtering in inter-module communication simpler if we require that all new fields are added in sequential order, ex. just checking that no field `> 5` is set. Also, we wouldn't have any issues with state machine breaking code on generated types because all the generated code used in the state machine would actually live in the state machine module itself. Depending on how interface types and protobuf `Any`s are used in other languages, however, it may still be desirable to take the handler approach described in approach A. Either way, types implementing interfaces would still need to be registered with an `InterfaceRegistry` as they are now because there would be no way to retrieve them via the global registry. In order to simplify access to other modules using ADR 033, a public API module (maybe even one [remotely generated by Buf](https://docs.buf.build/bsr/remote-generation/go)) could be used by client modules instead of requiring to generate all client types internally. The big downsides of this approach are that it requires big changes to how people use protobuf types and would be a substantial rewrite of the protobuf code generator. This new generated code, however, could still be made compatible with the [`google.golang.org/protobuf/reflect/protoreflect`](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect) API in order to work with all standard golang protobuf tooling. It is possible that the naive approach of marshaling/unmarshaling in the ADR 033 router is an acceptable intermediate solution if the changes to the code generator are seen as too complex. However, since all modules would likely need to migrate to ADR 033 anyway with this approach, it might be better to do this all at once. ### Approach C) Don't address these issues If the above solutions are seen as too complex, we can also decide not to do anything explicit to enable better module version compatibility, and break circular dependencies. In this case, when developers are confronted with the issues described above they can require dependencies to update in sync (what we do now) or attempt some ad-hoc potentially hacky solution. One approach is to ditch go semantic import versioning (SIV) altogether. Some people have commented that go's SIV (i.e. changing the import path to `foo/v2`, `foo/v3`, etc.) is too restrictive and that it should be optional. The golang maintainers disagree and only officially support semantic import versioning. We could, however, take the contrarian perspective and get more flexibility by using 0.x-based versioning basically forever. Module version compatibility could then be achieved using go.mod replace directives to pin dependencies to specific compatible 0.x versions. For instance if we knew `foo` 0.2 and 0.3 were both compatible with `bar` 0.3 and 0.4, we could use replace directives in our go.mod to stick to the versions of `foo` and `bar` we want. This would work as long as the authors of `foo` and `bar` avoid incompatible breaking changes between these modules. Or, if developers choose to use semantic import versioning, they can attempt the naive solution described above and would also need to use special tags and replace directives to make sure that modules are pinned to the correct versions. Note, however, that all of these ad-hoc approaches, would be vulnerable to the minor version compatibility issues described above unless [unknown field filtering](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#unknown-field-filtering) is properly addressed. ### Approach D) Avoid protobuf generated code in public APIs An alternative approach would be to avoid protobuf generated code in public module APIs. This would help avoid the discrepancy between state machine versions and client API versions at the module to module boundaries. It would mean that we wouldn't do inter-module message passing based on ADR 033, but rather stick to the existing keeper approach and take it one step further by avoiding any protobuf generated code in the keeper interface methods. Using this approach, our `foo.Keeper.DoSomething` method wouldn't have the generated `MsgDoSomething` struct (which comes from the protobuf API), but instead positional parameters. Then in order for `foo/v2` to support the `foo/v1` keeper it would simply need to implement both the v1 and v2 keeper APIs. The `DoSomething` method in v2 could have the additional `condition` parameter, but this wouldn't be present in v1 at all so there would be no danger of a client accidentally setting this when it isn't available. So this approach would avoid the challenge around minor version incompatibilities because the existing module keeper API would not get new fields when they are added to protobuf files. Taking this approach, however, would likely require making all protobuf generated code internal in order to prevent it from leaking into the keeper API. This means we would still need to modify the protobuf code generator to not register `internal/` code with the global registry, and we would still need to manually register protobuf `FileDescriptor`s (this is probably true in all scenarios). It may, however, be possible to avoid needing to refactor interface methods on generated types to handlers. Also, this approach doesn't address what would be done in scenarios where modules still want to use the message router. Either way, we probably still want a way to pass messages from one module to another router safely even if it's just for use cases like `x/gov`, `x/authz`, CosmWasm, etc. That would still require most of the things outlined in approach (B), although we could advise modules to prefer keepers for communicating with other modules. The biggest downside of this approach is probably that it requires a strict refactoring of keeper interfaces to avoid generated code leaking into the API. This may result in cases where we need to duplicate types that are already defined in proto files and then write methods for converting between the golang and protobuf version. This may end up in a lot of unnecessary boilerplate and that may discourage modules from actually adopting it and achieving effective version compatibility. Approaches (A) and (B), although heavy handed initially, aim to provide a system which once adopted more or less gives the developer version compatibility for free with minimal boilerplate. Approach (D) may not be able to provide such a straightforward system since it requires a golang API to be defined alongside a protobuf API in a way that requires duplication and differing sets of design principles (protobuf APIs encourage additive changes while golang APIs would forbid it). Other downsides to this approach are: * no clear roadmap to supporting modules in other languages like Rust * doesn't get us any closer to proper object capability security (one of the goals of ADR 033) * ADR 033 needs to be done properly anyway for the set of use cases which do need it ## Decision The latest **DRAFT** proposal is: 1. we are alignment on adopting [ADR 033](/sdk/v0.50/build/architecture/adr-033-protobuf-inter-module-comm) not just as an addition to the framework, but as a core replacement to the keeper paradigm entirely. 2. the ADR 033 inter-module router will accommodate any variation of approach (A) or (B) given the following rules: a. if the client type is the same as the server type then pass it directly through, b. if both client and server use the zero-copy generated code wrappers (which still need to be defined), then pass the memory buffers from one wrapper to the other, or c. marshal/unmarshal types between client and server. This approach will allow for both maximal correctness and enable a clear path to enabling modules within in other languages, possibly executed within a WASM VM. ### Minor API Revisions To declare minor API revisions of proto files, we propose the following guidelines (which were already documented in [cosmos.app.v1alpha module options](https://github.com/cosmos/cosmos-sdk/blob/v0.50.10/proto/cosmos/app/v1alpha1/module.proto)): * proto packages which are revised from their initial version (considered revision `0`) should include a `package` * comment in some .proto file containing the test `Revision N` at the start of a comment line where `N` is the current revision number. * all fields, messages, etc. added in a version beyond the initial revision should add a comment at the start of a comment line of the form `Since: Revision N` where `N` is the non-zero revision it was added. It is advised that there is a 1:1 correspondence between a state machine module and versioned set of proto files which are versioned either as a buf module a go API module or both. If the buf schema registry is used, the version of this buf module should always be `1.N` where `N` corresponds to the package revision. Patch releases should be used when only documentation comments are updated. It is okay to include proto packages named `v2`, `v3`, etc. in this same `1.N` versioned buf module (ex. `cosmos.bank.v2`) as long as all these proto packages consist of a single API intended to be served by a single SDK module. ### Introspecting Minor API Revisions In order for modules to introspect the minor API revision of peer modules, we propose adding the following method to `cosmossdk.io/core/intermodule.Client`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ServiceRevision(ctx context.Context, serviceName string) uint64 ``` Modules could all this using the service name statically generated by the go grpc code generator: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} intermoduleClient.ServiceRevision(ctx, bankv1beta1.Msg_ServiceDesc.ServiceName) ``` In the future, we may decide to extend the code generator used for protobuf services to add a field to client types which does this check more concisely, ex: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package bankv1beta1 type MsgClient interface { Send(context.Context, MsgSend) (MsgSendResponse, error) ServiceRevision(context.Context) uint64 } ``` ### Unknown Field Filtering To correctly perform [unknown field filtering](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding#unknown-field-filtering), the inter-module router can do one of the following: * use the `protoreflect` API for messages which support that * for gogo proto messages, marshal and use the existing `codec/unknownproto` code * for zero-copy messages, do a simple check on the highest set field number (assuming we can require that fields are adding consecutively in increasing order) ### `FileDescriptor` Registration Because a single go binary may contain different versions of the same generated protobuf code, we cannot rely on the global protobuf registry to contain the correct `FileDescriptor`s. Because `appconfig` module configuration is itself written in protobuf, we would like to load the `FileDescriptor`s for a module before loading a module itself. So we will provide ways to register `FileDescriptor`s at module registration time before instantiation. We propose the following `cosmossdk.io/core/appmodule.Option` constructors for the various cases of how `FileDescriptor`s may be packaged: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package appmodule // this can be used when we are using google.golang.org/protobuf compatible generated code // Ex: // ProtoFiles(bankv1beta1.File_cosmos_bank_v1beta1_module_proto) func ProtoFiles(file []protoreflect.FileDescriptor) Option { } // this can be used when we are using gogo proto generated code. func GzippedProtoFiles(file [][]byte) Option { } // this can be used when we are using buf build to generated a pinned file descriptor func ProtoImage(protoImage []byte) Option { } ``` This approach allows us to support several ways protobuf files might be generated: * proto files generated internally to a module (use `ProtoFiles`) * the API module approach with pinned file descriptors (use `ProtoImage`) * gogo proto (use `GzippedProtoFiles`) ### Module Dependency Declaration One risk of ADR 033 is that dependencies are called at runtime which are not present in the loaded set of SDK modules.\ Also we want modules to have a way to define a minimum dependency API revision that they require. Therefore, all modules should declare their set of dependencies upfront. These dependencies could be defined when a module is instantiated, but ideally we know what the dependencies are before instantiation and can statically look at an app config and determine whether the set of modules. For example, if `bar` requires `foo` revision `>= 1`, then we should be able to know this when creating an app config with two versions of `bar` and `foo`. We propose defining these dependencies in the proto options of the module config object itself. ### Interface Registration We will also need to define how interface methods are defined on types that are serialized as `google.protobuf.Any`'s. In light of the desire to support modules in other languages, we may want to think of solutions that will accommodate other languages such as plugins described briefly in [ADR 033](/sdk/v0.50/build/architecture/adr-033-protobuf-inter-module-comm#internal-methods). ### Testing In order to ensure that modules are indeed with multiple versions of their dependencies, we plan to provide specialized unit and integration testing infrastructure that automatically tests multiple versions of dependencies. #### Unit Testing Unit tests should be conducted inside SDK modules by mocking their dependencies. In a full ADR 033 scenario, this means that all interaction with other modules is done via the inter-module router, so mocking of dependencies means mocking their msg and query server implementations. We will provide both a test runner and fixture to make this streamlined. The key thing that the test runner should do to test compatibility is to test all combinations of dependency API revisions. This can be done by taking the file descriptors for the dependencies, parsing their comments to determine the revisions various elements were added, and then created synthetic file descriptors for each revision by subtracting elements that were added later. Here is a proposed API for the unit test runner and fixture: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package moduletesting import ( "context" "testing" "cosmossdk.io/core/intermodule" "cosmossdk.io/depinject" "google.golang.org/grpc" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" ) type TestFixture interface { context.Context intermodule.Client // for making calls to the module we're testing BeginBlock() EndBlock() } type UnitTestFixture interface { TestFixture grpc.ServiceRegistrar // for registering mock service implementations } type UnitTestConfig struct { ModuleConfig proto.Message // the module's config object DepinjectConfig depinject.Config // optional additional depinject config options DependencyFileDescriptors []protodesc.FileDescriptorProto // optional dependency file descriptors to use instead of the global registry } // Run runs the test function for all combinations of dependency API revisions. func (cfg UnitTestConfig) Run(t *testing.T, f func(t *testing.T, f UnitTestFixture)) { // ... } ``` Here is an example for testing bar calling foo which takes advantage of conditional service revisions in the expected mock arguments: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func TestBar(t *testing.T) { UnitTestConfig{ ModuleConfig: &foomodulev1.Module{ }}.Run(t, func (t *testing.T, f moduletesting.UnitTestFixture) { ctrl := gomock.NewController(t) mockFooMsgServer := footestutil.NewMockMsgServer() foov1.RegisterMsgServer(f, mockFooMsgServer) barMsgClient := barv1.NewMsgClient(f) if f.ServiceRevision(foov1.Msg_ServiceDesc.ServiceName) >= 1 { mockFooMsgServer.EXPECT().DoSomething(gomock.Any(), &foov1.MsgDoSomething{ ..., Condition: ..., // condition is expected in revision >= 1 }).Return(&foov1.MsgDoSomethingResponse{ }, nil) } else { mockFooMsgServer.EXPECT().DoSomething(gomock.Any(), &foov1.MsgDoSomething{... }).Return(&foov1.MsgDoSomethingResponse{ }, nil) } res, err := barMsgClient.CallFoo(f, &MsgCallFoo{ }) ... }) } ``` The unit test runner would make sure that no dependency mocks return arguments which are invalid for the service revision being tested to ensure that modules don't incorrectly depend on functionality not present in a given revision. #### Integration Testing An integration test runner and fixture would also be provided which instead of using mocks would test actual module dependencies in various combinations. Here is the proposed API: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type IntegrationTestFixture interface { TestFixture } type IntegrationTestConfig struct { ModuleConfig proto.Message // the module's config object DependencyMatrix map[string][]proto.Message // all the dependent module configs } // Run runs the test function for all combinations of dependency modules. func (cfg IntegationTestConfig) Run(t *testing.T, f func (t *testing.T, f IntegrationTestFixture)) { // ... } ``` And here is an example with foo and bar: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func TestBarIntegration(t *testing.T) { IntegrationTestConfig{ ModuleConfig: &barmodulev1.Module{ }, DependencyMatrix: map[string][]proto.Message{ "runtime": []proto.Message{ // test against two versions of runtime &runtimev1.Module{ }, &runtimev2.Module{ }, }, "foo": []proto.Message{ // test against three versions of foo &foomodulev1.Module{ }, &foomodulev2.Module{ }, &foomodulev3.Module{ }, } } }.Run(t, func (t *testing.T, f moduletesting.IntegrationTestFixture) { barMsgClient := barv1.NewMsgClient(f) res, err := barMsgClient.CallFoo(f, &MsgCallFoo{ }) ... }) } ``` Unlike unit tests, integration tests actually pull in other module dependencies. So that modules can be written without direct dependencies on other modules and because golang has no concept of development dependencies, integration tests should be written in separate go modules, ex. `example.com/bar/v2/test`. Because this paradigm uses go semantic versioning, it is possible to build a single go module which imports 3 versions of bar and 2 versions of runtime and can test these all together in the six various combinations of dependencies. ## Consequences ### Backwards Compatibility Modules which migrate fully to ADR 033 will not be compatible with existing modules which use the keeper paradigm. As a temporary workaround we may create some wrapper types that emulate the current keeper interface to minimize the migration overhead. ### Positive * we will be able to deliver interoperable semantically versioned modules which should dramatically increase the ability of the Cosmos SDK ecosystem to iterate on new features * it will be possible to write Cosmos SDK modules in other languages in the near future ### Negative * all modules will need to be refactored somewhat dramatically ### Neutral * the `cosmossdk.io/core/appconfig` framework will play a more central role in terms of how modules are defined, this is likely generally a good thing but does mean additional changes for users wanting to stick to the pre-depinject way of wiring up modules * `depinject` is somewhat less needed or maybe even obviated because of the full ADR 033 approach. If we adopt the core API proposed in [Link](https://github.com/cosmos/cosmos-sdk/pull/12239), then a module would probably always instantiate itself with a method `ProvideModule(appmodule.Service) (appmodule.AppModule, error)`. There is no complex wiring of keeper dependencies in this scenario and dependency injection may not have as much of (or any) use case. ## Further Discussions The decision described above is considered in draft mode and is pending final buy-in from the team and key stakeholders. Key outstanding discussions if we do adopt that direction are: * how do module clients introspect dependency module API revisions * how do modules determine a minor dependency module API revision requirement * how do modules appropriately test compatibility with different dependency versions * how to register and resolve interface implementations * how do modules register their protobuf file descriptors depending on the approach they take to generated code (the API module approach may still be viable as a supported strategy and would need pinned file descriptors) ## References * [Link](https://github.com/cosmos/cosmos-sdk/discussions/10162) * [Link](https://github.com/cosmos/cosmos-sdk/discussions/10582) * [Link](https://github.com/cosmos/cosmos-sdk/discussions/10368) * [Link](https://github.com/cosmos/cosmos-sdk/pull/11340) * [Link](https://github.com/cosmos/cosmos-sdk/issues/11899) * [ADR 020](/sdk/v0.50/build/architecture/adr-020-protobuf-transaction-encoding) * [ADR 033](/sdk/v0.50/build/architecture/adr-033-protobuf-inter-module-comm) # ADR 055: ORM Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-055-orm 2022-04-27: First draft ## Changelog * 2022-04-27: First draft ## Status ACCEPTED Implemented ## Abstract In order to make it easier for developers to build Cosmos SDK modules and for clients to query, index and verify proofs against state data, we have implemented an ORM (object-relational mapping) layer for the Cosmos SDK. ## Context Historically modules in the Cosmos SDK have always used the key-value store directly and created various handwritten functions for managing key format as well as constructing secondary indexes. This consumes a significant amount of time when building a module and is error-prone. Because key formats are non-standard, sometimes poorly documented, and subject to change, it is hard for clients to generically index, query and verify merkle proofs against state data. The known first instance of an "ORM" in the Cosmos ecosystem was in [weave](https://github.com/iov-one/weave/tree/master/orm). A later version was built for [regen-ledger](https://github.com/regen-network/regen-ledger/tree/157181f955823149e1825263a317ad8e16096da4/orm) for use in the group module and later [ported to the SDK](https://github.com/cosmos/cosmos-sdk/tree/35d3312c3be306591fcba39892223f1244c8d108/x/group/internal/orm) just for that purpose. While these earlier designs made it significantly easier to write state machines, they still required a lot of manual configuration, didn't expose state format directly to clients, and were limited in their support of different types of index keys, composite keys, and range queries. Discussions about the design continued in [Link](https://github.com/cosmos/cosmos-sdk/discussions/9156) and more sophisticated proofs of concept were created in [Link](https://github.com/allinbits/cosmos-sdk-poc/tree/master/runtime/orm) and [Link](https://github.com/cosmos/cosmos-sdk/pull/10454). ## Decision These prior efforts culminated in the creation of the Cosmos SDK `orm` go module which uses protobuf annotations for specifying ORM table definitions. This ORM is based on the new `google.golang.org/protobuf/reflect/protoreflect` API and supports: * sorted indexes for all simple protobuf types (except `bytes`, `enum`, `float`, `double`) as well as `Timestamp` and `Duration` * unsorted `bytes` and `enum` indexes * composite primary and secondary keys * unique indexes * auto-incrementing `uint64` primary keys * complex prefix and range queries * paginated queries * complete logical decoding of KV-store data Almost all the information needed to decode state directly is specified in .proto files. Each table definition specifies an ID which is unique per .proto file and each index within a table is unique within that table. Clients then only need to know the name of a module and the prefix ORM data for a specific .proto file within that module in order to decode state data directly. This additional information will be exposed directly through app configs which will be explained in a future ADR related to app wiring. The ORM makes optimizations around storage space by not repeating values in the primary key in the key value when storing primary key records. For example, if the object `{"a":0,"b":1}` has the primary key `a`, it will be stored in the key value store as `Key: '0', Value: {"b":1}` (with more efficient protobuf binary encoding). Also, the generated code from [Link](https://github.com/cosmos/cosmos-proto) does optimizations around the `google.golang.org/protobuf/reflect/protoreflect` API to improve performance. A code generator is included with the ORM which creates type safe wrappers around the ORM's dynamic `Table` implementation and is the recommended way for modules to use the ORM. The ORM tests provide a simplified bank module demonstration which illustrates: * [ORM proto options](https://github.com/cosmos/cosmos-sdk/blob/0d846ae2f0424b2eb640f6679a703b52d407813d/orm/internal/testpb/bank.proto) * [Generated Code](https://github.com/cosmos/cosmos-sdk/blob/0d846ae2f0424b2eb640f6679a703b52d407813d/orm/internal/testpb/bank.cosmos_orm.go) * [Example Usage in a Module Keeper](https://github.com/cosmos/cosmos-sdk/blob/0d846ae2f0424b2eb640f6679a703b52d407813d/orm/model/ormdb/module_test.go) ## Consequences ### Backwards Compatibility State machine code that adopts the ORM will need migrations as the state layout is generally backwards incompatible. These state machines will also need to migrate to [Link](https://github.com/cosmos/cosmos-proto) at least for state data. ### Positive * easier to build modules * easier to add secondary indexes to state * possible to write a generic indexer for ORM state * easier to write clients that do state proofs * possible to automatically write query layers rather than needing to manually implement gRPC queries ### Negative * worse performance than handwritten keys (for now). See [Further Discussions](#further-discussions) for potential improvements ### Neutral ## Further Discussions Further discussions will happen within the Cosmos SDK Framework Working Group. Current planned and ongoing work includes: * automatically generate client-facing query layer * client-side query libraries that transparently verify light client proofs * index ORM data to SQL databases * improve performance by: * optimizing existing reflection based code to avoid unnecessary gets when doing deletes & updates of simple tables * more sophisticated code generation such as making fast path reflection even faster (avoiding `switch` statements), or even fully generating code that equals handwritten performance ## References * [Link](https://github.com/iov-one/weave/tree/master/orm)). * [Link](https://github.com/regen-network/regen-ledger/tree/157181f955823149e1825263a317ad8e16096da4/orm) * [Link](https://github.com/cosmos/cosmos-sdk/tree/35d3312c3be306591fcba39892223f1244c8d108/x/group/internal/orm) * [Link](https://github.com/cosmos/cosmos-sdk/discussions/9156) * [Link](https://github.com/allinbits/cosmos-sdk-poc/tree/master/runtime/orm) * [Link](https://github.com/cosmos/cosmos-sdk/pull/10454) # ADR 057: App Wiring Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-057-app-wiring 2022-05-04: Initial Draft 2022-08-19: Updates ## Changelog * 2022-05-04: Initial Draft * 2022-08-19: Updates ## Status PROPOSED Implemented ## Abstract In order to make it easier to build Cosmos SDK modules and apps, we propose a new app wiring system based on dependency injection and declarative app configurations to replace the current `app.go` code. ## Context A number of factors have made the SDK and SDK apps in their current state hard to maintain. A symptom of the current state of complexity is [`simapp/app.go`](https://github.com/cosmos/cosmos-sdk/blob/c3edbb22cab8678c35e21fe0253919996b780c01/simapp/app.go) which contains almost 100 lines of imports and is otherwise over 600 lines of mostly boilerplate code that is generally copied to each new project. (Not to mention the additional boilerplate which gets copied in `simapp/simd`.) The large amount of boilerplate needed to bootstrap an app has made it hard to release independently versioned go modules for Cosmos SDK modules as described in [ADR 053: Go Module Refactoring](/sdk/latest/reference/architecture/adr-053-go-module-refactoring). In addition to being very verbose and repetitive, `app.go` also exposes a large surface area for breaking changes as most modules instantiate themselves with positional parameters which forces breaking changes anytime a new parameter (even an optional one) is needed. Several attempts were made to improve the current situation including [ADR 033: Internal-Module Communication](/sdk/latest/reference/architecture/adr-033-protobuf-inter-module-comm) and [a proof-of-concept of a new SDK](https://github.com/allinbits/cosmos-sdk-poc). The discussions around these designs led to the current solution described here. ## Decision In order to improve the current situation, a new "app wiring" paradigm has been designed to replace `app.go` which involves: * declaration configuration of the modules in an app which can be serialized to JSON or YAML * a dependency-injection (DI) framework for instantiating apps from the that configuration ### Dependency Injection When examining the code in `app.go` most of the code simply instantiates modules with dependencies provided either by the framework (such as store keys) or by other modules (such as keepers). It is generally pretty obvious given the context what the correct dependencies actually should be, so dependency-injection is an obvious solution. Rather than making developers manually resolve dependencies, a module will tell the DI container what dependency it needs and the container will figure out how to provide it. We explored several existing DI solutions in golang and felt that the reflection-based approach in [uber/dig](https://github.com/uber-go/dig) was closest to what we needed but not quite there. Assessing what we needed for the SDK, we designed and built the Cosmos SDK [depinject module](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/depinject), which has the following features: * dependency resolution and provision through functional constructors, ex: `func(need SomeDep) (AnotherDep, error)` * dependency injection `In` and `Out` structs which support `optional` dependencies * grouped-dependencies (many-per-container) through the `ManyPerContainerType` tag interface * module-scoped dependencies via `ModuleKey`s (where each module gets a unique dependency) * one-per-module dependencies through the `OnePerModuleType` tag interface * sophisticated debugging information and container visualization via GraphViz Here are some examples of how these would be used in an SDK module: * `StoreKey` could be a module-scoped dependency which is unique per module * a module's `AppModule` instance (or the equivalent) could be a `OnePerModuleType` * CLI commands could be provided with `ManyPerContainerType`s Note that even though dependency resolution is dynamic and based on reflection, which could be considered a pitfall of this approach, the entire dependency graph should be resolved immediately on app startup and only gets resolved once (except in the case of dynamic config reloading which is a separate topic). This means that if there are any errors in the dependency graph, they will get reported immediately on startup so this approach is only slightly worse than fully static resolution in terms of error reporting and much better in terms of code complexity. ### Declarative App Config In order to compose modules into an app, a declarative app configuration will be used. This configuration is based off of protobuf and its basic structure is very simple: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package cosmos.app.v1; message Config { repeated ModuleConfig modules = 1; } message ModuleConfig { string name = 1; google.protobuf.Any config = 2; } ``` (See also [Link](https://github.com/cosmos/cosmos-sdk/blob/6e18f582bf69e3926a1e22a6de3c35ea327aadce/proto/cosmos/app/v1alpha1/config.proto)) The configuration for every module is itself a protobuf message and modules will be identified and loaded based on the protobuf type URL of their config object (ex. `cosmos.bank.module.v1.Module`). Modules are given a unique short `name` to share resources across different versions of the same module which might have a different protobuf package versions (ex. `cosmos.bank.module.v2.Module`). All module config objects should define the `cosmos.app.v1alpha1.module` descriptor option which will provide additional useful metadata for the framework and which can also be indexed in module registries. An example app config in YAML might look like this: ```yaml expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} modules: - name: baseapp config: "@type": cosmos.baseapp.module.v1.Module begin_blockers: [staking, auth, bank] end_blockers: [bank, auth, staking] init_genesis: [bank, auth, staking] - name: auth config: "@type": cosmos.auth.module.v1.Module bech32_prefix: "foo" - name: bank config: "@type": cosmos.bank.module.v1.Module - name: staking config: "@type": cosmos.staking.module.v1.Module ``` In the above example, there is a hypothetical `baseapp` module which contains the information around ordering of begin blockers, end blockers, and init genesis. Rather than lifting these concerns up to the module config layer, they are themselves handled by a module which could allow a convenient way of swapping out different versions of baseapp (for instance to target different versions of tendermint), without needing to change the rest of the config. The `baseapp` module would then provide to the server framework (which sort of sits outside the ABCI app) an instance of `abci.Application`. In this model, an app is *modules all the way down* and the dependency injection/app config layer is very much protocol-agnostic and can adapt to even major breaking changes at the protocol layer. ### Module & Protobuf Registration In order for the two components of dependency injection and declarative configuration to work together as described, we need a way for modules to actually register themselves and provide dependencies to the container. One additional complexity that needs to be handled at this layer is protobuf registry initialization. Recall that in both the current SDK `codec` and the proposed [ADR 054: Protobuf Semver Compatible Codegen](https://github.com/cosmos/cosmos-sdk/pull/11802), protobuf types need to be explicitly registered. Given that the app config itself is based on protobuf and uses protobuf `Any` types, protobuf registration needs to happen before the app config itself can be decoded. Because we don't know which protobuf `Any` types will be needed a priori and modules themselves define those types, we need to decode the app config in separate phases: 1. parse app config JSON/YAML as raw JSON and collect required module type URLs (without doing proto JSON decoding) 2. build a [protobuf type registry](https://pkg.go.dev/google.golang.org/protobuf@v1.28.0/reflect/protoregistry) based on file descriptors and types provided by each required module 3. decode the app config as proto JSON using the protobuf type registry Because in [ADR 054: Protobuf Semver Compatible Codegen](https://github.com/cosmos/cosmos-sdk/pull/11802), each module might use `internal` generated code which is not registered with the global protobuf registry, this code should provide an alternate way to register protobuf types with a type registry. In the same way that `.pb.go` files currently have a `var File_foo_proto protoreflect.FileDescriptor` for the file `foo.proto`, generated code should have a new member `var Types_foo_proto TypeInfo` where `TypeInfo` is an interface or struct with all the necessary info to register both the protobuf generated types and file descriptor. So a module must provide dependency injection providers and protobuf types, and takes as input its module config object which uniquely identifies the module based on its type URL. With this in mind, we define a global module register which allows module implementations to register themselves with the following API: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Register registers a module with the provided type name (ex. cosmos.bank.module.v1.Module) // and the provided options. func Register(configTypeName protoreflect.FullName, option ...Option) { ... } type Option { /* private methods */ } // Provide registers dependency injection provider functions which work with the // cosmos-sdk container module. These functions can also accept an additional // parameter for the module's config object. func Provide(providers ...interface{ }) Option { ... } // Types registers protobuf TypeInfo's with the protobuf registry. func Types(types ...TypeInfo) Option { ... } ``` Ex: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func init() { appmodule.Register("cosmos.bank.module.v1.Module", appmodule.Types( types.Types_tx_proto, types.Types_query_proto, types.Types_types_proto, ), appmodule.Provide( provideBankModule, ) ) } type Inputs struct { container.In AuthKeeper auth.Keeper DB ormdb.ModuleDB } type Outputs struct { Keeper bank.Keeper AppModule appmodule.AppModule } func ProvideBankModule(config *bankmodulev1.Module, Inputs) (Outputs, error) { ... } ``` Note that in this module, a module configuration object *cannot* register different dependency providers at runtime based on the configuration. This is intentional because it allows us to know globally which modules provide which dependencies, and it will also allow us to do code generation of the whole app initialization. This can help us figure out issues with missing dependencies in an app config if the needed modules are loaded at runtime. In cases where required modules are not loaded at runtime, it may be possible to guide users to the correct module if through a global Cosmos SDK module registry. The `*appmodule.Handler` type referenced above is a replacement for the legacy `AppModule` framework, and described in [ADR 063: Core Module API](/sdk/latest/reference/architecture/adr-063-core-module-api). ### New `app.go` With this setup, `app.go` might now look something like this: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package main import ( // Each go package which registers a module must be imported just for side-effects // so that module implementations are registered. _ "github.com/cosmos/cosmos-sdk/x/auth/module" _ "github.com/cosmos/cosmos-sdk/x/bank/module" _ "github.com/cosmos/cosmos-sdk/x/staking/module" "github.com/cosmos/cosmos-sdk/core/app" ) // go:embed app.yaml var appConfigYAML []byte func main() { app.Run(app.LoadYAML(appConfigYAML)) } ``` ### Application to existing SDK modules So far we have described a system which is largely agnostic to the specifics of the SDK such as store keys, `AppModule`, `BaseApp`, etc. Improvements to these parts of the framework that integrate with the general app wiring framework defined here are described in [ADR 063: Core Module API](/sdk/latest/reference/architecture/adr-063-core-module-api). ### Registration of Inter-Module Hooks ### Registration of Inter-Module Hooks Some modules define a hooks interface (ex. `StakingHooks`) which allows one module to call back into another module when certain events happen. With the app wiring framework, these hooks interfaces can be defined as a `OnePerModuleType`s and then the module which consumes these hooks can collect these hooks as a map of module name to hook type (ex. `map[string]FooHooks`). Ex: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func init() { appmodule.Register( &foomodulev1.Module{ }, appmodule.Invoke(InvokeSetFooHooks), ... ) } func InvokeSetFooHooks( keeper *keeper.Keeper, fooHooks map[string]FooHooks, ) error { for k in sort.Strings(maps.Keys(fooHooks)) { keeper.AddFooHooks(fooHooks[k]) } } ``` Optionally, the module consuming hooks can allow app's to define an order for calling these hooks based on module name in its config object. An alternative way for registering hooks via reflection was considered where all keeper types are inspected to see if they implement the hook interface by the modules exposing hooks. This has the downsides of: * needing to expose all the keepers of all modules to the module providing hooks, * not allowing for encapsulating hooks on a different type which doesn't expose all keeper methods, * harder to know statically which module expose hooks or are checking for them. With the approach proposed here, hooks registration will be obviously observable in `app.go` if `depinject` codegen (described below) is used. ### Code Generation The `depinject` framework will optionally allow the app configuration and dependency injection wiring to be code generated. This will allow: * dependency injection wiring to be inspected as regular go code just like the existing `app.go`, * dependency injection to be opt-in with manual wiring 100% still possible. Code generation requires that all providers and invokers and their parameters are exported and in non-internal packages. ### Module Semantic Versioning When we start creating semantically versioned SDK modules that are in standalone go modules, a state machine breaking change to a module should be handled as follows: * the semantic major version should be incremented, and * a new semantically versioned module config protobuf type should be created. For instance, if we have the SDK module for bank in the go module `github.com/cosmos/cosmos-sdk/x/bank` with the module config type `cosmos.bank.module.v1.Module`, and we want to make a state machine breaking change to the module, we would: * create a new go module `github.com/cosmos/cosmos-sdk/x/bank/v2`, * with the module config protobuf type `cosmos.bank.module.v2.Module`. This *does not* mean that we need to increment the protobuf API version for bank. Both modules can support `cosmos.bank.v1`, but `github.com/cosmos/cosmos-sdk/x/bank/v2` will be a separate go module with a separate module config type. This practice will eventually allow us to use appconfig to load new versions of a module via a configuration change. Effectively, there should be a 1:1 correspondence between a semantically versioned go module and a versioned module config protobuf type, and major versioning bumps should occur whenever state machine breaking changes are made to a module. NOTE: SDK modules that are standalone go modules *should not* adopt semantic versioning until the concerns described in [ADR 054: Module Semantic Versioning](/sdk/latest/reference/architecture/adr-054-semver-compatible-modules) are addressed. The short-term solution for this issue was left somewhat unresolved. However, the easiest tactic is likely to use a standalone API go module and follow the guidelines described in this comment: [Link](https://github.com/cosmos/cosmos-sdk/pull/11802#issuecomment-1406815181). For the time-being, it is recommended that Cosmos SDK modules continue to follow tried and true [0-based versioning](https://0ver.org) until an officially recommended solution is provided. This section of the ADR will be updated when that happens and for now, this section should be considered as a design recommendation for future adoption of semantic versioning. ## Consequences ### Backwards Compatibility Modules which work with the new app wiring system do not need to drop their existing `AppModule` and `NewKeeper` registration paradigms. These two methods can live side-by-side for as long as is needed. ### Positive * wiring up new apps will be simpler, more succinct and less error-prone * it will be easier to develop and test standalone SDK modules without needing to replicate all of simapp * it may be possible to dynamically load modules and upgrade chains without needing to do a coordinated stop and binary upgrade using this mechanism * easier plugin integration * dependency injection framework provides more automated reasoning about dependencies in the project, with a graph visualization. ### Negative * it may be confusing when a dependency is missing although error messages, the GraphViz visualization, and global module registration may help with that ### Neutral * it will require work and education ## Further Discussions The protobuf type registration system described in this ADR has not been implemented and may need to be reconsidered in light of code generation. It may be better to do this type registration with a DI provider. ## References * [Link](https://github.com/cosmos/cosmos-sdk/blob/c3edbb22cab8678c35e21fe0253919996b780c01/simapp/app.go) * [Link](https://github.com/allinbits/cosmos-sdk-poc) * [Link](https://github.com/uber-go/dig) * [Link](https://github.com/google/wire) * [Link](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/container) * [Link](https://github.com/cosmos/cosmos-sdk/pull/11802) * [ADR 063: Core Module API](/sdk/latest/reference/architecture/adr-063-core-module-api) # ADR 058: Auto-Generated CLI Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-058-auto-generated-cli 2022-05-04: Initial Draft ## Changelog * 2022-05-04: Initial Draft ## Status ACCEPTED Partially Implemented ## Abstract In order to make it easier for developers to write Cosmos SDK modules, we provide infrastructure which automatically generates CLI commands based on protobuf definitions. ## Context Current Cosmos SDK modules generally implement a CLI command for every transaction and every query supported by the module. These are handwritten for each command and essentially amount to providing some CLI flags or positional arguments for specific fields in protobuf messages. In order to make sure CLI commands are correctly implemented as well as to make sure that the application works in end-to-end scenarios, we do integration tests using CLI commands. While these tests are valuable on some-level, they can be hard to write and maintain, and run slowly. [Some teams have contemplated](https://github.com/regen-network/regen-ledger/issues/1041) moving away from CLI-style integration tests (which are really end-to-end tests) towards narrower integration tests which exercise `MsgClient` and `QueryClient` directly. This might involve replacing the current end-to-end CLI tests with unit tests as there still needs to be some way to test these CLI commands for full quality assurance. ## Decision To make module development simpler, we provide infrastructure - in the new [`client/v2`](https://github.com/cosmos/cosmos-sdk/tree/main/client/v2) go module - for automatically generating CLI commands based on protobuf definitions to either replace or complement handwritten CLI commands. This will mean that when developing a module, it will be possible to skip both writing and testing CLI commands as that can all be taken care of by the framework. The basic design for automatically generating CLI commands is to: * create one CLI command for each `rpc` method in a protobuf `Query` or `Msg` service * create a CLI flag for each field in the `rpc` request type * for `query` commands call gRPC and print the response as protobuf JSON or YAML (via the `-o`/`--output` flag) * for `tx` commands, create a transaction and apply common transaction flags In order to make the auto-generated CLI as easy to use (or easier) than handwritten CLI, we need to do custom handling of specific protobuf field types so that the input format is easy for humans: * `Coin`, `Coins`, `DecCoin`, and `DecCoins` should be input using the existing format (i.e. `1000uatom`) * it should be possible to specify an address using either the bech32 address string or a named key in the keyring * `Timestamp` and `Duration` should accept strings like `2001-01-01T00:00:00Z` and `1h3m` respectively * pagination should be handled with flags like `--page-limit`, `--page-offset`, etc. * it should be possible to customize any other protobuf type either via its message name or a `cosmos_proto.scalar` annotation At a basic level it should be possible to generate a command for a single `rpc` method as well as all the commands for a whole protobuf `service` definition. It should be possible to mix and match auto-generated and handwritten commands. ## Consequences ### Backwards Compatibility Existing modules can mix and match auto-generated and handwritten CLI commands so it is up to them as to whether they make breaking changes by replacing handwritten commands with slightly different auto-generated ones. For now the SDK will maintain the existing set of CLI commands for backwards compatibility but new commands will use this functionality. ### Positive * module developers will not need to write CLI commands * module developers will not need to test CLI commands * [lens](https://github.com/strangelove-ventures/lens) may benefit from this ### Negative ### Neutral ## Further Discussions We would like to be able to customize: * short and long usage strings for commands * aliases for flags (ex. `-a` for `--amount`) * which fields are positional parameters rather than flags It is an [open discussion](https://github.com/cosmos/cosmos-sdk/pull/11725#issuecomment-1108676129) as to whether these customizations options should line in: * the .proto files themselves, * separate config files (ex. YAML), or * directly in code Providing the options in .proto files would allow a dynamic client to automatically generate CLI commands on the fly. However, that may pollute the .proto files themselves with information that is only relevant for a small subset of users. ## References * [Link](https://github.com/regen-network/regen-ledger/issues/1041) * [Link](https://github.com/cosmos/cosmos-sdk/tree/main/client/v2) * [Link](https://github.com/cosmos/cosmos-sdk/pull/11725#issuecomment-1108676129) # ADR 059: Test Scopes Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-059-test-scopes 2022-08-02: Initial Draft 2023-03-02: Add precision for integration tests 2023-03-23: Add precision for E2E tests ## Changelog * 2022-08-02: Initial Draft * 2023-03-02: Add precision for integration tests * 2023-03-23: Add precision for E2E tests ## Status PROPOSED Partially Implemented ## Abstract Recent work in the SDK aimed at breaking apart the monolithic root go module has highlighted shortcomings and inconsistencies in our testing paradigm. This ADR clarifies a common language for talking about test scopes and proposes an ideal state of tests at each scope. ## Context [ADR-053: Go Module Refactoring](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-053-go-module-refactoring.md) expresses our desire for an SDK composed of many independently versioned Go modules, and [ADR-057: App Wiring](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-057-app-wiring.md) offers a methodology for breaking apart inter-module dependencies through the use of dependency injection. As described in [EPIC: Separate all SDK modules into standalone go modules](https://github.com/cosmos/cosmos-sdk/issues/11899), module dependencies are particularly complected in the test phase, where simapp is used as the key test fixture in setting up and running tests. It is clear that the successful completion of Phases 3 and 4 in that EPIC require the resolution of this dependency problem. In [EPIC: Unit Testing of Modules via Mocks](https://github.com/cosmos/cosmos-sdk/issues/12398) it was thought this Gordian knot could be unwound by mocking all dependencies in the test phase for each module, but seeing how these refactors were complete rewrites of test suites discussions began around the fate of the existing integration tests. One perspective is that they ought to be thrown out, another is that integration tests have some utility of their own and a place in the SDK's testing story. Another point of confusion has been the current state of CLI test suites, [x/auth](https://github.com/cosmos/cosmos-sdk/blob/0f7e56c6f9102cda0ca9aba5b6f091dbca976b5a/x/auth/client/testutil/suite.go#L44-L49) for example. In code these are called integration tests, but in reality function as end to end tests by starting up a tendermint node and full application. [EPIC: Rewrite and simplify CLI tests](https://github.com/cosmos/cosmos-sdk/issues/12696) identifies the ideal state of CLI tests using mocks, but does not address the place end to end tests may have in the SDK. From here we identify three scopes of testing, **unit**, **integration**, **e2e** (end to end), seek to define the boundaries of each, their shortcomings (real and imposed), and their ideal state in the SDK. ### Unit tests Unit tests exercise the code contained in a single module (e.g. `/x/bank`) or package (e.g. `/client`) in isolation from the rest of the code base. Within this we identify two levels of unit tests, *illustrative* and *journey*. The definitions below lean heavily on [The BDD Books - Formulation](https://leanpub.com/bddbooks-formulation) section 1.3. *Illustrative* tests exercise an atomic part of a module in isolation - in this case we might do fixture setup/mocking of other parts of the module. Tests which exercise a whole module's function with dependencies mocked, are *journeys*. These are almost like integration tests in that they exercise many things together but still use mocks. Example 1 journey vs illustrative tests - depinject's BDD style tests, show how we can rapidly build up many illustrative cases demonstrating behavioral rules without [very much code](https://github.com/cosmos/cosmos-sdk/blob/main/depinject/binding_test.go) while maintaining high level readability. Example 2 [depinject table driven tests](https://github.com/cosmos/cosmos-sdk/blob/main/depinject/provider_desc_test.go) Example 3 [Bank keeper tests](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/x/bank/keeper/keeper_test.go#L94-L105) - A mock implementation of `AccountKeeper` is supplied to the keeper constructor. #### Limitations Certain modules are tightly coupled beyond the test phase. A recent dependency report for `bank -> auth` found 274 total usages of `auth` in `bank`, 50 of which are in production code and 224 in test. This tight coupling may suggest that either the modules should be merged, or refactoring is required to abstract references to the core types tying the modules together. It could also indicate that these modules should be tested together in integration tests beyond mocked unit tests. In some cases setting up a test case for a module with many mocked dependencies can be quite cumbersome and the resulting test may only show that the mocking framework works as expected rather than working as a functional test of interdependent module behavior. ### Integration tests Integration tests define and exercise relationships between an arbitrary number of modules and/or application subsystems. Wiring for integration tests is provided by `depinject` and some [helper code](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/testutil/sims/app_helpers.go#L95) starts up a running application. A section of the running application may then be tested. Certain inputs during different phases of the application life cycle are expected to produce invariant outputs without too much concern for component internals. This type of black box testing has a larger scope than unit testing. Example 1 [client/grpc\_query\_test/TestGRPCQuery](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/client/grpc_query_test.go#L111-L129) - This test is misplaced in `/client`, but tests the life cycle of (at least) `runtime` and `bank` as they progress through startup, genesis and query time. It also exercises the fitness of the client and query server without putting bytes on the wire through the use of [QueryServiceTestHelper](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/baseapp/grpcrouter_helpers.go#L31). Example 2 `x/evidence` Keeper integration tests - Starts up an application composed of [8 modules](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/x/evidence/testutil/app.yaml#L1) with [5 keepers](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/x/evidence/keeper/keeper_test.go#L101-L106) used in the integration test suite. One test in the suite exercises [HandleEquivocationEvidence](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/x/evidence/keeper/infraction_test.go#L42) which contains many interactions with the staking keeper. Example 3 - Integration suite app configurations may also be specified via golang (not YAML as above) statically or [dynamically](https://github.com/cosmos/cosmos-sdk/blob/8c23f6f957d1c0bedd314806d1ac65bea59b084c/tests/integration/bank/keeper/keeper_test.go#L129-L134). #### Limitations Setting up a particular input state may be more challenging since the application is starting from a zero state. Some of this may be addressed by good test fixture abstractions with testing of their own. Tests may also be more brittle, and larger refactors could impact application initialization in unexpected ways with harder to understand errors. This could also be seen as a benefit, and indeed the SDK's current integration tests were helpful in tracking down logic errors during earlier stages of app-wiring refactors. ### Simulations Simulations (also called generative testing) are a special case of integration tests where deterministically random module operations are executed against a running simapp, building blocks on the chain until a specified height is reached. No *specific* assertions are made for the state transitions resulting from module operations but any error will halt and fail the simulation. Since `crisis` is included in simapp and the simulation runs EndBlockers at the end of each block any module invariant violations will also fail the simulation. Modules must implement [AppModuleSimulation.WeightedOperations](https://github.com/cosmos/cosmos-sdk/blob/2bec9d2021918650d3938c3ab242f84289daef80/types/module/simulation.go#L31) to define their simulation operations. Note that not all modules implement this which may indicate a gap in current simulation test coverage. Modules not returning simulation operations: * `auth` * `evidence` * `mint` * `params` A separate binary, [runsim](https://github.com/cosmos/tools/tree/master/cmd/runsim), is responsible for kicking off some of these tests and managing their life cycle. #### Limitations * A success may take a long time to run, 7-10 minutes per simulation in CI. * Timeouts sometimes occur on apparent successes without any indication why. * Useful error messages not provided on failure from CI, requiring a developer to run the simulation locally to reproduce. ### E2E tests End to end tests exercise the entire system as we understand it in as close an approximation to a production environment as is practical. Presently these tests are located at [tests/e2e](https://github.com/cosmos/cosmos-sdk/tree/main/tests/e2e) and rely on [testutil/network](https://github.com/cosmos/cosmos-sdk/tree/main/testutil/network) to start up an in-process Tendermint node. An application should be built as minimally as possible to exercise the desired functionality. The SDK uses an application will only the required modules for the tests. The application developer is adviced to use its own application for e2e tests. #### Limitations In general the limitations of end to end tests are orchestration and compute cost. Scaffolding is required to start up and run a prod-like environment and the this process takes much longer to start and run than unit or integration tests. Global locks present in Tendermint code cause stateful starting/stopping to sometimes hang or fail intermittently when run in a CI environment. The scope of e2e tests has been complected with command line interface testing. ## Decision We accept these test scopes and identify the following decisions points for each. | Scope | App Type | Mocks? | | ----------- | ------------------- | ------ | | Unit | None | Yes | | Integration | integration helpers | Some | | Simulation | minimal app | No | | E2E | minimal app | No | The decision above is valid for the SDK. An application developer should test their application with their full application instead of the minimal app. ### Unit Tests All modules must have mocked unit test coverage. Illustrative tests should outnumber journeys in unit tests. Unit tests should outnumber integration tests. Unit tests must not introduce additional dependencies beyond those already present in production code. When module unit test introduction as per [EPIC: Unit testing of modules via mocks](https://github.com/cosmos/cosmos-sdk/issues/12398) results in a near complete rewrite of an integration test suite the test suite should be retained and moved to `/tests/integration`. We accept the resulting test logic duplication but recommend improving the unit test suite through the addition of illustrative tests. ### Integration Tests All integration tests shall be located in `/tests/integration`, even those which do not introduce extra module dependencies. To help limit scope and complexity, it is recommended to use the smallest possible number of modules in application startup, i.e. don't depend on simapp. Integration tests should outnumber e2e tests. ### Simulations Simulations shall use a minimal application (usually via app wiring). They are located under `/x/{moduleName}/simulation`. ### E2E Tests Existing e2e tests shall be migrated to integration tests by removing the dependency on the test network and in-process Tendermint node to ensure we do not lose test coverage. The e2e rest runner shall transition from in process Tendermint to a runner powered by Docker via [dockertest](https://github.com/ory/dockertest). E2E tests exercising a full network upgrade shall be written. The CLI testing aspect of existing e2e tests shall be rewritten using the network mocking demonstrated in [PR#12706](https://github.com/cosmos/cosmos-sdk/pull/12706). ## Consequences ### Positive * test coverage is increased * test organization is improved * reduced dependency graph size in modules * simapp removed as a dependency from modules * inter-module dependencies introduced in test code are removed * reduced CI run time after transitioning away from in process Tendermint ### Negative * some test logic duplication between unit and integration tests during transition * test written using dockertest DX may be a bit worse ### Neutral * some discovery required for e2e transition to dockertest ## Further Discussions It may be useful if test suites could be run in integration mode (with mocked tendermint) or with e2e fixtures (with real tendermint and many nodes). Integration fixtures could be used for quicker runs, e2e fixures could be used for more battle hardening. A PoC `x/gov` was completed in PR [#12847](https://github.com/cosmos/cosmos-sdk/pull/12847) is in progress for unit tests demonstrating BDD \[Rejected]. Observing that a strength of BDD specifications is their readability, and a con is the cognitive load while writing and maintaining, current consensus is to reserve BDD use for places in the SDK where complex rules and module interactions are demonstrated. More straightforward or low level test cases will continue to rely on go table tests. Levels are network mocking in integration and e2e tests are still being worked on and formalized. # ADR 60: ABCI 1.0 Integration (Phase I) Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-060-abci-1.0 2022-08-10: Initial Draft (@alexanderbez, @tac0turtle) Nov 12, 2022: Update PrepareProposal and ProcessProposal semantics per the initial implementation PR (@alexanderbez) ## Changelog * 2022-08-10: Initial Draft (@alexanderbez, @tac0turtle) * Nov 12, 2022: Update `PrepareProposal` and `ProcessProposal` semantics per the initial implementation [PR](https://github.com/cosmos/cosmos-sdk/pull/13453) (@alexanderbez) ## Status ACCEPTED ## Abstract This ADR describes the initial adoption of [ABCI 1.0](https://github.com/tendermint/tendermint/blob/master/spec/abci%2B%2B/README.md), the next evolution of ABCI, within the Cosmos SDK. ABCI 1.0 aims to provide application developers with more flexibility and control over application and consensus semantics, e.g. in-application mempools, in-process oracles, and order-book style matching engines. ## Context Tendermint will release ABCI 1.0. Notably, at the time of this writing, Tendermint is releasing v0.37.0 which will include `PrepareProposal` and `ProcessProposal`. The `PrepareProposal` ABCI method is concerned with a block proposer requesting the application to evaluate a series of transactions to be included in the next block, defined as a slice of `TxRecord` objects. The application can either accept, reject, or completely ignore some or all of these transactions. This is an important consideration to make as the application can essentially define and control its own mempool allowing it to define sophisticated transaction priority and filtering mechanisms, by completely ignoring the `TxRecords` Tendermint sends it, favoring its own transactions. This essentially means that the Tendermint mempool acts more like a gossip data structure. The second ABCI method, `ProcessProposal`, is used to process the block proposer's proposal as defined by `PrepareProposal`. It is important to note the following with respect to `ProcessProposal`: * Execution of `ProcessProposal` must be deterministic. * There must be coherence between `PrepareProposal` and `ProcessProposal`. In other words, for any two correct processes *p* and *q*, if *q*'s Tendermint calls `RequestProcessProposal` on *up*, *q*'s Application returns ACCEPT in `ResponseProcessProposal`. It is important to note that in ABCI 1.0 integration, the application is NOT responsible for locking semantics -- Tendermint will still be responsible for that. In the future, however, the application will be responsible for locking, which allows for parallel execution possibilities. ## Decision We will integrate ABCI 1.0, which will be introduced in Tendermint v0.37.0, in the next major release of the Cosmos SDK. We will integrate ABCI 1.0 methods on the `BaseApp` type. We describe the implementations of the two methods individually below. Prior to describing the implementation of the two new methods, it is important to note that the existing ABCI methods, `CheckTx`, `DeliverTx`, etc, still exist and serve the same functions as they do now. ### `PrepareProposal` Prior to evaluating the decision for how to implement `PrepareProposal`, it is important to note that `CheckTx` will still be executed and will be responsible for evaluating transaction validity as it does now, with one very important *additive* distinction. When executing transactions in `CheckTx`, the application will now add valid transactions, i.e. passing the AnteHandler, to its own mempool data structure. In order to provide a flexible approach to meet the varying needs of application developers, we will define both a mempool interface and a data structure utilizing Golang generics, allowing developers to focus only on transaction ordering. Developers requiring absolute full control can implement their own custom mempool implementation. We define the general mempool interface as follows (subject to change): ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Mempool interface { // Insert attempts to insert a Tx into the app-side mempool returning // an error upon failure. Insert(sdk.Context, sdk.Tx) error // Select returns an Iterator over the app-side mempool. If txs are specified, // then they shall be incorporated into the Iterator. The Iterator must // closed by the caller. Select(sdk.Context, [][]byte) Iterator // CountTx returns the number of transactions currently in the mempool. CountTx() int // Remove attempts to remove a transaction from the mempool, returning an error // upon failure. Remove(sdk.Tx) error } // Iterator defines an app-side mempool iterator interface that is as minimal as // possible. The order of iteration is determined by the app-side mempool // implementation. type Iterator interface { // Next returns the next transaction from the mempool. If there are no more // transactions, it returns nil. Next() Iterator // Tx returns the transaction at the current position of the iterator. Tx() sdk.Tx } ``` We will define an implementation of `Mempool`, defined by `nonceMempool`, that will cover most basic application use-cases. Namely, it will prioritize transactions by transaction sender, allowing for multiple transactions from the same sender. The default app-side mempool implementation, `nonceMempool`, will operate on a single skip list data structure. Specifically, transactions with the lowest nonce globally are prioritized. Transactions with the same nonce are prioritized by sender address. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type nonceMempool struct { txQueue *huandu.SkipList } ``` Previous discussions1 have come to the agreement that Tendermint will perform a request to the application, via `RequestPrepareProposal`, with a certain amount of transactions reaped from Tendermint's local mempool. The exact amount of transactions reaped will be determined by a local operator configuration. This is referred to as the "one-shot approach" seen in discussions. When Tendermint reaps transactions from the local mempool and sends them to the application via `RequestPrepareProposal`, the application will have to evaluate the transactions. Specifically, it will need to inform Tendermint if it should reject and or include each transaction. Note, the application can even *replace* transactions entirely with other transactions. When evaluating transactions from `RequestPrepareProposal`, the application will ignore *ALL* transactions sent to it in the request and instead reap up to `RequestPrepareProposal.max_tx_bytes` from its own mempool. Since an application can technically insert or inject transactions on `Insert` during `CheckTx` execution, it is recommended that applications ensure transaction validity when reaping transactions during `PrepareProposal`. However, what validity exactly means is entirely determined by the application. The Cosmos SDK will provide a default `PrepareProposal` implementation that simply select up to `MaxBytes` *valid* transactions. However, applications can override this default implementation with their own implementation and set that on `BaseApp` via `SetPrepareProposal`. ### `ProcessProposal` The `ProcessProposal` ABCI method is relatively straightforward. It is responsible for ensuring validity of the proposed block containing transactions that were selected from the `PrepareProposal` step. However, how an application determines validity of a proposed block depends on the application and its varying use cases. For most applications, simply calling the `AnteHandler` chain would suffice, but there could easily be other applications that need more control over the validation process of the proposed block, such as ensuring txs are in a certain order or that certain transactions are included. While this theoretically could be achieved with a custom `AnteHandler` implementation, it's not the cleanest UX or the most efficient solution. Instead, we will define an additional ABCI interface method on the existing `Application` interface, similar to the existing ABCI methods such as `BeginBlock` or `EndBlock`. This new interface method will be defined as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ProcessProposal(sdk.Context, abci.RequestProcessProposal) error { } ``` Note, we must call `ProcessProposal` with a new internal branched state on the `Context` argument as we cannot simply just use the existing `checkState` because `BaseApp` already has a modified `checkState` at this point. So when executing `ProcessProposal`, we create a similar branched state, `processProposalState`, off of `deliverState`. Note, the `processProposalState` is never committed and is completely discarded after `ProcessProposal` finishes execution. The Cosmos SDK will provide a default implementation of `ProcessProposal` in which all transactions are validated using the CheckTx flow, i.e. the AnteHandler, and will always return ACCEPT unless any transaction cannot be decoded. ### `DeliverTx` Since transactions are not truly removed from the app-side mempool during `PrepareProposal`, since `ProcessProposal` can fail or take multiple rounds and we do not want to lose transactions, we need to finally remove the transaction from the app-side mempool during `DeliverTx` since during this phase, the transactions are being included in the proposed block. Alternatively, we can keep the transactions as truly being removed during the reaping phase in `PrepareProposal` and add them back to the app-side mempool in case `ProcessProposal` fails. ## Consequences ### Backwards Compatibility ABCI 1.0 is naturally not backwards compatible with prior versions of the Cosmos SDK and Tendermint. For example, an application that requests `RequestPrepareProposal` to the same application that does not speak ABCI 1.0 will naturally fail. However, in the first phase of the integration, the existing ABCI methods as we know them today will still exist and function as they currently do. ### Positive * Applications now have full control over transaction ordering and priority. * Lays the groundwork for the full integration of ABCI 1.0, which will unlock more app-side use cases around block construction and integration with the Tendermint consensus engine. ### Negative * Requires that the "mempool", as a general data structure that collects and stores uncommitted transactions will be duplicated between both Tendermint and the Cosmos SDK. * Additional requests between Tendermint and the Cosmos SDK in the context of block execution. Albeit, the overhead should be negligible. * Not backwards compatible with previous versions of Tendermint and the Cosmos SDK. ## Further Discussions It is possible to design the app-side implementation of the `Mempool[T MempoolTx]` in many different ways using different data structures and implementations. All of which have different tradeoffs. The proposed solution keeps things simple and covers cases that would be required for most basic applications. There are tradeoffs that can be made to improve performance of reaping and inserting into the provided mempool implementation. ## References * [Link](https://github.com/tendermint/tendermint/blob/master/spec/abci%2B%2B/README.md) * \[1] [Link](https://github.com/tendermint/tendermint/issues/7750#issuecomment-1076806155) * \[2] [Link](https://github.com/tendermint/tendermint/issues/7750#issuecomment-1075717151) # ADR ADR-061: Liquid Staking Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-061-liquid-staking 2022-09-10: Initial Draft (@zmanian) ## Changelog * 2022-09-10: Initial Draft (@zmanian) ## Status ACCEPTED ## Abstract Add a semi-fungible liquid staking primitive to the default Cosmos SDK staking module. This upgrades proof of stake to enable safe designs with lower overall monetary issuance and integration with numerous liquid staking protocols like Stride, Persistence, Quicksilver, Lido etc. ## Context The original release of the Cosmos Hub featured the implementation of a ground breaking proof of stake mechanism featuring delegation, slashing, in protocol reward distribution and adaptive issuance. This design was state of the art for 2016 and has been deployed without major changes by many L1 blockchains. As both Proof of Stake and blockchain use cases have matured, this design has aged poorly and should no longer be considered a good baseline Proof of Stake issuance. In the world of application specific blockchains, there cannot be a one size fits all blockchain but the Cosmos SDK does endeavour to provide a good baseline implementation and one that is suitable for the Cosmos Hub. The most important deficiency of the legacy staking design is that it composes poorly with on chain protocols for trading, lending, derivatives that are referred to collectively as DeFi. The legacy staking implementation starves these applications of liquidity by increasing the risk free rate adaptively. It basically makes DeFi and staking security somewhat incompatible. The Osmosis team has adopted the idea of Superfluid and Interfluid staking where assets that are participating in DeFi appliactions can also be used in proof of stake. This requires tight integration with an enshrined set of DeFi applications and thus is unsuitable for the Cosmos SDK. It's also important to note that Interchain Accounts are available in the default IBC implementation and can be used to [rehypothecate](https://www.investopedia.com/terms/h/hypothecation.asp#toc-what-is-rehypothecation) delegations. Thus liquid staking is already possible and these changes merely improve the UX of liquid staking. Centralized exchanges also rehypothecate staked assets, posing challenges for decentralization. This ADR takes the position that adoption of in-protocol liquid staking is the preferable outcome and provides new levers to incentivize decentralization of stake. These changes to the staking module have been in development for more than a year and have seen substantial industry adoption who plan to build staking UX. The internal economics at Informal team has also done a review of the impacts of these changes and this review led to the development of the exempt delegation system. This system provides governance with a tuneable parameter for modulating the risks of principal agent problem called the exemption factor. ## Decision We implement the semi-fungible liquid staking system and exemption factor system within the cosmos sdk. Though registered as fungible assets, these tokenized shares have extremely limited fungibility, only among the specific delegation record that was created when shares were tokenized. These assets can be used for OTC trades but composability with DeFi is limited. The primary expected use case is improving the user experience of liquid staking providers. A new governance parameter is introduced that defines the ratio of exempt to issued tokenized shares. This is called the exemption factor. A larger exemption factor allows more tokenized shares to be issued for a smaller amount of exempt delegations. If governance is comfortable with how the liquid staking market is evolving, it makes sense to increase this value. Min self delegation is removed from the staking system with the expectation that it will be replaced by the exempt delegations system. The exempt delegation system allows multiple accounts to demonstrate economic alignment with the validator operator as team members, partners etc. without co-mingling funds. Delegation exemption will likely be required to grow the validators' business under widespread adoption of liquid staking once governance has adjusted the exemption factor. When shares are tokenized, the underlying shares are transferred to a module account and rewards go to the module account for the TokenizedShareRecord. There is no longer a mechanism to override the validators vote for TokenizedShares. ### `MsgTokenizeShares` The MsgTokenizeShares message is used to create tokenize delegated tokens. This message can be executed by any delegator who has positive amount of delegation and after execution the specific amount of delegation disappear from the account and share tokens are provided. Share tokens are denominated in the validator and record id of the underlying delegation. A user may tokenize some or all of their delegation. They will receive shares with the denom of `cosmosvaloper1xxxx/5` where 5 is the record id for the validator operator. MsgTokenizeShares fails if the account is a VestingAccount. Users will have to move vested tokens to a new account and endure the unbonding period. We view this as an acceptable tradeoff vs. the complex book keeping required to track vested tokens. The total amount of outstanding tokenized shares for the validator is checked against the sum of exempt delegations multiplied by the exemption factor. If the tokenized shares exceeds this limit, execution fails. MsgTokenizeSharesResponse provides the number of tokens generated and their denom. ### `MsgRedeemTokensforShares` The MsgRedeemTokensforShares message is used to redeem the delegation from share tokens. This message can be executed by any user who owns share tokens. After execution delegations will appear to the user. ### `MsgTransferTokenizeShareRecord` The MsgTransferTokenizeShareRecord message is used to transfer the ownership of rewards generated from the tokenized amount of delegation. The tokenize share record is created when a user tokenize his/her delegation and deleted when the full amount of share tokens are redeemed. This is designed to work with liquid staking designs that do not redeem the tokenized shares and may instead want to keep the shares tokenized. ### `MsgExemptDelegation` The MsgExemptDelegation message is used to exempt a delegation to a validator. If the exemption factor is greater than 0, this will allow more delegation shares to be issued from the validator. This design allows the chain to force an amount of self-delegation by validators participating in liquid staking schemes. ## Consequences ### Backwards Compatibility By setting the exemption factor to zero, this module works like legacy staking. The only substantial change is the removal of min-self-bond and without any tokenized shares, there is no incentive to exempt delegation. ### Positive This approach should enable integration with liquid staking providers and improved user experience. It provides a pathway to security under non-exponential issuance policies in the baseline staking module. # ADR 062: Collections, a simplified storage layer for cosmos-sdk modules. Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-062-collections-state-layer 30/11/2022: PROPOSED ## Changelog * 30/11/2022: PROPOSED ## Status PROPOSED - Implemented ## Abstract We propose a simplified module storage layer which leverages golang generics to allow module developers to handle module storage in a simple and straightforward manner, whilst offering safety, extensibility and standardisation. ## Context Module developers are forced into manually implementing storage functionalities in their modules, those functionalities include but are not limited to: * Defining key to bytes formats. * Defining value to bytes formats. * Defining secondary indexes. * Defining query methods to expose outside to deal with storage. * Defining local methods to deal with storage writing. * Dealing with genesis imports and exports. * Writing tests for all the above. This brings in a lot of problems: * It blocks developers from focusing on the most important part: writing business logic. * Key to bytes formats are complex and their definition is error-prone, for example: * how do I format time to bytes in such a way that bytes are sorted? * how do I ensure when I don't have namespace collisions when dealing with secondary indexes? * The lack of standardisation makes life hard for clients, and the problem is exacerbated when it comes to providing proofs for objects present in state. Clients are forced to maintain a list of object paths to gather proofs. ### Current Solution: ORM The current SDK proposed solution to this problem is [ORM](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-055-orm.md). While ORM offers a lot of good functionality aimed at solving these specific problems, it has some downsides: * It requires migrations. * It uses the newest protobuf golang API, whilst the SDK still mainly uses gogoproto. * Integrating ORM into a module would require the developer to deal with two different golang frameworks (golang protobuf + gogoproto) representing the same API objects. * It has a high learning curve, even for simple storage layers as it requires developers to have knowledge around protobuf options, custom cosmos-sdk storage extensions, and tooling download. Then after this they still need to learn the code-generated API. ### CosmWasm Solution: cw-storage-plus The collections API takes inspiration from [cw-storage-plus](https://docs.cosmwasm.com/docs/1.0/smart-contracts/state/cw-plus/), which has demonstrated to be a powerful tool for dealing with storage in CosmWasm contracts. It's simple, does not require extra tooling, it makes it easy to deal with complex storage structures (indexes, snapshot, etc). The API is straightforward and explicit. ## Decision We propose to port the `collections` API, whose implementation lives in [NibiruChain/collections](https://github.com/NibiruChain/collections) to cosmos-sdk. Collections implements four different storage handlers types: * `Map`: which deals with simple `key=>object` mappings. * `KeySet`: which acts as a `Set` and only retains keys and no object (usecase: allow-lists). * `Item`: which always contains only one object (usecase: Params) * `Sequence`: which implements a simple always increasing number (usecase: Nonces) * `IndexedMap`: builds on top of `Map` and `KeySet` and allows to create relationships with `Objects` and `Objects` secondary keys. All the collection APIs build on top of the simple `Map` type. Collections is fully generic, meaning that anything can be used as `Key` and `Value`. It can be a protobuf object or not. Collections types, in fact, delegate the duty of serialisation of keys and values to a secondary collections API component called `ValueEncoders` and `KeyEncoders`. `ValueEncoders` take care of converting a value to bytes (relevant only for `Map`). And offers a plug and play layer which allows us to change how we encode objects, which is relevant for swapping serialisation frameworks and enhancing performance. `Collections` already comes in with default `ValueEncoders`, specifically for: protobuf objects, special SDK types (sdk.Int, sdk.Dec). `KeyEncoders` take care of converting keys to bytes, `collections` already comes in with some default `KeyEncoders` for some privimite golang types (uint64, string, time.Time, ...) and some widely used sdk types (sdk.Acc/Val/ConsAddress, sdk.Int/Dec, ...). These default implementations also offer safety around proper lexicographic ordering and namespace-collision. Examples of the collections API can be found here: * introduction: [Link](https://github.com/NibiruChain/collections/tree/main/examples) * usage in nibiru: [x/oracle](https://github.com/NibiruChain/nibiru/blob/master/x/oracle/keeper/keeper.go#L32), x/perp (`https://github.com/NibiruChain/nibiru/blob/master/x/perp/keeper/keeper.go#L31`) * cosmos-sdk's x/staking migrated: [Link](https://github.com/testinginprod/cosmos-sdk/pull/22) ## Consequences ### Backwards Compatibility The design of `ValueEncoders` and `KeyEncoders` allows modules to retain the same `byte(key)=>byte(value)` mappings, making the upgrade to the new storage layer non-state breaking. ### Positive * ADR aimed at removing code from the SDK rather than adding it. Migrating just `x/staking` to collections would yield to a net decrease in LOC (even considering the addition of collections itself). * Simplifies and standardises storage layers across modules in the SDK. * Does not require to have to deal with protobuf. * It's pure golang code. * Generalisation over `KeyEncoders` and `ValueEncoders` allows us to not tie ourself to the data serialisation framework. * `KeyEncoders` and `ValueEncoders` can be extended to provide schema reflection. ### Negative * Golang generics are not as battle-tested as other Golang features, despite being used in production right now. * Collection types instantiation needs to be improved. ### Neutral `{neutral consequences}` ## Further Discussions * Automatic genesis import/export (not implemented because of API breakage) * Schema reflection ## References # ADR 063: Core Module API Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-063-core-module-api 2022-08-18 First Draft 2022-12-08 First Draft 2023-01-24 Updates ## Changelog * 2022-08-18 First Draft * 2022-12-08 First Draft * 2023-01-24 Updates ## Status ACCEPTED Partially Implemented ## Abstract A new core API is proposed as a way to develop cosmos-sdk applications that will eventually replace the existing `AppModule` and `sdk.Context` frameworks a set of core services and extension interfaces. This core API aims to: * be simpler * more extensible * more stable than the current framework * enable deterministic events and queries, * support event listeners * [ADR 033: Protobuf-based Inter-Module Communication](/sdk/latest/reference/architecture/adr-033-protobuf-inter-module-comm) clients. ## Context Historically modules have exposed their functionality to the framework via the `AppModule` and `AppModuleBasic` interfaces which have the following shortcomings: * both `AppModule` and `AppModuleBasic` need to be defined and registered which is counter-intuitive * apps need to implement the full interfaces, even parts they don't need (although there are workarounds for this), * interface methods depend heavily on unstable third party dependencies, in particular Comet, * legacy required methods have littered these interfaces for far too long In order to interact with the state machine, modules have needed to do a combination of these things: * get store keys from the app * call methods on `sdk.Context` which contains more or less the full set of capability available to modules. By isolating all the state machine functionality into `sdk.Context`, the set of functionalities available to modules are tightly coupled to this type. If there are changes to upstream dependencies (such as Comet) or new functionalities are desired (such as alternate store types), the changes need impact `sdk.Context` and all consumers of it (basically all modules). Also, all modules now receive `context.Context` and need to convert these to `sdk.Context`'s with a non-ergonomic unwrapping function. Any breaking changes to these interfaces, such as ones imposed by third-party dependencies like Comet, have the side effect of forcing all modules in the ecosystem to update in lock-step. This means it is almost impossible to have a version of the module which can be run with 2 or 3 different versions of the SDK or 2 or 3 different versions of another module. This lock-step coupling slows down overall development within the ecosystem and causes updates to components to be delayed longer than they would if things were more stable and loosely coupled. ## Decision The `core` API proposes a set of core APIs that modules can rely on to interact with the state machine and expose their functionalities to it that are designed in a principled way such that: * tight coupling of dependencies and unrelated functionalities is minimized or eliminated * APIs can have long-term stability guarantees * the SDK framework is extensible in a safe and straightforward way The design principles of the core API are as follows: * everything that a module wants to interact with in the state machine is a service * all services coordinate state via `context.Context` and don't try to recreate the "bag of variables" approach of `sdk.Context` * all independent services are isolated in independent packages with minimal APIs and minimal dependencies * the core API should be minimalistic and designed for long-term support (LTS) * a "runtime" module will implement all the "core services" defined by the core API and can handle all module functionalities exposed by core extension interfaces * other non-core and/or non-LTS services can be exposed by specific versions of runtime modules or other modules following the same design principles, this includes functionality that interacts with specific non-stable versions of third party dependencies such as Comet * the core API doesn't implement *any* functionality, it just defines types * go stable API compatibility guidelines are followed: [Link](https://go.dev/blog/module-compatibility) A "runtime" module is any module which implements the core functionality of composing an ABCI app, which is currently handled by `BaseApp` and the `ModuleManager`. Runtime modules which implement the core API are *intentionally* separate from the core API in order to enable more parallel versions and forks of the runtime module than is possible with the SDK's current tightly coupled `BaseApp` design while still allowing for a high degree of composability and compatibility. Modules which are built only against the core API don't need to know anything about which version of runtime, `BaseApp` or Comet in order to be compatible. Modules from the core mainline SDK could be easily composed with a forked version of runtime with this pattern. This design is intended to enable matrices of compatible dependency versions. Ideally a given version of any module is compatible with multiple versions of the runtime module and other compatible modules. This will allow dependencies to be selectively updated based on battle-testing. More conservative projects may want to update some dependencies slower than more fast moving projects. ### Core Services The following "core services" are defined by the core API. All valid runtime module implementations should provide implementations of these services to modules via both [dependency injection](/sdk/latest/reference/architecture/adr-057-app-wiring) and manual wiring. The individual services described below are all bundled in a convenient `appmodule.Service` "bundle service" so that for simplicity modules can declare a dependency on a single service. #### Store Services Store services will be defined in the `cosmossdk.io/core/store` package. The generic `store.KVStore` interface is the same as current SDK `KVStore` interface. Store keys have been refactored into store services which, instead of expecting the context to know about stores, invert the pattern and allow retrieving a store from a generic context. There are three store services for the three types of currently supported stores - regular kv-store, memory, and transient: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type KVStoreService interface { OpenKVStore(context.Context) KVStore } type MemoryStoreService interface { OpenMemoryStore(context.Context) KVStore } type TransientStoreService interface { OpenTransientStore(context.Context) KVStore } ``` Modules can use these services like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k msgServer) Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { store := k.kvStoreSvc.OpenKVStore(ctx) } ``` Just as with the current runtime module implementation, modules will not need to explicitly name these store keys, but rather the runtime module will choose an appropriate name for them and modules just need to request the type of store they need in their dependency injection (or manual) constructors. #### Event Service The event `Service` will be defined in the `cosmossdk.io/core/event` package. The event `Service` allows modules to emit typed and legacy untyped events: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package event type Service interface { // EmitProtoEvent emits events represented as a protobuf message (as described in ADR 032). // // Callers SHOULD assume that these events may be included in consensus. These events // MUST be emitted deterministically and adding, removing or changing these events SHOULD // be considered state-machine breaking. EmitProtoEvent(ctx context.Context, event protoiface.MessageV1) error // EmitKVEvent emits an event based on an event and kv-pair attributes. // // These events will not be part of consensus and adding, removing or changing these events is // not a state-machine breaking change. EmitKVEvent(ctx context.Context, eventType string, attrs ...KVEventAttribute) error // EmitProtoEventNonConsensus emits events represented as a protobuf message (as described in ADR 032), without // including it in blockchain consensus. // // These events will not be part of consensus and adding, removing or changing events is // not a state-machine breaking change. EmitProtoEventNonConsensus(ctx context.Context, event protoiface.MessageV1) error } ``` Typed events emitted with `EmitProto` should be assumed to be part of blockchain consensus (whether they are part of the block or app hash is left to the runtime to specify). Events emitted by `EmitKVEvent` and `EmitProtoEventNonConsensus` are not considered to be part of consensus and cannot be observed by other modules. If there is a client-side need to add events in patch releases, these methods can be used. #### Logger A logger (`cosmossdk.io/log`) must be supplied using `depinject`, and will be made available for modules to use via `depinject.In`. Modules using it should follow the current pattern in the SDK by adding the module name before using it. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ModuleInputs struct { depinject.In Logger log.Logger } func ProvideModule(in ModuleInputs) ModuleOutputs { keeper := keeper.NewKeeper( in.logger, ) } func NewKeeper(logger log.Logger) Keeper { return Keeper{ logger: logger.With(log.ModuleKey, "x/"+types.ModuleName), } } ``` ### Core `AppModule` extension interfaces Modules will provide their core services to the runtime module via extension interfaces built on top of the `cosmossdk.io/core/appmodule.AppModule` tag interface. This tag interface requires only two empty methods which allow `depinject` to identify implementors as `depinject.OnePerModule` types and as app module implementations: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type AppModule interface { depinject.OnePerModuleType // IsAppModule is a dummy method to tag a struct as implementing an AppModule. IsAppModule() } ``` Other core extension interfaces will be defined in `cosmossdk.io/core` should be supported by valid runtime implementations. #### `MsgServer` and `QueryServer` registration `MsgServer` and `QueryServer` registration is done by implementing the `HasServices` extension interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type HasServices interface { AppModule RegisterServices(grpc.ServiceRegistrar) } ``` Because of the `cosmos.msg.v1.service` protobuf option, required for `Msg` services, the same `ServiceRegitrar` can be used to register both `Msg` and query services. #### Genesis The genesis `Handler` functions - `DefaultGenesis`, `ValidateGenesis`, `InitGenesis` and `ExportGenesis` - are specified against the `GenesisSource` and `GenesisTarget` interfaces which will abstract over genesis sources which may be a single JSON object or collections of JSON objects that can be efficiently streamed. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // GenesisSource is a source for genesis data in JSON format. It may abstract over a // single JSON object or separate files for each field in a JSON object that can // be streamed over. Modules should open a separate io.ReadCloser for each field that // is required. When fields represent arrays they can efficiently be streamed // over. If there is no data for a field, this function should return nil, nil. It is // important that the caller closes the reader when done with it. type GenesisSource = func(field string) (io.ReadCloser, error) // GenesisTarget is a target for writing genesis data in JSON format. It may // abstract over a single JSON object or JSON in separate files that can be // streamed over. Modules should open a separate io.WriteCloser for each field // and should prefer writing fields as arrays when possible to support efficient // iteration. It is important the caller closers the writer AND checks the error // when done with it. It is expected that a stream of JSON data is written // to the writer. type GenesisTarget = func(field string) (io.WriteCloser, error) ``` All genesis objects for a given module are expected to conform to the semantics of a JSON object. Each field in the JSON object should be read and written separately to support streaming genesis. The [ORM](/sdk/latest/reference/architecture/adr-055-orm) and [collections](/sdk/latest/reference/architecture/adr-062-collections-state-layer) both support streaming genesis and modules using these frameworks generally do not need to write any manual genesis code. To support genesis, modules should implement the `HasGenesis` extension interface: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type HasGenesis interface { AppModule // DefaultGenesis writes the default genesis for this module to the target. DefaultGenesis(GenesisTarget) error // ValidateGenesis validates the genesis data read from the source. ValidateGenesis(GenesisSource) error // InitGenesis initializes module state from the genesis source. InitGenesis(context.Context, GenesisSource) error // ExportGenesis exports module state to the genesis target. ExportGenesis(context.Context, GenesisTarget) error } ``` #### Pre Blockers Modules that have functionality that runs before BeginBlock and should implement the has `HasPreBlocker` interfaces: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type HasPreBlocker interface { AppModule PreBlock(context.Context) error } ``` #### Begin and End Blockers Modules that have functionality that runs before transactions (begin blockers) or after transactions (end blockers) should implement the has `HasBeginBlocker` and/or `HasEndBlocker` interfaces: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type HasBeginBlocker interface { AppModule BeginBlock(context.Context) error } type HasEndBlocker interface { AppModule EndBlock(context.Context) error } ``` The `BeginBlock` and `EndBlock` methods will take a `context.Context`, because: * most modules don't need Comet information other than `BlockInfo` so we can eliminate dependencies on specific Comet versions * for the few modules that need Comet block headers and/or return validator updates, specific versions of the runtime module will provide specific functionality for interacting with the specific version(s) of Comet supported In order for `BeginBlock`, `EndBlock` and `InitGenesis` to send back validator updates and retrieve full Comet block headers, the runtime module for a specific version of Comet could provide services like this: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ValidatorUpdateService interface { SetValidatorUpdates(context.Context, []abci.ValidatorUpdate) } ``` Header Service defines a way to get header information about a block. This information is generalized for all implementations: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Service interface { GetHeaderInfo(context.Context) Info } type Info struct { Height int64 // Height returns the height of the block Hash []byte // Hash returns the hash of the block header Time time.Time // Time returns the time of the block ChainID string // ChainId returns the chain ID of the block } ``` Comet Service provides a way to get comet specific information: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Service interface { GetCometInfo(context.Context) Info } type CometInfo struct { Evidence []abci.Misbehavior // Misbehavior returns the misbehavior of the block // ValidatorsHash returns the hash of the validators // For Comet, it is the hash of the next validators ValidatorsHash []byte ProposerAddress []byte // ProposerAddress returns the address of the block proposer DecidedLastCommit abci.CommitInfo // DecidedLastCommit returns the last commit info } ``` If a user would like to provide a module other information they would need to implement another service like: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type RollKit Interface { ... } ``` We know these types will change at the Comet level and that also a very limited set of modules actually need this functionality, so they are intentionally kept out of core to keep core limited to the necessary, minimal set of stable APIs. #### Remaining Parts of AppModule The current `AppModule` framework handles a number of additional concerns which aren't addressed by this core API. These include: * gas * block headers * upgrades * registration of gogo proto and amino interface types * cobra query and tx commands * gRPC gateway * crisis module invariants * simulations Additional `AppModule` extension interfaces either inside or outside of core will need to be specified to handle these concerns. In the case of gogo proto and amino interfaces, the registration of these generally should happen as early as possible during initialization and in [ADR 057: App Wiring](/sdk/latest/reference/architecture/adr-057-app-wiring), protobuf type registration\ happens before dependency injection (although this could alternatively be done dedicated DI providers). gRPC gateway registration should probably be handled by the runtime module, but the core API shouldn't depend on gRPC gateway types as 1) we are already using an older version and 2) it's possible the framework can do this registration automatically in the future. So for now, the runtime module should probably provide some sort of specific type for doing this registration ex: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type GrpcGatewayInfo struct { Handlers []GrpcGatewayHandler } type GrpcGatewayHandler func(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error ``` which modules can return in a provider: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func ProvideGrpcGateway() GrpcGatewayInfo { return GrpcGatewayinfo { Handlers: []Handler { types.RegisterQueryHandlerClient } } } ``` Crisis module invariants and simulations are subject to potential redesign and should be managed with types defined in the crisis and simulation modules respectively. Extension interface for CLI commands will be provided via the `cosmossdk.io/client/v2` module and its [autocli](/sdk/latest/reference/architecture/adr-058-auto-generated-cli) framework. #### Example Usage Here is an example of setting up a hypothetical `foo` v2 module which uses the [ORM](/sdk/latest/reference/architecture/adr-055-orm) for its state management and genesis. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { db orm.ModuleDB evtSrv event.Service } func (k Keeper) RegisterServices(r grpc.ServiceRegistrar) { foov1.RegisterMsgServer(r, k) foov1.RegisterQueryServer(r, k) } func (k Keeper) BeginBlock(context.Context) error { return nil } func ProvideApp(config *foomodulev2.Module, evtSvc event.EventService, db orm.ModuleDB) (Keeper, appmodule.AppModule) { k := &Keeper{ db: db, evtSvc: evtSvc } return k, k } ``` ### Runtime Compatibility Version The `core` module will define a static integer var, `cosmossdk.io/core.RuntimeCompatibilityVersion`, which is a minor version indicator of the core module that is accessible at runtime. Correct runtime module implementations should check this compatibility version and return an error if the current `RuntimeCompatibilityVersion` is higher than the version of the core API that this runtime version can support. When new features are adding to the `core` module API that runtime modules are required to support, this version should be incremented. ### Runtime Modules The initial `runtime` module will simply be created within the existing `github.com/cosmos/cosmos-sdk` go module under the `runtime` package. This module will be a small wrapper around the existing `BaseApp`, `sdk.Context` and module manager and follow the Cosmos SDK's existing [0-based versioning](https://0ver.org). To move to semantic versioning as well as runtime modularity, new officially supported runtime modules will be created under the `cosmossdk.io/runtime` prefix. For each supported consensus engine a semantically-versioned go module should be created with a runtime implementation for that consensus engine. For example: * `cosmossdk.io/runtime/comet` * `cosmossdk.io/runtime/comet/v2` * `cosmossdk.io/runtime/rollkit` * etc. These runtime modules should attempt to be semantically versioned even if the underlying consensus engine is not. Also, because a runtime module is also a first class Cosmos SDK module, it should have a protobuf module config type. A new semantically versioned module config type should be created for each of these runtime module such that there is a 1:1 correspondence between the go module and module config type. This is the same practice should be followed for every semantically versioned Cosmos SDK module as described in [ADR 057: App Wiring](/sdk/latest/reference/architecture/adr-057-app-wiring). Currently, `github.com/cosmos/cosmos-sdk/runtime` uses the protobuf config type `cosmos.app.runtime.v1alpha1.Module`. When we have a standalone v1 comet runtime, we should use a dedicated protobuf module config type such as `cosmos.runtime.comet.v1.Module1`. When we release v2 of the comet runtime (`cosmossdk.io/runtime/comet/v2`) we should have a corresponding `cosmos.runtime.comet.v2.Module` protobuf type. In order to make it easier to support different consensus engines that support the same core module functionality as described in this ADR, a common go module should be created with shared runtime components. The easiest runtime components to share initially are probably the message/query router, inter-module client, service register, and event router. This common runtime module should be created initially as the `cosmossdk.io/runtime/common` go module. When this new architecture has been implemented, the main dependency for a Cosmos SDK module would be `cosmossdk.io/core` and that module should be able to be used with any supported consensus engine (to the extent that it does not explicitly depend on consensus engine specific functionality such as Comet's block headers). An app developer would then be able to choose which consensus engine they want to use by importing the corresponding runtime module. The current `BaseApp` would be refactored into the `cosmossdk.io/runtime/comet` module, the router infrastructure in `baseapp/` would be refactored into `cosmossdk.io/runtime/common` and support ADR 033, and eventually a dependency on `github.com/cosmos/cosmos-sdk` would no longer be required. In short, modules would depend primarily on `cosmossdk.io/core`, and each `cosmossdk.io/runtime/{consensus-engine}` would implement the `cosmossdk.io/core` functionality for that consensus engine. On additional piece that would need to be resolved as part of this architecture is how runtimes relate to the server. Likely it would make sense to modularize the current server architecture so that it can be used with any runtime even if that is based on a consensus engine besides Comet. This means that eventually the Comet runtime would need to encapsulate the logic for starting Comet and the ABCI app. ### Testing A mock implementation of all services should be provided in core to allow for unit testing of modules without needing to depend on any particular version of runtime. Mock services should allow tests to observe service behavior or provide a non-production implementation - for instance memory stores can be used to mock stores. For integration testing, a mock runtime implementation should be provided that allows composing different app modules together for testing without a dependency on runtime or Comet. ## Consequences ### Backwards Compatibility Early versions of runtime modules should aim to support as much as possible modules built with the existing `AppModule`/`sdk.Context` framework. As the core API is more widely adopted, later runtime versions may choose to drop support and only support the core API plus any runtime module specific APIs (like specific versions of Comet). The core module itself should strive to remain at the go semantic version `v1` as long as possible and follow design principles that allow for strong long-term support (LTS). Older versions of the SDK can support modules built against core with adaptors that convert wrap core `AppModule` implementations in implementations of `AppModule` that conform to that version of the SDK's semantics as well as by providing service implementations by wrapping `sdk.Context`. ### Positive * better API encapsulation and separation of concerns * more stable APIs * more framework extensibility * deterministic events and queries * event listeners * inter-module msg and query execution support * more explicit support for forking and merging of module versions (including runtime) ### Negative ### Neutral * modules will need to be refactored to use this API * some replacements for `AppModule` functionality still need to be defined in follow-ups (type registration, commands, invariants, simulations) and this will take additional design work ## Further Discussions * gas * block headers * upgrades * registration of gogo proto and amino interface types * cobra query and tx commands * gRPC gateway * crisis module invariants * simulations ## References * [ADR 033: Protobuf-based Inter-Module Communication](/sdk/latest/reference/architecture/adr-033-protobuf-inter-module-comm) * [ADR 057: App Wiring](/sdk/latest/reference/architecture/adr-057-app-wiring) * [ADR 055: ORM](/sdk/latest/reference/architecture/adr-055-orm) * [ADR 028: Public Key Addresses](/sdk/latest/reference/architecture/adr-028-public-key-addresses) * [Keeping Your Modules Compatible](https://go.dev/blog/module-compatibility) # ADR 64: ABCI 2.0 Integration (Phase II) Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-064-abci-2.0 ## Changelog * 2023-01-17: Initial Draft (@alexanderbez) * 2023-04-06: Add upgrading section (@alexanderbez) * 2023-04-10: Simplify vote extension state persistence (@alexanderbez) * 2023-07-07: Revise vote extension state persistence (@alexanderbez) * 2023-08-24: Revise vote extension power calculations and staking interface (@davidterpay) ## Status ACCEPTED ## Abstract This ADR outlines the continuation of the efforts to implement ABCI++ in the Cosmos SDK outlined in [ADR 060: ABCI 1.0 (Phase I)](/sdk/latest/reference/architecture/adr-060-abci-1.0). Specifically, this ADR outlines the design and implementation of ABCI 2.0, which includes `ExtendVote`, `VerifyVoteExtension` and `FinalizeBlock`. ## Context ABCI 2.0 continues the promised updates from ABCI++, specifically three additional ABCI methods that the application can implement in order to gain further control, insight and customization of the consensus process, unlocking many novel use-cases that previously not possible. We describe these three new methods below: ### `ExtendVote` This method allows each validator process to extend the pre-commit phase of the CometBFT consensus process. Specifically, it allows the application to perform custom business logic that extends the pre-commit vote and supply additional data as part of the vote, although they are signed separately by the same key. The data, called vote extension, will be broadcast and received together with the vote it is extending, and will be made available to the application in the next height. Specifically, the proposer of the next block will receive the vote extensions in `RequestPrepareProposal.local_last_commit.votes`. If the application does not have vote extension information to provide, it returns a 0-length byte array as its vote extension. **NOTE**: * Although each validator process submits its own vote extension, ONLY the *proposer* of the *next* block will receive all the vote extensions included as part of the pre-commit phase of the previous block. This means only the proposer will implicitly have access to all the vote extensions, via `RequestPrepareProposal`, and that not all vote extensions may be included, since a validator does not have to wait for all pre-commits, only 2/3. * The pre-commit vote is signed independently from the vote extension. ### `VerifyVoteExtension` This method allows validators to validate the vote extension data attached to each pre-commit message it receives. If the validation fails, the whole pre-commit message will be deemed invalid and ignored by CometBFT. CometBFT uses `VerifyVoteExtension` when validating a pre-commit vote. Specifically, for a pre-commit, CometBFT will: * Reject the message if it doesn't contain a signed vote AND a signed vote extension * Reject the message if the vote's signature OR the vote extension's signature fails to verify * Reject the message if `VerifyVoteExtension` was rejected by the app Otherwise, CometBFT will accept the pre-commit message. Note, this has important consequences on liveness, i.e., if vote extensions repeatedly cannot be verified by correct validators, CometBFT may not be able to finalize a block even if sufficiently many (+2/3) validators send pre-commit votes for that block. Thus, `VerifyVoteExtension` should be used with special care. CometBFT recommends that an application that detects an invalid vote extension SHOULD accept it in `ResponseVerifyVoteExtension` and ignore it in its own logic. ### `FinalizeBlock` This method delivers a decided block to the application. The application must execute the transactions in the block deterministically and update its state accordingly. Cryptographic commitments to the block and transaction results, returned via the corresponding parameters in `ResponseFinalizeBlock`, are included in the header of the next block. CometBFT calls it when a new block is decided. In other words, `FinalizeBlock` encapsulates the current ABCI execution flow of `BeginBlock`, one or more `DeliverTx`, and `EndBlock` into a single ABCI method. CometBFT will no longer execute requests for these legacy methods and instead will just simply call `FinalizeBlock`. ## Decision We will discuss changes to the Cosmos SDK to implement ABCI 2.0 in two distinct phases, `VoteExtensions` and `FinalizeBlock`. ### `VoteExtensions` Similarly for `PrepareProposal` and `ProcessProposal`, we propose to introduce two new handlers that an application can implement in order to provide and verify vote extensions. We propose the following new handlers for applications to implement: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ExtendVoteHandler func(sdk.Context, abci.RequestExtendVote) abci.ResponseExtendVote type VerifyVoteExtensionHandler func(sdk.Context, abci.RequestVerifyVoteExtension) abci.ResponseVerifyVoteExtension ``` An ephemeral context and state will be supplied to both handlers. The context will contain relevant metadata such as the block height and block hash. The state will be a cached version of the committed state of the application and will be discarded after the execution of the handler, this means that both handlers get a fresh state view and no changes made to it will be written. If an application decides to implement `ExtendVoteHandler`, it must return a non-nil `ResponseExtendVote.VoteExtension`. Recall, an implementation of `ExtendVoteHandler` does NOT need to be deterministic, however, given a set of vote extensions, `VerifyVoteExtensionHandler` must be deterministic, otherwise the chain may suffer from liveness faults. In addition, recall CometBFT proceeds in rounds for each height, so if a decision cannot be made about about a block proposal at a given height, CometBFT will proceed to the next round and thus will execute `ExtendVote` and `VerifyVoteExtension` again for the new round for each validator until 2/3 valid pre-commits can be obtained. Given the broad scope of potential implementations and use-cases of vote extensions, and how to verify them, most applications should choose to implement the handlers through a single handler type, which can have any number of dependencies injected such as keepers. In addition, this handler type could contain some notion of volatile vote extension state management which would assist in vote extension verification. This state management could be ephemeral or could be some form of on-disk persistence. Example: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // VoteExtensionHandler implements an Oracle vote extension handler. type VoteExtensionHandler struct { cdc Codec mk MyKeeper state VoteExtState // This could be a map or a DB connection object } // ExtendVoteHandler can do something with h.mk and possibly h.state to create // a vote extension, such as fetching a series of prices for supported assets. func (h VoteExtensionHandler) ExtendVoteHandler(ctx sdk.Context, req abci.RequestExtendVote) abci.ResponseExtendVote { prices := GetPrices(ctx, h.mk.Assets()) bz, err := EncodePrices(h.cdc, prices) if err != nil { panic(fmt.Errorf("failed to encode prices for vote extension: %w", err)) } // store our vote extension at the given height // // NOTE: Vote extensions can be overridden since we can timeout in a round. SetPrices(h.state, req, bz) return abci.ResponseExtendVote{ VoteExtension: bz } } // VerifyVoteExtensionHandler can do something with h.state and req to verify // the req.VoteExtension field, such as ensuring the provided oracle prices are // within some valid range of our prices. func (h VoteExtensionHandler) VerifyVoteExtensionHandler(ctx sdk.Context, req abci.RequestVerifyVoteExtension) abci.ResponseVerifyVoteExtension { prices, err := DecodePrices(h.cdc, req.VoteExtension) if err != nil { log("failed to decode vote extension", "err", err) return abci.ResponseVerifyVoteExtension{ Status: REJECT } } if err := ValidatePrices(h.state, req, prices); err != nil { log("failed to validate vote extension", "prices", prices, "err", err) return abci.ResponseVerifyVoteExtension{ Status: REJECT } } // store updated vote extensions at the given height // // NOTE: Vote extensions can be overridden since we can timeout in a round. SetPrices(h.state, req, req.VoteExtension) return abci.ResponseVerifyVoteExtension{ Status: ACCEPT } } ``` #### Vote Extension Propagation & Verification As mentioned previously, vote extensions for height `H` are only made available to the proposer at height `H+1` during `PrepareProposal`. However, in order to make vote extensions useful, all validators should have access to the agreed upon vote extensions at height `H` during `H+1`. Since CometBFT includes all the vote extension signatures in `RequestPrepareProposal`, we propose that the proposing validator manually "inject" the vote extensions along with their respective signatures via a special transaction, `VoteExtsTx`, into the block proposal during `PrepareProposal`. The `VoteExtsTx` will be populated with a single `ExtendedCommitInfo` object which is received directly from `RequestPrepareProposal`. For convention, the `VoteExtsTx` transaction should be the first transaction in the block proposal, although chains can implement their own preferences. For safety purposes, we also propose that the proposer itself verify all the vote extension signatures it receives in `RequestPrepareProposal`. A validator, upon a `RequestProcessProposal`, will receive the injected `VoteExtsTx` which includes the vote extensions along with their signatures. If no such transaction exists, the validator MUST REJECT the proposal. When a validator inspects a `VoteExtsTx`, it will evaluate each `SignedVoteExtension`. For each signed vote extension, the validator will generate the signed bytes and verify the signature. At least 2/3 valid signatures, based on voting power, must be received in order for the block proposal to be valid, otherwise the validator MUST REJECT the proposal. In order to have the ability to validate signatures, `BaseApp` must have access to the `x/staking` module, since this module stores an index from consensus address to public key. However, we will avoid a direct dependency on `x/staking` and instead rely on an interface instead. In addition, the Cosmos SDK will expose a default signature verification method which applications can use: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ValidatorStore interface { GetPubKeyByConsAddr(context.Context, sdk.ConsAddress) (cmtprotocrypto.PublicKey, error) } // ValidateVoteExtensions is a function that an application can execute in // ProcessProposal to verify vote extension signatures. func (app *BaseApp) ValidateVoteExtensions(ctx sdk.Context, currentHeight int64, extCommit abci.ExtendedCommitInfo) error { votingPower := 0 totalVotingPower := 0 for _, vote := range extCommit.Votes { totalVotingPower += vote.Validator.Power if !vote.SignedLastBlock || len(vote.VoteExtension) == 0 { continue } valConsAddr := sdk.ConsAddress(vote.Validator.Address) pubKeyProto, err := valStore.GetPubKeyByConsAddr(ctx, valConsAddr) if err != nil { return fmt.Errorf("failed to get public key for validator %s: %w", valConsAddr, err) } if len(vote.ExtensionSignature) == 0 { return fmt.Errorf("received a non-empty vote extension with empty signature for validator %s", valConsAddr) } cmtPubKey, err := cryptoenc.PubKeyFromProto(pubKeyProto) if err != nil { return fmt.Errorf("failed to convert validator %X public key: %w", valConsAddr, err) } cve := cmtproto.CanonicalVoteExtension{ Extension: vote.VoteExtension, Height: currentHeight - 1, // the vote extension was signed in the previous height Round: int64(extCommit.Round), ChainId: app.GetChainID(), } extSignBytes, err := cosmosio.MarshalDelimited(&cve) if err != nil { return fmt.Errorf("failed to encode CanonicalVoteExtension: %w", err) } if !cmtPubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) { return errors.New("received vote with invalid signature") } votingPower += vote.Validator.Power } if (votingPower / totalVotingPower) < threshold { return errors.New("not enough voting power for the vote extensions") } return nil } ``` Once at least 2/3 signatures, by voting power, are received and verified, the validator can use the vote extensions to derive additional data or come to some decision based on the vote extensions. > NOTE: It is very important to state, that neither the vote propagation technique > nor the vote extension verification mechanism described above is required for > applications to implement. In other words, a proposer is not required to verify > and propagate vote extensions along with their signatures nor are proposers > required to verify those signatures. An application can implement its own > PKI mechanism and use that to sign and verify vote extensions. #### Vote Extension Persistence In certain contexts, it may be useful or necessary for applications to persist data derived from vote extensions. In order to facilitate this use case, we propose to allow app developers to define a pre-Blocker hook which will be called at the very beginning of `FinalizeBlock`, i.e. before `BeginBlock` (see below). Note, we cannot allow applications to directly write to the application state during `ProcessProposal` because during replay, CometBFT will NOT call `ProcessProposal`, which would result in an incomplete state view. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (a MyApp) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock) error { voteExts := GetVoteExtensions(ctx, req.Txs) // Process and perform some compute on vote extensions, storing any resulting // state. if err a.processVoteExtensions(ctx, voteExts); if err != nil { return err } } ``` ### `FinalizeBlock` The existing ABCI methods `BeginBlock`, `DeliverTx`, and `EndBlock` have existed since the dawn of ABCI-based applications. Thus, applications, tooling, and developers have grown used to these methods and their use-cases. Specifically, `BeginBlock` and `EndBlock` have grown to be pretty integral and powerful within ABCI-based applications. E.g. an application might want to run distribution and inflation related operations prior to executing transactions and then have staking related changes to happen after executing all transactions. We propose to keep `BeginBlock` and `EndBlock` within the SDK's core module interfaces only so application developers can continue to build against existing execution flows. However, we will remove `BeginBlock`, `DeliverTx` and `EndBlock` from the SDK's `BaseApp` implementation and thus the ABCI surface area. What will then exist is a single `FinalizeBlock` execution flow. Specifically, in `FinalizeBlock` we will execute the application's `BeginBlock`, followed by execution of all the transactions, finally followed by execution of the application's `EndBlock`. Note, we will still keep the existing transaction execution mechanics within `BaseApp`, but all notions of `DeliverTx` will be removed, i.e. `deliverState` will be replace with `finalizeState`, which will be committed on `Commit`. However, there are current parameters and fields that exist in the existing `BeginBlock` and `EndBlock` ABCI types, such as votes that are used in distribution and byzantine validators used in evidence handling. These parameters exist in the `FinalizeBlock` request type, and will need to be passed to the application's implementations of `BeginBlock` and `EndBlock`. This means the Cosmos SDK's core module interfaces will need to be updated to reflect these parameters. The easiest and most straightforward way to achieve this is to just pass `RequestFinalizeBlock` to `BeginBlock` and `EndBlock`. Alternatively, we can create dedicated proxy types in the SDK that reflect these legacy ABCI types, e.g. `LegacyBeginBlockRequest` and `LegacyEndBlockRequest`. Or, we can come up with new types and names altogether. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *BaseApp) FinalizeBlock(req abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { ctx := ... if app.preBlocker != nil { ctx := app.finalizeBlockState.ctx rsp, err := app.preBlocker(ctx, req) if err != nil { return nil, err } if rsp.ConsensusParamsChanged { app.finalizeBlockState.ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) } } beginBlockResp, err := app.beginBlock(req) appendBlockEventAttr(beginBlockResp.Events, "begin_block") txExecResults := make([]abci.ExecTxResult, 0, len(req.Txs)) for _, tx := range req.Txs { result := app.runTx(runTxModeFinalize, tx) txExecResults = append(txExecResults, result) } endBlockResp, err := app.endBlock(app.finalizeBlockState.ctx) appendBlockEventAttr(beginBlockResp.Events, "end_block") return abci.ResponseFinalizeBlock{ TxResults: txExecResults, Events: joinEvents(beginBlockResp.Events, endBlockResp.Events), ValidatorUpdates: endBlockResp.ValidatorUpdates, ConsensusParamUpdates: endBlockResp.ConsensusParamUpdates, AppHash: nil, } } ``` #### Events Many tools, indexers and ecosystem libraries rely on the existence `BeginBlock` and `EndBlock` events. Since CometBFT now only exposes `FinalizeBlockEvents`, we find that it will still be useful for these clients and tools to still query for and rely on existing events, especially since applications will still define `BeginBlock` and `EndBlock` implementations. In order to facilitate existing event functionality, we propose that all `BeginBlock` and `EndBlock` events have a dedicated `EventAttribute` with `key=block` and `value=begin_block|end_block`. The `EventAttribute` will be appended to each event in both `BeginBlock` and `EndBlock` events\`. ### Upgrading CometBFT defines a consensus parameter, [`VoteExtensionsEnableHeight`](https://github.com/cometbft/cometbft/blob/v0.38.0-alpha.1/spec/abci/abci%2B%2B_app_requirements.md#abciparamsvoteextensionsenableheight), which specifies the height at which vote extensions are enabled and **required**. If the value is set to zero, which is the default, then vote extensions are disabled and an application is not required to implement and use vote extensions. However, if the value `H` is positive, at all heights greater than the configured height `H` vote extensions must be present (even if empty). When the configured height `H` is reached, `PrepareProposal` will not include vote extensions yet, but `ExtendVote` and `VerifyVoteExtension` will be called. Then, when reaching height `H+1`, `PrepareProposal` will include the vote extensions from height `H`. It is very important to note, for all heights after H: * Vote extensions CANNOT be disabled * They are mandatory, i.e. all pre-commit messages sent MUST have an extension attached (even if empty) When an application updates to the Cosmos SDK version with CometBFT v0.38 support, in the upgrade handler it must ensure to set the consensus parameter `VoteExtensionsEnableHeight` to the correct value. E.g. if an application is set to perform an upgrade at height `H`, then the value of `VoteExtensionsEnableHeight` should be set to any value `>=H+1`. This means that at the upgrade height, `H`, vote extensions will not be enabled yet, but at height `H+1` they will be enabled. ## Consequences ### Backwards Compatibility ABCI 2.0 is naturally not backwards compatible with prior versions of the Cosmos SDK and CometBFT. For example, an application that requests `RequestFinalizeBlock` to the same application that does not speak ABCI 2.0 will naturally fail. In addition, `BeginBlock`, `DeliverTx` and `EndBlock` will be removed from the application ABCI interfaces and along with the inputs and outputs being modified in the module interfaces. ### Positive * `BeginBlock` and `EndBlock` semantics remain, so burden on application developers should be limited. * Less communication overhead as multiple ABCI requests are condensed into a single request. * Sets the groundwork for optimistic execution. * Vote extensions allow for an entirely new set of application primitives to be developed, such as in-process price oracles and encrypted mempools. ### Negative * Some existing Cosmos SDK core APIs may need to be modified and thus broken. * Signature verification in `ProcessProposal` of 100+ vote extension signatures will add significant performance overhead to `ProcessProposal`. Granted, the signature verification process can happen concurrently using an error group with `GOMAXPROCS` goroutines. ### Neutral * Having to manually "inject" vote extensions into the block proposal during `PrepareProposal` is an awkward approach and takes up block space unnecessarily. * The requirement of `ResetProcessProposalState` can create a footgun for application developers if they're not careful, but this is necessary in order for applications to be able to commit state from vote extension computation. ## Further Discussions Future discussions include design and implementation of ABCI 3.0, which is a continuation of ABCI++ and the general discussion of optimistic execution. ## References * [ADR 060: ABCI 1.0 (Phase I)](/sdk/latest/reference/architecture/adr-060-abci-1.0) # ADR-065: Store V2 Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-065-store-v2 Feb 14, 2023: Initial Draft (@alexanderbez) ## Changelog * Feb 14, 2023: Initial Draft (@alexanderbez) ## Status DRAFT ## Abstract The storage and state primitives that Cosmos SDK based applications have used have by and large not changed since the launch of the inaugural Cosmos Hub. The demands and needs of Cosmos SDK based applications, from both developer and client UX perspectives, have evolved and outgrown the ecosystem since these primitives were first introduced. Over time as these applications have gained significant adoption, many critical shortcomings and flaws have been exposed in the state and storage primitives of the Cosmos SDK. In order to keep up with the evolving demands and needs of both clients and developers, a major overhaul to these primitives are necessary. ## Context The Cosmos SDK provides application developers with various storage primitives for dealing with application state. Specifically, each module contains its own merkle commitment data structure -- an IAVL tree. In this data structure, a module can store and retrieve key-value pairs along with Merkle commitments, i.e. proofs, to those key-value pairs indicating that they do or do not exist in the global application state. This data structure is the base layer `KVStore`. In addition, the SDK provides abstractions on top of this Merkle data structure. Namely, a root multi-store (RMS) is a collection of each module's `KVStore`. Through the RMS, the application can serve queries and provide proofs to clients in addition to provide a module access to its own unique `KVStore` though the use of `StoreKey`, which is an OCAP primitive. There are further layers of abstraction that sit between the RMS and the underlying IAVL `KVStore`. A `GasKVStore` is responsible for tracking gas IO consumption for state machine reads and writes. A `CacheKVStore` is responsible for providing a way to cache reads and buffer writes to make state transitions atomic, e.g. transaction execution or governance proposal execution. There are a few critical drawbacks to these layers of abstraction and the overall design of storage in the Cosmos SDK: * Since each module has its own IAVL `KVStore`, commitments are not [atomic](https://github.com/cosmos/cosmos-sdk/issues/14625) * Note, we can still allow modules to have their own IAVL `KVStore`, but the IAVL library will need to support the ability to pass a DB instance as an argument to various IAVL APIs. * Since IAVL is responsible for both state storage and commitment, running an archive node becomes increasingly expensive as disk space grows exponentially. * As the size of a network increases, various performance bottlenecks start to emerge in many areas such as query performance, network upgrades, state migrations, and general application performance. * Developer UX is poor as it does not allow application developers to experiment with different types of approaches to storage and commitments, along with the complications of many layers of abstractions referenced above. See the [Storage Discussion](https://github.com/cosmos/cosmos-sdk/discussions/13545) for more information. ## Alternatives There was a previous attempt to refactor the storage layer described in [ADR-040](/sdk/v0.50/build/architecture/adr-040-storage-and-smt-state-commitments). However, this approach mainly stems on the short comings of IAVL and various performance issues around it. While there was a (partial) implementation of [ADR-040](/sdk/v0.50/build/architecture/adr-040-storage-and-smt-state-commitments), it was never adopted for a variety of reasons, such as the reliance on using an SMT, which was more in a research phase, and some design choices that couldn't be fully agreed upon, such as the snap-shotting mechanism that would result in massive state bloat. ## Decision We propose to build upon some of the great ideas introduced in [ADR-040](/sdk/v0.50/build/architecture/adr-040-storage-and-smt-state-commitments), while being a bit more flexible with the underlying implementations and overall less intrusive. Specifically, we propose to: * Separate the concerns of state commitment (**SC**), needed for consensus, and state storage (**SS**), needed for state machine and clients. * Reduce layers of abstractions necessary between the RMS and underlying stores. * Provide atomic module store commitments by providing a batch database object to core IAVL APIs. * Reduce complexities in the `CacheKVStore` implementation while also improving performance\[3]. Furthermore, we will keep the IAVL is the backing [commitment](https://cryptography.fandom.com/wiki/Commitment_scheme) store for the time being. While we might not fully settle on the use of IAVL in the long term, we do not have strong empirical evidence to suggest a better alternative. Given that the SDK provides interfaces for stores, it should be sufficient to change the backing commitment store in the future should evidence arise to warrant a better alternative. However there is promising work being done to IAVL that should result in significant performance improvement \[1,2]. ### Separating SS and SC By separating SS and SC, it will allow for us to optimize against primary use cases and access patterns to state. Specifically, The SS layer will be responsible for direct access to data in the form of (key, value) pairs, whereas the SC layer (IAVL) will be responsible for committing to data and providing Merkle proofs. Note, the underlying physical storage database will be the same between both the SS and SC layers. So to avoid collisions between (key, value) pairs, both layers will be namespaced. #### State Commitment (SC) Given that the existing solution today acts as both SS and SC, we can simply repurpose it to act solely as the SC layer without any significant changes to access patterns or behavior. In other words, the entire collection of existing IAVL-backed module `KVStore`s will act as the SC layer. However, in order for the SC layer to remain lightweight and not duplicate a majority of the data held in the SS layer, we encourage node operators to keep tight pruning strategies. #### State Storage (SS) In the RMS, we will expose a *single* `KVStore` backed by the same physical database that backs the SC layer. This `KVStore` will be explicitly namespaced to avoid collisions and will act as the primary storage for (key, value) pairs. While we most likely will continue the use of `cosmos-db`, or some local interface, to allow for flexibility and iteration over preferred physical storage backends as research and benchmarking continues. However, we propose to hardcode the use of RocksDB as the primary physical storage backend. Since the SS layer will be implemented as a `KVStore`, it will support the following functionality: * Range queries * CRUD operations * Historical queries and versioning * Pruning The RMS will keep track of all buffered writes using a dedicated and internal `MemoryListener` for each `StoreKey`. For each block height, upon `Commit`, the SS layer will write all buffered (key, value) pairs under a [RocksDB user-defined timestamp](https://github.com/facebook/rocksdb/wiki/User-defined-Timestamp-%28Experimental%29) column family using the block height as the timestamp, which is an unsigned integer. This will allow a client to fetch (key, value) pairs at historical and current heights along with making iteration and range queries relatively performant as the timestamp is the key suffix. Note, we choose not to use a more general approach of allowing any embedded key/value database, such as LevelDB or PebbleDB, using height key-prefixed keys to effectively version state because most of these databases use variable length keys which would effectively make actions likes iteration and range queries less performant. Since operators might want pruning strategies to differ in SS compared to SC, e.g. having a very tight pruning strategy in SC while having a looser pruning strategy for SS, we propose to introduce an additional pruning configuration, with parameters that are identical to what exists in the SDK today, and allow operators to control the pruning strategy of the SS layer independently of the SC layer. Note, the SC pruning strategy must be congruent with the operator's state sync configuration. This is so as to allow state sync snapshots to execute successfully, otherwise, a snapshot could be triggered on a height that is not available in SC. #### State Sync The state sync process should be largely unaffected by the separation of the SC and SS layers. However, if a node syncs via state sync, the SS layer of the node will not have the state synced height available, since the IAVL import process is not setup in way to easily allow direct key/value insertion. A modification of the IAVL import process would be necessary to facilitate having the state sync height available. Note, this is not problematic for the state machine itself because when a query is made, the RMS will automatically direct the query correctly (see [Queries](#queries)). #### Queries To consolidate the query routing between both the SC and SS layers, we propose to have a notion of a "query router" that is constructed in the RMS. This query router will be supplied to each `KVStore` implementation. The query router will route queries to either the SC layer or the SS layer based on a few parameters. If `prove: true`, then the query must be routed to the SC layer. Otherwise, if the query height is available in the SS layer, the query will be served from the SS layer. Otherwise, we fall back on the SC layer. If no height is provided, the SS layer will assume the latest height. The SS layer will store a reverse index to lookup `LatestVersion -> timestamp(version)` which is set on `Commit`. #### Proofs Since the SS layer is naturally a storage layer only, without any commitments to (key, value) pairs, it cannot provide Merkle proofs to clients during queries. Since the pruning strategy against the SC layer is configured by the operator, we can therefore have the RMS route the query SC layer if the version exists and `prove: true`. Otherwise, the query will fall back to the SS layer without a proof. We could explore the idea of using state snapshots to rebuild an in-memory IAVL tree in real time against a version closest to the one provided in the query. However, it is not clear what the performance implications will be of this approach. ### Atomic Commitment We propose to modify the existing IAVL APIs to accept a batch DB object instead of relying on an internal batch object in `nodeDB`. Since each underlying IAVL `KVStore` shares the same DB in the SC layer, this will allow commits to be atomic. Specifically, we propose to: * Remove the `dbm.Batch` field from `nodeDB` * Update the `SaveVersion` method of the `MutableTree` IAVL type to accept a batch object * Update the `Commit` method of the `CommitKVStore` interface to accept a batch object * Create a batch object in the RMS during `Commit` and pass this object to each `KVStore` * Write the database batch after all stores have committed successfully Note, this will require IAVL to be updated to not rely or assume on any batch being present during `SaveVersion`. ## Consequences As a result of a new store V2 package, we should expect to see improved performance for queries and transactions due to the separation of concerns. We should also expect to see improved developer UX around experimentation of commitment schemes and storage backends for further performance, in addition to a reduced amount of abstraction around KVStores making operations such as caching and state branching more intuitive. However, due to the proposed design, there are drawbacks around providing state proofs for historical queries. ### Backwards Compatibility This ADR proposes changes to the storage implementation in the Cosmos SDK through an entirely new package. Interfaces may be borrowed and extended from existing types that exist in `store`, but no existing implementations or interfaces will be broken or modified. ### Positive * Improved performance of independent SS and SC layers * Reduced layers of abstraction making storage primitives easier to understand * Atomic commitments for SC * Redesign of storage types and interfaces will allow for greater experimentation such as different physical storage backends and different commitment schemes for different application modules ### Negative * Providing proofs for historical state is challenging ### Neutral * Keeping IAVL as the primary commitment data structure, although drastic performance improvements are being made ## Further Discussions ### Module Storage Control Many modules store secondary indexes that are typically solely used to support client queries, but are actually not needed for the state machine's state transitions. What this means is that these indexes technically have no reason to exist in the SC layer at all, as they take up unnecessary space. It is worth exploring what an API would look like to allow modules to indicate what (key, value) pairs they want to be persisted in the SC layer, implicitly indicating the SS layer as well, as opposed to just persisting the (key, value) pair only in the SS layer. ### Historical State Proofs It is not clear what the importance or demand is within the community of providing commitment proofs for historical state. While solutions can be devised such as rebuilding trees on the fly based on state snapshots, it is not clear what the performance implications are for such solutions. ### Physical DB Backends This ADR proposes usage of RocksDB to utilize user-defined timestamps as a versioning mechanism. However, other physical DB backends are available that may offer alternative ways to implement versioning while also providing performance improvements over RocksDB. E.g. PebbleDB supports MVCC timestamps as well, but we'll need to explore how PebbleDB handles compaction and state growth over time. ## References * \[1] [Link](https://github.com/cosmos/iavl/pull/676) * \[2] [Link](https://github.com/cosmos/iavl/pull/664) * \[3] [Link](https://github.com/cosmos/cosmos-sdk/issues/14990) # ADR 068: Preblock Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-068-preblock Sept 13, 2023: Initial Draft ## Changelog * Sept 13, 2023: Initial Draft ## Status DRAFT ## Abstract Introduce `PreBlock`, which runs before begin blocker other modules, and allows to modify consensus parameters, and the changes are visible to the following state machine logics. ## Context When upgrading to sdk 0.47, the storage format for consensus parameters changed, but in the migration block, `ctx.ConsensusParams()` is always `nil`, because it fails to load the old format using new code, it's supposed to be migrated by the `x/upgrade` module first, but unfortunately, the migration happens in `BeginBlocker` handler, which runs after the `ctx` is initialized. When we try to solve this, we find the `x/upgrade` module can't modify the context to make the consensus parameters visible for the other modules, the context is passed by value, and sdk team want to keep it that way, that's good for isolations between modules. ## Alternatives The first alternative solution introduced a `MigrateModuleManager`, which only includes the `x/upgrade` module right now, and baseapp will run their `BeginBlocker`s before the other modules, and reload context's consensus parameters in between. ## Decision Suggested this new lifecycle method. ### `PreBlocker` There are two semantics around the new lifecycle method: * It runs before the `BeginBlocker` of all modules * It can modify consensus parameters in storage, and signal the caller through the return value. When it returns `ConsensusParamsChanged=true`, the caller must refresh the consensus parameter in the finalize context: ``` app.finalizeBlockState.ctx = app.finalizeBlockState.ctx.WithConsensusParams(app.GetConsensusParams()) ``` The new ctx must be passed to all the other lifecycle methods. ## Consequences ### Backwards Compatibility ### Positive ### Negative ### Neutral ## Further Discussions ## Test Cases ## References * \[1] [Link](https://github.com/cosmos/cosmos-sdk/issues/16494) * \[2] [Link](https://github.com/cosmos/cosmos-sdk/pull/16583) * \[3] [Link](https://github.com/cosmos/cosmos-sdk/pull/17421) * \[4] [Link](https://github.com/cosmos/cosmos-sdk/pull/17713) # ADR 070: Unordered Transactions Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-070-unordered-account ## Changelog * Dec 4, 2023: Initial Draft (@yihuang, @tac0turtle, @alexanderbez) * Jan 30, 2024: Include section on deterministic transaction encoding * Mar 18, 2025: Revise implementation to use Cosmos SDK KV Store and require unique timeouts per-address (@technicallyty) * Apr 25, 2025: Add note about rejecting unordered txs with sequence values. ## Status ACCEPTED Not Implemented ## Abstract We propose a way to do replay-attack protection without enforcing the order of transactions and without requiring the use of monotonically increasing sequences. Instead, we propose the use of a time-based, ephemeral sequence. ## Context Account sequence values serve to prevent replay attacks and ensure transactions from the same sender are included into blocks and executed in sequential order. Unfortunately, this makes it difficult to reliably send many concurrent transactions from the same sender. Victims of such limitations include IBC relayers and crypto exchanges. ## Decision We propose adding a boolean field `unordered` and a google.protobuf.Timestamp field `timeout_timestamp` to the transaction body. Unordered transactions will bypass the traditional account sequence rules and follow the rules described below, without impacting traditional ordered transactions which will follow the same sequence rules as before. We will introduce new storage of time-based, ephemeral unordered sequences using the SDK's existing KV Store library. Specifically, we will leverage the existing x/auth KV store to store the unordered sequences. When an unordered transaction is included in a block, a concatenation of the `timeout_timestamp` and sender’s address bytes will be recorded to state (i.e. `542939323/`). In cases of multi-party signing, one entry per signer will be recorded to state. New transactions will be checked against the state to prevent duplicate submissions. To prevent the state from growing indefinitely, we propose the following: * Define an upper bound for the value of `timeout_timestamp` (i.e. 10 minutes). * Add PreBlocker method x/auth that removes state entries with a `timeout_timestamp` earlier than the current block time. ### Transaction Format ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message TxBody { ... bool unordered = 4; google.protobuf.Timestamp timeout_timestamp = 5 } ``` ### Replay Protection We facilitate replay protection by storing the unordered sequence in the Cosmos SDK KV store. Upon transaction ingress, we check if the transaction's unordered sequence exists in state, or if the TTL value is stale, i.e. before the current block time. If so, we reject it. Otherwise, we add the unordered sequence to the state. This section of the state will belong to the `x/auth` module. The state is evaluated during x/auth's `PreBlocker`. All transactions with an unordered sequence earlier than the current block time will be deleted. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (am AppModule) PreBlock(ctx context.Context) (appmodule.ResponsePreBlock, error) { err := am.accountKeeper.RemoveExpired(sdk.UnwrapSDKContext(ctx)) if err != nil { return nil, err } return &sdk.ResponsePreBlock{ ConsensusParamsChanged: false }, nil } ``` ```golang expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" "cosmossdk.io/collections" "cosmossdk.io/core/store" ) var ( // just arbitrarily picking some upper bound number. unorderedSequencePrefix = collections.NewPrefix(90) ) type AccountKeeper struct { // ... unorderedSequences collections.KeySet[collections.Pair[uint64, []byte]] } func (m *AccountKeeper) Contains(ctx sdk.Context, sender []byte, timestamp uint64) (bool, error) { return m.unorderedSequences.Has(ctx, collections.Join(timestamp, sender)) } func (m *AccountKeeper) Add(ctx sdk.Context, sender []byte, timestamp uint64) error { return m.unorderedSequences.Set(ctx, collections.Join(timestamp, sender)) } func (m *AccountKeeper) RemoveExpired(ctx sdk.Context) error { blkTime := ctx.BlockTime().UnixNano() it, err := m.unorderedSequences.Iterate(ctx, collections.NewPrefixUntilPairRange[uint64, []byte](uint64(blkTime))) if err != nil { return err } defer it.Close() keys, err := it.Keys() if err != nil { return err } for _, key := range keys { if err := m.unorderedSequences.Remove(ctx, key); err != nil { return err } } return nil } ``` ### AnteHandler Decorator To facilitate bypassing nonce verification, we must modify the existing `IncrementSequenceDecorator` AnteHandler decorator to skip the nonce verification when the transaction is marked as unordered. ```golang theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { if tx.UnOrdered() { return next(ctx, tx, simulate) } // ... } ``` We also introduce a new decorator to perform the unordered transaction verification. ```golang expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package ante import ( "slices" "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" errorsmod "cosmossdk.io/errors" ) var _ sdk.AnteDecorator = (*UnorderedTxDecorator)(nil) // UnorderedTxDecorator defines an AnteHandler decorator that is responsible for // checking if a transaction is intended to be unordered and, if so, evaluates // the transaction accordingly. An unordered transaction will bypass having its // nonce incremented, which allows fire-and-forget transaction broadcasting, // removing the necessity of ordering on the sender-side. // // The transaction sender must ensure that unordered=true and a timeout_height // is appropriately set. The AnteHandler will check that the transaction is not // a duplicate and will evict it from state when the timeout is reached. // // The UnorderedTxDecorator should be placed as early as possible in the AnteHandler // chain to ensure that during DeliverTx, the transaction is added to the unordered sequence state. type UnorderedTxDecorator struct { // maxUnOrderedTTL defines the maximum TTL a transaction can define. maxTimeoutDuration time.Duration txManager authkeeper.UnorderedTxManager } func NewUnorderedTxDecorator( utxm authkeeper.UnorderedTxManager, ) *UnorderedTxDecorator { return &UnorderedTxDecorator{ maxTimeoutDuration: 10 * time.Minute, txManager: utxm, } } func (d *UnorderedTxDecorator) AnteHandle( ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler, ) (sdk.Context, error) { if err := d.ValidateTx(ctx, tx); err != nil { return ctx, err } return next(ctx, tx, false) } func (d *UnorderedTxDecorator) ValidateTx(ctx sdk.Context, tx sdk.Tx) error { unorderedTx, ok := tx.(sdk.TxWithUnordered) if !ok || !unorderedTx.GetUnordered() { // If the transaction does not implement unordered capabilities or has the // unordered value as false, we bypass. return nil } blockTime := ctx.BlockTime() timeoutTimestamp := unorderedTx.GetTimeoutTimeStamp() if timeoutTimestamp.IsZero() || timeoutTimestamp.Unix() == 0 { return errorsmod.Wrap( sdkerrors.ErrInvalidRequest, "unordered transaction must have timeout_timestamp set", ) } if timeoutTimestamp.Before(blockTime) { return errorsmod.Wrap( sdkerrors.ErrInvalidRequest, "unordered transaction has a timeout_timestamp that has already passed", ) } if timeoutTimestamp.After(blockTime.Add(d.maxTimeoutDuration)) { return errorsmod.Wrapf( sdkerrors.ErrInvalidRequest, "unordered tx ttl exceeds %s", d.maxTimeoutDuration.String(), ) } execMode := ctx.ExecMode() if execMode == sdk.ExecModeSimulate { return nil } signerAddrs, err := getSigners(tx) if err != nil { return err } for _, signer := range signerAddrs { contains, err := d.txManager.Contains(ctx, signer, uint64(unorderedTx.GetTimeoutTimeStamp().Unix())) if err != nil { return errorsmod.Wrap( sdkerrors.ErrIO, "failed to check contains", ) } if contains { return errorsmod.Wrapf( sdkerrors.ErrInvalidRequest, "tx is duplicated for signer %x", signer, ) } if err := d.txManager.Add(ctx, signer, uint64(unorderedTx.GetTimeoutTimeStamp().Unix())); err != nil { return errorsmod.Wrap( sdkerrors.ErrIO, "failed to add unordered sequence to state", ) } } return nil } func getSigners(tx sdk.Tx) ([][]byte, error) { sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { return nil, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") } return sigTx.GetSigners() } ``` ### Unordered Sequences Unordered sequences provide a simple, straightforward mechanism to protect against both transaction malleability and transaction duplication. It is important to note that the unordered sequence must still be unique. However, the value is not required to be strictly increasing as with regular sequences, and the order in which the node receives the transactions no longer matters. Clients can handle building unordered transactions similarly to the code below: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} for _, tx := range txs { tx.SetUnordered(true) tx.SetTimeoutTimestamp(time.Now() + 1 * time.Nanosecond) } ``` We will reject transactions that have both sequence and unordered timeouts set. We do this to avoid assuming the intent of the user. ### State Management The storage of unordered sequences will be facilitated using the Cosmos SDK's KV Store service. ## Note On Previous Design Iteration The previous iteration of unordered transactions worked by using an ad-hoc state-management system that posed severe risks and a vector for duplicated tx processing. It relied on graceful app closure which would flush the current state of the unordered sequence mapping. If the 2/3's of the network crashed, and the graceful closure did not trigger, the system would lose track of all sequences in the mapping, allowing those transactions to be replayed. The implementation proposed in the updated version of this ADR solves this by writing directly to the Cosmos KV Store. While this is less performant, for the initial implementation, we opted to choose a safer path and postpone performance optimizations until we have more data on real-world impacts and a more battle-tested approach to optimization. Additionally, the previous iteration relied on using hashes to create what we call an "unordered sequence." There are known issues with transaction malleability in Cosmos SDK signing modes. This ADR gets away from this problem by enforcing single-use unordered nonces, instead of deriving nonces from bytes in the transaction. ## Consequences ### Positive * Support unordered transaction inclusion, enabling the ability to "fire and forget" many transactions at once. ### Negative * Requires additional storage overhead. * Requirement of unique timestamps per transaction causes a small amount of additional overhead for clients. Clients must ensure each transaction's timeout timestamp is different. However, nanosecond differentials suffice. * Usage of Cosmos SDK KV store is slower in comparison to using a non-merklized store or ad-hoc methods, and block times may slow down as a result. ## References * [Link](https://github.com/cosmos/cosmos-sdk/issues/13009) # Cosmos SDK Transaction Malleability Risk Review and Recommendations Source: https://docs.cosmos.network/sdk/latest/reference/architecture/adr-076-tx-malleability 2025-03-10: Initial draft (@aaronc) ## Changelog * 2025-03-10: Initial draft (@aaronc) ## Status PROPOSED: Not Implemented ## Abstract Several encoding and sign mode related issues have historically resulted in the possibility that Cosmos SDK transactions may be re-encoded in such a way as to change their hash (and in rare cases, their meaning) without invalidating the signature. This document details these cases, their potential risks, the extent to which they have been addressed, and provides recommendations for future improvements. ## Review One naive assumption about Cosmos SDK transactions is that hashing the raw bytes of a submitted transaction creates a safe unique identifier for the transaction. In reality, there are multiple ways in which transactions could be manipulated to create different transaction bytes (and as a result different hashes) that still pass signature verification. This document attempts to enumerate the various potential transaction "malleability" risks that we have identified and the extent to which they have or have not been addressed in various sign modes. We also identify vulnerabilities that could be introduced if developers make changes in the future without careful consideration of the complexities involved with transaction encoding, sign modes and signatures. ### Risks Associated with Malleability The malleability of transactions poses the following potential risks to end users: * unsigned data could get added to transactions and be processed by state machines * clients often rely on transaction hashes for checking transaction status, but whether or not submitted transaction hashes match processed transaction hashes depends primarily on good network actors rather than fundamental protocol guarantees * transactions could potentially get executed more than once (faulty replay protection) If a client generates a transaction, keeps a record of its hash and then attempts to query nodes to check the transaction's status, this process may falsely conclude that the transaction had not been processed if an intermediary processor decoded and re-encoded the transaction with different encoding rules (either maliciously or unintentionally). As long as no malleability is present in the signature bytes themselves, clients *should* query transactions by signature instead of hash. Not being cognizant of this risk may lead clients to submit the same transaction multiple times if they believe that earlier transactions had failed or gotten lost in processing. This could be an attack vector against users if wallets primarily query transactions by hash. If the state machine were to rely on transaction hashes as a replay mechanism itself, this would be faulty and not provide the intended replay protection. Instead, the state machine should rely on deterministic representations of transactions rather than the raw encoding, or other nonces, if they want to provide some replay protection that doesn't rely on a monotonically increasing account sequence number. ### Sources of Malleability #### Non-deterministic Protobuf Encoding Cosmos SDK transactions are encoded using protobuf binary encoding when they are submitted to the network. Protobuf binary is not inherently a deterministic encoding meaning that the same logical payload could have several valid bytes representations. In a basic sense, this means that protobuf in general can be decoded and re-encoded to produce a different byte stream (and thus different hash) without changing the logical meaning of the bytes. [ADR 027: Deterministic Protobuf Serialization](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-027-deterministic-protobuf-serialization.md) describes in detail what needs to be done to produce what we consider to be a "canonical", deterministic protobuf serialization. Briefly, the following sources of malleability at the encoding level have been identified and are addressed by this specification: * fields can be emitted in any order * default field values can be included or omitted, and this doesn't change meaning unless `optional` is used * `repeated` fields of scalars may use packed or "regular" encoding * `varint`s can include extra ignored bits * extra fields may be added and are usually simply ignored by decoders. [ADR 020](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-020-protobuf-transaction-encoding.md#unknown-field-filtering) specifies that in general such extra fields should cause messages and transactions to be rejected) When using `SIGN_MODE_DIRECT` none of the above malleabilities will be tolerated because: * signatures of messages and extensions must be done over the raw encoded bytes of those fields * the outer tx envelope (`TxRaw`) must follow ADR 027 rules or be rejected Transactions signed with `SIGN_MODE_LEGACY_AMINO_JSON`, however, have no way of protecting against the above malleabilities because what is signed is a JSON representation of the logical contents of the transaction. These logical contents could have any number of valid protobuf binary encodings, so in general there are no guarantees regarding transaction hash with Amino JSON signing. In addition to being aware of the general non-determinism of protobuf binary, developers need to pay special attention to make sure that unknown protobuf fields get rejected when developing new capabilities related to protobuf transactions. The protobuf serialization format was designed with the assumption that unknown data known to encoders could safely be ignored by decoders. This assumption may have been fairly safe within the walled garden of Google's centralized infrastructure. However, in distributed blockchain systems, this assumption is generally unsafe. If a newer client encodes a protobuf message with data intended for a newer server, it is not safe for an older server to simply ignore and discard instructions that it does not understand. These instructions could include critical information that the transaction signer is relying upon and just assuming that it is unimportant is not safe. [ADR 020](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-020-protobuf-transaction-encoding.md#unknown-field-filtering) specifies some provisions for "non-critical" fields which can safely be ignored by older servers. In practice, I have not seen any valid usages of this. It is something in the design that maintainers should be aware of, but it may not be necessary or even 100% safe. #### Non-deterministic Value Encoding In addition to the non-determinism present in protobuf binary itself, some protobuf field data is encoded using a micro-format which itself may not be deterministic. Consider for instance integer or decimal encoding. Some decoders may allow for the presence of leading or trailing zeros without changing the logical meaning, ex. `00100` vs `100` or `100.00` vs `100`. So if a sign mode encodes numbers deterministically, but decoders accept multiple representations, a user may sign over the value `100` while `0100` gets encoded. This would be possible with Amino JSON to the extent that the integer decoder accepts leading zeros. I believe the current `Int` implementation will reject this, however, it is probably possible to encode a octal or hexadecimal representation in the transaction whereas the user signs over a decimal integer. #### Signature Encoding Signatures themselves are encoded using a micro-format specific to the signature algorithm being used and sometimes these micro-formats can allow for non-determinism (multiple valid bytes for the same signature). Most of the signature algorithms supported by the SDK should reject non-canonical bytes in their current implementation. However, the `Multisignature` protobuf type uses normal protobuf encoding and there is no check as to whether the decoded bytes followed canonical ADR 027 rules or not. Therefore, multisig transactions can have malleability in their signatures. Any new or custom signature algorithms must make sure that they reject any non-canonical bytes, otherwise even with `SIGN_MODE_DIRECT` there can be transaction hash malleability by re-encoding signatures with a non-canonical representation. #### Fields not covered by Amino JSON Another area that needs to be addressed carefully is the discrepancy between `AminoSignDoc`(see [`aminojson.proto`](https://github.com/cosmos/cosmos-sdk/blob/v0.50.10/x/tx/signing/aminojson/internal/aminojsonpb/aminojson.proto)) used for `SIGN_MODE_LEGACY_AMINO_JSON` and the actual contents of `TxBody` and `AuthInfo` (see [`tx.proto`](https://github.com/cosmos/cosmos-sdk/blob/v0.50.10/proto/cosmos/tx/v1beta1/tx.proto)). If fields get added to `TxBody` or `AuthInfo`, they must either have a corresponding representing in `AminoSignDoc` or Amino JSON signatures must be rejected when those new fields are set. Making sure that this is done is a highly manual process, and developers could easily make the mistake of updating `TxBody` or `AuthInfo` without paying any attention to the implementation of `GetSignBytes` for Amino JSON. This is a critical vulnerability in which unsigned content can now get into the transaction and signature verification will pass. ## Sign Mode Summary and Recommendations The sign modes officially supported by the SDK are `SIGN_MODE_DIRECT`, `SIGN_MODE_TEXTUAL`, `SIGN_MODE_DIRECT_AUX`, and `SIGN_MODE_LEGACY_AMINO_JSON`. `SIGN_MODE_LEGACY_AMINO_JSON` is used commonly by wallets and is currently the only sign mode supported on Nano Ledger hardware devices (although `SIGN_MODE_TEXTUAL` was designed to also support hardware devices). `SIGN_MODE_DIRECT` is the simplest sign mode and its usage is also fairly common. `SIGN_MODE_DIRECT_AUX` is a variant of `SIGN_MODE_DIRECT` that can be used by auxiliary signers in a multi-signer transaction by those signers who are not paying gas. `SIGN_MODE_TEXTUAL` was intended as a replacement for `SIGN_MODE_LEGACY_AMINO_JSON`, but as far as we know it has not been adopted by any clients yet and thus is not in active use. All known malleability concerns have been addressed in the current implementation of `SIGN_MODE_DIRECT`. The only known malleability that could occur with a transaction signed with `SIGN_MODE_DIRECT` would need to be in the signature bytes themselves. Since signatures are not signed over, it is impossible for any sign mode to address this directly and instead signature algorithms need to take care to reject any non-canonically encoded signature bytes to prevent malleability. For the known malleability of the `Multisignature` type, we should make sure that any valid signatures were encoded following canonical ADR 027 rules when doing signature verification. `SIGN_MODE_DIRECT_AUX` provides the same level of safety as `SIGN_MODE_DIRECT` because * the raw encoded `TxBody` bytes are signed over in `SignDocDirectAux`, and * a transaction using `SIGN_MODE_DIRECT_AUX` still requires the primary signer to sign the transaction with `SIGN_MODE_DIRECT` `SIGN_MODE_TEXTUAL` also provides the same level of safety as `SIGN_MODE_DIRECT` because the hash of the raw encoded `TxBody` and `AuthInfo` bytes are signed over. Unfortunately, the vast majority of unaddressed malleability risks affect `SIGN_MODE_LEGACY_AMINO_JSON` and this sign mode is still commonly used. It is recommended that the following improvements be made to Amino JSON signing: * hashes of `TxBody` and `AuthInfo` should be added to `AminoSignDoc` so that encoding-level malleablity is addressed * when constructing `AminoSignDoc`, [protoreflect](https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect) API should be used to ensure that there no fields in `TxBody` or `AuthInfo` which do not have a mapping in `AminoSignDoc` have been set * fields present in `TxBody` or `AuthInfo` that are not present in `AminoSignDoc` (such as extension options) should be added to `AminoSignDoc` if possible ## Testing To test that transactions are resistant to malleability, we can develop a test suite to run against all sign modes that attempts to manipulate transaction bytes in the following ways: * changing protobuf encoding by * reordering fields * setting default values * adding extra bits to varints, or * setting new unknown fields * modifying integer and decimal values encoded as strings with leading or trailing zeros Whenever any of these manipulations is done, we should observe that the sign doc bytes for the sign mode being tested also change, meaning that the corresponding signatures will also have to change. In the case of Amino JSON, we should also develop tests which ensure that if any `TxBody` or `AuthInfo` field not supported by Amino's `AminoSignDoc` is set that signing fails. In the general case of transaction decoding, we should have unit tests to ensure that * any `TxRaw` bytes which do not follow ADR 027 canonical encoding cause decoding to fail, and * any top-level transaction elements including `TxBody`, `AuthInfo`, public keys, and messages which have unknown fields set cause the transaction to be rejected (this ensures that ADR 020 unknown field filtering is properly applied) For each supported signature algorithm, there should also be unit tests to ensure that signatures must be encoded canonically or get rejected. ## References * [ADR 027: Deterministic Protobuf Serialization](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-027-deterministic-protobuf-serialization.md) * [ADR 020](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-020-protobuf-transaction-encoding.md#unknown-field-filtering) * [`aminojson.proto`](https://github.com/cosmos/cosmos-sdk/blob/v0.50.10/x/tx/signing/aminojson/internal/aminojsonpb/aminojson.proto) * [`tx.proto`](https://github.com/cosmos/cosmos-sdk/blob/v0.50.10/proto/cosmos/tx/v1beta1/tx.proto) # RFC Creation Process Source: https://docs.cosmos.network/sdk/latest/reference/rfc/PROCESS 1. Copy the `rfc-template.md` file. Use the following filename pattern: `rfc-next_number-title.md` 2. Create a draft Pull Request if you want to get an early feedback. 3. Make sure the context and a solution is clear and well documented. 4. Add an entry to a list in the [README](/sdk/v0.50/build/rfc/README) file. 5. Create a Pull Request to propose a new ADR. ## What is an RFC? An RFC is a sort of async whiteboarding session. It is meant to replace the need for a distributed team to come together to make a decision. Currently, the Cosmos SDK team and contributors are distributed around the world. The team conducts working groups to have a synchronous discussion and an RFC can be used to capture the discussion for a wider audience to better understand the changes that are coming to the software. The main difference the Cosmos SDK is defining as a differentiation between RFC and ADRs is that one is to come to consensus and circulate information about a potential change or feature. An ADR is used if there is already consensus on a feature or change and there is not a need to articulate the change coming to the software. An ADR will articulate the changes and have a lower amount of communication . ## RFC life cycle RFC creation is an **iterative** process. An RFC is meant as a distributed collaboration session, it may have many comments and is usually the byproduct of no working group or synchronous communication 1. Proposals could start with a new GitHub Issue, be a result of existing Issues or a discussion. 2. An RFC doesn't have to arrive to `main` with an *accepted* status in a single PR. If the motivation is clear and the solution is sound, we SHOULD be able to merge it and keep a *proposed* status. It's preferable to have an iterative approach rather than long, not merged Pull Requests. 3. If a *proposed* RFC is merged, then it should clearly document outstanding issues either in the RFC document notes or in a GitHub Issue. 4. The PR SHOULD always be merged. In the case of a faulty RFC, we still prefer to merge it with a *rejected* status. The only time the RFC SHOULD NOT be merged is if the author abandons it. 5. Merged RFCs SHOULD NOT be pruned. 6. If there is consensus and enough feedback then the RFC can be accepted. > Note: An RFC is written when there is no working group or team session on the problem. RFC's are meant as a distributed whiteboarding session. If there is a working group on the proposal there is no need to have an RFC as there is synchronous whiteboarding going on. ### RFC status Status has two components: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {CONSENSUS STATUS} ``` #### Consensus Status ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DRAFT -> PROPOSED -> LAST CALL yyyy-mm-dd -> ACCEPTED | REJECTED -> SUPERSEDED by ADR-xxx \ | \ | v v ABANDONED ``` * `DRAFT`: \[optional] an ADR which is work in progress, not being ready for a general review. This is to present an early work and get an early feedback in a Draft Pull Request form. * `PROPOSED`: an ADR covering a full solution architecture and still in the review - project stakeholders haven't reached agreement yet. * `LAST CALL `: \[optional] clear notify that we are close to accept updates. Changing a status to `LAST CALL` means that social consensus (of Cosmos SDK maintainers) has been reached and we still want to give it a time to let the community react or analyze. * `ACCEPTED`: ADR which will represent a currently implemented or to be implemented architecture design. * `REJECTED`: ADR can go from PROPOSED or ACCEPTED to rejected if the consensus among project stakeholders will decide so. * `SUPERSEDED by ADR-xxx`: ADR which has been superseded by a new ADR. * `ABANDONED`: the ADR is no longer pursued by the original authors. ## Language used in RFC * The background/goal should be written in the present tense. * Avoid using a first, personal form. # Requests for Comments Source: https://docs.cosmos.network/sdk/latest/reference/rfc/README A Request for Comments (RFC) is a record of discussion on an open-ended topic related to the design and implementation of the Cosmos SDK, for which no immediate decision is required. A Request for Comments (RFC) is a record of discussion on an open-ended topic related to the design and implementation of the Cosmos SDK, for which no immediate decision is required. The purpose of an RFC is to serve as a historical record of a high-level discussion that might otherwise only be recorded in an ad-hoc way (for example, via gists or Google docs) that are difficult to discover for someone after the fact. An RFC *may* give rise to more specific architectural *decisions* for the Cosmos SDK, but those decisions must be recorded separately in [Architecture Decision Records (ADR)](/sdk/latest/reference/architecture/README). As a rule of thumb, if you can articulate a specific question that needs to be answered, write an ADR. If you need to explore the topic and get input from others to know what questions need to be answered, an RFC may be appropriate. ## RFC Content An RFC should provide: * A **changelog**, documenting when and how the RFC has changed. * An **abstract**, briefly summarizing the topic so the reader can quickly tell whether it is relevant to their interest. * Any **background** a reader will need to understand and participate in the substance of the discussion (links to other documents are fine here). * The **discussion**, the primary content of the document. The `rfc-template.md` file includes placeholders for these sections. ## Table of Contents * [RFC-001: Tx Validation](/sdk/v0.50/build/rfc/rfc-001-tx-validation) # RFC 001: Transaction Validation Source: https://docs.cosmos.network/sdk/latest/reference/rfc/rfc-001-tx-validation 2023-03-12: Proposed ## Changelog * 2023-03-12: Proposed ## Background Transaction Validation is crucial to a functioning state machine. Within the Cosmos SDK there are two validation flows, one is outside the message server and the other within. The flow outside of the message server is the `ValidateBasic` function. It is called in the antehandler on both `CheckTx` and `DeliverTx`. There is an overhead and sometimes duplication of validation within these two flows. This extra validation provides an additional check before entering the mempool. With the deprecation of [`GetSigners`](https://github.com/cosmos/cosmos-sdk/issues/11275) we have the optionality to remove [sdk.Msg](https://github.com/cosmos/cosmos-sdk/blob/16a5404f8e00ddcf8857c8a55dca2f7c109c29bc/types/tx_msg.go#L16) and the `ValidateBasic` function. With the separation of CometBFT and Cosmos-SDK, there is a lack of control of what transactions get broadcasted and included in a block. This extra validation in the antehandler is meant to help in this case. In most cases the transaction is or should be simulated against a node for validation. With this flow transactions will be treated the same. ## Proposal The acceptance of this RFC would move validation within `ValidateBasic` to the message server in modules, update tutorials and docs to remove mention of using `ValidateBasic` in favour of handling all validation for a message where it is executed. We can and will still support the `ValidateBasic` function for users and provide an extension interface of the function once `sdk.Msg` is deprecated. > Note: This is how messages are handled in VMs like Ethereum and CosmWasm. ### Consequences The consequence of updating the transaction flow is that transaction that may have failed before with the `ValidateBasic` flow will now be included in a block and fees charged. # Specifications Source: https://docs.cosmos.network/sdk/latest/reference/spec/README This directory contains specifications for the modules of the Cosmos SDK as well as Interchain Standards (ICS) and other specifications. This directory contains specifications for the modules of the Cosmos SDK as well as Interchain Standards (ICS) and other specifications. Cosmos SDK applications hold this state in a Merkle store. Updates to the store may be made during transactions and at the beginning and end of every block. ## Cosmos SDK specifications * [Store](/sdk/v0.50/learn/advanced/store) - The core Merkle store that holds the state. * [Bech32](/sdk/v0.50/build/spec/addresses/bech32) - Address format for Cosmos SDK applications. ## Modules specifications Go the [module directory](/sdk/latest/modules/modules) ## CometBFT For details on the underlying blockchain and p2p protocols, see the [CometBFT specification](https://github.com/cometbft/cometbft/tree/v0.40.x/spec). # Specification of Modules Source: https://docs.cosmos.network/sdk/latest/reference/spec/SPEC_MODULE This file intends to outline the common structure for specifications within this directory. This file intends to outline the common structure for specifications within this directory. ## Tense For consistency, specs should be written in passive present tense. ## Pseudo-Code Generally, pseudo-code should be minimized throughout the spec. Often, simple bulleted-lists which describe a function's operations are sufficient and should be considered preferable. In certain instances, due to the complex nature of the functionality being described pseudo-code may be the most suitable form of specification. In these cases use of pseudo-code is permissible, but should be presented in a concise manner, ideally restricted to only the complex element as a part of a larger description. ## Common Layout The following generalized `README` structure should be used to breakdown specifications for modules. The following list is nonbinding and all sections are optional. * `# {Module Name}` - overview of the module * `## Concepts` - describe specialized concepts and definitions used throughout the spec * `## State` - specify and describe structures expected to be marshaled into the store, and their keys * `## State Transitions` - standard state transition operations triggered by hooks, messages, etc. * `## Messages` - specify message structure(s) and expected state machine behavior(s) * `## Begin Block` - specify any begin-block operations * `## End Block` - specify any end-block operations * `## Hooks` - describe available hooks to be called by/from this module * `## Events` - list and describe event tags used * `## Client` - list and describe CLI commands and gRPC and REST endpoints * `## Params` - list all module parameters, their types (in JSON) and examples * `## Future Improvements` - describe future improvements of this module * `## Tests` - acceptance tests * `## Appendix` - supplementary details referenced elsewhere within the spec ### Notation for key-value mapping Within `## State` the following notation `->` should be used to describe key to value mapping: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} key -> value ``` to represent byte concatenation the `|` may be used. In addition, encoding type may be specified, for example: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} 0x00 | addressBytes | address2Bytes -> amino(value_object) ``` Additionally, index mappings may be specified by mapping to the `nil` value, for example: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} 0x01 | address2Bytes | addressBytes -> nil ``` # What is an SDK standard? Source: https://docs.cosmos.network/sdk/latest/reference/spec/SPEC_STANDARD An SDK standard is a design document describing a particular protocol, standard, or feature expected to be used by the Cosmos SDK. An SDK standard should list the desired properties of the standard, explain the design rationale, and provide a concise but comprehensive technical specification. The primary author is responsible for pushing the proposal through the standardization process, soliciting input and support from the community, and communicating with relevant stakeholders to ensure (social) consensus. ## Sections An SDK standard consists of: * a synopsis, * overview and basic concepts, * technical specification, * history log, and * copyright notice. All top-level sections are required. References should be included inline as links, or tabulated at the bottom of the section if necessary. Included subsections should be listed in the order specified below. ### Table Of Contents Provide a table of contents at the top of the file to help readers. ### Synopsis The document should include a brief (\~200 word) synopsis providing a high-level description of and rationale for the specification. ### Overview and basic concepts This section should include a motivation subsection and a definition subsection if required: * *Motivation* - A rationale for the existence of the proposed feature, or the proposed changes to an existing feature. * *Definitions* - A list of new terms or concepts used in the document or required to understand it. ### System model and properties This section should include an assumption subsection if any, the mandatory properties subsection, and a dependency subsection. Note that the first two subsections are tightly coupled: how to enforce a property will depend directly on the assumptions made. This subsection is important to capture the interactions of the specified feature with the "rest-of-the-world," i.e., with other features of the ecosystem. * *Assumptions* - A list of any assumptions made by the feature designer. It should capture which features are used by the feature under specification, and what do we expect from them. * *Properties* - A list of the desired properties or characteristics of the feature specified, and expected effects or failures when the properties are violated. In case it is relevant, it can also include a list of properties that the feature does not guarantee. * *Dependencies* - A list of the features that use the feature under specification and how. ### Technical specification This is the main section of the document, and should contain protocol documentation, design rationale, required references, and technical details where appropriate. The section may have any or all of the following subsections, as appropriate to the particular specification. The API subsection is especially encouraged when appropriate. * *API* - A detailed description of the feature's API. * *Technical Details* - All technical details including syntax, diagrams, semantics, protocols, data structures, algorithms, and pseudocode as appropriate. The technical specification should be detailed enough such that separate correct implementations of the specification without knowledge of each other are compatible. * *Backwards Compatibility* - A discussion of compatibility (or lack thereof) with previous feature or protocol versions. * *Known Issues* - A list of known issues. This subsection is specially important for specifications of already in-use features. * *Example Implementation* - A concrete example implementation or description of an expected implementation to serve as the primary reference for implementers. ### History A specification should include a history section, listing any inspiring documents and a plaintext log of significant changes. See an example history section [below](#history-1). ### Copyright A specification should include a copyright section waiving rights via [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). ## Formatting ### General Specifications must be written in GitHub-flavored Markdown. For a GitHub-flavored Markdown cheat sheet, see [here](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). For a local Markdown renderer, see [here](https://github.com/joeyespo/grip). ### Language Specifications should be written in Simple English, avoiding obscure terminology and unnecessary jargon. For excellent examples of Simple English, please see the [Simple English Wikipedia](https://simple.wikipedia.org/wiki/Main_Page). The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in specifications are to be interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119). ### Pseudocode Pseudocode in specifications should be language-agnostic and formatted in a simple imperative standard, with line numbers, variables, simple conditional blocks, for loops, and English fragments where necessary to explain further functionality such as scheduling timeouts. LaTeX images should be avoided because they are challenging to review in diff form. Pseudocode for structs can be written in a simple language like TypeScript or golang, as interfaces. Example Golang pseudocode struct: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type CacheKVStore interface { cache: map[Key]Value parent: KVStore deleted: Key } ``` Pseudocode for algorithms should be written in simple Golang, as functions. Example pseudocode algorithm: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func get( store CacheKVStore, key Key) Value { value = store.cache.get(Key) if (value !== null) { return value } else { value = store.parent.get(key) store.cache.set(key, value) return value } } ``` ## History This specification was significantly inspired by and derived from IBC's [ICS](https://github.com/cosmos/ibc/blob/main/spec/ics-001-ics-standard/README.md), which was in turn derived from Ethereum's [EIP 1](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1.md). Nov 24, 2022 - Initial draft finished and submitted as a PR ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # Security Audits Source: https://docs.cosmos.network/sdk/latest/security/audits Security audits and transparency reports for Cosmos Stack components This page is auto-generated from the [cosmos/security](https://github.com/cosmos/security) repository. **Last synced:** Aug 31, 2026 | [View all audits](https://github.com/cosmos/security/tree/main/audits) Cosmos Labs maintains a comprehensive security program for all Cosmos Stack components. This page provides links to third-party security audits and transparency reports. ## Cosmos EVM * [Sherlock 2025 07 28 Final](https://github.com/cosmos/security/blob/main/audits/evm/sherlock_2025_07_28_final.pdf) ## Cosmos Hub (Gaia) * [2022 Liquid Staking Oak](https://github.com/cosmos/security/blob/main/audits/gaia/2022-liquid-staking-oak.pdf) ## Interchain Security (ICS) * [Informal Ics 2023](https://github.com/cosmos/security/blob/main/audits/ics/informal-ics-2023.pdf) ## Ledger **ledger/** * [2023 Zondax](https://github.com/cosmos/security/blob/main/audits/ledger/ledger/2023-zondax.pdf) * [2026 Zondax](https://github.com/cosmos/security/blob/main/audits/ledger/ledger/2026-zondax.pdf) ## Cosmos SDK * [Cosmos Sdk 2019 Final](https://github.com/cosmos/security/blob/main/audits/sdk/cosmos_sdk_2019_final.pdf) * [Cosmos Sdk V53 Audit Final](https://github.com/cosmos/security/blob/main/audits/sdk/cosmos_sdk_v53_audit_final.pdf) * [Group Module Audit](https://github.com/cosmos/security/blob/main/audits/sdk/group_module_audit.pdf) ## Transparency Reports * [Transparency Report 2023 2024](https://github.com/cosmos/security/blob/main/reports/transparency_report_2023_2024.pdf) ## Additional Resources * [Security and Maintenance Policy](/sdk/latest/security/security-policy) - Release and maintenance policy * [Bug Bounty Program](/sdk/latest/security/bug-bounty) - Report vulnerabilities and earn rewards * [cosmos/security Repository](https://github.com/cosmos/security) - Complete security documentation # Bug Bounty Program Source: https://docs.cosmos.network/sdk/latest/security/bug-bounty Security and maintenance policy documentation for the Cosmos Stack This content is sourced from the official [Cosmos Security](https://github.com/cosmos/security) repository. **Last sync:** Aug 31, 2026 | [View source](https://github.com/cosmos/security/blob/main/SECURITY.md) ## Introduction Cosmos Labs is committed to maintaining the security of the Cosmos Stack and supporting responsible vulnerability disclosure. We operate a bug bounty program to incentivize security researchers to identify and report security issues. This document defines the process for reporting vulnerabilities, describes the bug bounty program, and outlines Cosmos Labs’ approach to patching and public disclosure. *** ## Reporting a Vulnerability **Private Disclosure Required** Security vulnerabilities affecting the Cosmos ecosystem—including the Cosmos SDK, CometBFT, IBC, and other core components—must be reported privately through the channels listed below. * **Preferred:** Submit reports through the [Cosmos Immunefi Bug Bounty Program](https://immunefi.com/bug-bounty/cosmos/information/). > Reports submitted via email are *not eligible* for bounty rewards. > Only reports submitted through the Bug Bounty qualify for bounties. Public disclosure of vulnerabilities (including GitHub issues, blog posts, or social media) is prohibited until Cosmos Labs has remediated the issue and explicitly authorized disclosure. Disclosure timelines may be coordinated with the reporter. Submission of a report constitutes agreement to participate in **coordinated vulnerability disclosure**, allowing time for development, testing, and deployment of a fix prior to public release of details. *** ## Bug Bounty Program Overview Cosmos Labs operates a bug bounty program through **Immunefi**. Eligible reports are rewarded based on severity, impact, and quality. **In Scope:** Core Cosmos Stack components, including the Cosmos SDK, CometBFT, IBC, Cosmos EVM, and other critical infrastructure components. The authoritative scope definition, severity classifications, and reward ranges are maintained on the [Cosmos Immunefi program page](https://immunefi.com/bug-bounty/cosmos/scope/#top). The program is governed by **Safe Harbor** provisions for good-faith research. The Immunefi page defines the applicable **Coordinated Vulnerability Disclosure Policy** and **Safe Harbor terms**. > In the event of conflict, the Immunefi policy supersedes all other > documentation. *** ## Vulnerability Severity Levels Reported vulnerabilities are assigned a severity classification that determines handling priority and disclosure timing. The definitions that define severity classification and the reward ranges that follow are maintained on our Immunefi page and not duplicated here. See the [Cosmos Immunefi program page](https://immunefi.com/bug-bounty/cosmos/scope/#top) Impacts in Scope section for further details, as well as our [classification framework](https://github.com/cosmos/security/blob/main/resources/CLASSIFICATION_MATRIX.md) for details on our classification methodology. *** ## Silent Patch and Disclosure Process Cosmos Labs follows a **silent patch** model for most security vulnerabilities. Issues are addressed privately and remediated prior to public disclosure. This approach aligns with practices used by other major protocols, such as **Ethereum's Geth** (see [https://geth.ethereum.org/docs/developers/geth-developer/disclosures](https://geth.ethereum.org/docs/developers/geth-developer/disclosures)), **Bitcoin Core** (see [https://bitcoincore.org/en/security-advisories/](https://bitcoincore.org/en/security-advisories/)), and **Zcash** (see [https://z.cash/technology/security-advisories/](https://z.cash/technology/security-advisories/)). Premature disclosure can place unpatched networks at risk. Silent remediation allows operators time to upgrade before vulnerability details become public. Vulnerabilities classified as **Critical** are handled on a case-by-case basis. When an issue presents an immediate or network-wide risk, Cosmos Labs will initiate emergency mitigations, private fix distribution, or coordinated upgrades before any public disclosure occurs. If Cosmos Labs determines that a vulnerability with **network-wide impact** (such as a chain halt or consensus failure) is already being actively exploited, or that attacker awareness is confirmed prior to a scheduled release, the issue is escalated and handled as **Critical** for response and disclosure purposes, regardless of its original classification. ### Fix Distribution * Fixes are delivered through patch or minor releases. * Release notes may omit explicit references to security implications. * Validators and node operators may be notified privately to upgrade. * For critical vulnerabilities, fixes may be distributed privately to key operators or require emergency network upgrades. ### Disclosure Timeline | **Severity** | **Disclosure Timing** | **Details** | | ---------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------- | | **Low / Medium** | Approximately four weeks after public release of the fix | Full advisory published with impact and remediation details. | | **High** | After the affected version reaches **End-of-Life (EOL)** (\~1 year typical) | Disclosure delayed to reduce exploitation risk. | | **Critical** | Case-by-case (At minimum after EOL) | Disclosure only when deemed safe; details may be limited or withheld. | *** ## Transparency and Post-Disclosure After expiration of the disclosure embargo, Cosmos Labs publishes a **Security Advisory** (via GitHub advisories or official blog posts) containing: * Vulnerability description * Affected versions * Severity classification * Remediation guidance * Reporter attribution (unless anonymity is requested) All advisories remain publicly available. This delayed disclosure model balances ecosystem safety with long-term transparency. *** Cosmos Labs acknowledges and appreciates the contributions of security researchers, auditors, and white-hat hackers who strengthen the Cosmos ecosystem. *** ### References * [Bitcoin Core Security Advisories](https://bitcoincore.org/en/security-advisories/) * [Go Ethereum Vulnerability Disclosure](https://ethereumpow.github.io/go-ethereum/docs/vulnerabilities/vulnerabilities) * [Bitcoin Core Security Disclosure Policy Announcement](https://bitexes.com/blog/124272) # Internal Audit Process Source: https://docs.cosmos.network/sdk/latest/security/internal-audits Internal audit process for Cosmos Stack components This page outlines the internal audit process for Cosmos Stack components. ## Overview Cosmos Labs runs a structured internal audit on complex features before they ship, in addition to any external audits. The goal is to verify correctness, security, and maintainability through a collaborative, line-by-line review led by the engineers with the most context on the code. The process has two stages: 1. Pre-audit report 2. Internal audit Each audit is run by a designated audit lead, normally the person with the deepest context on the feature under review. The lead prepares the materials, briefs the team, facilitates the review, and splits the audit across multiple sessions when the feature is large. ## Stage 1: Pre-audit report The pre-audit report is a prerequisite to the internal audit. It is a macro-level review that surfaces systemic and integration risks early, before the full team is brought in. Critical issues found here must be resolved before the internal audit is scheduled. The audit lead produces a pre-audit document covering: * Scope definition: every file added or modified, with attention to those that introduce or depend on integration points. * Critical integration point analysis: changes in related or dependent modules outside the core feature that could introduce integration failures. * Spec diff: updates to the feature specification, compared against the current implementation. * Problem-first search: known problem areas and likely failure cases specific to the feature. Objectives of this stage: * Review code changes since the last stable milestone. * Detect integration issues across system boundaries. * Proactively investigate known vulnerability patterns and risk vectors, including state corruption, authorization failures, replay or race conditions, data consistency issues, resource exhaustion, callback or reentrancy risks, and other feature-specific failure modes. * Evaluate the risk introduced by untrusted or novel external dependencies. ## Stage 2: Internal audit Once the pre-audit report is complete and any critical issues are addressed, the audit lead schedules the internal audit: a live, line-by-line code review with the full team. The session is interactive. Participants ask questions, clarify assumptions, and flag concerns while the lead provides context on design intent and technical decisions. Large or complex features are split across multiple sessions by subsystem. ### Context briefing The lead opens the audit by providing the context needed to review the feature: * A summary of the feature and its intended behavior. * Key design decisions and tradeoffs. * Integration risks identified during the pre-audit. * Diagrams and visuals for complex workflows. Context briefings are recorded so they can be preserved as a reusable knowledge base. A briefing is skipped when an existing recording already covers the same ground. ### Line-by-line review The review starts at user entry points and follows the flow of logic, with attention to: * Correctness of implementation and internal function logic. * Input validation. * Authentication, authorization, and access control. * State consistency and post-conditions. * Performance, resource usage, storage layout, and efficiency. * Error handling and logging. * Adherence to the specification and intended semantics. * Event emission and observability. * Test coverage, edge cases, and failure-path validation. * Code readability and maintainability. * Use of external libraries and dependencies. Security-critical external code that is not heavily battle-tested receives extra scrutiny. This includes parsing, serialization, cryptography, verification logic, and other security-sensitive components, where past audits have uncovered bugs in upstream dependencies. ## Audit outcomes Throughout the audit, designated team members document every issue, concern, and open question. These notes are formalized into tracked issues and follow-up tasks scheduled in upcoming engineering iterations. Each item is categorized to support prioritization and triage after the audit, for example as a bug, optimization, specification mismatch, documentation improvement, or investigation needed. # Security and Maintenance Policy Source: https://docs.cosmos.network/sdk/latest/security/security-policy Security and maintenance policy documentation for the Cosmos Stack This content is sourced from the official [Cosmos Security](https://github.com/cosmos/security) repository. **Last sync:** Aug 31, 2026 | [View source](https://github.com/cosmos/security/blob/main/POLICY.md) ## Overview This policy defines how Cosmos Labs manages maintenance and support for the core Cosmos Stack components: * **CometBFT** * **Cosmos SDK** * **Cosmos EVM** * **Inter-Blockchain Communication Protocol (IBC)** This release process aims to provide clarity and predictability to both developers using the Stack and the Cosmos Labs engineering team. Developers should know exactly which software combinations are supported and should be used in production. At the same time, the Cosmos Labs team can coordinate fixes, security patches, and upgrades across a smaller set of well-defined release families, allowing for faster response times and more predictable maintenance. To achieve this, we are introducing the concept of **Release Families**, curated sets of component versions of the Stack. Each family is fully tested for compatibility, stability, and long-term support. Maintenance and bug fixes are provided only for active families. *** ## Release Families A **Release Family** is defined as a specific combination of component versions. The canonical source of truth for release family lifecycle, active support windows, and retirement policy is maintained in Cosmos docs: * [https://docs.cosmos.network/sdk/latest/release-family](https://docs.cosmos.network/sdk/latest/release-family) * [https://github.com/cosmos/docs/blob/main/sdk/latest/release-family.mdx](https://github.com/cosmos/docs/blob/main/sdk/latest/release-family.mdx) This file intentionally does not duplicate lifecycle timelines to avoid policy drift across multiple sources. *** ## What Is Supported * **Bug Fixes:** Critical security and stability issues are patched for all active families. * **Compatibility:** All components within a family are guaranteed to work together. * **Lifecycle and Retirement:** Maintained on the canonical Release Families page in Cosmos docs. * **Upgradability:** We guarantee an upgrade path from one release family to the next adjacent family in the form of clear guides, compatibility guarantees, and tooling for assistance. *** ## Security Fix Process Please read our [security policy](https://github.com/cosmos/security/blob/main/SECURITY.md) for a detailed breakdown of how bugs and vulnerabilities are to be handled for the Cosmos Stack. *** ## End of Life (EOL) Notices Current and historical EOL notices for release families are maintained on the canonical Release Families page in Cosmos docs: * [https://docs.cosmos.network/sdk/latest/release-family](https://docs.cosmos.network/sdk/latest/release-family) CometBFT v1.x is not supported. That release line was retracted and is not part of any supported release family. # v0.55 Release Notes Source: https://docs.cosmos.network/sdk/latest/upgrade/v0.55-release What's new in the 2026.1 Ledger Security release: post-quantum keys, validator consensus key rotation, and remote signing with Cosmos-KMS. If you are upgrading to v0.55, see the [upgrade guide](/sdk/latest/upgrade/v0.55). For a full list of changes, see the [changelog](https://github.com/cosmos/cosmos-sdk/blob/release/v0.55.x/CHANGELOG.md). ## Overview This release is a holistic upgrade to the security of the Cosmos Stack. It adds the first native post-quantum key option in Cosmos, in-place validator consensus key rotation with no downtime, and a remote signer that keeps validator keys in your own KMS or HSM. All four artifacts ship together and join the existing 2026.1 release family. For the versions each family pins, see [Release Families](/sdk/latest/release-family). ## What ships | Artifact | Version | What changed | | ------------------------------------------------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------ | | [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) | v0.55.0 | ML-DSA account and consensus keys, consensus key rotation through `x/staking`, keyring key types | | [CometBFT](https://github.com/cometbft/cometbft) | v0.40.0 | ML-DSA consensus key support and remote signer compatibility | | [enterprise/poa](https://github.com/cosmos/cosmos-sdk/tree/release/v0.55.x/enterprise/poa) | v1.1.0 | Consensus key rotation for PoA validators, by the operator or the chain admin | | [cosmos-kms](https://github.com/cosmos/kms) | v1.0.0 | First release of the remote signer | ## Features ### Post-quantum keys (ML-DSA) Chains can run ML-DSA for consensus and user-account keys. ML-DSA keys use lattice-based signatures, which are considered more quantum resistant than elliptic-curve-based keys. New chains set the allowed key types through consensus params; existing chains migrate one validator at a time, and a validator-led path moves a classical key to ML-DSA in place with no hard fork. A chain reaches post-quantum security once validators holding two-thirds of voting power have rotated to ML-DSA keys, the same threshold CometBFT uses to finalize blocks. See [Post-quantum keys](/sdk/latest/keys/post-quantum-keys) for the tradeoffs, [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys) to allow the type on a chain, and [Migrate a validator to ML-DSA](/sdk/latest/keys/migrate-validator-ml-dsa) for the per-validator path. ### Validator consensus key rotation Staked validators rotate a consensus key in place, keeping the validator's address, voting power, and accumulated fees, so the rotation stays invisible to delegators. Before this, a compromised or policy-expired consensus key meant standing up a new validator and rebuilding the delegator base. The operator submits `MsgRotateConsPubKey` with the new consensus public key, and CometBFT applies the change two heights later, which lets the operator bring up the new node with no downtime. Each rotation burns the `key_rotation_fee` staking parameter, a validator can rotate once per unbonding period, and a rotated-away key stays attributable for slashing until equivocation evidence for it can no longer be admitted. See [Key rotation](/sdk/latest/keys/key-rotation) for the mechanics and security implications, and [Rotate a consensus key, Staking](/sdk/latest/keys/rotate-validator-key) for the procedure. PoA chains follow [Rotate a consensus key, PoA](/sdk/latest/keys/rotate-validator-key-poa). ### Remote signing with Cosmos-KMS `cosmos-kms` is a new remote signing solution that signs on the validator's behalf while keys stay in your own HSM or cloud KMS rather than in local files on the node. It adds AWS KMS and PKCS#11 backends and post-quantum ML-DSA signing, none of which TMKMS supported. See [Cosmos-KMS and remote signing](/sdk/latest/kms/remote-signing) for the architecture, and the [remote signing tutorial](/sdk/latest/kms/tutorial-file-backend) to run one against a local chain. ## Removals and deprecations ### TMKMS deprecation notice This release begins the deprecation of TMKMS. TMKMS reaches official deprecation six months from this release, so operators running it have that window to move to `cosmos-kms`. Validators using TMKMS should migrate. See [Migrate from TMKMS](/sdk/latest/kms/migrate-from-tmkms), which covers moving each TMKMS backend to `cosmos-kms`. ### Removed in v0.55 * `x/params`, replaced by per-module params. * `x/protocolpool`, with the community pool returning to `x/distribution`. * `SIGN_MODE_TEXTUAL`. See the [upgrade guide](/sdk/latest/upgrade/v0.55) for the wiring changes each removal requires. ## Upgrading Upgrading to Cosmos SDK v0.55.0 bumps CometBFT to v0.40.0 automatically, so you do not upgrade CometBFT separately. Coordinate the upgrade across the validator set, since it moves the SDK and CometBFT together. We document the [0.54 to 0.55 upgrade path](/sdk/latest/upgrade/v0.55), which also includes a [section on upgrading from 0.53 directly to 0.55](/sdk/latest/upgrade/v0.55#upgrading-from-v0-53-x). Module upgrades work across the last two SDK versions. ## Upcoming The following features are planned for a future release: * Enterprise HSM and key custody. AWS KMS supports ML-DSA signatures through Cosmos-KMS in this release. Other HSM and KMS solutions will be supported in a future release. * Post-quantum support for attestors and signers. * Ledger-layer confidential transactions. # Staking Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/staking gRPC queries and transaction messages defined by the staking module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### Delegation Delegation queries delegate info for given validator delegator pair. | | | | ---- | ------------------------------------------------------------------------------------ | | gRPC | `cosmos.staking.v1beta1.Query/Delegation` | | REST | `GET /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations/{delegatorAddr}` | Request `QueryDelegationRequest`: | Field | Type | Description | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `delegator_addr` | `string` | delegator\_addr defines the delegator address to query for. Encoded as cosmos.AddressString. | | `validator_addr` | `string` | validator\_addr defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryDelegationResponse`: | Field | Type | Description | | --------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ | | `delegation_response` | [`DelegationResponse`](#cosmos-staking-v1beta1-delegationresponse) | delegation\_responses defines the delegation info of a delegation. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_addr":"","validator_addr":""}' \ localhost:9090 cosmos.staking.v1beta1.Query/Delegation ``` ### DelegatorDelegations DelegatorDelegations queries all delegations of a given delegator address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | --------------------------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/DelegatorDelegations` | | REST | `GET /cosmos/staking/v1beta1/delegations/{delegatorAddr}` | Request `QueryDelegatorDelegationsRequest`: | Field | Type | Description | | ---------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `delegator_addr` | `string` | delegator\_addr defines the delegator address to query for. Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDelegatorDelegationsResponse`: | Field | Type | Description | | ---------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `delegation_responses` | [`DelegationResponse`](#cosmos-staking-v1beta1-delegationresponse)\[] | delegation\_responses defines all the delegations' info of a delegator. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_addr":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorDelegations ``` ### DelegatorUnbondingDelegations DelegatorUnbondingDelegations queries all unbonding delegations of a given delegator address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ------------------------------------------------------------------------------ | | gRPC | `cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations` | | REST | `GET /cosmos/staking/v1beta1/delegators/{delegatorAddr}/unbonding_delegations` | Request `QueryDelegatorUnbondingDelegationsRequest`: | Field | Type | Description | | ---------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `delegator_addr` | `string` | delegator\_addr defines the delegator address to query for. Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDelegatorUnbondingDelegationsResponse`: | Field | Type | Description | | --------------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | | `unbonding_responses` | [`UnbondingDelegation`](#cosmos-staking-v1beta1-unbondingdelegation)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_addr":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations ``` ### DelegatorValidator DelegatorValidator queries validator info for given delegator validator pair. | | | | ---- | ----------------------------------------------------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/DelegatorValidator` | | REST | `GET /cosmos/staking/v1beta1/delegators/{delegatorAddr}/validators/{validatorAddr}` | Request `QueryDelegatorValidatorRequest`: | Field | Type | Description | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `delegator_addr` | `string` | delegator\_addr defines the delegator address to query for. Encoded as cosmos.AddressString. | | `validator_addr` | `string` | validator\_addr defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryDelegatorValidatorResponse`: | Field | Type | Description | | ----------- | ------------------------------------------------ | ------------------------------------- | | `validator` | [`Validator`](#cosmos-staking-v1beta1-validator) | validator defines the validator info. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_addr":"","validator_addr":""}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorValidator ``` ### DelegatorValidators DelegatorValidators queries all validators info for given delegator address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ------------------------------------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/DelegatorValidators` | | REST | `GET /cosmos/staking/v1beta1/delegators/{delegatorAddr}/validators` | Request `QueryDelegatorValidatorsRequest`: | Field | Type | Description | | ---------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `delegator_addr` | `string` | delegator\_addr defines the delegator address to query for. Encoded as cosmos.AddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryDelegatorValidatorsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ------------------------------------------------------- | | `validators` | [`Validator`](#cosmos-staking-v1beta1-validator)\[] | validators defines the validators' info of a delegator. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_addr":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.staking.v1beta1.Query/DelegatorValidators ``` ### HistoricalInfo HistoricalInfo queries the historical info for given height. | | | | ---- | ------------------------------------------------------ | | gRPC | `cosmos.staking.v1beta1.Query/HistoricalInfo` | | REST | `GET /cosmos/staking/v1beta1/historical_info/{height}` | Request `QueryHistoricalInfoRequest`: | Field | Type | Description | | -------- | ------- | ------------------------------------------------------------ | | `height` | `int64` | height defines at which height to query the historical info. | Response `QueryHistoricalInfoResponse`: | Field | Type | Description | | ------ | ---------------------------------------------------------- | ----------------------------------------------------- | | `hist` | [`HistoricalInfo`](#cosmos-staking-v1beta1-historicalinfo) | hist defines the historical info at the given height. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"height":"1"}' \ localhost:9090 cosmos.staking.v1beta1.Query/HistoricalInfo ``` ### Params Parameters queries the staking parameters. | | | | ---- | ------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/Params` | | REST | `GET /cosmos/staking/v1beta1/params` | Request `QueryParamsRequest`: This message has no fields. Response `QueryParamsResponse`: | Field | Type | Description | | -------- | ------------------------------------------ | ----------------------------------------------- | | `params` | [`Params`](#cosmos-staking-v1beta1-params) | params holds all the parameters of this module. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.staking.v1beta1.Query/Params ``` ### Pool Pool queries the pool info. | | | | ---- | ----------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/Pool` | | REST | `GET /cosmos/staking/v1beta1/pool` | Request `QueryPoolRequest`: This message has no fields. Response `QueryPoolResponse`: | Field | Type | Description | | ------ | -------------------------------------- | --------------------------- | | `pool` | [`Pool`](#cosmos-staking-v1beta1-pool) | pool defines the pool info. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.staking.v1beta1.Query/Pool ``` ### Redelegations Redelegations queries redelegations of given address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ---------------------------------------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/Redelegations` | | REST | `GET /cosmos/staking/v1beta1/delegators/{delegatorAddr}/redelegations` | Request `QueryRedelegationsRequest`: | Field | Type | Description | | -------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `delegator_addr` | `string` | delegator\_addr defines the delegator address to query for. Encoded as cosmos.AddressString. | | `src_validator_addr` | `string` | src\_validator\_addr defines the validator address to redelegate from. Encoded as cosmos.ValidatorAddressString. | | `dst_validator_addr` | `string` | dst\_validator\_addr defines the validator address to redelegate to. Encoded as cosmos.ValidatorAddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryRedelegationsResponse`: | Field | Type | Description | | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------- | | `redelegation_responses` | [`RedelegationResponse`](#cosmos-staking-v1beta1-redelegationresponse)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_addr":"","src_validator_addr":"","dst_validator_addr":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.staking.v1beta1.Query/Redelegations ``` ### UnbondingDelegation UnbondingDelegation queries unbonding info for given validator delegator pair. | | | | ---- | --------------------------------------------------------------------------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/UnbondingDelegation` | | REST | `GET /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations/{delegatorAddr}/unbonding_delegation` | Request `QueryUnbondingDelegationRequest`: | Field | Type | Description | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `delegator_addr` | `string` | delegator\_addr defines the delegator address to query for. Encoded as cosmos.AddressString. | | `validator_addr` | `string` | validator\_addr defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryUnbondingDelegationResponse`: | Field | Type | Description | | -------- | -------------------------------------------------------------------- | --------------------------------------------------------- | | `unbond` | [`UnbondingDelegation`](#cosmos-staking-v1beta1-unbondingdelegation) | unbond defines the unbonding information of a delegation. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"delegator_addr":"","validator_addr":""}' \ localhost:9090 cosmos.staking.v1beta1.Query/UnbondingDelegation ``` ### Validator Validator queries validator info for given validator address. | | | | ---- | -------------------------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/Validator` | | REST | `GET /cosmos/staking/v1beta1/validators/{validatorAddr}` | Request `QueryValidatorRequest`: | Field | Type | Description | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `validator_addr` | `string` | validator\_addr defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | Response `QueryValidatorResponse`: | Field | Type | Description | | ----------- | ------------------------------------------------ | ------------------------------------- | | `validator` | [`Validator`](#cosmos-staking-v1beta1-validator) | validator defines the validator info. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_addr":""}' \ localhost:9090 cosmos.staking.v1beta1.Query/Validator ``` ### ValidatorDelegations ValidatorDelegations queries delegate info for given validator. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | -------------------------------------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/ValidatorDelegations` | | REST | `GET /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations` | Request `QueryValidatorDelegationsRequest`: | Field | Type | Description | | ---------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `validator_addr` | `string` | validator\_addr defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryValidatorDelegationsResponse`: | Field | Type | Description | | ---------------------- | --------------------------------------------------------------------- | -------------------------------------------------- | | `delegation_responses` | [`DelegationResponse`](#cosmos-staking-v1beta1-delegationresponse)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_addr":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.staking.v1beta1.Query/ValidatorDelegations ``` ### Validators Validators queries all validators that match the given status. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ----------------------------------------- | | gRPC | `cosmos.staking.v1beta1.Query/Validators` | | REST | `GET /cosmos/staking/v1beta1/validators` | Request `QueryValidatorsRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `status` | `string` | status enables to query for validators matching a given status. One of the [`BondStatus`](#cosmos-staking-v1beta1-bondstatus) values. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryValidatorsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | -------------------------------------------------- | | `validators` | [`Validator`](#cosmos-staking-v1beta1-validator)\[] | validators contains all the queried validators. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"status":"BOND_STATUS_UNBONDED","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.staking.v1beta1.Query/Validators ``` ### ValidatorUnbondingDelegations ValidatorUnbondingDelegations queries unbonding delegations of a validator. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. | | | | ---- | ------------------------------------------------------------------------------ | | gRPC | `cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations` | | REST | `GET /cosmos/staking/v1beta1/validators/{validatorAddr}/unbonding_delegations` | Request `QueryValidatorUnbondingDelegationsRequest`: | Field | Type | Description | | ---------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `validator_addr` | `string` | validator\_addr defines the validator address to query for. Encoded as cosmos.ValidatorAddressString. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines an optional pagination for the request. | Response `QueryValidatorUnbondingDelegationsResponse`: | Field | Type | Description | | --------------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | | `unbonding_responses` | [`UnbondingDelegation`](#cosmos-staking-v1beta1-unbondingdelegation)\[] | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines the pagination in the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"validator_addr":"","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### BeginRedelegate BeginRedelegate defines a method for performing a redelegation of coins from a delegator and source validator to a destination validator. | | | | ---------- | -------------------------------------------- | | Type URL | `/cosmos.staking.v1beta1.MsgBeginRedelegate` | | Handler | `cosmos.staking.v1beta1.Msg/BeginRedelegate` | | Signer | `delegator_address` | | Amino name | `cosmos-sdk/MsgBeginRedelegate` | | Field | Type | Description | | ----------------------- | ----------------------------------- | ----------------------------------------- | | `delegator_address` | `string` | Encoded as cosmos.AddressString. | | `validator_src_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `validator_dst_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin) | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgBeginRedelegate", "delegator_address": "", "validator_src_address": "", "validator_dst_address": "", "amount": { "denom": "", "amount": "" } } ``` Response `MsgBeginRedelegateResponse`: | Field | Type | Description | | ----------------- | ----------- | ----------- | | `completion_time` | `Timestamp` | | ### CancelUnbondingDelegation CancelUnbondingDelegation defines a method for performing canceling the unbonding delegation and delegate back to previous validator. | | | | ---------- | ------------------------------------------------------ | | Type URL | `/cosmos.staking.v1beta1.MsgCancelUnbondingDelegation` | | Handler | `cosmos.staking.v1beta1.Msg/CancelUnbondingDelegation` | | Signer | `delegator_address` | | Amino name | `cosmos-sdk/MsgCancelUnbondingDelegation` | | Field | Type | Description | | ------------------- | ----------------------------------- | ------------------------------------------------------------------------- | | `delegator_address` | `string` | Encoded as cosmos.AddressString. | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin) | amount is always less than or equal to unbonding delegation entry balance | | `creation_height` | `int64` | creation\_height is the height which the unbonding took place. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgCancelUnbondingDelegation", "delegator_address": "", "validator_address": "", "amount": { "denom": "", "amount": "" }, "creation_height": "1" } ``` Response `MsgCancelUnbondingDelegationResponse`: This message has no fields. ### CreateValidator CreateValidator defines a method for creating a new validator. | | | | ---------- | -------------------------------------------- | | Type URL | `/cosmos.staking.v1beta1.MsgCreateValidator` | | Handler | `cosmos.staking.v1beta1.Msg/CreateValidator` | | Signer | `validator_address` | | Amino name | `cosmos-sdk/MsgCreateValidator` | | Field | Type | Description | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `description` | [`Description`](#cosmos-staking-v1beta1-description) | | | `commission` | [`CommissionRates`](#cosmos-staking-v1beta1-commissionrates) | | | `min_self_delegation` | `string` | Encoded as cosmos.Int. | | `delegator_address` | `string` | Deprecated: Use of Delegator Address in MsgCreateValidator is deprecated. The validator address bytes and delegator address bytes refer to the same account while creating validator (defer only in bech32 notation). Deprecated. Encoded as cosmos.AddressString. | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `pubkey` | `Any` | Accepts any message implementing cosmos.crypto.PubKey. | | `value` | [`Coin`](#cosmos-base-v1beta1-coin) | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgCreateValidator", "description": { "moniker": "", "identity": "", "website": "", "security_contact": "", "details": "" }, "commission": { "rate": "", "max_rate": "", "max_change_rate": "" }, "min_self_delegation": "", "validator_address": "", "pubkey": { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" }, "value": { "denom": "", "amount": "" } } ``` Response `MsgCreateValidatorResponse`: This message has no fields. ### Delegate Delegate defines a method for performing a delegation of coins from a delegator to a validator. | | | | ---------- | ------------------------------------- | | Type URL | `/cosmos.staking.v1beta1.MsgDelegate` | | Handler | `cosmos.staking.v1beta1.Msg/Delegate` | | Signer | `delegator_address` | | Amino name | `cosmos-sdk/MsgDelegate` | | Field | Type | Description | | ------------------- | ----------------------------------- | ----------------------------------------- | | `delegator_address` | `string` | Encoded as cosmos.AddressString. | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin) | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgDelegate", "delegator_address": "", "validator_address": "", "amount": { "denom": "", "amount": "" } } ``` Response `MsgDelegateResponse`: This message has no fields. ### EditValidator EditValidator defines a method for editing an existing validator. | | | | ---------- | ------------------------------------------ | | Type URL | `/cosmos.staking.v1beta1.MsgEditValidator` | | Handler | `cosmos.staking.v1beta1.Msg/EditValidator` | | Signer | `validator_address` | | Amino name | `cosmos-sdk/MsgEditValidator` | | Field | Type | Description | | --------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `description` | [`Description`](#cosmos-staking-v1beta1-description) | | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `commission_rate` | `string` | We pass a reference to the new commission rate and min self delegation as it's not mandatory to update. If not updated, the deserialized rate will be zero with no way to distinguish if an update was intended. REF: #2373 Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `min_self_delegation` | `string` | Encoded as cosmos.Int. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgEditValidator", "description": { "moniker": "", "identity": "", "website": "", "security_contact": "", "details": "" }, "validator_address": "", "commission_rate": "", "min_self_delegation": "" } ``` Response `MsgEditValidatorResponse`: This message has no fields. ### RotateConsPubKey RotateConsPubKey defines an operation for rotating the consensus keys of a validator. | | | | ---------- | --------------------------------------------- | | Type URL | `/cosmos.staking.v1beta1.MsgRotateConsPubKey` | | Handler | `cosmos.staking.v1beta1.Msg/RotateConsPubKey` | | Signer | `validator_address` | | Amino name | `cosmos-sdk/MsgRotateConsPubKey` | | Field | Type | Description | | ------------------- | -------- | ------------------------------------------------------ | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `new_pubkey` | `Any` | Accepts any message implementing cosmos.crypto.PubKey. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgRotateConsPubKey", "validator_address": "", "new_pubkey": { "@type": "/cosmos.example.v1.MsgExample", "...": "the fields of that message, inline" } } ``` Response `MsgRotateConsPubKeyResponse`: This message has no fields. ### Undelegate Undelegate defines a method for performing an undelegation from a delegate and a validator. | | | | ---------- | --------------------------------------- | | Type URL | `/cosmos.staking.v1beta1.MsgUndelegate` | | Handler | `cosmos.staking.v1beta1.Msg/Undelegate` | | Signer | `delegator_address` | | Amino name | `cosmos-sdk/MsgUndelegate` | | Field | Type | Description | | ------------------- | ----------------------------------- | ----------------------------------------- | | `delegator_address` | `string` | Encoded as cosmos.AddressString. | | `validator_address` | `string` | Encoded as cosmos.ValidatorAddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin) | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgUndelegate", "delegator_address": "", "validator_address": "", "amount": { "denom": "", "amount": "" } } ``` Response `MsgUndelegateResponse`: | Field | Type | Description | | ----------------- | ----------------------------------- | ---------------------------------------------- | | `completion_time` | `Timestamp` | | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin) | amount returns the amount of undelegated coins | ### UpdateParams UpdateParams defines an operation for updating the x/staking module parameters. | | | | ---------- | ----------------------------------------- | | Type URL | `/cosmos.staking.v1beta1.MsgUpdateParams` | | Handler | `cosmos.staking.v1beta1.Msg/UpdateParams` | | Signer | `authority` | | Amino name | `cosmos-sdk/x/staking/MsgUpdateParams` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `params` | [`Params`](#cosmos-staking-v1beta1-params) | params defines the x/staking parameters to update. NOTE: All parameters must be supplied. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.staking.v1beta1.MsgUpdateParams", "authority": "", "params": { "unbonding_time": "0s", "max_validators": 0, "max_entries": 0, "historical_entries": 0, "bond_denom": "", "min_commission_rate": "", "key_rotation_fee": { "denom": "", "amount": "" } } } ``` Response `MsgUpdateParamsResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.base.v1beta1.Coin Coin defines a token with a denomination and an amount. NOTE: The amount field is an Int which implements the custom method signatures required by gogoproto. | Field | Type | Description | | -------- | -------- | ---------------------- | | `denom` | `string` | | | `amount` | `string` | Encoded as cosmos.Int. | ### cosmos.staking.v1beta1.BondStatus Written as a quoted string in JSON. | Value | | | ------------------------- | ------------------------------- | | `BOND_STATUS_UNSPECIFIED` | Zero value. Handlers reject it. | | `BOND_STATUS_UNBONDED` | | | `BOND_STATUS_UNBONDING` | | | `BOND_STATUS_BONDED` | | ### cosmos.staking.v1beta1.Commission Commission defines commission parameters for a given validator. | Field | Type | Description | | ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | | `commission_rates` | [`CommissionRates`](#cosmos-staking-v1beta1-commissionrates) | commission\_rates defines the initial commission rates to be used for creating a validator. | | `update_time` | `Timestamp` | update\_time is the last time the commission rate was changed. | ### cosmos.staking.v1beta1.CommissionRates CommissionRates defines the initial commission rates to be used for creating a validator. | Field | Type | Description | | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `rate` | `string` | rate is the commission rate charged to delegators, as a fraction. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `max_rate` | `string` | max\_rate defines the maximum commission rate which validator can ever charge, as a fraction. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `max_change_rate` | `string` | max\_change\_rate defines the maximum daily increase of the validator commission, as a fraction. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | ### cosmos.staking.v1beta1.Delegation Delegation represents the bond with tokens held by an account. It is owned by one delegator, and is associated with the voting power of one validator. | Field | Type | Description | | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | `delegator_address` | `string` | delegator\_address is the encoded address of the delegator. Encoded as cosmos.AddressString. | | `validator_address` | `string` | validator\_address is the encoded address of the validator. Encoded as cosmos.ValidatorAddressString. | | `shares` | `string` | shares define the delegation shares received. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | ### cosmos.staking.v1beta1.DelegationResponse DelegationResponse is equivalent to Delegation except that it contains a balance in addition to shares which is more suitable for client responses. | Field | Type | Description | | ------------ | -------------------------------------------------- | ----------- | | `delegation` | [`Delegation`](#cosmos-staking-v1beta1-delegation) | | | `balance` | [`Coin`](#cosmos-base-v1beta1-coin) | | ### cosmos.staking.v1beta1.Description Description defines a validator description. | Field | Type | Description | | ------------------ | -------- | ----------------------------------------------------------------------- | | `moniker` | `string` | moniker defines a human-readable name for the validator. | | `identity` | `string` | identity defines an optional identity signature (ex. UPort or Keybase). | | `website` | `string` | website defines an optional website link. | | `security_contact` | `string` | security\_contact defines an optional email for security contact. | | `details` | `string` | details define other optional details. | ### cosmos.staking.v1beta1.HistoricalInfo HistoricalInfo contains header and validator information for a given block. It is stored as part of staking module's state, which persists the `n` most recent HistoricalInfo (`n` is set by the staking module's `historical_entries` parameter). | Field | Type | Description | | -------- | --------------------------------------------------- | ----------- | | `header` | `Header` | | | `valset` | [`Validator`](#cosmos-staking-v1beta1-validator)\[] | | ### cosmos.staking.v1beta1.Params Params defines the parameters for the x/staking module. | Field | Type | Description | | --------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `unbonding_time` | `Duration` | unbonding\_time is the time duration of unbonding. | | `max_validators` | `uint32` | max\_validators is the maximum number of validators. | | `max_entries` | `uint32` | max\_entries is the max entries for either unbonding delegation or redelegation (per pair/trio). | | `historical_entries` | `uint32` | historical\_entries is the number of historical entries to persist. | | `bond_denom` | `string` | bond\_denom defines the bondable coin denomination. | | `min_commission_rate` | `string` | min\_commission\_rate is the chain-wide minimum commission rate that a validator can charge their delegators Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `key_rotation_fee` | [`Coin`](#cosmos-base-v1beta1-coin) | key\_rotation\_fee is the fee charged when rotating a validator's consensus key. | ### cosmos.staking.v1beta1.Pool Pool is used for tracking bonded and not-bonded token supply of the bond denomination. | Field | Type | Description | | ------------------- | -------- | ---------------------- | | `not_bonded_tokens` | `string` | Encoded as cosmos.Int. | | `bonded_tokens` | `string` | Encoded as cosmos.Int. | ### cosmos.staking.v1beta1.Redelegation Redelegation contains the list of a particular delegator's redelegating bonds from a particular source validator to a particular destination validator. | Field | Type | Description | | ----------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `delegator_address` | `string` | delegator\_address is the bech32-encoded address of the delegator. Encoded as cosmos.AddressString. | | `validator_src_address` | `string` | validator\_src\_address is the validator redelegation source operator address. Encoded as cosmos.ValidatorAddressString. | | `validator_dst_address` | `string` | validator\_dst\_address is the validator redelegation destination operator address. Encoded as cosmos.ValidatorAddressString. | | `entries` | [`RedelegationEntry`](#cosmos-staking-v1beta1-redelegationentry)\[] | entries are the redelegation entries. | ### cosmos.staking.v1beta1.RedelegationEntry RedelegationEntry defines a redelegation object with relevant metadata. | Field | Type | Description | | ----------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `creation_height` | `int64` | creation\_height defines the height which the redelegation took place. | | `completion_time` | `Timestamp` | completion\_time defines the unix time for redelegation completion. | | `initial_balance` | `string` | initial\_balance defines the initial balance when redelegation started. Encoded as cosmos.Int. | | `shares_dst` | `string` | shares\_dst is the amount of destination-validator shares created by redelegation. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `unbonding_id` | `uint64` | Incrementing id that uniquely identifies this entry | | `unbonding_on_hold_ref_count` | `int64` | Strictly positive if this entry's unbonding has been stopped by external modules | ### cosmos.staking.v1beta1.RedelegationEntryResponse RedelegationEntryResponse is equivalent to a RedelegationEntry except that it contains a balance in addition to shares which is more suitable for client responses. | Field | Type | Description | | -------------------- | ---------------------------------------------------------------- | ---------------------- | | `redelegation_entry` | [`RedelegationEntry`](#cosmos-staking-v1beta1-redelegationentry) | | | `balance` | `string` | Encoded as cosmos.Int. | ### cosmos.staking.v1beta1.RedelegationResponse RedelegationResponse is equivalent to a Redelegation except that its entries contain a balance in addition to shares which is more suitable for client responses. | Field | Type | Description | | -------------- | ----------------------------------------------------------------------------------- | ----------- | | `redelegation` | [`Redelegation`](#cosmos-staking-v1beta1-redelegation) | | | `entries` | [`RedelegationEntryResponse`](#cosmos-staking-v1beta1-redelegationentryresponse)\[] | | ### cosmos.staking.v1beta1.UnbondingDelegation UnbondingDelegation stores all of a single delegator's unbonding bonds for a single validator in an time-ordered list. | Field | Type | Description | | ------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `delegator_address` | `string` | delegator\_address is the encoded address of the delegator. Encoded as cosmos.AddressString. | | `validator_address` | `string` | validator\_address is the encoded address of the validator. Encoded as cosmos.ValidatorAddressString. | | `entries` | [`UnbondingDelegationEntry`](#cosmos-staking-v1beta1-unbondingdelegationentry)\[] | entries are the unbonding delegation entries. | ### cosmos.staking.v1beta1.UnbondingDelegationEntry UnbondingDelegationEntry defines an unbonding object with relevant metadata. | Field | Type | Description | | ----------------------------- | ----------- | -------------------------------------------------------------------------------------------------------- | | `creation_height` | `int64` | creation\_height is the height which the unbonding took place. | | `completion_time` | `Timestamp` | completion\_time is the unix time for unbonding completion. | | `initial_balance` | `string` | initial\_balance defines the tokens initially scheduled to receive at completion. Encoded as cosmos.Int. | | `balance` | `string` | balance defines the tokens to receive at completion. Encoded as cosmos.Int. | | `unbonding_id` | `uint64` | Incrementing id that uniquely identifies this entry | | `unbonding_on_hold_ref_count` | `int64` | Strictly positive if this entry's unbonding has been stopped by external modules | ### cosmos.staking.v1beta1.Validator Validator defines a validator, together with the total amount of the Validator's bond shares and their exchange rate to coins. Slashing results in a decrease in the exchange rate, allowing correct calculation of future undelegations without iterating over delegators. When coins are delegated to this validator, the validator is credited with a delegation whose number of bond shares is based on the amount of coins delegated divided by the current exchange rate. Voting power can be calculated as total bonded shares multiplied by exchange rate. | Field | Type | Description | | ----------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `operator_address` | `string` | operator\_address defines the address of the validator's operator; bech encoded in JSON. Encoded as cosmos.AddressString. | | `consensus_pubkey` | `Any` | consensus\_pubkey is the consensus public key of the validator, as a Protobuf Any. Accepts any message implementing cosmos.crypto.PubKey. | | `jailed` | `bool` | jailed defined whether the validator has been jailed from bonded status or not. | | `status` | [`BondStatus`](#cosmos-staking-v1beta1-bondstatus) | status is the validator status (bonded/unbonding/unbonded). | | `tokens` | `string` | tokens define the delegated tokens (incl. self-delegation). Encoded as cosmos.Int. | | `delegator_shares` | `string` | delegator\_shares defines total shares issued to a validator's delegators. Encoded as cosmos.Dec, read back as an integer string scaled by 10^18 over gRPC. | | `description` | [`Description`](#cosmos-staking-v1beta1-description) | description defines the description terms for the validator. | | `unbonding_height` | `int64` | unbonding\_height defines, if unbonding, the height at which this validator has begun unbonding. | | `unbonding_time` | `Timestamp` | unbonding\_time defines, if unbonding, the min time for the validator to complete unbonding. | | `commission` | [`Commission`](#cosmos-staking-v1beta1-commission) | commission defines the commission parameters. | | `min_self_delegation` | `string` | min\_self\_delegation is the validator's self declared minimum self delegation. Encoded as cosmos.Int. | | `unbonding_on_hold_ref_count` | `int64` | strictly positive if this validator's unbonding has been stopped by external modules | | `unbonding_ids` | `uint64`\[] | list of unbonding ids, each uniquely identifying an unbonding of this validator | # Store Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/store gRPC queries and transaction messages defined by the store module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### ListenCommit ListenCommit is the corresponding endpoint for ABCIListener.ListenCommit | | | | ---- | -------------------------------------------------------------- | | gRPC | `cosmos.store.streaming.abci.ABCIListenerService/ListenCommit` | Request `ListenCommitRequest`: | Field | Type | Description | | -------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------- | | `block_height` | `int64` | explicitly pass in block height as ResponseCommit does not contain this info | | `res` | `ResponseCommit` | | | `change_set` | [`StoreKVPair`](#cosmos-store-v1beta1-storekvpair)\[] | | Response `ListenCommitResponse`: This message has no fields. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"block_height":"1","res":{"retain_height":"1"},"change_set":[{"store_key":"","delete":false,"key":"","value":""}]}' \ localhost:9090 cosmos.store.streaming.abci.ABCIListenerService/ListenCommit ``` ### ListenFinalizeBlock ListenFinalizeBlock is the corresponding endpoint for ABCIListener.ListenEndBlock | | | | ---- | --------------------------------------------------------------------- | | gRPC | `cosmos.store.streaming.abci.ABCIListenerService/ListenFinalizeBlock` | Request `ListenFinalizeBlockRequest`: | Field | Type | Description | | ----- | ----------------------- | ----------- | | `req` | `RequestFinalizeBlock` | | | `res` | `ResponseFinalizeBlock` | | Response `ListenFinalizeBlockResponse`: This message has no fields. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"req":{"txs":[""],"decided_last_commit":{"round":0,"votes":[{"validator":{"address":"","power":"1"},"block_id_flag":"BLOCK_ID_FLAG_UNKNOWN"}]},"misbehavior":[{"type":"UNKNOWN","validator":{"address":"","power":"1"},"height":"1","time":"","total_voting_power":"1"}],"hash":"","height":"1","time":"","next_validators_hash":"","proposer_address":""},"res":{"events":[{"type":"NEW","attributes":[{"key":"","value":"","index":false}]}],"tx_results":[{"code":0,"data":"","log":"","info":"","gas_wanted":"1","gas_used":"1","events":[{"type":"NEW","attributes":[{"key":"","value":"","index":false}]}],"codespace":""}],"validator_updates":[{"pub_key":{"ed25519":""},"power":"1"}],"consensus_param_updates":{"block":{"max_bytes":"1","max_gas":"1"},"evidence":{"max_age_num_blocks":"1","max_age_duration":"0s","max_bytes":"1"},"validator":{"pub_key_types":[""]},"version":{"app":"1"},"abci":{"vote_extensions_enable_height":"1"},"authority":{"authority":""}},"app_hash":""}}' \ localhost:9090 cosmos.store.streaming.abci.ABCIListenerService/ListenFinalizeBlock ``` ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.store.v1beta1.StoreKVPair StoreKVPair is a KVStore KVPair used for listening to state changes (Sets and Deletes) It optionally includes the StoreKey for the originating KVStore and a Boolean flag to distinguish between Sets and Deletes | Field | Type | Description | | ----------- | -------- | ----------- | | `store_key` | `string` | | | `delete` | `bool` | | | `key` | `bytes` | | | `value` | `bytes` | | # Tx Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/tx gRPC queries and transaction messages defined by the tx module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### BroadcastTx BroadcastTx broadcast transaction. | | | | ---- | --------------------------------------- | | gRPC | `cosmos.tx.v1beta1.Service/BroadcastTx` | | REST | `POST /cosmos/tx/v1beta1/txs` | Request `BroadcastTxRequest`: | Field | Type | Description | | ---------- | --------------------------------------------------- | --------------------------------- | | `tx_bytes` | `bytes` | tx\_bytes is the raw transaction. | | `mode` | [`BroadcastMode`](#cosmos-tx-v1beta1-broadcastmode) | | Response `BroadcastTxResponse`: | Field | Type | Description | | ------------- | ---------------------------------------------------- | ---------------------------------------- | | `tx_response` | [`TxResponse`](#cosmos-base-abci-v1beta1-txresponse) | tx\_response is the queried TxResponses. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"tx_bytes":"","mode":"BROADCAST_MODE_SYNC"}' \ localhost:9090 cosmos.tx.v1beta1.Service/BroadcastTx ``` ### GetBlockWithTxs GetBlockWithTxs fetches a block with decoded txs. | | | | ---- | ------------------------------------------- | | gRPC | `cosmos.tx.v1beta1.Service/GetBlockWithTxs` | | REST | `GET /cosmos/tx/v1beta1/txs/block/{height}` | Request `GetBlockWithTxsRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------ | | `height` | `int64` | height is the height of the block to query. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines a pagination for the request. | Response `GetBlockWithTxsResponse`: | Field | Type | Description | | ------------ | --------------------------------------------------------- | ------------------------------------------------- | | `txs` | [`Tx`](#cosmos-tx-v1beta1-tx)\[] | txs are the transactions in the block. | | `block_id` | `BlockID` | | | `block` | `Block` | | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines a pagination for the response. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"height":"1","pagination":{"limit":"1"}}' \ localhost:9090 cosmos.tx.v1beta1.Service/GetBlockWithTxs ``` ### GetTx GetTx fetches a tx by hash. | | | | ---- | ----------------------------------- | | gRPC | `cosmos.tx.v1beta1.Service/GetTx` | | REST | `GET /cosmos/tx/v1beta1/txs/{hash}` | Request `GetTxRequest`: | Field | Type | Description | | ------ | -------- | ------------------------------------------------------ | | `hash` | `string` | hash is the tx hash to query, encoded as a hex string. | Response `GetTxResponse`: | Field | Type | Description | | ------------- | ---------------------------------------------------- | ---------------------------------------- | | `tx` | [`Tx`](#cosmos-tx-v1beta1-tx) | tx is the queried transaction. | | `tx_response` | [`TxResponse`](#cosmos-base-abci-v1beta1-txresponse) | tx\_response is the queried TxResponses. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"hash":""}' \ localhost:9090 cosmos.tx.v1beta1.Service/GetTx ``` ### GetTxsEvent GetTxsEvent fetches txs by event. | | | | ---- | --------------------------------------- | | gRPC | `cosmos.tx.v1beta1.Service/GetTxsEvent` | | REST | `GET /cosmos/tx/v1beta1/txs` | Request `GetTxsEventRequest`: | Field | Type | Description | | ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `events` | `string`\[] | events is the list of transaction event type. Deprecated post v0.47.x: use query instead, which should contain a valid events query. Deprecated. | | `pagination` | [`PageRequest`](#cosmos-base-query-v1beta1-pagerequest) | pagination defines a pagination for the request. Deprecated post v0.46.x: use page and limit instead. Deprecated. | | `order_by` | [`OrderBy`](#cosmos-tx-v1beta1-orderby) | | | `page` | `uint64` | page is the page number to query, starts at 1. If not provided, will default to first page. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `query` | `string` | query defines the transaction event query that is proxied to Tendermint's TxSearch RPC method. The query must be valid. | Response `GetTxsEventResponse`: | Field | Type | Description | | -------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `txs` | [`Tx`](#cosmos-tx-v1beta1-tx)\[] | txs is the list of queried transactions. | | `tx_responses` | [`TxResponse`](#cosmos-base-abci-v1beta1-txresponse)\[] | tx\_responses is the list of queried TxResponses. | | `pagination` | [`PageResponse`](#cosmos-base-query-v1beta1-pageresponse) | pagination defines a pagination for the response. Deprecated post v0.46.x: use total instead. Deprecated. | | `total` | `uint64` | total is total number of results available | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"order_by":"ORDER_BY_ASC","page":"1","limit":"1","query":""}' \ localhost:9090 cosmos.tx.v1beta1.Service/GetTxsEvent ``` ### Simulate Simulate simulates executing a transaction for estimating gas usage. | | | | ---- | ------------------------------------ | | gRPC | `cosmos.tx.v1beta1.Service/Simulate` | | REST | `POST /cosmos/tx/v1beta1/simulate` | Request `SimulateRequest`: | Field | Type | Description | | ---------- | ----------------------------- | ------------------------------------------------------------------------------------- | | `tx` | [`Tx`](#cosmos-tx-v1beta1-tx) | tx is the transaction to simulate. Deprecated. Send raw tx bytes instead. Deprecated. | | `tx_bytes` | `bytes` | tx\_bytes is the raw transaction. | Response `SimulateResponse`: | Field | Type | Description | | ---------- | ---------------------------------------------- | -------------------------------------------------------------- | | `gas_info` | [`GasInfo`](#cosmos-base-abci-v1beta1-gasinfo) | gas\_info is the information about gas used in the simulation. | | `result` | [`Result`](#cosmos-base-abci-v1beta1-result) | result is the result of the simulation. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"tx_bytes":""}' \ localhost:9090 cosmos.tx.v1beta1.Service/Simulate ``` ### TxDecode TxDecode decodes the transaction. | | | | ---- | ------------------------------------ | | gRPC | `cosmos.tx.v1beta1.Service/TxDecode` | | REST | `POST /cosmos/tx/v1beta1/decode` | Request `TxDecodeRequest`: | Field | Type | Description | | ---------- | ------- | --------------------------------- | | `tx_bytes` | `bytes` | tx\_bytes is the raw transaction. | Response `TxDecodeResponse`: | Field | Type | Description | | ----- | ----------------------------- | ------------------------------ | | `tx` | [`Tx`](#cosmos-tx-v1beta1-tx) | tx is the decoded transaction. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"tx_bytes":""}' \ localhost:9090 cosmos.tx.v1beta1.Service/TxDecode ``` ### TxDecodeAmino TxDecodeAmino decodes an Amino transaction from encoded bytes to JSON. | | | | ---- | ----------------------------------------- | | gRPC | `cosmos.tx.v1beta1.Service/TxDecodeAmino` | | REST | `POST /cosmos/tx/v1beta1/decode/amino` | Request `TxDecodeAminoRequest`: | Field | Type | Description | | -------------- | ------- | ----------- | | `amino_binary` | `bytes` | | Response `TxDecodeAminoResponse`: | Field | Type | Description | | ------------ | -------- | ----------- | | `amino_json` | `string` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"amino_binary":""}' \ localhost:9090 cosmos.tx.v1beta1.Service/TxDecodeAmino ``` ### TxEncode TxEncode encodes the transaction. | | | | ---- | ------------------------------------ | | gRPC | `cosmos.tx.v1beta1.Service/TxEncode` | | REST | `POST /cosmos/tx/v1beta1/encode` | Request `TxEncodeRequest`: | Field | Type | Description | | ----- | ----------------------------- | -------------------------------- | | `tx` | [`Tx`](#cosmos-tx-v1beta1-tx) | tx is the transaction to encode. | Response `TxEncodeResponse`: | Field | Type | Description | | ---------- | ------- | ------------------------------------------- | | `tx_bytes` | `bytes` | tx\_bytes is the encoded transaction bytes. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"tx":{"body":{"messages":[{"@type":"/cosmos.example.v1.MsgExample","...":"the fields of that message, inline"}],"memo":"","timeout_height":"1","unordered":false,"timeout_timestamp":"","extension_options":[{"@type":"/cosmos.example.v1.MsgExample","...":"the fields of that message, inline"}],"non_critical_extension_options":[{"@type":"/cosmos.example.v1.MsgExample","...":"the fields of that message, inline"}]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.example.v1.MsgExample","...":"the fields of that message, inline"},"mode_info":{"single":{}},"sequence":"1"}],"fee":{"amount":[{"denom":"","amount":""}],"gas_limit":"1","payer":"","granter":""}},"signatures":[""]}}' \ localhost:9090 cosmos.tx.v1beta1.Service/TxEncode ``` ### TxEncodeAmino TxEncodeAmino encodes an Amino transaction from JSON to encoded bytes. | | | | ---- | ----------------------------------------- | | gRPC | `cosmos.tx.v1beta1.Service/TxEncodeAmino` | | REST | `POST /cosmos/tx/v1beta1/encode/amino` | Request `TxEncodeAminoRequest`: | Field | Type | Description | | ------------ | -------- | ----------- | | `amino_json` | `string` | | Response `TxEncodeAminoResponse`: | Field | Type | Description | | -------------- | ------- | ----------- | | `amino_binary` | `bytes` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"amino_json":""}' \ localhost:9090 cosmos.tx.v1beta1.Service/TxEncodeAmino ``` ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.abci.v1beta1.ABCIMessageLog ABCIMessageLog defines a structure containing an indexed tx ABCI message log. | Field | Type | Description | | ----------- | --------------------------------------------------------- | --------------------------------------------------------------------------------- | | `msg_index` | `uint32` | | | `log` | `string` | | | `events` | [`StringEvent`](#cosmos-base-abci-v1beta1-stringevent)\[] | Events contains a slice of Event objects that were emitted during some execution. | ### cosmos.base.abci.v1beta1.Attribute Attribute defines an attribute wrapper where the key and value are strings instead of raw bytes. | Field | Type | Description | | ------- | -------- | ----------- | | `key` | `string` | | | `value` | `string` | | ### cosmos.base.abci.v1beta1.GasInfo GasInfo defines tx execution gas context. | Field | Type | Description | | ------------ | -------- | ------------------------------------------------------------------- | | `gas_wanted` | `uint64` | GasWanted is the maximum units of work we allow this tx to perform. | | `gas_used` | `uint64` | GasUsed is the amount of gas actually consumed. | ### cosmos.base.abci.v1beta1.Result Result is the union of ResponseFormat and ResponseCheckTx. | Field | Type | Description | | --------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | `bytes` | Data is any data returned from message or handler execution. It MUST be length prefixed in order to separate data from multiple message executions. Deprecated. This field is still populated, but prefer msg\_response instead because it also contains the Msg response typeURL. Deprecated. | | `log` | `string` | Log contains the log information from message or handler execution. | | `events` | `Event`\[] | Events contains a slice of Event objects that were emitted during message or handler execution. | | `msg_responses` | `Any`\[] | msg\_responses contains the Msg handler responses type packed in Anys. | ### cosmos.base.abci.v1beta1.StringEvent StringEvent defines en Event object wrapper where all the attributes contain key/value pairs that are strings instead of raw bytes. | Field | Type | Description | | ------------ | ----------------------------------------------------- | ----------- | | `type` | `string` | | | `attributes` | [`Attribute`](#cosmos-base-abci-v1beta1-attribute)\[] | | ### cosmos.base.abci.v1beta1.TxResponse TxResponse defines a structure containing relevant tx data and metadata. The tags are stringified and the log is JSON decoded. | Field | Type | Description | | ------------ | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `height` | `int64` | The block height | | `txhash` | `string` | The transaction hash. | | `codespace` | `string` | Namespace for the Code | | `code` | `uint32` | Response code. | | `data` | `string` | Result bytes, if any. | | `raw_log` | `string` | The output of the application's logger (raw string). May be non-deterministic. | | `logs` | [`ABCIMessageLog`](#cosmos-base-abci-v1beta1-abcimessagelog)\[] | The output of the application's logger (typed). May be non-deterministic. | | `info` | `string` | Additional information. May be non-deterministic. | | `gas_wanted` | `int64` | Amount of gas requested for transaction. | | `gas_used` | `int64` | Amount of gas consumed by transaction. | | `tx` | `Any` | The request transaction bytes. | | `timestamp` | `string` | Time of the previous block. For heights > 1, it's the weighted median of the timestamps of the valid votes in the block.LastCommit. For height == 1, it's genesis time. | | `events` | `Event`\[] | Events defines all the events emitted by processing a transaction. Note, these events include those emitted by processing all the messages and those emitted from the ante. Whereas Logs contains the events, with additional metadata, emitted only by processing the messages. | ### cosmos.base.query.v1beta1.PageRequest PageRequest is to be embedded in gRPC request messages for efficient pagination. Ex: `message SomeRequest { Foo some_parameter = 1; PageRequest pagination = 2; }` | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `key` | `bytes` | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | `uint64` | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | `uint64` | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | `bool` | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | `bool` | reverse is set to true if results are to be returned in the descending order. | ### cosmos.base.query.v1beta1.PageResponse PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. `message SomeResponse { repeated Bar results = 1; PageResponse page = 2; }` | Field | Type | Description | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `next_key` | `bytes` | next\_key is the key to be passed to PageRequest.key to query the next page most efficiently. It will be empty if there are no more results. | | `total` | `uint64` | total is total number of results available if PageRequest.count\_total was set, its value is undefined otherwise | ### cosmos.base.v1beta1.Coin Coin defines a token with a denomination and an amount. NOTE: The amount field is an Int which implements the custom method signatures required by gogoproto. | Field | Type | Description | | -------- | -------- | ---------------------- | | `denom` | `string` | | | `amount` | `string` | Encoded as cosmos.Int. | ### cosmos.crypto.multisig.v1beta1.CompactBitArray CompactBitArray is an implementation of a space efficient bit array. This is used to ensure that the encoded data takes up a minimal amount of space after proto encoding. This is not thread safe, and is not intended for concurrent usage. | Field | Type | Description | | ------------------- | -------- | ----------- | | `extra_bits_stored` | `uint32` | | | `elems` | `bytes` | | ### cosmos.tx.signing.v1beta1.SignMode Written as a quoted string in JSON. | Value | | | ----------------------------- | ------------------------------- | | `SIGN_MODE_UNSPECIFIED` | Zero value. Handlers reject it. | | `SIGN_MODE_DIRECT` | | | `SIGN_MODE_DIRECT_AUX` | | | `SIGN_MODE_LEGACY_AMINO_JSON` | | | `SIGN_MODE_EIP_191` | | ### cosmos.tx.v1beta1.AuthInfo AuthInfo describes the fee and signer modes that are used to sign a transaction. | Field | Type | Description | | -------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `signer_infos` | [`SignerInfo`](#cosmos-tx-v1beta1-signerinfo)\[] | signer\_infos defines the signing modes for the required signers. The number and order of elements must match the required signers from TxBody's messages. The first element is the primary signer and the one which pays the fee. | | `fee` | [`Fee`](#cosmos-tx-v1beta1-fee) | Fee is the fee and gas limit for the transaction. The first signer is the primary signer and the one which pays the fee. The fee can be calculated based on the cost of evaluating the body and doing signature verification of the signers. This can be estimated via simulation. | | `tip` | [`Tip`](#cosmos-tx-v1beta1-tip) | Tip is the optional tip used for transactions fees paid in another denom. This field is ignored if the chain didn't enable tips, i.e. didn't add the `TipDecorator` in its posthandler. Deprecated. | ### cosmos.tx.v1beta1.BroadcastMode Written as a quoted string in JSON. | Value | | | ---------------------------- | ------------------------------- | | `BROADCAST_MODE_UNSPECIFIED` | Zero value. Handlers reject it. | | `BROADCAST_MODE_BLOCK` | Deprecated. | | `BROADCAST_MODE_SYNC` | | | `BROADCAST_MODE_ASYNC` | | ### cosmos.tx.v1beta1.Fee Fee includes the amount of coins paid in fees and the maximum gas to be used by the transaction. The ratio yields an effective "gasprice", which must be above some minimum to be accepted into the mempool. | Field | Type | Description | | ----------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | amount is the amount of coins to be paid as a fee | | `gas_limit` | `uint64` | gas\_limit is the maximum gas that can be used in transaction processing before an out of gas error occurs | | `payer` | `string` | if unset, the first signer is responsible for paying the fees. If set, the specified account must pay the fees. the payer must be a tx signer (and thus have signed this field in AuthInfo). setting this field does *not* change the ordering of required signers for the transaction. Encoded as cosmos.AddressString. | | `granter` | `string` | if set, the fee payer (either the first signer or the value of the payer field) requests that a fee grant be used to pay fees instead of the fee payer's own balance. If an appropriate fee grant does not exist or the chain does not support fee grants, this will fail Encoded as cosmos.AddressString. | ### cosmos.tx.v1beta1.ModeInfo ModeInfo describes the signing mode of a single or nested multisig signer. | Field | Type | Description | | -------- | ---------------------------------------------- | ----------------------------------------- | | `single` | [`Single`](#cosmos-tx-v1beta1-modeinfo-single) | single represents a single signer | | `multi` | [`Multi`](#cosmos-tx-v1beta1-modeinfo-multi) | multi represents a nested multisig signer | ### cosmos.tx.v1beta1.ModeInfo.Multi Multi is the mode info for a multisig public key | Field | Type | Description | | ------------ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `bitarray` | [`CompactBitArray`](#cosmos-crypto-multisig-v1beta1-compactbitarray) | bitarray specifies which keys within the multisig are signing | | `mode_infos` | [`ModeInfo`](#cosmos-tx-v1beta1-modeinfo)\[] | mode\_infos is the corresponding modes of the signers of the multisig which could include nested multisig public keys | ### cosmos.tx.v1beta1.ModeInfo.Single Single is the mode info for a single signer. It is structured as a message to allow for additional fields such as locale for SIGN\_MODE\_TEXTUAL in the future | Field | Type | Description | | ------ | ------------------------------------------------- | --------------------------------------------- | | `mode` | [`SignMode`](#cosmos-tx-signing-v1beta1-signmode) | mode is the signing mode of the single signer | ### cosmos.tx.v1beta1.OrderBy Written as a quoted string in JSON. | Value | | | ---------------------- | ------------------------------- | | `ORDER_BY_UNSPECIFIED` | Zero value. Handlers reject it. | | `ORDER_BY_ASC` | | | `ORDER_BY_DESC` | | ### cosmos.tx.v1beta1.SignerInfo SignerInfo describes the public key and signing mode of a single top-level signer. | Field | Type | Description | | ------------ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `public_key` | `Any` | public\_key is the public key of the signer. It is optional for accounts that already exist in state. If unset, the verifier can use the required \ signer address for this position and lookup the public key. | | `mode_info` | [`ModeInfo`](#cosmos-tx-v1beta1-modeinfo) | mode\_info describes the signing mode of the signer and is a nested structure to support nested multisig pubkey's | | `sequence` | `uint64` | sequence is the sequence of the account, which describes the number of committed transactions signed by a given address. It is used to prevent replay attacks. | ### cosmos.tx.v1beta1.Tip Tip is the tip used for meta-transactions. | Field | Type | Description | | -------- | -------------------------------------- | ---------------------------------------------------------------------------------------- | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | amount is the amount of the tip | | `tipper` | `string` | tipper is the address of the account paying for the tip Encoded as cosmos.AddressString. | ### cosmos.tx.v1beta1.Tx Tx is the standard type used for broadcasting transactions. | Field | Type | Description | | ------------ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `body` | [`TxBody`](#cosmos-tx-v1beta1-txbody) | body is the processable content of the transaction | | `auth_info` | [`AuthInfo`](#cosmos-tx-v1beta1-authinfo) | auth\_info is the authorization related content of the transaction, specifically signers, signer modes and fee | | `signatures` | `bytes`\[] | signatures is a list of signatures that matches the length and order of AuthInfo's signer\_infos to allow connecting signature meta information like public key and signing mode by position. | ### cosmos.tx.v1beta1.TxBody TxBody is the body of a transaction that all signers sign over. | Field | Type | Description | | -------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `messages` | `Any`\[] | messages is a list of messages to be executed. The required signers of those messages define the number and order of elements in AuthInfo's signer\_infos and Tx's signatures. Each required signer address is added to the list only the first time it occurs. By convention, the first required signer (usually from the first message) is referred to as the primary signer and pays the fee for the whole transaction. | | `memo` | `string` | memo is any arbitrary note/comment to be added to the transaction. WARNING: in clients, any publicly exposed text should not be called memo, but should be called `note` instead (see [https://github.com/cosmos/cosmos-sdk/issues/9122](https://github.com/cosmos/cosmos-sdk/issues/9122)). | | `timeout_height` | `uint64` | timeout\_height is the block height after which this transaction will not be processed by the chain. | | `unordered` | `bool` | unordered, when set to true, indicates that the transaction signer(s) intend for the transaction to be evaluated and executed in an un-ordered fashion. Specifically, the account's nonce will NOT be checked or incremented, which allows for fire-and-forget as well as concurrent transaction execution. Note, when set to true, the existing 'timeout\_timestamp' value must be set and will be used to correspond to a timestamp in which the transaction is deemed valid. When true, the sequence value MUST be 0, and any transaction with unordered=true and a non-zero sequence value will be rejected. External services that make assumptions about sequence values may need to be updated because of this. | | `timeout_timestamp` | `Timestamp` | timeout\_timestamp is the block time after which this transaction will not be processed by the chain. Note, if unordered=true this value MUST be set and will act as a short-lived TTL in which the transaction is deemed valid and kept in memory to prevent duplicates. | | `extension_options` | `Any`\[] | extension\_options are arbitrary options that can be added by chains when the default options are not sufficient. If any of these are present and can't be handled, the transaction will be rejected | | `non_critical_extension_options` | `Any`\[] | extension\_options are arbitrary options that can be added by chains when the default options are not sufficient. If any of these are present and can't be handled, they will be ignored | # Upgrade Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/upgrade gRPC queries and transaction messages defined by the upgrade module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Queries A node exposes only the services its application registers: * gRPC on port 9090 * REST on port 1317, if the method has an HTTP binding Run `grpcurl -plaintext localhost:9090 list` to see what a node serves. Queries are read-only and never pass through consensus. See [Queries](/sdk/latest/learn/concepts/transactions#queries). Replace `` placeholders with real values and omit unused filters. Payloads use protobuf JSON, and some `string` fields accept enum constants listed under Types. Field tables give the encoding each value takes. Examples assume a local node. For TLS endpoints, use port 443 and omit `-plaintext`. See [gRPC services](/sdk/latest/api-reference/grpc/index) for reflection, address formats, and decimal encoding. ### AppliedPlan AppliedPlan queries a previously applied upgrade plan by its name. | | | | ---- | ------------------------------------------------- | | gRPC | `cosmos.upgrade.v1beta1.Query/AppliedPlan` | | REST | `GET /cosmos/upgrade/v1beta1/applied_plan/{name}` | Request `QueryAppliedPlanRequest`: | Field | Type | Description | | ------ | -------- | -------------------------------------------------- | | `name` | `string` | name is the name of the applied plan to query for. | Response `QueryAppliedPlanResponse`: | Field | Type | Description | | -------- | ------- | --------------------------------------------------------- | | `height` | `int64` | height is the block height at which the plan was applied. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"name":""}' \ localhost:9090 cosmos.upgrade.v1beta1.Query/AppliedPlan ``` ### Authority Returns the account with authority to conduct upgrades | | | | ---- | ---------------------------------------- | | gRPC | `cosmos.upgrade.v1beta1.Query/Authority` | | REST | `GET /cosmos/upgrade/v1beta1/authority` | Request `QueryAuthorityRequest`: This message has no fields. Response `QueryAuthorityResponse`: | Field | Type | Description | | --------- | -------- | ----------- | | `address` | `string` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.upgrade.v1beta1.Query/Authority ``` ### CurrentPlan CurrentPlan queries the current upgrade plan. | | | | ---- | ------------------------------------------ | | gRPC | `cosmos.upgrade.v1beta1.Query/CurrentPlan` | | REST | `GET /cosmos/upgrade/v1beta1/current_plan` | Request `QueryCurrentPlanRequest`: This message has no fields. Response `QueryCurrentPlanResponse`: | Field | Type | Description | | ------ | -------------------------------------- | --------------------------------- | | `plan` | [`Plan`](#cosmos-upgrade-v1beta1-plan) | plan is the current upgrade plan. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext localhost:9090 cosmos.upgrade.v1beta1.Query/CurrentPlan ``` ### ModuleVersions ModuleVersions queries the list of module versions from state. | | | | ---- | --------------------------------------------- | | gRPC | `cosmos.upgrade.v1beta1.Query/ModuleVersions` | | REST | `GET /cosmos/upgrade/v1beta1/module_versions` | Request `QueryModuleVersionsRequest`: | Field | Type | Description | | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `module_name` | `string` | module\_name is a field to query a specific module consensus version from state. Leaving this empty will fetch the full list of module versions from state | Response `QueryModuleVersionsResponse`: | Field | Type | Description | | ----------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------- | | `module_versions` | [`ModuleVersion`](#cosmos-upgrade-v1beta1-moduleversion)\[] | module\_versions is a list of module names with their consensus versions. | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"module_name":""}' \ localhost:9090 cosmos.upgrade.v1beta1.Query/ModuleVersions ``` ### UpgradedConsensusState This method is deprecated. UpgradedConsensusState queries the consensus state that will serve as a trusted kernel for the next version of this chain. It will only be stored at the last height of this chain. UpgradedConsensusState RPC not supported with legacy querier This rpc is deprecated now that IBC has its own replacement ([https://github.com/cosmos/ibc-go/blob/2c880a22e9f9cc75f62b527ca94aa75ce1106001/proto/ibc/core/client/v1/query.proto#L54](https://github.com/cosmos/ibc-go/blob/2c880a22e9f9cc75f62b527ca94aa75ce1106001/proto/ibc/core/client/v1/query.proto#L54)) | | | | ---- | ------------------------------------------------------------------- | | gRPC | `cosmos.upgrade.v1beta1.Query/UpgradedConsensusState` | | REST | `GET /cosmos/upgrade/v1beta1/upgraded_consensus_state/{lastHeight}` | Request `QueryUpgradedConsensusStateRequest`: | Field | Type | Description | | ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | | `last_height` | `int64` | last height of the current chain must be sent in request as this is the height under which next consensus state is stored | Response `QueryUpgradedConsensusStateResponse`: | Field | Type | Description | | -------------------------- | ------- | ----------- | | `upgraded_consensus_state` | `bytes` | | ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext -d '{"last_height":"1"}' \ localhost:9090 cosmos.upgrade.v1beta1.Query/UpgradedConsensusState ``` ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### CancelUpgrade CancelUpgrade is a governance operation for cancelling a previously approved software upgrade. | | | | ---------- | ------------------------------------------ | | Type URL | `/cosmos.upgrade.v1beta1.MsgCancelUpgrade` | | Handler | `cosmos.upgrade.v1beta1.Msg/CancelUpgrade` | | Signer | `authority` | | Amino name | `cosmos-sdk/MsgCancelUpgrade` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.upgrade.v1beta1.MsgCancelUpgrade", "authority": "" } ``` Response `MsgCancelUpgradeResponse`: This message has no fields. ### SoftwareUpgrade SoftwareUpgrade is a governance operation for initiating a software upgrade. | | | | ---------- | -------------------------------------------- | | Type URL | `/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade` | | Handler | `cosmos.upgrade.v1beta1.Msg/SoftwareUpgrade` | | Signer | `authority` | | Amino name | `cosmos-sdk/MsgSoftwareUpgrade` | The signer is the governance module account, which no user holds a key for. This message executes only through a passed governance proposal, not as a transaction you submit directly. | Field | Type | Description | | ----------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `authority` | `string` | authority is the address that controls the module (defaults to x/gov unless overwritten). Encoded as cosmos.AddressString. | | `plan` | [`Plan`](#cosmos-upgrade-v1beta1-plan) | plan is the upgrade plan. | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", "authority": "", "plan": { "name": "", "height": "1", "info": "" } } ``` Response `MsgSoftwareUpgradeResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.upgrade.v1beta1.ModuleVersion ModuleVersion specifies a module and its consensus version. | Field | Type | Description | | --------- | -------- | ----------------------------------- | | `name` | `string` | name of the app module | | `version` | `uint64` | consensus version of the app module | ### cosmos.upgrade.v1beta1.Plan Plan specifies information about a planned upgrade and when it should occur. | Field | Type | Description | | ----------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | Sets the name for the upgrade. This name will be used by the upgraded version of the software to apply any special "on-upgrade" commands during the first BeginBlock method after the upgrade is applied. It is also used to detect whether a software version can handle a given upgrade. If no upgrade handler with this name has been set in the software, it will be assumed that the software is out-of-date when the upgrade Time or Height is reached and the software will exit. | | `time` | `Timestamp` | Deprecated: Time based upgrades have been deprecated. Time based upgrade logic has been removed from the SDK. If this field is not empty, an error will be thrown. Deprecated. | | `height` | `int64` | The height at which the upgrade must be performed. | | `info` | `string` | Any application specific upgrade info to be included on-chain such as a git commit that validators could automatically upgrade to | | `upgraded_client_state` | `Any` | Deprecated: UpgradedClientState field has been deprecated. IBC upgrade logic has been moved to the IBC module in the sub module 02-client. If this field is not empty, an error will be thrown. Deprecated. | # Vesting Source: https://docs.cosmos.network/sdk/latest/api-reference/grpc/vesting gRPC queries and transaction messages defined by the vesting module in Cosmos SDK v0.55. Generated from [`cosmos/cosmos-sdk@2086680ff8b0`](https://github.com/cosmos/cosmos-sdk/tree/2086680ff8b08fd269ee653e087ea577bab79534/proto) on ref `release/v0.55.x`. ## Transaction messages These messages are included in signed transactions, not called as endpoints. See [Transactions](/sdk/latest/learn/concepts/transactions#transactions) for the execution model, and [Sending transactions](/sdk/latest/api-reference/transactions) for the envelope they go into. Examples use CLI transaction JSON. Decimal fields use values such as `"0.05"`, even when their proto type is `bytes`. The gRPC `TxEncode` method instead requires the scaled value `"50000000000000000"`. See [scalar encodings](/sdk/latest/api-reference/grpc/index#scalar-encodings). * Handler: Generated client method * Signer: Account that must sign * Amino name: Legacy identifier used by hardware wallets and other Amino signers ### CreatePeriodicVestingAccount CreatePeriodicVestingAccount defines a method that enables creating a periodic vesting account. | | | | ---------- | --------------------------------------------------------- | | Type URL | `/cosmos.vesting.v1beta1.MsgCreatePeriodicVestingAccount` | | Handler | `cosmos.vesting.v1beta1.Msg/CreatePeriodicVestingAccount` | | Signer | `from_address` | | Amino name | `cosmos-sdk/MsgCreatePeriodVestAccount` | | Field | Type | Description | | ----------------- | --------------------------------------------- | ------------------------------------------- | | `from_address` | `string` | | | `to_address` | `string` | | | `start_time` | `int64` | start of vesting as unix time (in seconds). | | `vesting_periods` | [`Period`](#cosmos-vesting-v1beta1-period)\[] | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.vesting.v1beta1.MsgCreatePeriodicVestingAccount", "from_address": "", "to_address": "", "start_time": "1", "vesting_periods": [ { "length": "1", "amount": [ { "denom": "", "amount": "" } ] } ] } ``` Response `MsgCreatePeriodicVestingAccountResponse`: This message has no fields. ### CreatePermanentLockedAccount CreatePermanentLockedAccount defines a method that enables creating a permanent locked account. | | | | ---------- | --------------------------------------------------------- | | Type URL | `/cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccount` | | Handler | `cosmos.vesting.v1beta1.Msg/CreatePermanentLockedAccount` | | Signer | `from_address` | | Amino name | `cosmos-sdk/MsgCreatePermLockedAccount` | | Field | Type | Description | | -------------- | -------------------------------------- | ----------- | | `from_address` | `string` | | | `to_address` | `string` | | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccount", "from_address": "", "to_address": "", "amount": [ { "denom": "", "amount": "" } ] } ``` Response `MsgCreatePermanentLockedAccountResponse`: This message has no fields. ### CreateVestingAccount CreateVestingAccount defines a method that enables creating a vesting account. | | | | ---------- | ------------------------------------------------- | | Type URL | `/cosmos.vesting.v1beta1.MsgCreateVestingAccount` | | Handler | `cosmos.vesting.v1beta1.Msg/CreateVestingAccount` | | Signer | `from_address` | | Amino name | `cosmos-sdk/MsgCreateVestingAccount` | | Field | Type | Description | | -------------- | -------------------------------------- | ----------------------------------------- | | `from_address` | `string` | Encoded as cosmos.AddressString. | | `to_address` | `string` | Encoded as cosmos.AddressString. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | | `end_time` | `int64` | end of vesting as unix time (in seconds). | | `delayed` | `bool` | | In a transaction: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/cosmos.vesting.v1beta1.MsgCreateVestingAccount", "from_address": "", "to_address": "", "amount": [ { "denom": "", "amount": "" } ], "end_time": "1", "delayed": false } ``` Response `MsgCreateVestingAccountResponse`: This message has no fields. ## Types Messages referenced by the fields above. Protocol buffers version 3 has no required fields, so every field is optional on the wire, and a `[]` suffix marks a repeated field. ### cosmos.base.v1beta1.Coin Coin defines a token with a denomination and an amount. NOTE: The amount field is an Int which implements the custom method signatures required by gogoproto. | Field | Type | Description | | -------- | -------- | ---------------------- | | `denom` | `string` | | | `amount` | `string` | Encoded as cosmos.Int. | ### cosmos.vesting.v1beta1.Period Period defines a length of time and amount of coins that will vest. | Field | Type | Description | | -------- | -------------------------------------- | --------------------------- | | `length` | `int64` | Period duration in seconds. | | `amount` | [`Coin`](#cosmos-base-v1beta1-coin)\[] | | # Account Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/account /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/accounts/{address} gRPC: `cosmos.auth.v1beta1.Query/Account` ([reference](/sdk/latest/api-reference/grpc/auth#account)) Account returns account details based on address. # AccountAddressByID Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/accountaddressbyid /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/address_by_id/{accountId} gRPC: `cosmos.auth.v1beta1.Query/AccountAddressByID` ([reference](/sdk/latest/api-reference/grpc/auth#accountaddressbyid)) AccountAddressByID returns account address based on account number. # AccountInfo Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/accountinfo /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/account_info/{address} gRPC: `cosmos.auth.v1beta1.Query/AccountInfo` ([reference](/sdk/latest/api-reference/grpc/auth#accountinfo)) AccountInfo queries account info which is common to all account types. # Accounts Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/accounts /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/accounts gRPC: `cosmos.auth.v1beta1.Query/Accounts` ([reference](/sdk/latest/api-reference/grpc/auth#accounts)) Accounts returns all the existing accounts. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # AddressBytesToString Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/addressbytestostring /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/bech32/{addressBytes} gRPC: `cosmos.auth.v1beta1.Query/AddressBytesToString` ([reference](/sdk/latest/api-reference/grpc/auth#addressbytestostring)) AddressBytesToString converts Account Address bytes to string # Bech32Prefix Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/bech32prefix /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/bech32 gRPC: `cosmos.auth.v1beta1.Query/Bech32Prefix` ([reference](/sdk/latest/api-reference/grpc/auth#bech32prefix)) Bech32Prefix queries bech32Prefix # ModuleAccountByName Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/moduleaccountbyname /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/module_accounts/{name} gRPC: `cosmos.auth.v1beta1.Query/ModuleAccountByName` ([reference](/sdk/latest/api-reference/grpc/auth#moduleaccountbyname)) ModuleAccountByName returns the module account info by module name # ModuleAccounts Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/moduleaccounts /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/module_accounts gRPC: `cosmos.auth.v1beta1.Query/ModuleAccounts` ([reference](/sdk/latest/api-reference/grpc/auth#moduleaccounts)) ModuleAccounts returns all the existing module accounts. # Params Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/auth/params /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/auth/v1beta1/params gRPC: `cosmos.auth.v1beta1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/auth#params)) Params queries all parameters. # GranteeGrants Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/authz/granteegrants /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/authz/v1beta1/grants/grantee/{grantee} gRPC: `cosmos.authz.v1beta1.Query/GranteeGrants` ([reference](/sdk/latest/api-reference/grpc/authz#granteegrants)) GranteeGrants returns a list of `GrantAuthorization` by grantee. # GranterGrants Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/authz/grantergrants /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/authz/v1beta1/grants/granter/{granter} gRPC: `cosmos.authz.v1beta1.Query/GranterGrants` ([reference](/sdk/latest/api-reference/grpc/authz#grantergrants)) GranterGrants returns list of `GrantAuthorization`, granted by granter. # Grants Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/authz/grants /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/authz/v1beta1/grants gRPC: `cosmos.authz.v1beta1.Query/Grants` ([reference](/sdk/latest/api-reference/grpc/authz#grants)) Returns list of `Authorization`, granted to the grantee by the granter. # AllBalances Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/allbalances /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/balances/{address} gRPC: `cosmos.bank.v1beta1.Query/AllBalances` ([reference](/sdk/latest/api-reference/grpc/bank#allbalances)) AllBalances queries the balance of all coins for a single account. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # Balance Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/balance /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/balances/{address}/by_denom gRPC: `cosmos.bank.v1beta1.Query/Balance` ([reference](/sdk/latest/api-reference/grpc/bank#balance)) Balance queries the balance of a single coin for a single account. # DenomMetadata Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/denommetadata /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/denoms_metadata/{denom} gRPC: `cosmos.bank.v1beta1.Query/DenomMetadata` ([reference](/sdk/latest/api-reference/grpc/bank#denommetadata)) DenomMetadata queries the client metadata of a given coin denomination. # DenomMetadataByQueryString Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/denommetadatabyquerystring /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/denoms_metadata_by_query_string gRPC: `cosmos.bank.v1beta1.Query/DenomMetadataByQueryString` ([reference](/sdk/latest/api-reference/grpc/bank#denommetadatabyquerystring)) DenomMetadataByQueryString queries the client metadata of a given coin denomination. # DenomOwners Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/denomowners /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/denom_owners/{denom} gRPC: `cosmos.bank.v1beta1.Query/DenomOwners` ([reference](/sdk/latest/api-reference/grpc/bank#denomowners)) DenomOwners queries for all account addresses that own a particular token denomination. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # DenomOwnersByQuery Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/denomownersbyquery /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/denom_owners_by_query gRPC: `cosmos.bank.v1beta1.Query/DenomOwnersByQuery` ([reference](/sdk/latest/api-reference/grpc/bank#denomownersbyquery)) DenomOwnersByQuery queries for all account addresses that own a particular token denomination. # DenomsMetadata Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/denomsmetadata /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/denoms_metadata gRPC: `cosmos.bank.v1beta1.Query/DenomsMetadata` ([reference](/sdk/latest/api-reference/grpc/bank#denomsmetadata)) DenomsMetadata queries the client metadata for all registered coin denominations. # Params Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/params /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/params gRPC: `cosmos.bank.v1beta1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/bank#params)) Params queries the parameters of x/bank module. # SendEnabled Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/sendenabled /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/send_enabled gRPC: `cosmos.bank.v1beta1.Query/SendEnabled` ([reference](/sdk/latest/api-reference/grpc/bank#sendenabled)) SendEnabled queries for SendEnabled entries. This query only returns denominations that have specific SendEnabled settings. Any denomination that does not have a specific setting will use the default params.default_send_enabled, and will not be returned by this query. # SpendableBalanceByDenom Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/spendablebalancebydenom /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/spendable_balances/{address}/by_denom gRPC: `cosmos.bank.v1beta1.Query/SpendableBalanceByDenom` ([reference](/sdk/latest/api-reference/grpc/bank#spendablebalancebydenom)) SpendableBalanceByDenom queries the spendable balance of a single denom for a single account. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # SpendableBalances Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/spendablebalances /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/spendable_balances/{address} gRPC: `cosmos.bank.v1beta1.Query/SpendableBalances` ([reference](/sdk/latest/api-reference/grpc/bank#spendablebalances)) SpendableBalances queries the spendable balance of all coins for a single account. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # SupplyOf Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/supplyof /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/supply/by_denom gRPC: `cosmos.bank.v1beta1.Query/SupplyOf` ([reference](/sdk/latest/api-reference/grpc/bank#supplyof)) SupplyOf queries the supply of a single coin. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # TotalSupply Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/bank/totalsupply /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/bank/v1beta1/supply gRPC: `cosmos.bank.v1beta1.Query/TotalSupply` ([reference](/sdk/latest/api-reference/grpc/bank#totalsupply)) TotalSupply queries the total supply of all coins. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # ABCIQuery Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/abciquery /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/abci_query gRPC: `cosmos.base.tendermint.v1beta1.Service/ABCIQuery` ([reference](/sdk/latest/api-reference/grpc/base#abciquery)) ABCIQuery defines a query handler that supports ABCI queries directly to the application, bypassing Tendermint completely. The ABCI query must contain a valid and supported path, including app, custom, p2p, and store. # Config Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/config /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/node/v1beta1/config gRPC: `cosmos.base.node.v1beta1.Service/Config` ([reference](/sdk/latest/api-reference/grpc/base#config)) Config queries for the operator configuration. # GetBlockByHeight Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getblockbyheight /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/blocks/{height} gRPC: `cosmos.base.tendermint.v1beta1.Service/GetBlockByHeight` ([reference](/sdk/latest/api-reference/grpc/base#getblockbyheight)) GetBlockByHeight queries block for given height. # GetBlockResults Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getblockresults /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/block_results/{height} gRPC: `cosmos.base.tendermint.v1beta1.Service/GetBlockResults` ([reference](/sdk/latest/api-reference/grpc/base#getblockresults)) GetBlockResults queries block results for given height. # GetLatestBlock Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getlatestblock /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/blocks/latest gRPC: `cosmos.base.tendermint.v1beta1.Service/GetLatestBlock` ([reference](/sdk/latest/api-reference/grpc/base#getlatestblock)) GetLatestBlock returns the latest block. # GetLatestBlockResults Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getlatestblockresults /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/block_results/latest gRPC: `cosmos.base.tendermint.v1beta1.Service/GetLatestBlockResults` ([reference](/sdk/latest/api-reference/grpc/base#getlatestblockresults)) GetLatestBlockResults returns the block results for the latest block. Block results contain finalize_block_events which include consensus-level events like slashing, jailing, and validator set updates. # GetLatestValidatorSet Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getlatestvalidatorset /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/validatorsets/latest gRPC: `cosmos.base.tendermint.v1beta1.Service/GetLatestValidatorSet` ([reference](/sdk/latest/api-reference/grpc/base#getlatestvalidatorset)) GetLatestValidatorSet queries latest validator-set. # GetNodeInfo Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getnodeinfo /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/node_info gRPC: `cosmos.base.tendermint.v1beta1.Service/GetNodeInfo` ([reference](/sdk/latest/api-reference/grpc/base#getnodeinfo)) GetNodeInfo queries the current node info. # GetSyncing Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getsyncing /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/syncing gRPC: `cosmos.base.tendermint.v1beta1.Service/GetSyncing` ([reference](/sdk/latest/api-reference/grpc/base#getsyncing)) GetSyncing queries node syncing. # GetValidatorSetByHeight Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/getvalidatorsetbyheight /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/tendermint/v1beta1/validatorsets/{height} gRPC: `cosmos.base.tendermint.v1beta1.Service/GetValidatorSetByHeight` ([reference](/sdk/latest/api-reference/grpc/base#getvalidatorsetbyheight)) GetValidatorSetByHeight queries validator-set at a given height. # Status Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/base/status /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/base/node/v1beta1/status gRPC: `cosmos.base.node.v1beta1.Service/Status` ([reference](/sdk/latest/api-reference/grpc/base#status)) Status queries for the node status. # Params Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/consensus/params /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/consensus/v1/params gRPC: `cosmos.consensus.v1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/consensus#params)) Params queries the parameters of x/consensus module. # CommunityPool Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/communitypool /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/community_pool gRPC: `cosmos.distribution.v1beta1.Query/CommunityPool` ([reference](/sdk/latest/api-reference/grpc/distribution#communitypool)) CommunityPool queries the community pool coins. # DelegationRewards Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/delegationrewards /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/rewards/{validatorAddress} gRPC: `cosmos.distribution.v1beta1.Query/DelegationRewards` ([reference](/sdk/latest/api-reference/grpc/distribution#delegationrewards)) DelegationRewards queries the total rewards accrued by a delegation. # DelegationTotalRewards Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/delegationtotalrewards /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/rewards gRPC: `cosmos.distribution.v1beta1.Query/DelegationTotalRewards` ([reference](/sdk/latest/api-reference/grpc/distribution#delegationtotalrewards)) DelegationTotalRewards queries the total rewards accrued by each validator. # DelegatorStartingInfo Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/delegatorstartinginfo /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/starting_info/{validatorAddress} gRPC: `cosmos.distribution.v1beta1.Query/DelegatorStartingInfo` ([reference](/sdk/latest/api-reference/grpc/distribution#delegatorstartinginfo)) DelegatorStartingInfo queries the starting info for a delegator. # DelegatorValidators Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/delegatorvalidators /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/validators gRPC: `cosmos.distribution.v1beta1.Query/DelegatorValidators` ([reference](/sdk/latest/api-reference/grpc/distribution#delegatorvalidators)) DelegatorValidators queries the validators of a delegator. # DelegatorWithdrawAddress Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/delegatorwithdrawaddress /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/delegators/{delegatorAddress}/withdraw_address gRPC: `cosmos.distribution.v1beta1.Query/DelegatorWithdrawAddress` ([reference](/sdk/latest/api-reference/grpc/distribution#delegatorwithdrawaddress)) DelegatorWithdrawAddress queries withdraw address of a delegator. # Params Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/params /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/params gRPC: `cosmos.distribution.v1beta1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/distribution#params)) Params queries params of the distribution module. # ValidatorCommission Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/validatorcommission /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/validators/{validatorAddress}/commission gRPC: `cosmos.distribution.v1beta1.Query/ValidatorCommission` ([reference](/sdk/latest/api-reference/grpc/distribution#validatorcommission)) ValidatorCommission queries accumulated commission for a validator. # ValidatorCurrentRewards Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/validatorcurrentrewards /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/validators/{validatorAddress}/current_rewards gRPC: `cosmos.distribution.v1beta1.Query/ValidatorCurrentRewards` ([reference](/sdk/latest/api-reference/grpc/distribution#validatorcurrentrewards)) ValidatorCurrentRewards queries current rewards for a validator. # ValidatorDistributionInfo Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/validatordistributioninfo /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/validators/{validatorAddress} gRPC: `cosmos.distribution.v1beta1.Query/ValidatorDistributionInfo` ([reference](/sdk/latest/api-reference/grpc/distribution#validatordistributioninfo)) ValidatorDistributionInfo queries validator commission and self-delegation rewards for validator # ValidatorHistoricalRewards Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/validatorhistoricalrewards /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/validators/{validatorAddress}/historical_rewards/{period} gRPC: `cosmos.distribution.v1beta1.Query/ValidatorHistoricalRewards` ([reference](/sdk/latest/api-reference/grpc/distribution#validatorhistoricalrewards)) ValidatorHistoricalRewards queries historical rewards for a validator at a specific period. # ValidatorOutstandingRewards Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/validatoroutstandingrewards /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/validators/{validatorAddress}/outstanding_rewards gRPC: `cosmos.distribution.v1beta1.Query/ValidatorOutstandingRewards` ([reference](/sdk/latest/api-reference/grpc/distribution#validatoroutstandingrewards)) ValidatorOutstandingRewards queries rewards of a validator address. # ValidatorSlashes Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/distribution/validatorslashes /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/distribution/v1beta1/validators/{validatorAddress}/slashes gRPC: `cosmos.distribution.v1beta1.Query/ValidatorSlashes` ([reference](/sdk/latest/api-reference/grpc/distribution#validatorslashes)) ValidatorSlashes queries slash events of a validator. # CurrentEpoch Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/epochs/currentepoch /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/epochs/v1beta1/current_epoch gRPC: `cosmos.epochs.v1beta1.Query/CurrentEpoch` ([reference](/sdk/latest/api-reference/grpc/epochs#currentepoch)) CurrentEpoch provide current epoch of specified identifier # EpochInfos Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/epochs/epochinfos /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/epochs/v1beta1/epochs gRPC: `cosmos.epochs.v1beta1.Query/EpochInfos` ([reference](/sdk/latest/api-reference/grpc/epochs#epochinfos)) EpochInfos provide running epochInfos # AllEvidence Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/evidence/allevidence /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/evidence/v1beta1/evidence gRPC: `cosmos.evidence.v1beta1.Query/AllEvidence` ([reference](/sdk/latest/api-reference/grpc/evidence#allevidence)) AllEvidence queries all evidence. # Evidence Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/evidence/evidence /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/evidence/v1beta1/evidence/{hash} gRPC: `cosmos.evidence.v1beta1.Query/Evidence` ([reference](/sdk/latest/api-reference/grpc/evidence#evidence)) Evidence queries evidence based on evidence hash. # Allowance Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/feegrant/allowance /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/feegrant/v1beta1/allowance/{granter}/{grantee} gRPC: `cosmos.feegrant.v1beta1.Query/Allowance` ([reference](/sdk/latest/api-reference/grpc/feegrant#allowance)) Allowance returns granted allowance to the grantee by the granter. # Allowances Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/feegrant/allowances /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/feegrant/v1beta1/allowances/{grantee} gRPC: `cosmos.feegrant.v1beta1.Query/Allowances` ([reference](/sdk/latest/api-reference/grpc/feegrant#allowances)) Allowances returns all the grants for the given grantee address. # AllowancesByGranter Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/feegrant/allowancesbygranter /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/feegrant/v1beta1/issued/{granter} gRPC: `cosmos.feegrant.v1beta1.Query/AllowancesByGranter` ([reference](/sdk/latest/api-reference/grpc/feegrant#allowancesbygranter)) AllowancesByGranter returns all the grants given by an address # Constitution Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/constitution /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/constitution gRPC: `cosmos.gov.v1.Query/Constitution` ([reference](/sdk/latest/api-reference/grpc/gov#constitution)) Constitution queries the chain's constitution. # Deposit (Query, v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/deposit-query-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/proposals/{proposalId}/deposits/{depositor} gRPC: `cosmos.gov.v1.Query/Deposit` ([reference](/sdk/latest/api-reference/grpc/gov#deposit-query-v1)) Deposit queries single deposit information based on proposalID, depositAddr. # Deposit (Query, v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/deposit-query-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/proposals/{proposalId}/deposits/{depositor} gRPC: `cosmos.gov.v1beta1.Query/Deposit` ([reference](/sdk/latest/api-reference/grpc/gov#deposit-query-v1beta1)) Deposit queries single deposit information based on proposalID, depositor address. # Deposits (v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/deposits-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/proposals/{proposalId}/deposits gRPC: `cosmos.gov.v1.Query/Deposits` ([reference](/sdk/latest/api-reference/grpc/gov#deposits-v1)) Deposits queries all deposits of a single proposal. # Deposits (v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/deposits-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/proposals/{proposalId}/deposits gRPC: `cosmos.gov.v1beta1.Query/Deposits` ([reference](/sdk/latest/api-reference/grpc/gov#deposits-v1beta1)) Deposits queries all deposits of a single proposal. # Params (v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/params-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/params/{paramsType} gRPC: `cosmos.gov.v1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/gov#params-v1)) Params queries all parameters of the gov module. # Params (v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/params-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/params/{paramsType} gRPC: `cosmos.gov.v1beta1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/gov#params-v1beta1)) Params queries all parameters of the gov module. # Proposal (v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/proposal-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/proposals/{proposalId} gRPC: `cosmos.gov.v1.Query/Proposal` ([reference](/sdk/latest/api-reference/grpc/gov#proposal-v1)) Proposal queries proposal details based on ProposalID. # Proposal (v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/proposal-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/proposals/{proposalId} gRPC: `cosmos.gov.v1beta1.Query/Proposal` ([reference](/sdk/latest/api-reference/grpc/gov#proposal-v1beta1)) Proposal queries proposal details based on ProposalID. # Proposals (v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/proposals-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/proposals gRPC: `cosmos.gov.v1.Query/Proposals` ([reference](/sdk/latest/api-reference/grpc/gov#proposals-v1)) Proposals queries all proposals based on given status. # Proposals (v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/proposals-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/proposals gRPC: `cosmos.gov.v1beta1.Query/Proposals` ([reference](/sdk/latest/api-reference/grpc/gov#proposals-v1beta1)) Proposals queries all proposals based on given status. # TallyResult (v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/tallyresult-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/proposals/{proposalId}/tally gRPC: `cosmos.gov.v1.Query/TallyResult` ([reference](/sdk/latest/api-reference/grpc/gov#tallyresult-v1)) TallyResult queries the tally of a proposal vote. # TallyResult (v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/tallyresult-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/proposals/{proposalId}/tally gRPC: `cosmos.gov.v1beta1.Query/TallyResult` ([reference](/sdk/latest/api-reference/grpc/gov#tallyresult-v1beta1)) TallyResult queries the tally of a proposal vote. # Vote (Query, v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/vote-query-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/proposals/{proposalId}/votes/{voter} gRPC: `cosmos.gov.v1.Query/Vote` ([reference](/sdk/latest/api-reference/grpc/gov#vote-query-v1)) Vote queries voted information based on proposalID, voterAddr. # Vote (Query, v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/vote-query-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/proposals/{proposalId}/votes/{voter} gRPC: `cosmos.gov.v1beta1.Query/Vote` ([reference](/sdk/latest/api-reference/grpc/gov#vote-query-v1beta1)) Vote queries voted information based on proposalID, voterAddr. # Votes (v1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/votes-v1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1/proposals/{proposalId}/votes gRPC: `cosmos.gov.v1.Query/Votes` ([reference](/sdk/latest/api-reference/grpc/gov#votes-v1)) Votes queries votes of a given proposal. # Votes (v1beta1) Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/gov/votes-v1beta1 /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/gov/v1beta1/proposals/{proposalId}/votes gRPC: `cosmos.gov.v1beta1.Query/Votes` ([reference](/sdk/latest/api-reference/grpc/gov#votes-v1beta1)) Votes queries votes of a given proposal. # AnnualProvisions Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/mint/annualprovisions /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/mint/v1beta1/annual_provisions gRPC: `cosmos.mint.v1beta1.Query/AnnualProvisions` ([reference](/sdk/latest/api-reference/grpc/mint#annualprovisions)) AnnualProvisions current minting annual provisions value. # Inflation Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/mint/inflation /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/mint/v1beta1/inflation gRPC: `cosmos.mint.v1beta1.Query/Inflation` ([reference](/sdk/latest/api-reference/grpc/mint#inflation)) Inflation returns the current minting inflation value. # Params Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/mint/params /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/mint/v1beta1/params gRPC: `cosmos.mint.v1beta1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/mint#params)) Params returns the total set of minting parameters. # Params Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/slashing/params /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/slashing/v1beta1/params gRPC: `cosmos.slashing.v1beta1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/slashing#params)) Params queries the parameters of slashing module # SigningInfo Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/slashing/signinginfo /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/slashing/v1beta1/signing_infos/{consAddress} gRPC: `cosmos.slashing.v1beta1.Query/SigningInfo` ([reference](/sdk/latest/api-reference/grpc/slashing#signinginfo)) SigningInfo queries the signing info of given cons address # SigningInfos Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/slashing/signinginfos /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/slashing/v1beta1/signing_infos gRPC: `cosmos.slashing.v1beta1.Query/SigningInfos` ([reference](/sdk/latest/api-reference/grpc/slashing#signinginfos)) SigningInfos queries signing info of all validators # Delegation Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/delegation /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations/{delegatorAddr} gRPC: `cosmos.staking.v1beta1.Query/Delegation` ([reference](/sdk/latest/api-reference/grpc/staking#delegation)) Delegation queries delegate info for given validator delegator pair. # DelegatorDelegations Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/delegatordelegations /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/delegations/{delegatorAddr} gRPC: `cosmos.staking.v1beta1.Query/DelegatorDelegations` ([reference](/sdk/latest/api-reference/grpc/staking#delegatordelegations)) DelegatorDelegations queries all delegations of a given delegator address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # DelegatorUnbondingDelegations Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/delegatorunbondingdelegations /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/delegators/{delegatorAddr}/unbonding_delegations gRPC: `cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations` ([reference](/sdk/latest/api-reference/grpc/staking#delegatorunbondingdelegations)) DelegatorUnbondingDelegations queries all unbonding delegations of a given delegator address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # DelegatorValidator Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/delegatorvalidator /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/delegators/{delegatorAddr}/validators/{validatorAddr} gRPC: `cosmos.staking.v1beta1.Query/DelegatorValidator` ([reference](/sdk/latest/api-reference/grpc/staking#delegatorvalidator)) DelegatorValidator queries validator info for given delegator validator pair. # DelegatorValidators Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/delegatorvalidators /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/delegators/{delegatorAddr}/validators gRPC: `cosmos.staking.v1beta1.Query/DelegatorValidators` ([reference](/sdk/latest/api-reference/grpc/staking#delegatorvalidators)) DelegatorValidators queries all validators info for given delegator address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # HistoricalInfo Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/historicalinfo /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/historical_info/{height} gRPC: `cosmos.staking.v1beta1.Query/HistoricalInfo` ([reference](/sdk/latest/api-reference/grpc/staking#historicalinfo)) HistoricalInfo queries the historical info for given height. # Params Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/params /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/params gRPC: `cosmos.staking.v1beta1.Query/Params` ([reference](/sdk/latest/api-reference/grpc/staking#params)) Parameters queries the staking parameters. # Pool Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/pool /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/pool gRPC: `cosmos.staking.v1beta1.Query/Pool` ([reference](/sdk/latest/api-reference/grpc/staking#pool)) Pool queries the pool info. # Redelegations Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/redelegations /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/delegators/{delegatorAddr}/redelegations gRPC: `cosmos.staking.v1beta1.Query/Redelegations` ([reference](/sdk/latest/api-reference/grpc/staking#redelegations)) Redelegations queries redelegations of given address. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # UnbondingDelegation Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/unbondingdelegation /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations/{delegatorAddr}/unbonding_delegation gRPC: `cosmos.staking.v1beta1.Query/UnbondingDelegation` ([reference](/sdk/latest/api-reference/grpc/staking#unbondingdelegation)) UnbondingDelegation queries unbonding info for given validator delegator pair. # Validator Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/validator /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/validators/{validatorAddr} gRPC: `cosmos.staking.v1beta1.Query/Validator` ([reference](/sdk/latest/api-reference/grpc/staking#validator)) Validator queries validator info for given validator address. # ValidatorDelegations Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/validatordelegations /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/validators/{validatorAddr}/delegations gRPC: `cosmos.staking.v1beta1.Query/ValidatorDelegations` ([reference](/sdk/latest/api-reference/grpc/staking#validatordelegations)) ValidatorDelegations queries delegate info for given validator. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # Validators Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/validators /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/validators gRPC: `cosmos.staking.v1beta1.Query/Validators` ([reference](/sdk/latest/api-reference/grpc/staking#validators)) Validators queries all validators that match the given status. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # ValidatorUnbondingDelegations Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/staking/validatorunbondingdelegations /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/staking/v1beta1/validators/{validatorAddr}/unbonding_delegations gRPC: `cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations` ([reference](/sdk/latest/api-reference/grpc/staking#validatorunbondingdelegations)) ValidatorUnbondingDelegations queries unbonding delegations of a validator. When called from another module, this query might consume a high amount of gas if the pagination field is incorrectly set. # TxDecode Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/txdecode /sdk/latest/api-reference/rest/openapi.yaml post /cosmos/tx/v1beta1/decode gRPC: `cosmos.tx.v1beta1.Service/TxDecode` ([reference](/sdk/latest/api-reference/grpc/tx#txdecode)) TxDecode decodes the transaction. # BroadcastTx Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/broadcasttx /sdk/latest/api-reference/rest/openapi.yaml post /cosmos/tx/v1beta1/txs gRPC: `cosmos.tx.v1beta1.Service/BroadcastTx` ([reference](/sdk/latest/api-reference/grpc/tx#broadcasttx)) BroadcastTx broadcast transaction. # GetBlockWithTxs Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/getblockwithtxs /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/tx/v1beta1/txs/block/{height} gRPC: `cosmos.tx.v1beta1.Service/GetBlockWithTxs` ([reference](/sdk/latest/api-reference/grpc/tx#getblockwithtxs)) GetBlockWithTxs fetches a block with decoded txs. # GetTx Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/gettx /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/tx/v1beta1/txs/{hash} gRPC: `cosmos.tx.v1beta1.Service/GetTx` ([reference](/sdk/latest/api-reference/grpc/tx#gettx)) GetTx fetches a tx by hash. # GetTxsEvent Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/gettxsevent /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/tx/v1beta1/txs gRPC: `cosmos.tx.v1beta1.Service/GetTxsEvent` ([reference](/sdk/latest/api-reference/grpc/tx#gettxsevent)) GetTxsEvent fetches txs by event. # Simulate Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/simulate /sdk/latest/api-reference/rest/openapi.yaml post /cosmos/tx/v1beta1/simulate gRPC: `cosmos.tx.v1beta1.Service/Simulate` ([reference](/sdk/latest/api-reference/grpc/tx#simulate)) Simulate simulates executing a transaction for estimating gas usage. # TxDecodeAmino Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/txdecodeamino /sdk/latest/api-reference/rest/openapi.yaml post /cosmos/tx/v1beta1/decode/amino gRPC: `cosmos.tx.v1beta1.Service/TxDecodeAmino` ([reference](/sdk/latest/api-reference/grpc/tx#txdecodeamino)) TxDecodeAmino decodes an Amino transaction from encoded bytes to JSON. # TxEncode Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/txencode /sdk/latest/api-reference/rest/openapi.yaml post /cosmos/tx/v1beta1/encode gRPC: `cosmos.tx.v1beta1.Service/TxEncode` ([reference](/sdk/latest/api-reference/grpc/tx#txencode)) TxEncode encodes the transaction. # TxEncodeAmino Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/tx/txencodeamino /sdk/latest/api-reference/rest/openapi.yaml post /cosmos/tx/v1beta1/encode/amino gRPC: `cosmos.tx.v1beta1.Service/TxEncodeAmino` ([reference](/sdk/latest/api-reference/grpc/tx#txencodeamino)) TxEncodeAmino encodes an Amino transaction from JSON to encoded bytes. # AppliedPlan Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/upgrade/appliedplan /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/upgrade/v1beta1/applied_plan/{name} gRPC: `cosmos.upgrade.v1beta1.Query/AppliedPlan` ([reference](/sdk/latest/api-reference/grpc/upgrade#appliedplan)) AppliedPlan queries a previously applied upgrade plan by its name. # Authority Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/upgrade/authority /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/upgrade/v1beta1/authority gRPC: `cosmos.upgrade.v1beta1.Query/Authority` ([reference](/sdk/latest/api-reference/grpc/upgrade#authority)) Returns the account with authority to conduct upgrades # CurrentPlan Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/upgrade/currentplan /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/upgrade/v1beta1/current_plan gRPC: `cosmos.upgrade.v1beta1.Query/CurrentPlan` ([reference](/sdk/latest/api-reference/grpc/upgrade#currentplan)) CurrentPlan queries the current upgrade plan. # ModuleVersions Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/upgrade/moduleversions /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/upgrade/v1beta1/module_versions gRPC: `cosmos.upgrade.v1beta1.Query/ModuleVersions` ([reference](/sdk/latest/api-reference/grpc/upgrade#moduleversions)) ModuleVersions queries the list of module versions from state. # UpgradedConsensusState Source: https://docs.cosmos.network/sdk/latest/api-reference/rest/upgrade/upgradedconsensusstate /sdk/latest/api-reference/rest/openapi.yaml get /cosmos/upgrade/v1beta1/upgraded_consensus_state/{lastHeight} gRPC: `cosmos.upgrade.v1beta1.Query/UpgradedConsensusState` ([reference](/sdk/latest/api-reference/grpc/upgrade#upgradedconsensusstate)) UpgradedConsensusState queries the consensus state that will serve as a trusted kernel for the next version of this chain. It will only be stored at the last height of this chain. UpgradedConsensusState RPC not supported with legacy querier This rpc is deprecated now that IBC has its own replacement (https://github.com/cosmos/ibc-go/blob/2c880a22e9f9cc75f62b527ca94aa75ce1106001/proto/ibc/core/client/v1/query.proto#L54) # Ethereum JSON-RPC Source: https://docs.cosmos.network/evm/latest/api-reference/ethereum-json-rpc/index The JSON-RPC server provides an API that allows you to connect to a Cosmos EVM-enabled blockchain and interact with the EVM. This gives you direct access to reading Ethereum-formatted transactions or sending them to the network. [JSON-RPC](http://www.jsonrpc.org/specification) is a stateless, light-weight remote procedure call (RPC) protocol. It defines several data structures and the rules around their processing. JSON-RPC is provided on multiple transports. Cosmos EVM supports JSON-RPC over HTTP and WebSocket. Transports must be enabled through command-line flags or through the `app.toml` configuration file. It uses JSON ([RFC 4627](https://www.ietf.org/rfc/rfc4627.txt)) as data format. More on Ethereum JSON-RPC: * [EthWiki JSON-RPC API](https://eth.wiki/json-rpc/API) * [Geth JSON-RPC Server](https://geth.ethereum.org/docs/interacting-with-geth/rpc) * [Ethereum's PubSub JSON-RPC API](https://geth.ethereum.org/docs/interacting-with-geth/rpc/pubsub) **Cosmos-Specific Extensions**: These methods are unique to Cosmos EVM and not found in standard Ethereum: **Additional Eth Methods:** * `eth_getTransactionLogs` - Returns logs for a specific transaction * `eth_getBlockReceipts` - Returns all receipts for a given block **Extended Debug Methods:** * `debug_freeOSMemory` - Forces garbage collection * `debug_setGCPercent` - Sets garbage collection percentage * `debug_memStats` - Returns detailed memory statistics * `debug_setBlockProfileRate` - Sets block profiling rate * `debug_writeBlockProfile` - Writes block profile to file * `debug_writeMemProfile` - Writes memory profile to file * `debug_writeMutexProfile` - Writes mutex contention profile to file **Notable Unsupported Methods**: The following standard Ethereum methods are not implemented: * `eth_fillTransaction` - Transaction filling utility * All `debug_getRaw*` methods - Raw data access not implemented * `eth_subscribe` syncing events - Only newHeads, logs, and newPendingTransactions work * All `trace_*` methods - Parity/OpenEthereum trace namespace * All `engine_*` methods - Post-merge Engine API See the [methods page](/evm/latest/api-reference/ethereum-json-rpc/methods) for complete details. ## Enabling the JSON-RPC Server To use the JSON-RPC API, you must enable it in your node's configuration. You can do this either through the `app.toml` configuration file or via command-line flags. ### Configuration File Confirm the following in your `app.toml`: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # app.toml [json-rpc] # Enable defines if the JSON-RPC server should be enabled. enable = true # Address defines the JSON-RPC server address to bind to. address = "127.0.0.1:8545" # WS-address defines the JSON-RPC WebSocket server address to bind to. ws-address = "127.0.0.1:8546" # API defines a list of JSON-RPC namespaces that should be enabled. # Example: "eth,web3,net,txpool,debug,personal" api = "eth,web3,net,txpool" # MaxOpenConnections sets the maximum number of simultaneous connections # for the JSON-RPC server. max-open-connections = 0 # RPCGasCap sets a cap on gas that can be used in eth_call/estimateGas queries. # If set to 0 (default), no cap is applied. rpc-gas-cap = 0 # RPCEVMTimeout sets a timeout used for eth_call queries. evm-timeout = "10s" ``` ### Command-Line Flags Alternatively, enable JSON-RPC when starting the node: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd start \ --json-rpc.enable \ --json-rpc.address="0.0.0.0:8545" \ --json-rpc.ws-address="0.0.0.0:8546" \ --json-rpc.api="eth,web3,net,txpool,debug,personal" ``` ## JSON-RPC over HTTP[​](#json-rpc-over-http "Direct link to JSON-RPC over HTTP") Cosmos EVM supports most of the standard web3 JSON-RPC APIs to connect with existing Ethereum-compatible web3 tooling over HTTP. Ethereum JSON-RPC APIs use a namespace system. RPC methods are grouped into several categories depending on their purpose. All method names are composed of the namespace, an underscore, and the actual method name within the namespace. For example, the `eth_call` method resides in the eth namespace. Access to RPC methods can be enabled on a per-namespace basis. ### HTTP Examples To interact with the JSON-RPC server, send HTTP POST requests with a JSON body: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Get the current block number curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ -H "Content-Type: application/json" \ http://localhost:8545 ``` Response: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc": "2.0", "id": 1, "result": "0xC9B3C0" } ``` ### Namespaces supported on Cosmos EVM See the [methods](/evm/latest/api-reference/ethereum-json-rpc/methods) page for an exhaustive list and working examples. | Namespace | Description | Supported | Enabled by Default | | ---------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------- | ------------------ | | [`eth`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth-methods) | Core Ethereum JSON-RPC methods for interacting with the EVM | Y | Y | | [`web3`](/evm/latest/api-reference/ethereum-json-rpc/methods#web3-methods) | Utility functions for the web3 client | Y | Y | | [`net`](/evm/latest/api-reference/ethereum-json-rpc/methods#net-methods) | Network information about the node | Y | Y | | [`txpool`](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool-methods) | Transaction pool inspection | Y | N | | [`debug`](/evm/latest/api-reference/ethereum-json-rpc/methods#debug-methods) | Debugging and tracing functionality | Y | N | | [`personal`](/evm/latest/api-reference/ethereum-json-rpc/methods#personal-methods) | Private key management | Y | N | | [`admin`](/evm/latest/api-reference/ethereum-json-rpc/methods#admin-methods) | Node administration | Y | N | | [`miner`](/evm/latest/api-reference/ethereum-json-rpc/methods#miner-methods) | Mining operations (stub for PoS) | Y | N | | `clique` | Proof-of-Authority consensus | N | N | | `les` | Light Ethereum Subprotocol | N | N | You should only expose the debug endpoint in non production settings as it could impact network performance and uptime under certain conditions. ## Subscribing to Ethereum Events[​](#subscribing-to-ethereum-events "Direct link to Subscribing to Ethereum Events") ### Filters[​](#filters "Direct link to Filters") Cosmos EVM also supports the Ethereum [JSON-RPC](/evm/latest/api-reference/ethereum-json-rpc/methods) filters calls to subscribe to [state logs](https://eth.wiki/json-rpc/API#eth_newfilter), [blocks](https://eth.wiki/json-rpc/API#eth_newblockfilter) or [pending transactions](https://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter) changes. Under the hood, it uses the CometBFT RPC client's event system to process subscriptions that are then formatted to Ethereum-compatible events. ``` curl -X POST --data '{"jsonrpc":"2.0","method":"eth_newBlockFilter","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545{"jsonrpc":"2.0","id":1,"result":"0x3503de5f0c766c68f78a03a3b05036a5"} ``` Then you can check if the state changes with the [`eth_getFilterChanges`](https://eth.wiki/json-rpc/API#eth_getfilterchanges) call: ``` curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getFilterChanges","params":["0x3503de5f0c766c68f78a03a3b05036a5"],"id":1}' -H "Content-Type: application/json" http://localhost:8545{"jsonrpc":"2.0","id":1,"result":["0x7d44dceff05d5963b5bc81df7e9f79b27e777b0a03a6feca09f3447b99c6fa71","0x3961e4050c27ce0145d375255b3cb829a5b4e795ac475c05a219b3733723d376","0xd7a497f95167d63e6feca70f344d9f6e843d097b62729b8f43bdcd5febf142ab","0x55d80a4ba6ef54f2a8c0b99589d017b810ed13a1fda6a111e1b87725bc8ceb0e","0x9e8b92c17280dd05f2562af6eea3285181c562ebf41fc758527d4c30364bcbc4","0x7353a4b9d6b35c9eafeccaf9722dd293c46ae2ffd4093b2367165c3620a0c7c9","0x026d91bda61c8789c59632c349b38fd7e7557e6b598b94879654a644cfa75f30","0x73e3245d4ddc3bba48fa67633f9993c6e11728a36401fa1206437f8be94ef1d3"]} ``` ### Ethereum Websocket[​](#ethereum-websocket "Direct link to Ethereum Websocket") The Ethereum Websocket allows you to subscribe to Ethereum logs and events emitted in smart contracts. This way you don't need to continuously make requests when you want specific information. Since Cosmos EVM is built with the Cosmos SDK framework and uses CometBFT as it's consensus Engine, it inherits the event format from them. However, in order to support the native Web3 compatibility for websockets of the [Ethereum's PubSubAPI](https://geth.ethereum.org/docs/interacting-with-geth/rpc/pubsub), Cosmos EVM needs to cast the CometBFT responses retrieved into the Ethereum types. You can start a connection with the Ethereum websocket using the `--json-rpc.ws-address` flag when starting the node (default `"0.0.0.0:8546"`): ``` evmd start --json-rpc.address="0.0.0.0:8545" --json-rpc.ws-address="0.0.0.0:8546" --json-rpc.api="eth,web3,net,txpool,debug" --json-rpc.enable ``` Then, start a websocket subscription with [`ws`](https://github.com/hashrocket/ws) ``` # connect to CometBFT websocket at port 8546 as defined abovews ws://localhost:8546/# subscribe to new Ethereum-formatted block Headers> {"id": 1, "method": "eth_subscribe", "params": ["newHeads", {}]}< {"jsonrpc":"2.0","result":"0x44e010cb2c3161e9c02207ff172166ef","id":1} ``` #### Subscribing to Events via WebSocket The WebSocket endpoint allows your application to subscribe to real-time events, such as new blocks or logs, without needing to poll the node constantly. ##### Example: Subscribe to New Block Headers 1. **Connect to the WebSocket**: Use a tool like `wscat` or `ws` to connect to the WebSocket endpoint. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ws ws://localhost:8546 ``` 2. **Send Subscription Request**: Send an `eth_subscribe` request. The parameter `newHeads` tells the server you want to listen for new blocks. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} > {"id": 1, "method": "eth_subscribe", "params": ["newHeads"]} ``` 3. **Receive Subscription ID**: The server responds with a unique subscription ID. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc": "2.0", "id": 1, "result": "0x9cef36b2817151b1686a73bcec17eff0" } ``` 4. **Receive Notifications**: As new blocks are created, the server will push notifications to your client with the block header details. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc": "2.0", "method": "eth_subscription", "params": { "subscription": "0x9cef36b2817151b1686a73bcec17eff0", "result": { "number": "0xC9B3C1", "parentHash": "0x1d4ea5a0e9e4f5b5f20f8a8b1d9b3e9d82a4c6a4f8f8e6e5c4a3b2a1b0f0c0d1", "timestamp": "0x68B9833D" } } } ``` ## Key Concepts[​](#key-concepts "Direct link to Key Concepts") ### Data Encoding[​](#data-encoding "Direct link to Data Encoding") JSON-RPC uses hexadecimal encoding for data, but the formatting differs based on the type: #### Quantities When encoding quantities (integers, numbers): * Encode as hex, prefix with `"0x"` * Use the most compact representation * Zero should be represented as `"0x0"` Examples: * `0x41` (65 in decimal) * `0x400` (1024 in decimal) * WRONG: `0x` (should always have at least one digit - zero is `"0x0"`) * WRONG: `0x0400` (no leading zeroes allowed) * WRONG: `ff` (must be prefixed `0x`) #### Unformatted Byte Arrays When encoding unformatted data (byte arrays, account addresses, hashes, bytecode arrays): * Encode as hex, prefix with `"0x"` * Two hex digits per byte Examples: * `0x41` (size 1, `"A"`) * `0x004200` (size 3, `"\0B\0"`) * `0x` (size 0, `""`) * WRONG: `0xf0f0f` (must be even number of digits) * WRONG: `004200` (must be prefixed `0x`) ### Default Block Parameter[​](#default-block-parameter "Direct link to Default block parameter") Several methods that query the state of the EVM accept a default block parameter. This allows you to specify the block height at which to perform the query. Methods supporting block parameter: * [`eth_getBalance`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_getbalance) * [`eth_getCode`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_getcode) * [`eth_getTransactionCount`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_gettransactioncount) * [`eth_getStorageAt`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_getstorageat) * [`eth_call`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_call) The possible values for the `defaultBlock` parameter: * **Hex String** - A specific block number (e.g., `0xC9B3C0`) * **`"latest"`** - The most recently mined block * **`"pending"`** - The pending state, including transactions not yet mined * **`"earliest"`** - The genesis block # Methods Source: https://docs.cosmos.network/evm/latest/api-reference/ethereum-json-rpc/methods Find below a list of JSON-RPC methods supported on Cosmos EVM, sorted by namespaces. ```ascii theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} **Legend**: - **Y** - Functional - **Stub** - Returns success but with null/empty/zero values for compatibility - **N** - Not Implemented - Returns error or not available ``` This documentation lists all Ethereum JSON-RPC methods and their implementation status in Cosmos EVM. For the official Ethereum JSON-RPC specification, see the [Ethereum Execution APIs](https://github.com/ethereum/execution-apis) repository. **Namespace Configuration**: By default, only the `eth`, `net`, and `web3` namespaces are enabled. To enable additional namespaces like `personal`, `txpool`, `debug`, or `miner`, you must configure them in your node's `app.toml` config. ## Non-Implemented Namespaces The following namespaces are not implemented in Cosmos EVM but may be present in the interactive explorer for testing against other networks: The `debug` namespace provides debugging and tracing tools for development. Some tracing and profiling methods are implemented in Cosmos EVM. Many methods require the Profiling config to be enabled. The `admin` namespace handles node administration and peer management. Cosmos nodes use different administration mechanisms through the Cosmos SDK CLI and configuration files. The `miner` namespace controls Proof of Work mining operations. Cosmos EVM uses 'CometBFT' Byzantine Fault Tolerant consensus instead of mining. The `engine` namespace implements Ethereum's post-merge Engine API for communication with the consensus layer. Cosmos EVM uses 'CometBFT' consensus instead of the beacon chain. The `clique` namespace implements Proof of Authority consensus for private networks. Cosmos EVM uses 'CometBFT' consensus. The `les` namespace implements the Light Ethereum Subprotocol. Cosmos has a different light client architecture through IBC. ## Endpoints | Method | Namespace | Status | Public | Notes | | ------------------------------------------------------------------------------------- | --------- | ------ | ------ | ---------------------------------------------------------------------- | | [`web3_clientVersion`](#web3_clientversion) | Web3 | Y | Y | | | [`web3_sha3`](#web3_sha3) | Web3 | Y | Y | | | [`net_version`](#net_version) | Net | Y | Y | | | [`net_peerCount`](#net_peercount) | Net | Stub | Y | Returns "0x0" | | [`net_listening`](#net_listening) | Net | Stub | Y | Returns true | | [`eth_protocolVersion`](#eth_protocolversion) | Eth | Y | Y | | | [`eth_syncing`](#eth_syncing) | Eth | Stub | Y | Returns false | | [`eth_gasPrice`](#eth_gasprice) | Eth | Stub | Y | Returns "0x0" | | [`eth_accounts`](#eth_accounts) | Eth | Y | Y | | | [`eth_blockNumber`](#eth_blocknumber) | Eth | Y | Y | | | [`eth_getBalance`](#eth_getbalance) | Eth | Y | Y | | | [`eth_getStorageAt`](#eth_getstorageat) | Eth | Y | Y | | | [`eth_getTransactionCount`](#eth_gettransactioncount) | Eth | Y | Y | | | [`eth_getBlockTransactionCountByNumber`](#eth_getblocktransactioncountbynumber) | Eth | Stub | Y | Returns "0x0" | | [`eth_getBlockTransactionCountByHash`](#eth_getblocktransactioncountbyhash) | Eth | Stub | Y | Returns "0x0" | | [`eth_getCode`](#eth_getcode) | Eth | Stub | Y | Returns "0x" | | [`eth_sign`](#eth_sign) | Eth | Y | N | | | [`eth_sendTransaction`](#eth_sendtransaction) | Eth | N | N | Not implemented | | [`eth_sendRawTransaction`](#eth_sendrawtransaction) | Eth | Y | Y | | | [`eth_call`](#eth_call) | Eth | Stub | Y | Returns "0x" | | [`eth_createAccessList`](#eth_createaccesslist) | Eth | Y | Y | EIP-2930 | | [`eth_estimateGas`](#eth_estimategas) | Eth | Y | Y | Optimized | | [`eth_getBlockByNumber`](#eth_getblockbynumber) | Eth | Y | Y | | | [`eth_getBlockByHash`](#eth_getblockbyhash) | Eth | Y | Y | | | [`eth_getTransactionByHash`](#eth_gettransactionbyhash) | Eth | Stub | Y | Returns null | | [`eth_getTransactionByBlockHashAndIndex`](#eth_gettransactionbyblockhashandindex) | Eth | Stub | Y | Returns null | | [`eth_getTransactionReceipt`](#eth_gettransactionreceipt) | Eth | Stub | Y | Returns null | | [`eth_newFilter`](#eth_newfilter) | Eth | Y | Y | | | [`eth_newBlockFilter`](#eth_newblockfilter) | Eth | Y | Y | | | [`eth_newPendingTransactionFilter`](#eth_newpendingtransactionfilter) | Eth | Y | Y | | | [`eth_uninstallFilter`](#eth_uninstallfilter) | Eth | Stub | Y | Returns true | | [`eth_getFilterChanges`](#eth_getfilterchanges) | Eth | Stub | Y | Returns empty array | | [`eth_getFilterLogs`](#eth_getfilterlogs) | Eth | Stub | Y | Returns empty array | | [`eth_getLogs`](#eth_getlogs) | Eth | Stub | Y | Returns \[] | | [`eth_getTransactionByBlockNumberAndIndex`](#eth_gettransactionbyblocknumberandindex) | Eth | Stub | Y | Returns null | | `eth_getWork` | Eth | N | Y | Not implemented | | `eth_submitWork` | Eth | N | Y | Not implemented | | `eth_submitHashrate` | Eth | N | Y | Not implemented | | `eth_getCompilers` | Eth | N | N | Not implemented | | `eth_compileLLL` | Eth | N | N | Not implemented | | `eth_compileSolidity` | Eth | N | N | Not implemented | | `eth_compileSerpent` | Eth | N | N | Not implemented | | `eth_signTransaction` | Eth | N | N | Not implemented | | `eth_mining` | Eth | Y | Y | Deprecated - always false | | [`eth_coinbase`](#eth_coinbase) | Eth | Y | Y | Deprecated in geth | | `eth_hashrate` | Eth | Y | Y | Deprecated - always 0 | | `eth_getUncleCountByBlockHash` | Eth | Stub | Y | Always "0x0" | | `eth_getUncleCountByBlockNumber` | Eth | Stub | Y | Always "0x0" | | `eth_getUncleByBlockHashAndIndex` | Eth | Stub | Y | Always null | | `eth_getUncleByBlockNumberAndIndex` | Eth | Stub | Y | Always null | | [`eth_getProof`](#eth_getproof) | Eth | Y | Y | | | `eth_feeHistory` | Eth | Y | Y | EIP-1559 | | `eth_maxPriorityFeePerGas` | Eth | Stub | Y | Returns "0x0" | | `eth_chainId` | Eth | Y | Y | | | `eth_getBlockReceipts` | Eth | Stub | Y | Returns empty array | | [`eth_resend`](#eth_resend) | Eth | N | Y | Requires nonce param | | [`eth_createAccessList`](#eth_createaccesslist) | Eth | Y | Y | EIP-2930 | | `eth_blobBaseFee` | Eth | N | Y | EIP-4844 not implemented | | `eth_fillTransaction` | Eth | N | Y | Not implemented | | [`eth_signTypedData`](#eth_signtypeddata) | Eth | N | N | Requires domain param | | `eth_signTypedData_v3` | Eth | N | N | Not implemented | | `eth_signTypedData_v4` | Eth | N | N | Not implemented | | `eth_pendingTransactions` | Eth | N | Y | Not implemented | | `eth_getPendingTransactions` | Eth | Y | Y | Deprecated in geth | | `eth_getHeaderByHash` | Eth | Y | Y | | | `eth_getHeaderByNumber` | Eth | Y | Y | | | `eth_simulateV1` | Eth | N | Y | Geth-specific, not implemented | | `eth_getRawTransactionByHash` | Eth | N | Y | Not implemented | | `eth_getRawTransactionByBlockNumberAndIndex` | Eth | N | Y | Not implemented | | `eth_getRawTransactionByBlockHashAndIndex` | Eth | N | Y | Not implemented | | [`eth_subscribe`](#eth_subscribe) | Websocket | Y | Y | WebSocket only | | [`eth_unsubscribe`](#eth_unsubscribe) | Websocket | Y | Y | WebSocket only | | [`personal_importRawKey`](#personal_importrawkey) | Personal | N | N | Requires valid hex key | | [`personal_listAccounts`](#personal_listaccounts) | Personal | Y | N | Requires `v0.4.x` mempool | | [`personal_lockAccount`](#personal_lockaccount) | Personal | Stub | N | Always false | | [`personal_newAccount`](#personal_newaccount) | Personal | Y | N | Requires `v0.4.x` mempool | | [`personal_unlockAccount`](#personal_unlockaccount) | Personal | Stub | N | Always false | | [`personal_sendTransaction`](#personal_sendtransaction) | Personal | N | N | Not implemented | | [`personal_sign`](#personal_sign) | Personal | Y | N | Requires `v0.4.x` mempool | | [`personal_ecRecover`](#personal_ecrecover) | Personal | N | N | Requires 65-byte sig | | [`personal_initializeWallet`](#personal_initializewallet) | Personal | N | N | Not implemented | | [`personal_unpair`](#personal_unpair) | Personal | N | N | Not implemented | | `personal_listWallets` | Personal | Stub | N | Returns null | | `personal_signTransaction` | Personal | N | N | Not implemented | | `personal_signAndSendTransaction` | Personal | N | N | Not implemented | | `personal_openWallet` | Personal | N | N | Not implemented | | `personal_deriveAccount` | Personal | N | N | Not implemented | | `db_putString` | DB | N | N | Deprecated | | `db_getString` | DB | N | N | Deprecated | | `db_putHex` | DB | N | N | Deprecated | | `db_getHex` | DB | N | N | Deprecated | | `shh_post` | SSH | N | N | Deprecated | | `shh_version` | SSH | N | N | Deprecated | | `shh_newIdentity` | SSH | N | N | Deprecated | | `shh_hasIdentity` | SSH | N | N | Deprecated | | `shh_newGroup` | SSH | N | N | Deprecated | | `shh_addToGroup` | SSH | N | N | Deprecated | | `shh_newFilter` | SSH | N | N | Deprecated | | `shh_uninstallFilter` | SSH | N | N | Deprecated | | `shh_getFilterChanges` | SSH | N | N | Deprecated | | `shh_getMessages` | SSH | N | N | Deprecated | | `admin_addPeer` | Admin | N | N | Returns undefined | | `admin_removePeer` | Admin | N | N | Returns undefined | | `admin_datadir` | Admin | N | N | Returns undefined | | `admin_nodeInfo` | Admin | N | N | Returns undefined | | `admin_peers` | Admin | N | N | Returns undefined | | `admin_startHTTP` | Admin | N | N | Returns undefined | | `admin_startWS` | Admin | N | N | Returns undefined | | `admin_stopHTTP` | Admin | N | N | Returns undefined | | `admin_stopWS` | Admin | N | N | Returns undefined | | `admin_addTrustedPeer` | Admin | N | N | Returns undefined | | `admin_removeTrustedPeer` | Admin | N | N | Returns undefined | | `admin_startRPC` | Admin | N | N | Returns undefined | | `admin_stopRPC` | Admin | N | N | Returns undefined | | `admin_exportChain` | Admin | N | N | Returns undefined | | `admin_importChain` | Admin | N | N | Returns undefined | | `admin_sleepBlocks` | Admin | N | N | Returns undefined | | `admin_clearPeerBanList` | Admin | N | N | Returns undefined | | `admin_listPeerBanList` | Admin | N | N | Returns undefined | | `clique_getSnapshot` | Clique | N | N | Returns undefined | | `clique_getSnapshotAtHash` | Clique | N | N | Returns undefined | | `clique_getSigners` | Clique | N | N | Returns undefined | | `clique_getSignersAtHash` | Clique | N | N | Returns undefined | | `clique_propose` | Clique | N | N | Returns undefined | | `clique_discard` | Clique | N | N | Returns undefined | | `clique_status` | Clique | N | N | Returns undefined | | `clique_getSigner` | Clique | N | N | Returns undefined | | `debug_backtraceAt` | Debug | N | N | Returns undefined | | `debug_blockProfile` | Debug | Y | N | | | `debug_cpuProfile` | Debug | Y | N | | | `debug_dumpBlock` | Debug | N | N | Returns undefined | | `debug_gcStats` | Debug | Y | N | | | [`debug_getBlockRlp`](#debug_getblockrlp) | Debug | N | N | Requires uint64 param | | `debug_goTrace` | Debug | Y | N | | | `debug_freeOSMemory` | Debug | Y | N | | | `debug_memStats` | Debug | Y | N | | | `debug_mutexProfile` | Debug | Y | N | | | `debug_seedHash` | Debug | N | N | Not implemented | | `debug_setHead` | Debug | N | N | Not implemented | | `debug_setBlockProfileRate` | Debug | Y | N | | | `debug_setGCPercent` | Debug | Y | N | | | `debug_setMutexProfileFraction` | Debug | Y | N | | | `debug_stacks` | Debug | Y | N | | | `debug_startCPUProfile` | Debug | Y | N | | | `debug_startGoTrace` | Debug | Y | N | | | `debug_stopCPUProfile` | Debug | Y | N | | | `debug_stopGoTrace` | Debug | Y | N | | | `debug_traceBlock` | Debug | Y | N | | | [`debug_traceBlockByNumber`](#debug_traceblockbynumber) | Debug | Y | N | | | [`debug_traceBlockByHash`](#debug_traceblockhash) | Debug | Y | N | | | `debug_traceBlockFromFile` | Debug | N | N | Returns undefined | | `debug_standardTraceBlockToFile` | Debug | N | N | Returns undefined | | `debug_standardTraceBadBlockToFile` | Debug | N | N | Returns undefined | | [`debug_traceTransaction`](#debug_tracetransaction) | Debug | Y | N | | | `debug_traceCall` | Debug | Y | N | | | `debug_traceChain` | Debug | N | N | Returns undefined | | `debug_traceBadBlock` | Debug | N | N | Returns undefined | | `debug_verbosity` | Debug | N | N | Not implemented | | `debug_vmodule` | Debug | N | N | Not implemented | | `debug_writeBlockProfile` | Debug | Y | N | | | `debug_writeMemProfile` | Debug | Y | N | | | `debug_writeMutexProfile` | Debug | Y | N | | | `debug_getRawBlock` | Debug | N | N | Not implemented | | `debug_getRawHeader` | Debug | N | N | Not implemented | | `debug_getRawReceipts` | Debug | N | N | Not implemented | | `debug_getRawTransaction` | Debug | N | N | Not implemented | | [`debug_printBlock`](#debug_printblock) | Debug | Y | N | | | [`debug_getHeaderRlp`](#debug_getheaderrlp) | Debug | N | N | Requires uint64 param | | `debug_intermediateRoots` | Debug | Y | N | Returns empty hash | | `debug_getBadBlocks` | Debug | N | N | Not implemented | | `debug_storageRangeAt` | Debug | N | N | Not implemented | | `debug_getModifiedAccountsByNumber` | Debug | N | N | Not implemented | | `debug_getModifiedAccountsByHash` | Debug | N | N | Not implemented | | `les_serverInfo` | Les | N | N | Returns undefined | | `les_clientInfo` | Les | N | N | Returns undefined | | `les_priorityClientInfo` | Les | N | N | Returns undefined | | `les_addBalance` | Les | N | N | Returns undefined | | `les_setClientParams` | Les | N | N | Returns undefined | | `les_setDefaultParams` | Les | N | N | Returns undefined | | `les_latestCheckpoint` | Les | N | N | Returns undefined | | `les_getCheckpoint` | Les | N | N | Returns undefined | | `les_getCheckpointContractAddress` | Les | N | N | Returns undefined | | [`txpool_content`](#txpool_content) | TxPool | Y | Y | See [experimental mempool](/evm/latest/documentation/concepts/mempool) | | [`txpool_contentFrom`](#txpool_contentfrom) | TxPool | Y | Y | See [experimental mempool](/evm/latest/documentation/concepts/mempool) | | [`txpool_inspect`](#txpool_inspect) | TxPool | Y | Y | See [experimental mempool](/evm/latest/documentation/concepts/mempool) | | [`txpool_status`](#txpool_status) | TxPool | Y | Y | See [experimental mempool](/evm/latest/documentation/concepts/mempool) | | `trace_callMany` | Trace | N | N | Not implemented | | `eth_signTypedData` | Eth | W | N | EIP-712 partial | | `eth_fillTransaction` | Eth | Y | Y | | | `eth_getTransactionLogs` | Eth | Y | Y | **Cosmos-specific** | | `miner_start` | Miner | N | N | Returns undefined | | `miner_stop` | Miner | N | N | Returns undefined | | `miner_setEtherbase` | Miner | N | N | Returns undefined | | `miner_setGasPrice` | Miner | N | N | Returns undefined | | `miner_setGasLimit` | Miner | N | N | Returns undefined | | `miner_setExtra` | Miner | N | N | Returns undefined | | `miner_setRecommitInterval` | Miner | N | N | Returns undefined | | `miner_getHashrate` | Miner | N | N | Returns undefined | | `engine_newPayloadV1` | Engine | N | N | Returns undefined | | `engine_newPayloadV2` | Engine | N | N | Returns undefined | | `engine_newPayloadV3` | Engine | N | N | Returns undefined | | `engine_forkchoiceUpdatedV1` | Engine | N | N | Returns undefined | | `engine_forkchoiceUpdatedV2` | Engine | N | N | Returns undefined | | `engine_forkchoiceUpdatedV3` | Engine | N | N | Returns undefined | | `engine_getPayloadV1` | Engine | N | N | Returns undefined | | `engine_getPayloadV2` | Engine | N | N | Returns undefined | | `engine_getPayloadV3` | Engine | N | N | Returns undefined | | `engine_exchangeCapabilities` | Engine | N | N | Returns undefined | | `engine_exchangeTransitionConfigurationV1` | Engine | N | N | Returns undefined | | `engine_getPayloadBodiesByHashV1` | Engine | N | N | Returns undefined | | `engine_getPayloadBodiesByRangeV1` | Engine | N | N | Returns undefined | | `engine_getBlobsV1` | Engine | N | N | Returns undefined | | `trace_call` | Trace | N | N | Not implemented | | `trace_rawTransaction` | Trace | N | N | Not implemented | | `trace_replayBlockTransactions` | Trace | N | N | Not implemented | | `trace_replayTransaction` | Trace | N | N | Not implemented | | `trace_block` | Trace | N | N | Not implemented | | `trace_filter` | Trace | N | N | Not implemented | | `trace_get` | Trace | N | N | Not implemented | | `trace_transaction` | Trace | N | N | Not implemented | Block Number can be entered as a Hex string, `"earliest"`, `"latest"` or `"pending"`. **EIP-1559 Support**: Cosmos EVM supports EIP-1559 transaction types with `eth_maxPriorityFeePerGas`, though `eth_feeHistory` is not yet implemented. **Consensus Differences**: Due to using 'CometBFT' BFT consensus instead of Proof of Work/Stake: * Uncle-related methods always return 0 or null * Block reorganizations are not possible * Engine API (`engine_*`) is not applicable - 'CometBFT' handles consensus **Methods Not Implemented**: * `eth_createAccessList` (EIP-2930) - Access list transactions * `eth_feeHistory` - Fee history for EIP-1559 * `eth_getFilterChanges` / `eth_getFilterLogs` - Filter management issues * `eth_fillTransaction` - Transaction filling utility * `eth_resend` - Transaction resending utility * `personal_sendTransaction` / `personal_ecRecover` - Personal namespace methods * `personal_lockAccount` - Always returns false (not supported) * `debug_getRaw*` methods - Raw data access methods * `debug_printBlock` - Block printing utility * `trace_*` methods - Advanced tracing features * `engine_*` methods - Post-merge Engine API (uses 'CometBFT' instead) * `admin_*` methods - Node administration * `les_*` methods - Light Ethereum Subprotocol * `clique_*` methods - Proof of Authority consensus **Partial Implementations**: * `eth_getProof` - Requires block height > 2 * `eth_signTypedData` (EIP-712) - Partial implementation * `eth_subscribe` - Works for newHeads, logs, and newPendingTransactions but not syncing * `personal_unlockAccount` - Always returns false * `debug_traceTransaction` - Has issues with block height For more details on how Cosmos EVM differs from standard Ethereum implementations, see [Differences from Standard EVMs](/evm/latest/documentation/evm-compatibility). ## Examples Below is a list of the RPC methods, the parameters and an example response from the namespaces. ## Web3 Methods ### `web3_clientVersion` Get the web3 client version. #### Parameters (0) #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"jsonrpc":"2.0","id":1,"result":"Cosmos/0.1.3+/linux/go1.18"} ``` #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST -H "Content-Type: application/json" http://localhost:8545 --data '{"jsonrpc": "2.0", "id": 42, "method": "web3_clientVersion", "params": []}' ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "web3_clientVersion", "params": []}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} web3.clientVersion(); ``` ### `web3_sha3` Returns Keccak-256 (not the standardized SHA3-256) of the given data. #### Parameters (1) 1: input `hexutil.Bytes` * Required: \[Y] Yes #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"jsonrpc":"2.0","id":1,"result":"0x1b84adea42d5b7d192fd8a61a85b25abe0757e9a65cab1da470258914053823f"} ``` #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST -H "Content-Type: application/json" http://localhost:8545 --data '{"jsonrpc": "2.0", "id": 42, "method": "web3_sha3", "params": []}' ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "web3_sha3", "params": []}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} web3.sha3(input); ``` ## Net Methods ### `net_version` Returns the current network id. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"8"} ``` ### `net_peerCount` Returns the number of peers currently connected to the client. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":23} ``` ### `net_listening` Returns if client is actively listening for network connections. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"net_listening","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":true} ``` ## Eth Methods ### `eth_protocolVersion` Returns the current ethereum protocol version. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_protocolVersion","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x3f"} ``` ### `eth_syncing` The sync status object may need to be different depending on the details of cometbft's sync protocol. However, the 'synced' result is simply a boolean, and can easily be derived from cometbft's internal sync state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":false} ``` ### `eth_gasPrice` Returns the current gas price in the default EVM denomination parameter. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x0"} ``` ### `eth_accounts` Returns array of all eth accounts. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":["0x3b7252d007059ffc82d16d022da3cbf9992d2f70","0xddd64b4712f7c8f1ace3c145c950339eddaf221d","0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0"]} ``` ### `eth_blockNumber` Returns the current block height. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x66"} ``` ### `eth_getBalance` Returns the account balance for a given account address and Block Number. #### Parameters * Account Address * Block Number or Block Hash ([EIP-1898](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0", "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x36354d5575577c8000"} ``` ### `eth_getStorageAt` Returns the storage address for a given account address. #### Parameters * Account Address * Integer of the position in the storage * Block Number or Block Hash ([EIP-1898](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0", "0", "latest"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000000"} ``` ### `eth_getTransactionCount` Returns the total transaction for a given account address and Block Number. #### Parameters * Account Address * Block Number or Block Hash ([EIP-1898](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x7bf7b17da59880d9bcca24915679668db75f9397", "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x8"} ``` ### `eth_getBlockTransactionCountByNumber` Returns the total transaction count for a given block number. #### Parameters * Block number ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockTransactionCountByNumber","params":["0x1"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"difficulty":null,"extraData":"0x0","gasLimit":"0xffffffff","gasUsed":"0x0","hash":"0x8101cc04aea3341a6d4b3ced715e3f38de1e72867d6c0db5f5247d1a42fbb085","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","nonce":null,"number":"0x17d","parentHash":"0x70445488069d2584fea7d18c829e179322e2b2185b25430850deced481ca2e77","sha3Uncles":null,"size":"0x1df","stateRoot":"0x269bb17fe7adb8dd5f15f57b717979f82078d6b7a675c1ba1b0da2d27e415fcc","timestamp":"0x5f5ba97c","totalDifficulty":null,"transactions":[],"transactionsRoot":"0x","uncles":[]}} ``` ### `eth_getBlockTransactionCountByHash` Returns the total transaction count for a given block hash. #### Parameters * Block Hash ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockTransactionCountByHash","params":["0x8101cc04aea3341a6d4b3ced715e3f38de1e72867d6c0db5f5247d1a42fbb085"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x3"} ``` ### `eth_getCode` Returns the code for a given account address and Block Number. #### Parameters * Account Address * Block Number or Block Hash ([EIP-1898](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getCode","params":["0x7bf7b17da59880d9bcca24915679668db75f9397", "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0xef616c92f3cfc9e92dc270d6acff9cea213cecc7020a76ee4395af09bdceb4837a1ebdb5735e11e7d3adb6104e0c3ac55180b4ddf5e54d022cc5e8837f6a4f971b"} ``` ### `eth_sign` The `sign` method calculates an Ethereum specific signature with: `sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))`. By adding a prefix to the message makes the calculated signature recognizable as an Ethereum specific signature. This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim. The address to sign with must be unlocked. #### Parameters * Account Address * Message to sign ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sign","params":["0x3b7252d007059ffc82d16d022da3cbf9992d2f70", "0xdeadbeaf"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x909809c76ed2a5d38733de39207d0f411222b9b49c64a192bf649cb13f63f37b45acb4f6939facb4f1c277bc70fb00407564140c0f18600ac44388f2c1dfd1dc1b"} ``` ### `eth_signTypedData` Signs typed structured data according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). This method provides a more secure way to sign structured data compared to `eth_sign`. **Implementation Note**: This method requires a properly formatted domain parameter in the typed data structure. Without the domain, it will return an error: "domain is undefined". #### Parameters * Account Address (20 bytes) * Typed data object containing: * `domain`: EIP-712 domain separator * `types`: Type definitions * `primaryType`: Primary type name * `message`: Message to sign ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note: proper domain structure is required curl -X POST --data '{"jsonrpc":"2.0","method":"eth_signTypedData","params":["0x3b7252d007059ffc82d16d022da3cbf9992d2f70", {"domain":{"name":"Example","version":"1","chainId":1,"verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}]},"primaryType":"Person","message":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"}}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x..."} // Returns signature ``` ### `eth_sendTransaction` Sends transaction from given account to a given account. #### Parameters * Object containing: `from`: `DATA`, 20 Bytes - The address the transaction is send from. `to`: `DATA`, 20 Bytes - (optional when creating new contract) The address the transaction is directed to. `gas`: QUANTITY - (optional, default: 90000) Integer of the gas provided for the transaction execution. It will return unused gas. `gasPrice`: QUANTITY - (optional, default: To-Be-Determined) Integer of the gasPrice used for each paid gas `value`: QUANTITY - value sent with this transaction `data`: `DATA` - The compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI. `nonce`: QUANTITY - (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0x3b7252d007059ffc82d16d022da3cbf9992d2f70", "to":"0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0", "value":"0x16345785d8a0000", "gasLimit":"0x5208", "gasPrice":"0x55ae82600"}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x33653249db68ebe5c7ae36d93c9b2abc10745c80a72f591e296f598e2d4709f6"} ``` ### `eth_sendRawTransaction` Creates new message call transaction or a contract creation for signed transactions. You can get signed transaction data using the [`personal_sign`](https://www.google.com/search?q=%23personal-sign) method. #### Parameters * The signed transaction data ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xf9ff74c86aefeb5f6019d77280bbb44fb695b4d45cfe97e6eed7acd62905f4a85034d5c68ed25a2e7a8eeb9baf1b8401e4f865d92ec48c1763bf649e354d900b1c"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000000"} ``` ### `eth_call` Executes a new message call immediately without creating a transaction on the block chain. #### Parameters * Object containing: `from`: `DATA`, 20 Bytes - (optional) The address the transaction is sent from. `to`: `DATA`, 20 Bytes - The address the transaction is directed to. `gas`: QUANTITY - gas provided for the transaction execution. eth\_call consumes zero gas, but this parameter may be needed by some executions. `gasPrice`: QUANTITY - gasPrice used for each paid gas `value`: QUANTITY - value sent with this transaction `data`: `DATA` - (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation * Block number or Block Hash ([EIP-1898](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x3b7252d007059ffc82d16d022da3cbf9992d2f70", "to":"0xddd64b4712f7c8f1ace3c145c950339eddaf221d", "gas":"0x5208", "gasPrice":"0x55ae82600", "value":"0x16345785d8a0000", "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}, "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x"} ``` ### `eth_createAccessList` Creates an access list for a transaction. Access lists specify which addresses and storage keys a transaction will access, potentially reducing gas costs for complex transactions. **Implementation:** [`rpc/backend/tx_info.go:396`](https://github.com/cosmos/evm/blob/main/rpc/backend/tx_info.go#L396) #### Parameters * Object containing transaction data: * `from`: `DATA`, 20 Bytes - (optional) The address the transaction is sent from * `to`: `DATA`, 20 Bytes - The address the transaction is directed to * `gas`: `QUANTITY` - (optional) Gas provided for execution * `gasPrice`: `QUANTITY` - (optional) Gas price for each unit of gas * `value`: `QUANTITY` - (optional) Value sent with the transaction * `data`: `DATA` - (optional) Transaction data payload * Block number or Block Hash ([EIP-1898](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)) #### Returns * Object containing: * `accessList`: Array of access list entries * `address`: `DATA`, 20 Bytes - Account address * `storageKeys`: Array of `DATA` - Storage keys accessed * `gasUsed`: `QUANTITY` - Gas used for execution with access list #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{ "jsonrpc":"2.0", "method":"eth_createAccessList", "params":[{ "from":"0x8ba1f109551bD432803012645Hac136c5dd7E3D40", "to":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "gas":"0x76c0", "gasPrice":"0x9184e72a000", "data":"0xa9059cbb000000000000000000000000a0b86a33e6d1cde81f9c1ce51dd74f3d1111bb9e0000000000000000000000000000000000000000000000000de0b6b3a7640000" }, "latest"], "id":1 }' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{ "jsonrpc": "2.0", "id": 1, "method": "eth_createAccessList", "params": [{ "from":"0x8ba1f109551bD432803012645Hac136c5dd7E3D40", "to":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "data":"0xa9059cbb000000000000000000000000a0b86a33e6d1cde81f9c1ce51dd74f3d1111bb9e0000000000000000000000000000000000000000000000000de0b6b3a7640000" }, "latest"] }' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} await provider.send("eth_createAccessList", [{ from: "0x8ba1f109551bD432803012645Hac136c5dd7E3D40", to: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", data: "0xa9059cbb000000000000000000000000a0b86a33e6d1cde81f9c1ce51dd74f3d1111bb9e0000000000000000000000000000000000000000000000000de0b6b3a7640000" }, "latest"]); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc":"2.0", "id":1, "result":{ "accessList":[ { "address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "storageKeys":[ "0x0000000000000000000000000000000000000000000000000000000000000000", "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace" ] } ], "gasUsed":"0x7536" } } ``` #### Usage Example ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Generate access list for optimization const accessList = await provider.send("eth_createAccessList", [{ to: contractAddress, data: contract.interface.encodeFunctionData("complexFunction", [arg1, arg2]), gasPrice: "0x" + gasPrice.toString(16) }, "latest"]); // Use access list in actual transaction for gas savings const tx = await contract.complexFunction(arg1, arg2, { accessList: accessList.accessList, gasLimit: accessList.gasUsed }); ``` ### `eth_estimateGas` Returns an estimate value of the gas required to send the transaction. #### Parameters * Object containing: `from`: `DATA`, 20 Bytes - The address the transaction is send from. `to`: `DATA`, 20 Bytes - (optional when creating new contract) The address the transaction is directed to. `value`: `QUANTITY` - value sent with this transaction ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_estimateGas","params":[{"from":"0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0", "to":"0x3b7252d007059ffc82d16d022da3cbf9992d2f70", "value":"0x16345785d8a00000"}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x1199b"} ``` ### `eth_getBlockByNumber` Returns information about a block by block number. #### Parameters * Block Number * If true it returns the full transaction objects, if false only the hashes of the transactions. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x1", false],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"difficulty":null,"extraData":"0x0","gasLimit":"0xffffffff","gasUsed":null,"hash":"0xabac6416f737a0eb54f47495b60246d405d138a6a64946458cf6cbeae0d48465","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","nonce":null,"number":"0x1","parentHash":"0x","sha3Uncles":null,"size":"0x9b","stateRoot":"0x","timestamp":"0x5f5bd3e5","totalDifficulty":null,"transactions":[],"transactionsRoot":"0x","uncles":[]}} ``` ### `eth_getBlockByHash` Returns the block info given the hash found in the command above and a bool. #### Parameters * Hash of a block. * If true it returns the full transaction objects, if false only the hashes of the transactions. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockByHash","params":["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4", false],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"difficulty":null,"extraData":"0x0","gasLimit":"0xffffffff","gasUsed":null,"hash":"0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4","logsBloom":"0x00000000100000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000002000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","nonce":null,"number":"0xc","parentHash":"0x404e58f31a9ede1b614b98701d6b0fbf1450f186842dbcf6426dd16811a5ca0d","sha3Uncles":null,"size":"0x307","stateRoot":"0x599ccdb111fc62c6398dc39be957df8e97bf8ab72ce6c06ff10641a92b754627","timestamp":"0x5f5fdbbd","totalDifficulty":null,"transactions":["0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615"],"transactionsRoot":"0x4764dba431128836fa919b83d314ba9cc000e75f38e1c31a60484409acea777b","uncles":[]}} ``` ### `eth_getHeaderByHash` Returns block header information by block hash. The header contains metadata about the block without the full transaction list. **Implementation:** [`rpc/backend/headers.go:21`](https://github.com/cosmos/evm/blob/main/rpc/backend/headers.go#L21) #### Parameters * `DATA`, 32 Bytes - Hash of a block #### Returns * Object - Block header object containing: * `parentHash`: `DATA`, 32 Bytes - Hash of the parent block * `sha3Uncles`: `DATA`, 32 Bytes - SHA3 of the uncles data in the block * `miner`: `DATA`, 20 Bytes - Address of the block miner * `stateRoot`: `DATA`, 32 Bytes - Root of the state trie * `transactionsRoot`: `DATA`, 32 Bytes - Root of the transaction trie * `receiptsRoot`: `DATA`, 32 Bytes - Root of the receipts trie * `logsBloom`: `DATA`, 256 Bytes - Bloom filter for block logs * `difficulty`: `QUANTITY` - Difficulty for this block * `number`: `QUANTITY` - Block number * `gasLimit`: `QUANTITY` - Maximum gas allowed in this block * `gasUsed`: `QUANTITY` - Total gas used by all transactions * `timestamp`: `QUANTITY` - Unix timestamp when block was created * `extraData`: `DATA` - Extra data field of this block * `mixHash`: `DATA`, 32 Bytes - Mix hash * `nonce`: `DATA`, 8 Bytes - Proof-of-work hash * `baseFeePerGas`: `QUANTITY` - Base fee per gas (EIP-1559) * `hash`: `DATA`, 32 Bytes - Hash of the block #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{ "jsonrpc":"2.0", "method":"eth_getHeaderByHash", "params":["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4"], "id":1 }' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{ "jsonrpc": "2.0", "id": 1, "method": "eth_getHeaderByHash", "params": ["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4"] }' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} await provider.send("eth_getHeaderByHash", ["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4"]); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc":"2.0", "id":1, "result":{ "parentHash":"0x404e58f31a9ede1b614b98701d6b0fbf1450f186842dbcf6426dd16811a5ca0d", "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "miner":"0x0000000000000000000000000000000000000000", "stateRoot":"0x599ccdb111fc62c6398dc39be957df8e97bf8ab72ce6c06ff10641a92b754627", "transactionsRoot":"0x4764dba431128836fa919b83d314ba9cc000e75f38e1c31a60484409acea777b", "receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "logsBloom":"0x00000000100000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000002000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000", "difficulty":"0x0", "number":"0xc", "gasLimit":"0xffffffff", "gasUsed":"0x5289", "timestamp":"0x5f5fdbbd", "extraData":"0x0", "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000", "nonce":"0x0000000000000000", "baseFeePerGas":"0x7", "hash":"0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4" } } ``` ### `eth_getHeaderByNumber` Returns block header information by block number. Similar to `eth_getHeaderByHash` but accepts a block number instead of a hash. **Implementation:** [`rpc/backend/headers.go:13`](https://github.com/cosmos/evm/blob/main/rpc/backend/headers.go#L13) #### Parameters * Block number as `QUANTITY` (hex string) or block tag: * `"latest"` - Latest mined block * `"earliest"` - Genesis block * `"pending"` - Pending state/transactions #### Returns * Object - Block header object (same structure as `eth_getHeaderByHash`) #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{ "jsonrpc":"2.0", "method":"eth_getHeaderByNumber", "params":["latest"], "id":1 }' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{ "jsonrpc": "2.0", "id": 1, "method": "eth_getHeaderByNumber", "params": ["0x1b4"] }' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Get latest block header await provider.send("eth_getHeaderByNumber", ["latest"]); // Get specific block header await provider.send("eth_getHeaderByNumber", ["0xc"]); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc":"2.0", "id":1, "result":{ "parentHash":"0x404e58f31a9ede1b614b98701d6b0fbf1450f186842dbcf6426dd16811a5ca0d", "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "miner":"0x0000000000000000000000000000000000000000", "stateRoot":"0x599ccdb111fc62c6398dc39be957df8e97bf8ab72ce6c06ff10641a92b754627", "transactionsRoot":"0x4764dba431128836fa919b83d314ba9cc000e75f38e1c31a60484409acea777b", "receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "logsBloom":"0x00000000100000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000002000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000", "difficulty":"0x0", "number":"0xc", "gasLimit":"0xffffffff", "gasUsed":"0x5289", "timestamp":"0x5f5fdbbd", "extraData":"0x0", "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000", "nonce":"0x0000000000000000", "baseFeePerGas":"0x7", "hash":"0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4" } } ``` ### `eth_getTransactionByHash` Returns transaction details given the ethereum tx something. #### Parameters * hash of a transaction ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xec5fa15e1368d6ac314f9f64118c5794f076f63c02e66f97ea5fe1de761a8973"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x7a7398cc11d9c4c8e6f53e0c73824297aceafdab62db9e4b867a0da694384864","blockNumber":"0x188","from":"0x3b7252d007059ffc82d16d022da3cbf9992d2f70","gas":"0x147ee","gasPrice":"0x3b9aca00","hash":"0xec5fa15e1368d6ac314f9f64118c5794f076f63c02e66f97ea5fe1de761a8973","input":"0x6dba746c","nonce":"0x18","to":"0xa655256f589060437e5ffe2246dec385d040f148","transactionIndex":"0x0","value":"0x0","v":"0xa96","r":"0x6db399d694a452fb4106419140a6e5dbbe6817743a0f6f695a651e6576e59a5e","s":"0x25dd6ab1f936d0280d2fed0caeb0ebe5b9a46de6d8cb08ad8fd2c88deb55fc31"}} ``` ### `eth_getTransactionByBlockHashAndIndex` Returns transaction details given the block hash and the transaction index. #### Parameters * Hash of a block. * Transaction index position. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByBlockHashAndIndex","params":["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4", "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4","blockNumber":"0xc","from":"0xddd64b4712f7c8f1ace3c145c950339eddaf221d","gas":"0x4c4b40","gasPrice":"0x3b9aca00","hash":"0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615","input":"0x4f2be91f","nonce":"0x0","to":"0x439c697e0742a0ddb124a376efd62a72a94ac35a","transactionIndex":"0x0","value":"0x0","v":"0xa96","r":"0xced57d973e58b0f634f776d57daf41d3d3387ceb347a3a72ca0746e5ec2b709e","s":"0x384e89e209a5eb147a2bac3a4e399507400ac7b29cd155531f9d6203a89db3f2"}} ``` ### `eth_getTransactionReceipt` Returns the receipt of a transaction by transaction hash. Note: Tx Code from CometBFT and the Ethereum receipt status are switched: | | CometBFT | Ethereum | | ------- | -------- | -------- | | Success | 0 | 1 | | Fail | 1 | 0 | #### Parameters * Hash of a transaction ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea614"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4","blockNumber":"0xc","contractAddress":"0x0000000000000000000000000000000000000000","cumulativeGasUsed":null,"from":"0xddd64b4712f7c8f1ace3c145c950339eddaf221d","gasUsed":"0x5289","logs":[{"address":"0x439c697e0742a0ddb124a376efd62a72a94ac35a","topics":["0x64a55044d1f2eddebe1b90e8e2853e8e96931cefadbfa0b2ceb34bee36061941"],"data":"0x0000000000000000000000000000000000000000000000000000000000000002","blockNumber":"0xc","transactionHash":"0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615","transactionIndex":"0x0","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x0","removed":false},{"address":"0x439c697e0742a0ddb124a376efd62a72a94ac35a","topics":["0x938d2ee5be9cfb0f7270ee2eff90507e94b37625d9d2b3a61c97d30a4560b829"],"data":"0x0000000000000000000000000000000000000000000000000000000000000002","blockNumber":"0xc","transactionHash":"0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615","transactionIndex":"0x0","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x1","removed":false}],"logsBloom":"0x00000000100000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000002000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x439c697e0742a0ddb124a376efd62a72a94ac35a","transactionHash":"0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615","transactionIndex":"0x0"}} ``` ### `eth_newFilter` Create new filter using topics of some kind. #### Parameters * hash of a transaction ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_newFilter","params":[{"topics":["0x0000000000000000000000000000000000000000000000000000000012341234"]}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0xdc714a4a2e3c39dc0b0b84d66a3ccb00"} ``` ### `eth_newBlockFilter` Creates a filter in the node, to notify when a new block arrives. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_newBlockFilter","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x3503de5f0c766c68f78a03a3b05036a5"} ``` ### `eth_newPendingTransactionFilter` Creates a filter in the node, to notify when new pending transactions arrive. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_newPendingTransactionFilter","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x9daacfb5893d946997d3801ea18e9902"} ``` ### `eth_uninstallFilter` Removes the filter with the given filter id. Returns true if the filter was successfully uninstalled, otherwise false. #### Parameters * The filter id ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_uninstallFilter","params":["0xb91b6608b61bf56288a661a1bd5eb34a"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":true} ``` ### `eth_getFilterChanges` Polling method for a filter, which returns an array of logs which occurred since last poll. #### Parameters * The filter id ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getFilterChanges","params":["0x127e9eca4f7751fb4e5cb5291ad8b455"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":["0xc6f08d183a81e149896fc5317c872f9092068e88e956ca1864e9bd4c81c09b44","0x3ca6dfb5be15549d721d1b3d10c1bec50ed6217c9ac7b61df361fac9692a27e5","0x776fffac134171acb1ebf2e59856625501ad5ccc5c4c8fe0359e0d4dff8919f2","0x84123103704dbd738c089276ab2b04b5936330b24f6e78453c4ba8bf4848aaf9","0xffddbe5bd8e8aa41e44002daa9ea89ade9e6980a0d83f51d104cf16498827eca","0x53430e49963e8ae32605d8f22dec2e757a691e6436d593854ca4d9383eeab86a","0x975948058c9351a91fbec332ca00dda39d1a919f5f16b996a4c7e30c38ba423b","0x619e37e32024c8efef7f7220e6caff4ee1d682ea78b2ac91e0a6b30850dc0677","0x31a5d985a40d08303ac68000ce008df512bcd1a911c497415c97f0624b4a271a","0x91dcf1fce4503a8dbb3e6fb61073f25cd31d69c766ecba639fefde4436e59d07","0x606d9e0143cfdb410a6812c590a8135b5c6b5c59eec26d760d5cd930aa47257d","0xd3c00b859b29b20ba654415eef648ef58251389c73a138580db87675b0d5465f","0x954391f0eb50888be90489898016ebb54f750f612f3adec2a00854955d5e52d8","0x698905f06aff921a9e9fcef39b8b0d107747c3e6204d2ea79cf4c12debf8d253","0x9fcafec5721938a06eb8e2951ede4b6ef8fae54a8c8f85f3166ec9782a0032b5","0xaec6d3364e47a5716ba69e4705f3c705d017f81298859589591183bfea87be7a","0x91bf2ee13319b6eaca96ed89c126437b66c4df1b13560c6a9bb18556ee3b7e1f","0x4f426dc1fc0ea8149052033065b237892d2d34927b2d558ab50c5a7fb98d6e79","0xdd809fb07e5aab638fef5311371b4e2b27c9c9a6183fde0cdd2b7724f6d2a89b","0x7e12fc92ab953e233a304959a2a8474d96195e71efd9388fdceb1326a577811a","0x30618ef6b490c3cc9979c47163459db37c1a1e0aa5793c56accd417f9d89973b","0x614609f06ee24bae7408e45895b1a25e6b19a8159aeea7a95c9d1339d9ba286f","0x115ddc6d533620040791d241f01f1c5ae3d9d1a8f64b15af5e9793e4d9096e22","0xb7458c9323beeca2cd54f32a6af5671f3cd5a7a251aed9d82bdd6ebe5f56305b","0x573dd48a5ba7bf4cc3d49597cd7419f75ecc9897258f1ebadebd670446d0d358","0xcb6670918439f9698413b53f3b5336d82ca4be152fdefaacf45e052fff6262fc","0xf3fe2a8945abafd269ab97bfdc80b3dbff2202ffdce59a227f952874b966b230","0x989980707007533cc0840a079f77f261a2e818abae1a1ffd3af02f3fff1d35fd","0x886b6ae365fec996be8a9a2c31cf4cda97ff8352908be2c83f17abd66ef1591e","0xfd90df68706ef95a62b317de93d6899a9bd6c80416e42d007f5c30fcdedfce24","0x7af8491fbb0373886d9032bb74e0ef52ed9e100f260b79bd15f46126b38cbede","0x91d1e2cd55533cf7dd5de86c9aa73295e811b1279be193d429bbd6ba83810e16","0x6b65b3128c2104005a04923288fe2aa33a2477a4962bef70532f94cab582f2a7"]} ``` ### `eth_getFilterLogs` Returns an array of all logs matching filter with given id. #### Parameters * `QUANTITY` - The filter id ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getFilterLogs","params":["0x127e9eca4f7751fb4e5cb5291ad8b455"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"filter 0x35b64c227ce30e84fc5c7bd347be380e doesn't have a LogsSubscription type: got 5"}} ``` ### `eth_getLogs` Returns an array of all logs matching a given filter object. #### Parameters * Object containing: `fromBlock`: `QUANTITY|TAG` - (optional, default: `"latest"`) Integer block number, or `"latest"` for the last mined block or `"pending"`, `"earliest"` for not yet mined transactions. `toBlock`: `QUANTITY|TAG` - (optional, default: `"latest"`) Integer block number, or `"latest"` for the last mined block or `"pending"`, `"earliest"` for not yet mined transactions. `address`: `DATA|Array`, 20 Bytes - (optional) Contract address or a list of addresses from which logs should originate. `topics`: Array of `DATA`, - (optional) Array of 32 Bytes `DATA` topics. Topics are order-dependent. Each topic can also be an array of `DATA` with “or” options. `blockhash`: (optional, future) With the addition of [EIP-234](https://eips.ethereum.org/EIPS/eip-234), `blockHash` will be a new filter option which restricts the logs returned to the single block with the 32-byte hash `blockHash`. Using `blockHash` is equivalent to `fromBlock` = `toBlock` = the block number with hash `blockHash`. If `blockHash` is present in in the filter criteria, then neither `fromBlock` nor `toBlock` are allowed. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"topics":["0x775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd738898","0x0000000000000000000000000000000000000000000000000000000000000011"], "fromBlock":"latest"}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":[]} ``` ### `eth_coinbase` Returns the account the mining rewards will be send to. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_coinbase","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E"} ``` ### `eth_mining` Returns whether the client is actively mining new blocks. Always returns `false` as Cosmos EVM uses 'CometBFT' consensus instead of mining. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_mining","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":false} ``` ### `eth_hashrate` Returns the number of hashes per second that the node is mining with. Always returns `0x0` as Cosmos EVM uses 'CometBFT' consensus instead of mining. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_hashrate","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x0"} ``` ### `eth_chainId` Returns the chain ID used for signing replay-protected transactions. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x40000"} ``` ### `eth_getUncleCountByBlockHash` Returns the number of uncles in a block matching the given block hash. Always returns `0x0` as Cosmos EVM does not have uncles due to using 'CometBFT' consensus. #### Parameters * Block hash ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getUncleCountByBlockHash","params":["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x0"} ``` ### `eth_getUncleCountByBlockNumber` Returns the number of uncles in a block matching the given block number. Always returns `0x0` as Cosmos EVM does not have uncles due to using 'CometBFT' consensus. #### Parameters * Block number ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getUncleCountByBlockNumber","params":["latest"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x0"} ``` ### `eth_getUncleByBlockHashAndIndex` Returns information about an uncle by block hash and uncle index position. Always returns `null` as Cosmos EVM does not have uncles due to using 'CometBFT' consensus. #### Parameters * Block hash * Uncle index position ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getUncleByBlockHashAndIndex","params":["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4", "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `eth_getUncleByBlockNumberAndIndex` Returns information about an uncle by block number and uncle index position. Always returns `null` as Cosmos EVM does not have uncles due to using 'CometBFT' consensus. #### Parameters * Block number * Uncle index position ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getUncleByBlockNumberAndIndex","params":["0x1", "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `eth_getTransactionByBlockNumberAndIndex` Returns information about a transaction by block number and transaction index position. #### Parameters * Block number * Transaction index position ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByBlockNumberAndIndex","params":["0x1", "0x0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4","blockNumber":"0x1","from":"0xddd64b4712f7c8f1ace3c145c950339eddaf221d","gas":"0x4c4b40","gasPrice":"0x3b9aca00","hash":"0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615","input":"0x4f2be91f","nonce":"0x0","to":"0x439c697e0742a0ddb124a376efd62a72a94ac35a","transactionIndex":"0x0","value":"0x0","v":"0xa96","r":"0xced57d973e58b0f634f776d57daf41d3d3387ceb347a3a72ca0746e5ec2b709e","s":"0x384e89e209a5eb147a2bac3a4e399507400ac7b29cd155531f9d6203a89db3f2"}} ``` ### `eth_maxPriorityFeePerGas` Returns the current maxPriorityFeePerGas per gas in wei. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_maxPriorityFeePerGas","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x0"} ``` ### `eth_getBlockReceipts` Returns all transaction receipts for a given block. #### Parameters * Block number or block hash ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockReceipts","params":["latest"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":[{"blockHash":"0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4","blockNumber":"0xc","contractAddress":"0x0000000000000000000000000000000000000000","cumulativeGasUsed":null,"from":"0xddd64b4712f7c8f1ace3c145c950339eddaf221d","gasUsed":"0x5289","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x439c697e0742a0ddb124a376efd62a72a94ac35a","transactionHash":"0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615","transactionIndex":"0x0"}]} ``` ### `eth_getTransactionLogs` Returns the logs for a specific transaction. #### Parameters * Transaction hash ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionLogs","params":["0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":[{"address":"0x439c697e0742a0ddb124a376efd62a72a94ac35a","topics":["0x64a55044d1f2eddebe1b90e8e2853e8e96931cefadbfa0b2ceb34bee36061941"],"data":"0x0000000000000000000000000000000000000000000000000000000000000002","blockNumber":"0xc","transactionHash":"0xae64961cb206a9773a6e5efeb337773a6fd0a2085ce480a174135a029afea615","transactionIndex":"0x0","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x0","removed":false}]} ``` ### `eth_fillTransaction` Fills the defaults (nonce, gas, gasPrice or 1559 fields) on a given unsigned transaction, and returns it to the caller for further processing (signing + broadcast). #### Parameters * Transaction object with optional fields ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_fillTransaction","params":[{"from":"0x0123456789012345678901234567890123456789","to":"0x0123456789012345678901234567890123456789","value":"0x1"}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"raw":"0x...","tx":{"nonce":"0x0","gasPrice":"0x3b9aca00","gas":"0x5208","to":"0x0123456789012345678901234567890123456789","value":"0x1","input":"0x","v":"0x0","r":"0x0","s":"0x0","hash":"0x..."}}} ``` ### `eth_resend` Resends a transaction with updated gas parameters. Removes the given transaction from the pool and reinserts it with the new gas price and limit. **Implementation Note**: This method requires a transaction `nonce` parameter in the transaction object. Without the nonce, it will return an error: "missing transaction nonce in transaction spec". #### Parameters * Original transaction object (must include `nonce`) * New gas price (hex) * New gas limit (hex) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note the required 'nonce' field in the transaction object curl -X POST --data '{"jsonrpc":"2.0","method":"eth_resend","params":[{"from":"0x0123456789012345678901234567890123456789","to":"0x0123456789012345678901234567890123456789","value":"0x1","gas":"0x5208","gasPrice":"0x3b9aca00","nonce":"0x0"},"0x3b9aca01","0x5209"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x..."} // Returns transaction hash ``` ### `eth_getProof` Returns the account- and storage-values of the specified account including the Merkle-proof. **Implementation Note**: This method requires a valid block height. Using "latest" or block heights less than 1 will return an error: "cannot query with height \< 0; please provide a valid height". You must specify an actual block number. #### Parameters * Address of account or contract * Array of storage positions (32-byte storage slot) * Block Number (must be a specific number, not "latest") ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note: using a specific block number instead of "latest" curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getProof","params":["0x1234567890123456789012345678901234567890",["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001"],"0x1"],"id":1}' -H "Content-type:application/json" http://localhost:8545 // Result {"jsonrpc": "2.0", "id": 1, "result": {"address": "0x1234567890123456789012345678901234567890", "accountProof": ["0xf90211a090dcaf88c40c7bbc95a912cbdde67c175767b31173df9ee4b0d733bfdd511c43a0babe369f6b12092f49181ae04ca173fb68d1a5456f18d20fa32cba73954052bda0473ecf8a7e36a829e75039a3b055e51b8332cbf03324ab4af2066bbd6fbf0021a0bbda34753d7aa6c38e603f360244e8f59611921d9e1f128372fec0d586d4f9e0a04e44caecff45c9891f74f6a2156735886eedf6f1a733628ebc802ec79d844648a0a5f3f2f7542148c973977c8a1e154c4300fec92f755f7846f1b734d3ab1d90e7a0e823850f50bf72baae9d1733a36a444ab65d0a6faaba404f0583ce0ca4dad92da0f7a00cbe7d4b30b11faea3ae61b7f1f2b315b61d9f6bd68bfe587ad0eeceb721a07117ef9fc932f1a88e908eaead8565c19b5645dc9e5b1b6e841c5edbdfd71681a069eb2de283f32c11f859d7bcf93da23990d3e662935ed4d6b39ce3673ec84472a0203d26456312bbc4da5cd293b75b840fc5045e493d6f904d180823ec22bfed8ea09287b5c21f2254af4e64fca76acc5cd87399c7f1ede818db4326c98ce2dc2208a06fc2d754e304c48ce6a517753c62b1a9c1d5925b89707486d7fc08919e0a94eca07b1c54f15e299bd58bdfef9741538c7828b5d7d11a489f9c20d052b3471df475a051f9dd3739a927c89e357580a4c97b40234aa01ed3d5e0390dc982a7975880a0a089d613f26159af43616fd9455bb461f4869bfede26f2130835ed067a8b967bfb80", "0xf90211a0395d87a95873cd98c21cf1df9421af03f7247880a2554e20738eec2c7507a494a0bcf6546339a1e7e14eb8fb572a968d217d2a0d1f3bc4257b22ef5333e9e4433ca012ae12498af8b2752c99efce07f3feef8ec910493be749acd63822c3558e6671a0dbf51303afdc36fc0c2d68a9bb05dab4f4917e7531e4a37ab0a153472d1b86e2a0ae90b50f067d9a2244e3d975233c0a0558c39ee152969f6678790abf773a9621a01d65cd682cc1be7c5e38d8da5c942e0a73eeaef10f387340a40a106699d494c3a06163b53d956c55544390c13634ea9aa75309f4fd866f312586942daf0f60fb37a058a52c1e858b1382a8893eb9c1f111f266eb9e21e6137aff0dddea243a567000a037b4b100761e02de63ea5f1fcfcf43e81a372dafb4419d126342136d329b7a7ba032472415864b08f808ba4374092003c8d7c40a9f7f9fe9cc8291f62538e1cc14a074e238ff5ec96b810364515551344100138916594d6af966170ff326a092fab0a0d31ac4eef14a79845200a496662e92186ca8b55e29ed0f9f59dbc6b521b116fea090607784fe738458b63c1942bba7c0321ae77e18df4961b2bc66727ea996464ea078f757653c1b63f72aff3dcc3f2a2e4c8cb4a9d36d1117c742833c84e20de994a0f78407de07f4b4cb4f899dfb95eedeb4049aeb5fc1635d65cf2f2f4dfd25d1d7a0862037513ba9d45354dd3e36264aceb2b862ac79d2050f14c95657e43a51b85c80", "0xf90171a04ad705ea7bf04339fa36b124fa221379bd5a38ffe9a6112cb2d94be3a437b879a08e45b5f72e8149c01efcb71429841d6a8879d4bbe27335604a5bff8dfdf85dcea00313d9b2f7c03733d6549ea3b810e5262ed844ea12f70993d87d3e0f04e3979ea0b59e3cdd6750fa8b15164612a5cb6567cdfb386d4e0137fccee5f35ab55d0efda0fe6db56e42f2057a071c980a778d9a0b61038f269dd74a0e90155b3f40f14364a08538587f2378a0849f9608942cf481da4120c360f8391bbcc225d811823c6432a026eac94e755534e16f9552e73025d6d9c30d1d7682a4cb5bd7741ddabfd48c50a041557da9a74ca68da793e743e81e2029b2835e1cc16e9e25bd0c1e89d4ccad6980a041dda0a40a21ade3a20fcd1a4abb2a42b74e9a32b02424ff8db4ea708a5e0fb9a09aaf8326a51f613607a8685f57458329b41e938bb761131a5747e066b81a0a16808080a022e6cef138e16d2272ef58434ddf49260dc1de1f8ad6dfca3da5d2a92aaaadc58080", "0xf851808080a009833150c367df138f1538689984b8a84fc55692d3d41fe4d1e5720ff5483a6980808080808080808080a0a319c1c415b271afc0adcb664e67738d103ac168e0bc0b7bd2da7966165cb9518080"], "balance": "0x0", "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "nonce": "0x0", "storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "storageProof": [{"key": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "proof": []}, {"key": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "proof": []}]}} ``` ## WebSocket Methods Read more about using [websockets](https://ethereum.org/en/developers/tutorials/using-websockets/). ### `eth_subscribe` subscribe using JSON-RPC notifications. This allows clients to wait for events instead of polling for them. It works by subscribing to particular events. The node will return a subscription id. For each event that matches the subscription a notification with relevant data is send together with the subscription id. #### Parameters * Subscription Name * Optional Arguments ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request {"id": 1, "method": "eth_subscribe", "params": ["newHeads", {"includeTransactions": true}]} // Result < {"jsonrpc":"2.0","result":"0x34da6f29e3e953af4d0c7c58658fd525","id":1} ``` ### `eth_unsubscribe` Unsubscribe from an event using the subscription id #### Parameters * Subscription ID ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request {"id": 1, "method": "eth_unsubscribe", "params": ["0x34da6f29e3e953af4d0c7c58658fd525"]} // Result {"jsonrpc":"2.0","result":true,"id":1} ``` ## Personal Methods ### `personal_importRawKey` **Private**: Requires authentication. Imports the given unencrypted private key (hex encoded string) into the key store, encrypting it with the passphrase. Returns the address of the new account. **Implementation Note**: The private key must be a valid 64-character hex string (32 bytes) without the "0x" prefix. Invalid hex characters will return an error: "invalid hex character 'x' in private key". #### Parameters (2) **1:** privkey `string` * Required: \[Y] Yes * Format: 64 hex characters (32 bytes) without "0x" prefix **2:** password `string` * Required: \[Y] Yes ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note: private key without "0x" prefix curl -X POST --data '{"jsonrpc":"2.0","method":"personal_importRawKey","params":["c5bd76cd0cd948de17a31261567d219576e992d9066fe1a6bca97496dec634e2", "the key is this"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x..."} // Returns the address ``` ### `personal_listAccounts` **Private**: Requires authentication. Returns a list of addresses for accounts this node manages. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"personal_listAccounts","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":["0x3b7252d007059ffc82d16d022da3cbf9992d2f70","0xddd64b4712f7c8f1ace3c145c950339eddaf221d","0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0"]} ``` ### `personal_lockAccount` **Private**: Requires authentication. Removes the private key with given address from memory. The account can no longer be used to send transactions. #### Parameters * Account Address ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"personal_lockAccount","params":["0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":true} ``` ### `personal_newAccount` **Private**: Requires authentication. Generates a new private key and stores it in the key store directory. The key file is encrypted with the given passphrase. It returns the address of the new account. #### Parameters * Passphrase ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"personal_newAccount","params":["This is the passphrase"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0xf0e4086ad1c6aab5d42161d5baaae2f9ad0571c0"} ``` ### `personal_unlockAccount` **Private**: Requires authentication. Decrypts the key with the given address from the key store. Both passphrase and unlock duration are optional when using the JavaScript console. The unencrypted key will be held in memory until the unlock duration expires. If the unlock duration defaults to 300 seconds. An explicit duration of zero seconds unlocks the key until geth exits. The account can be used with [`eth_sign`](https://www.google.com/search?q=%23eth_sign) and [`eth_sendTransaction`](https://www.google.com/search?q=%23eth_sendtransaction) while it is unlocked. #### Parameters * Account Address * Passphrase * Duration ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"personal_unlockAccount","params":["0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0", "secret passphrase", 30],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":true} ``` ### `personal_sendTransaction` **Private**: Requires authentication. Validate the given passphrase and submit transaction. The transaction is the same argument as for [`eth_sendTransaction`](https://www.google.com/search?q=%23eth_sendtransaction) and contains the `from` address. If the passphrase can be used to decrypt the private key belonging to `tx.from` the transaction is verified, signed and send onto the network. The account is not unlocked globally in the node and cannot be used in other RPC calls. #### Parameters * Object containing: `from`: `DATA`, 20 Bytes - The address the transaction is send from. `to`: `DATA`, 20 Bytes - (optional when creating new contract) The address the transaction is directed to. `value`: QUANTITY - value sent with this transaction * Passphrase ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"personal_sendTransaction","params":[{"from":"0x3b7252d007059ffc82d16d022da3cbf9992d2f70","to":"0xddd64b4712f7c8f1ace3c145c950339eddaf221d", "value":"0x16345785d8a0000"}, "passphrase"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0xd2a31ec1b89615c8d1f4d08fe4e4182efa4a9c0d5758ace6676f485ea60e154c"} ``` ### `personal_sign` **Private**: Requires authentication. The sign method calculates an Ethereum specific signature with: `sign(keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)))`, #### Parameters * Message * Account Address * Password ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"personal_sign","params":["0xdeadbeaf", "0x3b7252d007059ffc82d16d022da3cbf9992d2f70", "password"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0xf9ff74c86aefeb5f6019d77280bbb44fb695b4d45cfe97e6eed7acd62905f4a85034d5c68ed25a2e7a8eeb9baf1b8401e4f865d92ec48c1763bf649e354d900b1c"} ``` ### `personal_ecRecover` **Private**: Requires authentication. `ecRecover` returns the address associated with the private key that was used to calculate the signature in [`personal_sign`](#personal_sign). **Implementation Note**: The signature must be exactly 65 bytes (130 hex characters). Signatures of other lengths will return an error: "signature must be 65 bytes long". #### Parameters * Message (hex encoded) * Signature returned from [`personal_sign`](#personal_sign) (65 bytes) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note: signature must be exactly 130 hex characters (65 bytes) curl -X POST --data '{"jsonrpc":"2.0","method":"personal_ecRecover","params":["0xdeadbeaf", "0xf9ff74c86aefeb5f6019d77280bbb44fb695b4d45cfe97e6eed7acd62905f4a85034d5c68ed25a2e7a8eeb9baf1b8401e4f865d92ec48c1763bf649e354d900b1c"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0x3b7252d007059ffc82d16d022da3cbf9992d2f70"} ``` ### `personal_initializeWallet` **Private**: Requires authentication. Initializes a new wallet at the provided URL, by generating and returning a new private key. #### Parameters (1) Parameters must be given by position. 1: url `string` * Required: \[Y] Yes #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST -H "Content-Type: application/json" http://localhost:8545 --data '{"jsonrpc": "2.0", "id": 42, "method": "personal_initializeWallet", "params": []}' ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "personal_initializeWallet", "params": []}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} personal.initializeWallet(url); ``` ### `personal_unpair` **Private**: Requires authentication. Unpair deletes a pairing between wallet and the node. #### Parameters (2) * URL * Pairing password #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST -H "Content-Type: application/json" http://localhost:8545 --data '{"jsonrpc": "2.0", "id": 42, "method": "personal_unpair", "params": [, ]}' ``` WebSocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "personal_unpair", "params": [, ]}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} personal.unpair(url,pin); ``` ### `personal_listWallets` **Private**: Requires authentication. Returns a list of wallets this node manages. Currently returns `null` as wallet-level management is not supported. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"personal_listWallets","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ## Debug Methods ### `debug_traceTransaction` The `traceTransaction` debugging method will attempt to run the transaction in the exact same manner as it was executed on the network. It will replay any transaction that may have been executed prior to this one before it will finally attempt to execute the transaction that corresponds to the given hash. #### Parameters * Trace Config ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0xddecdb13226339681372b44e01df0fbc0f446fca6f834b2de5ecb1e569022ec8", {"tracer": "{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"CALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}"}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 //Result ["68410", "51470"] ``` ### `debug_traceBlockByNumber` The `traceBlockByNumber` endpoint accepts a block number and will replay the block that is already present in the database. **Stub Implementation**: This method is implemented but currently returns an empty array for all requests. The tracing functionality is not fully operational in Cosmos EVM. #### Parameters * Block number (hex string) * Trace Config (optional) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["0xe", {"tracer": "{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"CALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}"}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result - Currently returns empty array {"jsonrpc":"2.0","id":1,"result":[]} ``` ### `debug_traceBlockByHash` Similar to `debug_traceBlockByNumber`, this method accepts a block hash and will replay the block that is already present in the database. **Stub Implementation**: This method is implemented but currently returns an empty array for all requests. The tracing functionality is not fully operational in Cosmos EVM. #### Parameters * Block hash (32-byte hash) * Trace Config (optional) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_traceBlockByHash","params":["0x1b9911f57c13e5160d567ea6cf5b545413f96b95e43ec6e02787043351fb2cc4", {}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result - Currently returns empty array {"jsonrpc":"2.0","id":1,"result":[]} ``` ### `debug_traceCall` Traces the execution of an `eth_call` within the context of a given block. This allows you to simulate a transaction and trace its execution without broadcasting it to the network. **Implementation:** [`rpc/namespaces/ethereum/debug/api.go:158`](https://github.com/cosmos/evm/blob/main/rpc/namespaces/ethereum/debug/api.go#L158) #### Parameters * Object containing transaction call data: * `from`: `DATA`, 20 Bytes - (optional) The address the call is sent from * `to`: `DATA`, 20 Bytes - The address the call is directed to * `gas`: `QUANTITY` - (optional) Gas provided for the call execution * `gasPrice`: `QUANTITY` - (optional) Gas price for each unit of gas * `value`: `QUANTITY` - (optional) Value sent with the call * `data`: `DATA` - (optional) Call data payload * Block number or Block Hash ([EIP-1898](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)) * Tracer configuration (optional) - Object containing: * `tracer`: `string` - Tracer type ("callTracer", "prestateTracer", "4byteTracer", or custom JavaScript tracer) * `timeout`: `string` - Timeout for trace execution (e.g., "5s") * `tracerConfig`: Object - Tracer-specific configuration options #### Returns * Object - Trace result structure depends on the tracer used: * **callTracer**: Returns call frame tree with gas usage and sub-calls * **prestateTracer**: Returns pre-state (account balances, nonces, code, storage) * **4byteTracer**: Returns map of 4-byte function selectors to call counts * **opcodeTracer**: Returns list of executed opcodes #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{ "jsonrpc":"2.0", "method":"debug_traceCall", "params":[{ "from":"0x8ba1f109551bD432803012645Hac136c5dd7E3D40", "to":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "data":"0x70a08231000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" }, "latest", {"tracer": "callTracer"}], "id":1 }' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{ "jsonrpc": "2.0", "id": 1, "method": "debug_traceCall", "params": [{ "from":"0x8ba1f109551bD432803012645Hac136c5dd7E3D40", "to":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "data":"0x70a08231000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" }, "latest", {"tracer": "callTracer"}] }' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} await provider.send("debug_traceCall", [{ from: "0x8ba1f109551bD432803012645Hac136c5dd7E3D40", to: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", data: "0x70a08231000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" }, "latest", {tracer: "callTracer"}]); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc":"2.0", "id":1, "result":{ "type":"CALL", "from":"0x8ba1f109551bd432803012645hac136c5dd7e3d40", "to":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "value":"0x0", "gas":"0x75c58", "gasUsed":"0x5289", "input":"0x70a08231000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "output":"0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", "calls":[] } } ``` #### Usage Examples ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Trace a contract call to debug execution const trace = await provider.send("debug_traceCall", [ { to: contractAddress, data: contract.interface.encodeFunctionData("transfer", [recipient, amount]) }, "latest", { tracer: "callTracer" } ]); // Use prestate tracer to see initial state const prestate = await provider.send("debug_traceCall", [ { to: contractAddress, data: callData }, "latest", { tracer: "prestateTracer" } ]); // Trace with timeout const trace = await provider.send("debug_traceCall", [ { from: sender, to: contractAddress, data: complexCallData }, "latest", { tracer: "callTracer", timeout: "10s" } ]); ``` ### `debug_freeOSMemory` Forces the Go runtime to perform a garbage collection and return unused memory to the OS. **Private**: Requires authentication. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_freeOSMemory","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_setGCPercent` Sets the garbage collection target percentage. A negative value disables garbage collection. **Private**: Requires authentication. #### Parameters * GC percentage (integer) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_setGCPercent","params":[100],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":100} ``` ### `debug_memStats` Returns detailed runtime memory statistics. **Private**: Requires authentication. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_memStats","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"Alloc":83328680,"TotalAlloc":451796592,"Sys":166452520,"Lookups":0,"Mallocs":4071615,"Frees":3772501,"HeapAlloc":83328680,"HeapSys":153452544,"HeapIdle":51568640,"HeapInuse":101883904,"HeapReleased":44720128,"HeapObjects":299114,"StackInuse":1736704,"StackSys":1736704,"MSpanInuse":1119520,"MSpanSys":1958400,"MCacheInuse":16912,"MCacheSys":31408,"BuckHashSys":1583603,"GCSys":5251008,"OtherSys":2438853,"NextGC":142217095,"LastGC":1754180652189080000,"PauseTotalNs":1266251,"NumGC":18,"NumForcedGC":1,"GCCPUFraction":0.0002128524091018015,"EnableGC":true,"DebugGC":false}} ``` ### `debug_setBlockProfileRate` Sets the rate of goroutine block profile data collection. A non-zero value enables block profiling. **Private**: Requires authentication. #### Parameters * Profile rate (integer) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_setBlockProfileRate","params":[1],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_writeBlockProfile` Writes a goroutine blocking profile to the specified file. **Private**: Requires authentication. #### Parameters * File path (string) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_writeBlockProfile","params":["block.prof"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_writeMemProfile` Writes an allocation profile to the specified file. **Private**: Requires authentication. #### Parameters * File path (string) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_writeMemProfile","params":["mem.prof"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_writeMutexProfile` Writes a mutex contention profile to the specified file. **Private**: Requires authentication. #### Parameters * File path (string) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_writeMutexProfile","params":["mutex.prof"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_blockProfile` Turns on block profiling for the given duration and writes the profile data to disk. **Private**: Requires authentication. #### Parameters * File path (string) - Output file for the profile * Duration in seconds (number) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_blockProfile","params":["block.prof", 30],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_cpuProfile` Turns on CPU profiling for the given duration and writes the profile data to disk. **Private**: Requires authentication. #### Parameters * File path (string) - Output file for the profile * Duration in seconds (number) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_cpuProfile","params":["cpu.prof", 30],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_gcStats` Returns garbage collection statistics. **Private**: Requires authentication. **Cosmos-specific**: This method is unique to Cosmos EVM. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_gcStats","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":{"NumGC":10,"PauseTotal":1000000,"Pause":[100000,200000],"PauseEnd":[1234567890,1234567891],"...":"..."}} ``` ### `debug_goTrace` Turns on Go runtime tracing for the given duration and writes the trace data to disk. **Private**: Requires authentication. #### Parameters * File path (string) - Output file for the trace * Duration in seconds (number) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_goTrace","params":["trace.out", 5],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_stacks` Returns a printed representation of the stacks of all goroutines. **Private**: Requires authentication. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_stacks","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"goroutine 1 [running]:\n..."} ``` ### `debug_startCPUProfile` Starts indefinite CPU profiling, writing to the given file. **Private**: Requires authentication. #### Parameters * File path (string) - Output file for the profile ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_startCPUProfile","params":["cpu.prof"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_stopCPUProfile` Stops an ongoing CPU profile. **Private**: Requires authentication. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_stopCPUProfile","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_mutexProfile` Turns on mutex profiling for the given duration and writes the profile data to disk. **Private**: Requires authentication. #### Parameters * File path (string) - Output file for the profile * Duration in seconds (number) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_mutexProfile","params":["mutex.prof", 10],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":null} ``` ### `debug_setMutexProfileFraction` Sets the rate of mutex profiling. **Private**: Requires authentication. **Cosmos-specific**: This method is unique to Cosmos EVM. #### Parameters * Rate (number) - 0 to disable, 1 for full profiling ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_setMutexProfileFraction","params":[1],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":0} ``` ### `debug_getHeaderRlp` Returns the RLP encoding of the header of the block. **Private**: Requires authentication. **Parameter Type**: This method requires the block number as a `uint64` (number type), not a hex string. Passing a string will return an error: "invalid argument 0: json: cannot unmarshal string into Go value of type uint64". #### Parameters * Block number (uint64 number, not hex string) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note: block number as integer, not hex string curl -X POST --data '{"jsonrpc":"2.0","method":"debug_getHeaderRlp","params":[100],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0xf90..."} ``` ### `debug_getBlockRlp` Returns the RLP encoding of the block. **Private**: Requires authentication. **Parameter Type**: This method requires the block number as a `uint64` (number type), not a hex string. Passing a string will return an error: "invalid argument 0: json: cannot unmarshal string into Go value of type uint64". #### Parameters * Block number (uint64 number, not hex string) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note: block number as integer, not hex string curl -X POST --data '{"jsonrpc":"2.0","method":"debug_getBlockRlp","params":[100],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"0xf90..."} ``` ### `debug_printBlock` Returns a formatted string of the block. **Private**: Requires authentication. **Parameter Type**: This method requires the block number as a `uint64` (number type), not a hex string. Passing a string will return an error: "invalid argument 0: json: cannot unmarshal string into Go value of type uint64". #### Parameters * Block number (uint64 number, not hex string) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request - Note: block number as integer, not hex string curl -X POST --data '{"jsonrpc":"2.0","method":"debug_printBlock","params":[100],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":"Block #100\nParent: 0x...\nStateRoot: 0x...\n..."} ``` ### `debug_intermediateRoots` Returns the intermediate state roots for a transaction. **Private**: Requires authentication. #### Parameters * Transaction hash (hash) * Trace config (object, optional) ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Request curl -X POST --data '{"jsonrpc":"2.0","method":"debug_intermediateRoots","params":["0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", {}],"id":1}' -H "Content-Type: application/json" http://localhost:8545 // Result {"jsonrpc":"2.0","id":1,"result":["0x...","0x..."]} ``` ## TxPool Methods ### `txpool_content` Returns a list of the exact details of all the transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. #### Parameters (0) #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{"jsonrpc":"2.0","method":"txpool_content","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "txpool_content", "params": []}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txpool.content(); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"jsonrpc":"2.0","id":1,"result":{"pending":{},"queued":{}}} ``` ### `txpool_contentFrom` Returns transactions from a specific address that are currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. #### Parameters (1) 1. `address` - The address to filter transactions from (20 bytes) #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{"jsonrpc":"2.0","method":"txpool_contentFrom","params":["0x1234567890abcdef1234567890abcdef12345678"],"id":1}' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "txpool_contentFrom", "params": ["0x1234567890abcdef1234567890abcdef12345678"]}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txpool.contentFrom("0x1234567890abcdef1234567890abcdef12345678"); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"jsonrpc":"2.0","id":1,"result":{"pending":{},"queued":{}}} ``` ### `txpool_inspect` Returns a list on text format to summarize all the transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. This is a method specifically tailored to developers to quickly see the transactions in the pool and find any potential issues. #### Parameters (0) #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{"jsonrpc":"2.0","method":"txpool_inspect","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "txpool_inspect", "params": []}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txpool.inspect(); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"jsonrpc":"2.0","id":1,"result":{"pending":{},"queued":{}}} ``` ### `txpool_status` Returns the number of transactions currently pending for inclusion in the next block(s), as well as the ones that are being scheduled for future execution only. #### Parameters (0) #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{"jsonrpc":"2.0","method":"txpool_status","params":[],"id":1}' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{"jsonrpc": "2.0", "id": 1, "method": "txpool_status", "params": []}' ``` Javascript Console ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txpool.status(); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"jsonrpc":"2.0","id":1,"result":{"pending":"0x0","queued":"0x0"}} ``` ### `eth_createAccessList` This method is fully implemented and supports EIP-2930 access list transactions. Creates an access list for a given transaction. This is useful for optimizing gas costs when calling contracts, as it allows the transaction to specify which storage slots will be accessed, potentially reducing gas costs for those accesses. The access list contains addresses and storage keys that the transaction plans to access. When included in a transaction, these pre-declared accesses cost less gas. #### Parameters 1. **Transaction Object** - The transaction call object * `from`: `DATA`, 20 Bytes - (optional) The address the transaction is sent from * `to`: `DATA`, 20 Bytes - The address the transaction is directed to * `gas`: `QUANTITY` - (optional) Integer of the gas provided for the transaction execution * `gasPrice`: `QUANTITY` - (optional) Integer of the gasPrice used for each paid gas * `maxFeePerGas`: `QUANTITY` - (optional) Maximum fee per gas the sender is willing to pay * `maxPriorityFeePerGas`: `QUANTITY` - (optional) Maximum priority fee per gas the sender is willing to pay * `value`: `QUANTITY` - (optional) Integer of the value sent with this transaction * `data`: `DATA` - (optional) Hash of the method signature and encoded parameters * `accessList`: `ARRAY` - (optional) List of addresses and storage keys the transaction plans to access 2. **Block Parameter** - `QUANTITY|TAG` - integer block number, or the string "latest", "earliest" or "pending" #### Client Examples Shell HTTP ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST --data '{ "jsonrpc":"2.0", "method":"eth_createAccessList", "params":[{ "from": "0x8D97689C9818892B700e27F316cc3E41e17fBeb9", "to": "0xd3CdA913deB6f67967B99D67aCDFa1712C293601", "data": "0x70a08231000000000000000000000000d3CdA913deB6f67967B99D67aCDFa1712C293601" }, "latest"], "id":1 }' -H "Content-Type: application/json" http://localhost:8545 ``` Websocket ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} wscat -c ws://localhost:8546 -x '{ "jsonrpc": "2.0", "id": 1, "method": "eth_createAccessList", "params": [{ "from": "0x8D97689C9818892B700e27F316cc3E41e17fBeb9", "to": "0xd3CdA913deB6f67967B99D67aCDFa1712C293601", "data": "0x70a08231000000000000000000000000d3CdA913deB6f67967B99D67aCDFa1712C293601" }, "latest"] }' ``` Javascript (web3.js) ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} web3.eth.createAccessList({ from: "0x8D97689C9818892B700e27F316cc3E41e17fBeb9", to: "0xd3CdA913deB6f67967B99D67aCDFa1712C293601", data: "0x70a08231000000000000000000000000d3CdA913deB6f67967B99D67aCDFa1712C293601" }, "latest"); ``` #### Result ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "jsonrpc": "2.0", "id": 1, "result": { "accessList": [ { "address": "0xd3CdA913deB6f67967B99D67aCDFa1712C293601", "storageKeys": [ "0x0000000000000000000000000000000000000000000000000000000000000001" ] } ], "gasUsed": "0x5208" } } ``` #### Return Values * `accessList`: `ARRAY` - List of addresses and storage keys used by the transaction * `address`: `DATA`, 20 Bytes - Address accessed by the transaction * `storageKeys`: `ARRAY` - Array of storage keys accessed at this address * `gasUsed`: `QUANTITY` - Estimated gas consumed by the transaction with the access list # JSON-RPC Explorer Source: https://docs.cosmos.network/evm/latest/api-reference/ethereum-json-rpc/rpc-explorer Complete reference for Ethereum JSON-RPC methods supported on Cosmos EVM # Changelog Source: https://docs.cosmos.network/evm/latest/changelog/release-notes Release history and changelog for Cosmos EVM This page tracks releases and changes for v0.7.0. For the full release history, see the [CHANGELOG](https://github.com/cosmos/evm/blob/main/CHANGELOG.md) on GitHub. ## API-BREAKING * [#1146](https://github.com/cosmos/evm/pull/1146) Remove `EndBlocker` based mempool updates, use `PrepareCheckStater` instead. ## IMPROVEMENTS * [#758](https://github.com/cosmos/evm/pull/758) Cleanup precompiles abi.json. * [#810](https://github.com/cosmos/evm/pull/810) Fix integration test suite to resolve lock contention problem from external app injection * [#811](https://github.com/cosmos/evm/pull/811) Use sdk's DefaultBondDenom for default evm denom in genesis. * [#823](https://github.com/cosmos/evm/pull/823) Remove authz dependency from test suite and EvmApp interface * [#829](https://github.com/cosmos/evm/pull/829) Seperate test app interface * [#968](https://github.com/cosmos/evm/pull/968) Use normal gas config in ibc transfer to prevent potential DoS attack * [#1029](https://github.com/cosmos/evm/pull/1029) Mark EvmCoinInfo.Decimals field as deprecated * [#1079](https://github.com/cosmos/evm/pull/1079) Access Control List is now case-insensitive * [#1103](https://github.com/cosmos/evm/pull/1103) Align normal gas metering in ibc erc20 callback. * [#1108](https://github.com/cosmos/evm/pull/1108) Add json-rpc http server request body limit. * [#1118](https://github.com/cosmos/evm/pull/1118) Cache chain denom for evm mempool * [#1008](https://github.com/cosmos/evm/pull/1008) Stop enforcing JSON-RPC global filter cap and allow reclaim filters via configurable idle timeout. * [#1130](https://github.com/cosmos/evm/pull/1130) Use `sdk.ValidateAuthority` in `x/vm`, `x/erc20`, and `x/feemarket` `MsgServer` handlers so authority can optionally be centralized via the consensus `AuthorityParams` introduced in cosmos-sdk v0.54. * [#1164](https://github.com/cosmos/evm/pull/1164) Remove zero gas config from `ics20.transferWithStateDB` so inner KV ops in ICS20 transfer execution are metered, mirroring [#1103](https://github.com/cosmos/evm/pull/1103). ## FEATURES * New application evm+cosmos mempool ("Krakatoa") * [#589](https://github.com/cosmos/evm/pull/589) Remove parallelization blockers via migration from transient to object store, refactoring of gas, indexing, and bloom utilities. * [#768](https://github.com/cosmos/evm/pull/768) Added ICS-02 Client Router precompile * [#815](https://github.com/cosmos/evm/pull/815) Support for multi gRPC query clients serve with old binary. * [#1082](https://github.com/cosmos/evm/pull/1082) Enable incarnation cache for verify result. * [#1096](https://github.com/cosmos/evm/pull/1096) Allow eth\_call overrides work with static precompiles. ## BUG FIXES * [#1047](https://github.com/cosmos/evm/pull/1047) Resolve EthTxIndex -1 sentinel before uint cast in ReceiptsFromCometBlock, preventing transactionIndex overflow to MaxUint64. * [#965](https://github.com/cosmos/evm/pull/965) Fix gas double charging on EVM calls in IBCOnTimeoutPacketCallback. * [#869](https://github.com/cosmos/evm/pull/869) Fix erc20 IBC callbacks to check for native token transfer before parsing recipient. * [#860](https://github.com/cosmos/evm/pull/860) Fix EIP-712 signature verification to use configured EVM chain ID instead of parsing cosmos chain ID string and replace legacytx.StdSignBytes with the aminojson sign mode handler. * [#794](https://github.com/cosmos/evm/pull/794) Fix mempool.max-txs flag not using desired default of 0 * [#748](https://github.com/cosmos/evm/pull/748) Fix DynamicFeeChecker in Cosmos ante handler to respect NoBaseFee feemarkets' parameter. * [#690](https://github.com/cosmos/evm/pull/690) Fix Ledger hardware wallet support for coin type 60. * [#769](https://github.com/cosmos/evm/pull/769) Fix erc20 ibc middleware to not to validate sender address format. * [#756](https://github.com/cosmos/evm/pull/756) Fix error message typo in NewMsgCancelProposal. * [#772](https://github.com/cosmos/evm/pull/772) Avoid panic on close if evm mempool not used. * [#774](https://github.com/cosmos/evm/pull/774) Emit proper allowance amount in erc20 event. * [#790](https://github.com/cosmos/evm/pull/790) fix panic in historical query due to missing EvmCoinInfo. * [#800](https://github.com/cosmos/evm/pull/800) Fix denom exponent validation in virtual fee deduct in vm module. * [#1132](https://github.com/cosmos/evm/pull/1132) Patch block-cumulative `log.Index` and eth-only `log.TxIndex` post-execution to fix indexing under BlockSTM. * [#817](https://github.com/cosmos/evm/pull/817) Align GetCoinbaseAddress to handle empty proposer address in contexts like CheckTx where proposer doesn't exist. * [#814](https://github.com/cosmos/evm/pull/814) Fix duplicated events in post tx processor. * [#816](https://github.com/cosmos/evm/pull/816) Avoid nil pointer when RPC requests execute before evmCoinInfo initialization in PreBlock with defaultEvmCoinInfo fallback. * [#828](https://github.com/cosmos/evm/pull/828) Validate decimals before conversion to prevent panic when coininfo is missing in historical queries. * [#905](https://github.com/cosmos/evm/pull/905) Fix EIP-6780 selfdestruct to properly delete contracts at pre-funded addresses by persisting code and account before DeleteAccount's IsContract check. * [#920](https://github.com/cosmos/evm/pull/920) Fix GetCoinbaseAddress to correctly convert validator operator address from Bech32 format to Ethereum address for block.coinbase opcode. * [#705](https://github.com/cosmos/evm/pull/705) Fix dynamic precompiles being disabled when EVM state overrides are used in eth\_call. * [#967](https://github.com/cosmos/evm/pull/967) Fix return value of erc20 ibcv2 middleware to properly reflect application success and middleware failure. * [#992](https://github.com/cosmos/evm/pull/992) Respect the provided `gasCap` in `CallEVMWithData` instead of always used the default cap. * [#993](https://github.com/cosmos/evm/pull/993) Enforce `src_callback` contract address to match the packet sender for IBC acknowledgement and timeout callbacks to prevent arbitrary contract execution. * [#1061](https://github.com/cosmos/evm/pull/1061) Block nested ICS20 forwarding in source callbacks. * [#1050](https://github.com/cosmos/evm/pull/1050) Align precompile gas calculation with expected EVM gas semantics. * [#1107](https://github.com/cosmos/evm/pull/1107) Skip StateDB commit error transactions during receipt conversion to prevent `invalid message index` errors in block RPCs. # Accounts Source: https://docs.cosmos.network/evm/latest/documentation/concepts/accounts Cosmos EVM accounts are implemented to be compatible with Ethereum type addresses Accounts in Cosmos EVM are designed for compatibility with both Ethereum and Cosmos SDK tooling. For developers interfacing with account types (e.g., during wallet integration), understanding the dual representation is essential. **References**: [Cosmos SDK Accounts](/sdk/latest/learn/concepts/accounts), [Ethereum Accounts](https://ethereum.org/en/whitepaper/#ethereum-accounts) ## Account Structure In the Cosmos SDK, an account designates a pair of public key (PubKey) and private key (PrivKey). The derivation path defines what the private key, public key, and address would be. Cosmos blockchains support creating accounts with mnemonic phrases using [hierarchical deterministic key generation](https://github.com/confio/cosmos-hd-key-derivation-spec) (HD keys). HD keys generate addresses by combining the mnemonic phrase with a [derivation path](https://learnmeabitcoin.com/technical/derivation-paths). ## EVM Accounts Cosmos EVM defines a custom `Account` type implementing an HD wallet compatible with Ethereum addresses using: * Ethereum's ECDSA secp256k1 curve (`eth_secp256k1`) * [EIP84](https://github.com/ethereum/EIPs/issues/84) for full [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) paths * Root HD path: `m/44'/60'/0'/0` (Coin type `60` for Ethereum compatibility) The custom EthAccount satisfies the Cosmos SDK `AccountI` interface with additional Ethereum-specific fields (see [x/vm/types interfaces](https://github.com/cosmos/evm/blob/main/x/vm/types/interfaces.go) for the account keeper implementation): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // EthAccountI represents the interface of an EVM compatible account type EthAccountI interface { authtypes.AccountI // EthAddress returns the ethereum Address representation of the AccAddress EthAddress() common.Address // CodeHash is the keccak256 hash of the contract code (if any) GetCodeHash() common.Hash // SetCodeHash sets the code hash to the account fields SetCodeHash(code common.Hash) error // Type returns the type of Ethereum Account (EOA or Contract) Type() int8 } ``` **EIP-7702 Support**: Cosmos EVM supports [EIP-7702 code delegation](/evm/latest/documentation/evm-compatibility), allowing EOAs to temporarily delegate code execution to smart contracts through the `SetCodeHash` functionality. ## Address Formats ### Bech32 and Hex Representations `EthAccount` can be represented in both [Bech32](https://en.bitcoin.it/wiki/Bech32) (`cosmos1...`) and hex (`0x...`) formats for compatibility with both ecosystems. **Bech32** (default for Cosmos SDK): ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cosmos1z3t55m0l9h0eupuz3dp5t5cypyv674jj7mz2jw ``` **EIP-55 Hex** (Ethereum compatibility): ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} 0x91defC7fE5603DFA8CC9B655cF5772459BF10c6f ``` **Public Key**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "@type": "/ethermint.crypto.v1.ethsecp256k1.PubKey", "key": "AsV5oddeB+hkByIJo/4lZiVUgXTzNfBPKC73cZ4K1YD2" } ``` ### Address Types [BIP-0173](https://github.com/satoshilabs/slips/blob/master/slip-0173.md) defines Bech32 format with human-readable prefixes: **User Accounts** (`eth_secp256k1`): * Address: `cosmos` prefix, 20 bytes * Pubkey: `cosmospub` prefix, 33 bytes (compressed) **Validator Operators** (`eth_secp256k1`): * Address: `cosmosvaloper` prefix, 20 bytes * Pubkey: `cosmosvaloperpub` prefix, 33 bytes (compressed) **Consensus Nodes** (`ed25519`): * Address: `cosmosvalcons` prefix, 20 bytes * Pubkey: `cosmosvalconspub` prefix, 32 bytes A chain can allow additional consensus key types through `consensus_params.validator.pub_key_types`: * `secp256k1eth`: Ethereum-style validator consensus addresses, for chains that want consensus identities derived the Ethereum way. * `ml_dsa_65`: post-quantum consensus keys. A Cosmos EVM validator can run any of these consensus signature types, including post-quantum ML-DSA. However, ML-DSA keys are not currently supported for EVM user accounts. As a fully EVM-compatible ledger, Cosmos EVM follows Ethereum's account conventions and will adopt user-side post-quantum support as it lands upstream. See [Enable ML-DSA keys](/sdk/latest/keys/enable-ml-dsa-keys) to allow a type and [Post-quantum keys](/sdk/latest/keys/post-quantum-keys) for the tradeoffs. Before enabling a new consensus key type on a chain with live IBC connections, confirm every counterparty runs a stack that can verify it. ### Address Conversion Convert between formats using the CLI: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Bech32 to Hex $ evmd debug addr cosmos1z3t55m0l9h0eupuz3dp5t5cypyv674jj7mz2jw Address (hex): 14574A6DFF2DDF9E07828B4345D3040919AF5652 Bech32 Acc: cosmos1z3t55m0l9h0eupuz3dp5t5cypyv674jj7mz2jw Bech32 Val: cosmosvaloper1z3t55m0l9h0eupuz3dp5t5cypyv674jjn4d6nn ``` ## Querying Accounts Query accounts via CLI, REST, or JSON-RPC: **CLI**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd q auth account $(evmd keys show dev0 -a) -o json ``` **REST API**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X GET "http://localhost:10337/cosmos/auth/v1beta1/accounts/{address}" ``` **JSON-RPC**: [`eth_accounts`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth-accounts), [`personal_listAccounts`](/evm/latest/api-reference/ethereum-json-rpc/methods#personal-listAccounts) ## Related Documentation * [x/vm Module](/evm/latest/documentation/cosmos-sdk/modules/vm) - Ethereum account implementation details # Chain ID Source: https://docs.cosmos.network/evm/latest/documentation/concepts/chain-id Chain IDs are unique identifiers that distinguish blockchain networks from each other. Cosmos EVM uses a dual Chain ID system to maintain compatibility with both Cosmos SDK and Ethereum ecosystems. You can look up existing EVM Chain IDs by referring to [chainlist.org](https://chainlist.org/) to ensure your chosen ID is not already in use. ## Dual Chain ID System Cosmos EVM requires **two completely independent chain IDs** to maintain full compatibility with both the Cosmos SDK and Ethereum ecosystems. ### 1. Cosmos Chain ID (String) The **Cosmos Chain ID** is a string identifier used by: * CometBFT consensus engine * IBC (Inter-Blockchain Communication) protocol * Native Cosmos SDK transactions * Chain upgrades and governance **Format**: Any string (recommended format: `"mychain-1"`, `"testnet-2"`) **Example**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // In genesis.json { "chain_id": "mychain-1" } ``` ### 2. EVM Chain ID (Integer) The **EVM Chain ID** is an integer used by: * Ethereum transactions (EIP-155 replay protection) * MetaMask and other Ethereum wallets * Smart contract deployments * EVM tooling (Hardhat, Foundry, etc.) **Format**: Any unique positive integer (check [chainlist.org](https://chainlist.org)) **Example**: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # In app.toml [evm] chain-id = 262144 ``` ## Configuration Both chain IDs must be configured when setting up your chain: ### In Your Application Code ```go "Chain ID Configuration" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/app.go const ( CosmosChainID = "cosmosevm-1" // String for Cosmos/IBC EVMChainID = 9000 // Integer for EVM/Ethereum ) ``` ### In Genesis Configuration The Cosmos Chain ID is set in `genesis.json`: ```json "Genesis Chain ID Configuration" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "chain_id": "cosmosevm-1", // ... other genesis parameters } ``` The EVM Chain ID is configured in the EVM module parameters: ```go "EVM Chain ID Initialization" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // During chain initialization evmtypes.DefaultChainConfig(9000) // Your EVM Chain ID ``` ## Important Considerations ### EVM Chain ID Guidelines When selecting your EVM Chain ID: 1. **Check availability**: Verify your chosen ID is not already in use on [chainlist.org](https://chainlist.org/) 2. **Avoid conflicts**: Don't use well-known chain IDs (1 for Ethereum mainnet, 137 for Polygon, etc.) 3. **Choose any available integer**: There are no required ranges or formats - simply pick any integer not in use ### Chain Upgrades Unlike traditional Cosmos chains that change their chain ID during upgrades (e.g., `cosmoshub-4` to `cosmoshub-5`), the EVM Chain ID must remain **constant** across upgrades to maintain compatibility with deployed smart contracts and existing wallets. Only the Cosmos Chain ID may change during chain upgrades if needed for consensus-breaking changes. The EVM Chain ID should never change once set. ## Troubleshooting ### Common Issues 1. **"Chain ID mismatch" errors** * **Cause**: Using Cosmos Chain ID where EVM Chain ID is expected (or vice versa) * **Solution**: Ensure you're using the correct type of chain ID for each context 2. **MetaMask connection failures** * **Cause**: Incorrect EVM Chain ID in wallet configuration * **Solution**: Use the integer EVM Chain ID, not the string Cosmos Chain ID 3. **IBC transfer failures** * **Cause**: Using EVM Chain ID for IBC operations * **Solution**: IBC always uses the Cosmos Chain ID (string format) 4. **Smart contract deployment issues** * **Cause**: EIP-155 replay protection using wrong chain ID * **Solution**: Ensure your EVM Chain ID matches what's configured in the chain ### Verification Commands To verify your chain IDs are correctly configured: ```bash "Chain ID Verification Commands" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Check Cosmos Chain ID curl -s http://localhost:26657/status | jq '.result.node_info.network' # Check EVM Chain ID curl -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \ http://localhost:8545 | jq '.result' ``` # EIP-1559 Fee Market Source: https://docs.cosmos.network/evm/latest/documentation/concepts/eip-1559-feemarket Understanding dynamic fee pricing and the EIP-1559 mechanism in Cosmos EVM chains ## Overview [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) revolutionizes transaction fee mechanics by replacing the single gas price auction model with a dual-fee structure. This creates more predictable fees and improved network efficiency for EVM-compatible chains. ## Core Concepts ### Dual-Fee Structure Before EIP-1559, fees were simple: ``` fee = gasPrice * gasLimit ``` With EIP-1559, fees have two components: ``` fee = (baseFee + priorityTip) * gasLimit ``` where: * `baseFee`: Protocol-determined minimum price per gas unit * `priorityTip`: Optional fee for transaction prioritization The Cosmos SDK uses different terminology than Ethereum. What Ethereum calls `gasLimit` is `gasWanted` in Cosmos. You'll encounter both terms in the Cosmos EVM as it bridges Ethereum and Cosmos SDK concepts. ### Base Fee Mechanism The base fee is the minimum price per unit of gas required for transaction inclusion. It automatically adjusts each block based on network utilization. #### Adjustment Formula ``` NewBaseFee = BaseFee × (1 + (|GasUsed - GasTarget| / GasTarget / Denominator)) ``` The adjustment direction depends on block utilization: * **Increases** when `gasUsed > gasTarget` (congested) * **Decreases** when `gasUsed < gasTarget` (underutilized) * **Remains stable** when `gasUsed == gasTarget` (optimal) #### Example Calculation With standard Ethereum parameters: * Current base fee: 1000 units * Block capacity: 10M gas * Target (50%): 5M gas * Actual usage: 8M gas (80% full) * Denominator: 8 ``` Adjustment = (8M - 5M) / 5M / 8 = 0.075 (7.5% increase) New base fee = 1000 × 1.075 = 1075 units ``` ### Target Utilization The target utilization determines the optimal block fullness: ``` Target Gas = MaxBlockGas / ElasticityMultiplier Target Utilization % = 100 / ElasticityMultiplier ``` Common configurations: * `ElasticityMultiplier = 2`: 50% target (Ethereum standard) * `ElasticityMultiplier = 4`: 25% target (aggressive pricing) * `ElasticityMultiplier = 1`: 100% target (maximum throughput) ### Priority Tips and MEV The `max_priority_fee_per_gas` (tip) serves as an incentive for validators to include transactions faster. However, in Cosmos SDK implementations: * Transaction prioritization is limited compared to Ethereum * Tips may have minimal effect on inclusion order * MEV opportunities are generally reduced ### Effective Gas Price For EIP-1559 transactions, users specify two limits: * `maxFeePerGas`: Maximum total they're willing to pay * `maxPriorityFeePerGas`: Maximum tip for validators The effective price becomes: ``` effectiveGasPrice = min(baseFee + maxPriorityFeePerGas, maxFeePerGas) ``` This ensures users never pay more than their specified maximum while allowing for priority fees when network conditions permit. ## Fee Calculation Examples ### Low Activity Period * Base fee: 100 gwei * User sets: maxFeePerGas = 200 gwei, maxPriorityFeePerGas = 2 gwei * Effective price: 100 + 2 = 102 gwei * User pays: 102 gwei (well below their 200 gwei max) ### High Congestion * Base fee: 180 gwei * User sets: maxFeePerGas = 200 gwei, maxPriorityFeePerGas = 30 gwei * Effective price: min(180 + 30, 200) = 200 gwei * User pays: 200 gwei (capped at their maximum) ## Minimum Gas Prices The fee market enforces multiple price floors: ### Local vs. Global Minimums 1. **Local minimum**: Set by individual validators in node configuration 2. **Global minimum**: Set as the `MinGasPrice` parameter via governance 3. **Base fee**: Current protocol-calculated minimum The effective minimum is always the highest of these three values. If the base fee falls below the global `MinGasPrice`, it's automatically raised to `MinGasPrice`. This prevents the base fee from dropping below the spam prevention threshold. ## Benefits of EIP-1559 ### For Users * **Predictable fees**: Base fee is known before submitting * **Better UX**: Wallets can reliably estimate costs * **Fair pricing**: All users in a block pay similar rates * **Protection**: MaxFeePerGas prevents overpayment ### For Networks * **Automatic adjustment**: No manual intervention needed * **Spam resistance**: Dynamic fees deter attacks * **Optimal utilization**: Targets efficient block usage * **Economic stability**: Predictable fee revenue ### For Developers * **Simplified estimation**: Base fee is protocol-provided * **Better tools**: Standard APIs for fee data * **Consistent behavior**: Across EVM-compatible chains ## Common Misconceptions ### "Tips Always Speed Up Transactions" In Cosmos SDK chains, transaction ordering is often FIFO within the mempool, making tips less effective than on Ethereum mainnet. ### "Base Fee Always Burns" While Ethereum burns the base fee, Cosmos EVM chains may distribute it differently based on their economic model. ### "EIP-1559 Eliminates Fee Spikes" It smooths volatility but can't prevent spikes during extreme congestion. The adjustment rate is intentionally limited. ## Further Reading * [Original EIP-1559 Proposal](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) * [Ethereum Gas Tracker](https://etherscan.io/gastracker) * [cosmos/evm Fee Market Module](https://github.com/cosmos/evm/tree/v0.4.1/x/feemarket) * [Understanding Gas and Fees](/evm/latest/documentation/concepts/gas-and-fees) # Encoding Source: https://docs.cosmos.network/evm/latest/documentation/concepts/encoding Encoding refers to the process of converting data from one format to another to make it more secure and efficient. In the context of blockchain, encoding is used to ensure that data is stored and transmitted in a way that is secure and easily accessible. The Recursive Length Prefix (RLP) is a serialization format used extensively in Ethereum's execution clients. Its purpose is to encode arbitrarily nested arrays of binary data, and it is the main encoding method used to serialize objects in Ethereum. RLP only encodes structure and leaves encoding specific atomic data types, such as strings, integers, and floats, to higher-order protocols. In Ethereum, integers must be represented in big-endian binary form with no leading zeroes, making the integer value zero equivalent to the empty byte array. The RLP encoding function takes in an item, which is defined as a single byte whose value is in the \[0x00, 0x7f] range or a string of 0-55 bytes long. If the string is more than 55 bytes long, the RLP encoding consists of a single byte with value 0xb7 (dec. 183) plus the length in bytes of the length of the string in binary form, followed by the length of the string, followed by the string. RLP is used for hash verification, where a transaction is signed by signing the RLP hash of the transaction data, and blocks are identified by the RLP hash of their header. RLP is also used for encoding data over the wire and for some cases where there should be support for efficient encoding of the merkle tree data structure. The Ethereum execution layer uses RLP as the primary encoding method to serialize objects, but the newer Simple Serialize (SSZ) replaces RLP as the encoding for the new consensus layer in Ethereum 2.0. The Cosmos Stargate release introduces protobuf as the main encoding format for both client and state serialization. All the EVM module types that are used for state and clients, such as transaction messages, genesis, query services, etc., will be implemented as protocol buffer messages. The Cosmos SDK also supports the legacy Amino encoding. Protocol Buffers (protobuf) is a language-agnostic binary serialization format that is smaller and faster than JSON. It is used to serialize structured data, such as messages, and is designed to be highly efficient and extensible. The encoding format is defined in a language-agnostic language called Protocol Buffers Language (proto3), and the encoded messages can be used to generate code for a variety of programming languages. The main advantage of protobuf is its efficiency, which results in smaller message sizes and faster serialization and deserialization times. The RLP decoding process is as follows: according to the first byte (i.e., prefix) of input data and decoding the data type, the length of the actual data and offset; according to the type and offset of data, decode the data correspondingly. ## Prerequisite Readings Learn about protobuf and Amino encoding in Cosmos SDK Understand Recursive Length Prefix encoding ## Encoding Formats **Primary encoding for Cosmos EVM** The Cosmos [Stargate](https://stargate.cosmos.network/) release introduces [protobuf](https://developers.google.com/protocol-buffers) as the main encoding format for both client and state serialization. All EVM module types (transaction messages, genesis, query services) are implemented as protocol buffer messages. **Advantages:** * Language-agnostic binary serialization * Smaller message sizes than JSON * Faster serialization/deserialization * Strongly typed with schema validation **Ethereum compatibility encoding** Recursive Length Prefix ([RLP](https://eth.wiki/en/fundamentals/rlp)) is an encoding/decoding algorithm that serializes messages for Ethereum compatibility. Cosmos EVM uses RLP specifically for Ethereum transaction encoding to ensure JSON-RPC compatibility. **Usage in Cosmos EVM:** * Encoding `MsgEthereumTx` for JSON-RPC * Transaction signing and verification * Block hash computation * Merkle tree data structures **Backwards compatibility only** The Cosmos SDK supports legacy Amino encoding for backwards compatibility with older versions. Cosmos EVM does not support Amino in the EVM module. It's only available for other Cosmos SDK modules that enable it. **Limited use cases:** * Client encoding for legacy wallets * Signing with Ledger devices * Not used for EVM transactions ### Transaction Encoding Implementation The `x/vm` module handles `MsgEthereumTx` encoding by converting to go-ethereum's `Transaction` format and using RLP: ```go "Transaction Encoding Implementation" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // TxEncoder overwrites sdk.TxEncoder to support MsgEthereumTx func (g txConfig) TxEncoder() sdk.TxEncoder { return func(tx sdk.Tx) ([]byte, error) { msg, ok := tx.(*evmtypes.MsgEthereumTx) if ok { return msg.AsTransaction().MarshalBinary() } return g.TxConfig.TxEncoder()(tx) } } // TxDecoder overwrites sdk.TxDecoder to support MsgEthereumTx func (g txConfig) TxDecoder() sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, error) { tx := ðtypes.Transaction{} err := tx.UnmarshalBinary(txBytes) if err == nil { msg := &evmtypes.MsgEthereumTx{} msg.FromEthereumTx(tx) return msg, nil } return g.TxConfig.TxDecoder()(txBytes) } } ``` # Gas and Fees Source: https://docs.cosmos.network/evm/latest/documentation/concepts/gas-and-fees Fee calculation and gas metering in Cosmos EVM Cosmos EVM implements Ethereum-type fee calculation compatible with the Cosmos SDK. This overview explains gas calculation, fee provision, and how the fee market (EIP-1559) prioritizes transactions. **References**: [Cosmos SDK Gas](/sdk/latest/learn/concepts/context-gas-events), [Ethereum Gas](https://ethereum.org/en/developers/docs/gas/) ## Gas and Fees Fundamentals **Gas** is a unit measuring computational intensity of a transaction - the work required to execute it. Complex, multi-step transactions require more gas than simple transfers. **Fees** are the cost paid for gas: ``` Total Fees = Gas × Gas Price ``` ## Fee Handling ### Cosmos SDK Fees Users specify two fields: 1. `GasLimit` - upper bound on execution gas (`GasWanted`) 2. One of `Fees` or `GasPrice` to specify or calculate transaction fees The node consumes fees entirely, then executes the transaction. If `GasLimit` is insufficient, the transaction fails and rolls back without refunding fees. Validators specify their `min-gas-prices` for transaction selection. Transactions with insufficient fees encounter delays or fail. ### Ethereum Fees (EIP-1559) With EIP-1559 (London Hard Fork), `GasPrice` split into two components: ``` Gas Price = Base Fee + Priority Fee ``` * **BaseFee**: Calculated automatically based on block size (burned on Ethereum, distributed on Cosmos EVM) * **PriorityFee**: Tip to proposer for transaction inclusion Users specify: * `max_fee_per_gas`: Total `GasPrice` maximum * `max_priority_fee_per_gas`: Maximum `PriorityFee` * `gas_limit`: Gas consumption limit Surplus gas not required for execution is refunded. **Reference**: [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) ## Cosmos EVM Implementation Fundamentally, Cosmos EVM is a Cosmos SDK chain enabling EVM compatibility via a module. All EVM transactions are encoded as Cosmos SDK transactions and update Cosmos SDK-managed state. ### Fee Market Module Supports EIP-1559 gas calculation on the EVM layer: * Tracks gas supplied per block to calculate base fee for future transactions * For EVM transactions, bypasses local `min-gas-prices` and applies EIP-1559 fee logic * Gas price must exceed both global `min-gas-price` and block's `BaseFee` **Key differences from Ethereum**: * `BaseFee` is distributed to validators/delegators (not burned) * `BaseFee` is lower-bounded by global `min-gas-price` (currently zero, adjustable via governance) ### EVM Gas Refunds Cosmos EVM refunds a fraction (at least 50% by default) of unused gas for EVM transactions to approximate Ethereum behavior. This is adjustable. ## Block Processing Timeline 1. **EndBlock**: FeeMarket module tracks total `TransientGasWanted` from block transactions for next block's `BaseFee` 2. **Transaction Reception**: Nodes receive and gossip transactions, prioritized by fee price 3. **BeginBlock**: * FeeMarket calculates `BaseFee` ([code](https://github.com/cosmos/evm/blob/main/x/feemarket/keeper/abci.go)) using previous block's `GasWanted` * Distribution module distributes previous block's fee rewards 4. **Transaction Processing**: * `AnteHandler` validates transaction, verifies fees > minimum values and `BaseFee`, deducts fees to `fee_collector` module * For Cosmos transactions: Execute and consume gas * For Ethereum transactions: Execute with gas isolation, calculate gas used, refund surplus 5. **EndBlock**: Store block's `GasWanted` for next block ## Gas Mechanics ### Cosmos Gas Metering * `GasMeter`: Tracks gas consumed during state transitions (reset per transaction) * `BlockGasMeter`: Tracks gas consumed in block, enforces predefined limit (set via governance) Gas is priced per-byte. Since Cosmos uses [Big.Int](https://pkg.go.dev/math/big#Int) types (dynamically sized), larger parameter values are more gas-intensive than smaller ones. ### Matching EVM Gas Consumption The EVM uses a [gas table](https://github.com/ethereum/go-ethereum/blob/master/params/protocol_params.go) for each OPCODE, whereas Cosmos uses a `GasConfig` with flat and per-byte costs for database operations. To match EVM gas consumption, Cosmos EVM: * Ignores SDK gas consumption logic * Calculates gas consumed by subtracting EVM leftover gas + refund from gas limit * Resets transaction `GasMeter` to 0 and manually sets it to `gasUsed` value from EVM execution **Implementation**: [`x/vm/keeper/state_transition.go`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/state_transition.go) ### AnteHandler Performs basic checks before transaction execution: * Signature verification, transaction field validation, transaction fees * Verifies user has sufficient balance for tx cost (amount + fees) * Checks gas limit >= computed intrinsic gas ### Gas Estimation Ethereum's `eth_estimateGas` is implemented via `EstimateGas` query API. It: * Applies transaction against current block/state * Performs binary search to find optimal gas value * Uses cache context to avoid persisting state changes **Implementation**: [`x/vm/keeper/grpc_query.go`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/grpc_query.go) For Cosmos transactions, use [transaction simulation](/sdk/latest/node/txs#simulating-a-transaction). ### Zero-Fee Transactions In Cosmos, minimum gas price is not enforced by `AnteHandler` - validators specify their own `min-gas-prices`. This allows 0-fee transactions for non-EVM transactions if validators accept them. EVM transactions cannot have 0 fees as gas is inherently required by the EVM (validated in `ValidateBasic` and custom `AnteHandler`). ### Cross-Chain Gas and Fees For cross-chain scenarios (e.g., IBC transfer from Chain A to Cosmos EVM without native tokens), Cosmos SDK `Tips` allow users to: * Sign transaction with tip and no fees * Send to fee relayer who covers fees in native currency * Receive tip in payment (intermediary exchange behavior) ## Related Documentation * [EIP-1559 Fee Market](/evm/latest/documentation/concepts/eip-1559-feemarket) - Detailed fee market mechanics * [Performance Optimization](/evm/v0.5.0/documentation/concepts/performance) - Gas estimation optimizations * [x/feemarket Module](/evm/latest/documentation/cosmos-sdk/modules/feemarket) - Fee market implementation # IBC Source: https://docs.cosmos.network/evm/latest/documentation/concepts/ibc An Overview of the Inter-Blockchain Communication Protocol # IBC > An Overview of the Inter-Blockchain Communication Protocol This is a summary view of IBC v2 \[Eureka]. For more comprehensive details, guides and more please visit the official [Eureka documentation](/skip-go/eureka/eureka-overview). ### Core Data Structures The `Packet` is the primary container for cross-chain communication in IBC v2. Each packet may wrap **one or more** application-specific **`Payload`** objects. ```typescript "IBC v2 Core Data Structures" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Main container sent between chains interface Packet { sourceClientId: bytes; // Client ID for destination chain (stored on source) destClientId: bytes; // Client ID for source chain (stored on destination) sequence: uint64; // Monotonically increasing nonce per client-pair timeout: uint64; // UNIX timestamp (seconds) for packet expiry data: Payload[]; // Application payload(s) } // Application-specific data interface Payload { sourcePort: bytes; // Sending application identifier destPort: bytes; // Receiving application identifier version: string; // Application version encoding: string; // MIME-type for decoding value: bytes; // Opaque app data } ``` Timeout is measured **against the destination chain’s clock**, not the source. This ensures safety even under clock drift. ## Key On-Chain Components A light client verifies the state of a counterparty chain with cryptographic proofs against trusted consensus. Each IBC client defines a `ClientState` (long-term parameters) and evolving `ConsensusState`s (snapshots). If consensus assumptions are violated, misbehaviour evidence can freeze the client. A Merkle-proof-capable key–value store used for commitments. | Value | Path Format | | :---------------- | :--------------------------------------------- | | Packet Commitment | `{sourceClientId}0x1{bigEndianUint64Sequence}` | | Packet Receipt | `{destClientId}0x3{bigEndianUint64Sequence}` | | Acknowledgement | `{destClientId}0x2{bigEndianUint64Sequence}` | These standardized paths allow counterparties to verify packet existence, receipts, and acknowledgements. The IBC handler exposes the standard functions for packet relay: `SendPacket`, `RecvPacket`, `AcknowledgePacket`, and `TimeoutPacket`. It enforces **exactly-once delivery**, ensures valid ordering (ordered, unordered, or ordered-allow-timeout), and dispatches packets to the correct application. Applications must bind to unique **`portId`** values during initialization. Ports are referenced in every `Payload` for routing incoming packets. ## Application Interface (ICS-26) An IBC-enabled application **must** implement these callbacks to manage the packet lifecycle: * **`OnRecvPacket(...)`** – Executed on the **destination chain** to process incoming data. Must return an **Acknowledgement**, which may contain success data or an error. * **`OnAcknowledgePacket(...)`** – Executed on the **source chain** once an acknowledgement is verified. Provides acknowledgement data so the sending application can finalize or revert actions. * **`OnTimeoutPacket(...)`** – Executed on the **source chain** if a timeout occurs, enabling rollback or refunds. For packets with multiple payloads, execution is **atomic**. If `OnRecvPacket` fails for even one payload, the **entire packet** is considered failed. All state changes from other successful payloads **must be reverted**. ## Packet Lifecycle Application data is wrapped into a `Packet`, assigned a `sequence`, and committed at the **Packet Commitment** path. Destination chain verifies the packet commitment proof via its light client. Upon success, it stores a **Receipt** at the Packet Receipt path and invokes `OnRecvPacket`. The receiving application returns an `Acknowledgement`. This is committed under the **Acknowledgement** path, allowing proof for the source chain. Source chain verifies the acknowledgement proof, deletes the original commitment, and calls `OnAcknowledgePacket` on the sending application. If the `timeout` elapses before a receipt exists on the destination chain, the source verifies this via proof of non-existence, deletes the commitment, and triggers `OnTimeoutPacket`. # Mempool Source: https://docs.cosmos.network/evm/latest/documentation/concepts/mempool Design and Rationale ## Overview The EVM mempool manages both EVM and Cosmos transactions in a unified pool, enabling Ethereum-compatible transaction flows including out-of-order transactions and nonce gap handling. It replaces the default CometBFT FIFO mempool to support Ethereum tooling expectations while maintaining Cosmos SDK compatibility. ## Purpose and Design The EVM mempool serves as a bridge between Ethereum's transaction management model and Cosmos SDK's consensus layer. **Design Goals**: * **Ethereum Compatibility**: Out-of-order transaction submission, nonce gap handling, transaction replacement with higher fees, standard txpool RPC methods * **Cosmos Integration**: Unified mempool for both EVM and Cosmos transactions, fee-based prioritization, integration with ante handlers, preservation of consensus finality **Transaction Validation**: The EVM mempool is an implementation of CometBFT's application mempool type. This means that the application is fully responsible for transaction validation, transaction rechecking, and block building. Please see [CometBFT's mempool docs](https://docs.cosmos.network/cometbft/latest/docs/core/mempool) (section 3) for more info on this architecture. **Use Cases**: Complex contract deployments (DeFi protocols), batch transaction workflows (development scripts), transaction replacement (fee bumping) ## Architecture Below is a general architecture diagram of how the mempool works, specifically for EVM transactions. There are other components within the mempool to facilitate Cosmos transactions, however the EVM mempool is mostly concerned with the performance of EVM transactions, so this focuses on them. There is largely a mirror of each of these components for Cosmos transactions, with the exception of allowing nonce gapped transactions. Mempool Architecture ## Transaction Flow Given this architecture, we will now describe a transaction steps from ingestion, either via JSON-RPC, P2P, or BroadcastTx to being validated and included in a block. ### Transaction Submission #### JSON-RPC Transactions submitted via the `eth` JSON-RPC are directly added to the EVM mempool without going through CometBFT or any `CheckTx` validation (however **some** [EVM mempool validation ported from geth](https://github.com/cosmos/evm/blob/release/v0.7.x/mempool/txpool/legacypool/legacypool.go#L895) is done at insert time). #### P2P Transactions submitted via P2P are received by CometBFT and passed to the application via the `InsertTx` ABCI method. This method signals to the application that it should insert the transaction into its mempool and should validate it on its own terms, it does not have to happen synchronously at insertion time. Both EVM & Cosmos transactions that are ingested via P2P are added to the mempool's [`InsertQueue`](https://github.com/cosmos/evm/blob/release/v0.7.x/mempool/internal/queue/queue.go#L57) before immediately returning (RPC/local transactions also use this `InsertQueue` path, however the callers wait for the transaction to leave the `InsertQueue` before returning, so it is less noticeable). The application does not wait on any validation to happen. This ensures the P2P flow is extremely fast since this sees the highest amount of traffic during high TPS periods. #### BroadcastTx > Using CometBFT's `BroadcastTx...` methods is not recommended for the best > performance however it fully supported since many Cosmos/EVM chains still > require non EVM transactions that cannot be submitted via the application side > JSON-RPC. The EVM mempool provides a `CheckTx` handler that calls a synchronous `Insert` function. This will run `anteHanlder`'s in the insert hot path and provide the typical `BroadcastTx...` response clients are used to. See [CometBFT's app mempool documentation on `BroadcastTx...`](https://docs.cosmos.network/cometbft/latest/docs/core/mempool#updated-broadcast_tx_-rpc-lifecycle-app-mempool) for more details. ### Transaction Validation Nonce gapped EVM transactions are fully supported and are queued locally until they are available for execution. To determine if a transaction is valid and ready for execution (either after being enqueued locally due to a nonce gap, or asynchronously after insertion), the applications `anteHandler` is run the tx in order to determine validity. This ensures that all txs that are marked as executable (also called 'pending' within the EVM mempool) have passed `anteHandler`'s (this is the same validation that would typically be done via `CheckTx` when using a non `app` mempool). Since the `anteHandler` execution is happening asynchronously from transaction insertion, users will only see errors from the EVM mempools validation ported from geth at insert time. Users will not see errors that stem only from `anteHandler` validation and if the `anteHandler` fails, the transaction is silently dropped after a successful insert, however this should be rare. ### Transaction Revalidation One of the key aspects of implementing an `app` mempool is that CometBFT does not drive revalidation of txs after block inclusion. The EVM mempool ensures that after every block, each transaction (both queued and pending execution) is either revalidated or removed from the mempool if it has become invalid, and demoting (moving from pending execution -> queued) EVM transactions that have now become nonce gapped due to the drop (Cosmos transactions cannot have nonce gaps, so they are dropped if an earlier nonce tx is dropped). This revalidation is driven asynchronously either via CometBFT's event bus system, or via the `PrepareCheckStater` hook in the CosmosSDK. `PrepareCheckStater` runs just after block commit. CometBFT's event bus is the preferred method (`PrepareCheckStater` is largely used to drive integration tests running without a CometBFT instance under the hood). ### Transaction gossip Upon successful `anteHandler` validation, transactions are pushed onto a [`ReapList`](https://github.com/cosmos/evm/blob/release/v0.7.x/mempool/internal/reaplist/reaplist.go#L26) that will pass the transaction to CometBFT the next time CometBFT calls the `ReapTxs` ABCI method. CometBFT will then gossip the tx to the rest of the network. Note that reaping a transaction only happens the **first** time a transaction is successfully validated. This means that if a transaction is nonce gapped, it is not gossiped (it has not yet been validated via `anteHandler`'s). If a transaction is validated, invalidated, and revalidated, it is only gossiped upon the **first** validation, it is not re-gossipped upon the second validation. > See the [CometBFT app mempool > documentation](https://docs.cosmos.network/cometbft/latest/docs/core/mempool) > for more details on gossip once the transaction has passed the ABCI boundary. ### Block Building `app` mempools receive no transactions from CometBFT when creating a new block (it stores none, so it has none to give), thus all transactions that are going to be included in a proposal must be provided and validated via the EVM mempool. These transactions are provided as a CosmosSDK `Iterator` via the `SelectBy` function, that the CosmosSDK will call during the `PrepareProposal` ABCI method. The EVM mempool takes special care to be fair to both EVM and Cosmos transactions. It orders both sets of transactions by the same rules, nonce and effective tip. Effective tip is calculated as: * **EVM**: `min(gas_tip_cap, gas_fee_cap - base_fee)` * **Cosmos**: `fee_amount / gas_limit` Preference is given to EVM transactions in the case of ties. ## Application Requirements and Considerations Given that the EVM mempool is an `app` side mempool, there are special considerations that must be taken by the application to ensure the correctness of the transactions being validated. ### Address reservations The EVM mempool uses an address reservation system to ensure that no transaction signer can have a transaction in both the EVM subpool and the Cosmos subpool at the same time. That means, if signer A submits an EVM tx through the `eth_SubmitRawTransaction` JSON-RPC, user A cannot submit a Cosmos transaction via the `BroadcastTxSync` RPC method until their first EVM transaction has left the EVM mempool (either because it was included on chain, or dropped due to a validation error). This requirement is because of how the rechecking of transactions works internally between the two (EVM & Cosmos) pools works within the mempool. The rechecking runs asynchronously within each distinct pool, with no communicating between the two. Having the same signer in both pools would require synchronization between the two to ensure that the transactions are checked in the correct nonce ordering. ### Ante Handlers `AnteHandler` sequences must ensure that they do not have any cross-account state changes. This is due to the same reasoning as above, if validating a transaction in the EVM pool from signer A, can suddenly effect the outcome of validating signer B in the Cosmos pool, this is undefined behavior and the validation would depend on the ordering and timing of the rechecks across asynchronous loops. ## API Reference The mempool exposes Ethereum-compatible RPC methods. See [JSON-RPC Methods documentation](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool-methods) for detailed reference: * `txpool_status`, `txpool_content`, `txpool_contentFrom`, `txpool_inspect` Explore interactively: [RPC Explorer](/evm/latest/api-reference/ethereum-json-rpc/rpc-explorer) ## Configuration ### Basic Configuration & Wiring The helper function: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "github.com/cosmos/evm/server" servertypes "github.com/cosmos/cosmos-sdk/server/types" ) var appOpts servertypes.AppOptions config := server.ResolveMempoolConfig(app.GetAnteHandler(), appOpts, logger) ``` is provided in order to load a mempool configuration from the app options. Then wire up the mempools dependencies to construct it: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( evmmempool "github.com/cosmos/evm/mempool" "github.com/cosmos/evm/server" ) var ( txEncoder = evmmempool.NewTxEncoder(app.txConfig) evmRechecker = evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder) cosmosRechecker = evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder) cosmosPoolMaxTx = server.GetCosmosPoolMaxTx(appOpts, logger) ) mempool := evmmempool.NewMempool( app.CreateQueryContext, logger, app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, evmRechecker, cosmosRechecker, config, cosmosPoolMaxTx, ) ``` ## Integration For chain developers integrating the mempool and looking for full configuration details, see the [EVM Mempool Integration Guide](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/mempool-integration). ## Testing Verify mempool behavior using test scripts in [cosmos/evm](https://github.com/cosmos/evm). The [`tests/systemtests/Counter/script/SimpleSends.s.sol`](https://github.com/cosmos/evm/blob/main/tests/systemtests/Counter/script/SimpleSends.s.sol) script demonstrates typical Ethereum tooling behavior with 10 sequential transactions arriving out of order. # State Export/Import Source: https://docs.cosmos.network/evm/latest/documentation/concepts/migrations Cosmos EVM can dump the entire application state to a JSON file. This, besides upgrades, can be useful for manual analysis of the state at a given height. ## Export State[​](#export-state "Direct link to Export State") Export state with: ``` evmd export > new_genesis.json ``` You can also export state from a particular height (at the end of processing the block of that height): ``` evmd export --height [height] > new_genesis.json ``` If you plan to start a new network for 0 height (i.e genesis) from the exported state, export with the `--for-zero-height` flag: ``` evmd export --height [height] --for-zero-height > new_genesis.json ``` ## Manually Migrate State[​](#manually-migrate-state "Direct link to Manually Migrate State") If you want to migrate state manually, e.g. for local testing purpose. Note that for regular chain upgrades, a manual state migration is not required. After exporting your state into a json file, you can replace the old `genesis.json` with `new_genesis.json`. ``` cp -f genesis.json new_genesis.jsonmv new_genesis.json genesis.json ``` At this point, you might want to run a script to update the exported genesis into a genesis state that is compatible with your new version. You can use the `migrate` command to migrate from a given version to the next one (eg: `v0.X.X` to `v1.X.X`): ``` evmd migrate TARGET_VERSION GENESIS_FILE --chain-id= --genesis-time= ``` # Overview Source: https://docs.cosmos.network/evm/latest/documentation/concepts/overview Understanding how the EVM module provides Ethereum compatibility within the Cosmos SDK platform The Cosmos EVM module implements a complete Ethereum Virtual Machine as a Cosmos SDK module, providing an Ethereum-compatible interface to the underlying Cosmos consensus and infrastructure while enabling access to the broader Cosmos ecosystem. ## Platform Integration The EVM module sits atop the Cosmos SDK platform, leveraging its modular architecture: * **Consensus Layer**: CometBFT provides Byzantine fault-tolerant consensus with instant finality * **State Management**: Cosmos SDK's IAVL tree and KVStore handle state persistence * **Account System**: Unified account model supporting both Ethereum and Cosmos address formats * **Module Ecosystem**: Direct access to staking, governance, bank, and IBC modules through precompiles ## Ethereum Compatibility The EVM module provides complete Ethereum compatibility, enabling all standard Ethereum tooling and workflows: * **Smart Contracts**: Full EVM bytecode execution with identical gas costs and opcode behavior * **Transaction Types**: Support for all Ethereum transaction formats including EIP-1559 and EIP-7702 * **JSON-RPC API**: Complete Ethereum RPC implementation for seamless tool integration * **Development Tools**: Works with MetaMask, Hardhat, Foundry, Remix, and all Ethereum development frameworks ## Enhanced Features ### Cosmos SDK Benefits * **Instant Finality**: Transactions are final after one block (\~2 seconds) with no reorganizations * **Cross-Chain Integration**: Native IBC support for interacting with other Cosmos chains * **Modular Access**: Smart contracts can directly interact with staking, governance, and other Cosmos modules * **Enhanced Security**: Byzantine fault-tolerant consensus with stake-based validator selection ## Key Differences from Standard Ethereum * **Consensus**: CometBFT instead of proof-of-stake, providing instant finality * **State Storage**: IAVL tree and Cosmos SDK KVStore instead of Merkle Patricia Tree * **Fee Distribution**: Base fees distributed to validators instead of burned * **Cross-Chain**: Native IBC integration for seamless interchain operations * **Module Access**: Smart contracts can directly call Cosmos SDK modules ## Developer Experience Developers can build on Cosmos EVM using familiar Ethereum tools and patterns: * **Standard Tooling**: MetaMask, Hardhat, Foundry, Remix work without modification * **Ethereum Libraries**: Web3.js, Ethers.js, and other libraries work seamlessly * **Smart Contracts**: Deploy existing Ethereum contracts without changes * **Enhanced Capabilities**: Access Cosmos modules and IBC through precompiled contracts For detailed technical implementation, see individual concept pages for [transactions](/evm/latest/documentation/concepts/transactions), [accounts](/evm/latest/documentation/concepts/accounts), [gas and fees](/evm/latest/documentation/concepts/gas-and-fees), and [precompiles](/evm/latest/documentation/smart-contracts/precompiles). # Pending State Source: https://docs.cosmos.network/evm/latest/documentation/concepts/pending-state When a transaction is submitted to the Ethereum network, it first goes into the pending status, waiting to be executed by the nodes. A transaction can be in the pending state for a longer duration if the gas price is set very low in the transaction and the nodes are busy processing other higher gas price transactions. During the pending state, the transaction initiator is allowed to change the transaction fields at any time. They can do so by sending another transaction with the same nonce. ## Prerequisite Readings[​](#prerequisite-readings "Direct link to Prerequisite Readings") * [Cosmos SDK Mempool](/sdk/latest/guides/abci/app-mempool) * [Mempool Architecture](/evm/latest/documentation/concepts/mempool) ## Cosmos EVM vs Ethereum[​](#cosmos-evm-vs-ethereum "Direct link to Cosmos EVM vs Ethereum") In Ethereum, pending blocks are generated as they are queued for production by miners. These pending blocks include pending transactions that are picked out by miners, based on the highest reward paid in gas. This mechanism exists as block finality is not possible on the Ethereum network. Blocks are committed with probabilistic finality, which means that transactions and blocks become less likely to become reverted as more time (and blocks) passes. Cosmos EVM is designed differently as it uses [CometBFT](/cometbft/latest/docs/README) consensus which provides instant finality for transactions. While there is no concept of "pending blocks" that can be reorganized, Cosmos EVM implements an **experimental EVM mempool** that provides Ethereum-compatible pending state functionality. ## EVM-Compliant Mempool The experimental EVM mempool introduces a two-tiered system that brings Ethereum-like pending state behavior to Cosmos EVM: ### Transaction States Transactions with correct nonces that are immediately executable. These are in the public mempool, broadcast to peers, and ready for block inclusion. Transactions with future nonces (nonce gaps) that are stored locally. These wait for earlier transactions to execute before being promoted to pending. ### Key Differences from Traditional Cosmos 1. **Nonce Gap Handling**: Unlike traditional Cosmos chains that reject out-of-order transactions, the EVM mempool queues them locally until gaps are filled. 2. **Fee-Based Priority**: Both EVM and Cosmos transactions compete fairly based on their effective tips rather than FIFO ordering: * **EVM transactions**: Priority = `gas_tip_cap` or `min(gas_tip_cap, gas_fee_cap - base_fee)` * **Cosmos transactions**: Priority = `(fee_amount / gas_limit) - base_fee` 3. **Transaction Replacement**: Supports replacing pending transactions with higher fee versions using the same nonce, enabling "speed up" functionality common in Ethereum wallets. ## Pending State Queries[​](#pending-state-queries "Direct link to Pending State Queries") With the experimental EVM mempool, pending state queries now reflect a more Ethereum-compatible view: ### Transaction Pool Inspection The mempool provides dedicated RPC methods to inspect pending and queued transactions: Returns counts of pending and queued transactions: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "pending": "0x10", // 16 pending transactions "queued": "0x5" // 5 queued transactions } ``` Returns detailed information about all transactions in the pool, organized by account and nonce. Provides human-readable summaries of transactions for debugging. See the [JSON-RPC Methods](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool-methods) documentation for complete details. ### Pending State Behavior When making queries with "pending" as the block parameter: 1. **Balance Queries**: Reflect the account balance after all pending transactions from that account are applied 2. **Nonce Queries**: Return the next available nonce considering all pending transactions 3. **Gas Estimates**: Account for pending transactions that may affect gas costs Pending state queries are subjective to each node's local mempool view. Different nodes may return different results based on their transaction pool contents. ## JSON-RPC Calls Supporting Pending State[​](#json-rpc-calls-on-pending-transactions "Direct link to JSON-RPC Calls on Pending Transactions") The following RPC methods support the `"pending"` block parameter: * [`eth_getBalance`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_getbalance) - Get balance considering pending transactions * [`eth_getTransactionCount`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_gettransactioncount) - Get next nonce considering pending transactions * [`eth_getBlockTransactionCountByNumber`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_getblocktransactioncountbynumber) - Count pending transactions * [`eth_getBlockByNumber`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_getblockbynumber) - Get pending block information * [`eth_getTransactionByHash`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_gettransactionbyhash) - Retrieve pending transactions * [`eth_getTransactionByBlockNumberAndIndex`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_gettransactionbyblockhashandindex) - Access specific pending transactions * [`eth_sendTransaction`](/evm/latest/api-reference/ethereum-json-rpc/methods#eth_sendtransaction) - Submit new transactions Additionally, the `txpool_*` namespace provides specialized methods for mempool inspection: * [`txpool_status`](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool_status) - Get pool statistics * [`txpool_content`](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool_content) - View all pool transactions * [`txpool_contentFrom`](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool_contentfrom) - Filter by address * [`txpool_inspect`](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool_inspect) - Human-readable summaries ## Practical Examples ### Monitoring Transaction Status ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Check if transaction is pending const tx = await provider.getTransaction(txHash); if (tx && !tx.blockNumber) { console.log("Transaction is pending"); // Check pool status const poolStatus = await provider.send("txpool_status", []); console.log(`Pool has ${poolStatus.pending} pending, ${poolStatus.queued} queued`); } ``` ### Handling Nonce Gaps ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Send transactions with nonce gaps (they'll be queued) await wallet.sendTransaction({nonce: 100, ...}); // Executes immediately await wallet.sendTransaction({nonce: 102, ...}); // Queued (gap at 101) await wallet.sendTransaction({nonce: 101, ...}); // Fills gap, both execute ``` ### Transaction Replacement ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Speed up a pending transaction const originalTx = await wallet.sendTransaction({ nonce: 100, gasPrice: parseUnits("20", "gwei") }); // Replace with higher fee const fasterTx = await wallet.sendTransaction({ nonce: 100, // Same nonce gasPrice: parseUnits("30", "gwei") // Higher fee }); ``` ## Architecture Details For a detailed understanding of how the pending state is managed: * See [Mempool Architecture](/evm/latest/documentation/concepts/mempool#architecture) for the two-tier system design * Review [Transaction Flow](/evm/latest/documentation/concepts/mempool#transaction-flow) for state transitions * Check [Integration Guide](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/mempool-integration) for implementation details # EIP-155: Replay Protection Source: https://docs.cosmos.network/evm/latest/documentation/concepts/replay-protection EIP-155 is an Ethereum Improvement Proposal that introduced replay protection by including chain ID information in signed transaction data. This prevents a signed transaction from being valid on multiple networks, protecting users from replay attacks. ## Background **Replay attacks** occur when a signed transaction valid on one network can be replayed on another network without the user's consent. This is particularly problematic for Ethereum-based networks because: 1. **Address Format**: Ethereum uses hexadecimal addresses (e.g., `0x123...`) that are identical across all EVM chains 2. **Transaction Format**: The same transaction structure and signing scheme across networks 3. **Network Forks**: When chains fork, the same addresses exist on both chains with potentially different balances EIP-155 solves this by incorporating the chain ID into the transaction signing process, making signatures network-specific. ## How Cosmos EVM Handles Replay Protection ### Dual Protection System Cosmos EVM implements replay protection at two levels: **Inherent Protection**: Uses Bech32 addresses with chain-specific prefixes (e.g., `cosmos1...`, `osmo1...`). Addresses from one chain are invalid on others, providing automatic replay protection. **EIP-155 Protection**: Requires chain ID in transaction signatures. The EVM module enforces this by default, rejecting transactions without proper chain ID. ### Chain ID Configuration As documented in the [Chain ID](/evm/latest/documentation/concepts/chain-id) page, Cosmos EVM uses: * **EVM Chain ID** (integer): Used for EIP-155 replay protection in Ethereum transactions * **Cosmos Chain ID** (string): Used for Cosmos SDK transactions with Bech32 addresses The EVM Chain ID must be unique across all EVM-compatible chains to ensure replay protection. Check [chainlist.org](https://chainlist.org/) before selecting your chain ID. ## Configuring Replay Protection By default, replay protection is **enabled** on all Cosmos EVM nodes. The system enforces EIP-155 signed transactions, rejecting any unprotected transactions. ### Allowing Unprotected Transactions (Not Recommended) Disabling replay protection is **strongly discouraged** as it exposes users to replay attacks. Only consider this for specific testing scenarios or migration purposes. If absolutely necessary, unprotected transactions can be allowed through a two-step process: #### Step 1: Global Module Parameter The EVM module parameter must be changed via governance proposal or genesis configuration: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // In x/vm/types/params.go DefaultAllowUnprotectedTxs = false // Default value // To enable in genesis.json { "vm": { "params": { "allow_unprotected_txs": true // Must be set to true } } } ``` #### Step 2: Node Configuration Even when globally enabled, each node operator must individually opt-in by modifying their node configuration: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # In $HOME/.evmd/config/config.toml [json-rpc] # AllowUnprotectedTxs restricts unprotected (non EIP-155 signed) transactions # to be submitted via the node's RPC when the global parameter is disabled. allow-unprotected-txs = true # false by default ``` Both conditions must be met for unprotected transactions to be accepted: 1. Global parameter `allow_unprotected_txs` = true 2. Node configuration `allow-unprotected-txs` = true ## Transaction Types and Protection ### Protected Transactions (EIP-155) All modern Ethereum transaction types include chain ID: ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // EIP-1559 Dynamic Fee Transaction const tx = { type: 2, chainId: 9000, // EVM Chain ID included nonce: 0, maxFeePerGas: ..., maxPriorityFeePerGas: ..., // ... other fields } ``` ### Legacy Unprotected Transactions Pre-EIP-155 transactions without chain ID: ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Legacy transaction without chain ID (rejected by default) const tx = { nonce: 0, gasPrice: ..., gasLimit: ..., to: ..., value: ..., data: ..., // No chainId field } ``` ## Security Implications ### With Replay Protection Enabled (Default) **Protected from:** * Cross-chain replay attacks * Accidental transaction execution on wrong networks * Fork-related replay vulnerabilities ### With Replay Protection Disabled **Vulnerable to:** * Transactions being replayed on any EVM chain * Loss of funds through replay attacks * Unintended contract interactions on multiple chains ## Best Practices 1. **Always use EIP-155**: Ensure your wallet and tools sign transactions with chain ID 2. **Verify chain ID**: Double-check the chain ID in your transaction before signing 3. **Keep protection enabled**: Never disable replay protection in production 4. **Monitor transactions**: Track transaction execution across chains during migrations ## Verification To verify replay protection status: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Check global parameter evmd q vm params | grep allow_unprotected_txs # Check node configuration grep allow-unprotected-txs $HOME/.evmd/config/config.toml ``` ## Related Resources * [EIP-155 Specification](https://eips.ethereum.org/EIPS/eip-155) * [Chain ID Configuration](/evm/latest/documentation/concepts/chain-id) * [Transaction Types](/evm/latest/documentation/concepts/transactions#ethereum-tx-type) * [EVM Module Parameters](/evm/latest/documentation/cosmos-sdk/modules/vm#parameters) # Signing Source: https://docs.cosmos.network/evm/latest/documentation/concepts/signing Signing is the process of creating a digital signature using a private key to verify a transaction on a blockchain. The signature is created using a specific cryptographic algorithm that ensures the authenticity and integrity of the transaction using methods like wallets and the CLI. There are different methods for signing, but one of the most commonly used methods is the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) standard. Cosmos EVM leverages EIP-712 to homogenize the interaction between the EVM and Cosmos. ## EIP-712[​](#eip-712 "Direct link to EIP-712") EIP-712 introduces a standard for signing "typed-data" in a human-readable format. This standard allowed users to understand the data they are signing more easily and provides a more secure way to sign data, as it is less susceptible to phishing attacks. EIP-712 is not an Ethereum transaction type, but a method for signing structured data that can be used for authentication and indirect influence on program logic. To support signing Cosmos transactions, Cosmos EVM utilizes the EIP-712 protocol for encoding Cosmos transactions in a format that can be understood and processed by Ethereum signers, including Ledger hardware wallets. This approach helps to overcome the limitations of Ethereum signing devices, which often do not support signing arbitrary bytes for security reasons. The process works as follows: 1. A Cosmos transaction is represented as a JSON sign-doc. 2. The JSON sign-doc is converted to an EIP-712 object, which consists of types and messages. 3. The EIP-712 object is signed using an Ethereum signer, such as MetaMask or a Ledger hardware device. 4. The same process is performed on the node to verify the signature. By using EIP-712 for signing Cosmos transactions, Cosmos EVM ensures compatibility with popular Ethereum signing tools like MetaMask and Ledger devices as well as Cosmos specific ones like Keplr and Leap. This compatibility makes it easier for users to interact with both Ethereum and Cosmos networks, ultimately fostering greater interoperability between the two ecosystems. # Single Token Representation Source: https://docs.cosmos.network/evm/latest/documentation/concepts/single-token-representation Unified token model across Cosmos and EVM ecosystems ## The Problem Traditional blockchain ecosystems with EVM support often have token fragmentation: * Native tokens in the base blockchain format * Wrapped tokens as ERC20 contracts * IBC vouchers for cross-chain assets * Multiple bridge token representations This creates liquidity splitting, user confusion, and inefficient capital allocation. ## Single Token Representation v2 (STRv2) STRv2 ensures each asset has exactly one canonical representation that automatically adapts to its usage context. **Core Principle**: Each token exists in either Cosmos or EVM format at any given time, never both. The representation automatically switches based on where it's being used. **Automatic Conversion**: * Sending to Ethereum address → converts to ERC20 * Sending to Cosmos address → converts to native coin * IBC transfer with EVM recipient → arrives as ERC20 ## Token Pairs A TokenPair links a Cosmos denomination with an ERC20 contract address: ``` Cosmos Coin (uatom) ←→ TokenPair ←→ ERC20 Contract (0x...) ``` **Native Cosmos Coins**: * Start as Cosmos SDK coins (uatom, uosmo, etc.) * Module deploys an ERC20 contract representation * Conversions use mint/burn mechanism **Existing ERC20 Tokens**: * Start as deployed ERC20 contracts * Module creates a Cosmos coin denomination * Conversions use escrow/release mechanism ## Precompiled Contracts Instead of traditional wrapped token contracts, STRv2 uses precompiled contracts - native code that appears as ERC20 to the EVM but executes at native speed. **Native Precompiles**: * Standard ERC20 interface (transfer, approve, balanceOf) * Direct bank module integration * No contract storage overhead * Gas costs 10-100x lower than regular ERC20 **Dynamic Precompiles**: * WERC20 interfaces with deposit/withdraw * Runtime registration for custom tokens * Module-specific features ## Conversion Mechanics **Cosmos → ERC20**: 1. Coins escrowed in module account 2. ERC20 minted to recipient **ERC20 → Cosmos**: 1. Tokens burned (if module-owned) or escrowed (if external) 2. Coins released from module account Supply invariant maintained: ``` Total Supply = Cosmos Circulating + ERC20 Circulating + Module Escrow ``` ## IBC Integration STRv2 integrates with IBC through middleware that automatically converts based on recipient address type: * Ethereum address recipient → convert to ERC20 * Cosmos address recipient → keep as native * Missing token pair → create dynamically This enables seamless cross-chain DeFi workflows where tokens can be received via IBC and immediately used in EVM contracts without manual conversion steps. ## Design Rationale **Why Not Simple Wrapping?** Traditional wrapping (like WETH) requires manual wrap/unwrap steps, splits liquidity between native and wrapped versions, and adds gas overhead from extra contract calls. **Why Precompiles?** Precompiles provide native execution speed with ERC20 compatibility, no storage overhead, and appear as regular tokens to contracts. This is only possible with chain-level integration. **Why Automatic Conversion?** Removes manual steps, unifies liquidity into single pools per asset, and eliminates user errors from using wrong token representations. ## Security **Module Ownership** (native coins): Module can mint/burn ERC20, ensuring 1:1 backing with no external control risks. **External Ownership** (ERC20 origin): Original deployer retains control, module uses escrow mechanism, preserving contract upgradeability. **Validation**: ERC20 contracts validated before registration with event log verification and balance checks. ## Related Documentation * [ERC20 Module Reference](/evm/latest/documentation/cosmos-sdk/modules/erc20) - Configuration and token pair management * [Precompiled Contracts](/evm/latest/documentation/smart-contracts/precompiles/overview) - Technical implementation details # Tokens Source: https://docs.cosmos.network/evm/latest/documentation/concepts/tokens It is recommend to uses for your base denomination to maintain parity with Ethereum. There are two types of assets to consider on a Cosmos EVM-based chain: * Cosmos tokens issued by the `x/bank` module * Ethereum-typed tokens, e.g. ERC-20, issued by the EVM `1 stake = 1018 atest` This matches Ethereum denomination of: `1 ETH = 1018 wei` ## Cosmos Coins[​](#cosmos-coins "Direct link to Cosmos Coins") Accounts can own Cosmos coins in their balance, which are used for operations with other Cosmos and transactions. Examples of these are using the coins for staking, IBC transfers, governance deposits and EVM. ## EVM Tokens[​](#evm-tokens "Direct link to EVM Tokens") Cosmos EVM is compatible with ERC20 tokens and other non-fungible token standards (EIP721, EIP1155) that are natively supported by the EVM. # Transactions Source: https://docs.cosmos.network/evm/latest/documentation/concepts/transactions Transaction types and lifecycle in Cosmos EVM A transaction refers to an action initiated by an account that changes blockchain state. Transactions are broadcasted to the network where validators validate, execute, and propagate the resulting state change. Transactions consume computational resources (gas) requiring associated fees. Additionally, transactions must be signed using the sender's private key to prove authenticity. ## Transaction Lifecycle The transaction lifecycle involves a dual-phase process through CometBFT consensus: ### CheckTx Phase * **Transaction routing**: Ante handler identifies transaction type ([`ante/ante.go:18-71`](https://github.com/cosmos/evm/blob/main/ante/ante.go#L18-L71)) * **EVM validation**: MonoDecorator consolidates validation using go-ethereum's `txpool.ValidateTransaction()` ([`ante/evm/mono_decorator.go:99+`](https://github.com/cosmos/evm/blob/main/ante/evm/mono_decorator.go#L99)) * **Transaction filtering**: Uses `PendingFilter` with configurable `MinTip`, base fee, and transaction type filters ([`mempool/mempool.go:455-461`](https://github.com/cosmos/evm/blob/main/mempool/mempool.go#L455-L461)) * **Nonce gap handling**: Transactions with future nonces queued locally via `InsertInvalidNonce()` ([`mempool/check_tx.go:21-23`](https://github.com/cosmos/evm/blob/main/mempool/check_tx.go#L21-L23)) * **Mempool addition**: Valid transactions added to CometBFT mempool for P2P broadcast ### DeliverTx Phase * **Message unwrapping**: `msg.AsTransaction()` extracts Ethereum transaction from `MsgEthereumTx` ([`x/vm/keeper/msg_server.go:32`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/msg_server.go#L32)) * **State transition**: `ApplyTransaction()` executes EVM logic with gas isolation ([`x/vm/keeper/state_transition.go:165-199`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/state_transition.go#L165-L199)) * **Cache contexts**: Isolated execution with rollback capability * **State commitment**: Successful transactions committed to Cosmos SDK store For detailed transaction flow and mempool behavior, see [Mempool documentation](/evm/latest/documentation/concepts/mempool#transaction-flow) and [Cosmos SDK lifecycle](/sdk/latest/learn/concepts/lifecycle). ## Transaction Types Cosmos EVM supports two transaction types: ### 1. Cosmos Transactions Transactions comprised of metadata held in contexts and `sdk.Msg`s that trigger state changes through Protobuf [Msg services](/sdk/latest/learn/concepts/transactions). Cosmos transactions can have **multiple `sdk.Msg`s** and include: * `Msgs`: Array of messages * `GasLimit`: Gas calculation option * `FeeAmount`: Maximum fee willing to pay * `TimeoutHeight`: Block height validity * `Signatures`: Array of signer signatures * `Memo`: Note or comment ### 2. Ethereum Transactions Actions initiated by EOAs (externally-owned accounts) that transform EVM state. Must be broadcasted to the entire network. Ethereum transaction categories: * **Regular transactions**: Account to account * **Contract deployment**: No `to` address, contract code in `data` field * **Contract execution**: Interact with deployed smart contract Ethereum transaction fields: * `recipient`, `signature`, `nonce`, `value` * `data`: Arbitrary data for contract deployment/calls * `gasLimit`, `maxPriorityFeePerGas`, `maxFeePerGas` **Reference**: [Ethereum transactions](https://ethereum.org/en/developers/docs/transactions/) ### Supported Ethereum Transaction Types Cosmos EVM supports types defined in `AcceptedTxType` ([`ante/evm/mono_decorator.go:23-27`](https://github.com/cosmos/evm/blob/main/ante/evm/mono_decorator.go#L23-L27)): * **Legacy Transactions** (EIP-155): Chain ID protection * **Access List Transactions** (EIP-2930): Pre-declared storage access * **Dynamic Fee Transactions** (EIP-1559): Base fee + priority fee model * **Set Code Transactions** (EIP-7702): Account code assignment with authorization list **Note**: Unprotected legacy transactions are not supported by default. ### MsgEthereumTx Wrapper Cosmos EVM wraps Ethereum transactions in `MsgEthereumTx` ([`x/vm/types/tx.pb.go:36-43`](https://github.com/cosmos/evm/blob/main/x/vm/types/tx.pb.go#L36-L43)): * `From`: Ethereum signer address bytes for signature verification * `Raw`: Complete Ethereum transaction data This wrapper implements both `sdk.Msg` and `sdk.Tx` interfaces, enabling: * **Direct go-ethereum integration**: Uses `txpool.ValidateTransaction()` instead of SDK ante handlers * **Gas isolation**: EVM execution uses infinite gas meter, bypassing SDK gas consumption ([`x/vm/keeper/state_transition.go:153-164`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/state_transition.go#L153-L164)) * **Economic validation**: `CheckSenderBalance()` compares account balance with transaction cost ([`x/vm/keeper/fees.go:24-39`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/fees.go#L24-L39)) * **Single message constraint**: Only one EVM message per transaction ([`ante/evm/mono_decorator.go:88-91`](https://github.com/cosmos/evm/blob/main/ante/evm/mono_decorator.go#L88-L91)) ## EVM Execution Integration Cosmos EVM creates an execution environment bridging Ethereum and Cosmos SDK state management: **Block Context Mapping** ([`x/vm/keeper/state_transition.go:46-57`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/state_transition.go#L46-L57)): * CometBFT block proposer → EVM coinbase address * Block height → EVM block number * Block timestamp → EVM timestamp opcode * Historical block access via EIP-2935 contract **Access Control Hooks** ([`x/vm/keeper/state_transition.go:67-78`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/state_transition.go#L67-L78)): * Restrict contract creation and execution via EVM opcode interceptors * Policy-based permissions for `CREATE`, `CREATE2`, and `CALL` operations **Receipt Generation** ([`x/vm/keeper/state_transition.go:125`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/state_transition.go#L125)): * Ethereum-compatible bloom filters for log filtering * Dual transaction hash emission for cross-ecosystem compatibility * Contract address generation via `crypto.CreateAddress()` ## Transaction Ordering and Prioritization Transactions are ordered by effective tips: * **Ethereum**: `gas_tip_cap` or `min(gas_tip_cap, gas_fee_cap - base_fee)` * **Cosmos**: `(fee_amount / gas_limit) - base_fee` Higher tips = higher priority, regardless of transaction type. **Nonce Handling**: * **Ethereum transactions**: Support nonce gaps with local queuing and automatic promotion * **Cosmos transactions**: Require sequential nonces For detailed mempool behavior, see [Mempool Architecture](/evm/latest/documentation/concepts/mempool#architecture). ## Transaction Receipts Cosmos EVM generates Ethereum-compatible receipts with Cosmos SDK event integration ([`x/vm/keeper/state_transition.go:125-146`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/state_transition.go#L125-L146)): * **Bloom Filter Computation**: Creates transaction and block-level bloom filters * **Gas Reconciliation**: Reconciles EVM gas usage with SDK gas meter state * **Dual Event Emission**: Contains both Ethereum transaction hash and CometBFT transaction hash ([`x/vm/keeper/msg_server.go:77-80`](https://github.com/cosmos/evm/blob/main/x/vm/keeper/msg_server.go#L77-L80)) Receipt fields include: * `transactionHash`, `transactionIndex`, `blockHash`, `blockNumber` * `from`, `to`, `cumulativeGasUsed`, `effectiveGasPrice`, `gasUsed` * `contractAddress`, `logs`, `logsBloom` * `type`, `status` ## IBC Integration Cosmos EVM enables cross-chain functionality through IBC accessible from EVM smart contracts: * **ICS20 Precompile**: Direct interface for cross-chain token transfers * **Cosmos SDK Module Access**: Smart contracts interact with bank, staking, distribution, and governance modules through precompiled contracts ## Related Documentation * [Mempool](/evm/latest/documentation/concepts/mempool) - Transaction flow and mempool architecture * [Gas and Fees](/evm/latest/documentation/concepts/gas-and-fees) - Fee calculation and gas metering * [Accounts](/evm/latest/documentation/concepts/accounts) - Account structure and addressing * [x/vm Module](/evm/latest/documentation/cosmos-sdk/modules/vm) - EVM implementation details # Command Line Interface (CLI) Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/cli The CLI tool ('evmd') provides a full-feature interface for interacting with the blockchain. This includes commands for node operations, key management, querying blockchain state, submitting transactions, and more. **Node Requirements** To use the `query` and `tx` commands, your `evmd` node must either: * Be fully synced with the network you're interacting with, OR * Be configured to use an external RPC endpoint in `~/.evmd/config/client.toml` Example client.toml configuration: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # The network chain ID chain-id = "myapp-1" # The keyring's backend keyring-backend = "os" # CLI output format output = "text" # : to CometBFT RPC interface for this chain node = "tcp://localhost:26657" # Transaction broadcasting mode (sync|async) broadcast-mode = "sync" ``` To use an external RPC, update the `node` field to point to a public or private RPC endpoint. ## Global Flags These flags are available for all commands: | Flag | Description | Default | | ---------------------- | ---------------------------------------------------------------------------- | ----------------------- | | `-b, --broadcast-mode` | Transaction broadcasting mode (sync\|async) | `sync` | | `--chain-id` | Specify Chain ID for sending Tx | | | `--fees` | Fees to pay along with transaction (e.g., 10atest) | | | `--from` | Name or address of private key with which to sign | | | `--gas-adjustment` | Adjustment factor to multiply against the estimate returned by tx simulation | `1` | | `--gas-prices` | Gas prices to determine the transaction fee (e.g., 10atest) | | | `--home` | Directory for config and data | `~/.evmd` | | `--keyring-backend` | Select keyring's backend | `os` | | `--log_format` | The logging format (json\|plain) | `plain` | | `--log_level` | The logging level | `info` | | `--log_no_color` | Disable colored logs | | | `--node` | \:\ to CometBFT RPC interface | `tcp://localhost:26657` | | `--trace` | Print out full stack trace on errors | | ## Commands ### Starting the Node ```bash Basic theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd start ``` ```bash "With JSON-RPC" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd start \ --json-rpc.enable \ --json-rpc.api eth,net,web3,txpool ``` ```bash "Full Configuration" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd start \ --json-rpc.enable \ --json-rpc.address 0.0.0.0:8545 \ --json-rpc.ws-address 0.0.0.0:8546 \ --json-rpc.api eth,net,web3,txpool,debug \ --json-rpc.enable-indexer \ --json-rpc.gas-cap 50000000 ``` ### Node Initialization ```bash "Basic Init" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd init my-node --chain-id cosmos-evm-1 ``` ```bash "Custom Settings" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd init my-validator \ --chain-id cosmos-evm-1 \ --default-denom atest \ --initial-height 1 ``` ```bash "Overwrite Existing" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd init my-node \ --chain-id cosmos-evm-1 \ --overwrite ``` ### Node Status ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd status ``` ### Transaction Indexing ```bash "Index Forward" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd index-eth-tx forward ``` ```bash "Index Backward" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd index-eth-tx backward ``` ### Account Management ```bash "Create New Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys add my-account ``` ```bash "Recover from Mnemonic" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys add my-account --recover ``` ```bash "Create ETH Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys add my-eth-account \ --algo eth_secp256k1 \ --coin-type 60 ``` ### Key Operations ```bash "List All Keys" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys list ``` ```bash "Show Key Info" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys show my-account ``` ```bash "Show Address Only" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys show my-account --address ``` ```bash "Export Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys export my-account ``` ```bash "Import Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys import new-account keyfile.json ``` ### Ethereum Key Import/Export ```bash "Export ETH Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys unsafe-export-eth-key my-eth-account ``` ```bash "Import ETH Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd keys unsafe-import-eth-key imported-account 0x... ``` ### Account & Balance Queries ```bash "Account Info" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm account 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 ``` ```bash "Bank Balance" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm balance-bank 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 atest ``` ```bash "ERC20 Balance" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm balance-erc20 \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ``` ### Contract Queries ```bash "Get Contract Code" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm code 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ``` ```bash "Get Storage Value" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm storage \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ 0x0000000000000000000000000000000000000000000000000000000000000001 ``` ### Configuration & Parameters ```bash "EVM Config" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm config ``` ```bash "EVM Params" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm params ``` ### Address Conversion ```bash "0x to Bech32" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm 0x-to-bech32 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 ``` ```bash "Bech32 to 0x" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query evm bech32-to-0x cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s ``` ### Token Pair Queries ```bash "All Token Pairs" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query erc20 token-pairs ``` ```bash "Specific Token Pair" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query erc20 token-pair 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ``` ```bash "Query by Denom" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query erc20 token-pair usdc ``` ```bash "Module Parameters" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query erc20 params ``` ### Fee Market Information ```bash "Current Base Fee" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query feemarket base-fee ``` ```bash "Block Gas Usage" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query feemarket block-gas ``` ```bash "Module Parameters" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query feemarket params ``` ### Precision Banking ```bash "Module Remainder" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query precisebank remainder ``` ```bash "Fractional Balance" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query precisebank fractional-balance cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s ``` ### Bank Module ```bash "All Balances" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query bank balances cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s ``` ```bash "Specific Denom" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query bank balance cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s atest ``` ```bash "Total Supply" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query bank total ``` ### Staking Module ```bash "All Validators" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query staking validators ``` ```bash "Delegations" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query staking delegations cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s ``` ```bash "Unbonding" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query staking unbonding-delegations cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s ``` ### Distribution Module ```bash "Delegation Rewards" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query distribution rewards \ cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s \ cosmosvaloper1xyz... ``` ```bash "Validator Commission" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query distribution commission cosmosvaloper1xyz... ``` ### Governance Module ```bash "All Proposals" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query gov proposals ``` ```bash "Specific Proposal" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query gov proposal 1 ``` ```bash "Proposal Votes" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query gov votes 1 ``` ### EVM Transfers ```bash "Send Transaction" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx evm send \ my-account \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 \ 1000000atest \ --gas-prices 10atest ``` ```bash "Raw Transaction" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx evm raw \ 0xf86c0485... \ --from my-account ``` ### Token Conversions ```bash "Coin to ERC20" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx erc20 convert-coin \ 1000000atest \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 \ --from my-account \ --gas-prices 10atest ``` ```bash "ERC20 to Coin" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx erc20 convert-erc20 \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ 1000000 \ cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s \ --from my-account ``` ### Governance Operations ```bash "Register ERC20" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx erc20 register-erc20 \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ --from validator ``` ```bash "Toggle Conversion" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx erc20 toggle-conversion \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ --from validator ``` ### Bank Transactions ```bash "Send Tokens" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx bank send \ my-account \ cosmos1abc... \ 1000000atest \ --gas-prices 10atest ``` ```bash "Multi-Send" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx bank multi-send \ my-account \ cosmos1abc... 500000atest \ cosmos1xyz... 500000atest \ --gas-prices 10atest ``` ### Staking Transactions ```bash "Delegate" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx staking delegate \ cosmosvaloper1abc... \ 1000000atest \ --from my-account \ --gas-prices 10atest ``` ```bash "Unbond" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx staking unbond \ cosmosvaloper1abc... \ 1000000atest \ --from my-account ``` ```bash "Redelegate" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx staking redelegate \ cosmosvaloper1abc... \ cosmosvaloper1xyz... \ 1000000atest \ --from my-account ``` ### Governance Transactions ```bash "Submit Proposal" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx gov submit-proposal \ --title="Upgrade Proposal" \ --description="Upgrade to v2.0.0" \ --type="Text" \ --deposit="1000000atest" \ --from my-account ``` ```bash "Vote" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx gov vote 1 yes \ --from my-account \ --gas-prices 10atest ``` ```bash "Deposit" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx gov deposit 1 1000000atest \ --from my-account ``` ### Genesis Operations ```bash "Add Account" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd genesis add-genesis-account \ cosmos1abc... \ 1000000000atest ``` ```bash "Create GenTx" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd genesis gentx \ my-validator \ 1000000atest \ --chain-id cosmos-evm-1 ``` ```bash "Collect GenTxs" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd genesis collect-gentxs ``` ### CometBFT Commands ```bash "Show Node ID" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd comet show-node-id ``` ```bash "Show Validator" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd comet show-validator ``` ```bash "Reset State" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd comet unsafe-reset-all ``` ### Debug Utilities ```bash "Address Info" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd debug addr cosmos1abc... ``` ```bash "Decode Hex" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd debug raw-bytes 0xdeadbeef ``` ```bash "Public Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd debug pubkey '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"...."}' ``` ## Examples ### Complete Workflows ```bash "Create Account" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Create new Cosmos account evmd keys add my-account # Save mnemonic safely! # Address will be shown as cosmos1... ``` ```bash "Import ETH Key" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Import existing Ethereum private key evmd keys unsafe-import-eth-key \ eth-account \ 0x1234567890abcdef... # Show the imported account evmd keys show eth-account ``` ```bash "List & Export" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # List all accounts evmd keys list # Export account (encrypted) evmd keys export my-account > account.backup # Show account address only evmd keys show my-account --address ``` ```bash "Account Info" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query Cosmos account evmd query bank balances \ cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s # Query EVM account evmd query evm account \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 ``` ```bash "Token Balances" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Native token balance evmd query bank balance \ cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s \ atest # ERC20 token balance evmd query evm balance-erc20 \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ``` ```bash "Network State" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Current base fee evmd query feemarket base-fee # Node sync status evmd status | jq .sync_info # Latest block height evmd status | jq .sync_info.latest_block_height ``` ```bash "Native Transfer" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Send tokens via bank module evmd tx bank send \ my-account \ cosmos1abc... \ 1000000atest \ --gas-prices 10atest \ --gas-adjustment 1.5 ``` ```bash "EVM Transfer" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Send via EVM module evmd tx evm send \ my-account \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 \ 1000000atest \ --gas-prices 10atest ``` ```bash "Token Conversion" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Convert native to ERC20 evmd tx erc20 convert-coin \ 1000000atest \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb81 \ --from my-account \ --gas-prices 10atest # Convert ERC20 to native evmd tx erc20 convert-erc20 \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ 1000000 \ cosmos1wsk4trnzfszs55jlt5ugz76lwl2hp04slh5s5s \ --from my-account ``` ## Configuration * Configuration directory: `~/.evmd/` * Key storage: Managed by the keyring backend (os, file, test) * Node configuration: `~/.evmd/config/config.toml` * App configuration: `~/.evmd/config/app.toml` If you use the `--home` flag upon initializing the light client, the root/config directory will be generated there # ERC20 Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/modules/erc20 ERC-20 token representation and conversion for native Cosmos tokens The ERC20 module (`x/erc20`) implements Single Token Representation v2 (STRv2), ensuring seamless interoperability between Cosmos coins and EVM ERC-20 tokens. It automatically creates ERC-20 representations for IBC tokens and maintains bidirectional conversion. ## Module Overview **Purpose**: Provide unified token representation across Cosmos and EVM environments **Key Functionality**: * Automatic ERC-20 wrapper creation for Cosmos coins and IBC tokens * Bidirectional token conversion (Cosmos EVM) * Native token precompiles at deterministic addresses * Dynamic precompile generation for IBC tokens * ERC-20 allowance tracking for cross-environment compatibility * Single token representation prevents fragmentation **Source Code**: [x/erc20](https://github.com/cosmos/evm/tree/main/x/erc20) **Parameter Defaults**: [x/erc20/types/params.go](https://github.com/cosmos/evm/blob/main/x/erc20/types/params.go#L24-L29) *** ## Single Token Representation v2 (STRv2) ### Problem Solved Without STRv2, tokens exist in separate "worlds": * **Cosmos side**: Bank module tracks `ibc/ABC...` denominations * **EVM side**: Separate ERC-20 contracts with different addresses * **Result**: Token fragmentation, broken liquidity, complex accounting ### STRv2 Solution STRv2 ensures a single token representation: 1. **IBC token arrives** → Automatically gets ERC-20 wrapper at deterministic address 2. **User wants EVM access** → Convert bank balance to ERC-20 (same total supply) 3. **User wants Cosmos access** → Convert ERC-20 back to bank balance 4. **Result**: Same token, accessible from both environments, unified liquidity **Implementation**: [token\_pair.go:18-29](https://github.com/cosmos/evm/blob/main/x/erc20/types/token_pair.go#L18-L29) *** ## Configuration Methods The ERC20 module is configured through genesis.json before chain launch. ### Method 1: Direct JSON Editing Edit `~/.evmd/config/genesis.json` directly: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "erc20": { "params": { "enable_erc20": true, "permissionless_registration": true }, "token_pairs": [ { "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "denom": "atest", "enabled": true, "contract_owner": 1 } ], "native_precompiles": ["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"], "dynamic_precompiles": [], "allowances": [] } } } ``` ### Method 2: Using jq Command-Line Tool From [local\_node.sh:247-248](https://github.com/cosmos/evm/blob/main/local_node.sh#L247-L248): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} GENESIS="$HOME/.evmd/config/genesis.json" TMP="$HOME/.evmd/config/tmp_genesis.json" # Set native precompile for native token jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \ "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" # Create token pair for native token jq '.app_state.erc20.token_pairs=[{ contract_owner:1, erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", denom:"atest", enabled:true }]' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" ``` *** ## Parameters ### enable\_erc20 **What It Does**: Master switch to enable/disable the entire ERC20 module functionality. **Type**: `bool` **Valid Values**: * `true` - Enable ERC-20 conversions and token pairs (recommended) * `false` - Disable all ERC-20 functionality **Default**: `true` ([params.go:26](https://github.com/cosmos/evm/blob/main/x/erc20/types/params.go#L26)) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "erc20": { "params": { "enable_erc20": true } } } ``` **Impact**: * When `true`: Users can convert between Cosmos and ERC-20 representations * When `false`: All conversions disabled, tokens stay in their native format * Cannot be changed after genesis without governance proposal **Use Cases**: * `true` - Standard for EVM-compatible chains * `false` - Pure Cosmos chain without EVM token bridging *** ### permissionless\_registration **What It Does**: Controls who can register new token pairs (create ERC-20 wrappers for Cosmos tokens). **Type**: `bool` **Valid Values**: * `true` - Anyone can register token pairs (recommended for public chains) * `false` - Only governance can register token pairs (permissioned) **Default**: `true` ([params.go:27](https://github.com/cosmos/evm/blob/main/x/erc20/types/params.go#L27)) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "erc20": { "params": { "permissionless_registration": true } } } ``` **Impact**: **When `true` (permissionless)**: * Any user can call `RegisterCoin` or `RegisterERC20` messages * IBC tokens automatically get ERC-20 wrappers * Fastest UX for new tokens * Standard Ethereum-like behavior **When `false` (permissioned)**: * Only governance proposals can create token pairs * More control over which tokens get ERC-20 representations * Slower onboarding for new tokens * Useful for curated token lists **Recommendation**: Use `true` for public chains to match Ethereum UX *** ## Genesis State ### token\_pairs **What It Does**: Defines the mappings between Cosmos denominations and ERC-20 contract addresses. **Type**: Array of `TokenPair` objects **Structure**: ([token\_pair.go:32-39](https://github.com/cosmos/evm/blob/main/x/erc20/types/token_pair.go#L32-L39)) ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message TokenPair { string erc20_address = 1; // ERC-20 contract address (hex) string denom = 2; // Cosmos denomination bool enabled = 3; // Whether conversions are active Owner contract_owner = 4; // Who owns the ERC-20 contract } ``` **Contract Owner Types**: * `OWNER_MODULE = 1` - Owned by erc20 module (native Cosmos token) * `OWNER_EXTERNAL = 2` - Owned by external EOA (native ERC-20 token) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq (from local_node.sh:248) jq '.app_state.erc20.token_pairs=[{ contract_owner:1, erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", denom:"atest", enabled:true }]' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "erc20": { "token_pairs": [ { "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "denom": "atest", "enabled": true, "contract_owner": 1 } ] } } ``` **Special Address**: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` - Standard address for native token (matches Ethereum convention) **Validation**: ([genesis.go:30-46](https://github.com/cosmos/evm/blob/main/x/erc20/types/genesis.go#L30-L46)) * No duplicate ERC-20 addresses * No duplicate denominations * Valid Cosmos denom format * Valid Ethereum address format *** ### native\_precompiles **What It Does**: List of ERC-20 contract addresses that should be accessible as stateful precompiles for native Cosmos tokens. **Type**: Array of hex addresses (strings) **Purpose**: Enable EVM smart contracts to interact with native Cosmos tokens at deterministic addresses **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq (from local_node.sh:247) jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \ genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "erc20": { "native_precompiles": [ "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" ] } } ``` **Requirements**: * Each precompile address MUST have a corresponding enabled token pair ([genesis.go:54-56](https://github.com/cosmos/evm/blob/main/x/erc20/types/genesis.go#L54-L56)) * Precompile provides ERC-20 interface backed by bank module **Use Case**: Enable Solidity contracts to call `IERC20(0xEeee...).transfer()` for native token *** ### dynamic\_precompiles **What It Does**: Similar to native\_precompiles but for IBC tokens that arrive dynamically after genesis. **Type**: Array of hex addresses (strings) **Default**: `[]` (empty at genesis, populated automatically during IBC transfers) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "erc20": { "dynamic_precompiles": [] } } ``` **How It Works**: 1. IBC token arrives via `ibc/transfer` module 2. ERC20 module automatically creates token pair with deterministic address 3. Address is derived from IBC denom hash ([token\_pair.go:18-29](https://github.com/cosmos/evm/blob/main/x/erc20/types/token_pair.go#L18-L29)) 4. Dynamic precompile address added to list 5. EVM contracts can now interact with IBC token **Address Derivation**: Uses `utils.GetIBCDenomAddress(denom)` to compute deterministic address from IBC denom **Example**: * IBC denom: `ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2` * Derived ERC-20 address: `0x...` (deterministic from denom hash) *** ### allowances **What It Does**: Stores ERC-20 allowances (approve/transferFrom) that need to persist across Cosmos-EVM conversions. **Type**: Array of `Allowance` objects **Structure**: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Allowance { string erc20_address = 1; // ERC-20 contract string owner = 2; // Token owner address string spender = 3; // Approved spender address string amount = 4; // Approved amount } ``` **Default**: `[]` (empty at genesis) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "erc20": { "allowances": [ { "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "owner": "0x1234...", "spender": "0x5678...", "amount": "1000000000000000000" } ] } } ``` **Why Needed**: When users convert tokens between Cosmos/EVM, allowances must be preserved for DeFi protocols to work correctly. **Validation**: ([genesis.go:59-74](https://github.com/cosmos/evm/blob/main/x/erc20/types/genesis.go#L59-L74)) * No duplicate allowances * Allowance must reference existing token pair * Valid addresses and amounts *** ## Complete Configuration Example Based on [local\_node.sh:247-248](https://github.com/cosmos/evm/blob/main/local_node.sh#L247-L248): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} #!/bin/bash GENESIS="$HOME/.evmd/config/genesis.json" TMP="$HOME/.evmd/config/tmp_genesis.json" # Configure ERC20 module for native token (atest) jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \ "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" jq '.app_state.erc20.token_pairs=[{ contract_owner:1, erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", denom:"atest", enabled:true }]' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" # Validate genesis evmd genesis validate-genesis --home "$HOME/.evmd" ``` Or in genesis.json directly: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "erc20": { "params": { "enable_erc20": true, "permissionless_registration": true }, "token_pairs": [ { "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "denom": "atest", "enabled": true, "contract_owner": 1 } ], "native_precompiles": [ "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" ], "dynamic_precompiles": [], "allowances": [] } } } ``` *** ## Runtime Operations ### Query Token Pairs ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # List all token pairs evmd query erc20 token-pairs # Query specific token pair by denom evmd query erc20 token-pair atest # Query specific token pair by ERC-20 address evmd query erc20 token-pair-by-address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ``` ### Convert Tokens ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Convert Cosmos coin to ERC-20 evmd tx erc20 convert-coin 1000000000000000000atest 0xRecipientAddress \ --from mykey --chain-id mychain-1 # Convert ERC-20 back to Cosmos coin evmd tx erc20 convert-erc20 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \ 1000000000000000000 cosmos1recipient... --from mykey --chain-id mychain-1 ``` ### Register New Token Pair ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Register Cosmos coin (if permissionless_registration=true) evmd tx erc20 register-coin ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 \ --from mykey --chain-id mychain-1 # Register ERC-20 token (if permissionless_registration=true) evmd tx erc20 register-erc20 0xTokenAddress --from mykey --chain-id mychain-1 ``` *** ## EVM Integration ### Using Native Token in Solidity ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract MyDeFiProtocol { // Native token precompile address IERC20 constant NATIVE_TOKEN = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); function depositNative(uint256 amount) external { // Transfer native Cosmos token (atest) using ERC-20 interface NATIVE_TOKEN.transferFrom(msg.sender, address(this), amount); // ... DeFi logic ... } function withdrawNative(uint256 amount) external { // Transfer back to user NATIVE_TOKEN.transfer(msg.sender, amount); } } ``` ### Using IBC Token in Solidity ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // IBC token addresses are deterministic from denom // Query the address: evmd query erc20 token-pair ibc/ABC... IERC20 ibcToken = IERC20(0xDeterministicAddressFromIBCDenom); ibcToken.transfer(recipient, amount); ``` *** ## Common Issues and Solutions ### Issue: Token Pair Not Found **Symptom**: `error: token pair not found for denom X` **Cause**: No ERC-20 wrapper exists for the Cosmos token **Solution**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # If permissionless_registration=true: evmd tx erc20 register-coin --from mykey # If permissionless_registration=false: # Submit governance proposal to register token pair ``` ### Issue: Conversion Disabled **Symptom**: `error: token pair is not enabled` **Cause**: Token pair exists but `enabled=false` **Solution**: Submit governance proposal to enable token pair ### Issue: Precompile Not Accessible **Symptom**: EVM calls to precompile address fail or return no code **Cause**: Address not in `native_precompiles` or `dynamic_precompiles` **Solution**: * For native tokens: Add to `native_precompiles` in genesis * For IBC tokens: Ensure token pair exists and is enabled ### Issue: IBC Token No ERC-20 Address **Symptom**: IBC token arrives but no ERC-20 wrapper created **Cause**: `enable_erc20=false` or module not properly initialized **Solution**: * Check `enable_erc20` parameter is `true` * Check module is in app.go and BeginBlockers/EndBlockers *** ## Related Documentation * [Building Your Chain Guide](/evm/latest/documentation/getting-started/build-a-chain/overview) - Main configuration walkthrough * [VM Module](/evm/latest/documentation/cosmos-sdk/modules/vm) - EVM configuration * [IBC Module](/evm/latest/documentation/cosmos-sdk/modules/ibc) - IBC token handling *** ## Source Code References * **Module Implementation**: [x/erc20](https://github.com/cosmos/evm/tree/main/x/erc20) * **Parameter Types**: [x/erc20/types/params.go](https://github.com/cosmos/evm/blob/main/x/erc20/types/params.go) * **Genesis State**: [x/erc20/types/genesis.go](https://github.com/cosmos/evm/blob/main/x/erc20/types/genesis.go) * **Token Pair Logic**: [x/erc20/types/token\_pair.go](https://github.com/cosmos/evm/blob/main/x/erc20/types/token_pair.go) * **IBC Callbacks**: [x/erc20/keeper/ibc\_callbacks.go](https://github.com/cosmos/evm/blob/main/x/erc20/keeper/ibc_callbacks.go) * **Precompile Management**: [x/erc20/keeper/precompiles.go](https://github.com/cosmos/evm/blob/main/x/erc20/keeper/precompiles.go) * **Genesis Setup**: [local\_node.sh:247-248](https://github.com/cosmos/evm/blob/main/local_node.sh#L247-L248) * **ERC-20 Bytecode**: [x/erc20/types/constants.go:8](https://github.com/cosmos/evm/blob/main/x/erc20/types/constants.go#L8) # Fee Market Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/modules/feemarket EIP-1559 dynamic fee pricing for EVM transactions The Fee Market module (`x/feemarket`) implements EIP-1559 dynamic fee pricing, enabling base fee adjustment based on block utilization. This provides better fee predictability and network congestion management compared to static gas pricing. ## Module Overview **Purpose**: Implement EIP-1559 dynamic fee market mechanism for EVM transactions **Key Functionality**: * Dynamic base fee calculation based on block gas usage * EIP-1559 base fee per gas with automatic adjustment * Priority fee (tip) support for transaction ordering * Minimum gas price enforcement * Configurable elasticity for block gas limits * Fee burning mechanism (base fee is burned) **Source Code**: [x/feemarket](https://github.com/cosmos/evm/tree/main/x/feemarket) **Parameter Defaults**: [x/feemarket/types/params.go](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go) *** ## EIP-1559 Overview EIP-1559 introduces a dynamic fee structure: **Transaction Fee Components**: * **Base Fee**: Algorithmically determined fee per gas unit (burned) * **Priority Fee (Tip)**: Optional additional fee paid to validators * **Max Fee**: Maximum fee per gas the user is willing to pay **Fee Calculation**: ``` effective_gas_price = base_fee + priority_fee total_fee = gas_used * effective_gas_price ``` **Base Fee Adjustment**: * Block > target gas: Base fee increases (up to 12.5% per block with default settings) * Block \< target gas: Base fee decreases (down to 12.5% per block) * Block = target gas: Base fee remains constant **Benefits**: * Predictable fees for users (base fee visible before transaction) * Automatic congestion response (fees rise when busy, fall when quiet) * MEV reduction (base fee not capturable by validators) * Better UX (wallets can show reliable fee estimates) *** ## Configuration Methods The Fee Market module is configured through genesis.json before chain launch. Fee market parameters have defaults and typically don't require modification in local\_node.sh unless testing specific scenarios. ### Method 1: Direct JSON Editing Edit `~/.evmd/config/genesis.json` directly: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "feemarket": { "params": { "no_base_fee": false, "base_fee": "1000000000", "base_fee_change_denominator": 8, "elasticity_multiplier": 2, "enable_height": 0, "min_gas_price": "0", "min_gas_multiplier": "0.5" } } } } ``` ### Method 2: Using jq Command-Line Tool Programmatically modify genesis using jq: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} GENESIS="$HOME/.evmd/config/genesis.json" TMP_GENESIS="$HOME/.evmd/config/tmp_genesis.json" # Disable EIP-1559 (use fixed gas pricing) jq '.app_state["feemarket"]["params"]["no_base_fee"]=true' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Set initial base fee (1 gwei) jq '.app_state["feemarket"]["params"]["base_fee"]="1000000000"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Adjust base fee change speed (slower adjustment) jq '.app_state["feemarket"]["params"]["base_fee_change_denominator"]=50' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Set minimum gas price floor (0.5 gwei) jq '.app_state["feemarket"]["params"]["min_gas_price"]="500000000"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" ``` ### Method 3: Runtime Configuration Fee market behavior can also be influenced at node startup via app.toml: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # In app.toml - sets node-level minimum (separate from consensus param) minimum-gas-prices = "1000000000atest" # 1 gwei minimum ``` **Note**: The `minimum-gas-prices` in app.toml is a node-level filter. Genesis params are consensus-level and apply chain-wide. *** ## Parameters ### no\_base\_fee **What It Does**: Completely disables the EIP-1559 dynamic base fee mechanism. When true, the chain uses only `min_gas_price` as a fixed minimum fee. **Type**: `bool` **Valid Values**: * `false` - Enable EIP-1559 dynamic base fee (recommended, Ethereum-compatible) * `true` - Disable EIP-1559, use fixed min\_gas\_price only (pre-London behavior) **Default**: `false` ([params.go:21](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L21)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - Disable EIP-1559 jq '.app_state["feemarket"]["params"]["no_base_fee"]=true' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "no_base_fee": true } } } ``` **Impact**: **When false (EIP-1559 enabled)**: * Base fee adjusts dynamically based on block utilization * Better fee predictability for users * Automatic congestion management * Base fee is burned (deflationary pressure) * Standard Ethereum behavior **When true (EIP-1559 disabled)**: * Simple fixed minimum gas price * Easier to understand for some users * No dynamic adjustment to congestion * All fees go to validators (no burning) * Pre-London Ethereum behavior **Use Cases**: * `false` - Recommended for production chains, Ethereum compatibility * `true` - Simplified fee model for private chains or specific use cases **Compatibility Note**: Setting to `true` breaks Ethereum EIP-1559 transaction compatibility. Modern wallets expect dynamic fees. *** ### base\_fee **What It Does**: Sets the initial base fee per gas in wei at genesis. This is the starting point for EIP-1559 base fee algorithm. **Type**: `string` (decimal number representing wei) **Valid Values**: Any non-negative decimal value in wei **Default**: `"1000000000"` (1 gwei, [params.go:13](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L13)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - Set to 10 gwei jq '.app_state["feemarket"]["params"]["base_fee"]="10000000000"' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "base_fee": "10000000000" } } } ``` **Common Values**: * `"100000000"` (0.1 gwei) - Very low for testnets or L2s * `"1000000000"` (1 gwei) - Default, good starting point * `"10000000000"` (10 gwei) - Higher initial fee for expected high demand * `"100000000000"` (100 gwei) - Very high for mainnet launch with known demand **Impact**: * Sets initial transaction costs at chain launch * Adjusts up/down automatically after genesis based on block utilization * Should align with expected network usage and token economics * Too low: May cause congestion at launch * Too high: May deter early adoption **Recommendations**: * **Testnet**: 0.1-1 gwei (low cost for testing) * **L2/App-specific**: 0.1-1 gwei (optimize for low fees) * **General Purpose L1**: 1-10 gwei (balanced) * **High Demand Launch**: 10-100 gwei (prevent spam) **Validation**: Must be non-negative ([params.go:66-68](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L66-L68)) **Related**: Works with `enable_height` to delay EIP-1559 activation *** ### base\_fee\_change\_denominator **What It Does**: Controls how quickly the base fee can change per block. Acts as the denominator in the base fee change calculation. **Type**: `uint32` **Valid Values**: Any positive integer (cannot be 0) **Default**: `8` (standard EIP-1559, from go-ethereum params, [params.go:51](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L51)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - Slower adjustment (±2% per block) jq '.app_state["feemarket"]["params"]["base_fee_change_denominator"]=50' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "base_fee_change_denominator": 8 } } } ``` **How It Works**: The maximum base fee change per block is calculated as: ``` max_change_per_block = base_fee / base_fee_change_denominator ``` **Common Values**: | Value | Max Change/Block | Description | | ----- | ---------------- | ------------------------------------------- | | `4` | ±25% | Fast adjustment (2x change in \~3 blocks) | | `8` | ±12.5% | Standard EIP-1559 (2x change in \~6 blocks) | | `16` | ±6.25% | Slower (2x change in \~12 blocks) | | `50` | ±2% | Very slow (2x change in \~35 blocks) | | `100` | ±1% | Extremely slow (2x change in \~70 blocks) | **Impact**: **Lower Denominator (faster adjustment)**: * Pros: Responds quickly to sudden traffic spikes, less predictable attack surface * Cons: More volatile fees, harder to predict costs, potential for rapid increases **Higher Denominator (slower adjustment)**: * Pros: More stable fees, better predictability, smoother UX * Cons: Slower response to congestion, takes longer to reach equilibrium **Recommendations**: * **Standard chains**: `8` (matches Ethereum mainnet) * **High-throughput chains**: `4-8` (faster response to varying load) * **Stable fee chains**: `16-50` (prioritize predictability) * **Private networks**: `100+` (minimal fee variation) **Example Scenarios**: With `denominator=8`, starting from 10 gwei base fee: * Block 100% full: Next base fee = 11.25 gwei (+12.5%) * Block 50% full (target): Next base fee = 10 gwei (unchanged) * Block 0% full: Next base fee = 8.75 gwei (-12.5%) With `denominator=50`, same conditions: * Block 100% full: Next base fee = 10.2 gwei (+2%) * Block 0% full: Next base fee = 9.8 gwei (-2%) **Validation**: Cannot be 0 ([params.go:62-64](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L62-L64)) **Related**: Works with `elasticity_multiplier` to determine target gas usage *** ### elasticity\_multiplier **What It Does**: Defines the maximum block gas limit as a multiple of target gas. Allows blocks to temporarily exceed target during high demand. **Type**: `uint32` **Valid Values**: Any positive integer (cannot be 0) **Default**: `2` (standard EIP-1559, from go-ethereum params, [params.go:52](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L52)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - More elastic (3x target) jq '.app_state["feemarket"]["params"]["elasticity_multiplier"]=3' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "elasticity_multiplier": 2 } } } ``` **How It Works**: ``` target_gas = block_gas_limit / elasticity_multiplier max_block_gas = target_gas * elasticity_multiplier ``` With elasticity\_multiplier = 2: * If block gas limit is 30M gas * Target gas per block = 15M gas * Maximum gas per block = 30M gas (2x target) **Base Fee Adjustment Logic**: * Block gas used > target gas → Base fee increases * Block gas used = target gas → Base fee stays constant * Block gas used \< target gas → Base fee decreases **Common Values**: | Value | Description | Use Case | | ----- | ------------------------------------- | --------------------------------------- | | `1` | No elasticity (hard limit = target) | Strictly controlled throughput | | `2` | Standard EIP-1559 (2x burst capacity) | Balanced flexibility (Ethereum mainnet) | | `3` | Higher elasticity (3x burst capacity) | High-variance workloads | | `4+` | Very elastic | Extreme burst tolerance | **Impact**: **Lower Multiplier (1-2)**: * Pros: More predictable block sizes, easier to provision resources * Cons: Less burst capacity, may reject transactions during spikes **Higher Multiplier (3-4+)**: * Pros: Better handles traffic spikes, fewer transaction rejections * Cons: More variable block sizes, requires higher validator specs, longer block times **Example with multiplier=2**: * Block gas limit: 30M gas * Target: 15M gas * If block uses 20M gas (above target): Base fee increases * If block uses 10M gas (below target): Base fee decreases * If block uses 15M gas (at target): Base fee unchanged **Recommendations**: * **Standard chains**: `2` (matches Ethereum mainnet) * **High-throughput chains**: `2-3` (handle variable load) * **Resource-constrained chains**: `1-2` (predictable requirements) * **Burst-heavy chains**: `3-4` (accommodate spikes) **Validation**: Cannot be 0 ([params.go:74-76](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L74-L76)) **Related**: Interacts with consensus param `block.max_gas` which sets the absolute maximum *** ### enable\_height **What It Does**: Block height at which EIP-1559 base fee mechanism activates. Useful for coordinated mainnet upgrades to enable EIP-1559 after launch. **Type**: `int64` **Valid Values**: Any non-negative integer **Default**: `0` (enabled from genesis, [params.go:19](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L19)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - Enable EIP-1559 at block 100000 jq '.app_state["feemarket"]["params"]["enable_height"]=100000' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "enable_height": 0 } } } ``` **Common Values**: * `0` - Enable from genesis (recommended for new chains) * `100000` - Enable at specific future block height * `-1` or very large number - Effectively disabled (use `no_base_fee` instead) **Impact**: **Before enable\_height**: * Base fee mechanism is inactive * Falls back to `min_gas_price` behavior * Transactions use pre-EIP-1559 gas pricing **At and after enable\_height**: * Base fee mechanism activates * Dynamic fee adjustment begins * Transactions must use EIP-1559 fee structure **Use Cases**: **New Chain (enable\_height=0)**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "enable_height": 0, "base_fee": "1000000000" } ``` * EIP-1559 active from block 1 * Recommended for new chains **Coordinated Upgrade (enable\_height=100000)**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "enable_height": 100000, "base_fee": "5000000000" } ``` * Chain launches with fixed fees * Switches to dynamic fees at block 100000 * Allows testing before enabling * All validators must be ready by block 100000 **Recommendations**: * **New chains**: Set to `0` (enable from start) * **Existing chains**: Use governance + hard fork to change (cannot be done via enable\_height after launch) * **Testing**: Use specific block height to test fee market behavior **Validation**: Cannot be negative ([params.go:70-72](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L70-L72)) **Important**: This parameter is checked at each block. Setting it in genesis affects when the module begins calculating base fees. *** ### min\_gas\_price **What It Does**: Sets a global minimum gas price floor in native token decimals. Provides an absolute minimum fee even if the dynamic base fee drops lower. **Type**: `string` (decimal value) **Valid Values**: Any non-negative decimal value **Default**: `"0"` (disabled, [params.go:17](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L17)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - Set 0.5 gwei minimum jq '.app_state["feemarket"]["params"]["min_gas_price"]="500000000"' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "min_gas_price": "500000000" } } } ``` **Common Values**: * `"0"` - No minimum floor (rely on base fee only, default) * `"1000000000"` - 1 gwei minimum floor * `"10000000000"` - 10 gwei minimum floor * `"100000000"` - 0.1 gwei minimum for low-fee chains **How It Works**: ``` effective_minimum = max(min_gas_price, base_fee * min_gas_multiplier) if base_fee < effective_minimum: use effective_minimum else: use base_fee ``` **Impact**: **When min\_gas\_price = 0 (default)**: * Base fee can fall arbitrarily low during quiet periods * Purely market-driven pricing * May approach 0 on idle chains * Standard Ethereum behavior **When min\_gas\_price > 0**: * Prevents fees from falling below floor * Protects against spam during quiet periods * Creates fixed revenue for validators even at low usage * May discourage usage if set too high **Use Cases**: **Public Chain (min\_gas\_price=0)**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "min_gas_price": "0", "min_gas_multiplier": "0.5" } ``` * Pure EIP-1559 with no floor * Fees determined entirely by supply/demand * Recommended for Ethereum compatibility **Spam Protection (min\_gas\_price=1000000000)**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "min_gas_price": "1000000000", "min_gas_multiplier": "0" } ``` * 1 gwei absolute minimum * Prevents fee races to zero * Good for chains that want cost floor **Low-Fee L2 (min\_gas\_price=100000000)**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "min_gas_price": "100000000", "min_gas_multiplier": "0" } ``` * 0.1 gwei minimum * Cheap transactions guaranteed * Suitable for high-throughput chains **Recommendations**: * **Ethereum-compatible chains**: `"0"` (no floor) * **Anti-spam focus**: `"1000000000"` - `"10000000000"` (1-10 gwei) * **Low-fee chains**: `"100000000"` (0.1 gwei) * **Private chains**: Higher values to prevent test spam **Validation**: Must be non-negative ([params.go:89-98](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L89-L98)) **Node-Level Minimum**: Remember that nodes can also set `minimum-gas-prices` in app.toml, which filters transactions locally before consensus. *** ### min\_gas\_multiplier **What It Does**: Fraction of base fee to use as the effective minimum gas price. Creates a dynamic minimum that scales with base fee. **Type**: `string` (decimal value between 0 and 1) **Valid Values**: Decimal between `0` and `1` (inclusive) **Default**: `"0.5"` (50% of base fee, [params.go:15](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L15)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - Set to 80% of base fee jq '.app_state["feemarket"]["params"]["min_gas_multiplier"]="0.8"' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "min_gas_multiplier": "0.5" } } } ``` **How It Works**: ``` effective_minimum = max(min_gas_price, base_fee * min_gas_multiplier) ``` If base fee is 10 gwei and min\_gas\_multiplier is 0.5: * Effective minimum = 5 gwei * Transactions below 5 gwei are rejected * Even if base fee drops to 8 gwei next block, minimum becomes 4 gwei **Common Values**: | Value | Meaning | Use Case | | ------- | --------------------------- | ---------------------------- | | `"0"` | No multiplier-based minimum | Rely only on `min_gas_price` | | `"0.5"` | 50% of base fee (default) | Balanced protection | | `"0.8"` | 80% of base fee | Stricter minimum | | `"1.0"` | 100% of base fee | Force exact base fee payment | **Impact**: **Lower Multiplier (0-0.5)**: * Allows transactions with lower gas prices relative to base fee * More forgiving for users during fee spikes * May allow some low-fee spam * Better for adoption **Higher Multiplier (0.5-1.0)**: * Stricter minimum gas price requirements * Better spam protection * May frustrate users who try to pay slightly below base fee * Forces full EIP-1559 fee payment **Examples**: **Default (min\_gas\_multiplier=0.5)**: * Base fee = 10 gwei → Minimum = 5 gwei * Base fee = 100 gwei → Minimum = 50 gwei * Scales with congestion, always accepts at least 50% of base fee **Strict (min\_gas\_multiplier=1.0)**: * Base fee = 10 gwei → Minimum = 10 gwei * Forces exact base fee payment * No transactions accepted below current base fee **Permissive (min\_gas\_multiplier=0)**: * Minimum determined only by `min_gas_price` * If `min_gas_price=0`, can accept very low fees * High spam risk **Recommendations**: * **Standard chains**: `"0.5"` (default, good balance) * **High-value chains**: `"0.8"` - `"1.0"` (strict enforcement) * **Spam-prone chains**: `"0.8"` - `"1.0"` (better protection) * **Low-fee L2s**: `"0"` - `"0.3"` (maximize accessibility) **Validation**: Must be between 0 and 1 inclusive ([params.go:101-114](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go#L101-L114)) **Interaction**: Works together with `min_gas_price`. The effective minimum is the maximum of both values. *** ## Complete Configuration Examples ### Standard EIP-1559 Configuration (Recommended) Based on Ethereum mainnet defaults: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "no_base_fee": false, "base_fee": "1000000000", "base_fee_change_denominator": 8, "elasticity_multiplier": 2, "enable_height": 0, "min_gas_price": "0", "min_gas_multiplier": "0.5" } } } ``` Using jq: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} GENESIS="$HOME/.evmd/config/genesis.json" TMP="$HOME/.evmd/config/tmp_genesis.json" jq '.app_state["feemarket"]["params"]["no_base_fee"]=false' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" jq '.app_state["feemarket"]["params"]["base_fee"]="1000000000"' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" jq '.app_state["feemarket"]["params"]["base_fee_change_denominator"]=8' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" jq '.app_state["feemarket"]["params"]["elasticity_multiplier"]=2' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" jq '.app_state["feemarket"]["params"]["enable_height"]=0' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" jq '.app_state["feemarket"]["params"]["min_gas_price"]="0"' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" jq '.app_state["feemarket"]["params"]["min_gas_multiplier"]="0.5"' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS" ``` ### Low-Fee L2 Configuration Optimized for low transaction costs: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "no_base_fee": false, "base_fee": "100000000", "base_fee_change_denominator": 16, "elasticity_multiplier": 3, "enable_height": 0, "min_gas_price": "100000000", "min_gas_multiplier": "0.3" } } } ``` ### Fixed Fee Configuration (Disabled EIP-1559) Simple fixed pricing model: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "no_base_fee": true, "base_fee": "0", "base_fee_change_denominator": 8, "elasticity_multiplier": 2, "enable_height": 0, "min_gas_price": "1000000000", "min_gas_multiplier": "0" } } } ``` ### High-Stability Configuration Minimal fee volatility: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "feemarket": { "params": { "no_base_fee": false, "base_fee": "10000000000", "base_fee_change_denominator": 50, "elasticity_multiplier": 2, "enable_height": 0, "min_gas_price": "5000000000", "min_gas_multiplier": "0.8" } } } ``` *** ## Runtime Configuration (app.toml) In addition to genesis parameters, individual nodes can set minimum gas prices in app.toml: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # app.toml [json-rpc] # Node will reject transactions below this price minimum-gas-prices = "1000000000atest" # 1 gwei in atest token # Minimum priority fee for mempool ordering evm-timeout = "5s" evm-max-tx-gas-wanted = 0 ``` **Important Distinction**: * **Genesis params** (feemarket module): Consensus-level, enforced by all validators * **app.toml params**: Node-level filter, can vary per node Node-level `minimum-gas-prices` must be >= consensus-level minimum or the node will fail to produce valid blocks. *** ## Monitoring and Adjusting ### Query Current Base Fee ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query current base fee evmd query feemarket base-fee # Query all fee market params evmd query feemarket params ``` ### Via JSON-RPC ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Get current base fee via eth_gasPrice curl -X POST http://localhost:8545 -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' # Get specific block's base fee curl -X POST http://localhost:8545 -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":1}' \ | jq '.result.baseFeePerGas' ``` ### Governance Updates Fee market parameters can be updated via governance proposals after launch: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Example: Update base_fee_change_denominator to 50 (slower adjustment) evmd tx gov submit-proposal param-change proposal.json --from validator --chain-id mychain-1 # proposal.json { "title": "Slow Down Base Fee Adjustment", "description": "Reduce base fee volatility by setting denominator to 50", "changes": [ { "subspace": "feemarket", "key": "BaseFeeChangeDenominator", "value": "50" } ], "deposit": "10000000atest" } ``` *** ## Common Issues and Solutions ### Issue: Transactions Fail with "Gas Price Too Low" **Cause**: Transaction gas price \< effective minimum (base\_fee or min\_gas\_price) **Solution**: * Check current base fee: `evmd query feemarket base-fee` * Ensure wallet uses EIP-1559 transaction type * Set `maxFeePerGas` >= current base fee + desired priority fee * Check node's `minimum-gas-prices` in app.toml ### Issue: Base Fee Too Volatile **Cause**: `base_fee_change_denominator` too low (fast adjustment) **Solution**: * Increase `base_fee_change_denominator` (e.g., from 8 to 50) * Requires governance proposal to change after launch * Plan for this in genesis if predictable fees are critical ### Issue: Network Congestion Not Responding **Cause**: `base_fee_change_denominator` too high (slow adjustment) **Solution**: * Decrease `base_fee_change_denominator` for faster response * Increase `elasticity_multiplier` to allow bigger blocks during spikes * Requires governance proposal after launch ### Issue: Fees Never Decrease **Cause**: Blocks consistently above target gas **Solution**: * Check if `elasticity_multiplier` is appropriate for traffic pattern * Consider increasing block gas limit in consensus params * Evaluate if chain throughput needs scaling *** ## Related Documentation * [Building Your Chain Guide](/evm/latest/documentation/getting-started/build-a-chain/overview) - Main configuration walkthrough * [VM Module](/evm/latest/documentation/cosmos-sdk/modules/vm) - EVM configuration * [EIP-1559 Specification](https://eips.ethereum.org/EIPS/eip-1559) - Original Ethereum proposal *** ## Source Code References * **Module Implementation**: [x/feemarket](https://github.com/cosmos/evm/tree/main/x/feemarket) * **Parameter Types**: [x/feemarket/types/params.go](https://github.com/cosmos/evm/blob/main/x/feemarket/types/params.go) * **Keeper Logic**: [x/feemarket/keeper](https://github.com/cosmos/evm/tree/main/x/feemarket/keeper) * **Genesis Setup**: [local\_node.sh](https://github.com/cosmos/evm/blob/main/local_node.sh) (feemarket uses defaults) # IBC Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/modules/ibc Inter-Blockchain Communication protocol implementation with EVM callbacks The `x/ibc` module from [cosmos/evm](https://github.com/cosmos/evm) implements Inter-Blockchain Communication (IBC) protocol support with specialized EVM callback functionality for cross-chain smart contract interactions. ## Overview The IBC module extends the standard IBC protocol with EVM-specific features: * **IBC Callbacks**: Execute EVM contracts automatically during IBC packet lifecycle * **Cross-chain Contract Calls**: Enable smart contracts to interact across chains * **Packet Lifecycle Management**: Handle acknowledgments and timeouts through EVM contracts ## Components ### IBC Callbacks The EVM Callbacks module implements the EVM contractKeeper interface that interacts with ibc-go's [callbacks middleware](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/README.md), specifically for ICS-20 transfer applications. **Key Features:** * **Destination Callbacks**: Execute contracts on packet receipt (`onRecvPacket`) * **Source Callbacks**: Handle acknowledgments (`onAcknowledgePacket`) and timeouts (`onTimeoutPacket`) * **Atomic Execution**: Contract calls happen atomically with token transfers ### IBC Transfer Integration The module works closely with the ICS20 transfer application to enable: * Cross-chain token transfers to EVM contracts * Automatic contract execution with received funds * Custom calldata propagation across chains Smart contracts can initiate IBC transfers using the [ICS20 Precompile](/evm/latest/documentation/smart-contracts/precompiles/ics20), which provides the `transfer` function with memo field support for callbacks. **Address Format Limitation**: Currently, IBC transfer receiver addresses must be in bech32 format (e.g., `cosmos1...`). While sender addresses are automatically converted from hex to bech32, receiver addresses must be provided in bech32 format. Full hex address support for receivers is planned for a future release. ## Callback Types ### Destination Callbacks (`onRecvPacket`) Executed on the destination chain when a packet is received, allowing contracts to: * Receive cross-chain tokens * Execute custom logic with the received funds * Perform operations like DEX swaps or liquidity provision ### Source Callbacks (`onAcknowledgePacket` & `onTimeoutPacket`) Executed on the source chain when packet lifecycle completes, enabling contracts to: * Handle successful transfer acknowledgments * Recover funds from failed/timed out transfers * Implement retry logic for failed transfers ## Implementation Details ### Memo Format EVM callbacks use the `memo` field in ICS-20 transfers with specific JSON structure: **Destination Callback:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "dest_callback": { "address": "0x...", "gas_limit": "1000000", "calldata": "0x..." } } ``` **Source Callback:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "src_callback": { "address": "0x...", "gas_limit": "1000000" } } ``` ### Security Considerations * **Isolated Addresses**: Destination callbacks use ephemeral addresses to prevent confusion with local accounts * **Sender Validation**: Source callbacks validate that only the packet sender can set callbacks * **Gas Limits**: Callback execution is bounded by specified gas limits ## Related Documentation * [IBC Overview](/evm/latest/documentation/concepts/ibc) - IBC concepts and fundamentals * [ICS20 Precompile](/evm/latest/documentation/smart-contracts/precompiles/ics20) - Cross-chain token transfers * [Callbacks Interface](/evm/latest/documentation/smart-contracts/precompiles/callbacks) - Smart contract callback interface ## External Resources * [IBC Protocol Specification](/ibc/latest/intro) * [IBC-Go Callbacks Middleware](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/README.md) * [ICS-20 Token Transfer](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) # PreciseBank Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/modules/precisebank High-precision bank module for 18-decimal EVM token accounting The PreciseBank module (`x/precisebank`) extends the precision of the standard Cosmos SDK bank module from 6 decimals to 18 decimals, enabling full EVM compatibility while maintaining Cosmos coin integrity. This module is **required** for chains using non-18-decimal native tokens. **Big thanks to the [Kava](https://www.kava.io/) team for their valuable contributions to this module.** ## Module Overview **Purpose**: Bridge the decimal precision gap between Cosmos (typically 6 decimals) and EVM (18 decimals) **Key Functionality**: * Extends token precision without changing the base denomination * Tracks fractional balances (sub-atomic units) separate from integer balances * Maintains 1:1 backing between fractional and integer units * Transparent to users - balances appear as expected in both environments * Wraps `x/bank` to provide 18-decimal precision for `x/vm` **Source Code**: [x/precisebank](https://github.com/cosmos/evm/tree/main/x/precisebank) **Documentation**: [x/precisebank/README.md](https://github.com/cosmos/evm/blob/main/x/precisebank/README.md) *** ## When Do You Need PreciseBank? ### You NEED PreciseBank if: Your native token has **6 decimals** (or any non-18 decimal count): * Base denom: `ustake`, `utoken`, `uatom` (micro prefix = 10^6) * Display denom: `stake`, `token`, `atom` * Example: 1 STAKE = 1,000,000 ustake = 10^6 smallest units **Why**: EVM expects 18 decimals. Without PreciseBank, you lose 12 decimals of precision, causing rounding errors and broken DeFi protocols. ### You DON'T NEED PreciseBank if: Your native token has **18 decimals**: * Base denom: `atest`, `atoken` (atto prefix = 10^18) * Display denom: `test`, `token` * Example: 1 TEST = 1,000,000,000,000,000,000 atest = 10^18 smallest units **Why**: Your Cosmos denomination already matches EVM's 18-decimal expectation. Direct 1:1 mapping with no fractional tracking needed. *** ## Mathematical Foundation ### The Precision Problem **Cosmos Standard**: 6 decimal places ``` 1 ATOM = 1,000,000 uatom (10^6) Smallest unit: 0.000001 ATOM = 1 uatom ``` **EVM Standard**: 18 decimal places ``` 1 ETH = 1,000,000,000,000,000,000 wei (10^18) Smallest unit: 0.000000000000000001 ETH = 1 wei ``` **Gap**: 12 orders of magnitude (10^12) ### PreciseBank Solution PreciseBank subdivides each `uatom` into 10^12 sub-atomic units called `aatom`: ``` 1 ATOM = 1,000,000 uatom (Cosmos layer - x/bank) 1 uatom = 1,000,000,000,000 aatom (EVM layer - x/precisebank) 1 ATOM = 1,000,000,000,000,000,000 aatom (10^18 total) ``` **Key Principle**: Every `aatom` is fully backed by `uatom` in x/bank. You cannot have fractional `aatom` without corresponding integer `uatom` reserves. ### Balance Representation For any account `n`, the total balance in sub-atomic units `a(n)` is: $$ a(n) = b(n) \cdot C + f(n) $$ Where: * `a(n)` = Total aatom balance (18-decimal representation) * `b(n)` = Integer uatom balance (stored in x/bank) * `f(n)` = Fractional balance (stored in x/precisebank) * `C` = Conversion factor = 10^12 **Constraints**: ``` 0 ≤ f(n) < C a(n), b(n) ≥ 0 ``` **Derivation** (quotient-remainder theorem): ``` b(n) = ⌊a(n) / C⌋ (integer division) f(n) = a(n) mod C (remainder) ``` **Example**: ``` User has: 1,500,000,123,456,789,012 aatom b(n) = ⌊1,500,000,123,456,789,012 / 10^12⌋ = 1,500,000 uatom (in x/bank) f(n) = 1,500,000,123,456,789,012 mod 10^12 = 123,456,789,012 aatom (in x/precisebank) ``` **Source**: [README.md Background](https://github.com/cosmos/evm/blob/main/x/precisebank/README.md#background) *** ## Module Integration ### Adding to Your Chain PreciseBank requires integration in `app/app.go`: **1. Import the module**: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( precisebankkeeper "github.com/cosmos/evm/x/precisebank/keeper" precisebanktypes "github.com/cosmos/evm/x/precisebank/types" ) ``` **2. Add keeper to App struct**: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type App struct { // ... other keepers ... BankKeeper bankkeeper.Keeper PreciseBankKeeper precisebankkeeper.Keeper // ... other keepers ... } ``` **3. Initialize keeper (before VM keeper)**: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Create precisebank keeper wrapping bank keeper app.PreciseBankKeeper = precisebankkeeper.NewKeeper( appCodec, keys[precisebanktypes.StoreKey], app.BankKeeper, // Wrapped bank keeper app.AccountKeeper, ) ``` **4. Pass PreciseBankKeeper to VM module**: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // VM keeper needs precisebank for 18-decimal operations app.VMKeeper = vmkeeper.NewKeeper( appCodec, keys[vmtypes.StoreKey], app.PreciseBankKeeper, // Use precisebank instead of bank app.StakingKeeper, // ... other keepers ... ) ``` **5. Add to module manager**: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ModuleManager = module.NewManager( // ... other modules ... precisebank.NewAppModule(app.PreciseBankKeeper), // ... other modules ... ) ``` **Critical**: PreciseBank must wrap BankKeeper and be passed to VMKeeper, not BankKeeper directly. *** ## Configuration ### Genesis Configuration PreciseBank has minimal genesis configuration - it primarily tracks state, not parameters. **File Location**: `~/.evmd/config/genesis.json` under `app_state.precisebank` **Structure**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "precisebank": { "fractional_balances": [], "remainder": "0" } } } ``` ### Required VM Module Configuration When using PreciseBank, you **MUST** configure `extended_denom_options` in the VM module: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "vm": { "params": { "evm_denom": "ustake", "extended_denom_options": [ { "native_denom": "ustake", "extended_denom": "astake" } ] } } } } ``` **Explanation**: * `native_denom`: 6-decimal Cosmos denom (`ustake`) * `extended_denom`: 18-decimal EVM denom (`astake`) * Conversion: 1 ustake = 10^12 astake **Naming Pattern**: * `u` prefix (micro, 10^6) → `a` prefix (atto, 10^18): `ustake` → `astake` * Other prefixes → add `evm` prefix: `stake` → `evmstake` *** ## State ### fractional\_balances **What It Stores**: The fractional (sub-atomic) portion of each account's balance that cannot be represented as whole integer units. **Type**: Array of `FractionalBalance` objects **Structure** ([fractional\_balance.go:43-48](https://github.com/cosmos/evm/blob/main/x/precisebank/types/fractional_balance.go#L43-L48)): ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message FractionalBalance { string address = 1; // Bech32 account address string amount = 2; // Fractional amount (0 < amount < 10^12) } ``` **Validation** ([fractional\_balance.go:64-78](https://github.com/cosmos/evm/blob/main/x/precisebank/types/fractional_balance.go#L64-L78)): * Amount must be positive (`amount > 0`) * Amount must be less than conversion factor (`amount < 10^12`) * Address must be valid Bech32 **Example**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "fractional_balances": [ { "address": "cosmos1abc...", "amount": "123456789012" }, { "address": "cosmos1def...", "amount": "999999999999" } ] } ``` **Storage Key**: [keys.go:17](https://github.com/cosmos/evm/blob/main/x/precisebank/types/keys.go#L17) ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} FractionalBalancePrefix = []byte{0x01} FractionalBalanceKey(address) = address.Bytes() ``` *** ### remainder **What It Stores**: A module-level reserve balance that backs all fractional units in circulation. **Type**: Integer (sdkmath.Int) **Purpose**: Maintains invariant that total fractional balances equal the module reserve **Invariant**: $$ \text{remainder} = \sum_{n \in \mathcal{A}} f(n) $$ Where: * `remainder` = Module reserve in fractional units * $\sum f(n)$ = Sum of all account fractional balances **Why Needed**: Since fractional units aren't tracked in x/bank's total supply, this reserve account holds integer units to back them. When fractional balances sum to 10^12, one integer unit is held in reserve. **Example**: ``` Account 1 fractional: 400,000,000,000 aatom Account 2 fractional: 600,000,000,000 aatom Total fractional: 1,000,000,000,000 aatom = 1 ustake Module reserve: 1 ustake held in x/bank to back these fractional units Remainder in precisebank: 1,000,000,000,000 aatom ``` **Storage Key**: [keys.go:22](https://github.com/cosmos/evm/blob/main/x/precisebank/types/keys.go#L22) ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} RemainderBalanceKey = []byte{0x02} ``` **Source**: [remainder\_amount.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/remainder_amount.go) *** ## Operations ### Transfer When transferring fractional amounts, PreciseBank handles the complexity automatically: **Example Transfer**: Alice sends 1.5 ustake + 500 billion aatom to Bob ``` Alice initial: x/bank: 10 ustake x/precisebank: 500,000,000,000 aatom Total: 10,500,000,000,000 aatom Bob initial: x/bank: 5 ustake x/precisebank: 300,000,000,000 aatom Total: 5,300,000,000,000 aatom Transfer amount: 2,000,000,000,000 aatom = 2 ustake + 0 aatom fractional After transfer: Alice: x/bank: 8 ustake (10 - 2) x/precisebank: 500,000,000,000 aatom (unchanged - no fractional change) Total: 8,500,000,000,000 aatom Bob: x/bank: 7 ustake (5 + 2) x/precisebank: 300,000,000,000 aatom (unchanged) Total: 7,300,000,000,000 aatom ``` **Complex Transfer**: Alice sends 1,234,567,890,123 aatom to Bob ``` Transfer: 1,234,567,890,123 aatom = 1 ustake + 234,567,890,123 aatom fractional Alice: x/bank: 10 - 1 = 9 ustake x/precisebank: 500,000,000,000 - 234,567,890,123 = 265,432,109,877 aatom Total: 9,265,432,109,877 aatom Bob: x/bank: 5 + 1 = 6 ustake x/precisebank: 300,000,000,000 + 234,567,890,123 = 534,567,890,123 aatom Total: 6,534,567,890,123 aatom ``` **Source**: [send.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/send.go) *** ### Mint **Operation**: Create new fractional units ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Mint 1.5 ustake worth of fractional units (1,500,000,000,000 aatom) preciseBankKeeper.MintCoins(ctx, moduleName, coins) ``` **Process**: 1. Split amount into integer and fractional parts 2. Mint integer part via x/bank 3. Update fractional balance in x/precisebank 4. Update remainder to maintain backing invariant **Source**: [mint.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/mint.go) *** ### Burn **Operation**: Destroy fractional units ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Burn 2.3 ustake worth of fractional units (2,300,000,000,000 aatom) preciseBankKeeper.BurnCoins(ctx, moduleName, coins) ``` **Process**: 1. Split amount into integer and fractional parts 2. Burn integer part via x/bank 3. Update fractional balance in x/precisebank 4. Update remainder to maintain backing invariant **Source**: [burn.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/burn.go) *** ## Keeper Interface PreciseBank implements the full `BankKeeper` interface, making it a drop-in replacement: **Source**: [keeper.go:16](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/keeper.go#L16) ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var _ evmtypes.BankKeeper = Keeper{} ``` **Key Methods**: * `SendCoins(ctx, from, to, coins)` - Transfer with fractional precision * `MintCoins(ctx, module, coins)` - Create new fractional units * `BurnCoins(ctx, module, coins)` - Destroy fractional units * `GetBalance(ctx, addr, denom)` - Get extended balance (integer + fractional) * `SpendableCoins(ctx, addr)` - Get spendable balances with fractional precision **Passthrough Methods**: Methods not requiring fractional logic delegate directly to x/bank ([keeper.go:44-50](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/keeper.go#L44-L50)): * `GetSupply()` - Total supply * `IterateTotalSupply()` - Supply iteration *** ## Queries ### gRPC Queries **Query Fractional Balance**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query fractional balance for specific address evmd query precisebank fractional-balance cosmos1abc... --chain-id mychain-1 ``` **Query Total Fractional Balances**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Sum of all fractional balances in the system evmd query precisebank total-fractional-balances --chain-id mychain-1 ``` **Query Remainder**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query module reserve backing fractional units evmd query precisebank remainder --chain-id mychain-1 ``` **Source**: [grpc\_query.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/grpc_query.go) *** ## EVM Integration ### In Solidity Contracts From the EVM perspective, users interact with the extended denomination: ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Example { // Native token precompile (astake with 18 decimals) IERC20 constant NATIVE = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); function deposit() external payable { // User sends astake (18 decimals) // PreciseBank automatically handles conversion to/from ustake require(msg.value >= 1e18, "Minimum 1 STAKE"); // Transfer uses full 18-decimal precision // 1.5 STAKE = 1,500,000,000,000,000,000 astake NATIVE.transfer(address(this), 1.5e18); } function getBalance(address user) external view returns (uint256) { // Returns balance in astake (18 decimals) // PreciseBank computes: (b(n) * 10^12) + f(n) return NATIVE.balanceOf(user); } } ``` **Behind the Scenes**: * `transfer(recipient, 1.5e18 astake)` * PreciseBank: Transfers 1 ustake via x/bank + 500,000,000,000 aatom fractional * User sees seamless 18-decimal precision *** ## Events PreciseBank emits events for fractional balance changes: ### SendCoins Event ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "precisebank_send", "attributes": [ {"key": "from", "value": "cosmos1abc..."}, {"key": "to", "value": "cosmos1def..."}, {"key": "amount", "value": "1234567890123astake"} ] } ``` ### MintCoins Event ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "precisebank_mint", "attributes": [ {"key": "minter", "value": "evm"}, {"key": "amount", "value": "1000000000000astake"} ] } ``` ### BurnCoins Event ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "precisebank_burn", "attributes": [ {"key": "burner", "value": "evm"}, {"key": "amount", "value": "500000000000astake"} ] } ``` **Source**: [events.go](https://github.com/cosmos/evm/blob/main/x/precisebank/types/events.go) *** ## Common Issues and Solutions ### Issue: "Fractional amount exceeds conversion factor" **Symptom**: Transaction fails with fractional validation error **Cause**: Fractional balance >= 10^12 (should have been converted to integer unit) **Solution**: This indicates a bug in the keeper logic. Report to Cosmos EVM team. ### Issue: Balances Don't Match Between Cosmos/EVM **Symptom**: User sees different balance in Cosmos vs MetaMask **Cause**: * PreciseBank not integrated correctly in app.go * VM module not using PreciseBankKeeper * Missing `extended_denom_options` configuration **Solution**: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // In app.go - WRONG: app.VMKeeper = vmkeeper.NewKeeper(..., app.BankKeeper, ...) // In app.go - CORRECT: app.VMKeeper = vmkeeper.NewKeeper(..., app.PreciseBankKeeper, ...) ``` ### Issue: Chain Won't Start After Adding PreciseBank **Symptom**: Genesis validation fails **Cause**: Missing `extended_denom_options` in VM params **Solution**: Add to genesis.json: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "extended_denom_options": [{ "native_denom": "ustake", "extended_denom": "astake" }] } } } ``` ### Issue: Total Supply Mismatch **Symptom**: Sum of balances doesn't equal total supply **Cause**: Remainder not properly maintained **Solution**: Query remainder and verify: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Remainder should equal sum of all fractional balances evmd query precisebank remainder evmd query precisebank total-fractional-balances ``` *** ## Testing and Verification ### Verify Integration **1. Check module is loaded**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query precisebank params ``` **2. Query remainder (should be 0 at genesis)**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd query precisebank remainder ``` **3. Send fractional amount via EVM**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Use MetaMask or web3 to send 1.5 STAKE # Then check fractional balance: evmd query precisebank fractional-balance cosmos1abc... ``` **4. Verify invariant**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Total fractional balances should equal remainder TOTAL=$(evmd query precisebank total-fractional-balances -o json | jq -r '.total') REMAINDER=$(evmd query precisebank remainder -o json | jq -r '.remainder') [ "$TOTAL" == "$REMAINDER" ] && echo "Invariant maintained" || echo "ERROR: Invariant broken" ``` *** ## Performance Considerations **Storage**: Fractional balances add one storage entry per account with non-zero fractional amount **Gas Cost**: Fractional operations add minimal gas overhead (\~5-10% more than standard bank operations) **Scaling**: Module has been tested with millions of accounts, no performance degradation **Optimization**: Fractional balances are only created when needed. Transfers of exact integer amounts don't create fractional entries. *** ## Related Documentation * [Building Your Chain Guide](/evm/latest/documentation/getting-started/build-a-chain/overview) - Main configuration walkthrough * [VM Module](/evm/latest/documentation/cosmos-sdk/modules/vm) - extended\_denom\_options setup * [ERC20 Module](/evm/latest/documentation/cosmos-sdk/modules/erc20) - Token pair configuration *** ## Source Code References * **Module Implementation**: [x/precisebank](https://github.com/cosmos/evm/tree/main/x/precisebank) * **README (Math Background)**: [x/precisebank/README.md](https://github.com/cosmos/evm/blob/main/x/precisebank/README.md) * **Keeper**: [keeper/keeper.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/keeper.go) * **Fractional Balance Logic**: [types/fractional\_balance.go](https://github.com/cosmos/evm/blob/main/x/precisebank/types/fractional_balance.go) * **Send Operations**: [keeper/send.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/send.go) * **Mint Operations**: [keeper/mint.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/mint.go) * **Burn Operations**: [keeper/burn.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/burn.go) * **Remainder Management**: [keeper/remainder\_amount.go](https://github.com/cosmos/evm/blob/main/x/precisebank/keeper/remainder_amount.go) * **Storage Keys**: [types/keys.go](https://github.com/cosmos/evm/blob/main/x/precisebank/types/keys.go) # VM Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/modules/vm Core EVM implementation for Ethereum compatibility on Cosmos chains The VM module (`x/vm`) is the core EVM implementation that enables Ethereum compatibility on Cosmos chains. It provides the EVM runtime, state management, precompiled contracts, and transaction processing. ## Module Overview **Purpose**: Execute Ethereum smart contracts and process EVM transactions within the Cosmos SDK framework **Key Functionality**: * EVM state transitions and transaction execution * Ethereum fork activation management (Homestead, Berlin, London, Shanghai, Cancun, Prague, etc.) * Native precompiled contracts for Cosmos module access * EVM-to-Cosmos and Cosmos-to-EVM account bridging * Gas metering and fee handling * Historical state queries with configurable retention **Source Code**: [x/vm](https://github.com/cosmos/evm/tree/main/x/vm) **Parameter Defaults**: [x/vm/types/params.go](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go) *** ## Configuration Methods The VM module can be configured through genesis.json before chain launch. Here are the three primary methods: ### Method 1: Direct JSON Editing Edit `~/.evmd/config/genesis.json` directly: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "vm": { "params": { "evm_denom": "atest", "extra_eips": [3855], "active_static_precompiles": ["0x0000000000000000000000000000000000000800"], "access_control": { "create": {"access_type": 0}, "call": {"access_type": 0} } } } } } ``` ### Method 2: Using jq Command-Line Tool Programmatically modify genesis using jq (as seen in [local\_node.sh](https://github.com/cosmos/evm/blob/main/local_node.sh#L234-L249)): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Set evm_denom jq '.app_state["vm"]["params"]["evm_denom"]="atest"' genesis.json > tmp.json && mv tmp.json genesis.json # Enable all precompiles (matches x/vm/types/precompiles.go:22-32) jq '.app_state["vm"]["params"]["active_static_precompiles"]=[ "0x0000000000000000000000000000000000000100", # P256 "0x0000000000000000000000000000000000000400", # Bech32 "0x0000000000000000000000000000000000000800", # Staking "0x0000000000000000000000000000000000000801", # Distribution "0x0000000000000000000000000000000000000802", # ICS20 "0x0000000000000000000000000000000000000803", # Vesting "0x0000000000000000000000000000000000000804", # Bank "0x0000000000000000000000000000000000000805", # Governance "0x0000000000000000000000000000000000000806" # Slashing ]' genesis.json > tmp.json && mv tmp.json genesis.json # Enable extra EIP jq '.app_state["vm"]["params"]["extra_eips"]=[3855]' genesis.json > tmp.json && mv tmp.json genesis.json ``` ### Method 3: Using genesis CLI Commands Some parameters can be set through CLI commands (though most VM params require genesis.json editing): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Genesis file is created with: evmd init --chain-id # Then manually edit genesis.json for VM params # No direct CLI command for VM param modification ``` *** ## Parameters ### evm\_denom **What It Does**: Specifies which bank module denomination to use as the native EVM token (gas token). **Type**: `string` **Valid Values**: Must match a base denomination from bank metadata configuration **Default**: `"uatom"` ([params.go:21](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L21)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq jq '.app_state["vm"]["params"]["evm_denom"]="atest"' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "evm_denom": "atest" } } } ``` **Critical Requirements**: * **MUST match** `bank.denom_metadata[0].base` * **MUST match** `staking.params.bond_denom` * **MUST match** `mint.params.mint_denom` **Impact**: This is the token users pay for EVM gas, displayed in MetaMask balances, and used for all EVM operations. **Examples**: * `"atest"` - For 18 decimal token (atto prefix: 10^18) * `"ustake"` - For 6 decimal token (micro prefix: 10^6) **Common Errors**: * Mismatch with bank metadata causes EVM transactions to fail * Wrong decimal places leads to incorrect balance displays *** ### extra\_eips **What It Does**: Enables additional Ethereum Improvement Proposals beyond the default fork activations. **Type**: `[]int64` (array of EIP numbers) **Valid Values**: Any activatable EIP number **Default**: `[]` (empty - all EIPs come from chain\_config fork configuration) ([params.go:22](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L22)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq jq '.app_state["vm"]["params"]["extra_eips"]=[3855, 2929]' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "extra_eips": [3855, 2929] } } } ``` **Common EIPs**: | EIP | Description | Typical Use Case | | ---- | ----------------------------------- | ------------------------------ | | 3855 | PUSH0 instruction | Gas optimization for contracts | | 2200 | Net gas metering for SSTORE | Reduces gas costs | | 2929 | Gas cost increases for state access | Security hardening | | 3198 | BASEFEE opcode | EIP-1559 base fee queries | | 3529 | Reduction in refunds | Gas accounting changes | **Validation**: Checked against list of activatable EIPs ([params.go:182-200](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L182-L200)) **Impact**: * Enables opcodes/features not in your default fork configuration * Useful for testing upcoming Ethereum features * Can break compatibility if not carefully managed **Recommendation**: Leave empty unless you need specific EIPs for custom contracts or testing *** ### active\_static\_precompiles **What It Does**: List of precompiled contract addresses to enable for Cosmos module access from EVM. **Type**: `[]string` (array of hex addresses) **Valid Values**: Addresses from the available precompiles list ([precompiles.go:4-15](https://github.com/cosmos/evm/blob/main/x/vm/types/precompiles.go#L4-L15)) **Default**: `[]` (empty - no precompiles enabled) ([params.go:23](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L23)) **Configuration**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Using jq - Enable all precompiles (from local_node.sh:243) jq '.app_state["vm"]["params"]["active_static_precompiles"]=[ "0x0000000000000000000000000000000000000100", "0x0000000000000000000000000000000000000400", "0x0000000000000000000000000000000000000800", "0x0000000000000000000000000000000000000801", "0x0000000000000000000000000000000000000802", "0x0000000000000000000000000000000000000803", "0x0000000000000000000000000000000000000804", "0x0000000000000000000000000000000000000805", "0x0000000000000000000000000000000000000806" ]' genesis.json > tmp.json && mv tmp.json genesis.json ``` ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "active_static_precompiles": [ "0x0000000000000000000000000000000000000100", "0x0000000000000000000000000000000000000400", "0x0000000000000000000000000000000000000800" ] } } } ``` **Available Precompiles**: | Address | Name | Module | Description | | -------- | ------------ | -------------- | --------------------------------------------- | | `0x0100` | P256 | Cryptography | P256 elliptic curve operations | | `0x0400` | Bech32 | Addressing | Convert between Bech32 and hex addresses | | `0x0800` | Staking | x/staking | Delegate, undelegate, redelegate operations | | `0x0801` | Distribution | x/distribution | Claim staking rewards, set withdrawal address | | `0x0802` | ICS20 | IBC Transfer | IBC token transfers via EVM | | `0x0803` | Vesting | x/vesting | Vesting account operations | | `0x0804` | Bank | x/bank | Native Cosmos token transfers | | `0x0805` | Gov | x/gov | Submit and vote on governance proposals | | `0x0806` | Slashing | x/slashing | Query validator slashing info | **Production Recommendations**: * **Enable only needed precompiles** for security and gas efficiency * **Commonly enabled**: 0x0100 (P256), 0x0400 (Bech32), 0x0800 (Staking), 0x0804 (Bank) * **IBC chains**: Also enable 0x0802 (ICS20) * **Governance participation**: Enable 0x0805 (Gov) **Security Note**: Each enabled precompile increases attack surface. Only enable precompiles your applications will actually use. **Default evmd Example**: Enables ALL precompiles for development convenience ([local\_node.sh:243](https://github.com/cosmos/evm/blob/main/local_node.sh#L243)) *** ### evm\_channels **What It Does**: Whitelisted IBC channel IDs that the ICS20 precompile can use for token transfers. **Type**: `[]string` (array of channel IDs) **Valid Values**: Channel IDs matching format `channel-{N}` where N is a non-negative integer **Default**: `[]` (empty - no channels whitelisted) ([params.go:24](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L24)) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "evm_channels": ["channel-0", "channel-5", "channel-42"] } } } ``` **Validation**: Each channel must match regex pattern ([params.go:80-96](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L80-96)) **Impact**: * Restricts which IBC channels the ICS20 precompile (0x0802) can transfer tokens through * Empty list means ICS20 precompile cannot perform any IBC transfers * Provides security control over cross-chain token movements **When to Configure**: * Only needed if you enable ICS20 precompile (0x0802) * Set after establishing IBC connections with other chains * Update via governance when adding new IBC routes **Example Use Case**: Enable IBC transfers only to trusted chains via specific channels *** ### access\_control **What It Does**: Defines permission model for contract deployment (CREATE/CREATE2) and contract calls. **Type**: Object with `create` and `call` fields, each containing `access_type` and optional `access_control_list` **Valid Values**: * **access\_type**: `0` (Permissionless), `1` (Restricted), `2` (Permissioned) * **access\_control\_list**: Array of addresses (only used with Restricted or Permissioned) **Default**: Permissionless for both ([params.go:30-48](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L30-L48)) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "access_control": { "create": { "access_type": 2, "access_control_list": [ "0x1234567890123456789012345678901234567890", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" ] }, "call": { "access_type": 0 } } } } } ``` **Access Types**: **Type 0 - Permissionless** (Default): * Anyone can perform the operation * Standard Ethereum behavior * Recommended for public chains **Type 1 - Restricted**: * Everyone EXCEPT addresses in access\_control\_list can perform operation * Blacklist model * Useful for blocking specific malicious actors **Type 2 - Permissioned**: * ONLY addresses in access\_control\_list can perform operation * Whitelist model * Useful for private/consortium chains or phased launches **Common Configurations**: **Public Chain (Default)**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "create": {"access_type": 0}, "call": {"access_type": 0} } ``` **Permissioned Deployment, Public Usage**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "create": { "access_type": 2, "access_control_list": ["0x..."] }, "call": {"access_type": 0} } ``` **Validation**: Enforced in ([params.go:140-180](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L140-L180)) **Impact**: * Controls who can deploy contracts (important for chain security) * Controls who can call existing contracts (rarely restricted) * Can be updated via governance proposals after launch **Recommendation**: Use Type 0 (Permissionless) for public EVM chains to maintain Ethereum compatibility *** ### history\_serve\_window **What It Does**: Number of recent blocks to keep for historical EVM queries (eth\_getBlockByNumber, eth\_getLogs, etc.). **Type**: `uint64` **Valid Values**: Any non-negative integer **Default**: `8192` blocks ([params.go:50](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L50)) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "history_serve_window": 8192 } } } ``` **Impact**: **Storage**: * Larger window = more disk space required * Smaller window = less disk space usage * Each block stores EVM state diffs and receipts **Query Capability**: * Queries beyond history\_serve\_window will fail * Block explorers need sufficient history for user queries * DeFi analytics may require longer history **Performance**: * Very large windows can slow down state pruning * Affects database size and sync time **Common Values**: * `8192` - Default (roughly 11 hours at 5s blocks) * `100000` - Extended history (roughly 5.8 days at 5s blocks) * `1000000` - Full history (roughly 58 days at 5s blocks) * `0` - No history retention (not recommended for RPC nodes) **Recommendations**: * **Archive Nodes**: Set to very large number or 0 (unlimited) * **RPC Nodes**: 100,000 - 1,000,000 blocks * **Validator Nodes**: Can use default 8192 (validators don't serve RPC) **Related**: Works with EIP-2935 for historical block hash access *** ### extended\_denom\_options **What It Does**: Enables 18-decimal EVM representation for non-18-decimal Cosmos tokens. Required for 6-decimal tokens like ustake. **Type**: `[]ExtendedDenomOption` - Array of objects mapping Cosmos denoms to EVM extended denoms **Valid Values**: Each entry must have a valid denom pair following the extended denom pattern **Default**: `[]` (empty - no extended denoms) ([params.go:25](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go#L25)) **Configuration**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "vm": { "params": { "extended_denom_options": [ { "native_denom": "ustake", "extended_denom": "astake" } ] } } } ``` **When Required**: * **18 decimals**: NOT required - standard bank module works * **6 decimals**: REQUIRED - must add extended\_denom\_options * **Other decimals**: REQUIRED - must add extended\_denom\_options **Extended Denom Patterns**: * `u` prefix (micro, 10^6) → `a` prefix (atto, 10^18): `ustake` → `astake` * `n` prefix (nano, 10^9) → `a` prefix (atto, 10^18): `ntoken` → `atoken` * Any other → add `evm` prefix: `stake` → `evmstake` **How It Works**: 1. Native 6-decimal token: `ustake` (smallest unit) 2. Extended 18-decimal representation: `astake` (for EVM) 3. 1 ustake = 10^12 astake 4. PreciseBank module handles fractional conversions **Example: 6 Decimal Token**: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "vm": { "params": { "evm_denom": "ustake", "extended_denom_options": [ { "native_denom": "ustake", "extended_denom": "astake" } ] } } } } ``` **Related Configuration**: Requires [PreciseBank Module](/evm/latest/documentation/cosmos-sdk/modules/precisebank) to be included in app.go *** ## Complete Configuration Example Based on [local\_node.sh](https://github.com/cosmos/evm/blob/main/local_node.sh#L234-L249): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} #!/bin/bash GENESIS="$HOME/.evmd/config/genesis.json" TMP_GENESIS="$HOME/.evmd/config/tmp_genesis.json" # Set EVM denomination jq '.app_state["vm"]["params"]["evm_denom"]="atest"' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Enable all precompiles for development jq '.app_state["vm"]["params"]["active_static_precompiles"]=[ "0x0000000000000000000000000000000000000100", "0x0000000000000000000000000000000000000400", "0x0000000000000000000000000000000000000800", "0x0000000000000000000000000000000000000801", "0x0000000000000000000000000000000000000802", "0x0000000000000000000000000000000000000803", "0x0000000000000000000000000000000000000804", "0x0000000000000000000000000000000000000805", "0x0000000000000000000000000000000000000806" ]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" # Validate genesis evmd genesis validate-genesis --home "$HOME/.evmd" ``` Or in genesis.json directly: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "vm": { "params": { "evm_denom": "atest", "extra_eips": [], "active_static_precompiles": [ "0x0000000000000000000000000000000000000100", "0x0000000000000000000000000000000000000400", "0x0000000000000000000000000000000000000800", "0x0000000000000000000000000000000000000801", "0x0000000000000000000000000000000000000802", "0x0000000000000000000000000000000000000803", "0x0000000000000000000000000000000000000804", "0x0000000000000000000000000000000000000805", "0x0000000000000000000000000000000000000806" ], "evm_channels": [], "access_control": { "create": { "access_type": 0 }, "call": { "access_type": 0 } }, "history_serve_window": 8192, "extended_denom_options": [] }, "chain_config": { "chain_id": "9001", "homestead_block": "0", "dao_fork_block": "0", "dao_fork_support": true, "eip150_block": "0", "eip155_block": "0", "eip158_block": "0", "byzantium_block": "0", "constantinople_block": "0", "petersburg_block": "0", "istanbul_block": "0", "muir_glacier_block": "0", "berlin_block": "0", "london_block": "0", "arrow_glacier_block": "0", "gray_glacier_block": "0", "merge_netsplit_block": "0", "shanghai_time": "0", "cancun_time": "0", "prague_time": "0" } } } } ``` *** ## Related Documentation * [Building Your Chain Guide](/evm/latest/documentation/getting-started/build-a-chain/overview) - Main configuration walkthrough * [Fee Market Module](/evm/latest/documentation/cosmos-sdk/modules/feemarket) - EIP-1559 fee configuration * [local\_node.sh](https://github.com/cosmos/evm/blob/main/local_node.sh) - Reference implementation *** ## Source Code References * **Module Implementation**: [x/vm](https://github.com/cosmos/evm/tree/main/x/vm) * **Parameter Types**: [x/vm/types/params.go](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go) * **Precompile Addresses**: [x/vm/types/precompiles.go](https://github.com/cosmos/evm/blob/main/x/vm/types/precompiles.go) * **Chain Config**: [x/vm/types/chain\_config.go](https://github.com/cosmos/evm/blob/main/x/vm/types/chain_config.go) * **Genesis Setup**: [local\_node.sh](https://github.com/cosmos/evm/blob/main/local_node.sh) # Overview Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/overview Build application-specific blockchains with the modular Cosmos SDK framework. # Cosmos SDK Build application-specific blockchains with the modular Cosmos SDK framework. Create custom blockchain applications with ease using pre-built and custom modules. ## Build Without Limits The Cosmos SDK enables developers to create powerful blockchain applications: Build application-specific blockchains tailored to your exact use case with sovereign consensus and governance. Combine pre-built modules with custom logic to create sophisticated blockchain applications efficiently. Built-in IBC support enables seamless communication and value transfer across the Cosmos ecosystem. Maintain full control over the entire project through all stages of developent and production. ## Essential Modules The Cosmos SDK includes several battle-tested core modules: Token transfers, account balances, and multi-asset support with built-in safety features. Proof-of-Stake consensus, validator delegation, and slashing conditions for network security. On-chain governance with proposals, voting, and parameter changes for decentralized decision-making. Fee distribution, staking rewards, and validator commission handling with automated payouts. Account authentication, transaction signing, and account management with multi-signature support. Inter-blockchain communication protocol for cross-chain token transfers and data packets. ## Getting Started Comprehensive guides, tutorials, and API references for building with the Cosmos SDK. Visit SDK Docs → Learn how to build custom modules and integrate them into your blockchain application. Build Modules → ## Community & Resources Source code, issues, and contribution guidelines for the Cosmos SDK project. View GitHub → Join discussions with other Cosmos SDK developers and get help from the community. Join Forum → # Technical Architecture Source: https://docs.cosmos.network/evm/latest/documentation/cosmos-sdk/protocol Cosmos EVM is a framework that allows you to add Ethereum Virtual Machine (EVM) compatibility to any Cosmos SDK-based chain. Built on the CometBFT consensus engine, it provides fast finality, high transaction throughput, and short block times (~2 seconds). This architecture enables users to perform both Cosmos and EVM formatted transactions, allows developers to scale EVM dApps cross-chain via [IBC](https://cosmos.network/ibc), and supports tokens and assets from multiple independent sources. Cosmos EVM enables these key features by: * Leveraging [modules](/sdk/latest/guides/module-design/module-design-considerations) and other mechanisms implemented by the Cosmos SDK. * Implementing CometBFT's Application Blockchain Interface ([ABCI](/cometbft/latest/spec/abci/Overview)) to manage the blockchain. * Utilizing [`geth`](https://github.com/ethereum/go-ethereum) as a library to promote code reuse and improve maintainability. * Exposing a fully compatible Web3 [JSON-RPC](/evm/latest/api-reference/ethereum-json-rpc/methods) layer for interacting with existing Ethereum clients and tooling (MetaMask, Remix, etc.). These features enable developers to leverage existing Ethereum ecosystem tooling and software to seamlessly deploy smart contracts that interact with the rest of the Cosmos [ecosystem](https://cosmos.network/ecosystem). ## Cosmos SDK[​](#cosmos-sdk "Direct link to Cosmos SDK") Cosmos EVM enables the full composability and modularity of the [Cosmos SDK](/). It includes standard modules from the Cosmos SDK that work side by side with EVM-specific modules. Check out the [list of modules](/evm/latest/documentation/cosmos-sdk/modules) to get an overview of what each module is responsible for. ## CometBFT & ABCI[​](#cometbft--abci "Direct link to CometBFT & ABCI") [CometBFT](https://github.com/cometbft/cometbft) consists of two core technical components: a blockchain consensus engine and a generic application interface. The consensus engine ensures that the same transactions are recorded on every machine in the same order. The application interface, called the [Application Blockchain Interface (ABCI)](/cometbft/latest/spec/abci/Overview), enables transactions to be processed in any programming language. CometBFT has evolved into a general-purpose blockchain consensus engine that can host arbitrary application states. Since it can replicate arbitrary applications, it can be used as a plug-and-play replacement for the consensus engines of other blockchains. Cosmos EVM is an example of an ABCI application replacing Ethereum's Proof-of-Stake consensus via CometBFT's consensus engine. Another example of a cryptocurrency application built on CometBFT is the Cosmos Hub. CometBFT simplifies blockchain design by offering a simple API (the ABCI) between the application process and consensus process. ## EVM Compatibility[​](#evm-compatibility "Direct link to EVM Compatibility") Cosmos EVM enables EVM compatibility by implementing various components that together support all the EVM state transitions while ensuring the same developer experience as Ethereum: * Ethereum's transaction format as a Cosmos SDK `Tx` and `Msg` interface * Ethereum's `secp256k1` curve for the Cosmos Keyring * `StateDB` interface for state updates and queries * [JSON-RPC](/evm/latest/api-reference/ethereum-json-rpc) client for interacting with the EVM Most components are implemented in the [VM module](/evm/latest/documentation/cosmos-sdk/modules/vm). However, to achieve a seamless developer experience, some components are implemented outside of the module. To learn more about how Cosmos EVM achieves EVM compatibility as a Cosmos chain, explore the following concepts: * [Accounts](/evm/latest/documentation/concepts/accounts) * [Gas and Fees](/evm/latest/documentation/concepts/gas-and-fees) * [Token representations](/evm/latest/documentation/concepts/tokens) * [Transactions](/evm/latest/documentation/concepts/transactions) ## Contributing[​](#contributing "Direct link to Contributing") You can contribute to the Cosmos EVM open-source codebase through [issues on GitHub](https://github.com/cosmos/evm/issues) using the [Cosmos EVM Contributor Guideline](https://github.com/cosmos/evm/blob/main/CONTRIBUTING.md). # Custom Improvement Proposals Source: https://docs.cosmos.network/evm/latest/documentation/custom-improvement-proposals Cosmos EVM allows protocol developers to register custom EIP activators that modify EVM behavior. This advanced feature enables chains to enable additional Ethereum Improvement Proposals or create chain-specific EVM modifications when needed. ## Operations[​](#operations "Direct link to Operations") Operations are the base components of the Ethereum Virtual Machine (EVM) which allow the execution of the smart contract logic. When a developer builds a smart contract, the code written in Solidity, or Vyper, is not directly interpretable by the EVM. Before being able to execute the code in the blockchain, the contract has to be compiled via one of the available compilers, like [solc](https://docs.soliditylang.org/en/latest/using-the-compiler.html). The compilation converts the human-readable contract code into a sequence of operations that the virtual machine can interpret and execute to perform state transitions or query the latest committed state. These operations are called **opcodes**, and are contained in a structure called [**jump table**](https://github.com/ethereum/go-ethereum/blob/master/core/vm/jump_table.go). Each opcode is defined by specifying the logic that has to be executed when it is called inside the EVM, its relationship with the memory, and the gas cost associated with it. More specifically, an opcode is completely defined by: * `SetExecute`: update the execution logic for the opcode. * `SetConstantGas`: update the value used for the constant gas cost. * `SetDynamicGas`: update the function used to compute the dynamic gas cost. * `SetMinStack`: update the minimum number of items in the stack required to execute the operation. * `SetMaxStack`: update the maximum number of items that will be in the stack after executing the operation. * `SetMemorySize`: the memory size required by the operation. Within the Cosmos EVM framework, developers can modify any of the previous properties. ## Improvement Proposals[​](#improvement-proposals "Direct link to Improvement Proposals") Improvement proposals are the approach used by Cosmos EVM and Ethereum to modify the behavior of opcodes. They are composed of a function, which has access to the jump table to apply specific changes to operation behavior, and a name. In the context of Ethereum, these protocol changes are named Ethereum Improvement Proposals (EIPs) and are identified by a unique ID. For example, [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) is used to introduce the base fee. To allow any Cosmos EVM user to define their own specific improvements without overlapping with Cosmos EVM and Ethereum ones, each proposal is identified by a string, that is composed of the chain name and a number. For example, default Cosmos EVM improvements are associated with the string `evmos_XXXX`. This allows each chain to define their improvements without having to worry about existing or future ID clashes between different chains. Additionally, the ability to start enumeration at 0 is better for chain developers and allows having a better overview of the historical progress for each chain. Below, you will find an example of how the Cosmos EVM chain uses this functionality to modify the behavior of the `CREATE` and `CREATE2` opcodes. First, the modifier function has to be defined: ``` // Enable0000 contains the logic to modify the CREATE and CREATE2 opcodes// constant gas value.func Enable0000(jt *vm.JumpTable) { multiplier := 10 currentValCreate := jt[vm.CREATE].GetConstantGas() jt[vm.CREATE].SetConstantGas(currentValCreate * multiplier) currentValCreate2 := jt[vm.CREATE2].GetConstantGas() jt[vm.CREATE2].SetConstantGas(currentValCreate2 * multiplier)} ``` Then, the function as to be associated with a name via a custom activator: ``` cosmosEVMActivators = map[string]func(*vm.JumpTable){ "evmos_0": eips.Enable0000,} ``` ## Activation of Improvement Proposals[​](#activation-of-improvement-proposals "Direct link to Activation of Improvement Proposals") Due to continuous changes in the users' interaction with the protocol, and to introduce a safety measure along with the freedom to customize the virtual machine behavior, custom improvement proposals are not active by default. The activation of selected improvement proposals is controlled by the [EVM module's parameters](https://github.com/cosmos/evm/blob/main/proto/cosmos/evm/vm/v1/evm.proto). There are two ways of introducing the required parameter changes: 1. **Upgrade**: create a protocol upgrade handler which introduces the proposal name in the active list. 2. **Governance**: create governance proposal to add an improvement proposal to the EVM module parameters. This approach gives developers the ability to react to security issues or market conditions, while keeping the chain's participants in the loop. # EVM Compatibility Source: https://docs.cosmos.network/evm/latest/documentation/evm-compatibility Cosmos EVM provides full Ethereum compatibility for Cosmos SDK chains. Existing contracts, tools, and workflows run without modification: deploy Solidity contracts, connect MetaMask, and use the same libraries and frameworks you already know. ## Solidity Smart Contracts The Cosmos EVM smart contract development flow is identical to the flow on Ethereum. Contracts that work on Ethereum work on Cosmos EVM without code changes. The same tools, the same Solidity, the same patterns. ### Development Write Solidity using [Hardhat](https://hardhat.org), [Foundry](https://book.getfoundry.sh), or [Remix](https://remix.ethereum.org) with no custom configuration needed. Point your tooling at your chain's JSON-RPC endpoint and chain ID and you're set. See the [tooling overview](/evm/latest/documentation/getting-started/tooling-and-resources) for a full list of supported tools, and the [quick-start guide](/evm/latest/documentation/getting-started/build-a-chain/quick-start#deploy-a-solidity-contract-with-forge) for a step-by-step contract deployment walkthrough. ### Deployment [Deploy using standard Ethereum tools](/evm/latest/documentation/getting-started/build-a-chain/quick-start#deploy-a-solidity-contract-with-forge) by pointing them at your chain's RPC endpoint. Deployment transactions go through the JSON-RPC or Cosmos gRPC interface and are processed by the EVM module. Once deployed, contract bytecode is stored in chain state and callable at its Ethereum address like any other chain. ### Upgrading Cosmos EVM supports all standard EVM upgrade patterns: * **Proxy patterns (EIP-1967, Transparent Proxy, UUPS)**: Delegate calls to an implementation contract. Upgrading means swapping the implementation address in the proxy without touching the proxy's storage or address. * **Diamond pattern (EIP-2535)**: Modular contracts split into "facets" that can be added, replaced, or removed independently. The diamond routes calls to the appropriate facet, enabling granular upgrades without redeploying the whole contract. Any upgrade architecture that works on Ethereum works on Cosmos EVM. ## Supported Standards **Cosmos EVM supports all standard EVM opcodes, EIPs, and ERC token interfaces** up to the Prague hard fork. ### EIPs The following are notable supported EIPs; the full list is [much longer](https://eips.ethereum.org/all). | EIP | Name | Purpose | | --------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------- | | [EIP-155](https://eips.ethereum.org/EIPS/eip-155) | Replay Protection | Chain ID in signatures prevents cross-chain replay attacks | | [EIP-712](https://eips.ethereum.org/EIPS/eip-712) | Typed Structured Data Signing | Sign structured data, human-readable messages, and custom Cosmos transactions | | [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) | Dynamic Fees | Base fee + priority fee model; base fee is distributed to validators rather than burned | | [EIP-2535](https://eips.ethereum.org/EIPS/eip-2535) | Diamond Standard | Proxy pattern for upgradeable contracts with multiple implementation facets | | [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) | Historical Block Hashes | `BLOCKHASH` opcode provides access to up to 8192 previous block hashes (configurable) | | [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) | Account Abstraction | Smart contract wallets can pay fees, batch transactions, and use custom auth schemes | | [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) | Set Code for EOA | Temporarily assigns contract code to an EOA for sponsored transactions and complex auth | ### ERCs Any ERC standard that runs on the EVM is compatible with Cosmos EVM. The most commonly used are: * **[ERC-20](https://eips.ethereum.org/EIPS/eip-20) — Fungible Tokens:** Standard interface for currency representations with transfer, approval, and allowance mechanisms. * **[ERC-721](https://eips.ethereum.org/EIPS/eip-721) — Non-Fungible Tokens:** Standard for unique asset identifiers, digital certificates, or proof of ownership records. * **[ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) — Multi-Token Standard:** Supports both fungible and non-fungible assets in a single contract, enabling batch operations and reduced deployment costs for complex asset systems. ### Opcodes Cosmos EVM supports **all EVM opcodes up to the Prague hard fork**. For the full reference, see [evm.codes](https://www.evm.codes/). The following are **not supported**: * **[EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) — Blob-Carrying Transactions:** Blob data and L2-specific functionality. * **[EIP-4399](https://eips.ethereum.org/EIPS/eip-4399) — PREVRANDAO:** The replacement of the `DIFFICULTY` opcode with `PREVRANDAO`. ## Tooling Any tool that connects to a standard Ethereum JSON-RPC endpoint works with Cosmos EVM without custom configuration. | Tool | Category | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------ | | [Hardhat](https://hardhat.org), [Foundry](https://book.getfoundry.sh), [Remix](https://remix.ethereum.org) | Contract development | | [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts) | Smart contract libraries | | [Forge](https://book.getfoundry.sh/forge/), [Hardhat](https://hardhat.org) | Testing & fuzzing | | [MetaMask](https://metamask.io), [WalletConnect](https://walletconnect.com), [Rabby](https://rabby.io) | Wallets | | [ethers.js](https://docs.ethers.org/v6/), [viem](https://viem.sh), [web3.js](https://web3js.org), [wagmi](https://wagmi.sh) | JavaScript libraries | | [Blockscout](https://github.com/blockscout/blockscout) | Block explorer | ### JSON-RPC The JSON-RPC interface implements the full Ethereum spec. Point any standard tool at your chain's RPC endpoint and it works. Most standard Ethereum JSON-RPC methods are supported, with some returning stub values for compatibility. Click to view the complete reference. All standard transaction, query, and subscription methods work as expected — `eth_sendRawTransaction`, `eth_call`, `eth_estimateGas`, event logs and filters, WebSocket subscriptions, and the debug namespace for tracing. A few things to know: * `eth_gasPrice` returns 0 — use EIP-1559 fee fields instead * Mining-related methods don't apply (consensus is handled by CometBFT) * `txpool` methods require the experimental mempool to be enabled ## Beyond Ethereum Cosmos EVM adds capabilities on top of full Ethereum compatibility. None of these change how existing contracts or tooling behave. | Area | Ethereum | Cosmos EVM | | -------------------- | -------------------- | ------------------------- | | **Block time** | \~12 seconds | 1–2 seconds | | **Finality** | 12+ blocks (\~3 min) | 1 block (\~2 seconds) | | **Reorganizations** | Possible | Not possible | | **Cross-chain** | Bridge protocols | Native IBC | | **Fee distribution** | Base fee burned | Distributed to validators | ### Finality Cosmos EVM transactions are final after one block (\~2 seconds) via [CometBFT](/cometbft) consensus—no waiting for confirmations, no reorganizations possible. The validator set requires 2/3+ stake agreement for any block to be committed. ### Gas & fees Cosmos EVM uses EIP-1559 dynamic fees. Unlike Ethereum, the base fee is distributed to validators and delegators rather than burned. The base fee can also be disabled entirely via the `NoBaseFee` parameter, and a chain-wide minimum gas price floor is configurable. ### Address system Every account has two representations that reference the same underlying key: ``` Ethereum: 0x742d35cc6644c068532fddb11B4C36A58D6D3eAb Cosmos: cosmos1wskntvnryr5qxpe4tv5k64rhc6kx6ma4dxjmav ``` ### Chain IDs Cosmos EVM uses two independent chain IDs: a Cosmos Chain ID (string, e.g. `cosmosevm-1`) and an EVM Chain ID (integer, e.g. `9000`). These are configured separately, unlike legacy Ethermint where the format was coupled. ### Precompiled Contracts Full list of built-in Cosmos precompiles and their addresses. Enable built-in precompiles or add your own to your chain. All standard Ethereum cryptographic precompiles (ecrecover, sha256, etc.) are also supported; visit [evm.codes/precompiled](https://www.evm.codes/precompiled) for the full reference. ### Predeployed Contracts Cosmos EVM ships with standard EVM infrastructure contracts already in chain state at their canonical addresses — Create2, Multicall3, Permit2, Safe Singleton Factory, and EIP-2935 block hash storage. Any tooling that depends on these contracts at their well-known addresses works without extra setup. Learn which contracts are preinstalled and how to preinstall contracts at genesis. #### Key Precompile Addresses | Function | Address | | ---------------- | -------------------------------------------- | | **Staking** | `0x0000000000000000000000000000000000000800` | | **Distribution** | `0x0000000000000000000000000000000000000801` | | **IBC Transfer** | `0x0000000000000000000000000000000000000802` | | **Bank** | `0x0000000000000000000000000000000000000804` | | **Governance** | `0x0000000000000000000000000000000000000805` | ### Performance Cosmos EVM gas estimation is optimized: plain ETH transfers return 21000 immediately without simulation, and complex transactions use initial execution results to tighten the binary search bounds, making `eth_estimateGas` significantly faster across the board. The Cosmos EVM mempool follows standard Ethereum behavior (gas price + nonce ordering, multiple transactions per account per block). Pool limits, timeouts, and priority functions are [configurable per chain](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/mempool-integration). An optional experimental mempool adds nonce gap handling and automatic transaction promotion. ## Resources Deploy a chain and your first contract end-to-end. Full list of supported tools, libraries, and frameworks. Built-in Cosmos precompiles and their addresses. Standard EVM contracts available at genesis. # Mempool Configuration Source: https://docs.cosmos.network/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/mempool-integration Customize the EVM mempool behavior on your Cosmos EVM chain. The mempool holds submitted transactions before they are included in a block, handling ordering, nonce gap queuing, and fee-based prioritization across both EVM and Cosmos transactions. The EVM mempool is enabled by default in `evmd`. For conceptual information about mempool design and architecture, see the [Mempool Concepts](/evm/latest/documentation/concepts/mempool) page. The mempool setup is split across two locations: * [`evmd/mempool.go`](https://github.com/cosmos/evm/blob/main/evmd/mempool.go) — `configureEVMMempool` must be called from your `app.go` after `setAnteHandler` * [`mempool/`](https://github.com/cosmos/evm/tree/main/mempool) — the mempool implementation (`EVMMempool`,`TxPool`, `Rechecker`, `ReapList`, `RecheckPool`, etc) The most common EVM Mempool, Legacy pool parameters, `MinTip` are exposed [via `app.toml`](#app.toml-configuration) and require no code changes. The legacy pool (`legacypool.LegacyPool`) is a port of go-ethereum's transaction pool and handles all EVM transaction ordering and fee enforcement. Some advanced settings are not covered by `app.toml`, and require modifying `createMempoolConfig` in `evmd/mempool.go`. `BlockGasLimit` is read from `consensus_params.block.max_gas` in `genesis.json`. ## Configuration Options The `Config` struct controls mempool behavior: ```go title="mempool/mempool.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Config struct { LegacyPoolConfig *legacypool.Config // Optional: port of Geth's txpool — see Custom Legacy Pool below CosmosPoolConfig *sdkmempool.PriorityNonceMempoolConfig[math.Int] // Optional: Cosmos pool tuning — see Custom Cosmos Mempool below AnteHandler sdk.AnteHandler // Required: transaction validation BlockGasLimit uint64 // Required: gas limit for block selection MinTip *uint256.Int // Optional: minimum tip for EVM txs PendingTxProposalTimeout time.Duration // Optional but recommended: max amount of time to allocate to fetching pending execution txs InsertQueueSize int // Optional: how many txs can be pending insertion at once EnableTxTracker bool // Optional: if tracking transaction inclusion metrics is enabled } ``` ### Defaults and Fallbacks * If `BlockGasLimit` is `0`, the mempool uses a fallback of `100_000_000` gas. * If `LegacyPoolConfig` is not provided, defaults from `legacypool.DefaultConfig` are used. * If `CosmosPoolConfig` is not provided, a default `PriorityNonceMempool` is created with: * Priority = `(fee_amount / gas_limit)` in the EVM coin denom * Comparator = big-int comparison (higher is selected first) * `MinValue = 0` * `MinTip` is optional. If unset, selection uses the effective tip from each tx (`min(gas_tip_cap, gas_fee_cap - base_fee)`). * If `PendingTxProposalTimeout` is not provided, 0 is used. This means unlimited timeout and always wait for all tx rechecking to finish before creating a proposal. * If `InsertQueueSize` is 0, the mempool uses a fallback of 5000. * If `EnableTxTracker` is not provided, it is kept false. ### Custom Legacy Pool Configuration Customize EVM transaction pool parameters: ```go title="evmd/mempool.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // EVM legacy txpool tuning legacyCfg := legacypool.DefaultConfig legacyCfg.PriceLimit = 2 // Minimum gas price (wei) legacyCfg.PriceBump = 15 // 15% price bump to replace legacyCfg.AccountSlots = 32 // Slots per account legacyCfg.GlobalSlots = 10240 // Total executable slots legacyCfg.AccountQueue = 128 // Non-executable per account legacyCfg.GlobalQueue = 2048 // Total non-executable legacyCfg.Lifetime = 6 * time.Hour // Max queue time legacyCfg.IncludedNonceCacheSize = 5000 // Max cache size for tracking account nonces mempoolConfig.LegacyPoolConfig = &legacyCfg ``` ### Custom Cosmos Mempool Configuration The mempool uses a `PriorityNonceMempool` for Cosmos transactions by default. You can customize the priority calculation: ```go title="evmd/mempool.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Define custom priority calculation for Cosmos transactions cosmosCfg := sdkmempool.PriorityNonceMempoolConfig[math.Int]{} cosmosCfg.TxPriority = sdkmempool.TxPriority[math.Int]{ GetTxPriority: func(goCtx context.Context, tx sdk.Tx) math.Int { feeTx, ok := tx.(sdk.FeeTx) if !ok { return math.ZeroInt() } // Get fee in bond denomination bondDenom := "uatom" // or your chain's bond denom fee := feeTx.GetFee() found, coin := fee.Find(bondDenom) if !found { return math.ZeroInt() } // Calculate gas price: fee_amount / gas_limit gasPrice := coin.Amount.Quo(math.NewIntFromUint64(feeTx.GetGas())) return gasPrice }, Compare: func(a, b math.Int) int { return a.BigInt().Cmp(b.BigInt()) // Higher values have priority }, MinValue: math.ZeroInt(), } mempoolConfig.CosmosPoolConfig = &cosmosCfg ``` ### Custom Block Gas Limit `BlockGasLimit` is read automatically from `consensus_params.block.max_gas` in `genesis.json` — it is not an `app.toml` setting. To change it, update the genesis file before chain start. The value can also be overridden in code: ```go title="evmd/mempool.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Example: 50M gas limit for lower capacity chains mempoolConfig := &evmmempool.Config{ BlockGasLimit: 50_000_000, } ``` ### Event Bus Integration Users must connect the mempool to CometBFT's EventBus so it can react to finalized blocks: ```go title="evmd/app.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // After starting the CometBFT node if m, ok := app.GetMempool().(*evmmempool.EVMMempool); ok { m.SetEventBus(bftNode.EventBus()) } ``` This enables chain-head notifications so the mempool can promptly promote/evict transactions when blocks are committed. ## app.toml Configuration The following settings can be configured in `app.toml` and take effect at node startup without code changes: | Key | Default | Description | | ----------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `evm.min-tip` | `0` | Minimum tip (priority fee) in wei; transactions below this are excluded from block selection | | `evm.mempool.price-limit` | `1` | Minimum gas price in wei to accept a transaction into the pool | | `evm.mempool.price-bump` | `10` | Minimum % increase required to replace a pending transaction with the same nonce | | `evm.mempool.account-slots` | `16` | Max executable transactions per account | | `evm.mempool.global-slots` | `5120` | Max total executable transactions across all accounts | | `evm.mempool.account-queue` | `64` | Max queued (non-executable) transactions per account | | `evm.mempool.global-queue` | `1024` | Max total queued transactions across all accounts | | `evm.mempool.lifetime` | `3h` | Max time a transaction can remain queued before eviction | | `evm.mempool.included-nonce-cache-size` | `4096` | Max amount of nonces to track for eviction. Should be set to the maximum number of accounts you expect to see in a single block, given your chains block gas limit. | | `evm.mempool.pending-tx-proposal-timeout` | `0ms` | Max time to wait for fetching pending execution transaction when creating a proposal. 0 means wait for all transactions to be validated before creating a proposal. Note that this may take a significant amount of time under high load and can degrade performance. We've found setting this to \~250ms strikes the right balance between performance and rechecking a sufficient amount of transactions per block. | | `evm.mempool.check-tx-timeout` | `5s` | Timeout to wait for `CheckTx` on Cosmos txs insertion. | | `evm.mempool.insert-queue-size` | `5000` | Max amount of transactions that can be pending insertion before returning an error. Note that EVM & Cosmos transaction use separate queues. So you may have `insert-queue-size` EVM transactions pending insertion, and `insert-queue-size` Cosmos transactions pending insertion. | | `evm.mempool.enable-tx-tracker` | `false` | If metrics for tracking EVM transaction inclusion latencies should be enabled. | ## Monitoring and Debugging Use the [txpool RPC methods](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool-methods) to monitor mempool state: * `txpool_status`: Get pending and queued transaction counts * `txpool_content`: View all transactions in the pool * `txpool_inspect`: Get human-readable transaction summaries * `txpool_contentFrom`: View transactions from specific addresses ## Related Documentation * [Mempool Concepts](/evm/latest/documentation/concepts/mempool) - Understanding mempool behavior and design * [EVM Module Integration](/evm/latest/documentation/getting-started/build-a-chain/overview) - Prerequisites for mempool integration * [JSON-RPC Methods](/evm/latest/api-reference/ethereum-json-rpc/methods#txpool-methods) - Mempool query methods # Precompile Configuration Source: https://docs.cosmos.network/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/precompiles Choose which precompiles are active on your chain and add custom ones. [Precompiles](/evm/latest/documentation/smart-contracts/precompiles/overview) are smart contract interfaces at fixed addresses where the implementation runs as native Go code. Cosmos EVM ships with precompiles for staking, governance, IBC, and more. As a chain builder you control which ones are active and can add your own. For more information and to see the full list of available precompiles, see the [precompiles overview](/evm/latest/documentation/smart-contracts/precompiles/overview). ## Enabling Precompiles Precompiles are enabled via the `active_static_precompiles` parameter in the `vm` module. Only addresses listed here are callable at runtime. For the full list of built-in precompiles and their addresses, see the [precompiles overview](/evm/latest/documentation/smart-contracts/precompiles/overview). 1. Wire precompiles into the EVM keeper in `app.go` using `.WithStaticPrecompiles()`. The standard way is to pass `precompiletypes.DefaultStaticPrecompiles(...)`, which includes all built-in precompiles: ```go title="evmd/app.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ).WithStaticPrecompiles( precompiletypes.DefaultStaticPrecompiles( *app.StakingKeeper, app.DistrKeeper, app.PreciseBankKeeper, &app.Erc20Keeper, &app.TransferKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ClientKeeper, app.GovKeeper, app.SlashingKeeper, appCodec, ), ) ``` If you want a custom set, replace `DefaultStaticPrecompiles` with your own builder chain (see [Adding a Custom Precompile](#adding-a-custom-precompile) below). 2. Set the active precompiles in your genesis configuration ([`evmd/genesis.go`](https://github.com/cosmos/evm/blob/main/evmd/genesis.go)): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewEVMGenesisState() *evmtypes.GenesisState { evmGenState := evmtypes.DefaultGenesisState() // Enable all available precompiles evmGenState.Params.ActiveStaticPrecompiles = evmtypes.AvailableStaticPrecompiles evmGenState.Preinstalls = evmtypes.DefaultPreinstalls return evmGenState } ``` To enable only a specific subset, pass the addresses explicitly. Addresses must be in sorted order (see [Adding a Custom Precompile](#adding-a-custom-precompile) below): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmGenState.Params.ActiveStaticPrecompiles = []string{ evmtypes.StakingPrecompileAddress, // 0x0000000000000000000000000000000000000800 evmtypes.DistributionPrecompileAddress, // 0x0000000000000000000000000000000000000801 evmtypes.BankPrecompileAddress, // 0x0000000000000000000000000000000000000804 } ``` The full list of available addresses is defined in [`x/vm/types/precompiles.go`](https://github.com/cosmos/evm/blob/main/x/vm/types/precompiles.go). See the [precompiles overview](/evm/latest/documentation/smart-contracts/precompiles/overview) for more information. Already-registered precompiles can also be enabled or disabled after launch via a governance parameter change proposal targeting the `vm` module's `active_static_precompiles` param. Adding a genuinely new custom precompile requires a chain upgrade, since the implementation lives in the Go binary. ## Adding a Custom Precompile The following example adds a stateful `DenomSupply` precompile with a single `supplyOf` method that reads total token supply directly from the Cosmos bank module. This demonstrates the core pattern for basic precompiles: injecting a Cosmos SDK keeper and using `RunNativeAction` to access live chain state from an EVM call. ### 1. Create the precompile package Create a directory `precompiles/denomsupply/` with two files: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} mkdir -p precompiles/denomsupply ``` `precompiles/denomsupply/abi.json` — the Solidity ABI: ```json title="precompiles/denomsupply/abi.json" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [ { "inputs": [{"internalType": "string", "name": "denom", "type": "string"}], "name": "supplyOf", "outputs": [{"internalType": "uint256", "name": "amount", "type": "uint256"}], "stateMutability": "view", "type": "function" } ] ``` `precompiles/denomsupply/denomsupply.go` — the implementation: ```go title="precompiles/denomsupply/denomsupply.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package denomsupply import ( "bytes" _ "embed" "fmt" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" cmn "github.com/cosmos/evm/precompiles/common" evmtypes "github.com/cosmos/evm/x/vm/types" storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" ) var _ vm.PrecompiledContract = &Precompile{} var ( //go:embed abi.json f []byte ABI abi.ABI ) func init() { var err error ABI, err = abi.JSON(bytes.NewReader(f)) if err != nil { panic(err) } } // Precompile queries the total supply of a Cosmos denomination from the bank module. type Precompile struct { cmn.Precompile bankKeeper cmn.BankKeeper } func NewPrecompile(bankKeeper cmn.BankKeeper) *Precompile { return &Precompile{ Precompile: cmn.Precompile{ KvGasConfig: storetypes.GasConfig{}, TransientKVGasConfig: storetypes.GasConfig{}, ContractAddress: common.HexToAddress(evmtypes.DenomSupplyPrecompileAddress), }, bankKeeper: bankKeeper, } } func (p Precompile) RequiredGas(_ []byte) uint64 { return 3_000 } // Run executes the precompile inside the Cosmos EVM context. // RunNativeAction bridges the EVM execution environment to the Cosmos SDK, // providing an sdk.Context with access to all module state. func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { return p.RunNativeAction(evm, contract, func(ctx sdk.Context) ([]byte, error) { method, args, err := cmn.SetupABI(ABI, contract, readonly, p.IsTransaction) if err != nil { return nil, err } switch method.Name { case "supplyOf": denom, ok := args[0].(string) if !ok { return nil, fmt.Errorf("invalid argument: expected string") } coin := p.bankKeeper.GetSupply(ctx, denom) return method.Outputs.Pack(coin.Amount.BigInt()) } return nil, fmt.Errorf("unknown method: %s", method.Name) }) } // IsTransaction returns false because supplyOf is a read-only query. func (Precompile) IsTransaction(_ *abi.Method) bool { return false } ``` The struct embeds `cmn.Precompile` rather than handling context directly. `RunNativeAction` sets up the SDK context, manages gas metering, and handles snapshot/revert so that precompile calls participate correctly in EVM transaction atomicity. Inside the closure, `p.bankKeeper` provides access to the bank module's state. ### 2. Register the address In [`x/vm/types/precompiles.go`](https://github.com/cosmos/evm/blob/main/x/vm/types/precompiles.go), add a constant before the closing `)` of the const block (line 17) and append it to `AvailableStaticPrecompiles` before its closing `}` (line 35, shifted +1 by the constant insert). Addresses must be in sorted order. Add the constant (inserted before the closing `)` at line 17): ```go title="x/vm/types/precompiles.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DenomSupplyPrecompileAddress = "0x0000000000000000000000000000000000000809" ``` Append it to the `AvailableStaticPrecompiles` slice (inserted before the closing `}` at line 35, shifted +1 by the previous insert): ```go title="x/vm/types/precompiles.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} DenomSupplyPrecompileAddress, // appended in sorted order: 0x...0807 < 0x...0809 ``` ### 3. Add a builder method In [`precompiles/types/static_precompiles.go`](https://github.com/cosmos/evm/blob/main/precompiles/types/static_precompiles.go), add the import for the new package (inserted before `ics02precompile` at line 16, between `govprecompile` and `ics02precompile`): ```go title="precompiles/types/static_precompiles.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} denomsupplyprecompile "github.com/cosmos/evm/precompiles/denomsupply" ``` Then add the `With` method at the very end of the file. Pass any keepers your precompile needs as parameters: ```go title="precompiles/types/static_precompiles.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (s StaticPrecompiles) WithDenomSupplyPrecompile(bankKeeper cmn.BankKeeper) StaticPrecompiles { denomSupplyPrecompile := denomsupplyprecompile.NewPrecompile(bankKeeper) s[denomSupplyPrecompile.Address()] = denomSupplyPrecompile return s } ``` ### 4. Wire it into the app In [`precompiles/types/defaults.go`](https://github.com/cosmos/evm/blob/main/precompiles/types/defaults.go), add your method to the builder chain by replacing line 89 (`WithSlashingPrecompile`). The `bankKeeper` is already a parameter of `DefaultStaticPrecompiles`: ```go title="precompiles/types/defaults.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} WithSlashingPrecompile(slashingKeeper, bankKeeper, opts...). WithDenomSupplyPrecompile(bankKeeper) ``` ### 5. Activate at genesis Because `evmd/genesis.go` already uses `evmtypes.AvailableStaticPrecompiles`, adding your address to that slice in Step 2 is sufficient — no change to `genesis.go` is required. If you use `local_node.sh` for local development, that script hardcodes the precompile list via a `jq` command and does not read from `AvailableStaticPrecompiles` at runtime. Insert the following before line 244 of `local_node.sh` (the blank line after the `active_static_precompiles` jq command at line 243) to append your address: ```bash title="local_node.sh" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} jq '.app_state["evm"]["params"]["active_static_precompiles"] += ["0x0000000000000000000000000000000000000809"]' \ "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" ``` ### 6. Build and verify ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make install ``` Start a local chain in the background: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} bash local_node.sh -y --no-install ``` Once the chain is running, call the precompile and get a decoded result in one step: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} cast call 0x0000000000000000000000000000000000000809 \ "supplyOf(string)(uint256)" "atest" \ --rpc-url http://localhost:8545 # 100025807224055573593873019 [1e26] ``` When successful, you should see `100025807224055573593873019` in the output. # Predeployed Contracts Source: https://docs.cosmos.network/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/predeployed-contracts Deploy standard EVM contracts at fixed addresses on your Cosmos EVM chain. Predeployed contracts (also called preinstalls) are EVM contracts that exist in chain state at a specific address from genesis. Because the address is fixed and known in advance, the same contract can exist at the same address across every chain that includes it, making them useful for infrastructure that needs to be reliably reachable everywhere. ## Default contracts Cosmos EVM includes five default preinstalls ([`x/vm/types/preinstall.go`](https://github.com/cosmos/evm/blob/main/x/vm/types/preinstall.go)): | Contract | Address | Purpose | Docs | | -------------------------- | -------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | **Create2** | `0x4e59b44847b379578588920ca78fbf26c0b4956c` | Deterministic contract deployment using CREATE2 | [EIP-1014](https://eips.ethereum.org/EIPS/eip-1014) | | **Multicall3** | `0xcA11bde05977b3631167028862bE2a173976CA11` | Batch multiple contract calls in one transaction | [Repo](https://github.com/mds1/multicall) · [Site](https://www.multicall3.com/) | | **Permit2** | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | Signature-based token approvals for any ERC20 | [Repo](https://github.com/Uniswap/permit2) · [Docs](https://docs.uniswap.org/contracts/permit2/overview) | | **Safe Singleton Factory** | `0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7` | Deploy Safe multisig wallets at deterministic addresses | [Repo](https://github.com/safe-global/safe-singleton-factory) · [Docs](https://docs.safe.global/) | | **EIP-2935** | `0x0000F90827F1C53a10cb7A02335B175320002935` | Historical block hash storage | [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) | ## Enabling at genesis Preinstalls are set in the `app_state.evm.preinstalls` array of `genesis.json`. How you populate that array depends on how you generate genesis for your chain. * [**Using `local_node.sh`**](/evm/latest/documentation/getting-started/build-a-chain/quick-start): `evmd init` does not populate preinstalls automatically. you will need to update `local_node.sh` to write the default preinstalls into `genesis.json` before the node starts. Each entry requires the contract name, address, and compiled bytecode: ```bash title="local_node.sh" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} jq '.app_state["evm"]["preinstalls"]=[ { "name": "Create2", "address": "0x4e59b44847b379578588920ca78fbf26c0b4956c", "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" }, { "name": "Multicall3", "address": "0xcA11bde05977b3631167028862bE2a173976CA11", "code": "0x6080..." }, { "name": "Permit2", "address": "0x000000000022D473030F116dDEE9F6B43aC78BA3", "code": "0x6040..." }, { "name": "Safe singleton factory", "address": "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7", "code": "0x7fff..." }, { "name": "EIP-2935 - Serve historical block hashes from state", "address": "0x0000F90827F1C53a10cb7A02335B175320002935", "code": "0x3373..." } ]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" ``` Bytecodes are defined in [`x/vm/types/preinstall.go`](https://github.com/cosmos/evm/blob/main/x/vm/types/preinstall.go). * **Using programmatic genesis** (e.g., `evmd testnet`): Preinstalls are set in `NewEVMGenesisState()` in `evmd/genesis.go`. The reference chain already sets `evmGenState.Preinstalls = evmtypes.DefaultPreinstalls` there, so all five defaults are included automatically. * **Custom genesis generation**: Include the `preinstalls` array directly in your `genesis.json` under `app_state.evm`, with each entry specifying `name`, `address`, and hex-encoded `code`. Example: ```json title="genesis.json" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "evm": { "preinstalls": [ { "name": "MyContract", "address": "0x1234567890123456789012345678901234567890", "code": "0x6001..." } ] } } } ``` ## Add custom contracts 1. To deploy a contract beyond the defaults, open `evmd/genesis.go` and update `NewEVMGenesisState`. The example below uses a minimal 10-byte contract (`0x600160005260206000f3`) that returns `1` on any call — replace the name, address, and code with your own: ```go title="evmd/genesis.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewEVMGenesisState() *evmtypes.GenesisState { evmGenState := evmtypes.DefaultGenesisState() evmGenState.Params.ActiveStaticPrecompiles = evmtypes.AvailableStaticPrecompiles customPreinstall := evmtypes.Preinstall{ Name: "MyContract", Address: "0x1234567890123456789012345678901234567890", Code: "0x600160005260206000f3", } evmGenState.Preinstalls = append(evmtypes.DefaultPreinstalls, customPreinstall) return evmGenState } ``` 2. Add a `jq` patch to `local_node.sh` after the last genesis customization line (`.consensus.params.block.max_gas`), before the `# Change proposal periods` comment. Use `+=` to append without overwriting any previously set preinstalls: ```bash title="local_node.sh" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} jq '.app_state["evm"]["preinstalls"] += [ { "name": "MyContract", "address": "0x1234567890123456789012345678901234567890", "code": "0x600160005260206000f3" } ]' "$GENESIS" > "$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS" ``` Requirements for a valid preinstall: * Valid Ethereum address (0x prefix, 40 hex characters) * Must not conflict with existing contracts or precompile addresses (0x1–0x9FF) * Non-empty, valid EVM bytecode (hex encoded) 3. Rebuild the binary and start the chain: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make install && ./local_node.sh -y ``` The `-y` flag wipes any existing chain data and reinitializes from genesis, which is required for the preinstall to take effect. 4. Once the chain is running, open a new terminal and confirm the contract is installed by running the following commands: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Check contract code is present evmd query evm code 0x1234567890123456789012345678901234567890 ``` ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Check account exists evmd query evm account 0x1234567890123456789012345678901234567890 ``` Expected output for `code`: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} code: YAFgAFJgIGAA8w== ``` The value is the contract bytecode base64-encoded. This decodes to `0x600160005260206000f3`, which is the bytecode set in `genesis.go` and `local_node.sh`. A non-empty value confirms the code was written to state. Expected output for `account`: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} balance: "0" code_hash: 0xc1d5b4ce3e2a6227293fccce2904121c8647bbe16c1216340b851bf12d12560e nonce: "0" ``` * `code_hash`: a non-empty hash confirms the contract exists at this address * `balance: "0"`: preinstalls are deployed with no native token balance, which is expected * `nonce: "0"`: preinstalls are not deployed via a transaction, so the nonce starts at 0 ## Add contracts after launch The following methods are examples of ways to deploy predeployed contracts after the chain is running. ### Deploy via governance proposal Use `MsgRegisterPreinstalls` to deploy contracts on a running chain via governance: ```json title="proposal.json" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "messages": [ { "@type": "/cosmos.evm.vm.v1.MsgRegisterPreinstalls", "authority": "", "preinstalls": [ { "name": "Multicall3", "address": "0xcA11bde05977b3631167028862bE2a173976CA11", "code": "0x..." } ] } ], "deposit": "10000000", "title": "Deploy Multicall3", "summary": "Deploy Multicall3 to enable batched contract calls" } ``` ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd tx gov submit-proposal proposal.json --from mykey --chain-id --gas auto evmd tx gov vote 1 yes --from mykey --chain-id ``` ### Deploy via chain upgrade handler Include preinstalls in a coordinated chain upgrade: ```go title="app/upgrades/v2/upgrades.go" theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, evmKeeper *evmkeeper.Keeper, ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { if err := evmKeeper.AddPreinstalls(ctx, evmtypes.DefaultPreinstalls); err != nil { return nil, err } return mm.RunMigrations(ctx, configurator, fromVM) } } ``` # Run an EVM Chain Source: https://docs.cosmos.network/evm/latest/documentation/getting-started/build-a-chain/quick-start Create your own blockchain by forking and customizing the Cosmos EVM reference chain (evmd). This guide covers the example chain configuration, running the chain locally, and understanding the foundation for building your custom network. The `evmd` [directory in the Cosmos EVM repository](https://github.com/cosmos/evm/tree/main/evmd) contains an example chain that demonstrates the integration of Cosmos EVM modules. This reference implementation is based on the simapp implementation from the Cosmos SDK repository, which provides a simplified yet complete blockchain foundation. ## Prerequisites Make sure you have the following prerequisites: * **Go 1.23.8+** - [Installation guide](https://go.dev/doc/install) * **Git** - [Installation guide](https://git-scm.com/downloads) * **Make** - [Installation guide](https://www.gnu.org/software/make/) * **GCC/Build Tools** - [Installation guide](https://gcc.gnu.org/install/) If you're new to Go development, ensure your `$GOPATH/bin` is in your system PATH. Most package managers (Homebrew, apt, pacman) handle this automatically. See the [Go environment setup guide](https://go.dev/doc/gopath_code) for details. ## Building `evmd` The `evmd` implementation serves as the foundation for building your own custom blockchain. To create your chain: 1. **Fork the repository** — Clone the Cosmos EVM repository: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git clone https://github.com/cosmos/evm.git cd evm ``` 2. **Run the chain** — Run the local node script to start the chain: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./local_node.sh -y ``` The script runs `make install` before starting, so the `evmd` binary will be available in your `$GOPATH/bin` once it completes. The JSON-RPC server will be available at `http://localhost:8545`. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmd status evmd keys list evmd query bank balances
evmd --help # full list of commands ``` ## `local_node.sh` Flags The following are the available flags for the local node script: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./local_node.sh -y # fresh start (overwrite previous database) ./local_node.sh -n # resume from previous state ./local_node.sh --no-install # skip rebuild, use the existing binary ``` ## Connect a Wallet Once the chain is running, you can connect using any Ethereum-compatible wallet. The example below uses MetaMask: ### MetaMask Setup **1. Import the test account** — Add a new wallet in MetaMask using this seed phrase: ``` gesture inject test cycle original hollow east ridge hen combine junk child bacon zero hope comfort vacuum milk pitch cage oppose unhappy lunar seat ``` This is a well-known test seed phrase. **Never** use it for mainnet or with real funds. **2. Add the network** — Go to **Networks → Add custom network** and enter: | Setting | Value | | ---------------------- | ----------------------- | | **Network Name** | Cosmos EVM Local | | **RPC URL** | `http://localhost:8545` | | **Chain ID** | `262144` | | **Currency Symbol** | `TEST` | | **Block Explorer URL** | (leave empty) | **3. Verify** — Switch to "Cosmos EVM Local" in MetaMask. You should see your account balance and can now send transactions. **4. Send a test transaction** — Send 1 TEST to `0x742d35cc6644c068532fddb11B4C36A58D6D3eAb` to confirm end-to-end transaction flow is working. ## Deploy a Solidity Contract with Forge With the chain running, you can deploy Solidity contracts using [Forge](https://book.getfoundry.sh/forge/). Install it with `curl -L https://foundry.paradigm.xyz | bash && foundryup` if you haven't already. 1. **Create a contract** — Save the following as `Counter.sol`: ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Counter { uint256 public count; function increment() public { count++; } } ``` 2. **Deploy it** — Use the dev0 test account private key to deploy: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} forge create Counter.sol:Counter \ --rpc-url http://localhost:8545 \ --private-key 0x88cbead91aee890d27bf06e003ade3d4e952427e88f88d31d61d3ef5e5d54305 \ --broadcast ``` This private key is for local development only. Never use it with real funds. 3. **Interact with the contract** — Read the current count, send a transaction to increment it, then read again to confirm the state change: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Read current count (returns 0) cast call "count()(uint256)" --rpc-url http://localhost:8545 # Increment the counter cast send "increment()" \ --rpc-url http://localhost:8545 \ --private-key 0x88cbead91aee890d27bf06e003ade3d4e952427e88f88d31d61d3ef5e5d54305 # Read again (returns 1) cast call "count()(uint256)" --rpc-url http://localhost:8545 ``` ## Configuration ### Basic Chain Configuration The following are some examples of basic configuration options for the `evmd` chain. | Setting | Default | How to change | Reference | | ---------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | **Cosmos Chain ID** | `cosmos_262144-1` | Pass `--chain-id` at node start | — | | **EVM Chain ID** | `262144` | Pass `--evm-chain-id` at node start | [`x/vm/types/params.go`](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go) | | **Bech32 Prefix** | `cosmos` | Change `Bech32Prefix` constant | [`evmd/config/bech32.go`](https://github.com/cosmos/evm/blob/main/evmd/config/bech32.go) | | **Token Denomination** | `aatom` | Update `ExampleAttoDenom`, used by `NewMintGenesisState()` | [`testutil/constants/constants.go`](https://github.com/cosmos/evm/blob/main/testutil/constants/constants.go) | | **EVM Permissioning** | Permissionless | Set `AccessType` in `DefaultAccessControl` | [`x/vm/types/params.go`](https://github.com/cosmos/evm/blob/main/x/vm/types/params.go) | | **Precompiles** | All (9 enabled) | Replace `AvailableStaticPrecompiles` in `NewEVMGenesisState()` | [`evmd/genesis.go`](https://github.com/cosmos/evm/blob/main/evmd/genesis.go) | | **Modules** | Standard SDK set | Register, set the pre-begin-, begin-, and endblockers, genesis module order, and keeper initialization | [`evmd/app.go`](https://github.com/cosmos/evm/blob/main/evmd/app.go) | | **Binary name** | `evmd` | Rename directory and run `find . -type f -name "*.go" -exec sed -i 's/evmd/yourchain/g' {} \;`, then update `go.mod` | — | ### Advanced Configuration `evmd` works out of the box with sensible defaults. The following are optional configurations for chains that need to go further. | Configuration | Description | Reference | | ------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **Mempool** | Custom transaction prioritization, nonce gap handling, pool size limits | [Mempool Configuration](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/mempool-integration) | | **Fee Market** | Disable base fee, set min gas price, tune base fee adjustment rate | [Fee Market Module](/evm/latest/documentation/cosmos-sdk/modules/feemarket) | | **EVM Access Control** | Restrict contract deployment/calls to whitelisted addresses | [VM Module](/evm/latest/documentation/cosmos-sdk/modules/vm) | | **JSON-RPC** | Enable/disable namespaces, set resource caps, configure WebSocket origins | [Node Configuration](/evm/latest/documentation/getting-started/network-operators/node-configuration) | | **Predeployed Contracts** | Deploy Create2, Multicall3, Permit2, Safe at genesis | [Predeployed Contracts](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/predeployed-contracts) | | **Precompiles** | Enable built-in precompiles or add custom ones | [Precompile Configuration](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/precompiles) | For additional support and community resources, visit the [Cosmos EVM GitHub repository](https://github.com/cosmos/evm) or join the Cosmos developer community. # Frequently Asked Questions Source: https://docs.cosmos.network/evm/latest/documentation/getting-started/faq Cosmos EVM is an open-source [Cosmos SDK](https://docs.cosmos.network) module that embeds a full Ethereum Virtual Machine into a CometBFT-based blockchain. You can launch a sovereign L1 that runs standard Solidity contracts and supports the full Ethereum toolchain, while also getting things Ethereum doesn't have: single-block finality, no reorganizations, native IBC cross-chain transfers, and direct Cosmos SDK module access from smart contracts. Unlike rollups, you control your own validator set, governance, and fee economics. See the [overview](/evm/latest/documentation/overview) for more. Yes, fully. Cosmos EVM chains implement the complete Ethereum JSON-RPC API and execute the same EVM bytecode. Contracts that work on Ethereum work on Cosmos EVM without code changes, and tools like MetaMask, Hardhat, Foundry, ethers.js, and viem all work as-is. The differences are additions, not substitutions: faster finality (\~1–2 seconds vs. \~3 minutes on Ethereum), no reorgs, native IBC, and [precompiles](/evm/latest/documentation/smart-contracts/precompiles/overview) that expose Cosmos SDK functionality from Solidity. See the [EVM Compatibility page](/evm/latest/documentation/evm-compatibility) for a full breakdown. Yes. All opcodes, ABI encoding, and libraries work the same as on Ethereum. Any contract that compiles and runs on Ethereum runs on a Cosmos EVM chain without modification. Point your tooling at your chain's RPC endpoint and chain ID and you're set. No. Use Hardhat, Foundry, or Remix pointed at your chain's JSON-RPC endpoint. Deployment goes through `eth_sendRawTransaction` and is processed by the EVM module the same way as on Ethereum. The contract gets an Ethereum address and behaves as expected. See the [quick start guide](/evm/latest/documentation/getting-started/build-a-chain/quick-start#deploy-a-solidity-contract-with-forge) for a step-by-step deployment walkthrough with Forge. Any tool that speaks standard Ethereum JSON-RPC works without modification: * **Contract development:** [Hardhat](https://hardhat.org), [Foundry](https://book.getfoundry.sh), [Remix](https://remix.ethereum.org) * **Libraries:** [ethers.js](https://docs.ethers.org/v6/), [viem](https://viem.sh), [wagmi](https://wagmi.sh), [web3.js](https://web3js.org) * **Wallets:** [MetaMask](https://metamask.io), [Rabby](https://rabby.io), [WalletConnect](https://walletconnect.com), [Keplr](https://www.keplr.app) * **Block explorers:** [Blockscout](https://github.com/blockscout/blockscout) See the [Tooling & Resources page](/evm/latest/documentation/getting-started/tooling-and-resources) for the full list. Cosmos EVM supports all standard EVM opcodes and EIPs up to the Prague hard fork. Some notable ones: | EIP | Purpose | | -------- | ----------------------------------------------- | | EIP-155 | Replay protection via chain ID in signatures | | EIP-712 | Typed structured data signing | | EIP-1559 | Dynamic fees (base fee + priority fee) | | EIP-2535 | Diamond proxy pattern for upgradeable contracts | | EIP-4337 | Account abstraction | | EIP-7702 | Set code for EOAs | Two things are not supported: [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) (blob transactions) and [EIP-4399](https://eips.ethereum.org/EIPS/eip-4399) (PREVRANDAO). See the [EVM Compatibility page](/evm/latest/documentation/evm-compatibility#eips) for the complete list. Precompiles are smart contract interfaces at fixed addresses where the implementation runs as native Go code rather than EVM bytecode. On standard Ethereum, precompiles handle things like signature verification and hashing. Cosmos EVM adds stateful precompiles that let Solidity contracts interact directly with Cosmos SDK modules. From Solidity, you can call the staking precompile to delegate tokens, the governance precompile to submit a proposal, or the ICS20 precompile to send an IBC transfer, all within a single transaction. Built-in precompiles include: | Precompile | Address | Purpose | | ------------ | ----------- | ------------------------------------- | | Staking | `0x...0800` | Delegate, undelegate, claim rewards | | Distribution | `0x...0801` | Staking rewards and community pool | | ICS20 | `0x...0802` | IBC cross-chain token transfers | | Bank | `0x...0804` | ERC-20 access to native Cosmos tokens | | Governance | `0x...0805` | Submit proposals and vote | See the [Precompiles Overview](/evm/latest/documentation/smart-contracts/precompiles/overview) for the full list of addresses and interfaces. Predeployed contracts (also called preinstalls) are standard EVM contracts deployed at their canonical Ethereum addresses from genesis. Any tooling that expects them at those addresses works without extra setup. Cosmos EVM ships five by default: | Contract | Purpose | | ---------------------- | --------------------------------------- | | Create2 | Deterministic contract deployment | | Multicall3 | Batch multiple calls in one transaction | | Permit2 | Signature-based ERC-20 approvals | | Safe Singleton Factory | Deploy Safe multisig wallets | | EIP-2935 | Historical block hash storage | See the [Predeployed Contracts page](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/predeployed-contracts) for addresses, configuration, and how to add your own. The quickest path is running the example chain locally. It takes a few minutes and only requires Go and Make. The [Quick Start guide](/evm/latest/documentation/getting-started/build-a-chain/quick-start) covers cloning the repo, starting the chain, connecting a wallet, and deploying your first contract. We're working on a migration guide for this and will have it posted up here as soon as possible! Both are elliptic curve algorithms that produce 128-bit security. The practical difference is compatibility vs. attack surface. secp256k1 is the curve used by Bitcoin and most EVM chains. It's well-tested, broadly supported, and the natural choice when you need to interoperate with existing crypto infrastructure, including Ethereum tooling and wallets. ed25519 is faster for signature verification and resistant to certain [side-channel attacks](https://en.wikipedia.org/wiki/Side-channel_attack) that can affect secp256k1. Cosmos SDK validators default to ed25519 for this reason. The tradeoff is narrower tool support, though that gap has closed over time. In practice, the context usually decides for you: EVM contracts and Ethereum-style accounts use secp256k1; Cosmos validator keys use ed25519. See the Cosmos [Buf](https://buf.build/cosmos) project page. # Node Configuration Source: https://docs.cosmos.network/evm/latest/documentation/getting-started/network-operators/node-configuration Complete reference for configuring Cosmos EVM nodes, JSON-RPC settings, and command-line options Configure EVM-specific settings in `~/.evmd/config/app.toml`. This guide covers parameters unique to the EVM implementation. EVM execution environment configuration. Controls VM execution tracing for debugging. **Valid Options:** * `""` - Disabled (default) * `"json"` - Full execution trace in JSON format, used by debug\_traceTransaction RPC * `"struct"` - Go struct format for programmatic processing * `"access_list"` - Generates EIP-2930 access lists for gas optimization * `"markdown"` - Human-readable format for manual analysis **Source:** `server/config/config.go:139` **Implementation:** `x/vm/keeper/state_transition.go:150-157` Limits gas for transactions in CheckTx mode to prevent DoS attacks. When set to 0, any gas amount is accepted which can lead to mempool spam. **Source:** `server/config/config.go:141` **Testnet Default:** Set by `evmd/cmd/evmd/cmd/testnet.go:303` Enables SHA3 preimage recording in the VM for debugging tools to reverse hash lookups. Increases memory usage when enabled. **Source:** `server/config/config.go:143` EIP-155 replay protection chain ID. Must match the expected network chain ID. Used for transaction signing validation. **Source:** `server/config/config.go:145` **Used in:** `x/vm/genesis.go:22` Minimum priority fee (tip) required for transaction inclusion in mempool. Transactions with tips below this value may be rejected. Set to 0 to disable filtering. **Flag:** `EVMMinTip` **Source:** `server/config/config.go:150` **Type:** Converted to `uint256.Int` internally Address for go-ethereum metrics server. Emits EVM-specific metrics in Prometheus format on a separate server from Cosmos SDK metrics. **Source:** `server/config/config.go:152` **New in:** v0.5.0 EVM mempool configuration for transaction pool management. **New in v0.5.0**: Mempool configuration is now fully exposed in `app.toml` and can be adjusted without code changes. Previously, these settings were hardcoded. Minimum gas price to enforce for acceptance into the pool (in wei). Transactions with gas prices below this value are rejected from the mempool. **Source:** `server/config/config.go:160` **Validation:** Must be at least 1 Minimum price bump percentage to replace an already existing transaction (nonce). When replacing a transaction with the same nonce, the new transaction must have a gas price at least this percentage higher. **Source:** `server/config/config.go:162` **Example:** `10` = require 10% higher gas price **Validation:** Must be at least 1 Number of executable transaction slots guaranteed per account. Each account is guaranteed to have this many pending transactions in the executable queue. **Source:** `server/config/config.go:164` **Validation:** Must be at least 1 Maximum number of executable transaction slots for all accounts. Total capacity for all pending executable transactions across all accounts (4096 + 1024 = 5120). **Source:** `server/config/config.go:166` **Validation:** Must be at least 1 Maximum number of non-executable transaction slots permitted per account. For transactions with gaps in nonce sequence (future transactions). **Source:** `server/config/config.go:168` **Validation:** Must be at least 1 Maximum number of non-executable transaction slots for all accounts. Total capacity for queued (non-executable) transactions across all accounts. **Source:** `server/config/config.go:170` **Validation:** Must be at least 1 Maximum amount of time non-executable transactions are queued. Transactions that remain non-executable longer than this duration are removed from the mempool. **Source:** `server/config/config.go:172` **Format:** Go duration string (e.g., `"1h30m"`, `"30m0s"`, `"24h0m0s"`) **Validation:** Must be at least 1ns ### Configuration Examples **High-Throughput Chain:** ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [evm.mempool] global-slots = 10240 global-queue = 2048 price-limit = 100000000 # 0.1 gwei minimum lifetime = "6h0m0s" ``` **Low-Resource Node:** ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [evm.mempool] global-slots = 2048 global-queue = 512 lifetime = "1h0m0s" account-slots = 8 ``` **Strict Spam Protection:** ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [evm.mempool] price-limit = 1000000000 # 1 gwei minimum price-bump = 25 # 25% replacement cost lifetime = "30m0s" account-slots = 4 ``` Ethereum JSON-RPC server configuration. ## Core Settings Enable the JSON-RPC server. Must be `true` for Ethereum compatibility. **Source:** `server/config/config.go:169` **Testnet configuration:** `evmd/cmd/evmd/cmd/testnet.go:303` HTTP server bind address using standard Ethereum RPC port. **Source:** `server/config/config.go:153` WebSocket server address for eth\_subscribe methods. **Source:** `server/config/config.go:155` **Implementation:** `rpc/stream/rpc.go` JSON-RPC namespaces to enable. **Available namespaces:** `web3`, `eth`, `personal`, `net`, `txpool`, `debug`, `miner` **Source:** `server/config/config.go:151` **Namespace implementations:** `rpc/namespaces/` Allowed CORS origins for WebSocket connections. **Source:** `server/config/config.go:195` ## Resource Limits Gas limit for eth\_call/estimateGas operations (25M gas). Set to 0 for unlimited. **Source:** `server/config/config.go:157` **Enforced:** `rpc/namespaces/ethereum/eth/api.go:1039` Maximum transaction fee cap in ether for eth\_sendTransaction. **Source:** `server/config/config.go:163` **Enforced:** `rpc/namespaces/ethereum/eth/api.go:1586` Maximum concurrent filters per connection. **Source:** `server/config/config.go:165` **Implementation:** `rpc/namespaces/ethereum/eth/filters/` Maximum blocks that can be fetched for eth\_feeHistory. **Source:** `server/config/config.go:167` Maximum logs returned from a single eth\_getLogs query. **Source:** `server/config/config.go:171` **Enforced:** `rpc/namespaces/ethereum/eth/filters/api.go:442` Maximum block range allowed for eth\_getLogs queries. **Source:** `server/config/config.go:173` **Enforced:** `rpc/namespaces/ethereum/eth/filters/filter.go:268` ## Connection Settings Maximum number of requests in a batch (go-ethereum standard). **Source:** `server/config/config.go:182` Maximum bytes returned from a batched call (25MB). **Source:** `server/config/config.go:184` Read/write timeout for HTTP JSON-RPC server. **Source:** `server/config/config.go:175` Idle timeout for HTTP connections. **Source:** `server/config/config.go:177` Maximum simultaneous connections for the server listener. Set to 0 for unlimited. **Source:** `server/config/config.go:187` Global timeout for eth\_call operations. **Source:** `server/config/config.go:161` ## Features Enable custom transaction indexer for EVM transactions. **Required for:** `eth_getLogs`, `eth_getTransactionReceipt` **Source:** `server/config/config.go:189` **Testnet default:** `evmd/cmd/evmd/cmd/testnet.go:303` Enable pprof endpoints in debug namespace. **Source:** `server/config/config.go:197` Prometheus metrics server address. **Metrics path:** `/debug/metrics/prometheus` **Source:** `server/config/config.go:191` ## Security Allow non-EIP155 signed transactions to be submitted. **Source:** `server/config/config.go:180` Allow insecure account unlocking when personal namespace is enabled. **Source:** `server/config/config.go:159` CLI flags and environment variables for runtime configuration. These parameters can be set via command line or environment variables to override configuration file settings. **Context**: These are EVM-specific runtime parameters that complement the standard Cosmos SDK flags. They control chain initialization and runtime behavior specific to EVM functionality. Cosmos SDK chain identifier for transaction signing and network identification. **CLI Flag:** `--chain-id` **Environment:** `EVMD_CHAIN_ID` **Usage:** `evmd start --chain-id mychain-1` EIP-155 replay protection chain ID for Ethereum compatibility. Must be unique across all EVM networks. **CLI Flag:** `--evm.evm-chain-id` **Environment:** `EVMD_EVM_CHAIN_ID` **Usage:** `evmd start --evm.evm-chain-id 9000` **Config Alternative:** Set in `app.toml` under `[evm] evm-chain-id` Base denomination for native token transactions and staking. **CLI Flag:** `--denom` **Environment:** `EVMD_DENOM` **Usage:** `evmd init mynode --denom mytoken` Minimum priority fee (in wei) required for mempool inclusion. **CLI Flag:** `--evm.min-tip` **Environment:** `EVMD_EVM_MIN_TIP` **Usage:** `evmd start --evm.min-tip 1000000000` **Config Alternative:** Set in `app.toml` under `[evm] min-tip` Maximum gas limit for transactions in CheckTx validation mode. **CLI Flag:** `--evm.max-tx-gas-wanted` **Environment:** `EVMD_MAX_TX_GAS_WANTED` **Usage:** `evmd start --evm.max-tx-gas-wanted 50000000` **Config Alternative:** Set in `app.toml` under `[evm] max-tx-gas-wanted` Complete `app.toml` with EVM-specific sections highlighted. ```toml Complete Application Configuration expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml ############################################################################### ### Base Configuration ### ############################################################################### # The minimum gas prices a validator is willing to accept for processing a # transaction. A transaction's fees must meet the minimum of any denomination # specified in this config (e.g. 0.25token1,0.0001token2). minimum-gas-prices = "0atest" # The maximum gas a query coming over rest/grpc may consume. # If this is set to zero, the query can consume an unbounded amount of gas. query-gas-limit = "0" # default: the last 362880 states are kept, pruning at 10 block intervals # nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) # everything: 2 latest states will be kept; pruning at 10 block intervals. # custom: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval' pruning = "default" # These are applied if and only if the pruning strategy is custom. pruning-keep-recent = "0" pruning-interval = "0" # HaltHeight contains a non-zero block height at which a node will gracefully # halt and shutdown that can be used to assist upgrades and testing. # # Note: Commitment of state will be attempted on the corresponding block. halt-height = 0 # HaltTime contains a non-zero minimum block time (in Unix seconds) at which # a node will gracefully halt and shutdown that can be used to assist upgrades # and testing. # # Note: Commitment of state will be attempted on the corresponding block. halt-time = 0 # MinRetainBlocks defines the minimum block height offset from the current # block being committed, such that all blocks past this offset are pruned # from CometBFT. It is used as part of the process of determining the # ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates # that no blocks should be pruned. # # This configuration value is only responsible for pruning CometBFT blocks. # It has no bearing on application state pruning which is determined by the # "pruning-*" configurations. # # Note: CometBFT block pruning is dependant on this parameter in conjunction # with the unbonding (safety threshold) period, state pruning and state sync # snapshot parameters to determine the correct minimum value of # ResponseCommit.RetainHeight. min-retain-blocks = 0 # InterBlockCache enables inter-block caching. inter-block-cache = true # IndexEvents defines the set of events in the form {eventType}.{attributeKey}, # which informs CometBFT what to index. If empty, all events will be indexed. # # Example: # ["message.sender", "message.recipient"] index-events = [] # IavlCacheSize set the size of the iavl tree cache (in number of nodes). iavl-cache-size = 781250 # IAVLDisableFastNode enables or disables the fast node feature of IAVL. # Default is false. iavl-disable-fastnode = false # AppDBBackend defines the database backend type to use for the application and snapshots DBs. # An empty string indicates that a fallback will be used. # The fallback is the db_backend value set in CometBFT's config.toml. app-db-backend = "" ############################################################################### ### Telemetry Configuration ### ############################################################################### [telemetry] # Prefixed with keys to separate services. service-name = "" # Enabled enables the application telemetry functionality. When enabled, # an in-memory sink is also enabled by default. Operators may also enabled # other sinks such as Prometheus. enabled = true # Enable prefixing gauge values with hostname. enable-hostname = false # Enable adding hostname to labels. enable-hostname-label = false # Enable adding service to labels. enable-service-label = false # PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. prometheus-retention-time = 1000000000000 # GlobalLabels defines a global set of name/value label tuples applied to all # metrics emitted using the wrapper functions defined in telemetry package. # # Example: # [["chain_id", "cosmoshub-1"]] global-labels = [ ] # MetricsSink defines the type of metrics sink to use. metrics-sink = "" # StatsdAddr defines the address of a statsd server to send metrics to. # Only utilized if MetricsSink is set to "statsd" or "dogstatsd". statsd-addr = "" # DatadogHostname defines the hostname to use when emitting metrics to # Datadog. Only utilized if MetricsSink is set to "dogstatsd". datadog-hostname = "" ############################################################################### ### API Configuration ### ############################################################################### [api] # Enable defines if the API server should be enabled. enable = true # Swagger defines if swagger documentation should automatically be registered. swagger = false # Address defines the API server to listen on. address = "tcp://localhost:1317" # MaxOpenConnections defines the number of maximum open connections. max-open-connections = 1000 # RPCReadTimeout defines the CometBFT RPC read timeout (in seconds). rpc-read-timeout = 10 # RPCWriteTimeout defines the CometBFT RPC write timeout (in seconds). rpc-write-timeout = 0 # RPCMaxBodyBytes defines the CometBFT maximum request body (in bytes). rpc-max-body-bytes = 1000000 # EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). enabled-unsafe-cors = false ############################################################################### ### gRPC Configuration ### ############################################################################### [grpc] # Enable defines if the gRPC server should be enabled. enable = true # Address defines the gRPC server address to bind to. address = "localhost:9090" # MaxRecvMsgSize defines the max message size in bytes the server can receive. # The default value is 10MB. max-recv-msg-size = "10485760" # MaxSendMsgSize defines the max message size in bytes the server can send. # The default value is math.MaxInt32. max-send-msg-size = "2147483647" ############################################################################### ### gRPC Web Configuration ### ############################################################################### [grpc-web] # GRPCWebEnable defines if the gRPC-web should be enabled. # NOTE: gRPC must also be enabled, otherwise, this configuration is a no-op. # NOTE: gRPC-Web uses the same address as the API server. enable = true ############################################################################### ### State Sync Configuration ### ############################################################################### # State sync snapshots allow other nodes to rapidly join the network without replaying historical # blocks, instead downloading and applying a snapshot of the application state at a given height. [state-sync] # snapshot-interval specifies the block interval at which local state sync snapshots are # taken (0 to disable). snapshot-interval = 0 # snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). snapshot-keep-recent = 2 ############################################################################### ### State Streaming ### ############################################################################### # Streaming allows nodes to stream state to external systems. [streaming] # streaming.abci specifies the configuration for the ABCI Listener streaming service. [streaming.abci] # List of kv store keys to stream out via gRPC. # The store key names MUST match the module's StoreKey name. # # Example: # ["acc", "bank", "gov", "staking", "mint"[,...]] # ["*"] to expose all keys. keys = [] # The plugin name used for streaming via gRPC. # Streaming is only enabled if this is set. # Supported plugins: abci plugin = "" # stop-node-on-err specifies whether to stop the node on message delivery error. stop-node-on-err = true ############################################################################### ### Mempool ### ############################################################################### [mempool] # Setting max-txs to 0 will allow for a unbounded amount of transactions in the mempool. # Setting max_txs to negative 1 (-1) will disable transactions from being inserted into the mempool (no-op mempool). # Setting max_txs to a positive number (> 0) will limit the number of transactions in the mempool, by the specified amount. # # Note, this configuration only applies to SDK built-in app-side mempool # implementations. max-txs = -1 ############################################################################### ### EVM Configuration ### ############################################################################### [evm] # Tracer defines the 'vm.Tracer' type that the EVM will use when the node is run in # debug mode. To enable tracing use the '--evm.tracer' flag when starting your node. # Valid types are: json|struct|access_list|markdown tracer = "" # MaxTxGasWanted defines the gas wanted for each eth tx returned in ante handler in check tx mode. max-tx-gas-wanted = 0 # EnablePreimageRecording enables tracking of SHA3 preimages in the VM cache-preimage = false # EVMChainID is the EIP-155 compatible replay protection chain ID. This is separate from the Cosmos chain ID. evm-chain-id = 262144 # MinTip defines the minimum priority fee for the mempool. min-tip = 0 # Geth metrics server address geth-metrics-address = "127.0.0.1:8100" # Mempool configuration for EVM transactions [evm.mempool] # PriceLimit is the minimum gas price to enforce for acceptance into the pool (in wei) price-limit = 1 # PriceBump is the minimum price bump percentage to replace an already existing transaction (nonce) price-bump = 10 # AccountSlots is the number of executable transaction slots guaranteed per account account-slots = 16 # GlobalSlots is the maximum number of executable transaction slots for all accounts global-slots = 5120 # AccountQueue is the maximum number of non-executable transaction slots permitted per account account-queue = 64 # GlobalQueue is the maximum number of non-executable transaction slots for all accounts global-queue = 1024 # Lifetime is the maximum amount of time non-executable transaction are queued lifetime = "3h0m0s" ############################################################################### ### JSON RPC Configuration ### ############################################################################### [json-rpc] # Enable defines if the JSONRPC server should be enabled. enable = true # Address defines the EVM RPC HTTP server address to bind to. address = "127.0.0.1:8545" # Address defines the EVM WebSocket server address to bind to. ws-address = "127.0.0.1:8546" # WSOrigins defines the allowed origins for WebSocket connections. # Example: ["localhost", "127.0.0.1", "myapp.example.com"] ws-origins = ["127.0.0.1", "localhost"] # API defines a list of JSON-RPC namespaces that should be enabled # Example: "eth,txpool,personal,net,debug,web3" api = "eth,net,web3" # GasCap sets a cap on gas that can be used in eth_call/estimateGas (0=infinite). Default: 25,000,000. gas-cap = 25000000 # Allow insecure account unlocking when account-related RPCs are exposed by http allow-insecure-unlock = true # EVMTimeout is the global timeout for eth_call. Default: 5s. evm-timeout = "5s" # TxFeeCap is the global tx-fee cap for send transaction. Default: 1eth. txfee-cap = 1 # FilterCap sets the global cap for total number of filters that can be created filter-cap = 200 # FeeHistoryCap sets the global cap for total number of blocks that can be fetched feehistory-cap = 100 # LogsCap defines the max number of results can be returned from single 'eth_getLogs' query. logs-cap = 10000 # BlockRangeCap defines the max block range allowed for 'eth_getLogs' query. block-range-cap = 10000 # HTTPTimeout is the read/write timeout of http json-rpc server. http-timeout = "30s" # HTTPIdleTimeout is the idle timeout of http json-rpc server. http-idle-timeout = "2m0s" # AllowUnprotectedTxs restricts unprotected (non EIP155 signed) transactions to be submitted via # the node's RPC when the global parameter is disabled. allow-unprotected-txs = false # MaxOpenConnections sets the maximum number of simultaneous connections # for the server listener. max-open-connections = 0 # EnableIndexer enables the custom transaction indexer for the EVM (ethereum transactions). enable-indexer = false # MetricsAddress defines the EVM Metrics server address to bind to. Pass --metrics in CLI to enable # Prometheus metrics path: /debug/metrics/prometheus metrics-address = "127.0.0.1:6065" # Maximum number of requests in a batch. batch-request-limit = 1000 # Maximum number of bytes returned from a batched call. batch-response-max-size = 25000000 # Enabled profiling in the debug namespace enable-profiling = false ############################################################################### ### TLS Configuration ### ############################################################################### [tls] # Certificate path defines the cert.pem file path for the TLS configuration. certificate-path = "" # Key path defines the key.pem file path for the TLS configuration. key-path = "" ``` **Lines (130-260):** are EVM-specific configuration sections added by Cosmos EVM **Lines (1-129):** are Base Cosmos SDK parameters **Key EVM sections:** * `[evm]`: EVM runtime settings (lines 645-670) * `[evm.mempool]`: Mempool configuration (lines 672-694, **new in v0.5.0**) * `[json-rpc]`: Ethereum JSON-RPC server (lines 700-747) * `[tls]`: TLS configuration (lines 753-760) **Template sources:** * SDK config: `cosmos-sdk/server/config/toml.go` * EVM config: `evm/server/config/toml.go` Blockchain initialization parameters set during chain genesis. These EVM-specific parameters configure the initial state and behavior of the EVM module. **Context**: These parameters are set once during chain initialization and typically cannot be changed without governance proposals or network upgrades. They control fundamental EVM behavior and compatibility. ```json Genesis Configuration Example expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "chain_id": "mychain-1", "app_state": { "evm": { "params": { "evm_denom": "atest", "history_serve_window": 8192, "active_static_precompiles": [ "0x0000000000000000000000000000000000000100", "0x0000000000000000000000000000000000000400", "0x0000000000000000000000000000000000000800", "0x0000000000000000000000000000000000000801", "0x0000000000000000000000000000000000000802", "0x0000000000000000000000000000000000000804", "0x0000000000000000000000000000000000000805" ], "access_control": { "create": {"access_type": "ACCESS_TYPE_PERMISSIONLESS"}, "call": {"access_type": "ACCESS_TYPE_PERMISSIONLESS"} } } }, "feemarket": { "params": { "no_base_fee": false, "base_fee_change_denominator": 8, "elasticity_multiplier": 2, "base_fee": "1000000000", "min_gas_price": "0", "min_gas_multiplier": "0.5" } } }, "consensus": { "params": { "block": { "max_bytes": "22020096", "max_gas": "100000000" } } } } ``` **EVM-Specific Parameters:** * `evm_denom`: Base denomination for EVM operations and gas payments * `history_serve_window`: Number of historical block hashes to store (EIP-2935) * `active_static_precompiles`: Enabled precompile contract addresses * `access_control`: Permissions for contract creation and execution **Fee Market Parameters:** * `base_fee`: Initial EIP-1559 base fee value * `base_fee_change_denominator`: Rate of base fee adjustment * `elasticity_multiplier`: Block utilization threshold for fee changes **Consensus Parameters:** * `max_gas`: Maximum gas per block (critical for mempool configuration) Production validator node configuration and security considerations. **Critical Security Notice:** Validator nodes should NEVER expose RPC/API ports publicly. Only dedicated RPC nodes (non-validators) should serve public traffic. ## Validator vs RPC Node Architecture Production networks should use a two-tier architecture: ### Validator Nodes (Private) **Purpose:** Block production and consensus participation only **Configuration Requirements:** * Disable all public-facing services * Restrict network access to other validators via persistent peers * Do NOT enable JSON-RPC server * Do NOT expose API/gRPC ports publicly * Use firewall rules to block external access **Recommended `app.toml` settings:** ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [api] enable = false # Disable REST API [grpc] enable = false # Disable gRPC or bind to localhost only address = "localhost:9090" [json-rpc] enable = false # CRITICAL: Keep JSON-RPC disabled on validators [evm] max-tx-gas-wanted = 50000000 # Set reasonable gas limits ``` **Why this matters:** * **DDoS Protection:** Public RPC access can overwhelm validator resources * **Resource Exhaustion:** Heavy query load impacts block production * **Uptime:** Validators must prioritize consensus participation over serving requests * **Security:** Reduced attack surface for exploits * **Slashing Risk:** Downtime from overload can lead to slashing penalties ### RPC Nodes (Public) **Purpose:** Serve public API/RPC requests without participating in consensus **Configuration Requirements:** * Enable JSON-RPC, API, and WebSocket endpoints * Use load balancers for distribution * Set appropriate rate limits and caps * Can run multiple RPC nodes for redundancy * No validator keys stored **Recommended `app.toml` settings:** ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [api] enable = true address = "tcp://0.0.0.0:1317" # Bind to all interfaces max-open-connections = 1000 [grpc] enable = true address = "0.0.0.0:9090" [json-rpc] enable = true address = "0.0.0.0:8545" ws-address = "0.0.0.0:8546" api = "eth,net,web3,txpool" # Set resource limits gas-cap = 50000000 filter-cap = 200 logs-cap = 10000 block-range-cap = 10000 max-open-connections = 500 batch-request-limit = 100 ``` ## Validator Security Checklist * [ ] JSON-RPC is disabled on validator nodes * [ ] API/gRPC endpoints not exposed publicly on validators * [ ] Firewall rules restrict validator access to known peers only * [ ] Separate RPC nodes deployed for public access * [ ] Load balancers configured for RPC node redundancy * [ ] Monitoring alerts for validator downtime * [ ] Regular security audits of network architecture * [ ] Validator keys stored securely (preferably HSM) * [ ] SSH access restricted and key-based only ## Network Topology Example ``` Public Internet │ ├─── Load Balancer │ │ │ ┌────┴────┬────────┐ │ │ │ │ │ RPC Node RPC Node RPC Node │ │ │ │ └────┴─────────┴────────┘ │ ═══════════════════════════ Private Validator Network ═══════════════════════════ │ ┌──────────┼──────────┐ │ │ │ Validator 1 Validator 2 Validator 3 (RPC disabled) (RPC disabled) (RPC disabled) ``` **State Sync Consideration:** If validators need to quickly sync using state sync, they may temporarily enable RPC on a private network segment to serve snapshots to each other. This should be done on a separate internal interface, never exposed publicly. # Tooling & Resources Source: https://docs.cosmos.network/evm/latest/documentation/getting-started/tooling-and-resources Tools, libraries, wallets, and explorers for building on Cosmos EVM. Cosmos EVM implements a complete Ethereum execution environment, so the standard development toolchain carries over without modification. You can use any of the EVM tools you already know; just point them at your chain's RPC endpoint and chain ID. ## Development Tools The following tools are a few examples of the many tools that are available for development on Cosmos EVM: | Tool | Description | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | [Hardhat](https://hardhat.org) | JavaScript/TypeScript-based framework with a flexible plugin system and Ethers.js integration. | | [Foundry](https://book.getfoundry.sh) | Rust-based toolkit with Solidity-native tests and fast execution. Includes `forge`, `cast`, `anvil`, and `chisel`. | | [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts) | Audited implementations of common standards — ERC-20, ERC-721, access control, and more. | ## Client Libraries | Library | Description | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- | | [Ethers.js](https://docs.ethers.org/v6/) | JS/TS library for contract interaction, transaction signing, and provider management. | | [Viem](https://viem.sh) | TypeScript-first, tree-shakeable library for RPC calls, ABI encoding, and contract access. Used by Wagmi. | | [Wagmi](https://wagmi.sh) | React hooks for wallet connection, chain state, and contract reads/writes. Built on Viem. | | [RainbowKit](https://www.rainbowkit.com) | React component library for wallet connection UI. Integrates with Wagmi. | ## Wallets See the [quick-start guide](/evm/latest/documentation/getting-started/build-a-chain/quick-start#connect-a-wallet) for a walkthrough of connecting MetaMask to a local chain. The following are a few examples of the many wallets that are available for development on Cosmos EVM: | Wallet | Notes | | ------------------------------------------ | ----------------------------------------------------- | | [MetaMask](https://metamask.io) | Add network via Settings → Networks | | [Rabby](https://rabby.io) | Add network via Settings → Networks | | [WalletConnect](https://walletconnect.com) | Standard WalletConnect integration | | [Keplr](https://www.keplr.app) | Supports both Cosmos and Ethereum transaction formats | | [Leap](https://www.leapwallet.io) | Supports both Cosmos and Ethereum transaction formats | | Ledger | Compatible via MetaMask or other wallet interfaces | To add a chain manually, you'll need: the network name, RPC URL (port 8545), chain ID, and currency symbol. ## Block Explorers Cosmos EVM chains support two types of explorer: EVM explorers for Ethereum-formatted data and Cosmos explorers for Cosmos and IBC data. [Mintscan](https://mintscan.io) supports both but requires a custom integration. | Explorer | Type | Link | | ---------- | ------ | ---------------------------------------------------------- | | Blockscout | EVM | [GitHub](https://github.com/blockscout/blockscout) | | Ping.pub | Cosmos | [GitHub](https://github.com/ping-pub/explorer) | | BigDipper | Cosmos | [GitHub](https://github.com/forbole/big-dipper-2.0-cosmos) | ## Testing & Analysis | Tool | Purpose | | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | [Slither](https://github.com/crytic/slither) | Static analysis — detects common vulnerability patterns in Solidity. | | [solidity-coverage](https://github.com/sc-forks/solidity-coverage) | Reports untested code branches. Works with Hardhat and Foundry. | | [Echidna](https://github.com/crytic/echidna) | Property-based fuzzer for Solidity contracts. | | [OpenZeppelin Test Helpers](https://github.com/OpenZeppelin/openzeppelin-test-helpers) | Time manipulation, event assertions, and revert testing for Hardhat/Mocha. | # Adding EVM to an Existing Chain Source: https://docs.cosmos.network/evm/latest/documentation/migrations/add-evm-to-existing-chain Guide for integrating the EVM module into a running Cosmos chain post-genesis ## Overview This guide covers the essential configuration requirements to ensure a smooth transition from a standard Cosmos chain to an EVM-compatible chain. The integration process focuses on two key areas: account derivation and gas token configuration. These must be set before adding the EVM module to ensure compatibility with Ethereum tooling. If you're pre-genesis and may add Cosmos EVM in the future, we strongly recommend completing steps 1 and 2a now to avoid major migrations later. ## Prerequisites Before beginning the integration process, ensure you have: * A running Cosmos SDK chain with [governance enabled](/evm/latest/documentation/migrations/upgrade-handlers) * Access to modify the chain's configuration and codebase * Understanding of your chain's current [account derivation](/evm/latest/documentation/concepts/accounts) setup * Knowledge of your gas token's [decimal configuration](/evm/latest/documentation/concepts/precision-handling) These changes require a coordinated chain upgrade via governance. *** ## 1) Address Derivation Setup Proper address derivation is critical for ensuring compatibility with Ethereum wallets, CLI tools, and block explorers. This setup consists of two components that work together to generate Ethereum-compatible addresses on your Cosmos chain. ### Set Coin Type to 60 The coin type is part of the BIP44 public key derivation path and must be set to `60` ([Ethereum's standard](https://github.com/cosmos/evm/blob/f1f4c2aee76243f0ffe0dd444e05b9cb2ef9898b/crypto/hd/hdpath.go#L9)) for EVM compatibility. This ensures that wallets using standard Ethereum derivation paths can correctly generate keys for your chain. **Configure in your chain's initialization:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Set the coin type in your SDK config config := sdk.GetConfig() config.SetCoinType(60) // Ethereum's coin type config.Seal() ``` **Reference implementation:** [evmd/config/bech32.go](https://github.com/cosmos/evm/blob/f1f4c2aee76243f0ffe0dd444e05b9cb2ef9898b/evmd/config/bech32.go#L34) The coin type must be set during chain initialization and before the SDK config is sealed. ### Add EthSecp256k1 Key Type The `EthSecp256k1` key type enables Ethereum-style address derivation, which uses the last 20 bytes of the public key rather than RIPEMD-160 hashing it before encoding. This makes it possible to convert between `0x` prefixed addresses and Cosmos Bech32 address encodings—both of which may be reverted back to the public key bytes. **Reference implementations:** * Keyring options: [cmd/evmd/cmd/root.go](https://github.com/cosmos/evm/blob/f1f4c2aee76243f0ffe0dd444e05b9cb2ef9898b/evmd/cmd/evmd/cmd/root.go#L85) * Encoding config in app.go: [evmd/app.go](https://github.com/cosmos/evm/blob/f1f4c2aee76243f0ffe0dd444e05b9cb2ef9898b/evmd/app.go#L214) * Encoding config helper: [encoding/config.go](https://github.com/cosmos/evm/blob/f1f4c2aee76243f0ffe0dd444e05b9cb2ef9898b/encoding/config.go#L33) ### Why This Matters The combination of coin type 60 and `EthSecp256k1` key type ensures: * **Unified Account Access**: The same private key/mnemonic derives the same address on both Cosmos and EVM sides, so users see identical balances and can access the same funds through MetaMask or Cosmos wallets * **Wallet Compatibility**: MetaMask, Ledger, and other Ethereum wallets work seamlessly * **Address Conversion**: Users can easily convert between `0x` and Bech32 address formats for the same underlying account * **Tooling Support**: Ethereum development tools (Hardhat, Foundry, Remix) function correctly **Critical:** If you don't set this up before adding the EVM module, private keys will derive completely different addresses on the EVM side than on the Cosmos side. This makes it extremely difficult to associate users' existing Cosmos-side balances and state with their EVM accounts, potentially requiring complex migration tooling or forcing users to manually transfer funds between their disconnected accounts. *** ## 2) Gas Token Decimals Ethereum uses 18 decimals for its gas token (1 wei = 10^-18 ETH), and maintaining this standard is strongly preferred for EVM compatibility. The decimal configuration affects how gas prices are calculated and displayed across all Ethereum tooling. **Strongly Recommended: Use 18-Decimal Tokens** If you are pre-genesis or planning to add EVM support in the future, configure your gas token with 18 decimals from the start. Non-18-decimal tokens require additional module dependencies (specifically the `x/precisebank` module) and introduce complexity in managing fractional balances. This workaround is available for post-genesis chains that cannot migrate their token decimals, but it adds overhead and should be avoided when possible. ### Option A: 18-Decimal Gas Token (Preferred) If your chain already uses an 18-decimal token as the gas token, no additional configuration is needed. The EVM module will work natively with your existing token. **Genesis configuration:** ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "bank": { "denom_metadata": [ { "base": "atoken", "display": "token", "name": "Token", "symbol": "TKN", "denom_units": [ { "denom": "atoken", "exponent": 0 }, { "denom": "token", "exponent": 18 } ] } ] } } } ``` ### Option B: Non-18-Decimal Token with PreciseBank For chains with non-18-decimal gas tokens (e.g., 6 decimals like `uatom`), use the [`x/precisebank`](https://github.com/cosmos/evm/tree/main/x/precisebank) module to track fractional balances at the EVM level while maintaining the native denomination for Cosmos transactions. The `precisebank` module wraps the standard `bank` module and stores fractional balances separately. For example, if someone transfers 0.5 × 10^-6 tokens (less than 1 `uatom`) via an EVM transaction, precisebank tracks this fractional amount even though the base denomination can't represent it. ### Decimal Configuration Summary | Token Type | Configuration | Module Required | | ----------------------- | ---------------------------------- | --------------- | | 18-decimal native token | Standard setup | None | | Non-18-decimal token | ExtendedDenomOptions + precisebank | `x/precisebank` | *** ## 3) Upgrade Handler Implementation After configuring address derivation and gas token decimals, implement an upgrade handler to add the EVM module to your running chain. Refer to the [upgrade handlers documentation](./upgrade-handlers) for more details on implementing and testing upgrade handlers. *** ## 4) Testing and Validation Before proposing the upgrade on mainnet, thoroughly test on a testnet or local network. ### Pre-Upgrade Checklist * \[] Coin type set to 60 * \[] EthSecp256k1 added to keyring and signing options * \[] Gas token decimal configuration determined (18-decimal native or `x/precisebank`) * \[] Upgrade handler implemented and tested ### Post-Upgrade Validation After the upgrade executes: 1. **Verify address derivation:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Create a new key with eth_secp256k1 keys add test-key --keyring-backend test --algo eth_secp256k1 # Verify the address format matches Ethereum expectations ``` 2. **Test EVM transactions:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Send a simple value transfer using Forge's cast cast send \ --value 1ether \ --rpc-url http://localhost:8545 \ --private-key # Verify transaction succeeded and check the receipt cast receipt --rpc-url http://localhost:8545 # Check balance to confirm transfer (value shown in wei) cast balance --rpc-url http://localhost:8545 ``` This confirms: * EVM transactions are processed correctly * Gas is calculated in wei (18 decimals) * Ethereum tooling (Forge) works seamlessly with your chain 3. **Check module parameters:** ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} query vm params query feemarket params ``` *** ## Key Considerations ### Chain Halt Risk Improper configuration can cause consensus failures. Always test the complete upgrade process on a testnet that mirrors your mainnet state before proposing the governance upgrade. ### Account Migration Existing accounts on your chain will continue to work after adding the EVM module. However: * Legacy accounts using `secp256k1` keys can still interact with Cosmos SDK modules * New EVM-compatible accounts should be created with `eth_secp256k1` keys If Step 1 (address derivation setup) was skipped, users importing their mnemonic into an Ethereum wallet will see a completely different address than their Cosmos account, with no visible balances or state. This is why proper address derivation must be configured before adding the EVM module. ### Existing Balances Token balances are preserved during the upgrade: * For 18-decimal tokens: No migration needed, just [ensure bank metadata is set properly](https://github.com/cosmos/evm/blob/d97c9e5370b224b5d48e03592bb4074a42cee289/evmd/upgrades.go#L31-51). * For non-18-decimal tokens: Existing balances remain unchanged in the Cosmos SDK, but EVM interactions will use the precisebank wrapper for fractional precision *** ## Additional Resources * [Upgrade Handlers Guide](/evm/latest/documentation/migrations/upgrade-handlers) * [PreciseBank Module](/evm/latest/documentation/cosmos-sdk/modules/precisebank) * [Address Encoding](/evm/latest/documentation/concepts/encoding) * [Cosmos EVM Repository](https://github.com/cosmos/evm) # ERC20 Precompiles Migration Source: https://docs.cosmos.network/evm/latest/documentation/migrations/erc20-precompiles-migration Migration for ERC20 precompiles when upgrading to v0.4.0 **This is a mandatory breaking change for pre-v0.4.x chains with existing ERC20 token pairs.** If neglected, existing ERC20 tokens will become inaccessible, return zero balances and fail all operations. ## Impact Assessment ### Affected Chains Your chain needs this migration if you have: * IBC tokens converted to ERC20 * Token factory tokens with ERC20 representations * Any existing `DynamicPrecompiles` or `NativePrecompiles` in storage ### Symptoms if Not Migrated * ERC20 balances will show as 0 when queried via EVM * `totalSupply()` calls return 0 * Token transfers via ERC20 interface fail * Native Cosmos balances remain intact but inaccessible via EVM ## Storage Changes * Precompiles stored as concatenated hex strings in parameter storage * Keys: `"DynamicPrecompiles"` and `"NativePrecompiles"` * Format: Multiple addresses concatenated as 42-character hex strings * Dedicated prefix stores for each precompile type * Keys: `types.KeyPrefixDynamicPrecompiles` and `types.KeyPrefixNativePrecompiles` * Individual storage entries per address ## Implementation ### Quick Start Add to your existing upgrade handler: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // In your upgrade handler store := ctx.KVStore(storeKeys[erc20types.StoreKey]) const addressLength = 42 // "0x" + 40 hex characters // Migrate dynamic precompiles (IBC tokens, token factory) if oldData := store.Get([]byte("DynamicPrecompiles")); len(oldData) > 0 { for i := 0; i < len(oldData); i += addressLength { address := common.HexToAddress(string(oldData[i : i+addressLength])) erc20Keeper.SetDynamicPrecompile(ctx, address) } store.Delete([]byte("DynamicPrecompiles")) } // Migrate native precompiles if oldData := store.Get([]byte("NativePrecompiles")); len(oldData) > 0 { for i := 0; i < len(oldData); i += addressLength { address := common.HexToAddress(string(oldData[i : i+addressLength])) erc20Keeper.SetNativePrecompile(ctx, address) } store.Delete([]byte("NativePrecompiles")) } ```
Complete Implementation Example ### Create Upgrade Handler ```go app/upgrades/v040/handler.go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package v040 import ( "context" storetypes "cosmossdk.io/store/types" upgradetypes "cosmossdk.io/x/upgrade/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" erc20keeper "github.com/cosmos/evm/x/erc20/keeper" erc20types "github.com/cosmos/evm/x/erc20/types" "github.com/ethereum/go-ethereum/common" ) const UpgradeName = "v0.4.0" func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, keepers *UpgradeKeepers, storeKeys map[string]*storetypes.KVStoreKey, ) upgradetypes.UpgradeHandler { return func(c context.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { ctx := sdk.UnwrapSDKContext(c) ctx.Logger().Info("Starting v0.4.0 upgrade...") // Run standard module migrations vm, err := mm.RunMigrations(ctx, configurator, vm) if err != nil { return vm, err } // Migrate ERC20 precompiles if err := migrateERC20Precompiles(ctx, storeKeys[erc20types.StoreKey], keepers.Erc20Keeper); err != nil { return vm, err } ctx.Logger().Info("v0.4.0 upgrade complete") return vm, nil } } ``` ### Implement Migration Logic ```go app/upgrades/v040/erc20_migration.go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package v040 import ( sdk "github.com/cosmos/cosmos-sdk/types" storetypes "cosmossdk.io/store/types" erc20keeper "github.com/cosmos/evm/x/erc20/keeper" "github.com/ethereum/go-ethereum/common" ) func migrateERC20Precompiles( ctx sdk.Context, storeKey *storetypes.KVStoreKey, erc20Keeper erc20keeper.Keeper, ) error { store := ctx.KVStore(storeKey) const addressLength = 42 // "0x" + 40 hex characters migrations := []struct { oldKey string setter func(sdk.Context, common.Address) description string }{ { oldKey: "DynamicPrecompiles", setter: erc20Keeper.SetDynamicPrecompile, description: "dynamic precompiles (token factory, IBC tokens)", }, { oldKey: "NativePrecompiles", setter: erc20Keeper.SetNativePrecompile, description: "native precompiles", }, } for _, migration := range migrations { oldData := store.Get([]byte(migration.oldKey)) if len(oldData) == 0 { ctx.Logger().Info("No legacy data found", "type", migration.description) continue } addressCount := len(oldData) / addressLength ctx.Logger().Info("Migrating precompiles", "type", migration.description, "count", addressCount, ) migratedCount := 0 for i := 0; i < len(oldData); i += addressLength { if i+addressLength > len(oldData) { ctx.Logger().Error("Invalid data length", "type", migration.description, "position", i, ) break } addressStr := string(oldData[i : i+addressLength]) address := common.HexToAddress(addressStr) // Validate address if address == (common.Address{}) { ctx.Logger().Warn("Skipping zero address", "type", migration.description, "raw", addressStr, ) continue } // Migrate to new storage migration.setter(ctx, address) migratedCount++ ctx.Logger().Debug("Migrated precompile", "type", migration.description, "address", address.String(), "index", migratedCount, ) } // Clean up old storage store.Delete([]byte(migration.oldKey)) ctx.Logger().Info("Migration complete", "type", migration.description, "migrated", migratedCount, "expected", addressCount, ) } return nil } ``` ### Register Upgrade Handler ```go app/app.go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( v040 "github.com/yourchain/app/upgrades/v040" ) func (app *App) RegisterUpgradeHandlers() { app.UpgradeKeeper.SetUpgradeHandler( v040.UpgradeName, v040.CreateUpgradeHandler( app.ModuleManager, app.configurator, &v040.UpgradeKeepers{ Erc20Keeper: app.Erc20Keeper, }, app.keys, ), ) } ```
## Testing ### Pre-Upgrade Verification ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Query existing token pairs mantrachaind query erc20 token-pairs --output json | jq # Check ERC20 balances for a known address cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url http://localhost:8545 # Export state for backup mantrachaind export > pre-upgrade-state.json ``` ### Post-Upgrade Verification ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Verify precompiles are accessible cast call $TOKEN_ADDRESS "totalSupply()" --rpc-url http://localhost:8545 # Check balance restoration cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url http://localhost:8545 # Test token transfer cast send $TOKEN_ADDRESS "transfer(address,uint256)" $RECIPIENT 1000 \ --private-key $PRIVATE_KEY --rpc-url http://localhost:8545 # Verify in exported state mantrachaind export | jq '.app_state.erc20.dynamic_precompiles' ``` ## Integration Test ```go tests/upgrade_test.go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func TestERC20PrecompileMigration(t *testing.T) { // Setup test environment app, ctx := setupTestApp(t) // Create legacy storage entries store := ctx.KVStore(app.keys[erc20types.StoreKey]) // Add test addresses in old format dynamicAddresses := []string{ "0x6eC942095eCD4948d9C094337ABd59Dc3c521005", "0x1234567890123456789012345678901234567890", } dynamicData := "" for _, addr := range dynamicAddresses { dynamicData += addr } store.Set([]byte("DynamicPrecompiles"), []byte(dynamicData)) // Run migration err := migrateERC20Precompiles(ctx, app.keys[erc20types.StoreKey], app.Erc20Keeper) require.NoError(t, err) // Verify migration migratedAddresses := app.Erc20Keeper.GetDynamicPrecompiles(ctx) require.Len(t, migratedAddresses, len(dynamicAddresses)) // Verify old storage is cleaned oldData := store.Get([]byte("DynamicPrecompiles")) require.Nil(t, oldData) } ``` ## Verification Checklist * [ ] Test migration on testnet first * [ ] Document all existing token pairs * [ ] Verify ERC20 balances post-upgrade * [ ] Test token transfers work * [ ] Confirm IBC token conversions function ## References * [GitHub Issue #424](https://github.com/cosmos/evm/issues/424) * [MANTRA-Chain Implementation](https://github.com/MANTRA-Chain/mantrachain/pull/409) * [E2E Test Suite](https://github.com/MANTRA-Chain/mantrachain-e2e/pull/41) # Migrating off x/precisebank: Gas Converter Precompile Source: https://docs.cosmos.network/evm/latest/documentation/migrations/gas-converter-migration Run an 18-decimal gas token alongside a 6-decimal staking denom using a converter precompile, replacing the deprecated x/precisebank module. The code on this page is example code, not a production-ready implementation. It is not part of `cosmos/evm` and is not tested or maintained by the Cosmos EVM team. If you adopt this approach, the code lives in your application and you are responsible for auditing and maintaining it going forward. Applies to: chains on `cosmos/evm` v0.7.x+ with a 6-decimal staking denom. Scope: every change lives in your application, no fork of `cosmos/evm` is needed. Instead of scaling your 6-decimal denom (`ustake`) into the EVM, launch the EVM with a separate, natively 18-decimal gas denom (`astake`). A precompile is the sole mint/burn authority for `astake`, converting against escrowed `ustake` at a fixed rate: ``` 1 ustake = 10^12 astake deposit: escrow ustake → mint ustakeAmount × 10^12 astake to caller withdraw: burn astake → release astakeAmount ÷ 10^12 ustake to caller ``` `astake` is always fully collateralized. Assert this in your e2e tests (x/crisis is deprecated): ``` supply(astake) == 10^12 × balance(gasconverter module account, "ustake") ``` Staking, governance, inflation, and all existing balances stay on `ustake` untouched. Because `astake` is a plain 18-decimal denom (`EvmDenom == ExtendedDenom`), `x/vm`'s denom validation passes as-is, avoiding the scaled-denom machinery `x/precisebank` existed to support. ## Solidity interface ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface IGasConverter { function deposit(uint256 ustakeAmount) external returns (bool); function withdraw(uint256 astakeAmount) external returns (bool); // multiple of 1e12 event Deposit(address indexed account, uint256 ustakeAmount, uint256 astakeAmount); event Withdraw(address indexed account, uint256 astakeAmount, uint256 ustakeAmount); } ``` Neither method is payable: `ustake` is not the EVM-native token, so it cannot travel as `msg.value`. ## Precompile Standard stateful precompile in your app (the `distribution` precompile is the reference structure), at a free address: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package gasconverter const ( ModuleName = "gasconverter" UnderlyingDenom = "ustake" PrecompileAddress = "0x0000000000000000000000000000000000000900" // stock set ends at 0x807 ) var ScalingFactor = math.NewInt(1_000_000_000_000) // The shared precompiles/common.BankKeeper deliberately lacks mint/burn; // construct with the real bankkeeper.Keeper, which satisfies this superset. type BankKeeper interface { cmn.BankKeeper SendCoinsFromAccountToModule(ctx context.Context, sender sdk.AccAddress, module string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx context.Context, module string, recipient sdk.AccAddress, amt sdk.Coins) error MintCoins(ctx context.Context, module string, amt sdk.Coins) error BurnCoins(ctx context.Context, module string, amt sdk.Coins) error } func NewPrecompile(bankKeeper BankKeeper) *Precompile { return &Precompile{ Precompile: cmn.Precompile{ KvGasConfig: storetypes.KVGasConfig(), TransientKVGasConfig: storetypes.TransientGasConfig(), ContractAddress: common.HexToAddress(PrecompileAddress), // Replays bank events into the stateDB. It extracts only the EVM // denom, so the ustake legs journal as zero. BalanceHandlerFactory: cmn.NewBalanceHandlerFactory(bankKeeper), }, ABI: ABI, bankKeeper: bankKeeper, } } func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { // RunNativeAction journals bank writes on the stateDB so mints revert with // the EVM tx. A mint that escapes a revert is an inflation bug; never // bypass this. return p.RunNativeAction(evm, contract, func(ctx sdk.Context) ([]byte, error) { return p.Execute(ctx, evm.StateDB, contract, readonly) }) } // ConvertForFees is the deposit path, shared with the ante handler below. func ConvertForFees(ctx sdk.Context, bk BankKeeper, caller sdk.AccAddress, ustakeAmt math.Int) error { ustake := sdk.NewCoin("ustake", ustakeAmt) astake := sdk.NewCoin(evmtypes.GetEVMCoinDenom(), ustakeAmt.Mul(ScalingFactor)) if err := bk.SendCoinsFromAccountToModule(ctx, caller, ModuleName, sdk.NewCoins(ustake)); err != nil { return err } if err := bk.MintCoins(ctx, ModuleName, sdk.NewCoins(astake)); err != nil { return err } return bk.SendCoinsFromModuleToAccount(ctx, ModuleName, caller, sdk.NewCoins(astake)) } ``` `withdraw` reverses the sequence (collect → `BurnCoins` → release) after rejecting amounts not a multiple of `ScalingFactor`. The mint permission is the entire "module", no AppModule, store key, or Msg service: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var maccPerms = map[string][]string{ // ... gasconverter.ModuleName: {authtypes.Minter, authtypes.Burner}, } ``` ## Ante handlers: own the chain Assemble your own ante router from the exported `cosmos/evm` decorators (copy the stock assembly) and make two changes. ### First gas A `ustake`-only account cannot pay for the EVM tx that would give it `astake`. Front the fee in a decorator ahead of the unmodified mono decorator, whose balance checks then see the converted funds: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} decorators := []sdk.AnteDecorator{ NewGasConverterDepositDecorator(bankKeeper), // below evmante.NewEVMMonoDecorator(options.AccountKeeper, options.FeeMarketKeeper, options.EvmKeeper, options.MaxTxGasWanted, &evmParams, &feemarketParams), rootante.NewTxListenerDecorator(options.PendingTxListener), } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (d GasConverterDepositDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { msgs := tx.GetMsgs() if len(msgs) == 0 { return next(ctx, tx, simulate) } _, ethTx, err := evmtypes.UnpackEthMsg(msgs[0]) if err != nil || !isDepositTx(ethTx) { // to == PrecompileAddress && selector == deposit && value == 0 return next(ctx, tx, simulate) } // This runs before the mono decorator's signature verification, so recover // the sender here. Fronting early is safe: an invalid signature makes the // mono decorator error, discarding the whole ante state, conversion included. signer := ethtypes.LatestSignerForChainID(evmtypes.GetEthChainConfig().ChainID) from, err := ethtypes.Sender(signer, ethTx) if err != nil { return next(ctx, tx, simulate) } // Convert only the shortfall; the fee is still secured before execution // (a reverting tx still pays), so there is no free-tx vector. The unspent // remainder stays with the sender as spendable astake. sender := sdk.AccAddress(from.Bytes()) cost := math.NewIntFromBigInt(ethTx.GasFeeCap()).MulRaw(int64(ethTx.Gas())) short := cost.Sub(d.bankKeeper.SpendableCoin(ctx, sender, evmtypes.GetEVMCoinDenom()).Amount) if !short.IsPositive() { return next(ctx, tx, simulate) } ustakeNeeded := short.Add(gasconverter.ScalingFactor).SubRaw(1).Quo(gasconverter.ScalingFactor) // ceil if err := gasconverter.ConvertForFees(ctx, d.bankKeeper, sender, ustakeNeeded); err != nil { return ctx, err } return next(ctx, tx, simulate) } ``` ### Cosmos tx fees In your Cosmos-tx chain, substitute a min-gas-price decorator that quotes both denoms, and a fee checker that prices `ustake` fees at the fixed rate: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // min-gas-price decorator: accept either denom against feemarket MinGasPrice minGasPrices := sdk.DecCoins{ {Denom: evmtypes.GetEVMCoinDenom(), Amount: minGasPrice}, // e.g. 10^7 {Denom: gasconverter.UnderlyingDenom, Amount: minGasPrice.QuoInt(gasconverter.ScalingFactor)}, // e.g. 10^-5 } // fee checker: normalize before the EIP-1559 base-fee check and priority underlyingFeeAmt := feeCoins.AmountOf(gasconverter.UnderlyingDenom) feeAmtDec := sdkmath.LegacyNewDecFromInt( feeCoins.AmountOf(denom).Add(underlyingFeeAmt.Mul(gasconverter.ScalingFactor))) // ... if underlyingFeeAmt.IsPositive() { // Deduct exactly what was provided; the payer may hold no astake at all. effectiveFee = feeCoins } ``` Delegators and relayers then never need `astake`. ## Mempool: wrap the VM keeper you hand to it `cosmos/evm` now runs an app-side EVM mempool, and the JSON-RPC server requires it. EVM txs enter the pool before any ante handler runs, so without this change the first-gas `deposit()` dies at the RPC with `insufficient funds for gas * price + value: balance 0`. Your app chooses the VM keeper the pool reads state through: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Pool admission values an account at its total convertible wealth. Only the // mempool reads through this wrapper; consensus, RPC balances, and the ante // use the real keeper. Admission becomes slightly optimistic (any ustake // holder passes the funding check); the ante enforces real payability at // execution, and the pool's recheck (which runs the ante) evicts the rest. type convertibleBalanceKeeper struct { evmmempool.VMKeeperI bankKeeper bankkeeper.Keeper } func (k convertibleBalanceKeeper) GetAccount(ctx sdk.Context, addr common.Address) *statedb.Account { acct := k.VMKeeperI.GetAccount(ctx, addr) if acct == nil || acct.Balance == nil { return acct } spendable := k.bankKeeper.SpendableCoin(ctx, sdk.AccAddress(addr.Bytes()), gasconverter.UnderlyingDenom) if !spendable.Amount.IsPositive() { return acct } aux, overflow := uint256.FromBig(spendable.Amount.Mul(gasconverter.ScalingFactor).BigInt()) if overflow { return acct } augmented := *acct augmented.Balance = new(uint256.Int) if _, overflow := augmented.Balance.AddOverflow(acct.Balance, aux); overflow { return acct } return &augmented } mempool := evmmempool.NewMempool(app.CreateQueryContext, logger, convertibleBalanceKeeper{VMKeeperI: app.EVMKeeper, bankKeeper: app.BankKeeper}, app.FeeMarketKeeper, app.txConfig, evmRechecker, cosmosRechecker, mpConfig, cosmosPoolMaxTx) ``` ## Registration and genesis ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go — register alongside the stock set; the precompile address never // touches the library. staticPrecompiles := precompiletypes.DefaultStaticPrecompiles(/* stock args */) gc := gasconverter.NewPrecompile(app.BankKeeper) staticPrecompiles[gc.Address()] = gc app.EVMKeeper = app.EVMKeeper.WithStaticPrecompiles(staticPrecompiles) // genesis defaults — the default list ships WITHOUT your address, and a // missing entry makes calls no-op silently. 0x…0900 sorts after the stock // set, as params validation requires. evmGenState.Params.ActiveStaticPrecompiles = append( slices.Clone(evmtypes.AvailableStaticPrecompiles), gasconverter.PrecompileAddress) // blocklist — module accounts are blocked via maccPerms; block the precompile // address too, alongside the stock precompiles. blockedPrecompilesHex := append(slices.Clone(vmtypes.AvailableStaticPrecompiles), gasconverter.PrecompileAddress) // power reduction — the evmd example sets AttoPowerReduction (1e18) for its // 18-decimal bond denom; with a 6-decimal bond denom every gentx fails at // genesis unless this is micro. sdk.DefaultPowerReduction = utils.MicroPowerReduction // 1e6 ``` ```jsonc theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // genesis.json — staking/mint/gov stay on ustake; the EVM runs on astake "staking": { "params": { "bond_denom": "ustake" } }, "mint": { "params": { "mint_denom": "ustake" } }, "evm": { "params": { "evm_denom": "astake", "extended_denom_options": null } }, "feemarket": { "params": { // quoted in astake units: ×10^12 the "no_base_fee": false, // ustake-equivalent values "base_fee": "10000000", "min_gas_price": "10000000" } }, "bank": { "denom_metadata": [{ // x/vm derives decimals from the "base": "astake", "display": "gstake", // display unit's exponent — it must be 18 "denom_units": [ { "denom": "astake", "exponent": 0 }, { "denom": "gstake", "exponent": 18 } ] }] } ``` ## Caveats * Balances are split across two denoms permanently: `eth_getBalance` shows only `astake`; explorers and wallets should present `ustake + astake/10¹²` as one asset. * MetaMask pre-checks `eth_getBalance ≥ fee + value` locally and blocks zero-`astake` senders regardless of chain rules. The ante + mempool changes cover raw-RPC and dApp flows; wallet-first onboarding still needs a Cosmos-side path (fee-granted helper, faucet, or dust airdrop at the upgrade). * Staking/distribution precompiles operate in 6-dec `ustake` while the rest of the EVM is 18-dec `astake`; EVM stakers must `withdraw()` first. Rewards arrive in both denoms (EVM fees in `astake`, inflation in `ustake`). * `astake` is IBC-transferable and becomes a distinct voucher from `ustake` on remote chains; decide before launch whether to rate-limit or filter it. * Circulating supply is just `supply(ustake)`; adding `supply(astake)/10¹²` double-counts the escrow. # Migration: v0.3.0 to v0.4.0 Source: https://docs.cosmos.network/evm/latest/documentation/migrations/migration-v0.3-to-v0.4 ## Process Overview * Create an upgrade branch and freeze schema-affecting changes * Export a pre-upgrade state and archive node configs * Bump `cosmos/evm` to v0.4.0 and align Cosmos SDK/IBC/CometBFT constraints * Rewire keepers and AppModule (imports, constructors, `RegisterServices`) * Add client context field and `SetClientCtx` method * Add pending transaction listener support * **Migrate ERC20 precompiles if you have existing token pairs** (see \[this section]./(erc20-precompiles-migration)) * Audit and migrate EVM & FeeMarket params (EIP-1559 knobs, denom/decimals) * Implement store/params migrations in your UpgradeHandler ## Prep * Create a branch: `git switch -c upgrade/evm-v0.4` * Ensure a clean build + tests green pre-upgrade * Snapshot your current params/genesis for comparison later ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} git switch -c upgrade/evm-v0.4 go test ./... evmd export > pre-upgrade-genesis.json ``` ## Dependency bumps ### Pin EVM and tidy Bump the `cosmos/evm` dependency in `go.mod`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - github.com/cosmos/evm v0.3.1 + github.com/cosmos/evm v0.4.0 ``` ### Transitive bumps Check for minor dependency bumps (e.g., `google.golang.org/protobuf`, `github.com/gofrs/flock`, `github.com/consensys/gnark-crypto`): ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go mod tidy ``` Resolve any version conflicts here before moving on. ## App constructor return type & CLI command wiring Update your app's `newApp` to return an `evmserver.Application` rather than `servertypes.Application`, and CLI commands that still expect an SDK app creator require a wrapper. ### Change the return type ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // cmd/myapp/cmd/root.go import ( evmserver "github.com/cosmos/evm/server" ) func (a appCreator) newApp( l log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions, ) evmserver.Application { // Changed from servertypes.Application // ... } ``` ### Provide a wrapper for commands that expect the SDK type Create a thin wrapper and use it for `pruning.Cmd` and `snapshot.Cmd`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // cmd/myapp/cmd/root.go sdkAppCreatorWrapper := func(l log.Logger, d dbm.DB, w io.Writer, ao servertypes.AppOptions) servertypes.Application { return ac.newApp(l, d, w, ao) } rootCmd.AddCommand( pruning.Cmd(sdkAppCreatorWrapper, myapp.DefaultNodeHome), snapshot.Cmd(sdkAppCreatorWrapper), ) ``` ### Add clientCtx and SetClientCtx Add the clientCtx to your app object: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/app.go import ( "github.com/cosmos/cosmos-sdk/client" ) type MyApp struct { // ... existing fields clientCtx client.Context } func (app *MyApp) SetClientCtx(clientCtx client.Context) { app.clientCtx = clientCtx } ``` ## Pending-tx listener support ### Imports Import the EVM ante package and geth common: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/app.go import ( "github.com/cosmos/evm/ante" "github.com/ethereum/go-ethereum/common" ) ``` ### App state: listeners slice Add a new field for listeners: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/app.go type MyApp struct { // ... existing fields pendingTxListeners []ante.PendingTxListener } ``` ### Registration method Add a public method to register a listener by txHash: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/app.go func (app *MyApp) RegisterPendingTxListener(listener func(common.Hash)) { app.pendingTxListeners = append(app.pendingTxListeners, listener) } ``` ## Precompiles: optionals + codec injection ### New imports ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/keepers/precompiles.go import ( "cosmossdk.io/core/address" addresscodec "github.com/cosmos/cosmos-sdk/codec/address" sdk "github.com/cosmos/cosmos-sdk/types" ) ``` ### Define Optionals + defaults + functional options Create a small options container with sane defaults pulled from the app's bech32 config: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/keepers/precompiles.go type Optionals struct { AddressCodec address.Codec // used by gov/staking ValidatorAddrCodec address.Codec // used by slashing ConsensusAddrCodec address.Codec // used by slashing } func defaultOptionals() Optionals { return Optionals{ AddressCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()), ValidatorAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()), ConsensusAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()), } } type Option func(*Optionals) func WithAddressCodec(c address.Codec) Option { return func(o *Optionals) { o.AddressCodec = c } } func WithValidatorAddrCodec(c address.Codec) Option { return func(o *Optionals) { o.ValidatorAddrCodec = c } } func WithConsensusAddrCodec(c address.Codec) Option { return func(o *Optionals) { o.ConsensusAddrCodec = c } } ``` ### 4.3 Update the precompile factory to accept options ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/keepers/precompiles.go func NewAvailableStaticPrecompiles( ctx context.Context, // ... other params opts ...Option, ) map[common.Address]vm.PrecompiledContract { options := defaultOptionals() for _, opt := range opts { opt(&options) } // ... rest of implementation } ``` ### 4.4 Modify individual precompile constructors **ICS-20 precompile** now needs `bankKeeper` first: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - ibcTransferPrecompile, err := ics20precompile.NewPrecompile( - stakingKeeper, + ibcTransferPrecompile, err := ics20precompile.NewPrecompile( + bankKeeper, + stakingKeeper, transferKeeper, &channelKeeper, // ... ``` **Gov precompile** now requires an `AddressCodec`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - govPrecompile, err := govprecompile.NewPrecompile(govKeeper, cdc) + govPrecompile, err := govprecompile.NewPrecompile(govKeeper, cdc, options.AddressCodec) ``` ## ERC20 Precompiles Migration **This migration is required for chains with existing ERC20 token pairs** The storage mechanism for ERC20 precompiles has fundamentally changed in v0.4.0. Without proper migration, your ERC20 tokens will become inaccessible via EVM. Include this migration with your upgrade if your chain has: * IBC tokens converted to ERC20 * Token factory tokens with ERC20 representations * Any existing `DynamicPrecompiles` or `NativePrecompiles` in storage ### Implementation For complete migration instructions, see: **[ERC20 Precompiles Migration Guide](./erc20-precompiles-migration)** Add this to your upgrade handler: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // In your upgrade handler store := ctx.KVStore(storeKeys[erc20types.StoreKey]) const addressLength = 42 // Migrate dynamic precompiles if oldData := store.Get([]byte("DynamicPrecompiles")); len(oldData) > 0 { for i := 0; i < len(oldData); i += addressLength { address := common.HexToAddress(string(oldData[i : i+addressLength])) erc20Keeper.SetDynamicPrecompile(ctx, address) } store.Delete([]byte("DynamicPrecompiles")) } // Migrate native precompiles if oldData := store.Get([]byte("NativePrecompiles")); len(oldData) > 0 { for i := 0; i < len(oldData); i += addressLength { address := common.HexToAddress(string(oldData[i : i+addressLength])) erc20Keeper.SetNativePrecompile(ctx, address) } store.Delete([]byte("NativePrecompiles")) } ``` ### Verification Post-upgrade, verify your migration succeeded: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Check ERC20 balance (should NOT be 0 if tokens existed before) cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url http://localhost:8545 # Verify precompiles in state mantrachaind export | jq '.app_state.erc20.dynamic_precompiles' ``` ## Build & quick tests 1. **Compile**: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go build ./... ``` 2. **Smoke tests** (local single-node): * Start your node; ensure RPC starts cleanly * Deploy a trivial contract; verify events and logs * Send a couple 1559 txs and confirm base-fee behavior looks sane * (Optional) register a pending-tx listener and log hashes as they enter the mempool ## Rollout checklist * Package the new binary (and Cosmovisor upgrade folder if you use it) * Confirm all validators build the same commit (no `replace` lines) * Share an `app.toml` diff only if you changed defaults; otherwise regenerate the file from the new binary and re-apply customizations * Post-upgrade: monitor mempool/pending tx logs, base-fee progression, and contract events for the first 20-50 blocks ## Pitfalls & remedies * **Forgot wrapper for CLI commands** → `pruning`/`snapshot` panic or wrong type: * Ensure you pass `sdkAppCreatorWrapper` (not `ac.newApp`) into those commands * **ICS-20 precompile build error**: * You likely didn't pass `bankKeeper` first; update the call site * **Governance precompile address parsing fails**: * Provide the correct `AddressCodec` via defaults or `WithAddressCodec(...)` * **Listeners never fire**: * Register with `RegisterPendingTxListener` during app construction or module init ## Minimal code snippets **App listeners** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/app.go import ( "github.com/cosmos/evm/ante" "github.com/ethereum/go-ethereum/common" ) type MyApp struct { // ... pendingTxListeners []ante.PendingTxListener } func (app *MyApp) RegisterPendingTxListener(l func(common.Hash)) { app.pendingTxListeners = append(app.pendingTxListeners, l) } ``` **CLI wrapper** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // cmd/myapp/cmd/root.go sdkAppCreatorWrapper := func(l log.Logger, d dbm.DB, w io.Writer, ao servertypes.AppOptions) servertypes.Application { return ac.newApp(l, d, w, ao) } rootCmd.AddCommand( pruning.Cmd(sdkAppCreatorWrapper, myapp.DefaultNodeHome), snapshot.Cmd(sdkAppCreatorWrapper), ) ``` **Precompile options & usage** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/keepers/precompiles.go opts := []Option{ // override defaults only if you use non-standard prefixes/codecs WithAddressCodec(myAcctCodec), WithValidatorAddrCodec(myValCodec), WithConsensusAddrCodec(myConsCodec), } pcs := NewAvailableStaticPrecompiles(ctx, /* ... keepers ... */, opts...) ``` ## Additional Breaking Changes (v0.3.1 → v0.4.1) **Credits**: Thanks to the Mantrachain team for identifying these additional migration requirements. ### CallEVM Signature Change The `CallEVM` function now requires a `gasCap` parameter of type `*big.Int`. You can pass `nil` for default behavior. **Function Signature** (from `x/vm/keeper/call_evm.go:20`): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) CallEVM( ctx sdk.Context, abi abi.ABI, from, contract common.Address, commit bool, gasCap *big.Int, // New parameter method string, args ...interface{}, ) (*types.MsgEthereumTxResponse, error) ``` **Migration Example** (from `x/erc20/keeper/evm.go:87`): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Before v0.4.x - gasCap parameter didn't exist - res, err := k.evmKeeper.CallEVM(ctx, erc20, types.ModuleAddress, contract, false, "decimals") // After v0.4.x - gasCap parameter required (can be nil) + res, err := k.evmKeeper.CallEVM(ctx, erc20, types.ModuleAddress, contract, false, nil, "decimals") ``` **With Custom Gas Cap**: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // If you need to specify a gas cap import "math/big" gasLimit := big.NewInt(100000) res, err := k.evmKeeper.CallEVM(ctx, abi, from, contract, false, gasLimit, "balanceOf", account) ``` **Real usage examples**: * `x/erc20/keeper/evm.go:87` - decimals call * `x/erc20/keeper/evm.go:143` - balanceOf call * `x/ibc/callbacks/keeper/keeper.go:193` - approve call with custom gas cap ### Test Keyring Import Path The test keyring package has been reorganized into its own subdirectory. **Import Change**: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - testkeyring "github.com/cosmos/evm/testutil" + testkeyring "github.com/cosmos/evm/testutil/keyring" ``` **Example from Tests** (from `tests/integration/precompiles/erc20/test_setup.go:10`): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package erc20 import ( "github.com/stretchr/testify/suite" testkeyring "github.com/cosmos/evm/testutil/keyring" ) type PrecompileTestSuite struct { suite.Suite keyring testkeyring.Keyring } func (s *PrecompileTestSuite) SetupTest() { // Create keyring with 2 accounts keyring := testkeyring.New(2) // Get addresses for pre-funding options := []network.ConfigOption{ network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), } } ``` **Available Functions** (from `testutil/keyring/keyring.go:58`): * `New(nAccs int) Keyring` - Create keyring with n accounts * `GetPrivKey(index int)` - Get private key * `GetAddr(index int)` - Get Ethereum address * `GetAccAddr(index int)` - Get Cosmos SDK address * `GetAllAccAddrs()` - Get all SDK addresses ## Verify before tagging * `go.mod` has no `replace` lines for `github.com/cosmos/evm` * Node boots with expected RPC namespaces * Contracts deploy/call; events stream; fee market behaves * (If applicable) ICS-20 transfers work and precompiles execute # Migration: v0.4.x to v0.5.0 Source: https://docs.cosmos.network/evm/latest/documentation/migrations/migration-v0.4-to-v0.5 ## 0) Prep * Create a branch: `git switch -c upgrade/evm-v0.5`. * Ensure a clean build + tests green pre-upgrade. * Snapshot your current params/genesis for comparison later. *** ## 1) Dependency bumps (go.mod) * Bump `github.com/cosmos/evm` to v0.5.0 and run: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go mod tidy ``` *** ## 2) Fix `"github.com/cosmos/evm/types" imports` `v0.5.0` removes `github.com/cosmos/evm/types` and moves files to their folders, respective to function. For a complete list of changes, refer to [this PR](https://github.com/cosmos/evm/pull/639). The following list includes references within `evmd` that have been moved. ### Summary of import changes in `evmd`: **Removed import:** ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - cosmosevmtypes "github.com/cosmos/evm/types" ``` **Added imports:** ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + antetypes "github.com/cosmos/evm/ante/types" + evmaddress "github.com/cosmos/evm/encoding/address" + "github.com/cosmos/evm/utils" ``` ### Detailed mapping of moved items: * **`AttoPowerReduction`** → moved to `"github.com/cosmos/evm/utils"` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - sdk.DefaultPowerReduction = cosmosevmtypes.AttoPowerReduction + sdk.DefaultPowerReduction = utils.AttoPowerReduction ``` * **`HasDynamicFeeExtensionOption`** → moved to `"github.com/cosmos/evm/ante/types"` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - ExtensionOptionChecker: cosmosevmtypes.HasDynamicFeeExtensionOption, + ExtensionOptionChecker: antetypes.HasDynamicFeeExtensionOption, ``` * **Address Codec functions** → new package `"github.com/cosmos/evm/encoding/address"` Use `evmaddress.NewEvmCodec()` for address codec initialization: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.AccountKeeper = authkeeper.NewAccountKeeper( appCodec, runtime.NewKVStoreService(keys[authtypes.StoreKey]), authtypes.ProtoBaseAccount, evmconfig.GetMaccPerms(), evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix()), sdk.GetConfig().GetBech32AccountAddrPrefix(), authAddr, ) ``` * **`Bip44CoinType`, `BIP44HDPath`** → moved to `"github.com/cosmos/evm/crypto/hd"` * **`GenesisState`** → removed as a duplicate object can be found in the `evmd` folder and a testing version is in `"github.com/cosmos/evm/testutil"` *** ## 3) App wiring in `app.go` ### Mempool #### Custom initializer The mempool configuration can now be handled by a helper function. If you prefer to use the configuration from `app.toml` and CLI flags, you can refactor your mempool setup: * **Before**: Manual configuration in app.go ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} mempoolConfig := &evmmempool.EVMMempoolConfig{ AnteHandler: app.GetAnteHandler(), BlockGasLimit: blockGasLimit, MinTip: minTip, } evmMempool := evmmempool.NewExperimentalEVMMempool( app.CreateQueryContext, logger, app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, app.clientCtx, mempoolConfig, ) ``` * **After**: Using helper function (optional). See [https://github.com/cosmos/evm/blob/42e1141c9b00432020e69945e565f60f80ab501d/evmd/mempool.go](https://github.com/cosmos/evm/blob/42e1141c9b00432020e69945e565f60f80ab501d/evmd/mempool.go) for the reference implementation ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if err := app.configureEVMMempool(appOpts, logger); err != nil { panic(fmt.Sprintf("failed to configure EVM mempool: %s", err.Error())) } ``` The helper function reads configuration from `appOpts` or applies defaults if omitted. Note that `NewExperimentalEVMMempool` now takes an additional `cosmosPoolMaxTx` parameter, with a recommended default value being `4096` or `0` (uncapped). #### Simple config migration If `BlockGasLimit` is 0, it defaults to `100_000_000`. If `BroadCastTxFn` is not set, it's also set to a default value. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} mempoolConfig := &evmmempool.EVMMempoolConfig{ AnteHandler: app.GetAnteHandler(), BlockGasLimit: 100_000_000, // or 0 to use default } evmMempool := evmmempool.NewExperimentalEVMMempool( app.CreateQueryContext, logger, app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, app.clientCtx, mempoolConfig ) ``` #### Advanced setups: migrate your customizations PR [#496](https://github.com/cosmos/evm/pull/496) replaced pre-built pools with configs in `EVMMempoolConfig`: * Replace pools with configs * Removed: `TxPool *txpool.TxPool`, `CosmosPool sdkmempool.ExtMempool` * Added: `LegacyPoolConfig *legacypool.Config`, `CosmosPoolConfig *sdkmempool.PriorityNonceMempoolConfig[math.Int]` If you built custom pools yourself: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} mempoolConfig := &evmmempool.EVMMempoolConfig{ - TxPool: customTxPool, - CosmosPool: customCosmosPool, + LegacyPoolConfig: &legacyCfg, // or nil for defaults + CosmosPoolConfig: &cosmosCfg, // or nil for defaults AnteHandler: app.GetAnteHandler(), BroadCastTxFn: myBroadcast, // optional } ``` Example custom configs: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // EVM legacy txpool tuning legacyCfg := legacypool.DefaultConfig legacyCfg.PriceLimit = 2 mempoolConfig.LegacyPoolConfig = &legacyCfg // Cosmos priority mempool tuning cosmosCfg := sdkmempool.PriorityNonceMempoolConfig[math.Int]{} cosmosCfg.TxPriority = sdkmempool.TxPriority[math.Int]{ GetTxPriority: func(goCtx context.Context, tx sdk.Tx) math.Int { // Custom priority function }, Compare: func(a, b math.Int) int { return a.BigInt().Cmp(b.BigInt()) }, MinValue: math.ZeroInt(), } mempoolConfig.CosmosPoolConfig = &cosmosCfg // Custom EVM broadcast (optional) mempoolConfig.BroadCastTxFn = func(txs []*ethtypes.Transaction) error { return nil } ``` #### New Configuration Options PR [#698](https://github.com/cosmos/evm/pull/698) adds new configuration options for the EVM mempool that can be set via `app.toml` or CLI flags. These options allow fine-tuning of the EVM legacy pool behavior. ##### Configuration via `app.toml` The following mempool configuration options are now available in `app.toml` under the `[evm.mempool]` section: ```toml theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [evm.mempool] # PriceLimit is the minimum gas price to enforce for acceptance into the pool (in wei) price-limit = 1 # PriceBump is the minimum price bump percentage to replace an already existing transaction (nonce) price-bump = 10 # AccountSlots is the number of executable transaction slots guaranteed per account account-slots = 16 # GlobalSlots is the maximum number of executable transaction slots for all accounts global-slots = 5120 # AccountQueue is the maximum number of non-executable transaction slots permitted per account account-queue = 64 # GlobalQueue is the maximum number of non-executable transaction slots for all accounts global-queue = 1024 # Lifetime is the maximum amount of time non-executable transaction are queued lifetime = "3h0m0s" ``` ##### Configuration via CLI Flags These options can also be set via CLI flags: * `--evm.mempool.price-limit` (default: 1) * `--evm.mempool.price-bump` (default: 10) * `--evm.mempool.account-slots` (default: 16) * `--evm.mempool.global-slots` (default: 5120) * `--evm.mempool.account-queue` (default: 64) * `--evm.mempool.global-queue` (default: 1024) * `--evm.mempool.lifetime` (default: 3h0m0s) ##### Cosmos Mempool Max Transactions A new flag `--mempool.max-txs` allows limiting the maximum number of transactions in the Cosmos mempool. Set to 0 or -1 for unbounded (default: 0). The function signature for `NewExperimentalEVMMempool` also changed to add a cosmosPoolMaxTx field: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewExperimentalEVMMempool( getCtxCallback func(height int64, prove bool) (sdk.Context, error), logger log.Logger, vmKeeper VMKeeperI, feeMarketKeeper FeeMarketKeeperI, txConfig client.TxConfig, clientCtx client.Context, config *EVMMempoolConfig, + cosmosPoolMaxTx int, ) ``` #### EVM Chain ID The EVM chain ID is now retrieved directly from `appOpts` instead of being passed as a parameter. In `app.go`, the chain ID is obtained using: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} evmChainID := cast.ToUint64(appOpts.Get(srvflags.EVMChainID)) ``` See `evmd/app.go:216` for the reference implementation. The EVM Keeper now also takes in evmChainID as a parameter: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.EVMKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], keys, authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.PreciseBankKeeper, app.StakingKeeper, app.FeeMarketKeeper, &app.ConsensusParamsKeeper, &app.Erc20Keeper, + evmChainID, tracer, ) ``` #### Function Signature Changes In `app.go`, remove evmChainID and evmAppOptions from the NewApp signature. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // NewExampleApp returns a reference to an initialized EVMD. func NewExampleApp( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, - evmChainID uint64, - evmAppOptions evmconfig.EVMOptionsFn, baseAppOptions ...func(*baseapp.BaseApp), ) *EVMD { ``` Afterwards, fix any reference to the function by removing the inputs. Then, remove any reference of evmAppOptions being called: `app.go` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - if err := evmAppOptions(evmChainID); err != nil { - panic(err) - } ``` `root.go` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - noOpEvmAppOptions := func(_ uint64) error { - return nil - } ``` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - if initClientCtx.ChainID != "" { - if err := config.EvmAppOptions(config.EVMChainID); err != nil { - panic(err) - } - } ``` `evmd_config.go`, `chain_id.go`, `config.go`, `constants.go` have been moved to `github.com/cosmos/evm/config` and may be removed to your repo. ### Denom Configs [#661](https://github.com/cosmos/evm/pull/661) removes the instantiation of chain configs via app.go and moves them to state or genesis. It is critical to remove any use of EvmAppOptions as calling the configurator will panic the chain at runtime during startup. ### Default Precompiles Default precompiles have been moved to `/evm/precompiles/types/defaults.go` and the function name was changed to `DefaultStaticPrecompiles`. The function signature has also changed, and now takes pointers as inputs for the `Erc20Keeper` and `TransferKeeper`. Finally, the `WithStaticPrecompiles` builder function can now happen *alongside the keeper instantiation*, and not after. The new wiring is shown below: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.EVMKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], keys, authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.PreciseBankKeeper, app.StakingKeeper, app.FeeMarketKeeper, &app.ConsensusParamsKeeper, &app.Erc20Keeper, evmChainID, tracer, ).WithStaticPrecompiles( precompiletypes.DefaultStaticPrecompiles( *app.StakingKeeper, app.DistrKeeper, app.PreciseBankKeeper, &app.Erc20Keeper, // UPDATED &app.TransferKeeper, // UPDATED app.IBCKeeper.ChannelKeeper, app.GovKeeper, app.SlashingKeeper, appCodec, ), ) ``` #### UpgradeHandler As the configs have been moved to state and genesis, you must include an UpgradeHandler if your chain does not satisfy the following conditions. 1. Your EVM Denom set in the `x/vm` params must have `DenomMetadata` registered for it in `x/bank`. 2. Your EVM Denom must have a display denom associated with it in `DenomMetadata`. 1. The display denom for the EVM Denom must have an accurate decimal value (i.e. for `uatom`, `atom` must have a decimal value of 6. 3. Your chain is an 18-decimal chain. 4. Call `InitEvmCoinInfo` to initialize EVM coin metadata in the module store. In your UpgradeHandler: * **If your chain does not have DenomMetadata set for the EVM Denom, you must include it.** * **If your chain's EVM denom is *not* 18 decimals, you must add ExtendedDenomOptions to your `x/vm` params.** * **After setting metadata, initialize EVM coin info:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // After setting denom metadata if err := app.EVMKeeper.InitEvmCoinInfo(ctx); err != nil { return nil, err } ``` Please refer to the [upgrade example](https://github.com/cosmos/evm/blob/main/evmd/upgrades.go) for the complete implementation. *** ## 4) Build & quick tests ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go build ./... ``` Smoke test on a single node: * Send a few EVM txs; confirm promotion/broadcast (or your `BroadCastTxFn`). * Send Cosmos txs; confirm ordering reflects your `CosmosPoolConfig` (if customized). # Migration: v0.5.0 to v0.6.0 Source: https://docs.cosmos.network/evm/latest/documentation/migrations/migration-v0.5-to-v0.6 ## 0) Prep * Create a branch: `git switch -c upgrade/evm-v0.6`. * Ensure a clean build + tests green pre-upgrade. * Snapshot your current params/genesis for comparison later. *** ## 1) Dependency bumps (go.mod) * Bump `github.com/cosmos/evm` to v0.6.0 and run: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go mod tidy ``` *** ## 2) App Wiring Changes ### IBC Transfer Module v0.6.0 removes the custom IBC transfer keeper override and now uses the official IBC-Go transfer keeper directly. This means that **ERC20 conversions via Cosmos IBC transfer transactions are not possible**. These are now only handled in the ICS20 precompile, and any ERC20 transfer must be initiated through there. **Changes required in `app.go`:** 1. **Update imports** - Replace custom transfer imports with official IBC-Go imports: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - import ( - "github.com/cosmos/evm/x/ibc/transfer" - transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper" - transferv2 "github.com/cosmos/evm/x/ibc/transfer/v2" - ibctransfer "github.com/cosmos/ibc-go/v11/modules/apps/transfer" - ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" - ) + import ( + transfer "github.com/cosmos/ibc-go/v11/modules/apps/transfer" + transferkeeper "github.com/cosmos/ibc-go/v11/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" + transferv2 "github.com/cosmos/ibc-go/v11/modules/apps/transfer/v2" + ) ``` 2. **Update TransferKeeper initialization** - Remove ERC20 keeper parameter: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.TransferKeeper = transferkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), + nil, // ICS4Wrapper param app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, - app.Erc20Keeper, // Remove: no longer passed to transfer keeper authAddr, ) ``` 3. **Update module registration** - Use official transfer module: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.BasicModuleManager = module.NewBasicManager( // ... other modules - ibctransfertypes.ModuleName: transfer.AppModuleBasic{AppModuleBasic: &ibctransfer.AppModuleBasic{}}, + ibctransfertypes.ModuleName: transfer.AppModuleBasic{}, ) ``` 4. **Update ICS20 precompile wiring** - Pass ERC20 keeper to ICS20 precompile: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} precompiletypes.DefaultStaticPrecompiles( *app.StakingKeeper, app.DistrKeeper, app.PreciseBankKeeper, &app.Erc20Keeper, &app.TransferKeeper, app.IBCKeeper.ChannelKeeper, app.GovKeeper, app.SlashingKeeper, appCodec, ) ``` The ICS20 precompile now takes the ERC20 keeper as a parameter (instead of the transfer keeper receiving it). This allows the precompile to handle ERC20 conversions directly. *** ## 3) Breaking API Changes ### StateDB Requirements v0.6.0 introduces significant changes to event tracking and state management. All EVM execution functions now require an explicit `stateDB` parameter and a `callFromPrecompile` flag to properly handle event management and state transitions. **NOTE:** The only function calls affected are `CallEVM`, `CallEVMWithData`, `ApplyMessage`, and `ApplyMessageWithConfig`. These are typically used in common precompiles and logic that calls back into the EVM from the SDK. If your project does not use these functions, then no steps need to be taken for the upgrade. #### Advanced Changes The following functions have updated signatures: ##### `CallEVM` **Before (v0.5.x):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) CallEVM( ctx sdk.Context, abi abi.ABI, from, contract common.Address, commit bool, gasCap *big.Int, method string, args ...interface{}, ) (*types.MsgEthereumTxResponse, error) ``` **After (v0.6.0):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) CallEVM( ctx sdk.Context, stateDB *statedb.StateDB, abi abi.ABI, from, contract common.Address, commit bool, callFromPrecompile bool, gasCap *big.Int, method string, args ...interface{}, ) (*types.MsgEthereumTxResponse, error) ``` ##### `CallEVMWithData` **Before (v0.5.x):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) CallEVMWithData( ctx sdk.Context, from common.Address, contract *common.Address, data []byte, commit bool, gasCap *big.Int, ) (*types.MsgEthereumTxResponse, error) ``` **After (v0.6.0):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) CallEVMWithData( ctx sdk.Context, stateDB *statedb.StateDB, from common.Address, contract *common.Address, data []byte, commit bool, callFromPrecompile bool, gasCap *big.Int, ) (*types.MsgEthereumTxResponse, error) ``` ##### `ApplyMessage` **Before (v0.5.x):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k *Keeper) ApplyMessage( ctx sdk.Context, msg core.Message, tracer *tracing.Hooks, commit bool, internal bool, ) (*types.MsgEthereumTxResponse, error) ``` **After (v0.6.0):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k *Keeper) ApplyMessage( ctx sdk.Context, stateDB *statedb.StateDB, msg core.Message, tracer *tracing.Hooks, commit bool, callFromPrecompile bool, internal bool, ) (*types.MsgEthereumTxResponse, error) ``` ##### `ApplyMessageWithConfig` **Before (v0.5.x):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k *Keeper) ApplyMessageWithConfig( ctx sdk.Context, msg core.Message, tracer *tracing.Hooks, commit bool, cfg *statedb.EVMConfig, txConfig statedb.TxConfig, internal bool, overrides *rpctypes.StateOverride, ) (*types.MsgEthereumTxResponse, error) ``` **After (v0.6.0):** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k *Keeper) ApplyMessageWithConfig( ctx sdk.Context, stateDB *statedb.StateDB, msg core.Message, tracer *tracing.Hooks, commit bool, callFromPrecompile bool, cfg *statedb.EVMConfig, txConfig statedb.TxConfig, internal bool, overrides *rpctypes.StateOverride, ) (*types.MsgEthereumTxResponse, error) ``` ### Migration Steps #### For Non-Precompile Contexts If you're calling EVM functions from **outside** a precompile (e.g., from a module keeper, message server, or query handler): 1. Create a new `stateDB` before calling EVM functions 2. Pass `false` for the `callFromPrecompile` parameter **Example:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( "github.com/cosmos/evm/x/vm/statedb" ) // Before (v0.5.x) res, err := k.evmKeeper.CallEVM( ctx, abi, from, contract, false, // commit nil, // gasCap "balanceOf", account, ) // After (v0.6.0) stateDB := statedb.New(ctx, k.evmKeeper, statedb.NewEmptyTxConfig()) res, err := k.evmKeeper.CallEVM( ctx, stateDB, abi, from, contract, false, // commit false, // callFromPrecompile nil, // gasCap "balanceOf", account, ) ``` #### For Precompile Contexts If you're calling EVM functions from **within** a precompile: 1. **Reuse the existing `stateDB`** from your precompile context (do not create a new one) 2. Pass `true` for the `callFromPrecompile` parameter 3. The existing `stateDB` is typically available as a parameter in your precompile function **Example:** ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // In your precompile's Run() method, you'll have access to stateDB func (p *MyPrecompile) Run( evm *vm.EVM, contract *vm.Contract, readOnly bool, ) ([]byte, error) { stateDB := evm.StateDB.(*statedb.StateDB) // Use the existing stateDB and set callFromPrecompile=true res, err := p.evmKeeper.CallEVM( ctx, stateDB, // reuse existing stateDB abi, from, contract, true, // commit (will flush to cache context) true, // callFromPrecompile nil, // gasCap "transfer", recipient, amount, ) } ``` ### Important Notes * **Never pass `nil` for `stateDB`**: This will return `ErrNilStateDB` error * **Commit behavior in precompiles**: When `commit=true` and `callFromPrecompile=true`, the state changes are flushed to the cache context rather than fully committed. This prevents collapsing the cache stack in nested call scenarios. ### EVMKeeper Interface Changes If you implement or mock the `EVMKeeper` interface, update your implementation: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type EVMKeeper interface { // Updated signatures ApplyMessage( ctx sdk.Context, stateDB *statedb.StateDB, msg core.Message, tracer *tracing.Hooks, commit, callFromPrecompile, internal bool, ) (*evmtypes.MsgEthereumTxResponse, error) CallEVM( ctx sdk.Context, stateDB *statedb.StateDB, abi abi.ABI, from, contract common.Address, commit, callFromPrecompile bool, gasCap *big.Int, method string, args ...interface{}, ) (*evmtypes.MsgEthereumTxResponse, error) CallEVMWithData( ctx sdk.Context, stateDB *statedb.StateDB, from common.Address, contract *common.Address, data []byte, commit bool, callFromPrecompile bool, gasCap *big.Int, ) (*evmtypes.MsgEthereumTxResponse, error) // ... other methods } ``` *** ## 4) ERC20 Keeper Interface Changes The `ERC20Keeper` interface has new methods: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ERC20Keeper interface { // ... existing methods // New methods in v0.6.0 IsERC20Enabled(ctx sdk.Context) bool GetTokenPairID(ctx sdk.Context, token string) []byte ConvertERC20IntoCoinsForNativeToken( ctx sdk.Context, stateDB *statedb.StateDB, contract ethcommon.Address, amount math.Int, receiver sdk.AccAddress, sender ethcommon.Address, commit bool, callFromPrecompile bool, ) (*erc20types.MsgConvertERC20Response, error) } ``` If you implement this interface, add these methods to your implementation. *** ## 5) Error Handling A new error type has been added: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ErrNilStateDB = errorsmod.Register(ModuleName, codeErrNilStateDB, "stateDB cannot be nil") ``` This error is returned when `nil` is passed as the `stateDB` parameter to EVM functions. *** ## 6) Build & Tests ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go build ./... go test ./... ``` ### Testing Checklist After migration, verify: * [ ] All EVM calls pass a valid `stateDB` * [ ] Non-precompile calls use `callFromPrecompile=false` * [ ] Precompile calls reuse existing `stateDB` and use `callFromPrecompile=true` * [ ] Event emission works correctly in both success and revert scenarios * [ ] State changes are properly committed or reverted *** ## Common Migration Examples ### Example 1: Module Keeper Query ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Before (v0.5.x) func (k Keeper) QueryBalance(ctx sdk.Context, addr common.Address) (*big.Int, error) { res, err := k.evmKeeper.CallEVM( ctx, erc20ABI, moduleAddr, contract, false, nil, "balanceOf", addr, ) // ... } // After (v0.6.0) func (k Keeper) QueryBalance(ctx sdk.Context, addr common.Address) (*big.Int, error) { stateDB := statedb.New(ctx, k.evmKeeper, statedb.NewEmptyTxConfig()) res, err := k.evmKeeper.CallEVM( ctx, stateDB, erc20ABI, moduleAddr, contract, false, false, nil, "balanceOf", addr, ) // ... } ``` ### Example 2: Message Server Transaction ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Before (v0.5.x) func (ms msgServer) ConvertCoin( goCtx context.Context, msg *types.MsgConvertCoin ) (*types.MsgConvertCoinResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // ... res, err := ms.evmKeeper.CallEVMWithData( ctx, moduleAddr, &contract, data, true, nil, ) // ... } // After (v0.6.0) func (ms msgServer) ConvertCoin( goCtx context.Context, msg *types.MsgConvertCoin ) (*types.MsgConvertCoinResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // ... stateDB := statedb.New(ctx, ms.evmKeeper, statedb.NewEmptyTxConfig()) res, err := ms.evmKeeper.CallEVMWithData( ctx, stateDB, moduleAddr, &contract, data, true, false, nil, ) // ... } ``` ### Example 3: Precompile Internal Call ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Before (v0.5.x) func (p *StakingPrecompile) delegate( ctx sdk.Context, evm *vm.EVM, // ... ) ([]byte, error) { // ... delegate logic ... res, err := p.evmKeeper.CallEVM( ctx, delegationABI, from, contract, true, nil, "afterDelegate", ) // ... } // After (v0.6.0) func (p *StakingPrecompile) delegate( ctx sdk.Context, evm *vm.EVM, stateDB *statedb.StateDB, // typically passed from Run() // ... ) ([]byte, error) { // ... delegate logic ... res, err := p.evmKeeper.CallEVM( ctx, stateDB, delegationABI, from, contract, true, true, nil, "afterDelegate", ) // ... } ``` # Migration: v0.6.0 to v0.7.0 Source: https://docs.cosmos.network/evm/latest/documentation/migrations/migration-v0.6-to-v0.7 v0.7.0 ships two new headline features — the **Krakatoa application-layer mempool** and **BlockSTM parallel execution** (with virtual fee collection) — plus a large dependency-stack bump (cosmos-sdk v0.54, cometbft v0.39, ibc-go v11, geth 1.17, Go 1.25). The two features are independent of each other. BlockSTM is always opt-in; Krakatoa is required for forks that already wired the v0.6 `ExperimentalEVMMempool` (the type is gone in v0.7) and optional for forks on CometBFT's stock mempool. The unconditional migration work is the dependency bump and the keeper-signature shuffle that comes with it. Optional headline features (each enables a distinct path; see Step 5): * **Krakatoa mempool** (#1030, #1053, #1112) — moves the EVM mempool out of CometBFT and into the application layer. The chain gets full control over `CheckTx` / `RecheckTx`, sudo-rechecking on promote/demote, reapable-list filtering, and pool-level lifecycle hooks. Krakatoa is the only app-side EVM mempool in v0.7; the v0.6 `ExperimentalEVMMempool` type is gone. **If your v0.6 fork wired `ExperimentalEVMMempool`, migrating to Krakatoa is required** — see Step 5a. **If your v0.6 fork ran on CometBFT's stock mempool**, Krakatoa is optional and you can stay on stock CometBFT. * **BlockSTM parallel execution** (#589, #1082, #1132) — parallel state-transition function under a software-transactional-memory scheduler, with per-tx object stores for bloom / log indexing / gas accounting and an incarnation cache for signature/auth verification. **Includes virtual fee collection** (`EndBlock` fee settlement via per-tx bank object store, 18-decimal chains only) — virtual fees are bundled with BlockSTM, not a standalone feature. Chains that don't wire the STM runner keep sequential execution. Other features in this release (unconditional): * **ICS-02 client-router precompile** (#768) — Solidity contracts can now manage IBC light clients. * **Authority params** (#1130) — `x/vm`, `x/erc20`, `x/feemarket` `MsgServer` handlers consult `AuthorityParams` from the consensus module before falling back to per-module gov authority. * **OpenTelemetry tracing** (#871, #863) — spans across `x/vm` and the JSON-RPC layer. * **JSON-RPC filter lifecycle overhaul** (#1008) — idle-timeout reclamation replacing the old global filter cap. Secondary breaking changes — IBC callback hardening, EIP-712 verification, the `x/precisebank` deprecation, the `abi.json` strict-ABI requirement (#758) — round out the upgrade. The reference `evmd` also enables [optimistic execution](https://docs.cosmos.network/main/build/building-apps/optimistic-execution) (`baseapp.SetOptimisticExecution()`). That's an SDK-level feature, not new in v0.7, but if your fork hasn't turned it on yet the upgrade is a convenient time — see Step 5c for the wiring. For deeper background on the new features, see: * [Krakatoa overview](https://docs.cosmos.network/evm/latest/documentation/concepts/mempool) * [Krakatoa configuration](https://docs.cosmos.network/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/mempool-integration) * [BlockSTM and parallel execution](https://docs.cosmos.network/sdk/latest/experimental/blockstm#block-stm-parallel-transaction-execution) If you're skipping a release, read the [v0.5.x → v0.6.0 guide](/evm/latest/documentation/migrations/migration-v0.5-to-v0.6) first — its StateDB / `callFromPrecompile` API break still applies. *** ## Table of contents The migration steps split into three groups. Required steps apply to every fork; the Krakatoa and BlockSTM groups are independent and either may be skipped. (Step 5c covers optimistic execution — an SDK feature unrelated to v0.7 — for forks that haven't wired it.) **Required (every fork)** * [Step 1: Bump dependencies](#step-1-bump-dependencies) * [Step 1.5: Migrate import paths](#step-15-migrate-import-paths) * [Step 2: Update store keys in `app.go`](#step-2-update-store-keys-in-appgo) * [Step 3: Update `EVMD` struct fields](#step-3-update-evmd-struct-fields) * [Step 4: Update `NewExampleApp` and keeper constructors in `app.go`](#step-4-update-newexampleapp-and-keeper-constructors-in-appgo) * [Step 6: Drop `x/precisebank` (18-decimal chains)](#step-6-drop-xprecisebank-18-decimal-chains) * [Step 7: Update custom code](#step-7-update-custom-code) * [Step 8: Coordinate the upgrade](#step-8-coordinate-the-upgrade) * [Step 9: Verify](#step-9-verify) **Krakatoa application-layer mempool (required if you previously used `ExperimentalEVMMempool`; otherwise optional)** * [Step 5a: Krakatoa application-layer mempool](#5a-krakatoa-application-layer-mempool) **BlockSTM parallel execution (optional, bundled with virtual fee collection)** * [Step 5b: BlockSTM parallel execution and virtual fee collection](#5b-optional-blockstm-parallel-execution-and-virtual-fee-collection) **Optimistic execution (SDK feature, not v0.7-specific — opt-in)** * [Step 5c: Optimistic execution](#5c-optional-optimistic-execution) *** ## Step 1: Bump dependencies ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go get github.com/cosmos/evm@v0.7.0 go mod tidy ``` > **Audit every `go.mod` in your tree.** Forks with nested submodules (e.g., a separate `tests/systemtests/go.mod`) need the dependency bumps applied independently and `go mod tidy` run separately in each — root-only bumps leave the nested modules with stale `cosmossdk.io/log`, `cosmossdk.io/store` resolutions that fail to compile. v0.7.0 transitively bumps the entire stack. The minimum versions you need: | Dependency | v0.6.x | v0.7.0 | Upstream guide | | ------------- | ------- | ------------------------- | ------------------- | | Go toolchain | 1.23 | **1.25** | — | | `cosmos-sdk` | v0.53.x | **v0.54.2** | [SDK][sdk-upgrade] | | `cometbft` | v0.38.x | **v0.39.3** | [release notes][ct] | | `ibc-go` | v10 | **v11** | [v10→v11][ibc-v11] | | `go-ethereum` | v1.15 | **v1.17** via Cosmos fork | [release notes][gh] | [sdk-upgrade]: https://github.com/cosmos/cosmos-sdk/blob/main/UPGRADING.md [ibc-v11]: https://github.com/cosmos/ibc-go/blob/main/docs/docs/05-migrations/14-v10-to-v11.md [gh]: https://github.com/ethereum/go-ethereum/releases [ct]: https://github.com/cometbft/cometbft/releases Read the SDK and ibc-go guides end-to-end before starting — many of the keeper-signature edits in Step 4 are direct consequences of those upstream changes, and the upstream docs cover edge cases (capability migration, store/v2 nuances, gov vote-tally extension points) that this guide only summarizes. The geth bump uses a fork pin. Add (or update) the replace directive in your `go.mod`: ``` replace github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v1.17.2-cosmos-0 ``` If you depend on geth APIs directly (custom tracers, custom precompiles importing `core/vm`), expect compile errors and follow the geth 1.16 → 1.17 changelog for shim updates. ## Step 1.5: Migrate import paths A large block of `cosmossdk.io/...` packages moved into `github.com/cosmos/cosmos-sdk/...` in SDK v0.54, and `store` is now `store/v2`. Run these find-and-replaces across your fork: | Old import | New import | | ------------------------------------------- | ------------------------------------------------------- | | `cosmossdk.io/store` | `github.com/cosmos/cosmos-sdk/store/v2` | | `cosmossdk.io/store/types` | `github.com/cosmos/cosmos-sdk/store/v2/types` | | `cosmossdk.io/store/snapshots/types` | `github.com/cosmos/cosmos-sdk/store/v2/snapshots/types` | | `cosmossdk.io/store/prefix` | `github.com/cosmos/cosmos-sdk/store/v2/prefix` | | `cosmossdk.io/log` | `cosmossdk.io/log/v2` | | `cosmossdk.io/x/upgrade{,/keeper,/types}` | `github.com/cosmos/cosmos-sdk/x/upgrade/...` | | `cosmossdk.io/x/evidence{,/keeper,/types}` | `github.com/cosmos/cosmos-sdk/x/evidence/...` | | `cosmossdk.io/x/feegrant{,/keeper,/module}` | `github.com/cosmos/cosmos-sdk/x/feegrant/...` | | `cosmossdk.io/x/tx/signing` | `github.com/cosmos/cosmos-sdk/x/tx/signing` | | `cosmossdk.io/systemtests` | `github.com/cosmos/cosmos-sdk/tools/systemtests` | | `github.com/cosmos/ibc-go/v10/...` | `github.com/cosmos/ibc-go/v11/...` | If your fork has a separate `tests/systemtests/go.mod` (recommended pattern), apply the rename there too and run `go mod tidy` in that submodule independently of the main module. Internal Cosmos EVM relocations — the top-level `github.com/cosmos/evm/config` package was deleted and its symbols redistributed: | v0.6 symbol | v0.7 location | | --------------------------------- | ------------------------------------------------------- | | `config.MustGetDefaultNodeHome` | `evmd/config.MustGetDefaultNodeHome` | | `config.InitAppConfig` | `evmd/config.InitAppConfig` | | `config.BlockedAddresses` | `evmd/config.BlockedAddresses` | | `config.GetMaccPerms` | `evmd/config.GetMaccPerms` | | `config.SetBip44CoinType` | `evmd/config.SetBip44CoinType` | | `config.GetChainIDFromHome` | `utils.GetChainIDFromHome` | | `config.EVMChainID` (constant) | removed — use `evmtypes.DefaultEVMChainID` | | `config.GetCosmosPoolMaxTx` | `server.GetCosmosPoolMaxTx` | | `config.GetLegacyPoolConfig` etc. | folded into `server.ResolveMempoolConfig` (see Step 5a) | The example-app helpers (`MustGetDefaultNodeHome`, `InitAppConfig`, `BlockedAddresses`, `GetMaccPerms`) moved from `github.com/cosmos/evm/config` to `github.com/cosmos/evm/evmd/config`. Mempool / chain-id helpers moved to `github.com/cosmos/evm/server` and `github.com/cosmos/evm/utils`. Update every import in `evmd/cmd/evmd/cmd/root.go`, `evmd/cmd/evmd/main.go`, and `evmd/cmd/evmd/cmd/testnet.go` accordingly. ## Step 2: Update store keys in `app.go` The transient stores `evmtypes.TransientKey` and `feemarkettypes.TransientKey` were both removed. The EVM keeper still needs a per-tx scratch store for tx bloom and gas accounting, so it now consumes `evmtypes.ObjectKey` (an object store) instead. This change is **unconditional** — it applies even on the sequential path. Replace the transient-store declaration, mount the new object store, and build a `nonTransientKeys` slice for the EVM keeper: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} keys := storetypes.NewKVStoreKeys(/* ... */) - tkeys := storetypes.NewTransientStoreKeys(evmtypes.TransientKey, feemarkettypes.TransientKey) + oKeys := storetypes.NewObjectStoreKeys(evmtypes.ObjectKey) + + var nonTransientKeys []storetypes.StoreKey + for _, k := range keys { + nonTransientKeys = append(nonTransientKeys, k) + } + for _, k := range oKeys { + nonTransientKeys = append(nonTransientKeys, k) + } - app.MountTransientStores(tkeys) + app.MountObjectStores(oKeys) ``` `nonTransientKeys` is the 4th argument to `evmkeeper.NewKeeper` (Step 4) and the EVM keeper uses it for cross-module store access. Step 5 reuses the same slice if you wire the BlockSTM runner. > **Step 5 (parallel path) extends `oKeys`** with `banktypes.ObjectStoreKey` and wires it into the bank keeper. Skip if you're not adopting virtual fee collection. (The matching `EVMD` struct field rename is in Step 3.) ## Step 3: Update `EVMD` struct fields ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type EVMD struct { // ... - keys map[string]*storetypes.KVStoreKey - tkeys map[string]*storetypes.TransientStoreKey - memKeys map[string]*storetypes.MemoryStoreKey + keys map[string]*storetypes.KVStoreKey + oKeys map[string]*storetypes.ObjectStoreKey - clientCtx client.Context - // ... - TransferKeeper transferkeeper.Keeper + TransferKeeper *transferkeeper.Keeper - EVMMempool *evmmempool.ExperimentalEVMMempool + EVMMempool sdkmempool.ExtMempool - - PreciseBankKeeper precisebankkeeper.Keeper } ``` `TransferKeeper` is now stored as a pointer (ibc-go v11). `EVMMempool`'s field type was widened to the `ExtMempool` interface to permit Krakatoa or any future custom subpool. If you have getters/setters keyed on these field types (e.g. `GetTransferKeeper`, `SetTransferKeeper`), update their signatures too. The `Close()` method on `EVMD` does a type assertion against the old mempool type — update it to the new one (or remove if you're not wiring an EVM mempool): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *EVMD) Close() error { var err error - if m, ok := app.GetMempool().(*evmmempool.ExperimentalEVMMempool); ok { + if m, ok := app.EVMMempool.(*evmmempool.Mempool); ok && m != nil { app.Logger().Info("Shutting down mempool") err = m.Close() } // ... } ``` ## Step 4: Update `NewExampleApp` and keeper constructors in `app.go` ### `NewExampleApp` signature — drop `traceStore` The `traceStore io.Writer` parameter is gone. Update the function signature **and every call site of your app constructor** — CLI commands (`newApp`, `appExport`, the `appCreator` callback in your root cmd), test-network fixtures (e.g., `NewTestNetworkFixture`), and any custom integration-test bootstrapping. `nil`-passing call sites need to drop the `nil` argument too. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewExampleApp( logger log.Logger, db dbm.DB, - traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *EVMD { ``` Inside, drop the corresponding tracer wiring: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - bApp.SetCommitMultiStoreTracer(traceStore) ``` ### `feemarketkeeper.NewKeeper` — drop transient key ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.FeeMarketKeeper = feemarketkeeper.NewKeeper( appCodec, authtypes.NewModuleAddress(govtypes.ModuleName), keys[feemarkettypes.StoreKey], - tkeys[feemarkettypes.TransientKey], ) ``` ### `ibckeeper.NewKeeper` — drop capability keeper ibc-go v11 removed the capability-keeper argument. Existing chains have nothing to migrate — the parameter was already nil — but the call must lose it: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.IBCKeeper = ibckeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), - nil, app.UpgradeKeeper, authAddr, ) ``` ### `govkeeper.NewKeeper` — reordered args, new vote-results function Cosmos SDK v0.54 reordered the constructor and added a final pluggable vote-tally function: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} govKeeper := govkeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, - app.StakingKeeper, app.DistrKeeper, app.MsgServiceRouter(), govConfig, authAddr, + govkeeper.NewDefaultCalculateVoteResultsAndVotingPower(app.StakingKeeper), ) ``` ### `transferkeeper.NewKeeper` — pointer return, inline address codec, drop ICS4Wrapper / duplicate ChannelKeeper ibc-go v11 returns `*transferkeeper.Keeper`. The constructor also takes the EVM address codec inline (no more `SetAddressCodec`) and drops the duplicate ICS4Wrapper / ChannelKeeper params: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.TransferKeeper = transferkeeper.NewKeeper( appCodec, + evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix()), runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), - nil, // ICS4Wrapper param - app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, authAddr, ) - app.TransferKeeper.SetAddressCodec(evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix())) ``` > ⚠️ The reference `evmd` instantiates `TransferKeeper` **before** `EVMKeeper` so static precompiles receive a non-nil reference. v0.6 constructs them in the opposite order (EVMKeeper, then Erc20Keeper consuming `&app.TransferKeeper`, then TransferKeeper). To swap, you'll also need to move `Erc20Keeper` so it's constructed *after* `TransferKeeper` — and `Erc20Keeper` itself now takes the pointer-typed `app.TransferKeeper` directly, no `&`. Walk the three keeper constructions as a unit, not individually. ### IBC callbacks middleware — wrap with setter calls ibc-go v11 split the constructor and the wrapping. Replace the single-line `NewIBCMiddleware` with the three-step setter form: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCKeeper.ChannelKeeper, app.CallbackKeeper, maxCallbackGas) + callbacksMiddleware := ibccallbacks.NewIBCMiddleware(app.CallbackKeeper, maxCallbackGas) + callbacksMiddleware.SetICS4Wrapper(app.IBCKeeper.ChannelKeeper) + callbacksMiddleware.SetUnderlyingApplication(transferStack) + transferStack = callbacksMiddleware ``` Both setters are required — `NewIBCMiddleware` itself doesn't accept either argument anymore. A middleware constructed without `SetICS4Wrapper` or `SetUnderlyingApplication` compiles but panics with a nil-pointer dereference on the first packet send / receive (the constructor does panic on a nil contract keeper or zero gas, but the wrapper / underlying-app fields are checked at use, not construction). ### `evmkeeper.NewKeeper` — object store key, store-key slice, BankKeeper replaces PreciseBankKeeper The third arg is the new EVM object-store key (Step 2). The fourth changed from `map[string]*storetypes.KVStoreKey` to `[]storetypes.StoreKey` — the keeper uses it for cross-module store access. Pass the `nonTransientKeys` slice from Step 2; the same slice is also what the STM runner tracks if you opt into Step 5. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.EVMKeeper = evmkeeper.NewKeeper( - appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], keys, + appCodec, keys[evmtypes.StoreKey], oKeys[evmtypes.ObjectKey], nonTransientKeys, authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, - app.PreciseBankKeeper, + app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper, &app.ConsensusParamsKeeper, &app.Erc20Keeper, evmChainID, tracer, ) ``` ### `DefaultStaticPrecompiles` — Bank, dereferenced TransferKeeper, ClientKeeper `TransferKeeper` is already a pointer (Step 3); pass it directly. `IBCKeeper.ClientKeeper` is the new dependency — it backs the ICS-02 client-router precompile (#768): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} precompiletypes.DefaultStaticPrecompiles( *app.StakingKeeper, app.DistrKeeper, - app.PreciseBankKeeper, + app.BankKeeper, &app.Erc20Keeper, - &app.TransferKeeper, + app.TransferKeeper, app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ClientKeeper, app.GovKeeper, app.SlashingKeeper, appCodec, ) ``` ### `erc20keeper.NewKeeper` — Bank, dereferenced TransferKeeper ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.Erc20Keeper = erc20keeper.NewKeeper( keys[erc20types.StoreKey], appCodec, authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, - app.PreciseBankKeeper, + app.BankKeeper, app.EVMKeeper, app.StakingKeeper, - &app.TransferKeeper, + app.TransferKeeper, ) ``` ### `node.RegisterNodeService` — earliest-version callback ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *EVMD) RegisterNodeService(clientCtx client.Context, cfg config.Config) { - node.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg) + node.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg, func() int64 { + return app.CommitMultiStore().EarliestVersion() + }) } ``` ### Hydrate EVM globals on restart (#1126) Required regardless of which path you choose. After `LoadLatestVersion` (inside `if loadLatest`), hydrate the EVM globals from KV so `evmCoinInfo` is populated before any RPC handler runs: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + ctx := app.NewContextLegacy(true, cmtproto.Header{ + Height: app.LastBlockHeight(), + ChainID: app.ChainID(), + }) + vmModule.HydrateGlobals(ctx) ``` Without this, RPC calls that arrive before `PreBlock` panic on a nil `evmCoinInfo`. `vmModule` here is the value returned by `vm.NewAppModule(...)`. v0.6 calls `vm.NewAppModule(...)` inline inside `app.ModuleManager = module.NewManager(...)`, so before the `HydrateGlobals` call you'll need to refactor: bind the result to a local first, then pass that local into `module.NewManager`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - app.ModuleManager = module.NewManager( - // ... other modules - vm.NewAppModule(app.EVMKeeper, app.AccountKeeper, app.BankKeeper, app.AccountKeeper.AddressCodec()), - // ... - ) + vmModule := vm.NewAppModule(app.EVMKeeper, app.AccountKeeper, app.BankKeeper, app.AccountKeeper.AddressCodec()) + + app.ModuleManager = module.NewManager( + // ... other modules + vmModule, + // ... + ) ``` Imports to add: `cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"`. ### Wire the EVM tx runner (#1132) Required regardless of path. `vmrunner.SetRunner` installs the baseapp tx runner wrapped with the EVM module's `PatchTxResponses` post-execution log/tx index fix-up. Without it, `log.Index` and `transactionIndex` on receipts are wrong (the bug #1132 corrected). The wrapper works for both sequential and parallel inner runners — only the inner runner choice differs by path: * **Sequential (default)** — pass `txnrunner.NewDefaultRunner(txDecoder)`. * **Parallel** — pass `txnrunner.NewSTMRunner(...)` (see Step 5b). Bind the tx decoder to a local at the top of `NewExampleApp` so it can be reused by the runner (and by `bApp := baseapp.NewBaseApp(...)` if you want to share it): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txConfig := encodingConfig.TxConfig + txDecoder := encodingConfig.TxConfig.TxDecoder() ``` Then add the runner call just before `return app`. The sequential form: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + vmrunner.SetRunner(bApp, txnrunner.NewDefaultRunner(txDecoder)) ``` Imports to add: `"github.com/cosmos/cosmos-sdk/baseapp/txnrunner"`, `vmrunner "github.com/cosmos/evm/x/vm/runner"`. ### Removed `EVMD` methods These were deleted; remove any callers: * `GetTKey`, `GetMemKey` — transient/mem stores no longer exist as separate maps. * `GetAuthzKeeper` — the helper was redundant; expose `app.AuthzKeeper` directly if you need it. * `GetPreciseBankKeeper` — module is removed (Step 6). * `SetClientCtx` and the `clientCtx` field — unused. ## Step 5: Enable Krakatoa and/or BlockSTM Two independent v0.7 opt-ins: * **Krakatoa (5a)** is required for forks that already used the v0.6 `ExperimentalEVMMempool` (the type was removed); optional for forks on CometBFT's stock mempool. * **BlockSTM with virtual fee collection (5b)** is always opt-in. Skip 5b to keep sequential execution. Step 5c documents `baseapp.SetOptimisticExecution()` separately — it's an SDK-level feature, not new in v0.7, but the reference `evmd/app.go` enables it and forks that haven't yet adopted it can use this upgrade as the moment to do so. > **State-breaking features.** Each ships as part of the binary; adopting or dropping any of them requires a coordinated `MsgSoftwareUpgrade` and a binary swap, the same as any other consensus change. ### 5a. Krakatoa application-layer mempool Krakatoa is the only app-side EVM mempool in v0.7. The `ExperimentalEVMMempool` type from v0.6 is gone, so optionality depends on what your v0.6 fork already used: * **If you wired `ExperimentalEVMMempool` in v0.6**: this step is **required**. The construction signature changed and the handler set was redesigned; you must migrate or your fork won't compile. * **If you ran on CometBFT's stock mempool in v0.6** (no app-side EVM mempool): this step is **optional**. Adopt Krakatoa to gain app-level `CheckTx` / `RecheckTx` control, or skip it and stay on stock CometBFT. Independent of BlockSTM either way. To migrate from `ExperimentalEVMMempool`, replace the construction with `evmmempool.NewMempool` and the new handler set. The full reference is `evmd/mempool.go`'s `configureEVMMempool` — read it before applying the diff below; the local variables it constructs (`mpConfig`, `txEncoder`, `evmRechecker`, `cosmosRechecker`, `cosmosPoolMaxTx`, `checkTxTimeout`) are what the new `NewMempool` signature consumes: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Setup block (in evmd/mempool.go's configureEVMMempool, all required): mpConfig := server.ResolveMempoolConfig(app.GetAnteHandler(), appOpts, logger) txEncoder := evmmempool.NewTxEncoder(app.txConfig) evmRechecker := evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder) cosmosRechecker := evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder) cosmosPoolMaxTx := server.GetCosmosPoolMaxTx(appOpts, logger) checkTxTimeout := server.GetMempoolCheckTxTimeout(appOpts, logger) ``` `server.ResolveMempoolConfig`, `server.GetCosmosPoolMaxTx`, and `server.GetMempoolCheckTxTimeout` are all in `github.com/cosmos/evm/server`; the `evmconfig.GetLegacyPoolConfig` / `GetBlockGasLimit` / `GetMinTip` helpers from v0.6 collapsed into `ResolveMempoolConfig`. `app.TxDecode` and the `SetInsertTxHandler` / `SetReapTxsHandler` setters are new in cosmos-sdk v0.54 baseapp. > **Ordering: `app.SetAnteHandler(...)` must run before `configureEVMMempool`.** `ResolveMempoolConfig` calls `app.GetAnteHandler()` and stashes the result on `mpConfig.AnteHandler`, which the recheckers then close over. If the ante handler hasn't been set yet, `GetAnteHandler` returns nil and the chain panics on first `RecheckTx`. There's no compile-time signal — get the ordering right. > **`evmRechecker` and `cosmosRechecker` must be distinct instances**, even though they wrap identical config. They feed different subpools (the EVM eth-tx pool vs. the Cosmos pool) and need independent state for promotion/demotion bookkeeping. Sharing one instance compiles fine but produces silent cross-pool state interference at promote/demote time. Then replace the construction and handler wiring: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - evmMempool := evmmempool.NewExperimentalEVMMempool( - app.CreateQueryContext, logger, - app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, app.clientCtx, - mempoolConfig, cosmosPoolMaxTx, - ) - app.EVMMempool = evmMempool - app.SetMempool(evmMempool) - checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool) - app.SetCheckTxHandler(checkTxHandler) - abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app) - abciProposalHandler.SetSignerExtractionAdapter(/* ... */) - app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler()) + mempool := evmmempool.NewMempool( + app.CreateQueryContext, logger, + app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, + evmRechecker, cosmosRechecker, mpConfig, cosmosPoolMaxTx, + ) + app.EVMMempool = mempool + app.SetPrepareProposal(baseapp.NewDefaultProposalHandler(mempool, NewNoCheckProposalTxVerifier(app.BaseApp)).PrepareProposalHandler()) + app.SetInsertTxHandler(mempool.NewInsertTxHandler(app.TxDecode)) + app.SetReapTxsHandler(mempool.NewReapTxsHandler()) + app.SetCheckTxHandler(mempool.NewCheckTxHandler(app.TxDecode, checkTxTimeout)) + app.SetMempool(mempool) ``` > The reference application uses a `NewNoCheckProposalTxVerifier` at proposal time when verifying the txs in a given proposal. This is a performance optimization since with the Krakatoa mempool, it is a requirement that every tx within the proposal has already been validated, thus this check is redundant. If you choose to adopt this pattern as well copy the reference `NewNoCheckProposalTxVerifier` and pass this to your `PrepareProposalHandler`. To opt out entirely, drop the `configureEVMMempool(...)` call from `NewExampleApp`, or set `mempool.max-txs=-1` in `app.toml` (the SDK `FlagMempoolMaxTxs` — note this is the top-level `[mempool]` key, not `evm.mempool.max-txs`). `configureEVMMempool` reads it via `server.GetCosmosPoolMaxTx`, bails out on a negative value, and falls back to CometBFT's stock mempool. > ⚠️ **Set `mempool.type = "app"` in `config.toml`.** CometBFT v0.39 requires the app-side mempool type whenever the application supplies an EVM mempool. The default is `"flood"`; Krakatoa errors out at startup if it's left as the default. This is a one-line patch to each validator's `config.toml` (or your fork's equivalent), applied at the same time as the binary swap. Forks that opt out of Krakatoa (the previous paragraph) don't need this. > Note that by setting `mempool.type = "app"` in `config.toml`, there are additional configuration parameters you may want to configure that will affect Krakatoa mempool operations. Please see the [CometBFT application mempool documentation](https://docs.cosmos.network/cometbft/latest/docs/core/mempool) for more info. ### 5b. (Optional) BlockSTM parallel execution and virtual fee collection BlockSTM enables parallel execution of the state-transition function and bundles virtual fee collection (per-tx bank-balance accounting reduced at `EndBlock`). **Not** required — chains that skip this section keep sequential execution. Independent of Krakatoa, and independent of optimistic execution (5c). The two pieces below are wired together; you opt into the bundle, not into individual lines. #### Swap the inner tx runner to BlockSTM Step 4 already wired `vmrunner.SetRunner(bApp, ...)` with the sequential `txnrunner.NewDefaultRunner(txDecoder)` as the inner runner. To opt into parallel execution, swap the inner runner for `txnrunner.NewSTMRunner(...)`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - vmrunner.SetRunner(bApp, txnrunner.NewDefaultRunner(txDecoder)) + vmrunner.SetRunner(bApp, txnrunner.NewSTMRunner( + txDecoder, + nonTransientKeys, + min(goruntime.GOMAXPROCS(0), goruntime.NumCPU()), + true, + func(ms storetypes.MultiStore) string { + return app.EVMKeeper.GetParams(sdk.NewContext(ms, cmtproto.Header{}, false, log.NewNopLogger())).EvmDenom + }, + )) ``` The EvmDenom callback reads from EVM params on the live multi-store; do not hard-code `sdk.DefaultBondDenom` — chains that customized their EVM denom will diverge. Additional import: `goruntime "runtime"`. #### Virtual fee collection (18-decimal chains only) Virtual fee collection is part of the BlockSTM bundle — it's what lets fee settlement happen in parallel without contending the fee collector account on every tx. Two pieces are required. First, extend the `oKeys` declaration from Step 2 with `banktypes.ObjectStoreKey` and wire it into the bank keeper. **Order matters**: the `oKeys` declaration must include `banktypes.ObjectStoreKey` *before* the `for _, k := range oKeys` loop that builds `nonTransientKeys`, otherwise the bank object store won't be in the slice the EVM keeper sees. Apply the change at the Step 2 declaration site, not later: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - oKeys := storetypes.NewObjectStoreKeys(evmtypes.ObjectKey) + oKeys := storetypes.NewObjectStoreKeys(banktypes.ObjectStoreKey, evmtypes.ObjectKey) ``` Then, after `BankKeeper` is constructed, wire the object-store key into it: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + app.BankKeeper = app.BankKeeper.WithObjStoreKey(oKeys[banktypes.ObjectStoreKey]) ``` `WithObjStoreKey(storetypes.StoreKey) BaseKeeper` is part of the `bankkeeper.Keeper` interface in cosmos-sdk v0.54, so the call works whether your `EVMD.BankKeeper` field is the interface (`bankkeeper.Keeper`, the reference default) or the concrete `bankkeeper.BaseKeeper`. The method returns `BaseKeeper`, but `BaseKeeper` satisfies `Keeper`, so assigning the result back to an interface-typed field type-checks. No field-widening required. Then, after `WithStaticPrecompiles`, enable virtual fees on the EVM keeper: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.EVMKeeper = evmkeeper.NewKeeper(/* ... */).WithStaticPrecompiles(/* ... */) + app.EVMKeeper.EnableVirtualFeeCollection() ``` > ⚠️ **Do not call `EnableVirtualFeeCollection()` if your gas token is not 18-decimal.** `x/vm/keeper.DeductFees` reads the EVM denom's bank metadata and panics if the display-denom unit's exponent is not 18 (`x/vm/keeper/fees.go:156`). See Step 6. A non-18-decimal chain that still wants BlockSTM has to skip this final piece — but the parallel path's main throughput win comes from virtual fees, so the practical recommendation is to migrate to 18 decimals first. ### 5c. (Optional) Optimistic execution **Not new in v0.7** — `baseapp.SetOptimisticExecution()` has been an SDK feature since v0.50. Documented here because the reference `evmd/app.go` ships with it enabled, and forks that haven't yet adopted it can fold the wiring into this upgrade. Independent of BlockSTM and Krakatoa. The feature overlaps `FinalizeBlock` for height `H` with `ProcessProposal` for height `H+1`: while CometBFT is still finalizing the current block, the app speculatively executes the next proposed block. If the speculative result matches what `FinalizeBlock` is later asked to commit, the result is reused; if it diverges, the speculative state is discarded and execution falls back to the standard path. The reference `evmd/app.go` enables it. Add the option to `baseAppOptions` **before** `baseapp.NewBaseApp(...)` is called — append after the `baseAppOptions ...func(*baseapp.BaseApp)` parameter is in scope but before the constructor consumes it: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewExampleApp( logger log.Logger, db dbm.DB, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *EVMD { // ... encoding/txDecoder setup ... + // enable optimistic execution + baseAppOptions = append( + baseAppOptions, + baseapp.SetOptimisticExecution(), + ) bApp := baseapp.NewBaseApp( appName, logger, db, txDecoder, baseAppOptions..., ) ``` Operational notes: * **Determinism is preserved.** Speculative execution uses a sandboxed cache; on a mismatch the state is dropped, not committed. The user-visible behavior is identical to non-optimistic execution — only the latency of `FinalizeBlock` shifts. * **Composes with BlockSTM (5b) and Krakatoa (5a).** Optimistic execution runs the speculative block through whichever runner is wired (`vmrunner.SetRunner`), so you get parallel-on-speculative + parallel-on-final when 5b is also enabled. * **State-breaking.** Like 5a and 5b, this is baked into the binary. Adopting or dropping it requires a coordinated `MsgSoftwareUpgrade`, not a runtime toggle. See Step 8. * **Resource cost.** The speculative path uses an extra goroutine and an extra working state cache per height. On memory-tight nodes you may prefer to disable it; the SDK doc linked at the top of this guide covers the trade-offs. ## Step 6: Drop `x/precisebank` `x/precisebank` is deprecated (#1019). It only ever made sense for non-18-decimal gas tokens — it bridged them into the EVM's 18-decimal world. The reference v0.6 `evmd/app.go` wired it unconditionally despite the inline comment "PreciseBank is not needed if SDK use 18 decimals for gas coin", so most v0.6 forks have it threaded through `evmkeeper`, `erc20keeper`, and the precompiles regardless of their gas token's decimals. In v0.7 the module is gone from the main tree. `EvmCoinInfo.Decimals` is also marked deprecated (#1029). > **Non-18-decimal chains are not fully supported going forward.** v0.7.0 still ships precisebank under `contrib/x/precisebank`, but future EVM releases will not test or maintain it. Either stay on v0.6.x until you can migrate to 18 decimals, or pin `cosmos/evm/contrib/x/precisebank` and **do not** enable virtual fee collection (5b). If your chain has a 6-decimal staking denom, an alternative is to run a separate, natively 18-decimal gas denom backed by a converter precompile, with no precisebank dependency at all. See [Migrating off x/precisebank: Gas Converter Precompile](/evm/latest/documentation/migrations/gas-converter-migration). If your fork wired `PreciseBankKeeper` (whether or not your gas token is 18-decimal), unwire it: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type EVMD struct { // ... other keepers - PreciseBankKeeper precisebankkeeper.Keeper } - // Set up PreciseBank keeper - app.PreciseBankKeeper = precisebankkeeper.NewKeeper(/* ... */) app.ModuleManager = module.NewManager( // ... other modules - precisebank.NewAppModule(app.PreciseBankKeeper, app.BankKeeper, app.AccountKeeper), ) - func (app *EVMD) GetPreciseBankKeeper() *precisebankkeeper.Keeper { /* ... */ } ``` Also drop `precisebanktypes.StoreKey` from the `keys` declaration and the module name from your `SetOrderBeginBlockers` / `SetOrderEndBlockers` / `SetOrderInitGenesis` lists. Replace every `app.PreciseBankKeeper` reference (in `evmkeeper.NewKeeper`, `erc20keeper.NewKeeper`, `DefaultStaticPrecompiles`, and any custom modules) with `app.BankKeeper`. Verify decimal expectations at each call site — anywhere your code assumed the precisebank denom translation was happening, you now need to either operate on 18-decimal values directly or do the conversion yourself. Tests that referenced precisebank also need cleanup. In the reference repo this meant deleting `evmd/tests/integration/x_precisebank_test.go` and removing `PreciseBankMintEventCount` / `PreciseBankBurnEventCount` and their consumers from `evmd/tests/ibc/helper.go`. Audit your fork's test tree for any `precisebank` symbols and drop them — the code won't compile against v0.7 with them present. ## Step 7: Update custom code ### Custom ante handlers / mempool plugins The transient stores backing gas accounting and the feemarket are gone. Read gas-wanted from the SDK context instead — `ctx.GasMeter().GasConsumed()`. If your decorator wrote to the feemarket transient store, remove that logic — `feemarkettypes.TransientKey` and the keeper methods `GetTransientGasWanted`, `SetTransientBlockGasWanted`, `AddTransientGasWanted` are all gone. The same applies to the EVM keeper: `GetBlockBloomTransient`, `SetBlockBloomTransient`, `GetTxIndexTransient`, `SetTxIndexTransient`, and `WithDefaultEvmCoinInfo` were removed. ### Custom `BankKeeper` / `BankWrapper` implementations The interfaces in `x/vm/types/interfaces.go` changed. Any fork that supplies a non-default bank wrapper must update. `BankKeeper` gained five methods to support virtual fee collection and parallel-safe balance accounting: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} SendCoinsFromModuleToAccountVirtual( ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, ) error SendCoinsFromAccountToModuleVirtual( ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins, ) error UncheckedSetBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin) error LockedCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins BlockedAddr(addr sdk.AccAddress) bool ``` `BankWrapper` is a hard API break: `MintAmountToAccount` and `BurnAmountFromAccount` were **removed** and replaced by a single `SetBalance(ctx, account, amt *big.Int) error`. Callers and implementations must both change. `EmitBlockBloomEvent`'s argument type changed from `ethtypes.Bloom` to `[]byte`. A new `VMKeeper` interface (`GetEvmCoinInfo`) was added; some helpers now take it instead of a concrete keeper. ### Custom indexers / receipt consumers * Read `log.Index` and `log.TxIndex` only after post-execution patching (#1132). Recomputing from raw event order will diverge from canonical receipts. * Drop any workaround that special-cased the `MaxUint64` overflow on `transactionIndex` — fixed in #1047. * Don't assume tx counts in receipt arrays match raw block tx counts. StateDB-error txs are now skipped during receipt conversion (#1107). ### Forks of precompile `abi.json` PR #758 made the precompile ABI files strict — only valid Ethereum ABI fields are accepted. Forks that vendor or override `abi.json` for any precompile must remove non-ABI extension fields, or they will fail to parse at startup. ### Removed helpers * `Params.GetActiveStaticPrecompilesAddrs()` is gone — derive `[]common.Address` yourself if you used it. ### Mocks of `EVMKeeper` Regenerate against v0.7.0 — the constructor signature (Step 4) and static-precompile dependencies (Step 4) shifted. ## Step 8: Coordinate the upgrade This is a normal Cosmos consensus-breaking upgrade. * **All validators must run the same v0.7 binary post-upgrade.** Mixing v0.6 and v0.7 binaries will fork. * **Step 5 (Krakatoa, BlockSTM + virtual fees, optimistic execution) is state-breaking.** Each is baked into the binary — adopting or dropping any of them later is another coordinated `MsgSoftwareUpgrade`, not a runtime toggle. * **There is no on-chain governance flag** for these toggles. Switching paths later requires a new binary release and another `MsgSoftwareUpgrade`. Update the upgrade handler's `UpgradeName` constant **and its doc comment** to match the new release. The doc-comment drift between releases is a recurring foot-gun — the system test reads the constant verbatim, but readers and downstream handlers grep the comment for context: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // evmd/upgrades.go (or wherever your fork keeps its upgrade name) - // NOTE: This upgrade defines a reference implementation of what an upgrade - // could look like when an application is migrating from EVMD version - // v0.4.0 to v0.5.x - const UpgradeName = "v0.5.0-to-v0.6.0" + // NOTE: This upgrade defines a reference implementation of what an upgrade + // could look like when an application is migrating from EVMD version + // v0.6.x to v0.7.0 + const UpgradeName = "v0.6.0-to-v0.7.0" ``` The reference upgrade handler runs `RunMigrations` with empty `StoreUpgrades` — no module schema changes. Submit a `MsgSoftwareUpgrade` with `name: "v0.6.0-to-v0.7.0"` at your target height and swap the binary at the halt height. ## Step 9: Verify ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go build ./... go test ./... ``` ### Sanity check the upgrade path ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} make test-system ``` Drives a `MsgSoftwareUpgrade` for `v0.6.0-to-v0.7.0`, swaps to the v0.7 binary at the halt height, and runs a contended-account workload (many txs hitting the same hot contract per block). On the parallel path this exercises the BlockSTM scheduler's conflict detection across the upgrade boundary; on the sequential path it's still a useful end-to-end smoke test. The test reads a v0.6-era binary from `tests/systemtests/binaries/v0.6/evmd`. **For your fork, that binary is your own chain build pinned to the v0.6 cosmos-evm dependency** — not a generic `cosmos/evm@v0.6.0` binary. The legacy build's keepers, ante decorators, and module set must match what your validators are actually running pre-upgrade, otherwise the test exercises the wrong starting state. Wire whatever `make` (or shell) target you use for this — the key constraints are: * Output goes to `tests/systemtests/binaries/v0.6/evmd` (or wherever your `chainupgrade/v6_v7.go` reads from). * Your `test-system` Make target (or equivalent) depends on it and on the current binary, in that order. * If your previous release had a `build-v05` target chained from `test-system`, retarget that dependency to the new `build-v06` (or whatever you name it). # Upgrade Handlers Source: https://docs.cosmos.network/evm/latest/documentation/migrations/upgrade-handlers Understanding and performing coordinated chain upgrades ## Overview Upgrade handlers enable coordinated on-chain upgrades across all validators at specific block heights via governance proposals. They provide a mechanism for chains to perform data migrations, parameter updates, and module upgrades in a deterministic way. Upgrade handlers are critical for maintaining consensus during chain upgrades. All validators must run the same upgrade logic at the same height. ## When to Use Upgrade Handlers Upgrade handlers are required when: * **Breaking state changes**: Modifying storage formats or data structures * **Module migrations**: Updating module versions or parameters * **Protocol upgrades**: Implementing new features that require state transitions * **Data migrations**: Moving data between different storage locations ## Basic Structure ### Registering Upgrade Handlers Upgrade handlers are registered in your app's `RegisterUpgradeHandlers()` method: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/upgrades.go package app import ( "context" upgradetypes "cosmossdk.io/x/upgrade/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" ) func (app *App) RegisterUpgradeHandlers() { app.UpgradeKeeper.SetUpgradeHandler( "v1.0.0", // upgrade name (must match governance proposal) func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) sdkCtx.Logger().Info("Starting upgrade", "name", plan.Name) // Run module migrations migrations, err := app.ModuleManager.RunMigrations(ctx, app.configurator, fromVM) if err != nil { return nil, err } // Add custom migration logic here sdkCtx.Logger().Info("Upgrade complete", "name", plan.Name) return migrations, nil }, ) } ``` ### Module Version Management The upgrade handler receives and returns a `module.VersionMap` that tracks module versions: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // fromVM contains the module versions before the upgrade // The returned VersionMap contains the new versions after migration migrations, err := app.ModuleManager.RunMigrations(ctx, app.configurator, fromVM) ``` ## Organizing Upgrade Code For better maintainability, organize upgrades in separate packages: ### Directory Structure ``` app/ ├── upgrades/ │ ├── v1_0_0/ │ │ ├── constants.go # Upgrade name and configuration │ │ ├── handler.go # Main upgrade handler │ │ └── migrations.go # Migration logic │ ├── v1_1_0/ │ │ ├── constants.go │ │ ├── handler.go │ │ └── migrations.go │ └── types.go # Shared types └── upgrades.go # RegisterUpgradeHandlers ``` ### constants.go ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/upgrades/v1_0_0/constants.go package v1_0_0 import ( storetypes "cosmossdk.io/store/types" "github.com/yourchain/app/upgrades" ) const UpgradeName = "v1.0.0" var Upgrade = upgrades.Upgrade{ UpgradeName: UpgradeName, CreateUpgradeHandler: CreateUpgradeHandler, StoreUpgrades: storetypes.StoreUpgrades{ Added: []string{}, // New modules Deleted: []string{}, // Removed modules }, } ``` ### handler.go ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app/upgrades/v1_0_0/handler.go package v1_0_0 import ( "context" storetypes "cosmossdk.io/store/types" upgradetypes "cosmossdk.io/x/upgrade/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" ) func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, keepers *upgrades.UpgradeKeepers, storeKeys map[string]*storetypes.KVStoreKey, ) upgradetypes.UpgradeHandler { return func(c context.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { ctx := sdk.UnwrapSDKContext(c) // Run module migrations vm, err := mm.RunMigrations(c, configurator, vm) if err != nil { return nil, err } // Custom migrations if err := runCustomMigrations(ctx, keepers, storeKeys); err != nil { return nil, err } return vm, nil } } ``` ## Migration Patterns ### Parameter Migrations Migrating module parameters to new formats: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func migrateParams(ctx sdk.Context, keeper paramskeeper.Keeper) error { // Get old params var oldParams v1.Params keeper.GetParamSet(ctx, &oldParams) // Convert to new format newParams := v2.Params{ Field1: oldParams.Field1, Field2: convertField(oldParams.Field2), // New field with default value Field3: "default", } // Set new params keeper.SetParams(ctx, newParams) return nil } ``` ### State Migrations Moving data between different storage locations: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func migrateState(ctx sdk.Context, storeKey storetypes.StoreKey) error { store := ctx.KVStore(storeKey) // Iterate over old storage iterator := storetypes.KVStorePrefixIterator(store, oldPrefix) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { oldKey := iterator.Key() value := iterator.Value() // Transform key/value if needed newKey := transformKey(oldKey) newValue := transformValue(value) // Write to new location store.Set(newKey, newValue) // Delete old entry store.Delete(oldKey) } return nil } ``` ### Module Addition/Removal Adding or removing modules during upgrade: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // In constants.go var Upgrade = upgrades.Upgrade{ UpgradeName: "v2.0.0", CreateUpgradeHandler: CreateUpgradeHandler, StoreUpgrades: storetypes.StoreUpgrades{ Added: []string{"newmodule"}, Deleted: []string{"oldmodule"}, }, } // In handler.go func CreateUpgradeHandler(...) upgradetypes.UpgradeHandler { return func(c context.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { // Delete old module version delete(vm, "oldmodule") // Initialize new module if err := newModuleKeeper.InitGenesis(ctx, defaultGenesis); err != nil { return nil, err } // Run migrations return mm.RunMigrations(c, configurator, vm) } } ``` ## Best Practices Always test upgrade handlers thoroughly on testnets before mainnet deployment. ### Idempotency Make migrations idempotent when possible: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func migrateSomething(ctx sdk.Context, store sdk.KVStore) error { // Check if migration already done if store.Has(migrationCompleteKey) { ctx.Logger().Info("Migration already completed, skipping") return nil } // Perform migration // ... // Mark as complete store.Set(migrationCompleteKey, []byte{1}) return nil } ``` ### Error Handling Use comprehensive error handling and logging: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func migrate(ctx sdk.Context, keeper Keeper) error { ctx.Logger().Info("Starting migration", "module", "mymodule") count := 0 iterator := keeper.IterateAllRecords(ctx) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { if err := processRecord(iterator.Key(), iterator.Value()); err != nil { ctx.Logger().Error("Failed to migrate record", "key", iterator.Key(), "error", err, ) return fmt.Errorf("migration failed at record %d: %w", count, err) } count++ // Log progress for long migrations if count%1000 == 0 { ctx.Logger().Info("Migration progress", "processed", count) } } ctx.Logger().Info("Migration complete", "total_migrated", count) return nil } ``` ### Testing Create comprehensive tests for upgrade handlers: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func TestUpgradeHandler(t *testing.T) { app := setupApp(t) ctx := app.NewContext(false, tmproto.Header{Height: 1}) // Setup pre-upgrade state setupOldState(t, ctx, app) // Run upgrade handler _, err := v1_0_0.CreateUpgradeHandler( app.ModuleManager, app.configurator, &upgrades.UpgradeKeepers{ // ... keepers }, app.keys, )(ctx, upgradetypes.Plan{Name: "v1.0.0"}, app.ModuleManager.GetVersionMap()) require.NoError(t, err) // Verify post-upgrade state verifyNewState(t, ctx, app) } ``` ## Upgrade Process ### Create Upgrade Proposal Submit a governance proposal with the upgrade details: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} mantrachaind tx gov submit-proposal software-upgrade v1.0.0 \ --title "Upgrade to v1.0.0" \ --description "Upgrade description" \ --upgrade-height 1000000 \ --from validator \ --deposit 10000000stake ``` ### Vote on Proposal Validators and delegators vote on the upgrade: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} mantrachaind tx gov vote 1 yes --from validator ``` ### Prepare Binary Build and distribute the new binary with the upgrade handler: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Build new binary make build # Test upgrade on local network ./scripts/test-upgrade.sh # Distribute to validators # Use Cosmovisor for automated upgrades ``` ### Monitor Upgrade Watch logs during the upgrade height: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Monitor upgrade logs tail -f ~/.mantrachaind/logs/upgrade.log # Verify upgrade success mantrachaind query upgrade applied v1.0.0 ``` ## Cosmos EVM Specific Migrations For Cosmos EVM chains, specific migrations include: * **[ERC20 Precompiles Migration](./erc20-precompiles-migration)**: Required for v0.3.x to v0.4.0 * **Fee Market Parameters**: Updating EIP-1559 parameters * **Custom Precompiles**: Registering new precompiled contracts * **EVM State**: Migrating account balances or contract storage ## Troubleshooting ### Consensus Failure **Symptom:** Chain halts with consensus failure at upgrade height **Causes:** * Validators running different binary versions * Upgrade handler not registered * Non-deterministic migration logic **Solution:** * Ensure all validators have the same binary * Verify upgrade handler is registered * Review migration logic for non-determinism ### Upgrade Panic **Symptom:** Node panics during upgrade **Causes:** * Unhandled error in migration * Missing required state * Invalid type assertions **Solution:** * Add comprehensive error handling * Validate state before migration * Use safe type conversions ### State Corruption **Symptom:** Invalid state after upgrade **Causes:** * Partial migration completion * Incorrect data transformation * Missing cleanup of old data **Solution:** * Make migrations atomic * Thoroughly test transformations * Ensure old data is properly cleaned up ## References * [Cosmos SDK Upgrade Module](/sdk/latest/modules/upgrade/README) * [Cosmovisor Documentation](/sdk/latest/guides/upgrades/cosmovisor) # Overview Source: https://docs.cosmos.network/evm/latest/documentation/overview Cosmos EVM is an open-source Cosmos SDK module that embeds a full Ethereum Virtual Machine into a CometBFT-based chain. Deploy existing Solidity contracts, use standard EVM tooling, and launch a sovereign L1 with instant finality and native cross-chain support. Unlike rollups, Cosmos EVM chains control their own validator set, governance, and fee economics, all while preserving full Ethereum bytecode and JSON-RPC compatibility. Build and run your own EVM-compatible chain using the evmd reference implementation Explore the source code, open issues, and contribute to Cosmos EVM } href="/evm/latest/documentation/evm-compatibility"> Deploy existing Solidity contracts and use MetaMask, Hardhat, Foundry, Remix, ethers.js, viem, and more } href="/evm/latest/documentation/smart-contracts/precompiles/overview"> Access staking, governance, IBC, and other Cosmos SDK modules directly from smart contracts ## What you can build Cosmos EVM is for teams that want [full EVM compatibility](/evm/latest/documentation/evm-compatibility) without giving up the benefits of launching a sovereign L1. You control the entire stack: the EVM execution environment, gas model, validator set, governance rules, and which Cosmos SDK modules to include. If you already deploy to Ethereum or EVM rollups, you can deploy to a Cosmos EVM chain with no contract changes. Chains can also run a permissioned EVM—restricting contract deployment or calls to whitelisted addresses—for use cases that require access controls at the protocol level. ## EVM Equivalent Your chain runs standard Ethereum bytecode and behaves like any Ethereum network: deploy Solidity contracts with Hardhat, Foundry, or Remix; connect MetaMask or any EVM wallet; use ethers.js, viem, or web3.js without changes. See the [tooling and resources](/evm/latest/documentation/getting-started/tooling-and-resources) page for a list of supported tools. Cosmos EVM implements the full [Ethereum JSON-RPC API](/evm/latest/api-reference/ethereum-json-rpc/methods) and supports all common transaction formats: [EIP-155](https://eips.ethereum.org/EIPS/eip-155) (chain ID protection), [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) (dynamic fees), [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) (access lists), and [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) (EOA code delegation). Because Cosmos EVM chains are sovereign L1 chains, they do not support L2-specific features like blob transactions ([EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)) and other rollup-oriented primitives. ## Ethereum with more If you know Solidity, you already know how to build on Cosmos EVM. Contracts [compile and deploy the same way](/evm/latest/documentation/getting-started/build-a-chain/quick-start), the [same opcodes are available](/evm/latest/documentation/evm-compatibility#opcodes), and the same libraries work. The differences are additions, not substitutions. * **Instant finality:** On Ethereum, transactions reach probabilistic finality over multiple blocks. On Cosmos EVM, transactions are final after one block (\~1–2 seconds) via [CometBFT](/cometbft) with no possibility of reorganization. Your contracts don't need to account for reorgs. * **Fee distribution:** Both use EIP-1559 dynamic fees. The base fee on Cosmos EVM is distributed to validators and delegators rather than burned—same fee mechanics for the developer, different economics for the chain. * **Native cross-chain:** Rather than relying on external bridge contracts, Cosmos EVM has [IBC (Inter-Blockchain Communication)](/evm/latest/documentation/concepts/ibc) built into the protocol. Cross-chain token transfers are a first-class feature, accessible from Solidity via the [IBC precompile](/evm/latest/documentation/smart-contracts/precompiles/ics20). * **Precompiles:** Cosmos EVM exposes protocol-level functionality as precompiled contracts at fixed addresses. From Solidity, you can call into staking, distribution, governance, bank, IBC transfers (ICS-20), slashing, vesting, and address conversion utilities the same way you'd call any other contract. See the [precompiles reference](/evm/latest/documentation/smart-contracts/precompiles/overview) for the full list of addresses and interfaces. * **EIP-712 signing:** MetaMask and other EVM wallets can sign Cosmos SDK transactions—including governance votes, staking operations, and more—using the standard `eth_signTypedData` method. No separate Cosmos wallet required. ## Getting started The quickest way to get started is [running the example chain locally](/evm/latest/documentation/getting-started)— it takes less than 3 minutes and requires only Go and Make. # Bank Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/bank An ERC20 interface to native Cosmos SDK tokens for balance queries and supply information ## Overview The Bank precompile provides ERC20-style access to native Cosmos SDK tokens, enabling smart contracts to query balances and token supplies through standardized interfaces. It serves as a Solidity wrapper around the Cosmos SDK `x/bank` module. **Address**: `0x0000000000000000000000000000000000000804` **Related Module**: [x/bank](/sdk/latest/modules/bank/README) ## Gas Costs * **balances**: 2,851 + (2,851 × (n-1)) where n = number of tokens returned * **totalSupply**: 2,477 + (2,477 × (n-1)) where n = number of tokens returned * **supplyOf**: 2,477 ## Primary Methods ### balances **Signature**: `balances(address account) → Balance[] memory` **Description**: Queries all native token balances for a specific account address and returns an array of `Balance` (ERC-20 contract address & amount) structures. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract BankExample { address constant BANK_PRECOMPILE = 0x0000000000000000000000000000000000000804; struct Balance { address contractAddress; uint256 amount; } function getAccountBalances(address account) external view returns (Balance[] memory balances) { (bool success, bytes memory result) = BANK_PRECOMPILE.staticcall( abi.encodeWithSignature("balances(address)", account) ); require(success, "Balance query failed"); balances = abi.decode(result, (Balance[])); return balances; } // Convenience function to get caller's balances function getMyBalances() external view returns (Balance[] memory balances) { return this.getAccountBalances(msg.sender); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // Connect to the network const provider = new ethers.JsonRpcProvider(""); // Precompile address and ABI const precompileAddress = "0x0000000000000000000000000000000000000804"; const precompileAbi = [ "function balances(address account) view returns (tuple(address contractAddress, uint256 amount)[])" ]; // Create a contract instance const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Address to query const accountAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder async function getBalances() { try { const balances = await contract.balances(accountAddress); console.log("Balances:", balances); } catch (error) { console.error("Error fetching balances:", error); } } getBalances(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Replace and the account address with your actual data. # Data is ABI-encoded: function selector + padded address curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000804", "data": "0x27e235e3000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` **Parameters**: * `account` (address): The account address to query balances for **Returns**: Array of Balance structures with contractAddress and amount fields ### totalSupply **Signature**: `totalSupply() → Balance[] memory` **Description**: Queries the total supply of all native tokens in the system. Returns comprehensive supply information for every token registered in the system. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract BankExample { address constant BANK_PRECOMPILE = 0x0000000000000000000000000000000000000804; struct Balance { address contractAddress; uint256 amount; } function getTotalSupply() external view returns (Balance[] memory totalSupply) { (bool success, bytes memory result) = BANK_PRECOMPILE.staticcall( abi.encodeWithSignature("totalSupply()") ); require(success, "Total supply query failed"); totalSupply = abi.decode(result, (Balance[])); return totalSupply; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // Connect to the network const provider = new ethers.JsonRpcProvider(""); // Precompile address and ABI const precompileAddress = "0x0000000000000000000000000000000000000804"; const precompileAbi = [ "function totalSupply() view returns (tuple(address contractAddress, uint256 amount)[])" ]; // Create a contract instance const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); async function getTotalSupply() { try { const supply = await contract.totalSupply(); console.log("Total Supply:", supply); } catch (error) { console.error("Error fetching total supply:", error); } } getTotalSupply(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Replace with your actual RPC endpoint. # Data is the function selector for totalSupply() curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000804", "data": "0x18160ddd" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` **Parameters**: None **Returns**: Array of Balance structures showing total supply for each token ### supplyOf **Signature**: `supplyOf(address erc20Address) → uint256` **Description**: Queries the total supply of a specific token by providing its ERC20 contract address. More efficient when you need supply information for a single token. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract BankExample { address constant BANK_PRECOMPILE = 0x0000000000000000000000000000000000000804; function getTokenSupply(address erc20Address) external view returns (uint256 supply) { (bool success, bytes memory result) = BANK_PRECOMPILE.staticcall( abi.encodeWithSignature("supplyOf(address)", erc20Address) ); require(success, "Token supply query failed"); supply = abi.decode(result, (uint256)); return supply; } // Helper function to check if token exists (has supply > 0) function tokenExists(address erc20Address) external view returns (bool) { uint256 supply = this.getTokenSupply(erc20Address); return supply > 0; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // Connect to the network const provider = new ethers.JsonRpcProvider(""); // Precompile address and ABI const precompileAddress = "0x0000000000000000000000000000000000000804"; const precompileAbi = [ "function supplyOf(address erc20Address) view returns (uint256)" ]; // Create a contract instance const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // ERC20 address to query const erc20Address = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; // Placeholder for a token contract async function getSupplyOf() { try { const supply = await contract.supplyOf(erc20Address); console.log("Supply of", erc20Address, ":", supply.toString()); } catch (error) { console.error("Error fetching supply:", error); } } getSupplyOf(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Replace and the token address with your actual data. # Data is ABI-encoded: function selector + padded address curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000804", "data": "0x62400e4c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` **Parameters**: * `erc20Address` (address): The ERC20 contract address of the token **Returns**: Total supply of the specified token as uint256 ## Data Structures -> ### Balance The Balance struct represents a token balance with its associated ERC20 contract address: * `contractAddress` (address): The ERC20 contract address representing the native token * `amount` (uint256): Token amount in the smallest denomination ## Full Interface & ABI ```solidity title="Bank Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.18; /// @dev The IBank contract's address. address constant IBANK_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000804; /// @dev The IBank contract's instance. IBank constant IBANK_CONTRACT = IBank(IBANK_PRECOMPILE_ADDRESS); /// @dev Balance specifies the ERC20 contract address and the amount of tokens. struct Balance { /// contractAddress defines the ERC20 contract address. address contractAddress; /// amount of tokens uint256 amount; } /** * @author Evmos Team * @title Bank Interface * @dev Interface for querying balances and supply from the Bank module. */ interface IBank { /// @dev balances defines a method for retrieving all the native token balances /// for a given account. /// @param account the address of the account to query balances for. /// @return balances the array of native token balances. function balances( address account ) external view returns (Balance[] memory balances); /// @dev totalSupply defines a method for retrieving the total supply of all /// native tokens. /// @return totalSupply the supply as an array of native token balances function totalSupply() external view returns (Balance[] memory totalSupply); /// @dev supplyOf defines a method for retrieving the total supply of a particular native coin. /// @return totalSupply the supply as a uint256 function supplyOf( address erc20Address ) external view returns (uint256 totalSupply); } ``` ```json title="Bank ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "IBank", "sourceName": "solidity/precompiles/bank/IBank.sol", "abi": [ { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "balances", "outputs": [ { "components": [ { "internalType": "address", "name": "contractAddress", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Balance[]", "name": "balances", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "erc20Address", "type": "address" } ], "name": "supplyOf", "outputs": [ { "internalType": "uint256", "name": "totalSupply", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalSupply", "outputs": [ { "components": [ { "internalType": "address", "name": "contractAddress", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Balance[]", "name": "totalSupply", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" } ], "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {}, "deployedLinkReferences": {} } ``` ## Implementation Details ### Token Resolution The precompile resolves native Cosmos SDK denominations to their corresponding ERC-20 contract addresses through the `x/erc20` module's token pair registry. Only tokens with registered token pairs are returned in query results. ### Decimal Precision All amounts returned preserve the original decimal precision stored in the `x/bank` module. No decimal conversion is performed by the precompile. ### Gas Metering The precompile implements efficient gas metering by: * Charging base gas for the first result * Incrementally charging for each additional result in batch queries * Consuming gas before returning results to prevent DoS vectors ### Error Handling * Invalid token addresses in `supplyOf` return 0 rather than reverting * Queries for accounts with no balances return empty arrays * All methods are read-only and cannot modify state # Bech32 Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/bech32 Address format conversion between Ethereum hex addresses and Cosmos bech32 addresses **Address**: `0x0000000000000000000000000000000000000400` **Related Module**: Address conversion utilities The Bech32 precompile provides address format conversion functionality between Ethereum hex addresses and Cosmos bech32 addresses. ## Overview The Bech32 precompile exposes simple conversion helpers so Solidity contracts can: 1. Convert an Ethereum `address` to a Bech32 string with a desired `prefix` (e.g. `cosmos`). 2. Convert any Bech32 address string back into an EVM `address`. It is a thin wrapper around the Cosmos address‐conversion library and lives at **`0x0000000000000000000000000000000000000400`**. ## Gas Costs Both methods use a configurable base gas amount that is set during chain initialization. The gas cost is fixed regardless of string length within reasonable bounds. ## Primary Methods ### `hexToBech32` **Signature**: `hexToBech32(address addr, string memory prefix) → string memory` **Description**: Converts an Ethereum hex address to Cosmos bech32 format using the specified prefix. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Bech32 precompile interface IBech32 { function hexToBech32(address addr, string memory prefix) external returns (string memory bech32Address); function bech32ToHex(string memory bech32Address) external returns (address addr); } contract Bech32Example { address constant BECH32_PRECOMPILE = 0x0000000000000000000000000000000000000400; IBech32 public immutable bech32; event AddressConverted(address indexed hexAddress, string bech32Address, string prefix); constructor() { bech32 = IBech32(BECH32_PRECOMPILE); } function hexToBech32(address addr, string calldata prefix) external returns (string memory bech32Address) { require(addr != address(0), "Invalid address"); require(bytes(prefix).length > 0, "Prefix cannot be empty"); bech32Address = bech32.hexToBech32(addr, prefix); emit AddressConverted(addr, bech32Address, prefix); return bech32Address; } // Convert multiple addresses with the same prefix function batchHexToBech32(address[] calldata addresses, string calldata prefix) external returns (string[] memory bech32Addresses) { bech32Addresses = new string[](addresses.length); for (uint256 i = 0; i < addresses.length; i++) { require(addresses[i] != address(0), "Invalid address in batch"); bech32Addresses[i] = bech32.hexToBech32(addresses[i], prefix); emit AddressConverted(addresses[i], bech32Addresses[i], prefix); } return bech32Addresses; } // Get common address formats for a single hex address function getCommonFormats(address addr) external returns ( string memory cosmosAddr, string memory evmosAddr, string memory osmosisAddr ) { require(addr != address(0), "Invalid address"); cosmosAddr = bech32.hexToBech32(addr, "cosmos"); evmosAddr = bech32.hexToBech32(addr, "evmos"); osmosisAddr = bech32.hexToBech32(addr, "osmo"); return (cosmosAddr, evmosAddr, osmosisAddr); } // Convert caller's address to bech32 format function getMyBech32Address(string calldata prefix) external returns (string memory) { return bech32.hexToBech32(msg.sender, prefix); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // Connect to the network const provider = new ethers.JsonRpcProvider(""); // Precompile address and ABI const precompileAddress = "0x0000000000000000000000000000000000000400"; const precompileAbi = [ "function hexToBech32(address addr, string memory prefix) returns (string memory)" ]; // Create a contract instance const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const ethAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder const prefix = "cosmos"; async function convertToBech32() { try { const bech32Address = await contract.hexToBech32.staticCall(ethAddress, prefix); console.log("Bech32 Address:", bech32Address); } catch (error) { console.error("Error converting to Bech32:", error); } } convertToBech32(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Replace with your actual RPC endpoint. # The address and prefix are placeholders. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000400", "data": "0xf958a98c000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000006636f736d6f730000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` **Parameters**: * `addr` (address): The Ethereum hex address to convert * `prefix` (string): The bech32 prefix to use (e.g., "cosmos") **Returns**: String containing the bech32 formatted address ### `bech32ToHex` **Signature**: `bech32ToHex(string memory bech32Address) → address` **Description**: Converts a Cosmos bech32 address to Ethereum hex format. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Bech32 precompile interface IBech32 { function bech32ToHex(string memory bech32Address) external returns (address addr); function hexToBech32(address addr, string memory prefix) external returns (string memory bech32Address); } contract Bech32Example { address constant BECH32_PRECOMPILE = 0x0000000000000000000000000000000000000400; IBech32 public immutable bech32; event Bech32Converted(string bech32Address, address indexed hexAddress); constructor() { bech32 = IBech32(BECH32_PRECOMPILE); } function bech32ToHex(string calldata bech32Address) external returns (address hexAddress) { require(bytes(bech32Address).length > 0, "Bech32 address cannot be empty"); hexAddress = bech32.bech32ToHex(bech32Address); require(hexAddress != address(0), "Invalid bech32 address"); emit Bech32Converted(bech32Address, hexAddress); return hexAddress; } // Convert multiple bech32 addresses to hex format function batchBech32ToHex(string[] calldata bech32Addresses) external returns (address[] memory hexAddresses) { hexAddresses = new address[](bech32Addresses.length); for (uint256 i = 0; i < bech32Addresses.length; i++) { require(bytes(bech32Addresses[i]).length > 0, "Invalid bech32 address"); hexAddresses[i] = bech32.bech32ToHex(bech32Addresses[i]); require(hexAddresses[i] != address(0), "Invalid bech32 address in batch"); emit Bech32Converted(bech32Addresses[i], hexAddresses[i]); } return hexAddresses; } // Validate address conversion by converting back and forth function validateAddressConversion(address addr, string calldata prefix) external returns (bool isValid) { try bech32.hexToBech32(addr, prefix) returns (string memory bech32Addr) { try bech32.bech32ToHex(bech32Addr) returns (address convertedBack) { isValid = (convertedBack == addr); return isValid; } catch { return false; } } catch { return false; } } // Check if two addresses (hex and bech32) represent the same account function areAddressesEqual(address hexAddr, string calldata bech32Addr) external returns (bool isEqual) { try bech32.bech32ToHex(bech32Addr) returns (address convertedHex) { isEqual = (convertedHex == hexAddr); return isEqual; } catch { return false; } } // Safely convert bech32 to hex with error handling function safeBech32ToHex(string calldata bech32Address) external returns (bool success, address hexAddress) { try bech32.bech32ToHex(bech32Address) returns (address addr) { return (true, addr); } catch { return (false, address(0)); } } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // Connect to the network const provider = new ethers.JsonRpcProvider(""); // Precompile address and ABI const precompileAddress = "0x0000000000000000000000000000000000000400"; const precompileAbi = [ "function bech32ToHex(string memory bech32Address) returns (address)" ]; // Create a contract instance const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input const bech32Address = "cosmos1mrdxhunfvjhe6lhdncp72dq46da2jcz9d9sh93"; // Placeholder async function convertToHex() { try { const hexAddress = await contract.bech32ToHex.staticCall(bech32Address); console.log("Hex Address:", hexAddress); } catch (error) { console.error("Error converting to Hex:", error); } } convertToHex(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Replace with your actual RPC endpoint. # The address is a placeholder. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000400", "data": "0xe6df461e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002a636f736d6f733171716c38616734636c757a367234647a32327064656e726d776c6975667173733863396a306500000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` **Parameters**: * `bech32Address` (string): The bech32 formatted address to convert **Returns**: Ethereum address in hex format ## Full Interface & ABI ```solidity title="Bech32 Solidity Interface" lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.17; /// @dev The Bech32I contract's address. address constant Bech32_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000400; /// @dev The Bech32I contract's instance. Bech32I constant BECH32_CONTRACT = Bech32I(Bech32_PRECOMPILE_ADDRESS); interface Bech32I { function hexToBech32(address addr,string memory prefix) external returns (string memory bech32Address); function bech32ToHex(string memory bech32Address) external returns (address addr); } ``` ```json title="Bech32 ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "Bech32I", "sourceName": "solidity/precompiles/bech32/Bech32I.sol", "abi": [ { "inputs": [ { "internalType": "string", "name": "bech32Address", "type": "string" } ], "name": "bech32ToHex", "outputs": [ { "internalType": "address", "name": "addr", "type": "address" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "addr", "type": "address" }, { "internalType": "string", "name": "prefix", "type": "string" } ], "name": "hexToBech32", "outputs": [ { "internalType": "string", "name": "bech32Address", "type": "string" } ], "stateMutability": "nonpayable", "type": "function" } ], "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {}, "deployedLinkReferences": {} } ``` ## Implementation Details ### Address Validation Both methods perform validation on the address format: * **hexToBech32**: Validates that the hex address is exactly 20 bytes and the prefix is non-empty * **bech32ToHex**: Validates that the bech32 address contains the separator character "1" and decodes to 20 bytes ### Prefix Handling For `hexToBech32`: * The prefix parameter determines the human-readable part of the bech32 address * Common prefixes include account addresses, validator addresses, and consensus addresses * Empty or whitespace-only prefixes result in an error with suggested valid prefixes For `bech32ToHex`: * The prefix is automatically extracted from the bech32 address * No prefix parameter is required as it's embedded in the address ### State Mutability While both methods are marked as `nonpayable` in the ABI, they function as read-only operations and do not modify blockchain state. # Callbacks Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/callbacks Interface for IBC packet lifecycle callbacks in smart contracts ## Overview The Callbacks module provides a standardized interface for smart contracts to handle IBC (Inter-Blockchain Communication) packet lifecycle events. This allows contracts to implement callback functions that are invoked when packets are acknowledged or time out during cross-chain communication. This is not a precompile that is called directly, but rather an interface that a contract must implement to receive callbacks. **Related Module**: [x/ibc-callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/README.md) ## Callback Functions A contract that sends an IBC transfer may need to listen for the outcome of the packet lifecycle. `Ack` and `Timeout` callbacks allow contracts to execute custom logic on the basis of how the packet lifecycle completes. The sender of an IBC transfer packet may specify a contract to be called when the packet lifecycle completes. This contract must implement the expected entrypoints for `onPacketAcknowledgement` and `onPacketTimeout`. Critically, **only the IBC packet sender can set the callback**. ### `onPacketAcknowledgement` **Signature**: `onPacketAcknowledgement(string memory channelId, string memory portId, uint64 sequence, bytes memory data, bytes memory acknowledgement)` **Description**: Callback function invoked on the source chain after a packet lifecycle is completed and acknowledgement is processed. The contract implementing this interface receives packet information and acknowledgement data to execute custom callback logic. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract CallbacksExample { // Address of the authorized IBC module address public immutable ibcModule; // Mapping to track packet statuses mapping(bytes32 => PacketStatus) public packetStatuses; mapping(address => uint256) public userBalances; enum PacketStatus { None, Pending, Acknowledged, TimedOut } event PacketAcknowledged(bytes32 indexed packetId, string channelId, uint64 sequence); event RefundIssued(address indexed user, uint256 amount, bytes32 indexed packetId); event CrossChainOrderExecuted(bytes32 indexed packetId, address recipient, uint256 amount); error UnauthorizedCaller(); error PacketAlreadyProcessed(); error InvalidPacketData(); modifier onlyIBC() { if (msg.sender != ibcModule) revert UnauthorizedCaller(); _; } constructor(address _ibcModule) { require(_ibcModule != address(0), "Invalid IBC module address"); ibcModule = _ibcModule; } function onPacketAcknowledgement( string memory channelId, string memory portId, uint64 sequence, bytes memory data, bytes memory acknowledgement ) external onlyIBC { bytes32 packetId = keccak256(abi.encodePacked(channelId, portId, sequence)); // Ensure packet hasn't been processed already if (packetStatuses[packetId] != PacketStatus.None) { revert PacketAlreadyProcessed(); } packetStatuses[packetId] = PacketStatus.Acknowledged; // Parse acknowledgement to determine success/failure bool success = _parseAcknowledgement(acknowledgement); if (success) { _handleSuccessfulAcknowledgement(packetId, data, acknowledgement); } else { _handleFailedAcknowledgement(packetId, data, acknowledgement); } emit PacketAcknowledged(packetId, channelId, sequence); } function _parseAcknowledgement(bytes memory acknowledgement) internal pure returns (bool success) { if (acknowledgement.length == 0) return false; // Check for error indicators in acknowledgement bytes5 errorPrefix = bytes5(acknowledgement); if (errorPrefix == bytes5("error")) { return false; } return true; // Non-error acknowledgement indicates success } function _handleSuccessfulAcknowledgement( bytes32 packetId, bytes memory data, bytes memory acknowledgement ) internal { // Parse packet data to get sender and amount (address sender, uint256 amount, string memory operation) = _parsePacketData(data); if (keccak256(bytes(operation)) == keccak256(bytes("cross_chain_swap"))) { emit CrossChainOrderExecuted(packetId, sender, amount); } // Credit any rewards or returns userBalances[sender] += amount; } function _handleFailedAcknowledgement( bytes32 packetId, bytes memory data, bytes memory acknowledgement ) internal { (address sender, uint256 amount, ) = _parsePacketData(data); // Issue refund for failed transaction userBalances[sender] += amount; emit RefundIssued(sender, amount, packetId); } function _parsePacketData(bytes memory data) internal pure returns (address sender, uint256 amount, string memory operation) { // Simplified parser - extract sender, amount, and operation from packet data if (data.length < 64) { revert InvalidPacketData(); } assembly { sender := mload(add(data, 32)) amount := mload(add(data, 64)) } // Extract operation string (simplified) operation = "cross_chain_swap"; // Default operation return (sender, amount, operation); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // Deploy a contract that implements IBC callbacks class IBCCallbackHandler { constructor(provider, signer, contractAddress) { this.provider = provider; this.signer = signer; this.contractAddress = contractAddress; // ABI for the callback interface this.abi = [ "function onPacketAcknowledgement(string channelId, string portId, uint64 sequence, bytes data, bytes acknowledgement)", "event PacketAcknowledged(bytes32 indexed packetId, string channelId, uint64 sequence)", "event RefundIssued(address indexed user, uint256 amount, bytes32 indexed packetId)" ]; this.contract = new ethers.Contract(contractAddress, this.abi, signer); } // Listen for packet acknowledgement events async listenForAcknowledgements() { console.log("Listening for packet acknowledgements..."); this.contract.on("PacketAcknowledged", (packetId, channelId, sequence) => { console.log(`Packet acknowledged:`); console.log(` Packet ID: ${packetId}`); console.log(` Channel: ${channelId}`); console.log(` Sequence: ${sequence}`); }); this.contract.on("RefundIssued", (user, amount, packetId) => { console.log(`Refund issued:`); console.log(` User: ${user}`); console.log(` Amount: ${ethers.formatEther(amount)} ETH`); console.log(` Packet ID: ${packetId}`); }); } // Get packet status async getPacketStatus(channelId, portId, sequence) { const packetId = ethers.solidityPackedKeccak256( ["string", "string", "uint64"], [channelId, portId, sequence] ); // Assuming the contract has a packetStatuses mapping const statusAbi = ["function packetStatuses(bytes32) view returns (uint8)"]; const contract = new ethers.Contract(this.contractAddress, statusAbi, this.provider); const status = await contract.packetStatuses(packetId); const statusNames = ["None", "Pending", "Acknowledged", "TimedOut"]; return { packetId, status: statusNames[status] || "Unknown", statusCode: status }; } } // Example usage async function setupCallbackHandler() { const provider = new ethers.JsonRpcProvider(""); const signer = new ethers.Wallet("", provider); const contractAddress = ""; const handler = new IBCCallbackHandler(provider, signer, contractAddress); // Start listening for events await handler.listenForAcknowledgements(); // Check a packet status const status = await handler.getPacketStatus("channel-0", "transfer", 123); console.log("Packet status:", status); } // setupCallbackHandler(); ``` ### `onPacketTimeout` **Signature**: `onPacketTimeout(string memory channelId, string memory portId, uint64 sequence, bytes memory data)` **Description**: Callback function invoked on the source chain after a packet lifecycle is completed and the packet has timed out. The contract implementing this interface receives packet information to execute custom timeout handling logic. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract CallbacksExample { address public immutable ibcModule; mapping(bytes32 => PacketStatus) public packetStatuses; mapping(address => uint256) public userBalances; enum PacketStatus { None, Pending, Acknowledged, TimedOut } event PacketTimedOut(bytes32 indexed packetId, string channelId, uint64 sequence); event RefundIssued(address indexed user, uint256 amount, bytes32 indexed packetId); error UnauthorizedCaller(); error PacketAlreadyProcessed(); error InvalidPacketData(); modifier onlyIBC() { if (msg.sender != ibcModule) revert UnauthorizedCaller(); _; } constructor(address _ibcModule) { ibcModule = _ibcModule; } function onPacketTimeout( string memory channelId, string memory portId, uint64 sequence, bytes memory data ) external onlyIBC { bytes32 packetId = keccak256(abi.encodePacked(channelId, portId, sequence)); // Ensure packet hasn't been processed already if (packetStatuses[packetId] != PacketStatus.None) { revert PacketAlreadyProcessed(); } packetStatuses[packetId] = PacketStatus.TimedOut; // Handle timeout by issuing refunds _handleTimeout(packetId, data); emit PacketTimedOut(packetId, channelId, sequence); } function _handleTimeout(bytes32 packetId, bytes memory data) internal { // Parse packet data to extract sender and amount for refund (address sender, uint256 amount, string memory operation) = _parsePacketData(data); // Issue full refund for timed out packets _issueRefund(sender, amount, packetId); // Additional timeout-specific logic based on operation type if (keccak256(bytes(operation)) == keccak256(bytes("stake_remote"))) { _handleStakeTimeout(sender, amount, packetId); } else if (keccak256(bytes(operation)) == keccak256(bytes("cross_chain_swap"))) { _handleSwapTimeout(sender, amount, packetId); } } function _issueRefund(address user, uint256 amount, bytes32 packetId) internal { userBalances[user] += amount; emit RefundIssued(user, amount, packetId); } function _handleStakeTimeout(address user, uint256 amount, bytes32 packetId) internal { // Handle staking timeout - might need to cancel staking plans // Restore user's staking availability userBalances[user] += amount; // Return staked amount // Additional staking-specific cleanup logic here } function _handleSwapTimeout(address user, uint256 amount, bytes32 packetId) internal { // Handle swap timeout - return original tokens userBalances[user] += amount; // Additional swap-specific cleanup logic here } function _parsePacketData(bytes memory data) internal pure returns (address sender, uint256 amount, string memory operation) { if (data.length < 64) { revert InvalidPacketData(); } assembly { sender := mload(add(data, 32)) amount := mload(add(data, 64)) } operation = "timeout_operation"; // Default return (sender, amount, operation); } // User functions to interact with refunds function withdraw(uint256 amount) external { require(userBalances[msg.sender] >= amount, "Insufficient balance"); userBalances[msg.sender] -= amount; payable(msg.sender).transfer(amount); } function getAvailableBalance(address user) external view returns (uint256) { return userBalances[user]; } function isPacketTimedOut( string memory channelId, string memory portId, uint64 sequence ) external view returns (bool) { bytes32 packetId = keccak256(abi.encodePacked(channelId, portId, sequence)); return packetStatuses[packetId] == PacketStatus.TimedOut; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // Handle timeout callbacks class TimeoutHandler { constructor(provider, signer, contractAddress) { this.provider = provider; this.signer = signer; this.contractAddress = contractAddress; // ABI for timeout handling this.abi = [ "function onPacketTimeout(string channelId, string portId, uint64 sequence, bytes data)", "event PacketTimedOut(bytes32 indexed packetId, string channelId, uint64 sequence)", "event RefundIssued(address indexed user, uint256 amount, bytes32 indexed packetId)", "function userBalances(address) view returns (uint256)" ]; this.contract = new ethers.Contract(contractAddress, this.abi, signer); } // Listen for timeout events async listenForTimeouts() { console.log("Listening for packet timeouts..."); this.contract.on("PacketTimedOut", (packetId, channelId, sequence) => { console.log(`Packet timed out:`); console.log(` Packet ID: ${packetId}`); console.log(` Channel: ${channelId}`); console.log(` Sequence: ${sequence}`); }); this.contract.on("RefundIssued", async (user, amount, packetId) => { console.log(`Refund issued for timeout:`); console.log(` User: ${user}`); console.log(` Amount: ${ethers.formatEther(amount)} ETH`); console.log(` Packet ID: ${packetId}`); // Check new balance const newBalance = await this.contract.userBalances(user); console.log(` New user balance: ${ethers.formatEther(newBalance)} ETH`); }); } // Check user balance after refund async checkUserBalance(userAddress) { const balance = await this.contract.userBalances(userAddress); return { address: userAddress, balance: balance, formatted: ethers.formatEther(balance) + " ETH" }; } // Encode packet data for testing static encodePacketData(sender, amount, operation = "cross_chain_swap") { // Simple encoding for testing return ethers.AbiCoder.defaultAbiCoder().encode( ["address", "uint256", "string"], [sender, amount, operation] ); } } // Example usage async function handleTimeouts() { const provider = new ethers.JsonRpcProvider(""); const signer = new ethers.Wallet("", provider); const contractAddress = ""; const handler = new TimeoutHandler(provider, signer, contractAddress); // Start listening await handler.listenForTimeouts(); // Check balance after refund const balance = await handler.checkUserBalance("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb8"); console.log("User balance:", balance.formatted); } // handleTimeouts(); ``` ## Security Considerations When implementing the Callbacks interface, consider the following security aspects: ### Caller Validation * **Critical**: Only the IBC module should invoke these callback functions * Implementing contracts must validate that the caller is the authorized IBC module address * Failure to validate the caller could allow malicious actors to trigger callbacks ### Gas Considerations * Callback execution consumes gas from the IBC transaction * Complex callback logic may cause the transaction to run out of gas * Consider implementing gas-efficient callback logic or handling partial execution states * Be aware that callback failures may impact the overall IBC packet lifecycle ### Example Security Pattern ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} contract SecureIBCCallback is ICallbacks { address constant IBC_MODULE = 0x...; // IBC module address modifier onlyIBC() { require(msg.sender == IBC_MODULE, "Unauthorized"); _; } function onPacketAcknowledgement(...) external onlyIBC { // Callback logic } function onPacketTimeout(...) external onlyIBC { // Timeout logic } } ``` ## Full Solidity Interface & ABI ```solidity title="Callbacks Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.18; interface ICallbacks { /// @dev Callback function to be called on the source chain /// after the packet life cycle is completed and acknowledgement is processed /// by source chain. The contract address is passed the packet information and acknowledgmeent /// to execute the callback logic. /// @param channelId the channnel identifier of the packet /// @param portId the port identifier of the packet /// @param sequence the sequence number of the packet /// @param data the data of the packet /// @param acknowledgement the acknowledgement of the packet function onPacketAcknowledgement( string memory channelId, string memory portId, uint64 sequence, bytes memory data, bytes memory acknowledgement ) external; /// @dev Callback function to be called on the source chain /// after the packet life cycle is completed and the packet is timed out /// by source chain. The contract address is passed the packet information /// to execute the callback logic. /// @param channelId the channnel identifier of the packet /// @param portId the port identifier of the packet /// @param sequence the sequence number of the packet /// @param data the data of the packet function onPacketTimeout( string memory channelId, string memory portId, uint64 sequence, bytes memory data ) external; } ``` ```json title="Callbacks ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} [ { "inputs": [ { "internalType": "string", "name": "channelId", "type": "string" }, { "internalType": "string", "name": "portId", "type": "string" }, { "internalType": "uint64", "name": "sequence", "type": "uint64" }, { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "acknowledgement", "type": "bytes" } ], "name": "onPacketAcknowledgement", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "channelId", "type": "string" }, { "internalType": "string", "name": "portId", "type": "string" }, { "internalType": "uint64", "name": "sequence", "type": "uint64" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "name": "onPacketTimeout", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] ``` # Distribution Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/distribution Withdraw staking rewards and interact with the community pool ## Overview The Distribution precompile provides access to the Cosmos SDK `x/distribution` module, enabling smart contracts to manage staking rewards, interact with the community pool, and handle validator commission operations. **Precompile Address**: `0x0000000000000000000000000000000000000801` **Related Module**: [x/distribution](/sdk/latest/modules/distribution/README) ## Gas Costs Gas costs are approximated and may vary based on call complexity and chain settings. | Method | Gas Cost | | ---------------- | ------------------------------ | | **Transactions** | `2000 + (30 × bytes of input)` | | **Queries** | `1000 + (3 × bytes of input)` | ## Message Type Constants The precompile defines the following constants for the various Cosmos SDK message types: ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Transaction message type URLs string constant MSG_SET_WITHDRAWER_ADDRESS = "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress"; string constant MSG_WITHDRAW_DELEGATOR_REWARD = "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward"; string constant MSG_WITHDRAW_VALIDATOR_COMMISSION = "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission"; ``` ## Transaction Methods ### claimRewards Claims staking rewards from multiple validators. The `maxRetrieve` parameter limits the number of validators from which to claim rewards in a single transaction. This prevents excessive gas consumption when a delegator has rewards from many validators. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DistributionClaimRewards { address constant DISTRIBUTION_PRECOMPILE = 0x0000000000000000000000000000000000000801; event RewardsClaimed(address indexed delegator, uint256 maxRetrieve, bool success); function claimRewards(uint32 maxRetrieve) external returns (bool success) { (bool callSuccess, bytes memory result) = DISTRIBUTION_PRECOMPILE.call( abi.encodeWithSignature("claimRewards(address,uint32)", msg.sender, maxRetrieve) ); require(callSuccess, "Claim rewards call failed"); success = abi.decode(result, (bool)); emit RewardsClaimed(msg.sender, maxRetrieve, success); return success; } function claimAllRewards() external returns (bool success) { // Use a high maxRetrieve value to claim from all validators return claimRewards(100); } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Claiming rewards requires a signed transaction" ``` ### withdrawDelegatorRewards Withdraws staking rewards from a single, specific validator. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DistributionExample { address constant DISTRIBUTION_PRECOMPILE = 0x0000000000000000000000000000000000000801; struct Coin { string denom; uint256 amount; } event RewardsWithdrawn(address indexed delegator, string indexed validator, uint256 amount); function withdrawRewards(string calldata validatorAddress) external returns (Coin[] memory amount) { (bool success, bytes memory result) = DISTRIBUTION_PRECOMPILE.call( abi.encodeWithSignature("withdrawDelegatorRewards(address,string)", msg.sender, validatorAddress) ); require(success, "Withdraw rewards failed"); amount = abi.decode(result, (Coin[])); uint256 totalAmount = 0; for (uint i = 0; i < amount.length; i++) { totalAmount += amount[i].amount; } emit RewardsWithdrawn(msg.sender, validatorAddress, totalAmount); return amount; } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Reward withdrawal requires a signed transaction" ``` ### setWithdrawAddress Sets or changes the withdrawal address for receiving staking rewards. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DistributionSetWithdrawAddress { address constant DISTRIBUTION_PRECOMPILE = 0x0000000000000000000000000000000000000801; event WithdrawAddressSet(address indexed delegator, string withdrawerAddress, bool success); function setWithdrawAddress(string calldata withdrawerAddress) external returns (bool success) { (bool callSuccess, bytes memory result) = DISTRIBUTION_PRECOMPILE.call( abi.encodeWithSignature("setWithdrawAddress(address,string)", msg.sender, withdrawerAddress) ); require(callSuccess, "Set withdraw address call failed"); success = abi.decode(result, (bool)); emit WithdrawAddressSet(msg.sender, withdrawerAddress, success); return success; } function resetWithdrawAddress() external returns (bool success) { // Reset to delegator's own address by converting msg.sender to bech32 // Note: In practice, you'd need to convert the EVM address to bech32 format string memory selfAddress = "art1..."; // This would be the bech32 equivalent return setWithdrawAddress(selfAddress); } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Setting withdraw address requires a signed transaction" ``` ### withdrawValidatorCommission Withdraws a validator's accumulated commission rewards. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DistributionWithdrawCommission { address constant DISTRIBUTION_PRECOMPILE = 0x0000000000000000000000000000000000000801; struct Coin { string denom; uint256 amount; } event ValidatorCommissionWithdrawn(string indexed validatorAddress, uint256 totalAmount); function withdrawValidatorCommission(string calldata validatorAddress) external returns (Coin[] memory amount) { (bool success, bytes memory result) = DISTRIBUTION_PRECOMPILE.call( abi.encodeWithSignature("withdrawValidatorCommission(string)", validatorAddress) ); require(success, "Withdraw validator commission failed"); amount = abi.decode(result, (Coin[])); uint256 totalAmount = 0; for (uint i = 0; i < amount.length; i++) { totalAmount += amount[i].amount; } emit ValidatorCommissionWithdrawn(validatorAddress, totalAmount); return amount; } // Helper function for validator operators to withdraw their own commission function withdrawMyCommission(string calldata myValidatorAddress) external returns (Coin[] memory) { return withdrawValidatorCommission(myValidatorAddress); } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Withdrawing validator commission requires a signed transaction" ``` ### fundCommunityPool Sends tokens directly to the community pool. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DistributionFundCommunityPool { address constant DISTRIBUTION_PRECOMPILE = 0x0000000000000000000000000000000000000801; struct Coin { string denom; uint256 amount; } event CommunityPoolFunded(address indexed depositor, string denom, uint256 amount, bool success); function fundCommunityPool(Coin[] calldata amount) external returns (bool success) { (bool callSuccess, bytes memory result) = DISTRIBUTION_PRECOMPILE.call( abi.encodeWithSignature("fundCommunityPool(address,(string,uint256)[])", msg.sender, amount) ); require(callSuccess, "Fund community pool call failed"); success = abi.decode(result, (bool)); // Emit events for each coin funded for (uint i = 0; i < amount.length; i++) { emit CommunityPoolFunded(msg.sender, amount[i].denom, amount[i].amount, success); } return success; } function fundCommunityPoolSingleCoin(string calldata denom, uint256 amount) external returns (bool success) { Coin[] memory coins = new Coin[](1); coins[0] = Coin(denom, amount); return fundCommunityPool(coins); } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Funding community pool requires a signed transaction" ``` ### depositValidatorRewardsPool Deposits tokens into a specific validator's rewards pool. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract DistributionDepositValidatorRewards { address constant DISTRIBUTION_PRECOMPILE = 0x0000000000000000000000000000000000000801; struct Coin { string denom; uint256 amount; } event ValidatorRewardsPoolDeposited( address indexed depositor, string indexed validatorAddress, string denom, uint256 amount, bool success ); function depositValidatorRewardsPool( string calldata validatorAddress, Coin[] calldata amount ) external returns (bool success) { (bool callSuccess, bytes memory result) = DISTRIBUTION_PRECOMPILE.call( abi.encodeWithSignature( "depositValidatorRewardsPool(address,string,(string,uint256)[])", msg.sender, validatorAddress, amount ) ); require(callSuccess, "Deposit validator rewards pool call failed"); success = abi.decode(result, (bool)); // Emit events for each coin deposited for (uint i = 0; i < amount.length; i++) { emit ValidatorRewardsPoolDeposited( msg.sender, validatorAddress, amount[i].denom, amount[i].amount, success ); } return success; } function depositSingleCoinToValidator( string calldata validatorAddress, string calldata denom, uint256 amount ) external returns (bool success) { Coin[] memory coins = new Coin[](1); coins[0] = Coin(denom, amount); return depositValidatorRewardsPool(validatorAddress, coins); } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Depositing to validator rewards pool requires a signed transaction" ``` ## Query Methods ### delegationTotalRewards Retrieves comprehensive reward information for all of a delegator's positions. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the relevant parts of the precompile const precompileAbi = [ "function delegationTotalRewards(address delegatorAddress) view returns (tuple(string validatorAddress, tuple(string denom, uint256 amount, uint8 precision)[] reward)[] rewards, tuple(string denom, uint256 amount, uint8 precision)[] total)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input: The address of the delegator to query const delegatorAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder async function getTotalRewards() { try { const result = await contract.delegationTotalRewards(delegatorAddress); console.log("Total Rewards:", JSON.stringify({ rewards: result.rewards, total: result.total }, null, 2)); } catch (error) { console.error("Error fetching total rewards:", error); } } getTotalRewards(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # Data is ABI-encoded: function selector + padded delegator address curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x54be1a28000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### delegationRewards Queries pending rewards for a specific delegation. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function delegationRewards(address delegatorAddress, string memory validatorAddress) view returns (tuple(string denom, uint256 amount, uint8 precision)[] rewards)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const delegatorAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder const validatorAddress = "artvaloper1..."; // Placeholder async function getDelegationRewards() { try { const rewards = await contract.delegationRewards(delegatorAddress, validatorAddress); console.log("Delegation Rewards:", JSON.stringify(rewards, null, 2)); } catch (error) { console.error("Error fetching delegation rewards:", error); } } getDelegationRewards(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder addresses with your actual data. # Data is ABI-encoded: function selector + padded delegator address + encoded validator address string. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x9ad563b4000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa960450000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002b61727476616c6f70657231713233347273673574367568766a6b6c376c38396d6e30707172737475760000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### delegatorValidators Retrieves a list of all validators from which a delegator has rewards. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function delegatorValidators(address delegatorAddress) view returns (string[] validators)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input const delegatorAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder async function getDelegatorValidators() { try { const validators = await contract.delegatorValidators(delegatorAddress); console.log("Validators:", validators); } catch (error) { console.error("Error fetching delegator validators:", error); } } getDelegatorValidators(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # Data is ABI-encoded: function selector + padded delegator address. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0xa66cb605000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### delegatorWithdrawAddress Queries the address that can withdraw rewards for a given delegator. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function delegatorWithdrawAddress(address delegatorAddress) view returns (string withdrawAddress)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input const delegatorAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder async function getWithdrawAddress() { try { const withdrawAddress = await contract.delegatorWithdrawAddress(delegatorAddress); console.log("Withdraw Address:", withdrawAddress); } catch (error) { console.error("Error fetching withdraw address:", error); } } getWithdrawAddress(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # Data is ABI-encoded: function selector + padded delegator address. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x5431f450000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### communityPool Queries the current balance of the community pool. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function communityPool() view returns (tuple(string denom, uint256 amount, uint8 precision)[] coins)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); async function getCommunityPoolBalance() { try { const balance = await contract.communityPool(); console.log("Community Pool Balance:", JSON.stringify(balance, null, 2)); } catch (error) { console.error("Error fetching community pool balance:", error); } } getCommunityPoolBalance(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # Data is ABI-encoded: just the function selector. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x14d140b0" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### validatorCommission Queries the accumulated commission for a specific validator. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function validatorCommission(string memory validatorAddress) view returns (tuple(string denom, uint256 amount, uint8 precision)[] commission)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input const validatorAddress = "artvaloper1..."; // Placeholder async function getValidatorCommission() { try { const commission = await contract.validatorCommission(validatorAddress); console.log("Validator Commission:", JSON.stringify(commission, null, 2)); } catch (error) { console.error("Error fetching validator commission:", error); } } getValidatorCommission(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # Data is ABI-encoded: function selector + encoded validator address string. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x3dd40f780000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002b61727476616c6f70657231713233347273673574367568766a6b6c376c38396d6e30707172737475760000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### validatorDistributionInfo Queries a validator's commission and self-delegation rewards. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function validatorDistributionInfo(string memory validatorAddress) view returns (tuple(string operatorAddress, tuple(string denom, uint256 amount, uint8 precision)[] selfBondRewards, tuple(string denom, uint256 amount, uint8 precision)[] commission) distributionInfo)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input const validatorAddress = "artvaloper1..."; // Placeholder async function getValidatorDistInfo() { try { const info = await contract.validatorDistributionInfo(validatorAddress); console.log("Validator Distribution Info:", JSON.stringify(info, null, 2)); } catch (error) { console.error("Error fetching validator distribution info:", error); } } getValidatorDistInfo(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # Data is ABI-encoded: function selector + encoded validator address string. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x54212a890000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002b61727476616c6f70657231713233347273673574367568766a6b6c376c38396d6e30707172737475760000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### validatorOutstandingRewards Queries the outstanding rewards of a validator. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function validatorOutstandingRewards(string memory validatorAddress) view returns (tuple(string denom, uint256 amount, uint8 precision)[] rewards)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input const validatorAddress = "artvaloper1..."; // Placeholder async function getOutstandingRewards() { try { const rewards = await contract.validatorOutstandingRewards(validatorAddress); console.log("Validator Outstanding Rewards:", JSON.stringify(rewards, null, 2)); } catch (error) { console.error("Error fetching outstanding rewards:", error); } } getOutstandingRewards(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # Data is ABI-encoded: function selector + encoded validator address string. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x85b2d2da0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002b61727476616c6f70657231713233347273673574367568766a6b6c376c38396d6e30707172737475760000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### validatorSlashes Queries slashing events for a validator within a specific height range. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function validatorSlashes(string validatorAddress, uint64 startingHeight, uint64 endingHeight, tuple(bytes key, uint64 offset, uint64 limit, bool countTotal, bool reverse) pageRequest) view returns (tuple(uint64 validatorPeriod, tuple(uint256 value, uint8 precision) fraction, int64 height)[] slashes, tuple(bytes nextKey, uint64 total) pageResponse)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000801"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const validatorAddress = "artvaloper1..."; // Placeholder const startingHeight = 1000; // Starting block height const endingHeight = 2000; // Ending block height const pageRequest = { key: "0x", offset: 0, limit: 10, countTotal: true, reverse: false }; async function getValidatorSlashes() { try { const [slashes, pageResponse] = await contract.validatorSlashes( validatorAddress, startingHeight, endingHeight, pageRequest ); console.log("Validator Slashes:", JSON.stringify(slashes, null, 2)); console.log("Page Response:", JSON.stringify(pageResponse, null, 2)); } catch (error) { console.error("Error fetching validator slashes:", error); } } getValidatorSlashes(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and parameters with your actual data. # Data is ABI-encoded: function selector + encoded validator address + heights + page request curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000801", "data": "0x8f2473ce0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000007d000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002b61727476616c6f70657231713233347273673574367568766a6b6c376c38396d6e3070717273747576000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ## Full Solidity Interface & ABI ```solidity title="Distribution Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.17; import "../common/Types.sol"; /// @dev The DistributionI contract's address. address constant DISTRIBUTION_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000801; /// @dev The DistributionI contract's instance. DistributionI constant DISTRIBUTION_CONTRACT = DistributionI( DISTRIBUTION_PRECOMPILE_ADDRESS ); struct ValidatorSlashEvent { uint64 validatorPeriod; Dec fraction; } struct ValidatorDistributionInfo { string operatorAddress; DecCoin[] selfBondRewards; DecCoin[] commission; } struct DelegationDelegatorReward { string validatorAddress; DecCoin[] reward; } /// @author Evmos Team /// @title Distribution Precompile Contract /// @dev The interface through which solidity contracts will interact with Distribution /// @custom:address 0x0000000000000000000000000000000000000801 interface DistributionI { event ClaimRewards(address indexed delegatorAddress, uint256 amount); event SetWithdrawerAddress(address indexed caller, string withdrawerAddress); event WithdrawDelegatorReward(address indexed delegatorAddress, address indexed validatorAddress, uint256 amount); event WithdrawValidatorCommission(string indexed validatorAddress, uint256 commission); event FundCommunityPool(address indexed depositor, string denom, uint256 amount); event DepositValidatorRewardsPool(address indexed depositor, address indexed validatorAddress, string denom, uint256 amount); function claimRewards(address delegatorAddress, uint32 maxRetrieve) external returns (bool success); function setWithdrawAddress(address delegatorAddress, string memory withdrawerAddress) external returns (bool success); function withdrawDelegatorRewards(address delegatorAddress, string memory validatorAddress) external returns (Coin[] calldata amount); function withdrawValidatorCommission(string memory validatorAddress) external returns (Coin[] calldata amount); function fundCommunityPool(address depositor, Coin[] memory amount) external returns (bool success); function depositValidatorRewardsPool(address depositor, string memory validatorAddress, Coin[] memory amount) external returns (bool success); function validatorDistributionInfo(string memory validatorAddress) external view returns (ValidatorDistributionInfo calldata distributionInfo); function validatorOutstandingRewards(string memory validatorAddress) external view returns (DecCoin[] calldata rewards); function validatorCommission(string memory validatorAddress) external view returns (DecCoin[] calldata commission); function validatorSlashes(string memory validatorAddress, uint64 startingHeight, uint64 endingHeight, PageRequest calldata pageRequest) external view returns (ValidatorSlashEvent[] calldata slashes, PageResponse calldata pageResponse); function delegationRewards(address delegatorAddress, string memory validatorAddress) external view returns (DecCoin[] calldata rewards); function delegationTotalRewards(address delegatorAddress) external view returns (DelegationDelegatorReward[] calldata rewards, DecCoin[] calldata total); function delegatorValidators(address delegatorAddress) external view returns (string[] calldata validators); function delegatorWithdrawAddress(address delegatorAddress) external view returns (string memory withdrawAddress); function communityPool() external view returns (DecCoin[] calldata coins); } ``` ```json title="Distribution ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "DistributionI", "sourceName": "solidity/precompiles/distribution/DistributionI.sol", "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "ClaimRewards", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "depositor", "type": "address" }, { "indexed": true, "internalType": "address", "name": "validatorAddress", "type": "address" }, { "indexed": false, "internalType": "string", "name": "denom", "type": "string" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "DepositValidatorRewardsPool", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "depositor", "type": "address" }, { "indexed": false, "internalType": "string", "name": "denom", "type": "string" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "FundCommunityPool", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "caller", "type": "address" }, { "indexed": false, "internalType": "string", "name": "withdrawerAddress", "type": "string" } ], "name": "SetWithdrawerAddress", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "indexed": true, "internalType": "address", "name": "validatorAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "WithdrawDelegatorReward", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "string", "name": "validatorAddress", "type": "string" }, { "indexed": false, "internalType": "uint256", "name": "commission", "type": "uint256" } ], "name": "WithdrawValidatorCommission", "type": "event" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "uint32", "name": "maxRetrieve", "type": "uint32" } ], "name": "claimRewards", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "communityPool", "outputs": [ { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "coins", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "delegationRewards", "outputs": [ { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "rewards", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" } ], "name": "delegationTotalRewards", "outputs": [ { "components": [ { "internalType": "string", "name": "validatorAddress", "type": "string" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "reward", "type": "tuple[]" } ], "internalType": "struct DelegationDelegatorReward[]", "name": "rewards", "type": "tuple[]" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "total", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" } ], "name": "delegatorValidators", "outputs": [ { "internalType": "string[]", "name": "validators", "type": "string[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" } ], "name": "delegatorWithdrawAddress", "outputs": [ { "internalType": "string", "name": "withdrawAddress", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "depositor", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "name": "depositValidatorRewardsPool", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "depositor", "type": "address" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "name": "fundCommunityPool", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "withdrawerAddress", "type": "string" } ], "name": "setWithdrawAddress", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "validatorCommission", "outputs": [ { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "commission", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "validatorDistributionInfo", "outputs": [ { "components": [ { "internalType": "string", "name": "operatorAddress", "type": "string" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "selfBondRewards", "type": "tuple[]" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "commission", "type": "tuple[]" } ], "internalType": "struct ValidatorDistributionInfo", "name": "distributionInfo", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "validatorOutstandingRewards", "outputs": [ { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct DecCoin[]", "name": "rewards", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "validatorAddress", "type": "string" }, { "internalType": "uint64", "name": "startingHeight", "type": "uint64" }, { "internalType": "uint64", "name": "endingHeight", "type": "uint64" }, { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pageRequest", "type": "tuple" } ], "name": "validatorSlashes", "outputs": [ { "components": [ { "internalType": "uint64", "name": "validatorPeriod", "type": "uint64" }, { "components": [ { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct Dec", "name": "fraction", "type": "tuple" } ], "internalType": "struct ValidatorSlashEvent[]", "name": "slashes", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "withdrawDelegatorRewards", "outputs": [ { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "withdrawValidatorCommission", "outputs": [ { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "stateMutability": "nonpayable", "type": "function" } ], "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {}, "deployedLinkReferences": {} } ``` # ERC20 Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/erc20 Standard ERC20 token functionality for native Cosmos tokens ## Overview The ERC20 precompile provides a standard ERC20-compliant interface that allows EVM tooling and libraries to interact with native Cosmos SDK tokens stored in the bank module as if they were standard ERC20 tokens. Each registered token pair gets its own unique precompile contract address that directly interfaces with the bank module - no token conversion or wrapping occurs. All balances, transfers, and operations work directly on the native bank module token balances. **Address**: Dynamic (assigned per token pair registration) **Related Module**: [x/erc20](/evm/latest/documentation/cosmos-sdk/modules/erc20) ## Gas Costs Gas costs are approximated and may vary based on token complexity and chain settings. | Method | Gas Cost | | --------------------------------------- | ------------ | | `name()` | \~3,000 gas | | `symbol()` | \~3,000 gas | | `decimals()` | \~2,000 gas | | `totalSupply()` | \~2,500 gas | | `balanceOf(address)` | \~2,900 gas | | `allowance(address,address)` | \~3,000 gas | | `transfer(address,uint256)` | \~35,000 gas | | `transferFrom(address,address,uint256)` | \~40,000 gas | | `approve(address,uint256)` | \~30,000 gas | | `increaseAllowance(address,uint256)` | \~30,500 gas | | `decreaseAllowance(address,uint256)` | \~30,500 gas | ## Token Pair Registration The ERC20 precompile works through a token pair registration system: 1. **Native Cosmos Token**: Each Cosmos SDK denomination (e.g., `test`, `atest`) exists as a native token in the bank module 2. **ERC20 Interface**: A corresponding ERC20 precompile contract provides an interface at a unique address 3. **Direct Bank Module Access**: The ERC20 interface operates directly on bank module balances - there is no separate ERC20 token state 4. **Dynamic Addresses**: Each token pair gets its own precompile address when registered The precompile address is deterministically generated based on the token denomination. Query the x/erc20 module to find the precompile address for a specific token. ## Methods ### `totalSupply` Returns the total amount of tokens in existence. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } function getTotalSupply() external view returns (uint256 totalSupply) { (bool success, bytes memory result) = tokenContract.staticcall( abi.encodeWithSignature("totalSupply()") ); require(success, "Total supply query failed"); totalSupply = abi.decode(result, (uint256)); return totalSupply; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // The address of the ERC20 precompile is dynamic, assigned when a token pair is registered. const erc20PrecompileAddress = ""; const provider = new ethers.JsonRpcProvider(""); // A generic ERC20 ABI is sufficient for read-only calls const erc20Abi = ["function totalSupply() view returns (uint256)"]; const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider); async function getTotalSupply() { try { const totalSupply = await contract.totalSupply(); console.log("Total Supply:", totalSupply.toString()); } catch (error) { console.error("Error fetching total supply:", error); } } getTotalSupply(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and with your actual data. # Data is the function selector for totalSupply() curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "", "data": "0x18160ddd" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `balanceOf` Returns the token balance of a specified account. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } function getBalance(address account) external view returns (uint256 balance) { (bool success, bytes memory result) = tokenContract.staticcall( abi.encodeWithSignature("balanceOf(address)", account) ); require(success, "Balance query failed"); balance = abi.decode(result, (uint256)); return balance; } // Get caller's balance function getMyBalance() external view returns (uint256) { return this.getBalance(msg.sender); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const erc20PrecompileAddress = ""; const accountAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder const provider = new ethers.JsonRpcProvider(""); const erc20Abi = ["function balanceOf(address account) view returns (uint256)"]; const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider); async function getBalance() { try { const balance = await contract.balanceOf(accountAddress); console.log(`Balance of ${accountAddress}:`, balance.toString()); } catch (error) { console.error("Error fetching balance:", error); } } getBalance(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace , , and the placeholder address with your actual data. # Data is ABI-encoded: function selector + padded address curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "", "data": "0x70a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `transfer` Moves tokens from the caller's account to a recipient. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } event TokenTransfer(address indexed from, address indexed to, uint256 amount); function transferTokens(address to, uint256 amount) external returns (bool success) { require(to != address(0), "Cannot transfer to zero address"); require(amount > 0, "Amount must be positive"); (bool callSuccess, bytes memory result) = tokenContract.call( abi.encodeWithSignature("transfer(address,uint256)", to, amount) ); require(callSuccess, "Transfer call failed"); success = abi.decode(result, (bool)); require(success, "Transfer returned false"); emit TokenTransfer(msg.sender, to, amount); return success; } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Token transfer requires a signed transaction" ``` ### `transferFrom` Moves tokens from one account to another using an allowance. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } event TokenTransferFrom(address indexed from, address indexed to, uint256 amount, address indexed spender); function transferFromTokens(address from, address to, uint256 amount) external returns (bool success) { require(from != address(0), "Cannot transfer from zero address"); require(to != address(0), "Cannot transfer to zero address"); require(amount > 0, "Amount must be positive"); (bool callSuccess, bytes memory result) = tokenContract.call( abi.encodeWithSignature("transferFrom(address,address,uint256)", from, to, amount) ); require(callSuccess, "TransferFrom call failed"); success = abi.decode(result, (bool)); require(success, "TransferFrom returned false"); emit TokenTransferFrom(from, to, amount, msg.sender); return success; } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Token transferFrom requires a signed transaction" ``` ### `approve` Sets the allowance of a spender over the caller's tokens. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } event TokenApproval(address indexed owner, address indexed spender, uint256 amount); function approveSpender(address spender, uint256 amount) external returns (bool success) { require(spender != address(0), "Cannot approve zero address"); (bool callSuccess, bytes memory result) = tokenContract.call( abi.encodeWithSignature("approve(address,uint256)", spender, amount) ); require(callSuccess, "Approve call failed"); success = abi.decode(result, (bool)); require(success, "Approve returned false"); emit TokenApproval(msg.sender, spender, amount); return success; } // Helper function to approve maximum amount function approveMax(address spender) external returns (bool) { return this.approveSpender(spender, type(uint256).max); } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Token approval requires a signed transaction" ``` ### `allowance` Returns the remaining number of tokens that a spender is allowed to spend on behalf of an owner. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } function checkAllowance(address owner, address spender) external view returns (uint256 allowance) { (bool success, bytes memory result) = tokenContract.staticcall( abi.encodeWithSignature("allowance(address,address)", owner, spender) ); require(success, "Allowance query failed"); allowance = abi.decode(result, (uint256)); return allowance; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const erc20PrecompileAddress = ""; const ownerAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder const spenderAddress = "0x27f320b7280911c7987a421a8138997a48d4b315"; // Placeholder const provider = new ethers.JsonRpcProvider(""); const erc20Abi = ["function allowance(address owner, address spender) view returns (uint256)"]; const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider); async function getAllowance() { try { const allowance = await contract.allowance(ownerAddress, spenderAddress); console.log("Allowance:", allowance.toString()); } catch (error) { console.error("Error fetching allowance:", error); } } getAllowance(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace , , and the placeholder addresses with your actual data. # Data is ABI-encoded: function selector + padded owner address + padded spender address curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "", "data": "0xdd62ed3e000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000027f320b7280911c7987a421a8138997a48d4b315" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `name` Returns the name of the token. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } function getTokenName() external view returns (string memory name) { (bool success, bytes memory result) = tokenContract.staticcall( abi.encodeWithSignature("name()") ); require(success, "Name query failed"); name = abi.decode(result, (string)); return name; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const erc20PrecompileAddress = ""; const provider = new ethers.JsonRpcProvider(""); const erc20Abi = ["function name() view returns (string)"]; const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider); async function getName() { try { const name = await contract.name(); console.log("Token Name:", name); } catch (error) { console.error("Error fetching name:", error); } } getName(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and with your actual data. # Data is the function selector for name() curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "", "data": "0x06fdde03" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `symbol` Returns the symbol of the token. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } function getTokenSymbol() external view returns (string memory symbol) { (bool success, bytes memory result) = tokenContract.staticcall( abi.encodeWithSignature("symbol()") ); require(success, "Symbol query failed"); symbol = abi.decode(result, (string)); return symbol; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const erc20PrecompileAddress = ""; const provider = new ethers.JsonRpcProvider(""); const erc20Abi = ["function symbol() view returns (string)"]; const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider); async function getSymbol() { try { const symbol = await contract.symbol(); console.log("Token Symbol:", symbol); } catch (error) { console.error("Error fetching symbol:", error); } } getSymbol(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and with your actual data. # Data is the function selector for symbol() curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "", "data": "0x95d89b41" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `decimals` Returns the number of decimals used for the token. **Decimal Handling**: The ERC20 precompile may need to handle complex decimal conversions between Cosmos native tokens and ERC20 representation. Some Cosmos tokens use 6 decimals (e.g., `test`) while ERC20 typically uses 18. Always verify the decimal count for accurate amount calculations. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ERC20Example { address public immutable tokenContract; constructor(address _tokenContract) { tokenContract = _tokenContract; } function getTokenDecimals() external view returns (uint8 decimals) { (bool success, bytes memory result) = tokenContract.staticcall( abi.encodeWithSignature("decimals()") ); require(success, "Decimals query failed"); decimals = abi.decode(result, (uint8)); return decimals; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const erc20PrecompileAddress = ""; const provider = new ethers.JsonRpcProvider(""); const erc20Abi = ["function decimals() view returns (uint8)"]; const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider); async function getDecimals() { try { const decimals = await contract.decimals(); console.log("Token Decimals:", decimals.toString()); } catch (error) { console.error("Error fetching decimals:", error); } } getDecimals(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and with your actual data. # Data is the function selector for decimals() curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "", "data": "0x313ce567" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ## Full Solidity Interface & ABI ```solidity title="ERC20 Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 amount) external returns (bool); } ``` ```json title="ERC20 ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "IERC20MetadataAllowance", "sourceName": "solidity/precompiles/erc20/IERC20MetadataAllowance.sol", "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "balanceOf", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "decimals", "outputs": [ { "internalType": "uint8", "name": "", "type": "uint8" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "subtractedValue", "type": "uint256" } ], "name": "decreaseAllowance", "outputs": [ { "internalType": "bool", "name": "approved", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "addedValue", "type": "uint256" } ], "name": "increaseAllowance", "outputs": [ { "internalType": "bool", "name": "approved", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "name", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalSupply", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transfer", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" } ] } ``` # Governance Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/governance On-chain governance participation through proposal submission, voting, and governance query operations ## Overview The Governance precompile provides comprehensive access to the Cosmos SDK's `x/gov` module, enabling smart contracts to participate in on-chain governance. It allows for submitting and canceling proposals, depositing funds, and casting votes. Additionally, it offers extensive query capabilities to retrieve information about proposals, votes, deposits, and overall governance parameters. **Precompile Address**: `0x0000000000000000000000000000000000000805` **Related Module**: [x/gov](/sdk/latest/modules/gov/README) ## Gas Costs Gas costs are approximated and may vary based on proposal complexity and chain settings. | Method | Gas Cost | | ---------------- | ------------------------------ | | **Transactions** | `2000 + (30 × bytes of input)` | | **Queries** | `1000 + (3 × bytes of input)` | ## Transaction Methods ### `submitProposal` Submits a new governance proposal. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceExample { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct Coin { string denom; uint256 amount; } event ProposalSubmitted(address indexed proposer, uint64 indexed proposalId); function submitProposal(bytes calldata jsonProposal, Coin[] calldata initialDeposit) external payable returns (uint64 proposalId) { require(jsonProposal.length > 0, "Proposal cannot be empty"); require(initialDeposit.length > 0, "Initial deposit required"); // Call the governance precompile (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.call( abi.encodeWithSignature( "submitProposal(address,bytes,tuple(string,uint256)[])", msg.sender, jsonProposal, initialDeposit ) ); require(success, "Proposal submission failed"); proposalId = abi.decode(result, (uint64)); emit ProposalSubmitted(msg.sender, proposalId); return proposalId; } // Helper function to create a coin struct function createCoin(string memory denom, uint256 amount) external pure returns (Coin memory) { return Coin({denom: denom, amount: amount}); } } ``` ### `vote` Casts a single vote on an active proposal. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceExample { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; enum VoteOption { Unspecified, Yes, Abstain, No, NoWithVeto } event VoteCast(address indexed voter, uint64 indexed proposalId, VoteOption option); function vote(uint64 proposalId, VoteOption option, string calldata metadata) external { require(proposalId > 0, "Invalid proposal ID"); require(option != VoteOption.Unspecified, "Must specify a vote option"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.call( abi.encodeWithSignature( "vote(address,uint64,uint8,string)", msg.sender, proposalId, uint8(option), metadata ) ); require(success, "Vote failed"); bool voteSuccess = abi.decode(result, (bool)); require(voteSuccess, "Vote was rejected"); emit VoteCast(msg.sender, proposalId, option); } // Convenience function to vote yes function voteYes(uint64 proposalId) external { this.vote(proposalId, VoteOption.Yes, ""); } // Convenience function to vote no function voteNo(uint64 proposalId) external { this.vote(proposalId, VoteOption.No, ""); } } ``` ### `voteWeighted` Casts a weighted vote, splitting voting power across multiple options. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceWeightedVote { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; enum VoteOption { Unspecified, Yes, Abstain, No, NoWithVeto } struct WeightedVoteOption { VoteOption option; string weight; } event WeightedVoteCast( address indexed voter, uint64 indexed proposalId, WeightedVoteOption[] options ); function voteWeighted( uint64 proposalId, WeightedVoteOption[] calldata options, string calldata metadata ) external returns (bool success) { require(proposalId > 0, "Invalid proposal ID"); require(options.length > 0, "Must provide vote options"); // Validate that weights sum to 1.0 (represented as "1.000000000000000000") // This is just a basic check - in practice you'd want more robust validation (bool callSuccess, bytes memory result) = GOVERNANCE_PRECOMPILE.call( abi.encodeWithSignature( "voteWeighted(address,uint64,tuple(uint8,string)[],string)", msg.sender, proposalId, options, metadata ) ); require(callSuccess, "Weighted vote failed"); success = abi.decode(result, (bool)); require(success, "Weighted vote was rejected"); emit WeightedVoteCast(msg.sender, proposalId, options); return success; } // Helper function to create a split vote (e.g., 70% Yes, 30% Abstain) function voteSplit( uint64 proposalId, uint256 yesPercent, uint256 abstainPercent ) external returns (bool) { require(yesPercent + abstainPercent == 100, "Percentages must sum to 100"); WeightedVoteOption[] memory options = new WeightedVoteOption[](2); // Convert percentages to decimal weights (e.g., 70% = "0.700000000000000000") options[0] = WeightedVoteOption({ option: VoteOption.Yes, weight: string(abi.encodePacked("0.", _padPercentage(yesPercent))) }); options[1] = WeightedVoteOption({ option: VoteOption.Abstain, weight: string(abi.encodePacked("0.", _padPercentage(abstainPercent))) }); return voteWeighted(proposalId, options, "Split vote"); } // Helper function to pad percentage to 18 decimal places function _padPercentage(uint256 percent) internal pure returns (string memory) { require(percent <= 100, "Percent cannot exceed 100"); if (percent == 100) return "000000000000000000"; // Special case for 100% // This is a simplified version - in practice you'd want more robust decimal handling string memory percentStr = _toString(percent); if (percent < 10) { return string(abi.encodePacked("0", percentStr, "0000000000000000")); } else { return string(abi.encodePacked(percentStr, "0000000000000000")); } } function _toString(uint256 value) internal pure returns (string memory) { if (value == 0) return "0"; uint256 temp = value; uint256 digits; while (temp != 0) { digits++; temp /= 10; } bytes memory buffer = new bytes(digits); while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; } return string(buffer); } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Weighted voting requires a signed transaction" ``` ### `deposit` Adds funds to a proposal's deposit during the deposit period. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceExample { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct Coin { string denom; uint256 amount; } event DepositMade(address indexed depositor, uint64 indexed proposalId, uint256 amount); function deposit(uint64 proposalId, Coin[] calldata amount) external payable { require(proposalId > 0, "Invalid proposal ID"); require(amount.length > 0, "Deposit amount required"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.call{value: msg.value}( abi.encodeWithSignature( "deposit(address,uint64,tuple(string,uint256)[])", msg.sender, proposalId, amount ) ); require(success, "Deposit failed"); bool depositSuccess = abi.decode(result, (bool)); require(depositSuccess, "Deposit was rejected"); // Calculate total deposited amount for event uint256 totalAmount = 0; for (uint i = 0; i < amount.length; i++) { totalAmount += amount[i].amount; } emit DepositMade(msg.sender, proposalId, totalAmount); } // Convenience function to deposit native tokens function depositNative(uint64 proposalId, uint256 amount, string memory denom) external payable { require(msg.value >= amount, "Insufficient value sent"); Coin[] memory coins = new Coin[](1); coins[0] = Coin({denom: denom, amount: amount}); this.deposit{value: amount}(proposalId, coins); } } ``` ### `cancelProposal` Cancels a proposal that is still in its deposit period. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceCancelProposal { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; event ProposalCanceled(address indexed proposer, uint64 indexed proposalId, bool success); function cancelProposal(uint64 proposalId) external returns (bool success) { require(proposalId > 0, "Invalid proposal ID"); (bool callSuccess, bytes memory result) = GOVERNANCE_PRECOMPILE.call( abi.encodeWithSignature("cancelProposal(address,uint64)", msg.sender, proposalId) ); require(callSuccess, "Cancel proposal call failed"); success = abi.decode(result, (bool)); emit ProposalCanceled(msg.sender, proposalId, success); return success; } // Function to safely cancel a proposal with additional checks function safeCancelProposal(uint64 proposalId) external returns (bool success) { // First, check if the proposal exists and can be canceled // Note: In practice, you'd want to call getProposal first to verify status success = cancelProposal(proposalId); require(success, "Proposal cancellation failed - check if you're the proposer and proposal is in deposit period"); return success; } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Canceling proposals requires a signed transaction" ``` ## Query Methods ### `getProposal` Retrieves detailed information about a specific proposal. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceQueries { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct ProposalData { uint64 id; string[] messages; uint32 status; TallyResultData finalTallyResult; uint64 submitTime; uint64 depositEndTime; Coin[] totalDeposit; uint64 votingStartTime; uint64 votingEndTime; string metadata; string title; string summary; address proposer; } struct TallyResultData { string yes; string abstain; string no; string noWithVeto; } struct Coin { string denom; uint256 amount; } function getProposal(uint64 proposalId) external view returns (ProposalData memory proposal) { require(proposalId > 0, "Invalid proposal ID"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature("getProposal(uint64)", proposalId) ); require(success, "Failed to get proposal"); proposal = abi.decode(result, (ProposalData)); return proposal; } // Helper function to check if proposal is in voting period function isProposalInVotingPeriod(uint64 proposalId) external view returns (bool) { ProposalData memory proposal = this.getProposal(proposalId); return proposal.status == 2; // PROPOSAL_STATUS_VOTING_PERIOD } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile, including the complex return struct const precompileAbi = [ "function getProposal(uint64 proposalId) view returns (tuple(uint64 id, address proposer, string metadata, uint64 submit_time, uint64 voting_start_time, uint64 voting_end_time, uint8 status, tuple(string yes_count, string abstain_count, string no_count, string no_with_veto_count) final_tally_result, tuple(string denom, uint256 amount)[] total_deposit, string[] messages) proposal)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input: The ID of the proposal to query const proposalId = 1; async function getProposalDetails() { try { const proposal = await contract.getProposal(proposalId); console.log(`Proposal ${proposalId} Details:`, JSON.stringify(proposal, null, 2)); } catch (error) { console.error(`Error fetching proposal ${proposalId}:`, error); } } getProposalDetails(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual data. # This example queries for proposal with ID 1. # Data is ABI-encoded: function selector + padded proposal ID curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0xc73dce9c0000000000000000000000000000000000000000000000000000000000000001" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getProposals` Retrieves a filtered and paginated list of proposals. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceProposalsList { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct PageRequest { bytes key; uint64 offset; uint64 limit; bool countTotal; bool reverse; } struct PageResponse { bytes nextKey; uint64 total; } struct ProposalData { uint64 id; string[] messages; uint32 status; TallyResultData finalTallyResult; uint64 submitTime; uint64 depositEndTime; Coin[] totalDeposit; uint64 votingStartTime; uint64 votingEndTime; string metadata; string title; string summary; address proposer; } struct TallyResultData { string yes; string abstain; string no; string noWithVeto; } struct Coin { string denom; uint256 amount; } function getProposals( uint32 proposalStatus, address voter, address depositor, PageRequest memory pagination ) external view returns (ProposalData[] memory proposals, PageResponse memory pageResponse) { (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature( "getProposals(uint32,address,address,(bytes,uint64,uint64,bool,bool))", proposalStatus, voter, depositor, pagination ) ); require(success, "Failed to get proposals"); (proposals, pageResponse) = abi.decode(result, (ProposalData[], PageResponse)); return (proposals, pageResponse); } // Helper function to get all active proposals (in voting period) function getActiveProposals(uint64 limit) external view returns (ProposalData[] memory) { PageRequest memory pagination = PageRequest({ key: "", offset: 0, limit: limit, countTotal: true, reverse: false }); uint32 votingStatus = 2; // PROPOSAL_STATUS_VOTING_PERIOD (ProposalData[] memory proposals,) = this.getProposals( votingStatus, address(0), address(0), pagination ); return proposals; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile, including the complex return struct const precompileAbi = [ "function getProposals(tuple(uint64 offset, bytes key, uint64 limit, bool count_total, bool reverse) pagination, uint8 proposal_status, address voter, address depositor) view returns (tuple(uint64 id, address proposer, string metadata, uint64 submit_time, uint64 voting_start_time, uint64 voting_end_time, uint8 status, tuple(string yes_count, string abstain_count, string no_count, string no_with_veto_count) final_tally_result, tuple(string denom, uint256 amount)[] total_deposit, string[] messages)[] proposals, tuple(bytes next_key, uint64 total) page_response)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs for filtering and pagination const pagination = { offset: 0, key: "0x", // Start from the beginning limit: 10, count_total: true, reverse: false, }; const proposalStatus = 0; // 0 for Unspecified, 1 for Deposit, 2 for Voting, etc. const voterAddress = ethers.ZeroAddress; // Filter by voter, or ZeroAddress for none const depositorAddress = ethers.ZeroAddress; // Filter by depositor, or ZeroAddress for none async function getProposalsList() { try { const result = await contract.getProposals(pagination, proposalStatus, voterAddress, depositorAddress); // The result object contains 'proposals' and 'page_response' console.log("Proposals:", JSON.stringify(result.proposals, null, 2)); console.log("Pagination Response:", result.page_response); } catch (error) { console.error("Error fetching proposals:", error); } } getProposalsList(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # This example queries for the first 10 proposals with any status. # Data is ABI-encoded: function selector + complex input struct. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x458af537000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getTallyResult` Retrieves the current or final vote tally for a proposal. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceTally { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct TallyResultData { string yes; string abstain; string no; string noWithVeto; } function getTallyResult(uint64 proposalId) external view returns (TallyResultData memory tallyResult) { require(proposalId > 0, "Invalid proposal ID"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature("getTallyResult(uint64)", proposalId) ); require(success, "Failed to get tally result"); tallyResult = abi.decode(result, (TallyResultData)); return tallyResult; } // Helper function to check if proposal is passing function isProposalPassing(uint64 proposalId) external view returns (bool) { TallyResultData memory tally = this.getTallyResult(proposalId); // Convert string votes to numbers for comparison (simplified) // In production, use proper decimal math libraries return keccak256(bytes(tally.yes)) > keccak256(bytes(tally.no)); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile const precompileAbi = [ "function getTallyResult(uint64 proposalId) view returns (tuple(string yes_count, string abstain_count, string no_count, string no_with_veto_count) tally)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input: The ID of the proposal to get the tally for const proposalId = 1; async function getTally() { try { const tally = await contract.getTallyResult(proposalId); console.log(`Tally for Proposal ${proposalId}:`, { yes: tally.yes_count, abstain: tally.abstain_count, no: tally.no_count, noWithVeto: tally.no_with_veto_count, }); } catch (error) { console.error(`Error fetching tally for proposal ${proposalId}:`, error); } } getTally(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual data. # This example queries the tally for proposal with ID 1. # Data is ABI-encoded: function selector + padded proposal ID curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x25a8c82a0000000000000000000000000000000000000000000000000000000000000001" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getVote` Retrieves the vote cast by a specific address on a proposal. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceVoteQuery { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; enum VoteOption { Unspecified, Yes, Abstain, No, NoWithVeto } struct WeightedVoteOption { VoteOption option; string weight; } struct WeightedVote { uint64 proposalId; address voter; WeightedVoteOption[] options; string metadata; } function getVote(uint64 proposalId, address voter) external view returns (WeightedVote memory vote) { require(proposalId > 0, "Invalid proposal ID"); require(voter != address(0), "Invalid voter address"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature("getVote(uint64,address)", proposalId, voter) ); require(success, "Failed to get vote"); vote = abi.decode(result, (WeightedVote)); return vote; } // Helper function to check if an address has voted function hasVoted(uint64 proposalId, address voter) external view returns (bool) { try this.getVote(proposalId, voter) returns (WeightedVote memory vote) { return vote.options.length > 0; } catch { return false; } } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile const precompileAbi = [ "function getVote(uint64 proposalId, address voter) view returns (tuple(uint64 proposal_id, address voter, tuple(uint8 option, string weight)[] options, string metadata) vote)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const proposalId = 1; const voterAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder async function getVoteDetails() { try { const vote = await contract.getVote(proposalId, voterAddress); console.log("Vote Details:", JSON.stringify(vote, null, 2)); } catch (error) { console.error("Error fetching vote:", error); } } getVoteDetails(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # This example queries the vote of a specific address on proposal with ID 1. # Data is ABI-encoded: function selector + padded proposal ID + padded voter address curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x54f6530f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getVotes` Retrieves all votes cast on a proposal, with pagination. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceVotesList { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct PageRequest { bytes key; uint64 offset; uint64 limit; bool countTotal; bool reverse; } struct PageResponse { bytes nextKey; uint64 total; } enum VoteOption { Unspecified, Yes, Abstain, No, NoWithVeto } struct WeightedVoteOption { VoteOption option; string weight; } struct WeightedVote { uint64 proposalId; address voter; WeightedVoteOption[] options; string metadata; } function getVotes( uint64 proposalId, PageRequest memory pagination ) external view returns (WeightedVote[] memory votes, PageResponse memory pageResponse) { require(proposalId > 0, "Invalid proposal ID"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature( "getVotes(uint64,(bytes,uint64,uint64,bool,bool))", proposalId, pagination ) ); require(success, "Failed to get votes"); (votes, pageResponse) = abi.decode(result, (WeightedVote[], PageResponse)); return (votes, pageResponse); } // Helper function to get all Yes votes function getYesVoters(uint64 proposalId, uint64 limit) external view returns (address[] memory) { PageRequest memory pagination = PageRequest({ key: "", offset: 0, limit: limit, countTotal: false, reverse: false }); (WeightedVote[] memory votes,) = this.getVotes(proposalId, pagination); uint256 yesCount = 0; for (uint i = 0; i < votes.length; i++) { if (votes[i].options.length > 0 && votes[i].options[0].option == VoteOption.Yes) { yesCount++; } } address[] memory yesVoters = new address[](yesCount); uint256 index = 0; for (uint i = 0; i < votes.length; i++) { if (votes[i].options.length > 0 && votes[i].options[0].option == VoteOption.Yes) { yesVoters[index++] = votes[i].voter; } } return yesVoters; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile const precompileAbi = [ "function getVotes(uint64 proposalId, tuple(uint64 offset, bytes key, uint64 limit, bool count_total, bool reverse) pagination) view returns (tuple(uint64 proposal_id, address voter, tuple(uint8 option, string weight)[] options, string metadata)[] votes, tuple(bytes next_key, uint64 total) page_response)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const proposalId = 1; const pagination = { offset: 0, key: "0x", limit: 10, count_total: true, reverse: false, }; async function getVotesList() { try { const result = await contract.getVotes(proposalId, pagination); console.log(`Votes for proposal ${proposalId}:`, JSON.stringify(result.votes, null, 2)); console.log("Pagination Response:", result.page_response); } catch (error) { console.error("Error fetching votes:", error); } } getVotesList(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # This example queries for the first 10 votes on proposal with ID 1. # Data is ABI-encoded: function selector + padded proposal ID + pagination struct. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x6f74a8f300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getDeposit` Retrieves deposit information for a specific depositor on a proposal. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceDepositQuery { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct Coin { string denom; uint256 amount; } struct DepositData { uint64 proposalId; address depositor; Coin[] amount; } function getDeposit(uint64 proposalId, address depositor) external view returns (DepositData memory deposit) { require(proposalId > 0, "Invalid proposal ID"); require(depositor != address(0), "Invalid depositor address"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature("getDeposit(uint64,address)", proposalId, depositor) ); require(success, "Failed to get deposit"); deposit = abi.decode(result, (DepositData)); return deposit; } // Helper function to get total deposit amount for a specific denom function getDepositAmount(uint64 proposalId, address depositor, string memory denom) external view returns (uint256) { DepositData memory deposit = this.getDeposit(proposalId, depositor); for (uint i = 0; i < deposit.amount.length; i++) { if (keccak256(bytes(deposit.amount[i].denom)) == keccak256(bytes(denom))) { return deposit.amount[i].amount; } } return 0; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile const precompileAbi = [ "function getDeposit(uint64 proposalId, address depositor) view returns (tuple(address depositor, tuple(string denom, uint256 amount)[] amount) deposit)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const proposalId = 1; const depositorAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder async function getDepositDetails() { try { const deposit = await contract.getDeposit(proposalId, depositorAddress); console.log("Deposit Details:", JSON.stringify(deposit, null, 2)); } catch (error) { console.error("Error fetching deposit:", error); } } getDepositDetails(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder address with your actual data. # This example queries the deposit of a specific address on proposal with ID 1. # Data is ABI-encoded: function selector + padded proposal ID + padded depositor address curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x582138a30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getDeposits` Retrieves all deposits made on a proposal, with pagination. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceDepositsList { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct PageRequest { bytes key; uint64 offset; uint64 limit; bool countTotal; bool reverse; } struct PageResponse { bytes nextKey; uint64 total; } struct Coin { string denom; uint256 amount; } struct DepositData { uint64 proposalId; address depositor; Coin[] amount; } function getDeposits( uint64 proposalId, PageRequest memory pagination ) external view returns (DepositData[] memory deposits, PageResponse memory pageResponse) { require(proposalId > 0, "Invalid proposal ID"); (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature( "getDeposits(uint64,(bytes,uint64,uint64,bool,bool))", proposalId, pagination ) ); require(success, "Failed to get deposits"); (deposits, pageResponse) = abi.decode(result, (DepositData[], PageResponse)); return (deposits, pageResponse); } // Helper function to get all depositors function getDepositors(uint64 proposalId, uint64 limit) external view returns (address[] memory) { PageRequest memory pagination = PageRequest({ key: "", offset: 0, limit: limit, countTotal: false, reverse: false }); (DepositData[] memory deposits,) = this.getDeposits(proposalId, pagination); address[] memory depositors = new address[](deposits.length); for (uint i = 0; i < deposits.length; i++) { depositors[i] = deposits[i].depositor; } return depositors; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile const precompileAbi = [ "function getDeposits(uint64 proposalId, tuple(uint64 offset, bytes key, uint64 limit, bool count_total, bool reverse) pagination) view returns (tuple(address depositor, tuple(string denom, uint256 amount)[] amount)[] deposits, tuple(bytes next_key, uint64 total) page_response)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const proposalId = 1; const pagination = { offset: 0, key: "0x", limit: 10, count_total: true, reverse: false, }; async function getDepositsList() { try { const result = await contract.getDeposits(proposalId, pagination); console.log(`Deposits for proposal ${proposalId}:`, JSON.stringify(result.deposits, null, 2)); console.log("Pagination Response:", result.page_response); } catch (error) { console.error("Error fetching deposits:", error); } } getDepositsList(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # This example queries for the first 10 deposits on proposal with ID 1. # Data is ABI-encoded: function selector + padded proposal ID + pagination struct. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x3677fd5500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getParams` Retrieves current governance parameters. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceParams { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; struct Coin { string denom; uint256 amount; } struct Params { int64 votingPeriod; Coin[] minDeposit; int64 maxDepositPeriod; string quorum; string threshold; string vetoThreshold; string minInitialDepositRatio; string proposalCancelRatio; string proposalCancelDest; int64 expeditedVotingPeriod; string expeditedThreshold; Coin[] expeditedMinDeposit; bool burnVoteQuorum; bool burnProposalDepositPrevote; bool burnVoteVeto; string minDepositRatio; } function getParams() external view returns (Params memory params) { (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature("getParams()") ); require(success, "Failed to get params"); params = abi.decode(result, (Params)); return params; } // Helper function to get minimum deposit amount for a specific denom function getMinDepositForDenom(string memory denom) external view returns (uint256) { Params memory params = this.getParams(); for (uint i = 0; i < params.minDeposit.length; i++) { if (keccak256(bytes(params.minDeposit[i].denom)) == keccak256(bytes(denom))) { return params.minDeposit[i].amount; } } return 0; } // Helper function to get voting period in seconds function getVotingPeriodSeconds() external view returns (int64) { Params memory params = this.getParams(); return params.votingPeriod; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile const precompileAbi = [ "function getParams() view returns (tuple(string[] min_deposit, string max_deposit_period, string voting_period, string yes_quorum, string veto_threshold, string min_initial_deposit_ratio, string proposal_cancel_ratio, string proposal_cancel_dest, string min_deposit_ratio) params)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); async function getGovParams() { try { const params = await contract.getParams(); console.log("Governance Parameters:", JSON.stringify(params, null, 2)); } catch (error) { console.error("Error fetching governance parameters:", error); } } getGovParams(); ``` **Note**: The `getParams()` function returns a complex nested structure that may require manual ABI decoding in some ethers.js versions. The cURL method is more reliable for this specific function. ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # Data is ABI-encoded: just the function selector. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x5e615a6b" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getConstitution` Retrieves the current governance constitution. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract GovernanceConstitution { address constant GOVERNANCE_PRECOMPILE = 0x0000000000000000000000000000000000000805; function getConstitution() external view returns (string memory constitution) { (bool success, bytes memory result) = GOVERNANCE_PRECOMPILE.staticcall( abi.encodeWithSignature("getConstitution()") ); require(success, "Failed to get constitution"); constitution = abi.decode(result, (string)); return constitution; } // Helper function to check if constitution is set function hasConstitution() external view returns (bool) { string memory constitution = this.getConstitution(); return bytes(constitution).length > 0; } // Helper function to get constitution length function getConstitutionLength() external view returns (uint256) { string memory constitution = this.getConstitution(); return bytes(constitution).length; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI for the precompile const precompileAbi = [ "function getConstitution() view returns (string constitution)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000805"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); async function getGovConstitution() { try { const constitution = await contract.getConstitution(); console.log("Governance Constitution:", constitution); } catch (error) { console.error("Error fetching governance constitution:", error); } } getGovConstitution(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # Data is ABI-encoded: just the function selector. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000805", "data": "0x04b12c7f" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ## Full Solidity Interface & ABI ```solidity title="Governance Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.17; import "../common/Types.sol"; /// @dev The IGov contract's address. address constant GOV_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000805; /// @dev The IGov contract's instance. IGov constant GOV_CONTRACT = IGov(GOV_PRECOMPILE_ADDRESS); /** * @dev VoteOption enumerates the valid vote options for a given governance proposal. */ enum VoteOption { // Unspecified defines a no-op vote option. Unspecified, // Yes defines a yes vote option. Yes, // Abstain defines an abstain vote option. Abstain, // No defines a no vote option. No, // NoWithVeto defines a no with veto vote option. NoWithVeto } /// @dev WeightedVote represents a vote on a governance proposal struct WeightedVote { uint64 proposalId; address voter; WeightedVoteOption[] options; string metadata; } /// @dev WeightedVoteOption represents a weighted vote option struct WeightedVoteOption { VoteOption option; string weight; } /// @dev DepositData represents information about a deposit on a proposal struct DepositData { uint64 proposalId; address depositor; Coin[] amount; } /// @dev TallyResultData represents the tally result of a proposal struct TallyResultData { string yes; string abstain; string no; string noWithVeto; } /// @dev ProposalData represents a governance proposal struct ProposalData { uint64 id; string[] messages; uint32 status; TallyResultData finalTallyResult; uint64 submitTime; uint64 depositEndTime; Coin[] totalDeposit; uint64 votingStartTime; uint64 votingEndTime; string metadata; string title; string summary; address proposer; } /// @dev Params defines the governance parameters struct Params { int64 votingPeriod; Coin[] minDeposit; int64 maxDepositPeriod; string quorum; string threshold; string vetoThreshold; string minInitialDepositRatio; string proposalCancelRatio; string proposalCancelDest; int64 expeditedVotingPeriod; string expeditedThreshold; Coin[] expeditedMinDeposit; bool burnVoteQuorum; bool burnProposalDepositPrevote; bool burnVoteVeto; string minDepositRatio; } /// @author The Evmos Core Team /// @title Gov Precompile Contract /// @dev The interface through which solidity contracts will interact with Gov interface IGov { /// @dev SubmitProposal defines an Event emitted when a proposal is submitted. /// @param proposer the address of the proposer /// @param proposalId the proposal of id event SubmitProposal(address indexed proposer, uint64 proposalId); /// @dev CancelProposal defines an Event emitted when a proposal is canceled. /// @param proposer the address of the proposer /// @param proposalId the proposal of id event CancelProposal(address indexed proposer, uint64 proposalId); /// @dev Deposit defines an Event emitted when a deposit is made. /// @param depositor the address of the depositor /// @param proposalId the proposal of id /// @param amount the amount of the deposit event Deposit(address indexed depositor, uint64 proposalId, Coin[] amount); /// @dev Vote defines an Event emitted when a proposal voted. /// @param voter the address of the voter /// @param proposalId the proposal of id /// @param option the option for voter event Vote(address indexed voter, uint64 proposalId, uint8 option); /// @dev VoteWeighted defines an Event emitted when a proposal voted. /// @param voter the address of the voter /// @param proposalId the proposal of id /// @param options the options for voter event VoteWeighted( address indexed voter, uint64 proposalId, WeightedVoteOption[] options ); /// TRANSACTIONS /// @notice submitProposal creates a new proposal from a protoJSON document. /// @dev submitProposal defines a method to submit a proposal. /// @param jsonProposal The JSON proposal /// @param deposit The deposit for the proposal /// @return proposalId The proposal id function submitProposal( address proposer, bytes calldata jsonProposal, Coin[] calldata deposit ) external returns (uint64 proposalId); /// @dev cancelProposal defines a method to cancel a proposal. /// @param proposalId The proposal id /// @return success Whether the transaction was successful or not function cancelProposal( address proposer, uint64 proposalId ) external returns (bool success); /// @dev deposit defines a method to add a deposit to a proposal. /// @param proposalId The proposal id /// @param amount The amount to deposit function deposit( address depositor, uint64 proposalId, Coin[] calldata amount ) external returns (bool success); /// @dev vote defines a method to add a vote on a specific proposal. /// @param voter The address of the voter /// @param proposalId the proposal of id /// @param option the option for voter /// @param metadata the metadata for voter send /// @return success Whether the transaction was successful or not function vote( address voter, uint64 proposalId, VoteOption option, string memory metadata ) external returns (bool success); /// @dev voteWeighted defines a method to add a vote on a specific proposal. /// @param voter The address of the voter /// @param proposalId The proposal id /// @param options The options for voter /// @param metadata The metadata for voter send /// @return success Whether the transaction was successful or not function voteWeighted( address voter, uint64 proposalId, WeightedVoteOption[] calldata options, string memory metadata ) external returns (bool success); /// QUERIES /// @dev getVote returns the vote of a single voter for a /// given proposalId. /// @param proposalId The proposal id /// @param voter The voter on the proposal /// @return vote Voter's vote for the proposal function getVote( uint64 proposalId, address voter ) external view returns (WeightedVote memory vote); /// @dev getVotes Returns the votes for a specific proposal. /// @param proposalId The proposal id /// @param pagination The pagination options /// @return votes The votes for the proposal /// @return pageResponse The pagination information function getVotes( uint64 proposalId, PageRequest calldata pagination ) external view returns (WeightedVote[] memory votes, PageResponse memory pageResponse); /// @dev getDeposit returns the deposit of a single depositor for a given proposalId. /// @param proposalId The proposal id /// @param depositor The address of the depositor /// @return deposit The deposit information function getDeposit( uint64 proposalId, address depositor ) external view returns (DepositData memory deposit); /// @dev getDeposits returns all deposits for a specific proposal. /// @param proposalId The proposal id /// @param pagination The pagination options /// @return deposits The deposits for the proposal /// @return pageResponse The pagination information function getDeposits( uint64 proposalId, PageRequest calldata pagination ) external view returns ( DepositData[] memory deposits, PageResponse memory pageResponse ); /// @dev getTallyResult returns the tally result of a proposal. /// @param proposalId The proposal id /// @return tallyResult The tally result of the proposal function getTallyResult( uint64 proposalId ) external view returns (TallyResultData memory tallyResult); /// @dev getProposal returns the proposal details based on proposal id. /// @param proposalId The proposal id /// @return proposal The proposal data function getProposal( uint64 proposalId ) external view returns (ProposalData memory proposal); /// @dev getProposals returns proposals with matching status. /// @param proposalStatus The proposal status to filter by /// @param voter The voter address to filter by, if any /// @param depositor The depositor address to filter by, if any /// @param pagination The pagination config /// @return proposals The proposals matching the filter criteria /// @return pageResponse The pagination information function getProposals( uint32 proposalStatus, address voter, address depositor, PageRequest calldata pagination ) external view returns ( ProposalData[] memory proposals, PageResponse memory pageResponse ); /// @dev getParams returns the current governance parameters. /// @return params The governance parameters function getParams() external view returns (Params memory params); /// @dev getConstitution returns the current constitution. /// @return constitution The current constitution function getConstitution() external view returns (string memory constitution); } ``` ```json title="Governance ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "IGov", "sourceName": "solidity/precompiles/gov/IGov.sol", "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, { "indexed": true, "internalType": "uint64", "name": "proposalId", "type": "uint64" } ], "name": "CancelProposal", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "depositor", "type": "address" }, { "indexed": true, "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "indexed": false, "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "name": "Deposit", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, { "indexed": true, "internalType": "uint64", "name": "proposalId", "type": "uint64" } ], "name": "SubmitProposal", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "voter", "type": "address" }, { "indexed": true, "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "indexed": false, "internalType": "uint8", "name": "option", "type": "uint8" } ], "name": "Vote", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "voter", "type": "address" }, { "indexed": true, "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "components": [ { "internalType": "enum VoteOption", "name": "option", "type": "uint8" }, { "internalType": "string", "name": "weight", "type": "string" } ], "indexed": false, "internalType": "struct WeightedVoteOption[]", "name": "options", "type": "tuple[]" } ], "name": "VoteWeighted", "type": "event" }, { "inputs": [ { "internalType": "address", "name": "proposer", "type": "address" }, { "internalType": "uint64", "name": "proposalId", "type": "uint64" } ], "name": "cancelProposal", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "depositor", "type": "address" }, { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "name": "deposit", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "getConstitution", "outputs": [ { "internalType": "string", "name": "constitution", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "internalType": "address", "name": "depositor", "type": "address" } ], "name": "getDeposit", "outputs": [ { "components": [ { "internalType": "address", "name": "depositor", "type": "address" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "internalType": "struct DepositData", "name": "deposit", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pagination", "type": "tuple" } ], "name": "getDeposits", "outputs": [ { "components": [ { "internalType": "address", "name": "depositor", "type": "address" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "amount", "type": "tuple[]" } ], "internalType": "struct DepositData[]", "name": "deposits", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getParams", "outputs": [ { "components": [ { "internalType": "string", "name": "minDeposit", "type": "string" }, { "internalType": "string", "name": "maxDepositPeriod", "type": "string" }, { "internalType": "string", "name": "votingPeriod", "type": "string" }, { "internalType": "string", "name": "quorum", "type": "string" }, { "internalType": "string", "name": "threshold", "type": "string" }, { "internalType": "string", "name": "vetoThreshold", "type": "string" }, { "internalType": "string", "name": "minInitialDepositRatio", "type": "string" }, { "internalType": "string", "name": "proposalCancelRatio", "type": "string" }, { "internalType": "string", "name": "proposalCancelDest", "type": "string" }, { "internalType": "string", "name": "minDepositRatio", "type": "string" } ], "internalType": "struct Params", "name": "params", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" } ], "name": "getProposal", "outputs": [ { "components": [ { "internalType": "uint64", "name": "id", "type": "uint64" }, { "internalType": "address", "name": "proposer", "type": "address" }, { "internalType": "string", "name": "metadata", "type": "string" }, { "internalType": "uint64", "name": "submitTime", "type": "uint64" }, { "internalType": "uint64", "name": "votingStartTime", "type": "uint64" }, { "internalType": "uint64", "name": "votingEndTime", "type": "uint64" }, { "internalType": "uint8", "name": "status", "type": "uint8" }, { "components": [ { "internalType": "string", "name": "yesCount", "type": "string" }, { "internalType": "string", "name": "abstainCount", "type": "string" }, { "internalType": "string", "name": "noCount", "type": "string" }, { "internalType": "string", "name": "noWithVetoCount", "type": "string" } ], "internalType": "struct TallyResultData", "name": "finalTallyResult", "type": "tuple" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "totalDeposit", "type": "tuple[]" }, { "internalType": "string[]", "name": "messages", "type": "string[]" } ], "internalType": "struct ProposalData", "name": "proposal", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pagination", "type": "tuple" }, { "internalType": "uint8", "name": "proposalStatus", "type": "uint8" }, { "internalType": "address", "name": "voter", "type": "address" }, { "internalType": "address", "name": "depositor", "type": "address" } ], "name": "getProposals", "outputs": [ { "components": [ { "internalType": "uint64", "name": "id", "type": "uint64" }, { "internalType": "address", "name": "proposer", "type": "address" }, { "internalType": "string", "name": "metadata", "type": "string" }, { "internalType": "uint64", "name": "submitTime", "type": "uint64" }, { "internalType": "uint64", "name": "votingStartTime", "type": "uint64" }, { "internalType": "uint64", "name": "votingEndTime", "type": "uint64" }, { "internalType": "uint8", "name": "status", "type": "uint8" }, { "components": [ { "internalType": "string", "name": "yesCount", "type": "string" }, { "internalType": "string", "name": "abstainCount", "type": "string" }, { "internalType": "string", "name": "noCount", "type": "string" }, { "internalType": "string", "name": "noWithVetoCount", "type": "string" } ], "internalType": "struct TallyResultData", "name": "finalTallyResult", "type": "tuple" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "totalDeposit", "type": "tuple[]" }, { "internalType": "string[]", "name": "messages", "type": "string[]" } ], "internalType": "struct ProposalData[]", "name": "proposals", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" } ], "name": "getTallyResult", "outputs": [ { "components": [ { "internalType": "string", "name": "yesCount", "type": "string" }, { "internalType": "string", "name": "abstainCount", "type": "string" }, { "internalType": "string", "name": "noCount", "type": "string" }, { "internalType": "string", "name": "noWithVetoCount", "type": "string" } ], "internalType": "struct TallyResultData", "name": "tally", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "internalType": "address", "name": "voter", "type": "address" } ], "name": "getVote", "outputs": [ { "components": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "internalType": "address", "name": "voter", "type": "address" }, { "components": [ { "internalType": "uint8", "name": "option", "type": "uint8" }, { "internalType": "string", "name": "weight", "type": "string" } ], "internalType": "struct WeightedVoteOption[]", "name": "options", "type": "tuple[]" }, { "internalType": "string", "name": "metadata", "type": "string" } ], "internalType": "struct WeightedVote", "name": "vote", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pagination", "type": "tuple" } ], "name": "getVotes", "outputs": [ { "components": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "internalType": "address", "name": "voter", "type": "address" }, { "components": [ { "internalType": "uint8", "name": "option", "type": "uint8" }, { "internalType": "string", "name": "weight", "type": "string" } ], "internalType": "struct WeightedVoteOption[]", "name": "options", "type": "tuple[]" }, { "internalType": "string", "name": "metadata", "type": "string" } ], "internalType": "struct WeightedVote[]", "name": "votes", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "proposer", "type": "address" }, { "internalType": "bytes", "name": "jsonProposal", "type": "bytes" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin[]", "name": "deposit", "type": "tuple[]" } ], "name": "submitProposal", "outputs": [ { "internalType": "uint64", "name": "proposalId", "type": "uint64" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "voter", "type": "address" }, { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "internalType": "enum VoteOption", "name": "option", "type": "uint8" }, { "internalType": "string", "name": "metadata", "type": "string" } ], "name": "vote", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "voter", "type": "address" }, { "internalType": "uint64", "name": "proposalId", "type": "uint64" }, { "components": [ { "internalType": "enum VoteOption", "name": "option", "type": "uint8" }, { "internalType": "string", "name": "weight", "type": "string" } ], "internalType": "struct WeightedVoteOption[]", "name": "options", "type": "tuple[]" }, { "internalType": "string", "name": "metadata", "type": "string" } ], "name": "voteWeighted", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" } ] } ``` # ICS20 Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/ics20 Cross-chain token transfers via IBC (Inter-Blockchain Communication) protocol ## Overview The ICS20 precompile provides an interface to the Inter-Blockchain Communication (IBC) protocol, allowing smart contracts to perform cross-chain token transfers. It enables sending tokens to other IBC-enabled chains and querying information about IBC denominations. **Precompile Address**: `0x0000000000000000000000000000000000000802` **Related Module**: [x/ibc-transfer](/ibc/latest/apps/transfer/overview) ## Gas Costs Gas costs are approximated and may vary based on the transfer complexity and chain settings. | Method | Gas Cost | | ------------ | ------------------------------ | | **Transfer** | `50,000 + (100 × memo length)` | | **Queries** | `1000 + (3 × bytes of input)` | ## Channel Validation Before initiating a transfer, ensure that: * The source channel exists and is in an OPEN state * The channel is connected to the intended destination chain * The port ID matches the expected value (typically "transfer") You can verify channel status using the IBC module queries or chain explorers. ## Timeout Mechanism IBC transfers include two timeout options to prevent tokens from being locked indefinitely: 1. **Height Timeout**: Specified as `{revisionNumber, revisionHeight}`. Set both to 0 to disable. 2. **Timestamp Timeout**: Unix timestamp in nanoseconds. Set to 0 to disable. At least one timeout mechanism must be set. Recommended practice is to use timestamp timeout set to 1 hour from the current time. ## Address Format Requirements **Current Limitation**: Receiver addresses must be in bech32 format (e.g., `cosmos1...`). Hex addresses (e.g., `0x...`) are not currently supported for the receiver parameter, though this limitation will be removed in a future release. The sender parameter is automatically converted from hex to bech32 format internally. ## EVM Callbacks Support The ICS20 precompile supports EVM callbacks through the `memo` field, enabling smart contracts to: * Execute automatically when receiving cross-chain transfers * Handle acknowledgments and timeouts for sent transfers * Implement complex cross-chain contract interactions For detailed callback implementation, see [IBC Module](/evm/latest/documentation/cosmos-sdk/modules/ibc) and [Callbacks Interface](/evm/latest/documentation/smart-contracts/precompiles/callbacks). ## Transaction Methods ### `transfer` Initiates a cross-chain token transfer using the IBC protocol. **Receiver Address Format**: Currently only accepts bech32 addresses (e.g., `cosmos1...`) for the receiver parameter. Hex address support (e.g., `0x...`) will be added in a future release. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ICS20Example { address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802; struct Height { uint64 revisionNumber; uint64 revisionHeight; } event IBCTransferInitiated( address indexed sender, string indexed receiver, string sourceChannel, string denom, uint256 amount, uint64 sequence ); function transfer( string calldata sourceChannel, string calldata denom, uint256 amount, string calldata receiver, uint64 timeoutTimestamp, string calldata memo ) external payable returns (uint64 sequence) { require(bytes(sourceChannel).length > 0, "Source channel required"); require(bytes(denom).length > 0, "Denom required"); require(amount > 0, "Amount must be greater than 0"); require(bytes(receiver).length > 0, "Receiver address required"); require(timeoutTimestamp > 0, "Timeout timestamp required"); // Default port for ICS20 transfers string memory sourcePort = "transfer"; // Disable height-based timeout (using timestamp instead) Height memory timeoutHeight = Height({ revisionNumber: 0, revisionHeight: 0 }); (bool success, bytes memory result) = ICS20_PRECOMPILE.call{value: msg.value}( abi.encodeWithSignature( "transfer(string,string,string,uint256,address,string,tuple(uint64,uint64),uint64,string)", sourcePort, sourceChannel, denom, amount, msg.sender, receiver, timeoutHeight, timeoutTimestamp, memo ) ); require(success, "IBC transfer failed"); sequence = abi.decode(result, (uint64)); emit IBCTransferInitiated(msg.sender, receiver, sourceChannel, denom, amount, sequence); return sequence; } // Helper function to calculate timeout (1 hour from now) function calculateTimeout(uint256 durationSeconds) external view returns (uint64) { return uint64((block.timestamp + durationSeconds) * 1e9); // Convert to nanoseconds } // Quick transfer with 1-hour timeout function quickTransfer( string calldata sourceChannel, string calldata denom, uint256 amount, string calldata receiver ) external payable returns (uint64) { uint64 timeoutTimestamp = this.calculateTimeout(3600); // 1 hour return this.transfer{value: msg.value}( sourceChannel, denom, amount, receiver, timeoutTimestamp, "" ); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the transfer function const precompileAbi = [ "function transfer(string memory sourcePort, string memory sourceChannel, string memory denom, uint256 amount, address sender, string memory receiver, tuple(uint64 revisionNumber, uint64 revisionHeight) timeoutHeight, uint64 timeoutTimestamp, string memory memo) returns (uint64)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000802"; const signer = new ethers.Wallet("", provider); const contract = new ethers.Contract(precompileAddress, precompileAbi, signer); // Transfer parameters const sourcePort = "transfer"; const sourceChannel = "channel-0"; const denom = "test"; // Token denomination const amount = ethers.parseEther("1.0"); // Amount to transfer const sender = await signer.getAddress(); const receiver = "cosmos1..."; // Destination address on target chain const timeoutHeight = { revisionNumber: 0, revisionHeight: 0 }; // Height timeout disabled const timeoutTimestamp = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now const memo = ""; async function transferTokens() { try { const tx = await contract.transfer( sourcePort, sourceChannel, denom, amount, sender, receiver, timeoutHeight, timeoutTimestamp, memo ); console.log("Transfer initiated:", tx.hash); await tx.wait(); console.log("Transfer confirmed"); } catch (error) { console.error("Error initiating transfer:", error); } } // transferTokens(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: cURL cannot be used for transaction methods as they require signatures # Use the ethers.js example above for IBC transfers echo "IBC transfer requires a signed transaction - use ethers.js or other Web3 library" ``` ## Query Methods ### `denom` Queries denomination information for an IBC token by its hash. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ICS20DenomQuery { address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802; struct Hop { string portId; string channelId; } struct Denom { string base; Hop[] trace; } function getDenom(string memory hash) external view returns (Denom memory denom) { require(bytes(hash).length > 0, "Hash cannot be empty"); (bool success, bytes memory result) = ICS20_PRECOMPILE.staticcall( abi.encodeWithSignature("denom(string)", hash) ); require(success, "Failed to get denom"); denom = abi.decode(result, (Denom)); return denom; } // Helper function to check if a denom is native (no trace) function isNativeDenom(string memory hash) external view returns (bool) { Denom memory denom = this.getDenom(hash); return denom.trace.length == 0; } // Helper function to get the base denomination function getBaseDenom(string memory hash) external view returns (string memory) { Denom memory denom = this.getDenom(hash); return denom.base; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function denom(string memory hash) view returns (tuple(string base, tuple(string portId, string channelId)[] trace) denom)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000802"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input: The hash of the denomination to query const denomHash = "ibc/..."; // Placeholder for actual denomination hash async function getDenom() { try { const denom = await contract.denom(denomHash); console.log("Denomination Info:", JSON.stringify(denom, null, 2)); } catch (error) { console.error("Error fetching denomination:", error); } } getDenom(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder hash with your actual data. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000802", "data": "0x7780092400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `denoms` Retrieves a paginated list of all denomination traces registered on the chain. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ICS20DenomsList { address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802; struct PageRequest { bytes key; uint64 offset; uint64 limit; bool countTotal; bool reverse; } struct PageResponse { bytes nextKey; uint64 total; } struct Hop { string portId; string channelId; } struct Denom { string base; Hop[] trace; } function getDenoms(PageRequest memory pageRequest) external view returns (Denom[] memory denoms, PageResponse memory pageResponse) { (bool success, bytes memory result) = ICS20_PRECOMPILE.staticcall( abi.encodeWithSignature( "denoms((bytes,uint64,uint64,bool,bool))", pageRequest ) ); require(success, "Failed to get denoms"); (denoms, pageResponse) = abi.decode(result, (Denom[], PageResponse)); return (denoms, pageResponse); } // Helper function to get all IBC denoms (with trace) function getIBCDenoms(uint64 limit) external view returns (Denom[] memory) { PageRequest memory pageRequest = PageRequest({ key: "", offset: 0, limit: limit, countTotal: false, reverse: false }); (Denom[] memory allDenoms,) = this.getDenoms(pageRequest); // Count IBC denoms (those with trace) uint256 ibcCount = 0; for (uint i = 0; i < allDenoms.length; i++) { if (allDenoms[i].trace.length > 0) { ibcCount++; } } // Filter IBC denoms Denom[] memory ibcDenoms = new Denom[](ibcCount); uint256 index = 0; for (uint i = 0; i < allDenoms.length; i++) { if (allDenoms[i].trace.length > 0) { ibcDenoms[index++] = allDenoms[i]; } } return ibcDenoms; } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function denoms(tuple(bytes key, uint64 offset, uint64 limit, bool countTotal, bool reverse) pageRequest) view returns (tuple(string base, tuple(string portId, string channelId)[] trace)[] denoms, tuple(bytes nextKey, uint64 total) pageResponse)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000802"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input for pagination const pagination = { key: "0x", offset: 0, limit: 10, countTotal: true, reverse: false, }; async function getDenoms() { try { const result = await contract.denoms(pagination); console.log("Denominations:", JSON.stringify(result.denoms, null, 2)); console.log("Pagination Response:", result.pageResponse); } catch (error) { console.error("Error fetching denominations:", error); } } getDenoms(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # This example queries for the first 10 denominations. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000802", "data": "0x5310b39500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `denomHash` Computes the hash of a denomination trace path. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract ICS20DenomHash { address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802; function getDenomHash(string memory trace) external view returns (string memory hash) { require(bytes(trace).length > 0, "Trace cannot be empty"); (bool success, bytes memory result) = ICS20_PRECOMPILE.staticcall( abi.encodeWithSignature("denomHash(string)", trace) ); require(success, "Failed to compute denom hash"); hash = abi.decode(result, (string)); return hash; } // Helper function to build and hash a trace path function buildAndHashTrace( string memory portId, string memory channelId, string memory baseDenom ) external view returns (string memory) { // Build trace in format: "port/channel/denom" string memory trace = string(abi.encodePacked( portId, "/", channelId, "/", baseDenom )); return this.getDenomHash(trace); } // Helper function to verify if a hash matches a trace function verifyDenomHash( string memory trace, string memory expectedHash ) external view returns (bool) { string memory actualHash = this.getDenomHash(trace); return keccak256(bytes(actualHash)) == keccak256(bytes(expectedHash)); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function denomHash(string memory trace) view returns (string memory hash)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000802"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input: The trace path to hash const tracePath = "transfer/channel-0/test"; // Placeholder for actual trace path async function getDenomHash() { try { const hash = await contract.denomHash(tracePath); console.log("Denomination Hash:", hash); } catch (error) { console.error("Error computing denomination hash:", error); } } getDenomHash(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder trace with your actual data. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000802", "data": "0x82ff49f000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ## Full Solidity Interface & ABI ```solidity title="ICS20 Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.18; import "../common/Types.sol"; /// @dev The ICS20I contract's address. address constant ICS20_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000802; /// @dev The ICS20 contract's instance. ICS20I constant ICS20_CONTRACT = ICS20I(ICS20_PRECOMPILE_ADDRESS); /// @dev Denom contains the base denomination for ICS20 fungible tokens and the /// source tracing information path. struct Denom { /// base denomination of the relayed fungible token. string base; /// trace contains a list of hops for multi-hop transfers. Hop[] trace; } /// @dev Hop defines a port ID, channel ID pair specifying where /// tokens must be forwarded next in a multi-hop transfer. struct Hop { string portId; string channelId; } /// @author Evmos Team /// @title ICS20 Transfer Precompiled Contract /// @dev The interface through which solidity contracts will interact with IBC Transfer (ICS20) /// @custom:address 0x0000000000000000000000000000000000000802 interface ICS20I { /// @dev Emitted when an ICS-20 transfer is executed. /// @param sender The address of the sender. /// @param receiver The address of the receiver. /// @param sourcePort The source port of the IBC transaction, For v2 packets, leave it empty. /// @param sourceChannel The source channel of the IBC transaction, For v2 packets, set the client ID. /// @param denom The denomination of the tokens transferred. /// @param amount The amount of tokens transferred. /// @param memo The IBC transaction memo. event IBCTransfer( address indexed sender, string indexed receiver, string sourcePort, string sourceChannel, string denom, uint256 amount, string memo ); /// @dev Transfer defines a method for performing an IBC transfer. /// @param sourcePort the port on which the packet will be sent /// @param sourceChannel the channel by which the packet will be sent /// @param denom the denomination of the Coin to be transferred to the receiver /// @param amount the amount of the Coin to be transferred to the receiver /// @param sender the hex address of the sender /// @param receiver the bech32 address of the receiver (hex addresses not yet supported) /// @param timeoutHeight the timeout height relative to the current block height. /// The timeout is disabled when set to 0 /// @param timeoutTimestamp the timeout timestamp in absolute nanoseconds since unix epoch. /// The timeout is disabled when set to 0 /// @param memo optional memo /// @return nextSequence sequence number of the transfer packet sent function transfer( string memory sourcePort, string memory sourceChannel, string memory denom, uint256 amount, address sender, string memory receiver, Height memory timeoutHeight, uint64 timeoutTimestamp, string memory memo ) external returns (uint64 nextSequence); /// @dev denoms Defines a method for returning all denoms. /// @param pageRequest Defines the pagination parameters to for the request. function denoms( PageRequest memory pageRequest ) external view returns ( Denom[] memory denoms, PageResponse memory pageResponse ); /// @dev Denom defines a method for returning a denom. function denom( string memory hash ) external view returns (Denom memory denom); /// @dev DenomHash defines a method for returning a hash of the denomination info. function denomHash( string memory trace ) external view returns (string memory hash); } ``` ```json title="ICS20 ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "ICS20I", "sourceName": "solidity/precompiles/ics20/ICS20I.sol", "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, { "indexed": true, "internalType": "string", "name": "receiver", "type": "string" }, { "indexed": false, "internalType": "string", "name": "sourcePort", "type": "string" }, { "indexed": false, "internalType": "string", "name": "sourceChannel", "type": "string" }, { "indexed": false, "internalType": "string", "name": "denom", "type": "string" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "string", "name": "memo", "type": "string" } ], "name": "IBCTransfer", "type": "event" }, { "inputs": [ { "internalType": "string", "name": "hash", "type": "string" } ], "name": "denom", "outputs": [ { "components": [ { "internalType": "string", "name": "base", "type": "string" }, { "components": [ { "internalType": "string", "name": "portId", "type": "string" }, { "internalType": "string", "name": "channelId", "type": "string" } ], "internalType": "struct Hop[]", "name": "trace", "type": "tuple[]" } ], "internalType": "struct Denom", "name": "denom", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "trace", "type": "string" } ], "name": "denomHash", "outputs": [ { "internalType": "string", "name": "hash", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pageRequest", "type": "tuple" } ], "name": "denoms", "outputs": [ { "components": [ { "internalType": "string", "name": "base", "type": "string" }, { "components": [ { "internalType": "string", "name": "portId", "type": "string" }, { "internalType": "string", "name": "channelId", "type": "string" } ], "internalType": "struct Hop[]", "name": "trace", "type": "tuple[]" } ], "internalType": "struct Denom[]", "name": "denoms", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "sourcePort", "type": "string" }, { "internalType": "string", "name": "sourceChannel", "type": "string" }, { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "string", "name": "receiver", "type": "string" }, { "components": [ { "internalType": "uint64", "name": "revisionNumber", "type": "uint64" }, { "internalType": "uint64", "name": "revisionHeight", "type": "uint64" } ], "internalType": "struct Height", "name": "timeoutHeight", "type": "tuple" }, { "internalType": "uint64", "name": "timeoutTimestamp", "type": "uint64" }, { "internalType": "string", "name": "memo", "type": "string" } ], "name": "transfer", "outputs": [ { "internalType": "uint64", "name": "nextSequence", "type": "uint64" } ], "stateMutability": "nonpayable", "type": "function" } ] } ``` # Overview Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/overview Precompiles are predefined functions that are integrated at the protocol level but exposed as EVM smart contract interfaces. Many precompiles provide access to Cosmos SDK module functionality for EVM applications and clients to easily leverage. Precompiles are smart contract interfaces deployed at fixed addresses where the implementation runs as native Go code rather than EVM bytecode. In standard Ethereum, precompiles are stateless and handle things like signature verification and hashing. In Cosmos EVM, they can also be stateful — reading from and writing to Cosmos SDK module state outside the EVM. This is what makes them powerful: a smart contract can call the staking precompile to delegate tokens, the governance precompile to submit a proposal, or the IBC precompile to send tokens cross-chain — all using a standard Solidity interface, all within a single transaction. Precompiles act as a window into the Cosmos SDK: when a precompile is invoked, execution routes from the EVM through the corresponding Cosmos SDK module and returns the result to the EVM for continued execution. Gas is metered across both environments. All precompile contracts should use Ethereum's standard 18 decimals. Although typical Cosmos chains use 6 decimals, EVM chains should use 18 decimals. Always check your chain's decimal precision before interacting with a precompile. ## Built-in Precompiles | Precompile | Address | Purpose | Reference | | ------------ | -------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------- | | P256 | `0x0000000000000000000000000000000000000100` | P-256 elliptic curve signature verification | [Details](/evm/latest/documentation/smart-contracts/precompiles/p256) | | Bech32 | `0x0000000000000000000000000000000000000400` | Address format conversion between Ethereum hex and Cosmos bech32 | [Details](/evm/latest/documentation/smart-contracts/precompiles/bech32) | | Staking | `0x0000000000000000000000000000000000000800` | Validator operations, delegation, and staking rewards | [Details](/evm/latest/documentation/smart-contracts/precompiles/staking) | | Distribution | `0x0000000000000000000000000000000000000801` | Staking rewards and community pool management | [Details](/evm/latest/documentation/smart-contracts/precompiles/distribution) | | ICS20 | `0x0000000000000000000000000000000000000802` | Cross-chain token transfers via IBC | [Details](/evm/latest/documentation/smart-contracts/precompiles/ics20) | | Bank | `0x0000000000000000000000000000000000000804` | ERC20-style access to native Cosmos SDK tokens | [Details](/evm/latest/documentation/smart-contracts/precompiles/bank) | | Governance | `0x0000000000000000000000000000000000000805` | On-chain governance proposals and voting | [Details](/evm/latest/documentation/smart-contracts/precompiles/governance) | | Slashing | `0x0000000000000000000000000000000000000806` | Validator slashing and jail management | [Details](/evm/latest/documentation/smart-contracts/precompiles/slashing) | | ICS02 | `0x0000000000000000000000000000000000000807` | IBC light client queries | [Source](https://github.com/cosmos/evm/tree/main/precompiles/ics02) | | ERC20 | Dynamic per token | Standard ERC20 interface for native Cosmos tokens | [Details](/evm/latest/documentation/smart-contracts/precompiles/erc20) | | WERC20 | Dynamic per token | Wrapped native token functionality | [Details](/evm/latest/documentation/smart-contracts/precompiles/werc20) | Chain builders can control which precompiles are active and add their own. See the [Precompile Configuration guide](/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/precompiles) for details. # P256 Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/p256 secp256r1 (P-256) signature verification precompile for WebAuthn and secure hardware ## Overview The P256 precompile provides native support for verifying secp256r1 (P-256) elliptic curve signatures, implementing EIP-7212. This enables smart contracts to verify signatures from WebAuthn authenticators, secure hardware modules, and other systems using the P-256 curve. **Address**: `0x0000000000000000000000000000000000000100` **Related Standards**: [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md) ## Gas Costs Fixed cost: **3,450 gas** ## Method ### Signature Verification The precompile exposes a single unnamed function that verifies P-256 signatures. **Input Format** (160 bytes): * Bytes 0-31: `message_hash` (32 bytes) - The hash of the message * Bytes 32-63: `r` (32 bytes) - The r component of the signature * Bytes 64-95: `s` (32 bytes) - The s component of the signature * Bytes 96-127: `x` (32 bytes) - The x coordinate of the public key * Bytes 128-159: `y` (32 bytes) - The y coordinate of the public key **Output Format** (32 bytes): * Returns `0x0000...0001` (1) if signature is valid * Returns `0x0000...0000` (0) if signature is invalid ### Example Usage ```solidity Solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // P256 signature verification address constant P256_PRECOMPILE = 0x0000000000000000000000000000000000000100; function verifyP256Signature( bytes32 messageHash, bytes32 r, bytes32 s, bytes32 x, bytes32 y ) external view returns (bool) { bytes memory input = abi.encodePacked(messageHash, r, s, x, y); (bool success, bytes memory result) = P256_PRECOMPILE.staticcall(input); if (!success || result.length != 32) { return false; } return uint256(bytes32(result)) == 1; } ``` ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const ethers = require('ethers'); // P256 precompile address const P256_ADDRESS = '0x0000000000000000000000000000000000000100'; async function verifyP256Signature(provider, messageHash, r, s, x, y) { // Encode the input data const input = ethers.utils.concat([ messageHash, r, s, x, y ]); // Call the precompile const result = await provider.call({ to: P256_ADDRESS, data: input }); // Check if signature is valid (result should be 0x00...01) return result === '0x' + '00'.repeat(31) + '01'; } ``` ## Implementation Details ### Curve Parameters The precompile uses the secp256r1 (NIST P-256) elliptic curve with the following parameters: * Field prime: `p = 2^256 - 2^224 + 2^192 + 2^96 - 1` * Curve equation: `y² = x³ + ax + b` where: * `a = -3` * `b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b` ### Input Validation The precompile performs the following validations: 1. Input must be exactly 160 bytes 2. Public key coordinates (x, y) must be valid points on the curve 3. Signature components (r, s) must be within the valid range \[1, n-1] where n is the curve order ### Security Considerations * The precompile only verifies that a signature is mathematically valid for the given public key * Applications must implement additional checks such as: * Public key authentication (e.g., WebAuthn credential verification) * Message format validation * Replay attack prevention ## Use Cases ### WebAuthn Integration The P256 precompile enables smart contracts to verify WebAuthn assertions, allowing for: * Passwordless authentication * Hardware security key support * Biometric authentication via compatible devices ### Secure Hardware Modules Many secure elements and hardware security modules use P-256 for signing operations: * Apple Secure Enclave * Android Keystore (when configured for P-256) * TPM 2.0 modules * Smart cards ### Example: WebAuthn Verification ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} contract WebAuthnWallet { using bytes for bytes; struct Credential { bytes32 credentialId; uint256 publicKeyX; uint256 publicKeyY; } mapping(address => Credential) public credentials; function verify( bytes calldata authenticatorData, bytes calldata clientDataJSON, bytes32 r, bytes32 s ) external view returns (bool) { Credential memory cred = credentials[msg.sender]; // Compute challenge hash according to WebAuthn spec bytes32 clientDataHash = sha256(clientDataJSON); bytes32 messageHash = sha256(abi.encodePacked(authenticatorData, clientDataHash)); // Verify P-256 signature bytes memory input = abi.encodePacked( messageHash, r, s, bytes32(cred.publicKeyX), bytes32(cred.publicKeyY) ); (bool success, bytes memory result) = address(0x100).staticcall(input); return success && result.length == 32 && uint256(bytes32(result)) == 1; } } ``` ## Gas Optimization Since the gas cost is fixed at 3,450, optimizations should focus on: * Minimizing the number of signature verifications * Batch processing where possible * Caching verification results when appropriate # Slashing Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/slashing Validator slashing and jail management for network security ## Overview The Slashing precompile provides an interface to the Cosmos SDK's `x/slashing` module, which is responsible for penalizing validators for misbehavior, such as downtime or double-signing. This precompile allows smart contracts to unjail validators and query slashing-related information, including validator signing info and module parameters. **Precompile Address**: `0x0000000000000000000000000000000000000806` **Related Module**: [x/slashing](/sdk/latest/modules/slashing/README) ## Gas Costs Gas costs are approximated and may vary based on chain settings. | Method | Gas Cost | | ---------------- | ------------------------------ | | **Transactions** | `2000 + (30 × bytes of input)` | | **Queries** | `1000 + (3 × bytes of input)` | ## Transaction Methods ### `unjail` Allows a validator to unjail themselves after being jailed for downtime. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SlashingExample { address constant SLASHING_PRECOMPILE = 0x0000000000000000000000000000000000000806; struct SigningInfo { address validatorAddress; int64 startHeight; int64 indexOffset; int64 jailedUntil; bool tombstoned; int64 missedBlocksCounter; } event ValidatorUnjailed(address indexed validator); event ValidatorSlashingInfoQueried(address indexed validator, bool isJailed, bool isTombstoned); function unjailValidator(address validatorAddress) external returns (bool success) { require(validatorAddress != address(0), "Invalid validator address"); // Check signing info to prevent unjailing tombstoned validators SigningInfo memory info = this.getValidatorSigningInfo(validatorAddress); require(!info.tombstoned, "Tombstoned validators cannot be unjailed"); require(info.jailedUntil > int64(int256(block.timestamp)), "Validator is not jailed"); (bool callSuccess, bytes memory result) = SLASHING_PRECOMPILE.call( abi.encodeWithSignature("unjail(address)", validatorAddress) ); require(callSuccess, "Unjail call failed"); success = abi.decode(result, (bool)); require(success, "Unjail operation failed"); emit ValidatorUnjailed(validatorAddress); return success; } function getValidatorSigningInfo(address consAddress) external view returns (SigningInfo memory signingInfo) { (bool success, bytes memory result) = SLASHING_PRECOMPILE.staticcall( abi.encodeWithSignature("getSigningInfo(address)", consAddress) ); require(success, "Signing info query failed"); signingInfo = abi.decode(result, (SigningInfo)); return signingInfo; } function canValidatorBeUnjailed(address consAddress) external view returns (bool canUnjail, string memory reason) { SigningInfo memory info = this.getValidatorSigningInfo(consAddress); // CRITICAL: Tombstoned validators can NEVER be unjailed if (info.tombstoned) { return (false, "PERMANENT_TOMBSTONE: Validator can never be unjailed due to severe infractions (e.g., double-signing)"); } if (info.jailedUntil <= int64(int256(block.timestamp))) { return (false, "Validator is not currently jailed"); } return (true, "Validator can be unjailed"); } function getValidatorStatus(address consAddress) external view returns (bool isJailed, bool isTombstoned, int64 missedBlocks) { SigningInfo memory info = this.getValidatorSigningInfo(consAddress); isJailed = info.jailedUntil > int64(int256(block.timestamp)); isTombstoned = info.tombstoned; missedBlocks = info.missedBlocksCounter; emit ValidatorSlashingInfoQueried(consAddress, isJailed, isTombstoned); return (isJailed, isTombstoned, missedBlocks); } } ``` ## Query Methods ### `getSigningInfo` Returns the signing information for a specific validator. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function getSigningInfo(address consAddress) view returns (tuple(address validatorAddress, int64 startHeight, int64 indexOffset, int64 jailedUntil, bool tombstoned, int64 missedBlocksCounter) signingInfo)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000806"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input: The consensus address of the validator const consAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder async function getSigningInfo() { try { const signingInfo = await contract.getSigningInfo(consAddress); console.log("Signing Info:", JSON.stringify(signingInfo, null, 2)); } catch (error) { console.error("Error fetching signing info:", error); } } getSigningInfo(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder consensus address with your actual data. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000806", "data": "0x3f554612000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getSigningInfos` Returns the signing information for all validators, with pagination support. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function getSigningInfos(tuple(bytes key, uint64 offset, uint64 limit, bool countTotal, bool reverse) pagination) view returns (tuple(address validatorAddress, int64 startHeight, int64 indexOffset, int64 jailedUntil, bool tombstoned, int64 missedBlocksCounter)[] signingInfos, tuple(bytes nextKey, uint64 total) pageResponse)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000806"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input for pagination const pagination = { key: "0x", offset: 0, limit: 10, countTotal: true, reverse: false, }; async function getSigningInfos() { try { const result = await contract.getSigningInfos(pagination); console.log("Signing Infos:", JSON.stringify(result.signingInfos, null, 2)); console.log("Pagination Response:", result.pageResponse); } catch (error) { console.error("Error fetching signing infos:", error); } } getSigningInfos(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # This example queries for the first 10 validators' signing info. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000806", "data": "0x5f993f4300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `getParams` Returns the current parameters for the slashing module. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function getParams() view returns (tuple(int64 signedBlocksWindow, tuple(uint256 value, uint8 precision) minSignedPerWindow, int64 downtimeJailDuration, tuple(uint256 value, uint8 precision) slashFractionDoubleSign, tuple(uint256 value, uint8 precision) slashFractionDowntime) params)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000806"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); async function getParams() { try { const params = await contract.getParams(); console.log("Slashing Parameters:", JSON.stringify(params, null, 2)); } catch (error) { console.error("Error fetching slashing parameters:", error); } } getParams(); ``` **Note**: The slashing `getParams()` function may return empty data if slashing parameters are not configured on your network. The cURL method will still demonstrate the correct function call format. ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000806", "data": "0x4035236b" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ## Full Solidity Interface & ABI ```solidity title="Slashing Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.17; import "../common/Types.sol"; /// @dev The ISlashing contract's address. address constant SLASHING_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000806; /// @dev The ISlashing contract's instance. ISlashing constant SLASHING_CONTRACT = ISlashing(SLASHING_PRECOMPILE_ADDRESS); /// @dev SigningInfo defines a validator's signing info for monitoring their /// liveness activity. struct SigningInfo { /// @dev Address of the validator address validatorAddress; /// @dev Height at which validator was first a candidate OR was unjailed int64 startHeight; /// @dev Index offset into signed block bit array int64 indexOffset; /// @dev Timestamp until which validator is jailed due to liveness downtime int64 jailedUntil; /// @dev Whether or not a validator has been tombstoned (killed out of validator set) bool tombstoned; /// @dev Missed blocks counter (to avoid scanning the array every time) int64 missedBlocksCounter; } /// @dev Params defines the parameters for the slashing module. struct Params { /// @dev SignedBlocksWindow defines how many blocks the validator should have signed int64 signedBlocksWindow; /// @dev MinSignedPerWindow defines the minimum blocks signed per window to avoid slashing Dec minSignedPerWindow; /// @dev DowntimeJailDuration defines how long the validator will be jailed for downtime int64 downtimeJailDuration; /// @dev SlashFractionDoubleSign defines the percentage of slash for double sign Dec slashFractionDoubleSign; /// @dev SlashFractionDowntime defines the percentage of slash for downtime Dec slashFractionDowntime; } /// @author Evmos Team /// @title Slashing Precompiled Contract /// @dev The interface through which solidity contracts will interact with slashing. /// We follow this same interface including four-byte function selectors, in the precompile that /// wraps the pallet. /// @custom:address 0x0000000000000000000000000000000000000806 interface ISlashing { /// @dev Emitted when a validator is unjailed /// @param validator The address of the validator event ValidatorUnjailed(address indexed validator); /// @dev GetSigningInfo returns the signing info for a specific validator. /// @param consAddress The validator consensus address /// @return signingInfo The validator signing info function getSigningInfo( address consAddress ) external view returns (SigningInfo memory signingInfo); /// @dev GetSigningInfos returns the signing info for all validators. /// @param pagination Pagination configuration for the query /// @return signingInfos The list of validator signing info /// @return pageResponse Pagination information for the response function getSigningInfos( PageRequest calldata pagination ) external view returns (SigningInfo[] memory signingInfos, PageResponse memory pageResponse); /// @dev Unjail allows validators to unjail themselves after being jailed for downtime /// @param validatorAddress The validator operator address to unjail /// @return success true if the unjail operation was successful function unjail(address validatorAddress) external returns (bool success); /// @dev GetParams returns the slashing module parameters /// @return params The slashing module parameters function getParams() external view returns (Params memory params); } ``` ```json title="Slashing ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "ISlashing", "sourceName": "solidity/precompiles/slashing/ISlashing.sol", "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "validator", "type": "address" } ], "name": "ValidatorUnjailed", "type": "event" }, { "inputs": [], "name": "getParams", "outputs": [ { "components": [ { "internalType": "int64", "name": "signedBlocksWindow", "type": "int64" }, { "components": [ { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct Dec", "name": "minSignedPerWindow", "type": "tuple" }, { "internalType": "int64", "name": "downtimeJailDuration", "type": "int64" }, { "components": [ { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct Dec", "name": "slashFractionDoubleSign", "type": "tuple" }, { "components": [ { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "uint8", "name": "precision", "type": "uint8" } ], "internalType": "struct Dec", "name": "slashFractionDowntime", "type": "tuple" } ], "internalType": "struct Params", "name": "params", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "consAddress", "type": "address" } ], "name": "getSigningInfo", "outputs": [ { "components": [ { "internalType": "address", "name": "validatorAddress", "type": "address" }, { "internalType": "int64", "name": "startHeight", "type": "int64" }, { "internalType": "int64", "name": "indexOffset", "type": "int64" }, { "internalType": "int64", "name": "jailedUntil", "type": "int64" }, { "internalType": "bool", "name": "tombstoned", "type": "bool" }, { "internalType": "int64", "name": "missedBlocksCounter", "type": "int64" } ], "internalType": "struct SigningInfo", "name": "signingInfo", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pagination", "type": "tuple" } ], "name": "getSigningInfos", "outputs": [ { "components": [ { "internalType": "address", "name": "validatorAddress", "type": "address" }, { "internalType": "int64", "name": "startHeight", "type": "int64" }, { "internalType": "int64", "name": "indexOffset", "type": "int64" }, { "internalType": "int64", "name": "jailedUntil", "type": "int64" }, { "internalType": "bool", "name": "tombstoned", "type": "bool" }, { "internalType": "int64", "name": "missedBlocksCounter", "type": "int64" } ], "internalType": "struct SigningInfo[]", "name": "signingInfos", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "validatorAddress", "type": "address" } ], "name": "unjail", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" } ] } ``` # Staking Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/staking Validator operations, delegation management, and staking functionality ## Overview The Staking precompile provides a comprehensive interface to the Cosmos SDK's `x/staking` module, enabling smart contracts to engage in staking activities. This includes creating and managing validators, delegating and undelegating tokens, and querying a wide range of staking-related information, such as validator details, delegation records, and staking pool status. **Precompile Address**: `0x0000000000000000000000000000000000000800` **Related Module**: [x/staking](/sdk/latest/modules/staking/README) ## Gas Costs Gas costs are approximated and may vary based on the complexity of the staking operation and chain settings. | Method | Gas Cost | | ---------------- | ------------------------------ | | **Transactions** | `2000 + (30 × bytes of input)` | | **Queries** | `1000 + (3 × bytes of input)` | ## Transaction Methods ### `createValidator` Creates a new validator. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile interface IStaking { struct Description { string moniker; string identity; string website; string securityContact; string details; } struct CommissionRates { uint256 rate; uint256 maxRate; uint256 maxChangeRate; } function createValidator( Description memory description, CommissionRates memory commissionRates, uint256 minSelfDelegation, address validatorAddress, string memory pubkey, uint256 value ) external returns (bool success); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event ValidatorCreated(address indexed validatorAddress, string moniker, uint256 value); error InvalidInput(); error InsufficientValue(); error StakingOperationFailed(); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function createNewValidator( IStaking.Description calldata description, IStaking.CommissionRates calldata commissionRates, uint256 minSelfDelegation, string calldata pubkey, uint256 value ) external payable returns (bool success) { if (msg.value < value) revert InsufficientValue(); if (bytes(description.moniker).length == 0) revert InvalidInput(); if (bytes(pubkey).length == 0) revert InvalidInput(); try staking.createValidator( description, commissionRates, minSelfDelegation, msg.sender, pubkey, value ) returns (bool result) { if (!result) revert StakingOperationFailed(); emit ValidatorCreated(msg.sender, description.moniker, value); return result; } catch { revert StakingOperationFailed(); } } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Validator creation requires a signed transaction" ``` ### `editValidator` Edits an existing validator's parameters. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile interface IStaking { struct Description { string moniker; string identity; string website; string securityContact; string details; } function editValidator( Description memory description, uint256 commissionRate, uint256 minSelfDelegation ) external returns (bool success); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event ValidatorUpdated(address indexed validatorAddress, string moniker); error InvalidInput(); error StakingOperationFailed(); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function updateValidator( IStaking.Description calldata description, uint256 commissionRate, uint256 minSelfDelegation ) external returns (bool success) { if (bytes(description.moniker).length == 0) revert InvalidInput(); try staking.editValidator(description, commissionRate, minSelfDelegation) returns (bool result) { if (!result) revert StakingOperationFailed(); emit ValidatorUpdated(msg.sender, description.moniker); return result; } catch { revert StakingOperationFailed(); } } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Validator editing requires a signed transaction" ``` ### `delegate` Delegates tokens to a validator. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile interface IStaking { function delegate(address delegatorAddress, string memory validatorAddress, uint256 amount) external returns (bool success); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event DelegationSuccess(address indexed delegator, string indexed validator, uint256 amount); error InvalidAmount(); error InvalidValidator(); error StakingOperationFailed(); error InsufficientBalance(); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function delegateTokens(string calldata validatorAddress, uint256 amount) external payable { if (amount == 0) revert InvalidAmount(); if (msg.value != amount) revert InsufficientBalance(); if (bytes(validatorAddress).length == 0) revert InvalidValidator(); try staking.delegate(msg.sender, validatorAddress, amount) returns (bool success) { if (!success) revert StakingOperationFailed(); emit DelegationSuccess(msg.sender, validatorAddress, amount); } catch { revert StakingOperationFailed(); } } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the delegate function const precompileAbi = [ "function delegate(address delegatorAddress, string memory validatorAddress, uint256 amount) returns (bool)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000800"; const signer = new ethers.Wallet("", provider); const contract = new ethers.Contract(precompileAddress, precompileAbi, signer); // Delegation parameters const delegatorAddress = await signer.getAddress(); const validatorAddress = "cosmosvaloper1..."; // Validator operator address const amount = ethers.parseEther("10.0"); // Amount to delegate in native token async function delegateTokens() { try { const tx = await contract.delegate( delegatorAddress, validatorAddress, amount ); console.log("Delegation transaction:", tx.hash); await tx.wait(); console.log("Delegation confirmed"); } catch (error) { console.error("Error delegating tokens:", error); } } // delegateTokens(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: cURL cannot be used for transaction methods as they require signatures # Use the ethers.js example above for staking delegation echo "Staking delegation requires a signed transaction - use ethers.js or other Web3 library" ``` ### `undelegate` Undelegates tokens from a validator. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile interface IStaking { function beginUnbonding(address delegatorAddress, string memory validatorAddress, uint256 amount) external returns (int64 completionTime); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event UndelegationStarted(address indexed delegator, string indexed validator, uint256 amount, int64 completionTime); error InvalidAmount(); error InvalidValidator(); error StakingOperationFailed(); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function undelegateTokens(string calldata validatorAddress, uint256 amount) external returns (int64 completionTime) { if (amount == 0) revert InvalidAmount(); if (bytes(validatorAddress).length == 0) revert InvalidValidator(); try staking.beginUnbonding(msg.sender, validatorAddress, amount) returns (int64 completion) { completionTime = completion; emit UndelegationStarted(msg.sender, validatorAddress, amount, completionTime); return completionTime; } catch { revert StakingOperationFailed(); } } // Helper function to calculate days until completion function getDaysUntilCompletion(int64 completionTime) external view returns (uint256 days) { if (completionTime <= int64(block.timestamp)) { return 0; } return uint256(uint64(completionTime - int64(block.timestamp))) / 86400; } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Undelegation requires a signed transaction" ``` ### `redelegate` Redelegates tokens from one validator to another. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile interface IStaking { function beginRedelegate( address delegatorAddress, string memory validatorSrcAddress, string memory validatorDstAddress, uint256 amount ) external returns (int64 completionTime); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event RedelegationStarted( address indexed delegator, string indexed srcValidator, string indexed dstValidator, uint256 amount, int64 completionTime ); error InvalidValidator(); error InvalidAmount(); error SameValidator(); error StakingOperationFailed(); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function redelegateTokens( string calldata srcValidatorAddress, string calldata dstValidatorAddress, uint256 amount ) external returns (int64 completionTime) { if (bytes(srcValidatorAddress).length == 0) revert InvalidValidator(); if (bytes(dstValidatorAddress).length == 0) revert InvalidValidator(); if (amount == 0) revert InvalidAmount(); if (keccak256(bytes(srcValidatorAddress)) == keccak256(bytes(dstValidatorAddress))) { revert SameValidator(); } try staking.beginRedelegate(msg.sender, srcValidatorAddress, dstValidatorAddress, amount) returns (int64 completion) { completionTime = completion; emit RedelegationStarted(msg.sender, srcValidatorAddress, dstValidatorAddress, amount, completionTime); return completionTime; } catch { revert StakingOperationFailed(); } } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Redelegation requires a signed transaction" ``` ### `cancelUnbondingDelegation` Cancels an unbonding delegation. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile interface IStaking { function cancelUnbondingDelegation( address delegatorAddress, string memory validatorAddress, uint256 amount, int64 creationHeight ) external returns (bool success); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event UnbondingCancelled( address indexed delegator, string indexed validator, uint256 amount, int64 creationHeight ); error InvalidHeight(); error InvalidAmount(); error InvalidValidator(); error StakingOperationFailed(); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function cancelUnbonding( string calldata validatorAddress, uint256 amount, int64 creationHeight ) external returns (bool success) { if (creationHeight <= 0) revert InvalidHeight(); if (amount == 0) revert InvalidAmount(); if (bytes(validatorAddress).length == 0) revert InvalidValidator(); try staking.cancelUnbondingDelegation(msg.sender, validatorAddress, amount, creationHeight) returns (bool result) { if (!result) revert StakingOperationFailed(); emit UnbondingCancelled(msg.sender, validatorAddress, amount, creationHeight); return result; } catch { revert StakingOperationFailed(); } } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Transaction methods require signatures - use ethers.js or other Web3 library echo "Cancel unbonding requires a signed transaction" ``` ## Query Methods ### `validator` Queries information about a specific validator. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile (matches ~/repos/evm ABI) interface IStaking { struct Validator { string operatorAddress; string consensusPubkey; bool jailed; uint8 status; // BondStatus enum as uint8 uint256 tokens; uint256 delegatorShares; // uint256 string description; // string, not a nested struct int64 unbondingHeight; int64 unbondingTime; // int64 uint256 commission; // uint256 uint256 minSelfDelegation; } function validator(address validatorAddress) external view returns (Validator memory); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event ValidatorQueried(address indexed validatorAddress, string operatorAddress, uint256 tokens); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function getValidator(address validatorAddress) external view returns (IStaking.Validator memory validator) { validator = staking.validator(validatorAddress); return validator; } function getValidatorInfo(address validatorAddress) external returns (string memory operatorAddress, uint256 tokens, bool jailed) { IStaking.Validator memory validator = staking.validator(validatorAddress); operatorAddress = validator.operatorAddress; tokens = validator.tokens; jailed = validator.jailed; emit ValidatorQueried(validatorAddress, operatorAddress, tokens); return (operatorAddress, tokens, jailed); } } ``` ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function validator(address validatorAddress) view returns (tuple(string operatorAddress, string consensusPubkey, bool jailed, uint8 status, uint256 tokens, uint256 delegatorShares, string description, int64 unbondingHeight, int64 unbondingTime, uint256 commission, uint256 minSelfDelegation) validator)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000800"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Input - use actual validator address from the network const validatorAddress = "0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E"; // Example validator address async function getValidator() { try { const validator = await contract.validator(validatorAddress); console.log("Validator Info:", { operatorAddress: validator.operatorAddress, jailed: validator.jailed, status: validator.status, // 0=Unspecified, 1=Unbonded, 2=Unbonding, 3=Bonded tokens: ethers.formatEther(validator.tokens), delegatorShares: ethers.formatEther(validator.delegatorShares) }); } catch (error) { console.error("Error fetching validator:", error); } } getValidator(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the validator address with your actual data. # Data is ABI-encoded: validator(address) function selector + padded address parameter curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000800", "data": "0x223b3b7a0000000000000000000000007cb61d4117ae31a12e393a1cfa3bac666481d02e" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `validators` Queries validators with optional status filtering and pagination. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function validators(string memory status, tuple(bytes key, uint64 offset, uint64 limit, bool countTotal, bool reverse) pageRequest) view returns (tuple(string operatorAddress, string consensusPubkey, bool jailed, uint32 status, uint256 tokens, string delegatorShares, tuple(string moniker, string identity, string website, string securityContact, string details) description, int64 unbondingHeight, uint256 unbondingTime, tuple(tuple(string rate, string maxRate, string maxChangeRate) commissionRates, uint256 updateTime) commission, uint256 minSelfDelegation)[] validators, tuple(bytes nextKey, uint64 total) pageResponse)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000800"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const status = "BOND_STATUS_BONDED"; const pagination = { key: "0x", offset: 0, limit: 10, countTotal: true, reverse: false, }; async function getValidators() { try { const result = await contract.validators(status, pagination); console.log("Validators:", JSON.stringify(result.validators, null, 2)); console.log("Pagination Response:", result.pageResponse); } catch (error) { console.error("Error fetching validators:", error); } } getValidators(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace with your actual RPC endpoint. # This example queries for the first 10 bonded validators. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000800", "data": "0x186b2167000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000012424f4e445f5354415455535f424f4e444544000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `delegation` Queries the delegation amount between a delegator and a validator. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function delegation(address delegatorAddress, string memory validatorAddress) view returns (uint256 shares, tuple(string denom, uint256 amount) balance)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000800"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const delegatorAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; // Example delegator const validatorAddress = "cosmosvaloper10jmp6sgh4cc6zt3e8gw05wavvejgr5pw4xyrql"; // Example validator async function getDelegation() { try { const [shares, balance] = await contract.delegation(delegatorAddress, validatorAddress); console.log("Delegation Shares:", shares.toString()); console.log("Balance:", balance); } catch (error) { console.error("Error fetching delegation:", error); } } getDelegation(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder addresses with your actual data. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000800", "data": "0x241774e6000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000346636f736d6f7376616c6f70657231306a6d7036736768346363367a743365386777303577617676656a677235707734787972716c00000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `unbondingDelegation` Queries unbonding delegation information. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function unbondingDelegation(address delegatorAddress, string validatorAddress) view returns (tuple(string delegatorAddress, string validatorAddress, tuple(int64 creationHeight, int64 completionTime, uint256 initialBalance, uint256 balance, uint64 unbondingId, int64 unbondingOnHoldRefCount)[] entries) unbondingDelegation)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000800"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const delegatorAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; // Example delegator const validatorAddress = "cosmosvaloper10jmp6sgh4cc6zt3e8gw05wavvejgr5pw4xyrql"; // Example validator async function getUnbondingDelegation() { try { const unbonding = await contract.unbondingDelegation(delegatorAddress, validatorAddress); console.log("Unbonding Delegation:", JSON.stringify(unbonding, null, 2)); } catch (error) { console.error("Error fetching unbonding delegation:", error); } } getUnbondingDelegation(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and the placeholder addresses with your actual data. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000800", "data": "0xa03ffee1000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000346636f736d6f7376616c6f70657231306a6d7036736768346363367a743365386777303577617676656a677235707734787972716c00000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `redelegation` Queries a specific redelegation. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Staking precompile interface IStaking { struct RedelegationEntry { int64 creationHeight; int64 completionTime; uint256 initialBalance; uint256 sharesDst; uint64 unbondingId; int64 unbondingOnHoldRefCount; } struct Redelegation { string delegatorAddress; string validatorSrcAddress; string validatorDstAddress; RedelegationEntry[] entries; } function redelegation( address delegatorAddress, string memory validatorSrcAddress, string memory validatorDstAddress ) external view returns (Redelegation memory); } contract StakingExample { address constant STAKING_PRECOMPILE = 0x0000000000000000000000000000000000000800; IStaking public immutable staking; event RedelegationQueried( address indexed delegator, string srcValidator, string dstValidator, uint256 entriesCount ); constructor() { staking = IStaking(STAKING_PRECOMPILE); } function getRedelegation( address delegatorAddress, string calldata srcValidatorAddress, string calldata dstValidatorAddress ) external view returns (IStaking.Redelegation memory redelegation) { redelegation = staking.redelegation(delegatorAddress, srcValidatorAddress, dstValidatorAddress); return redelegation; } function getRedelegationInfo( address delegatorAddress, string calldata srcValidatorAddress, string calldata dstValidatorAddress ) external returns (uint256 entriesCount, bool hasActiveRedelegations) { IStaking.Redelegation memory redelegation = staking.redelegation( delegatorAddress, srcValidatorAddress, dstValidatorAddress ); entriesCount = redelegation.entries.length; hasActiveRedelegations = entriesCount > 0; // Check if any redelegations are still active for (uint256 i = 0; i < redelegation.entries.length; i++) { if (redelegation.entries[i].completionTime > int64(int256(block.timestamp))) { hasActiveRedelegations = true; break; } } emit RedelegationQueried(delegatorAddress, srcValidatorAddress, dstValidatorAddress, entriesCount); return (entriesCount, hasActiveRedelegations); } // Check if redelegation is complete function isRedelegationComplete(RedelegationOutput memory redelegation) external view returns (bool) { for (uint i = 0; i < redelegation.entries.length; i++) { if (redelegation.entries[i].completionTime > int64(block.timestamp)) { return false; } } return true; } } ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace addresses with your actual data curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000800", "data": "0x..." # ABI-encoded redelegation(address,string,string) call }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` ### `redelegations` Queries redelegations with optional filters. ```javascript Ethers.js expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; // ABI definition for the function const precompileAbi = [ "function redelegations(address delegatorAddress, string srcValidatorAddress, string dstValidatorAddress, tuple(bytes key, uint64 offset, uint64 limit, bool countTotal, bool reverse) pageRequest) view returns (tuple(tuple(string delegatorAddress, string validatorSrcAddress, string validatorDstAddress, tuple(int64 creationHeight, int64 completionTime, uint256 initialBalance, uint256 sharesDst)[] entries) redelegation, tuple(tuple(int64 creationHeight, int64 completionTime, uint256 initialBalance, uint256 sharesDst) redelegationEntry, uint256 balance)[] entries)[] redelegations, tuple(bytes nextKey, uint64 total) pageResponse)" ]; // Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000800"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider); // Inputs const delegatorAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; // Example delegator const srcValidatorAddress = ""; // Empty string for all const dstValidatorAddress = ""; // Empty string for all const pagination = { key: "0x", offset: 0, limit: 10, countTotal: true, reverse: false, }; async function getRedelegations() { try { const result = await contract.redelegations(delegatorAddress, srcValidatorAddress, dstValidatorAddress, pagination); console.log("Redelegations:", JSON.stringify(result.redelegations, null, 2)); console.log("Pagination Response:", result.pageResponse); } catch (error) { console.error("Error fetching redelegations:", error); } } getRedelegations(); ``` ```bash cURL expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Note: Replace and addresses with your actual data. # This example queries for the first 10 redelegations. curl -X POST --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0x0000000000000000000000000000000000000800", "data": "0x10a2851c000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, "latest" ], "id": 1 }' -H "Content-Type: application/json" ``` **Note**: The `pool()` and `params()` functions are not currently available in this staking precompile implementation. The available query methods are limited to delegation-specific functions shown above. ## Full Solidity Interface & ABI ```solidity title="Staking Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.17; import "../common/Types.sol"; /// @dev The StakingI contract's address. address constant STAKING_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000800; /// @dev The StakingI contract's instance. StakingI constant STAKING_CONTRACT = StakingI(STAKING_PRECOMPILE_ADDRESS); // BondStatus is the status of a validator. enum BondStatus { Unspecified, Unbonded, Unbonding, Bonded } // Description contains a validator's description. struct Description { string moniker; string identity; string website; string securityContact; string details; } // CommissionRates defines the initial commission rates to be used for a validator struct CommissionRates { uint256 rate; uint256 maxRate; uint256 maxChangeRate; } // Commission defines a commission parameters for a given validator. struct Commission { CommissionRates commissionRates; uint256 updateTime; } // Validator defines a validator, an account that can participate in consensus. struct Validator { string operatorAddress; string consensusPubkey; bool jailed; uint8 status; // BondStatus enum: 0=Unspecified, 1=Unbonded, 2=Unbonding, 3=Bonded uint256 tokens; uint256 delegatorShares; string description; int64 unbondingHeight; int64 unbondingTime; uint256 commission; uint256 minSelfDelegation; } // Delegation represents the bond with tokens held by an account. It is // owned by one delegator, and is associated with the voting power of one // validator. struct Delegation { address delegatorAddress; string validatorAddress; string shares; } // UnbondingDelegation stores all of a single delegator's unbonding bonds // for a single validator in an array. struct UnbondingDelegation { address delegatorAddress; string validatorAddress; UnbondingDelegationEntry[] entries; } // UnbondingDelegationEntry defines an unbonding object with relevant metadata. struct UnbondingDelegationEntry { uint256 creationHeight; uint256 completionTime; string initialBalance; string balance; } // RedelegationEntry defines a redelegation object with relevant metadata. struct RedelegationEntry { uint256 creationHeight; uint256 completionTime; string initialBalance; string sharesDst; } // Redelegation contains the list of a particular delegator's redelegating bonds // from a particular source validator to a particular destination validator. struct Redelegation { address delegatorAddress; string validatorSrcAddress; string validatorDstAddress; RedelegationEntry[] entries; } // DelegationResponse is equivalent to Delegation except that it contains a // balance in addition to shares which is more suitable for client responses. struct DelegationResponse { Delegation delegation; Coin balance; } // RedelegationEntryResponse is equivalent to a RedelegationEntry except that it // contains a balance in addition to shares which is more suitable for client // responses. struct RedelegationEntryResponse { RedelegationEntry redelegationEntry; string balance; } // RedelegationResponse is equivalent to a Redelegation except that its entries // contain a balance in addition to shares which is more suitable for client // responses. struct RedelegationResponse { Redelegation redelegation; RedelegationEntryResponse[] entries; } // Pool is used for tracking bonded and not-bonded token supply of the bond denomination. struct Pool { string notBondedTokens; string bondedTokens; } // StakingParams defines the parameters for the staking module. struct Params { uint256 unbondingTime; uint256 maxValidators; uint256 maxEntries; uint256 historicalEntries; string bondDenom; string minCommissionRate; } /// @author The Evmos Core Team /// @title Staking Precompile Contract /// @dev The interface through which solidity contracts will interact with Staking /// @custom:address 0x0000000000000000000000000000000000000800 interface StakingI { event CreateValidator(string indexed validatorAddress, uint256 value); event EditValidator(string indexed validatorAddress); event Delegate(address indexed delegatorAddress, string indexed validatorAddress, uint256 amount); event Unbond(address indexed delegatorAddress, string indexed validatorAddress, uint256 amount, uint256 completionTime); event Redelegate(address indexed delegatorAddress, address indexed validatorSrcAddress, address indexed validatorDstAddress, uint256 amount, uint256 completionTime); event CancelUnbondingDelegation(address indexed delegatorAddress, address indexed validatorAddress, uint256 amount, uint256 creationHeight); // Transactions function createValidator( Description calldata description, CommissionRates calldata commissionRates, uint256 minSelfDelegation, address validatorAddress, string calldata pubkey, uint256 value ) external returns (bool success); function editValidator( Description calldata description, address validatorAddress, int256 commissionRate, int256 minSelfDelegation ) external returns (bool success); function delegate( address delegatorAddress, string calldata validatorAddress, uint256 amount ) external payable returns (bool); function undelegate( address delegatorAddress, string calldata validatorAddress, uint256 amount ) external returns (bool); function redelegate( address delegatorAddress, string calldata validatorSrcAddress, string calldata validatorDstAddress, uint256 amount ) external returns (bool); function cancelUnbondingDelegation( address delegatorAddress, string calldata validatorAddress, uint256 amount, uint256 creationHeight ) external returns (bool); // Queries function validator( address validatorAddress ) external view returns (Validator memory); function validators( string calldata status, PageRequest calldata pageRequest ) external view returns (Validator[] memory, PageResponse memory); function delegation( address delegatorAddress, string calldata validatorAddress ) external view returns (uint256 shares, Coin memory balance); function unbondingDelegation( address delegatorAddress, string calldata validatorAddress ) external view returns (UnbondingDelegation memory); function redelegation( address delegatorAddress, string calldata srcValidatorAddress, string calldata dstValidatorAddress ) external view returns (Redelegation memory); function redelegations( address delegatorAddress, string calldata srcValidatorAddress, string calldata dstValidatorAddress, PageRequest calldata pageRequest ) external view returns (RedelegationResponse[] memory, PageResponse memory); } ``` ```json title="Staking ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "StakingI", "sourceName": "solidity/precompiles/staking/StakingI.sol", "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "indexed": true, "internalType": "address", "name": "validatorAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "creationHeight", "type": "uint256" } ], "name": "CancelUnbondingDelegation", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "validatorAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "CreateValidator", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "indexed": true, "internalType": "address", "name": "validatorAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "newShares", "type": "uint256" } ], "name": "Delegate", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "validatorAddress", "type": "address" }, { "indexed": false, "internalType": "int256", "name": "commissionRate", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "minSelfDelegation", "type": "int256" } ], "name": "EditValidator", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "indexed": true, "internalType": "address", "name": "validatorSrcAddress", "type": "address" }, { "indexed": true, "internalType": "address", "name": "validatorDstAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "completionTime", "type": "uint256" } ], "name": "Redelegate", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "indexed": true, "internalType": "address", "name": "validatorAddress", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "completionTime", "type": "uint256" } ], "name": "Unbond", "type": "event" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "creationHeight", "type": "uint256" } ], "name": "cancelUnbondingDelegation", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "string", "name": "moniker", "type": "string" }, { "internalType": "string", "name": "identity", "type": "string" }, { "internalType": "string", "name": "website", "type": "string" }, { "internalType": "string", "name": "securityContact", "type": "string" }, { "internalType": "string", "name": "details", "type": "string" } ], "internalType": "struct Description", "name": "description", "type": "tuple" }, { "components": [ { "internalType": "uint256", "name": "rate", "type": "uint256" }, { "internalType": "uint256", "name": "maxRate", "type": "uint256" }, { "internalType": "uint256", "name": "maxChangeRate", "type": "uint256" } ], "internalType": "struct CommissionRates", "name": "commissionRates", "type": "tuple" }, { "internalType": "uint256", "name": "minSelfDelegation", "type": "uint256" }, { "internalType": "address", "name": "validatorAddress", "type": "address" }, { "internalType": "string", "name": "pubkey", "type": "string" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "createValidator", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "delegate", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "delegation", "outputs": [ { "internalType": "uint256", "name": "shares", "type": "uint256" }, { "components": [ { "internalType": "string", "name": "denom", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "internalType": "struct Coin", "name": "balance", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "string", "name": "moniker", "type": "string" }, { "internalType": "string", "name": "identity", "type": "string" }, { "internalType": "string", "name": "website", "type": "string" }, { "internalType": "string", "name": "securityContact", "type": "string" }, { "internalType": "string", "name": "details", "type": "string" } ], "internalType": "struct Description", "name": "description", "type": "tuple" }, { "internalType": "address", "name": "validatorAddress", "type": "address" }, { "internalType": "int256", "name": "commissionRate", "type": "int256" }, { "internalType": "int256", "name": "minSelfDelegation", "type": "int256" } ], "name": "editValidator", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorSrcAddress", "type": "string" }, { "internalType": "string", "name": "validatorDstAddress", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "redelegate", "outputs": [ { "internalType": "int64", "name": "completionTime", "type": "int64" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "srcValidatorAddress", "type": "string" }, { "internalType": "string", "name": "dstValidatorAddress", "type": "string" } ], "name": "redelegation", "outputs": [ { "components": [ { "internalType": "string", "name": "delegatorAddress", "type": "string" }, { "internalType": "string", "name": "validatorSrcAddress", "type": "string" }, { "internalType": "string", "name": "validatorDstAddress", "type": "string" }, { "components": [ { "internalType": "int64", "name": "creationHeight", "type": "int64" }, { "internalType": "int64", "name": "completionTime", "type": "int64" }, { "internalType": "uint256", "name": "initialBalance", "type": "uint256" }, { "internalType": "uint256", "name": "sharesDst", "type": "uint256" } ], "internalType": "struct RedelegationEntry[]", "name": "entries", "type": "tuple[]" } ], "internalType": "struct RedelegationOutput", "name": "redelegation", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "srcValidatorAddress", "type": "string" }, { "internalType": "string", "name": "dstValidatorAddress", "type": "string" }, { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pageRequest", "type": "tuple" } ], "name": "redelegations", "outputs": [ { "components": [ { "components": [ { "internalType": "string", "name": "delegatorAddress", "type": "string" }, { "internalType": "string", "name": "validatorSrcAddress", "type": "string" }, { "internalType": "string", "name": "validatorDstAddress", "type": "string" }, { "components": [ { "internalType": "int64", "name": "creationHeight", "type": "int64" }, { "internalType": "int64", "name": "completionTime", "type": "int64" }, { "internalType": "uint256", "name": "initialBalance", "type": "uint256" }, { "internalType": "uint256", "name": "sharesDst", "type": "uint256" } ], "internalType": "struct RedelegationEntry[]", "name": "entries", "type": "tuple[]" } ], "internalType": "struct Redelegation", "name": "redelegation", "type": "tuple" }, { "components": [ { "components": [ { "internalType": "int64", "name": "creationHeight", "type": "int64" }, { "internalType": "int64", "name": "completionTime", "type": "int64" }, { "internalType": "uint256", "name": "initialBalance", "type": "uint256" }, { "internalType": "uint256", "name": "sharesDst", "type": "uint256" } ], "internalType": "struct RedelegationEntry", "name": "redelegationEntry", "type": "tuple" }, { "internalType": "uint256", "name": "balance", "type": "uint256" } ], "internalType": "struct RedelegationEntryResponse[]", "name": "entries", "type": "tuple[]" } ], "internalType": "struct RedelegationResponse[]", "name": "response", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" } ], "name": "unbondingDelegation", "outputs": [ { "components": [ { "internalType": "string", "name": "delegatorAddress", "type": "string" }, { "internalType": "string", "name": "validatorAddress", "type": "string" }, { "components": [ { "internalType": "int64", "name": "creationHeight", "type": "int64" }, { "internalType": "int64", "name": "completionTime", "type": "int64" }, { "internalType": "uint256", "name": "initialBalance", "type": "uint256" }, { "internalType": "uint256", "name": "balance", "type": "uint256" }, { "internalType": "uint64", "name": "unbondingId", "type": "uint64" }, { "internalType": "int64", "name": "unbondingOnHoldRefCount", "type": "int64" } ], "internalType": "struct UnbondingDelegationEntry[]", "name": "entries", "type": "tuple[]" } ], "internalType": "struct UnbondingDelegationOutput", "name": "unbondingDelegation", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "delegatorAddress", "type": "address" }, { "internalType": "string", "name": "validatorAddress", "type": "string" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "undelegate", "outputs": [ { "internalType": "int64", "name": "completionTime", "type": "int64" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "validatorAddress", "type": "address" } ], "name": "validator", "outputs": [ { "components": [ { "internalType": "string", "name": "operatorAddress", "type": "string" }, { "internalType": "string", "name": "consensusPubkey", "type": "string" }, { "internalType": "bool", "name": "jailed", "type": "bool" }, { "internalType": "enum BondStatus", "name": "status", "type": "uint8" }, { "internalType": "uint256", "name": "tokens", "type": "uint256" }, { "internalType": "uint256", "name": "delegatorShares", "type": "uint256" }, { "internalType": "string", "name": "description", "type": "string" }, { "internalType": "int64", "name": "unbondingHeight", "type": "int64" }, { "internalType": "int64", "name": "unbondingTime", "type": "int64" }, { "internalType": "uint256", "name": "commission", "type": "uint256" }, { "internalType": "uint256", "name": "minSelfDelegation", "type": "uint256" } ], "internalType": "struct Validator", "name": "validator", "type": "tuple" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", "name": "status", "type": "string" }, { "components": [ { "internalType": "bytes", "name": "key", "type": "bytes" }, { "internalType": "uint64", "name": "offset", "type": "uint64" }, { "internalType": "uint64", "name": "limit", "type": "uint64" }, { "internalType": "bool", "name": "countTotal", "type": "bool" }, { "internalType": "bool", "name": "reverse", "type": "bool" } ], "internalType": "struct PageRequest", "name": "pageRequest", "type": "tuple" } ], "name": "validators", "outputs": [ { "components": [ { "internalType": "string", "name": "operatorAddress", "type": "string" }, { "internalType": "string", "name": "consensusPubkey", "type": "string" }, { "internalType": "bool", "name": "jailed", "type": "bool" }, { "internalType": "enum BondStatus", "name": "status", "type": "uint8" }, { "internalType": "uint256", "name": "tokens", "type": "uint256" }, { "internalType": "uint256", "name": "delegatorShares", "type": "uint256" }, { "internalType": "string", "name": "description", "type": "string" }, { "internalType": "int64", "name": "unbondingHeight", "type": "int64" }, { "internalType": "int64", "name": "unbondingTime", "type": "int64" }, { "internalType": "uint256", "name": "commission", "type": "uint256" }, { "internalType": "uint256", "name": "minSelfDelegation", "type": "uint256" } ], "internalType": "struct Validator[]", "name": "validators", "type": "tuple[]" }, { "components": [ { "internalType": "bytes", "name": "nextKey", "type": "bytes" }, { "internalType": "uint64", "name": "total", "type": "uint64" } ], "internalType": "struct PageResponse", "name": "pageResponse", "type": "tuple" } ], "stateMutability": "view", "type": "function" } ], "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {}, "deployedLinkReferences": {} } ``` ``` ``` # WERC20 Source: https://docs.cosmos.network/evm/latest/documentation/smart-contracts/precompiles/werc20 Single token representation: An ERC20 interface for any token ## Overview The WERC20 precompile provides a standard ERC20 interface to native Cosmos tokens through Cosmos EVM's **[Single Token Representation](/evm/latest/documentation/concepts/single-token-representation)** architecture. Unlike traditional wrapped tokens that are functionally two separate tokens with unique individual properties and behaviors, Cosmos EVM's WERC20 logic gives smart contracts direct access to native bank module balances through familiar ERC20 methods. **Key Concept**: TEST and WTEST are not separate tokens—they are two different interfaces to the same token stored in the bank module. Native Cosmos tokens (including TEST and all IBC tokens) exist in both wrapped and unwrapped states at all times, allowing developers to choose the interaction method that best fits their use case: * Use it normally through Cosmos bank send (unwrapped state) * Use it like you would normally use ether or 'wei' on the EVM (native value transfers) * Use it as ERC20 WTEST with the contract address below (wrapped state) **WTEST Contract Address**: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` **Precompile Type**: Dynamic (unique address per wrapped token) **Related Module**: x/bank (via ERC20 module integration) ## Gas Costs Gas costs are approximated and may vary based on token complexity and chain settings. | Method | Gas Cost | | --------------------------------------- | -------------------- | | `name()` | \~3,000 gas | | `symbol()` | \~3,000 gas | | `decimals()` | \~2,000 gas | | `totalSupply()` | \~2,500 gas | | `balanceOf(address)` | \~2,900 gas | | `allowance(address,address)` | \~3,000 gas | | `transfer(address,uint256)` | \~35,000 gas | | `transferFrom(address,address,uint256)` | \~40,000 gas | | `approve(address,uint256)` | \~30,000 gas | | `deposit()` | \~23,000 gas (no-op) | | `withdraw(uint256)` | \~9,000 gas (no-op) | For a comprehensive understanding of how single token representation works and its benefits over traditional wrapping, see the [Single Token Representation](/evm/latest/documentation/concepts/single-token-representation) documentation. ## Technical Implementation ### Architecture Deep Dive The ERC20 module creates a **unified token representation** that bridges native Cosmos tokens with ERC20 interfaces: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Simplified conceptual flow (not actual implementation) func (k Keeper) ERC20Transfer(from, to common.Address, amount *big.Int) error { // Convert EVM addresses to Cosmos addresses cosmosFrom := sdk.AccAddress(from.Bytes()) cosmosTo := sdk.AccAddress(to.Bytes()) // Use bank module directly - no separate ERC20 state coin := sdk.NewCoin(k.denom, sdk.NewIntFromBigInt(amount)) return k.bankKeeper.SendCoins(ctx, cosmosFrom, cosmosTo, sdk.Coins{coin}) } func (k Keeper) ERC20BalanceOf(account common.Address) *big.Int { // Query bank module directly cosmosAddr := sdk.AccAddress(account.Bytes()) balance := k.bankKeeper.GetBalance(ctx, cosmosAddr, k.denom) return balance.Amount.BigInt() } ``` ### Deposit/Withdraw Implementation Details Since TEST and WTEST provide different interfaces to the same bank module token, deposit/withdraw functions exist for WETH interface compatibility: ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // These functions exist for WETH interface compatibility function deposit() external payable { // Handles msg.value by sending received coins back to the caller // Emits Deposit event for interface compatibility // Your bank balance reflects the same amount accessible via ERC20 interface } function withdraw(uint256 amount) external { // No-op implementation that only emits Withdrawal event // No actual token movement since bank balance is directly accessible // Exists purely for WETH interface compatibility } ``` **Understanding the Deposit/Withdraw Pattern** Unlike traditional WETH implementations where the contract holds wrapped tokens: * **Traditional WETH**: Contract receives ETH and mints WETH tokens that it holds * **WERC20**: Contract never holds tokens - all balances remain in the bank module * **Result**: The precompile contract address has no balance; tokens stay with users This is why `deposit()` and `withdraw()` are no-ops - there's no separate wrapped token state to manage. ### Real-World Example ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // User starts with 100 TEST in bank module const testBalance = await bankPrecompile.balances(userAddress); // Returns: [{denom: "atest", amount: "100000000000000000000"}] // 100 TEST (18 decimals) const wtestBalance = await wtest.balanceOf(userAddress); // Returns: "100000000000000000000" // Same 100 TEST, accessed via ERC20 interface // User transfers 50 WTEST via ERC20 await wtest.transfer(recipientAddress, "50000000000000000000"); // Check balances again const newTestBalance = await bankPrecompile.balances(userAddress); // Returns: [{denom: "atest", amount: "50000000000000000000"}] // 50 TEST (18 decimals) remaining const newWtestBalance = await wtest.balanceOf(userAddress); // Returns: "50000000000000000000" // Same 50 TEST, both queries return identical values ``` ## Methods ### Standard ERC20 Interface All standard ERC20 methods are available and operate on the underlying bank balance: ### `balanceOf` Returns the native token balance for a specific account (same as bank module balance). ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider(""); const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; const werc20Abi = ["function balanceOf(address account) view returns (uint256)"]; const wtest = new ethers.Contract(wtestAddress, werc20Abi, provider); async function getBalance() { try { const userAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; const balance = await wtest.balanceOf(userAddress); console.log("Balance (both TEST and WTEST):", balance.toString()); } catch (error) { console.error("Error:", error); } } ``` ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST \ -H "Content-Type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x70a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" }, "latest" ], "id": 1 }' ``` ### `transfer` Transfers tokens using the bank module (identical to native Cosmos transfer). ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract WERC20Example { // WTEST contract address address constant WTEST = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; IERC20 public immutable wtest; event TokensTransferred(address indexed from, address indexed to, uint256 amount); constructor() { wtest = IERC20(WTEST); } function transferWTEST(address to, uint256 amount) external returns (bool) { require(to != address(0), "Invalid recipient"); require(amount > 0, "Amount must be greater than 0"); // This directly moves tokens in the bank module // No wrapping/unwrapping - same underlying token balance bool success = wtest.transfer(to, amount); require(success, "Transfer failed"); emit TokensTransferred(msg.sender, to, amount); return true; } function transferFromWTEST(address from, address to, uint256 amount) external returns (bool) { require(from != address(0) && to != address(0), "Invalid addresses"); require(amount > 0, "Amount must be greater than 0"); bool success = wtest.transferFrom(from, to, amount); require(success, "Transfer from failed"); emit TokensTransferred(from, to, amount); return true; } // Batch transfer example function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external { require(recipients.length == amounts.length, "Arrays length mismatch"); for (uint256 i = 0; i < recipients.length; i++) { wtest.transferFrom(msg.sender, recipients[i], amounts[i]); emit TokensTransferred(msg.sender, recipients[i], amounts[i]); } } } ``` ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider(""); const signer = new ethers.Wallet("", provider); const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; const werc20Abi = ["function transfer(address to, uint256 amount) returns (bool)"]; const wtest = new ethers.Contract(wtestAddress, werc20Abi, signer); async function transferTokens() { try { const recipientAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f5b899"; const amount = ethers.parseUnits("10.0", 18); // 10 TEST (18 decimals) const tx = await wtest.transfer(recipientAddress, amount); const receipt = await tx.wait(); console.log("Transfer successful:", receipt.hash); } catch (error) { console.error("Error:", error); } } ``` ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # First, sign the transaction offline, then: curl -X POST \ -H "Content-Type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_sendRawTransaction", "params": [""], "id": 1 }' ``` ### `totalSupply` Returns the total supply from the bank module. ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider(""); const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; const werc20Abi = ["function totalSupply() view returns (uint256)"]; const wtest = new ethers.Contract(wtestAddress, werc20Abi, provider); async function getTotalSupply() { try { const supply = await wtest.totalSupply(); console.log("Total Supply:", supply.toString()); } catch (error) { console.error("Error:", error); } } ``` ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} curl -X POST \ -H "Content-Type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ { "to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x18160ddd" }, "latest" ], "id": 1 }' ``` ### `approve` / `allowance` / `transferFrom` Standard ERC20 approval mechanisms for delegated transfers. ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider(""); const signer = new ethers.Wallet("", provider); const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; const werc20Abi = [ "function approve(address spender, uint256 amount) returns (bool)", "function allowance(address owner, address spender) view returns (uint256)", "function transferFrom(address from, address to, uint256 amount) returns (bool)" ]; const wtest = new ethers.Contract(wtestAddress, werc20Abi, signer); async function approveAndTransfer() { try { const spenderAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f5b899"; const amount = ethers.parseUnits("50.0", 18); // 50 TEST (18 decimals) // Approve spending const approveTx = await wtest.approve(spenderAddress, amount); await approveTx.wait(); // Check allowance const allowance = await wtest.allowance(signer.address, spenderAddress); console.log("Allowance:", allowance.toString()); // Transfer from (would be called by spender) // const transferTx = await wtest.transferFrom(ownerAddress, recipientAddress, amount); } catch (error) { console.error("Error:", error); } } ``` ### `name` / `symbol` / `decimals` Token metadata (e.g., "Wrapped Test", "WTEST", 18). ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider(""); const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; const werc20Abi = [ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)" ]; const wtest = new ethers.Contract(wtestAddress, werc20Abi, provider); async function getTokenInfo() { try { const [name, symbol, decimals] = await Promise.all([ wtest.name(), wtest.symbol(), wtest.decimals() ]); console.log(`Token: ${name} (${symbol}) - ${decimals} decimals`); } catch (error) { console.error("Error:", error); } } ``` ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Get token name curl -X POST \ -H "Content-Type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ {"to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x06fdde03"}, "latest" ], "id": 1 }' # Get token symbol curl -X POST \ -H "Content-Type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ {"to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x95d89b41"}, "latest" ], "id": 1 }' # Get decimals curl -X POST \ -H "Content-Type: application/json" \ --data '{ "jsonrpc": "2.0", "method": "eth_call", "params": [ {"to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x313ce567"}, "latest" ], "id": 1 }' ``` ### WETH Compatibility Methods These methods exist for WETH interface compatibility: ### `deposit` **WETH compatibility function** - Handles payable deposits for interface compatibility. This function receives msg.value and immediately sends the coins back to the caller via the bank module, then emits a Deposit event. Since WTEST and TEST are the same underlying bank module token, no actual wrapping occurs - your balance is simply accessible through both native and ERC20 interfaces. ```solidity Solidity expandable lines theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for WERC20 precompile interface IWERC20 { event Deposit(address indexed dst, uint256 wad); event Withdrawal(address indexed src, uint256 wad); function deposit() external payable; function withdraw(uint256 wad) external; function balanceOf(address account) external view returns (uint256); } contract WERC20Example { address constant WTEST = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; IWERC20 public immutable wtest; constructor() { wtest = IWERC20(WTEST); } function depositToWTEST() external payable { require(msg.value > 0, "Must send tokens to deposit"); // Get balance before deposit uint256 balanceBefore = wtest.balanceOf(msg.sender); // WERC20 deposit is a no-op for compatibility // Your native token balance is immediately accessible as WTEST wtest.deposit{value: msg.value}(); // Verify balance is now accessible via WTEST interface uint256 balanceAfter = wtest.balanceOf(msg.sender); // Both TEST and WTEST balances reflect the same bank module amount // No actual wrapping occurred - same token, different interface require(balanceAfter >= balanceBefore, "Deposit processed"); } function withdrawFromWTEST(uint256 amount) external { require(amount > 0, "Amount must be greater than 0"); require(wtest.balanceOf(msg.sender) >= amount, "Insufficient balance"); // WERC20 withdraw is a no-op that emits event for compatibility // Your bank balance remains accessible as both native TEST and WTEST wtest.withdraw(amount); // Tokens are still in bank module and accessible both ways } // Helper function to demonstrate balance consistency function checkBalanceConsistency(address user) external view returns ( uint256 wtestBalance, string memory explanation ) { wtestBalance = wtest.balanceOf(user); explanation = "This WTEST balance equals the user's native TEST balance in bank module"; return (wtestBalance, explanation); } // Example DeFi integration showing no wrapping needed function addLiquidityWithDeposit() external payable { require(msg.value > 0, "Must send tokens"); // Deposit via WERC20 interface (compatibility no-op) wtest.deposit{value: msg.value}(); // Your tokens are now accessible as WTEST for DeFi protocols // No additional steps needed - same token, ERC20 interface available uint256 availableForDeFi = wtest.balanceOf(msg.sender); // Use in DeFi protocols immediately // wtest.transfer(defiProtocolAddress, availableForDeFi); } } ``` ### `withdraw` **No-op function** - Included for interface compatibility with WETH contracts. This function only emits a Withdrawal event but performs no actual token movement. Since WTEST and TEST are the same underlying bank module token, your native token balance is always directly accessible without any unwrapping process. ## Usage Examples ### DeFi Integration Example ```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} contract LiquidityPool { IERC20 public immutable WTEST; constructor(address _wtest) { WTEST = IERC20(_wtest); } function addLiquidity(uint256 amount) external { // This transfers from the user's bank balance WTEST.transferFrom(msg.sender, address(this), amount); // Pool now has tokens in its bank balance // No wrapping/unwrapping needed - it's all the same token! } function removeLiquidity(uint256 amount) external { // This transfers back to user's bank balance WTEST.transfer(msg.sender, amount); // User can now use these tokens as native TEST // or continue using WTEST interface - both access same balance } } ``` ### Cross-Interface Balance Verification ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Verify that both interfaces show the same balance async function verifyBalanceConsistency(userAddress) { // Query via bank precompile (native interface) const bankBalance = await bankContract.balances(userAddress); const testAmount = bankBalance.find(b => b.denom === "test")?.amount || "0"; // Query via WERC20 precompile (ERC20 interface) const wtestAmount = await wtest.balanceOf(userAddress); // These will always be equal since the ERC20 balance is just // an abstracted bank module balance query console.log(`Consistent balance: ${testAmount} (both TEST and WTEST)`); } ``` ### Working with IBC Tokens ```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // IBC tokens work exactly the same way const ibcTokenAddress = "0x..."; // Each IBC token gets its own WERC20 address const ibcToken = new ethers.Contract(ibcTokenAddress, werc20Abi, signer); // Check balance (same as bank module balance) const balance = await ibcToken.balanceOf(userAddress); // Transfer IBC tokens via ERC20 interface await ibcToken.transfer(recipientAddress, amount); // Use in DeFi protocols just like any ERC20 token await defiProtocol.stake(ibcTokenAddress, amount); ``` ## Solidity Interface & ABI ```solidity title="WERC20 Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.18; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title WERC20 Precompile Contract /// @dev Provides ERC20 interface to native Cosmos tokens via bank module /// @notice This is NOT a traditional wrapped token - both native and ERC20 interfaces access the same balance interface IWERC20 is IERC20 { /// @dev Emitted when deposit() is called (no-op for compatibility) /// @param dst The address that called deposit /// @param wad The amount specified (though no conversion occurs) event Deposit(address indexed dst, uint256 wad); /// @dev Emitted when withdraw() is called (no-op for compatibility) /// @param src The address that called withdraw /// @param wad The amount specified (though no conversion occurs) event Withdrawal(address indexed src, uint256 wad); /// @dev No-op function for WETH compatibility - native tokens automatically update balance /// @notice This function exists for interface compatibility but performs no conversion function deposit() external payable; /// @dev No-op function for WETH compatibility - native tokens always accessible /// @param wad Amount to "withdraw" (no conversion performed) /// @notice This function exists for interface compatibility but performs no conversion function withdraw(uint256 wad) external; } ``` ```json title="WERC20 ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "_format": "hh-sol-artifact-1", "contractName": "IWERC20", "sourceName": "solidity/precompiles/werc20/IWERC20.sol", "abi": [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "dst", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "wad", "type": "uint256" } ], "name": "Deposit", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "src", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "wad", "type": "uint256" } ], "name": "Withdrawal", "type": "event" }, { "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "balanceOf", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "deposit", "outputs": [], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "totalSupply", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transfer", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "wad", "type": "uint256" } ], "name": "withdraw", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] } ``` # Custom Address Codec Source: https://docs.cosmos.network/ibc/latest/ibc/apps/address-codec ## Overview Starting in ibc-go `v10.4.0`, the IBC transfer module uses the application's configured address codec to parse sender and receiver addresses. This enables chains to accept multiple address formats in IBC packets—for example, both standard Cosmos bech32 addresses (`cosmos1...`) and Ethereum hex addresses (`0x...`). ## Interface The Cosmos SDK defines a simple interface for converting between address representations: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Codec interface { StringToBytes(text string) ([]byte, error) BytesToString(bz []byte) (string, error) } ``` Applications configure a codec implementation on the `AccountKeeper`. The IBC transfer module retrieves this codec via `accountKeeper.AddressCodec()` and uses it throughout packet processing—validating sender addresses when creating packets and parsing receiver addresses when delivering funds. **Chain independence:** Each chain applies its own codec independently. The sending chain validates senders with its codec, the receiving chain validates receivers with its codec. This works seamlessly across chains with different codec configurations without any protocol changes. ## Implementation A typical implementation composes the SDK's standard bech32 codec and extends it to parse hex addresses: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type EvmCodec struct { bech32Codec address.Codec } func (c *EvmCodec) StringToBytes(text string) ([]byte, error) { if strings.HasPrefix(text, "0x") { // Validate and parse hex address using go-ethereum/common if !common.IsHexAddress(text) { return nil, errors.New("invalid hex address") } addr := common.HexToAddress(text) return addr.Bytes(), nil } // Default to bech32 parsing return c.bech32Codec.StringToBytes(text) } func (c *EvmCodec) BytesToString(bz []byte) (string, error) { // Always return bech32 format return c.bech32Codec.BytesToString(bz) } ``` This pattern accepts both address formats as input while consistently outputting bech32. This makes the codec a drop-in replacement for the standard codec—existing tooling continues to work unchanged while users gain the ability to specify hex addresses where convenient. **Note:** A recommended address codec implementation is available in the [cosmos/evm repository](https://github.com/cosmos/evm/blob/main/encoding/address/address_codec.go). ### Application Wiring After initializing your transfer keeper, configure the codec using the `SetAddressCodec` method: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.TransferKeeper.SetAddressCodec(evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix())) ``` For a complete example showing the transfer keeper initialization and address codec configuration, see [evmd app.go](https://github.com/cosmos/evm/blob/720ba9cf908a20a29b7401b19a136caeb8c4092f/evmd/app.go#L483-L494). ## Usage Once configured, the chain accepts IBC transfers with receiver addresses in either format: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} # Standard bech32 address gaiad tx ibc-transfer transfer transfer channel-0 \ cosmos1p9p6h9m8jcn8f7l6h3k2wq9g6yx0l8a9u2n4lr 1000uatom --from sender # Ethereum hex address gaiad tx ibc-transfer transfer transfer channel-0 \ 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb 1000uatom --from sender ``` Both formats resolve to the same on-chain account when derived from the same private key. The codec handles conversion to the internal byte representation transparently. ## Reference Implementation The cosmos/evm repository provides a complete implementation in `utils/address_codec.go` with integration examples in the `evmd` reference chain: * [**Implementation PR**](https://github.com/cosmos/evm/pull/665) * [**Reference Chain "evmd"**](https://github.com/cosmos/evm/tree/main/evmd) # IBC Applications Source: https://docs.cosmos.network/ibc/latest/ibc/apps/apps This page is relevant for IBC Classic, naviagate to the IBC v2 applications page for information on v2 apps Learn how to configure your application to use IBC and send data packets to other chains. This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification, however a brief explaination is given. Then the document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, and `IBCModule` callbacks. To have your module interact over IBC you must: bind to a port(s), define your own packet data and acknowledgement structs as well as how to encode/decode them, and implement the `IBCModule` interface. Below is a more detailed explanation of how to write an IBC application module correctly. ## Pre-requisites Readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC default integration](/ibc/latest/ibc/integration) ## Create a custom IBC application module ### Implement `IBCModule` Interface and callbacks The Cosmos SDK expects all IBC modules to implement the [`IBCModule` interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. This section will describe the callbacks that are called during channel handshake execution. Here are the channel handshake callbacks that modules are expected to implement: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Called by IBC Handler on MsgOpenInit func (k Keeper) OnChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, counterparty channeltypes.Counterparty, version string, ) error { / ... do custom initialization logic / Use above arguments to determine if we want to abort handshake / Examples: Abort if order == UNORDERED, / Abort if version is unsupported err := checkArguments(args) return err } / Called by IBC Handler on MsgOpenTry OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, counterparty channeltypes.Counterparty, counterpartyVersion string, ) (string, error) { / ... do custom initialization logic / Use above arguments to determine if we want to abort handshake if err := checkArguments(args); err != nil { return err } / Construct application version / IBC applications must return the appropriate application version / This can be a simple string or it can be a complex version constructed / from the counterpartyVersion and other arguments. / The version returned will be the channel version used for both channel ends. appVersion := negotiateAppVersion(counterpartyVersion, args) return appVersion, nil } / Called by IBC Handler on MsgOpenAck OnChanOpenAck( ctx sdk.Context, portID, channelID string, counterpartyVersion string, ) error { / ... do custom initialization logic / Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } / Called by IBC Handler on MsgOpenConfirm OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, ) error { / ... do custom initialization logic / Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } ``` The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Called by IBC Handler on MsgCloseInit OnChanCloseInit( ctx sdk.Context, portID, channelID string, ) error { / ... do custom finalization logic / Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } / Called by IBC Handler on MsgCloseConfirm OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, ) error { / ... do custom finalization logic / Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } ``` #### Channel Handshake Version Negotiation Application modules are expected to verify versioning used during the channel handshake procedure. * `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid * `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. * `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. IBC expects application modules to perform application version negotiation in `OnChanOpenTry`. The negotiated version must be returned to core IBC. If the version cannot be negotiated, an error should be returned. Versions must be strings but can implement any versioning structure. If your application plans to have linear releases then semantic versioning is recommended. If your application plans to release various features in between major releases then it is advised to use the same versioning scheme as IBC. This versioning scheme specifies a version identifier and compatible feature set with that identifier. Valid version selection includes selecting a compatible version identifier with a subset of features supported by your application for that version. The struct is used for this scheme can be found in `03-connection/types`. Since the version type is a string, applications have the ability to do simple version verification via string matching or they can use the already implemented versioning system and pass the proto encoded version into each handhshake call as necessary. ICS20 currently implements basic string matching with a single supported version. ### ICS4Wrapper The IBC application interacts with core IBC through the `ICS4Wrapper` interface for any application-initiated actions like: `SendPacket` and `WriteAcknowledgement`. This may be directly the IBCChannelKeeper or a middleware that sits between the application and the IBC ChannelKeeper. If the application is being wired with a custom middleware, the application **must** have its ICS4Wrapper set to the middleware directly above it on the stack through the following call: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / SetICS4Wrapper sets the ICS4Wrapper. This function may be used after / the module's initialization to set the middleware which is above this / module in the IBC application stack. / The ICS4Wrapper **must** be used for sending packets and writing acknowledgements / to ensure that the middleware can intercept and process these calls. / Do not use the channel keeper directly to send packets or write acknowledgements / as this will bypass the middleware. SetICS4Wrapper(wrapper ICS4Wrapper) ``` ### Custom Packets Modules connected by a channel must agree on what application data they are sending over the channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up to each application module to determine how to implement this agreement. However, for most applications this will happen as a version negotiation during the channel handshake. While more complex version negotiation is possible to implement inside the channel opening handshake, a very simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). Thus, a module must define its custom packet data structure, along with a well-defined way to encode and decode it to and from `[]byte`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Custom packet data defined in application module type CustomPacketData struct { / Custom fields ... } EncodePacketData(packetData CustomPacketData) []byte { / encode packetData to bytes } DecodePacketData(encoded []byte) (CustomPacketData) { / decode from bytes to packet data } ``` Then a module must encode its packet data before sending it through IBC. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Sending custom application packet data data := EncodePacketData(customPacketData) packet.Data = data / Send packet to IBC, authenticating with channelCap sequence, err := IBCChannelKeeper.SendPacket( ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, ) ``` A module receiving a packet must decode the `PacketData` into a structure it expects so that it can act on it. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Receiving custom application packet data (in OnRecvPacket) packetData := DecodePacketData(packet.Data) / handle received custom packet data ``` #### Packet Flow Handling Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules to implement callbacks for handling the packet flow through a channel. Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. ![IBC packet flow diagram](https://media.githubusercontent.com/media/cosmos/ibc/old/spec/ics-004-channel-and-packet-semantics/channel-state-machine.png) Briefly, a successful packet flow works as follows: 1. module A sends a packet through the IBC module 2. the packet is received by module B 3. if module B writes an acknowledgement of the packet then module A will process the acknowledgement 4. if the packet is not successfully received before the timeout, then module A processes the packet's timeout. ##### Sending Packets Modules do not send packets through callbacks, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where msgs sent to the IBC module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Sending custom application packet data data := EncodePacketData(customPacketData) / Send packet to IBC, authenticating with channelCap sequence, err := IBCChannelKeeper.SendPacket( ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, ) ``` ##### Receiving Packets To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state changes given the packet data without worrying about whether the packet is valid or not. Modules may return to the IBC handler an acknowledgement which implements the Acknowledgement interface. The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the acknowledgement back to the sender module. The state changes that occurred during this callback will only be written if: * the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement * if the acknowledgement returned is nil indicating that an asynchronous process is occurring NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written for asynchronous acknowledgements. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} OnRecvPacket( ctx sdk.Context, packet channeltypes.Packet, ) ibcexported.Acknowledgement { / Decode the packet data packetData := DecodePacketData(packet.Data) / do application state changes based on packet data and return the acknowledgement / NOTE: The acknowledgement will indicate to the IBC handler if the application / state changes should be written via the `Success()` function. Application state / changes are only written if the acknowledgement is successful or the acknowledgement / returned is nil indicating that an asynchronous acknowledgement will occur. ack := processPacket(ctx, packet, packetData) return ack } ``` The Acknowledgement interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Acknowledgement defines the interface used to return / acknowledgements in the OnRecvPacket callback. type Acknowledgement interface { Success() bool Acknowledgement() []byte } ``` ### Acknowledgements Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement will be written once the packet has been processed by the application which may be well after the packet receipt. NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement for a packet as soon as it has been received from the IBC module. This acknowledgement can then be relayed back to the original sender chain, which can take action depending on the contents of the acknowledgement. Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and receive acknowledegments with the IBC modules as byte strings. Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an acknowledgement struct along with encoding and decoding it, is very similar to the packet data example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) specifies a recommended format for acknowledgements. This acknowledgement type can be imported from [channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): ```proto expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Acknowledgement is the recommended acknowledgement format to be used by / app-specific protocols. / NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental / conflicts with other protobuf message formats used for acknowledgements. / The first byte of any message with this format will be the non-ASCII values / `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: / https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope message Acknowledgement { / response contains either a result or an error and must be non-empty oneof response { bytes result = 21; string error = 22; } } ``` #### Acknowledging Packets After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it may often contain information on whether the packet was successfully processed along with some additional data that could be useful for remediation if the packet processing failed. Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback is responsible for decoding the acknowledgement and processing it. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} OnAcknowledgementPacket( ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, ) (*sdk.Result, error) { / Decode acknowledgement ack := DecodeAcknowledgement(acknowledgement) / process ack res, err := processAck(ack) return res, err } ``` #### Timeout Packets If the timeout for a packet is reached before the packet is successfully received or the counterparty channel end is closed before the packet is successfully received, then the receiving chain can no longer process it. Thus, the sending chain must process the timeout using `OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is indeed valid, so our module only needs to implement the state machine logic for what to do once a timeout is reached and the packet can no longer be received. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} OnTimeoutPacket( ctx sdk.Context, packet channeltypes.Packet, ) (*sdk.Result, error) { / do custom timeout logic } ``` ### Routing As mentioned above, modules must implement the IBC module interface (which contains both channel handshake callbacks and packet handling callbacks). The concrete implementation of this interface must be registered with the module name as a route on the IBC `Router`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go func NewApp(...args) *App { / ... / Create static IBC router, add module routes, then set and seal it ibcRouter := port.NewRouter() ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) / Note: moduleCallbacks must implement IBCModule interface ibcRouter.AddRoute(moduleName, moduleCallbacks) / Setting Router will finalize all routes by sealing router / No more routes can be added app.IBCKeeper.SetRouter(ibcRouter) ``` ## Working Example For a real working example of an IBC application, you can look through the `ibc-transfer` module which implements everything discussed above. Here are the useful parts of the module to look at: [Binding to transfer port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) [Sending transfer packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) [Implementing IBC callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) # Bind ports Source: https://docs.cosmos.network/ibc/latest/ibc/apps/bindports ## Synopsis Learn what changes to make to bind modules to their ports on initialization. ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC default integration](/ibc/latest/ibc/integration) Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: > Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. 1. Add port ID to the `GenesisState` proto definition: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GenesisState { string port_id = 1; / other fields } ``` 2. Add port ID as a key to the module store: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / x//types/keys.go const ( / ModuleName defines the IBC Module name ModuleName = "moduleName" / Version defines the current version the IBC / module supports Version = "moduleVersion-1" / PortID is the default port id that module binds to PortID = "portID" / ... ) ``` 3. Add port ID to `x//types/genesis.go`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / in x//types/genesis.go / DefaultGenesisState returns a GenesisState with "portID" as the default PortID. func DefaultGenesisState() *GenesisState { return &GenesisState{ PortId: PortID, / additional k-v fields } } / Validate performs basic genesis state validation returning an error upon any / failure. func (gs GenesisState) Validate() error { if err := host.PortIdentifierValidator(gs.PortId); err != nil { return err } /additional validations return gs.Params.Validate() } ``` 4. Set the port in the module keeper's for `InitGenesis`: The capability module has been removed so port binding has also changed ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / SetPort sets the portID for the transfer module. Used in InitGenesis func (k Keeper) SetPort(ctx sdk.Context, portID string) { store := k.storeService.OpenKVStore(ctx) if err := store.Set(types.PortKey, []byte(portID)); err != nil { panic(err) } } / Initialize any other module state, like params with SetParams. func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(¶ms) if err := store.Set([]byte(types.ParamsKey), bz); err != nil { panic(err) } } / ... ``` The module is set to the desired port. The setting and sealing happens during creation of the IBC router. # Implement IBCModule interface and callbacks Source: https://docs.cosmos.network/ibc/latest/ibc/apps/ibcmodule ## Synopsis Learn how to implement the `IBCModule` interface and all of the callbacks it requires. The Cosmos SDK expects all IBC modules to implement the [`IBCModule` interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // IBCModule implements the ICS26 interface for given the keeper. // The implementation of the IBCModule interface could for example be in a file called ibc_module.go, // but ultimately file structure is up to the developer type IBCModule struct { keeper keeper.Keeper } ``` All `IBCModule` implementations must also implement `SetICS4Wrapper`, which is called by the `IBCStackBuilder` during application setup to wire the ICS4 channel handler into the module: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im *IBCModule) SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) { im.keeper.SetICS4Wrapper(wrapper) } ``` Additionally, in the `module.go` file, add the following line: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ( _ module.AppModule = AppModule{ } _ module.AppModuleBasic = AppModuleBasic{ } // Add this line _ porttypes.IBCModule = IBCModule{ } ) ``` ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC default integration](/ibc/latest/ibc/integration) ## Channel handshake callbacks This section will describe the callbacks that are called during channel handshake execution. Here are the channel handshake callbacks that modules are expected to implement: > Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called by IBC Handler on MsgOpenInit func (im IBCModule) OnChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, counterparty channeltypes.Counterparty, version string, ) (string, error) { // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake // Examples: // - Abort if order == UNORDERED, // - Abort if version is unsupported if err := checkArguments(args); err != nil { return "", err } return version, nil } // Called by IBC Handler on MsgOpenTry func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, counterparty channeltypes.Counterparty, counterpartyVersion string, ) (string, error) { // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake if err := checkArguments(args); err != nil { return "", err } // Construct application version // IBC applications must return the appropriate application version // This can be a simple string or it can be a complex version constructed // from the counterpartyVersion and other arguments. // The version returned will be the channel version used for both channel ends. appVersion := negotiateAppVersion(counterpartyVersion, args) return appVersion, nil } // Called by IBC Handler on MsgOpenAck func (im IBCModule) OnChanOpenAck( ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string, ) error { if counterpartyVersion != types.Version { return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) } // do custom logic return nil } // Called by IBC Handler on MsgOpenConfirm func (im IBCModule) OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, ) error { // do custom logic return nil } ``` ### Channel closing callbacks The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. Currently, all IBC modules in this repository return an error for `OnChanCloseInit` to prevent the channels from closing. This is because any user can call `ChanCloseInit` by submitting a `MsgChannelCloseInit` transaction. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called by IBC Handler on MsgCloseInit func (im IBCModule) OnChanCloseInit( ctx sdk.Context, portID, channelID string, ) error { // ... do custom finalization logic // Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } // Called by IBC Handler on MsgCloseConfirm func (im IBCModule) OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, ) error { // ... do custom finalization logic // Use above arguments to determine if we want to abort handshake err := checkArguments(args) return err } ``` ### Channel handshake version negotiation Application modules are expected to verify versioning used during the channel handshake procedure. * `OnChanOpenInit` will verify that the relayer-chosen parameters are valid and perform any custom `INIT` logic. It may return an error if the chosen parameters are invalid in which case the handshake is aborted. If the provided version string is non-empty, `OnChanOpenInit` should return the version string if valid or an error if the provided version is invalid. **If the version string is empty, `OnChanOpenInit` is expected to return a default version string representing the version(s) it supports.** If there is no default version string for the application, it should return an error if the provided version is an empty string. * `OnChanOpenTry` will verify the relayer-chosen parameters along with the counterparty-chosen version string and perform custom `TRY` logic. If the relayer-chosen parameters are invalid, the callback must return an error to abort the handshake. If the counterparty-chosen version is not compatible with this module's supported versions, the callback must return an error to abort the handshake. If the versions are compatible, the try callback must select the final version string and return it to core IBC. `OnChanOpenTry` may also perform custom initialization logic. * `OnChanOpenAck` will error if the counterparty selected version string is invalid and abort the handshake. It may also perform custom ACK logic. Versions must be strings but can implement any versioning structure. If your application plans to have linear releases then semantic versioning is recommended. If your application plans to release various features in between major releases then it is advised to use the same versioning scheme as IBC. This versioning scheme specifies a version identifier and compatible feature set with that identifier. Valid version selection includes selecting a compatible version identifier with a subset of features supported by your application for that version. The struct used for this scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). Since the version type is a string, applications have the ability to do simple version verification via string matching or they can use the already implemented versioning system and pass the proto encoded version into each handhshake call as necessary. ICS20 currently implements basic string matching with a single supported version. ## Packet callbacks Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. IBC packet flow diagram Briefly, a successful packet flow works as follows: 1. Module A sends a packet through the IBC module 2. The packet is received by module B 3. If module B writes an acknowledgement of the packet then module A will process the acknowledgement 4. If the packet is not successfully received before the timeout, then module A processes the packet's timeout. ### Sending packets Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. > Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Sending custom application packet data data := EncodePacketData(customPacketData) // Send packet to IBC, authenticating with channelCap sequence, err := IBCChannelKeeper.SendPacket( ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, ) ``` ### Receiving packets To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state changes given the packet data without worrying about whether the packet is valid or not. Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the acknowledgement back to the sender module. The state changes that occurred during this callback will only be written if: * the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement * if the acknowledgement returned is nil indicating that an asynchronous process is occurring NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written for asynchronous acknowledgements. > Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCModule) OnRecvPacket( ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, ) ibcexported.Acknowledgement { // Decode the packet data packetData := DecodePacketData(packet.Data) // do application state changes based on packet data and return the acknowledgement // NOTE: The acknowledgement will indicate to the IBC handler if the application // state changes should be written via the `Success()` function. Application state // changes are only written if the acknowledgement is successful or the acknowledgement // returned is nil indicating that an asynchronous acknowledgement will occur. ack := processPacket(ctx, packet, packetData) return ack } ``` Reminder, the `Acknowledgement` interface: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Acknowledgement defines the interface used to return // acknowledgements in the OnRecvPacket callback. type Acknowledgement interface { Success() bool Acknowledgement() []byte } ``` ### Acknowledging packets After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it may often contain information on whether the packet was successfully processed along with some additional data that could be useful for remediation if the packet processing failed. Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback is responsible for decoding the acknowledgement and processing it. > Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCModule) OnAcknowledgementPacket( ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, ) error { // Decode acknowledgement ack := DecodeAcknowledgement(acknowledgement) // process ack res, err := processAck(ack) return res, err } ``` ### Timeout packets If the timeout for a packet is reached before the packet is successfully received or the counterparty channel end is closed before the packet is successfully received, then the receiving chain can no longer process it. Thus, the sending chain must process the timeout using `OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is indeed valid, so our module only needs to implement the state machine logic for what to do once a timeout is reached and the packet can no longer be received. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCModule) OnTimeoutPacket( ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, ) error { // do custom timeout logic } ``` ### Optional interfaces The following interface are optional and MAY be implemented by an IBCModule. #### PacketDataUnmarshaler The `PacketDataUnmarshaler` interface is defined as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PacketDataUnmarshaler defines an optional interface which allows a middleware to // request the packet data to be unmarshaled by the base application. type PacketDataUnmarshaler interface { // UnmarshalPacketData unmarshals the packet data into a concrete type // ctx, portID, channelID are provided as arguments, so that (if needed) // the packet data can be unmarshaled based on the channel version. // The version of the underlying app is also returned. UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{ }, string, error) } ``` The implementation of `UnmarshalPacketData` should unmarshal the bytes into the packet data type defined for an IBC stack. The base application of an IBC stack should unmarshal the bytes into its packet data type, while a middleware may simply defer the call to the underlying application. This interface allows middlewares to unmarshal a packet data in order to make use of interfaces the packet data type implements. For example, the callbacks middleware makes use of this function to access packet data types which implement the `PacketData` and `PacketDataProvider` interfaces. # IBC v2 Applications Source: https://docs.cosmos.network/ibc/latest/ibc/apps/ibcv2apps ## Synopsis Learn how to implement IBC v2 applications To build an IBC v2 application the following steps are required: 1. [Implement the `IBCModule` interface](#implement-the-ibcmodule-interface) 2. [Bind Ports](#bind-ports) 3. [Implement the IBCModule Keeper](#implement-the-ibcmodule-keeper) 4. [Implement application payload and success acknowledgement](#packets-and-payloads) 5. [Set and Seal the IBC Router](#routing) Highlighted improvements for app developers with IBC v2: * No need to support channel handshake callbacks * Flexibility on upgrading application versioning, no need to use channel upgradability to renegotiate an application version, simply support the application version on both sides of the connection. * Flexibility to choose your desired encoding type. ## Implement the `IBCModule` interface The Cosmos SDK expects all IBC modules to implement the [`IBCModule` interface](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L9-L53). This interface contains all of the callbacks IBC expects modules to implement. Note that for IBC v2, an application developer no longer needs to implement callbacks for the channel handshake. Note that this interface is distinct from the [porttypes.IBCModule interface][porttypes.IBCModule] used for IBC Classic. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / IBCModule implements the application interface given the keeper. / The implementation of the IBCModule interface could for example be in a file called ibc_module.go, / but ultimately file structure is up to the developer type IBCModule struct { keeper keeper.Keeper } ``` Additionally, in the `module.go` file, add the following line: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ( _ module.AppModule = AppModule{ } _ module.AppModuleBasic = AppModuleBasic{ } / Add this line _ porttypes.IBCModule = IBCModule{ } ) ``` ### Packet callbacks IBC expects modules to implement callbacks for handling the packet lifecycle, as defined in the `IBCModule` interface. With IBC v2, modules are not directly connected. Instead a pair of clients are connected and register the counterparty clientID. Packets are routed to the relevant application module by the portID registered in the Router. Relayers send packets between the routers/packet handlers on each chain. IBC packet flow diagram Briefly, a successful packet flow works as follows: 1. A user sends a message to the IBC packet handler 2. The IBC packet handler validates the message, creates the packet and stores the commitment and returns the packet sequence number. The [`Payload`](https://github.com/cosmos/ibc-go/blob/fe25b216359fab71b3228461b05dbcdb1a554158/proto/ibc/core/channel/v2/packet.proto#L26-L38), which contains application specific data, is routed to the relevant application. 3. If the counterparty writes an acknowledgement of the packet then the sending chain will process the acknowledgement. 4. If the packet is not successfully received before the timeout, then the sending chain processes the packet's timeout. #### Sending packets [`MsgSendPacket`](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/tx.pb.go#L69-L75) is sent by a user to the [channel v2 message server](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/keeper/msg_server.go), which calls `ChannelKeeperV2.SendPacket`. This validates the message, creates the packet, stores the commitment and returns the packet sequence number. The application must specify its own payload which is used by the application and sent with `MsgSendPacket`. An application developer needs to implement the custom logic the application executes when a packet is sent. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / OnSendPacket logic func (im *IBCModule) OnSendPacket( ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, signer sdk.AccAddress) error { / implement any validation / implement payload decoding and validation / call the relevant keeper method for state changes as a result of application logic / emit events or telemetry data return nil } ``` #### Receiving packets To handle receiving packets, the module must implement the `OnRecvPacket` callback. An application module should validate and confirm support for the given version and encoding method used as there is greater flexibility in IBC v2 to support a range of versions and encoding methods. The `OnRecvPacket` callback is invoked by the IBC module after the packet has been proven to be valid and correctly processed by the IBC keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state changes given the packet data without worrying about whether the packet is valid or not. Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the acknowledgement back to the sender module. The state changes that occurr during this callback could be: * the packet processing was successful as indicated by the `PacketStatus_Success` and an `Acknowledgement()` will be written * if the packet processing was unsuccessful as indicated by the `PacketStatus_Failure` and an `ackErr` will be written Note that with IBC v2 the error acknowledgements are standardised and cannot be customised. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCModule) OnRecvPacket( ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) channeltypesv2.RecvPacketResult { / do application state changes based on payload and return the result / state changes should be written via the `RecvPacketResult` return recvResult } ``` #### Acknowledging packets After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the acknowledgement is entirely up to the application developer. IBC will pass in the acknowledgements as `[]byte` to this callback. The callback is responsible for decoding the acknowledgement and processing it. The acknowledgement is serialised and deserialised using JSON. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCModule) OnAcknowledgementPacket( ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, acknowledgement []byte, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { / check the type of the acknowledgement / if not ackErr, unmarshal the JSON acknowledgement and unmarshal packet payload / perform any application specific logic for processing acknowledgement / emit events return nil } ``` #### Timeout packets If the timeout for a packet is reached before the packet is successfully received or the receiving chain can no longer process the packet the sending chain must process the timeout using `OnTimeoutPacket`. Again the IBC module will verify that the timeout is valid, so our module only needs to implement the state machine logic for what to do once a timeout is reached and the packet can no longer be received. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCModule) OnTimeoutPacket( ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { / unmarshal packet data / do custom timeout logic, e.g. refund tokens for transfer } ``` #### PacketDataUnmarshaler The `PacketDataUnmarshaler` interface is required for IBC v2 applications to implement because the encoding type is specified by the `Payload` and multiple encoding types are supported. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type PacketDataUnmarshaler interface { / UnmarshalPacketData unmarshals the packet data into a concrete type / the payload is provided and the packet data interface is returned UnmarshalPacketData(payload channeltypesv2.Payload) (interface{ }, error) } ``` ## Bind Ports Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: > Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. Add port ID to the `GenesisState` proto definition: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message GenesisState { string port_id = 1; / other fields } ``` You can see an example for transfer [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/applications/transfer/v1/genesis.proto). Add port ID as a key to the module store: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / x//types/keys.go const ( / ModuleName defines the IBC Module name ModuleName = "moduleName" / PortID is the default port id that module binds to PortID = "portID" / ... ) ``` Note that with IBC v2, the version does not need to be added as a key (as required with IBC classic) because versioning of applications is now contained within the [packet Payload](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.go#L23-L32). Add port ID to `x//types/genesis.go`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / in x//types/genesis.go / DefaultGenesisState returns a GenesisState / with the portID defined in keys.go func DefaultGenesisState() *GenesisState { return &GenesisState{ PortId: PortID, / additional k-v fields } } / Validate performs basic genesis state validation / returning an error upon any failure. func (gs GenesisState) Validate() error { if err := host.PortIdentifierValidator(gs.PortId); err != nil { return err } /additional validations return gs.Params.Validate() } ``` Set the port in the module keeper's for `InitGenesis`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / SetPort sets the portID for the transfer module. Used in InitGenesis func (k Keeper) SetPort(ctx sdk.Context, portID string) { store := k.storeService.OpenKVStore(ctx) if err := store.Set(types.PortKey, []byte(portID)); err != nil { panic(err) } } / Initialize any other module state, like params with SetParams. func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { store := k.storeService.OpenKVStore(ctx) bz := k.cdc.MustMarshal(¶ms) if err := store.Set([]byte(types.ParamsKey), bz); err != nil { panic(err) } } / ... ``` The module is set to the desired port. The setting and sealing happens during creation of the IBC router. ## Implement the IBCModule Keeper More information on implementing the IBCModule Keepers can be found in the [keepers section](/ibc/latest/ibc/apps/keeper) ## Packets and Payloads Applications developers need to define the `Payload` contained within an [IBC packet](https://github.com/cosmos/ibc-go/blob/fe25b216359fab71b3228461b05dbcdb1a554158/proto/ibc/core/channel/v2/packet.proto#L11-L24). Note that in IBC v2 the `timeoutHeight` has been removed and only `timeoutTimestamp` is used. A packet can contain multiple payloads in a list. Each Payload includes: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) message Payload { / specifies the source port of the packet. string source_port = 1; / specifies the destination port of the packet. string destination_port = 2; / version of the specified application. string version = 3; / the encoding used for the provided value. string encoding = 4; / the raw bytes for the payload. bytes value = 5; } ``` Note that compared to IBC classic, where the applications version and encoding is negotiated during the channel handshake, IBC v2 provides enhanced flexibility. The application version and encoding used by the Payload is defined in the Payload. An example Payload is illustrated below: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MyAppPayloadData struct { Field1 string Field2 uint64 } / Marshal your payload to bytes using your encoding bz, err := json.Marshal(MyAppPayloadData{ Field1: "example", Field2: 7 }) / Wrap it in a channel v2 Payload payload := channeltypesv2.NewPayload( sourcePort, destPort, "my-app-v1", / App version channeltypesv2.EncodingJSON, / Encoding type, e.g. JSON, protobuf or ABI bz, / Encoded data ) ``` It is also possible to define your own custom success acknowledgement which will be returned to the sender if the packet is successfully recieved and is returned in the `RecvPacketResult`. Note that if the packet processing fails, it is not possible to define a custom error acknowledgment, a constant ackErr is returned. ## Routing More information on implementing the IBC Router can be found in the [routing section](/ibc/latest/ibc/apps/routing). [porttypes.IBCModule]: https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go # Keeper Source: https://docs.cosmos.network/ibc/latest/ibc/apps/keeper ## Synopsis Learn how to implement the IBC Module keeper. Relevant for IBC classic and v2 ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC default integration](/ibc/latest/ibc/integration) In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. > Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Keeper defines the IBC app module keeper type Keeper struct { storeKey sdk.StoreKey cdc codec.BinaryCodec paramSpace paramtypes.Subspace channelKeeper types.ChannelKeeper portKeeper types.PortKeeper / ... additional according to custom logic } / NewKeeper creates a new IBC app module Keeper instance func NewKeeper( / args ) Keeper { / ... return Keeper{ cdc: cdc, storeKey: key, paramSpace: paramSpace, channelKeeper: channelKeeper, portKeeper: portKeeper, / ... additional according to custom logic } } / GetPort returns the portID for the IBC app module. Used in ExportGenesis func (k Keeper) GetPort(ctx sdk.Context) string { store := ctx.KVStore(k.storeKey) return string(store.Get(types.PortKey)) } / SetPort sets the portID for the IBC app module. Used in InitGenesis func (k Keeper) SetPort(ctx sdk.Context, portID string) { store := ctx.KVStore(k.storeKey) store.Set(types.PortKey, []byte(portID)) } / ... additional according to custom logic ``` # Define packets and acks Source: https://docs.cosmos.network/ibc/latest/ibc/apps/packets_acks ## Synopsis Learn how to define custom packet and acknowledgement structs and how to encode and decode them. ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC default integration](/ibc/latest/ibc/integration) ## Custom packets Modules connected by a channel must agree on what application data they are sending over the channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up to each application module to determine how to implement this agreement. However, for most applications this will happen as a version negotiation during the channel handshake. While more complex version negotiation is possible to implement inside the channel opening handshake, a very simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). Thus, a module must define its custom packet data structure, along with a well-defined way to encode and decode it to and from `[]byte`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Custom packet data defined in application module type CustomPacketData struct { / Custom fields ... } EncodePacketData(packetData CustomPacketData) []byte { / encode packetData to bytes } DecodePacketData(encoded []byte) (CustomPacketData) { / decode from bytes to packet data } ``` > Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. Then a module must encode its packet data before sending it through IBC. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Sending custom application packet data data := EncodePacketData(customPacketData) / Send packet to IBC, authenticating with channelCap sequence, err := IBCChannelKeeper.SendPacket( ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, ) ``` A module receiving a packet must decode the `PacketData` into a structure it expects so that it can act on it. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Receiving custom application packet data (in OnRecvPacket) packetData := DecodePacketData(packet.Data) / handle received custom packet data ``` ### Optional interfaces The following interfaces are optional and MAY be implemented by a custom packet type. They allow middlewares such as callbacks to access information stored within the packet data. #### PacketData interface The `PacketData` interface is defined as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / PacketData defines an optional interface which an application's packet data structure may implement. type PacketData interface { / GetPacketSender returns the sender address of the packet data. / If the packet sender is unknown or undefined, an empty string should be returned. GetPacketSender(sourcePortID string) string } ``` The implementation of `GetPacketSender` should return the sender of the packet data. If the packet sender is unknown or undefined, an empty string should be returned. This interface is intended to give IBC middlewares access to the packet sender of a packet data type. #### PacketDataProvider interface The `PacketDataProvider` interface is defined as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. / An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. / A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. / This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. type PacketDataProvider interface { / GetCustomPacketData returns the packet data held on behalf of another application. / The name the information is stored under should be provided as the key. / If no custom packet data exists for the key, nil should be returned. GetCustomPacketData(key string) interface{ } } ``` The implementation of `GetCustomPacketData` should return packet data held on behalf of another application (if present and supported). If this functionality is not supported, it should return nil. Otherwise it should return the packet data associated with the provided key. This interface gives IBC applications access to the packet data information embedded into the base packet data type. Within transfer and interchain accounts, the embedded packet data is stored within the Memo field. Once all IBC applications within an IBC stack are capable of creating/maintaining their own packet data type's, this interface function will be deprecated and removed. ## Acknowledgements Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement will be written once the packet has been processed by the application which may be well after the packet receipt. NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement for a packet as soon as it has been received from the IBC module. This acknowledgement can then be relayed back to the original sender chain, which can take action depending on the contents of the acknowledgement. Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and receive acknowledegments with the IBC modules as byte strings. Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an acknowledgement struct along with encoding and decoding it, is very similar to the packet data example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) specifies a recommended format for acknowledgements. This acknowledgement type can be imported from [channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Acknowledgement is the recommended acknowledgement format to be used by / app-specific protocols. / NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental / conflicts with other protobuf message formats used for acknowledgements. / The first byte of any message with this format will be the non-ASCII values / `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: / https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope message Acknowledgement { / response contains either a result or an error and must be non-empty oneof response { bytes result = 21; string error = 22; } } ``` # Routing Source: https://docs.cosmos.network/ibc/latest/ibc/apps/routing ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC default integration](/ibc/latest/ibc/integration) ## Synopsis Learn how to hook a route to the IBC router for the custom IBC module. As mentioned above, modules must implement the `IBCModule` interface (which contains both channel handshake callbacks for IBC classic only, and packet handling callbacks for IBC classic and v2). The concrete implementation of this interface must be registered with the module name as a route on the IBC `Router`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go func NewApp(...args) *App { / ... / Create static IBC router, add module routes, then set and seal it ibcRouter := port.NewRouter() ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) / Note: moduleCallbacks must implement IBCModule interface ibcRouter.AddRoute(moduleName, moduleCallbacks) / Setting Router will finalize all routes by sealing router / No more routes can be added app.IBCKeeper.SetRouter(ibcRouter) / ... } ``` # Best Practices Source: https://docs.cosmos.network/ibc/latest/ibc/best-practices ## Identifying legitimate channels Identifying which channel to use can be difficult as it requires verifying information about the chains you want to connect to. Channels are based on a light client. A chain can be uniquely identified by its chain ID, validator set pairing. It is unsafe to rely only on the chain ID. Any user can create a client with any chain ID, but only the chain with correct validator set and chain ID can produce headers which would update that client. Which channel to use is based on social consensus. The desired channel should have the following properties: * based on a valid client (can only be updated by the chain it connects to) * has sizable activity * the underlying client is active To verify if a client is valid. You will need to obtain a header from the chain you want to connect to. This can be done by running a full node for that chain or relying on a trusted rpc address. Then you should query the light client you want to verify and obtain its latest consensus state. All consensus state fields must match the header queried for at same height as the consensus state (root, timestamp, next validator set hash). Explorers and wallets are highly encouraged to follow this practice. It is unsafe to algorithmically add new channels without following this process. # Integration Source: https://docs.cosmos.network/ibc/latest/ibc/integration ## Synopsis Learn how to integrate IBC to your application This document outlines the required steps to integrate and configure the [IBC module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and enable sending fungible token transfers to other chains. There is a [simapp included using ibc-go](https://github.com/cosmos/ibc-go/tree/main/simapp) as a reference. ## Integrating the IBC module Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: * [Define additional `Keeper` fields for the new modules on the `App` type](#add-application-fields-to-app). * [Add the module's `StoreKey`s and initialize their `Keeper`s](#configure-the-keepers). * [Create Application Stacks with Middleware](#create-application-stacks-with-middleware) * [Set up IBC router and add route for the `transfer` module](#register-module-routes-in-the-ibc-router). * [Grant permissions to `transfer`'s `ModuleAccount`](#module-account-permissions). * [Add the modules to the module `Manager`](#module-manager-and-simulationmanager). * [Update the module `SimulationManager` to enable simulations](#module-manager-and-simulationmanager). * [Integrate light client modules (e.g. `07-tendermint`)](#integrating-light-clients). * [Add modules to `Begin/EndBlockers` and `InitGenesis`](#application-abci-ordering). ### Add application fields to `App` We need to register the core `ibc` and `transfer` `Keeper`s. The `transferv2` package is used for IBC v2 routing and does not require a separate keeper — the same `TransferKeeper` is reused. ```go title="app.go" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( // other imports // ... ibckeeper "github.com/cosmos/ibc-go/v11/modules/core/keeper" ibctransferkeeper "github.com/cosmos/ibc-go/v11/modules/apps/transfer/keeper" // ibc v2 import (no separate keeper needed) transferv2 "github.com/cosmos/ibc-go/v11/modules/apps/transfer/v2" ) type App struct { // baseapp, keys and subspaces definitions // other keepers // ... IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly TransferKeeper *ibctransferkeeper.Keeper // for cross-chain fungible token transfers // ... // module and simulation manager definitions } ``` ### Configure the `Keeper`s Initialize the IBC `Keeper`s (for core `ibc` and `transfer` modules), and any additional modules you want to include. **Notice** The capability module has been removed in ibc-go v10, therefore the `ScopedKeeper` has also been removed ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( // other imports // ... authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ibcexported "github.com/cosmos/ibc-go/v11/modules/core/exported" ibckeeper "github.com/cosmos/ibc-go/v11/modules/core/keeper" "github.com/cosmos/ibc-go/v11/modules/apps/transfer" ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" ibctm "github.com/cosmos/ibc-go/v11/modules/light-clients/07-tendermint" ) func NewApp(...args) *App { // define codecs and baseapp // ... other module keepers // Create IBC Keeper app.IBCKeeper = ibckeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), app.UpgradeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // Create Transfer Keeper app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.IBCKeeper.ChannelKeeper, app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // ... continues } ``` Starting from ibc-go v11, consensus parameter authority takes precedence over keeper authority for all IBC modules due to [`sdk.ValidateAuthority`](https://github.com/cosmos/cosmos-sdk/blob/d17338af62722f9fa4108d8494b876dff64a4c6e/types/authority.go#L11). ### Create Application Stacks with Middleware Middleware stacks in IBC allow you to wrap an `IBCModule` with additional logic for packets and acknowledgements. This is a chain of handlers that execute in order. Use `porttypes.NewIBCStackBuilder` to wire up a stack. The builder wires the `app`, `ics4Wrapper`, and each middleware together automatically via `SetUnderlyingApplication` and `SetICS4Wrapper` — you do **not** pass these into middleware constructors directly. The transfer stack below shows how to wire up transfer with rate limiting and packet forward middleware. Note that `Base` is the bottom of the stack (the application), and each `Next` call adds a middleware on top: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Create Transfer Stack for IBC Classic // Stack order (top to bottom): RateLimit -> PacketForward -> Transfer transferApp := transfer.NewIBCModule(app.TransferKeeper) transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper). Base(transferApp). Next(packetforward.NewIBCMiddleware(app.PFMKeeper, 0, packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp)). Next(ratelimiting.NewIBCMiddleware(app.RateLimitKeeper)). Build() ``` #### IBC v2 Application Stack For IBC v2, register the transfer v2 module directly on the v2 router — no middleware stack is needed: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferv2.NewIBCModule(app.TransferKeeper)) ``` ### Register module routes in the IBC `Router` IBC needs to know which module is bound to which port so that it can route packets to the appropriate module and call the appropriate callbacks. The port to module name mapping is handled by IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished by the port [`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the `ibc` module. Adding the module routes allows the IBC handler to call the appropriate callback when processing a channel handshake or a packet. Currently, a `Router` is static so it must be initialized and set correctly on app initialization. Once the `Router` has been set, no new routes can be added. ```go title="app.go" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( // other imports // ... porttypes "github.com/cosmos/ibc-go/v11/modules/core/05-port/types" ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" ) func NewApp(...args) *App { // .. continuation from above // Create static IBC router, add transfer module route, then set and seal it ibcRouter := porttypes.NewRouter() ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) // Setting Router will finalize all routes by sealing router // No more routes can be added app.IBCKeeper.SetRouter(ibcRouter) // ... continues ``` #### IBC v2 Router With IBC v2, there is a new [router](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/router.go) that needs to register the routes for a portID to a given IBCModule. It supports two kinds of routes: direct routes and prefix-based routes. The direct routes match one specific port ID to a module, while the prefix-based routes match any port ID with a specific prefix to a module. For example, if a direct route named `someModule` exists, only messages addressed to exactly that port ID will be passed to the corresponding module. However, if instead, `someModule` is a prefix-based route, port IDs like `someModuleRandomPort1`, `someModuleRandomPort2`, etc., will be passed to the module. Note that the router will panic when you add a route that conflicts with an already existing route. This is also the case if you add a prefix-based route that conflicts with an existing direct route or vice versa. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // IBC v2 router creation ibcRouterV2 := ibcapi.NewRouter() ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) // Setting Router will finalize all routes by sealing router // No more routes can be added app.IBCKeeper.SetRouterV2(ibcRouterV2) ``` ### Module `Manager` and `SimulationManager` In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager`, in case your application supports [simulations](/sdk/latest/guides/testing/simulator). ```go title="app.go" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( // other imports // ... "github.com/cosmos/cosmos-sdk/types/module" ibc "github.com/cosmos/ibc-go/v11/modules/core" "github.com/cosmos/ibc-go/v11/modules/apps/transfer" ) func NewApp(...args) *App { // ... continuation from above app.ModuleManager = module.NewManager( // other modules // ... // highlight-start + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), // highlight-end ) // ... app.simulationManager = module.NewSimulationManagerFromAppModules( // other modules // ... app.ModuleManager.Modules, map[string]module.AppModuleSimulation{ }, ) // ... continues ``` ### Module account permissions After that, we need to grant `Minter` and `Burner` permissions to the `transfer` `ModuleAccount` to mint and burn relayed tokens. ```go title="app.go" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( // other imports // ... "github.com/cosmos/cosmos-sdk/types/module" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" // highlight-next-line + ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" ) // app.go var ( // module account permissions maccPerms = map[string][]string{ // other module accounts permissions // ... ibctransfertypes.ModuleName: { authtypes.Minter, authtypes.Burner }, } ) ``` ### Integrating light clients > Note that from v10 onwards, all light clients are expected to implement the [`LightClientInterface` interface](/ibc/latest/light-clients/developer-guide/light-client-module#implementing-the-lightclientmodule-interface) defined by core IBC, and have to be explicitly registered in a chain's app.go. This is in contrast to earlier versions of ibc-go when `07-tendermint` and `06-solomachine` were added out of the box. Follow the steps below to integrate the `07-tendermint` light client. All light clients must be registered with `module.Manager` in a chain's app.go file. The following code example shows how to instantiate `07-tendermint` light client module and register its `ibctm.AppModule`. ```go title="app.go" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( // other imports // ... "github.com/cosmos/cosmos-sdk/types/module" // highlight-next-line + ibctm "github.com/cosmos/ibc-go/v11/modules/light-clients/07-tendermint" ) // app.go // after sealing the IBC router clientKeeper := app.IBCKeeper.ClientKeeper storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) // ... app.ModuleManager = module.NewManager( // ... ibc.NewAppModule(app.IBCKeeper), transfer.NewAppModule(app.TransferKeeper), // i.e ibc-transfer module // register light clients on IBC // highlight-next-line + ibctm.NewAppModule(tmLightClientModule), ) ``` #### Allowed Clients Params The allowed clients parameter defines an allow list of client types supported by the chain. The default value is a single-element list containing the [`AllowedClients`](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/types/client.pb.go#L248-L253) wildcard (`"*"`). Alternatively, the parameter may be set with a list of client types (e.g. `"06-solomachine","07-tendermint","09-localhost"`). A client type that is not registered on this list will fail upon creation or on genesis validation. Note that, since the client type is an arbitrary string, chains must not register two light clients which return the same value for the `ClientType()` function, otherwise the allow list check can be bypassed. ### Application ABCI ordering One addition from IBC is the concept of `HistoricalInfo` which is stored in the Cosmos SDK `x/staking` module. The number of records stored by `x/staking` is controlled by the `HistoricalEntries` parameter which stores `HistoricalInfo` on a per-height basis. Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored at each height during the `BeginBlock` call. The `HistoricalInfo` is required to introspect a blockchain's prior state at a given height in order to verify the light client `ConsensusState` during the connection handshake. ```go title="app.go" expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( // other imports // ... stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ibcexported "github.com/cosmos/ibc-go/v11/modules/core/exported" ibckeeper "github.com/cosmos/ibc-go/v11/modules/core/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" ) func NewApp(...args) *App { // ... continuation from above // add x/staking, ibc and transfer modules to BeginBlockers app.ModuleManager.SetOrderBeginBlockers( // other modules ... stakingtypes.ModuleName, ibcexported.ModuleName, ibctransfertypes.ModuleName, ) app.ModuleManager.SetOrderEndBlockers( // other modules ... stakingtypes.ModuleName, ibcexported.ModuleName, ibctransfertypes.ModuleName, ) // ... genesisModuleOrder := []string{ // other modules // ... ibcexported.ModuleName, ibctransfertypes.ModuleName, } app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) // ... continues ``` That's it! You have now wired up the IBC module and the `transfer` module, and are now able to send fungible tokens across different chains. If you want to have a broader view of the changes take a look into the SDK's [`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). # Create a custom IBC middleware Source: https://docs.cosmos.network/ibc/latest/ibc/middleware/develop IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.\[Must]MarshalJSON For middleware builders this means: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import transfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" transfertypes.ModuleCdc.[Must]MarshalJSON func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { return transfertypes.ModuleCdc.MarshalJSON(&ack) } ``` The interfaces a middleware must implement are found [here](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Middleware implements the ICS26 Module interface type Middleware interface { IBCModule // middleware has access to an underlying application which may be wrapped by more middleware ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. // SetUnderlyingModule sets the underlying IBC module. This function may be used after // the middleware's initialization to set the ibc module which is below this middleware. SetUnderlyingApplication(IBCModule) } ``` An `IBCMiddleware` struct implementing the `Middleware` interface can be defined with its constructor as follows. Note that the constructor **does not** accept the underlying `app` or `ics4Wrapper` — those are wired up later by `IBCStackBuilder` via `SetUnderlyingApplication` and `SetICS4Wrapper`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // @ x/module_name/ibc_middleware.go // IBCMiddleware implements the ICS26 callbacks and ICS4Wrapper for the middleware given its keeper. type IBCMiddleware struct { app porttypes.IBCModule ics4Wrapper porttypes.ICS4Wrapper keeper *keeper.Keeper } // NewIBCMiddleware creates a new IBCMiddleware given the keeper. // The underlying app and ICS4Wrapper are set later by IBCStackBuilder. func NewIBCMiddleware(k *keeper.Keeper) *IBCMiddleware { return &IBCMiddleware{ keeper: k, } } // SetUnderlyingApplication sets the underlying IBC module. func (im *IBCMiddleware) SetUnderlyingApplication(app porttypes.IBCModule) { if im.app != nil { panic("underlying application already set") } im.app = app } // SetICS4Wrapper sets the ICS4Wrapper. func (im *IBCMiddleware) SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) { if wrapper == nil { panic("ICS4Wrapper cannot be nil") } im.ics4Wrapper = wrapper } ``` ## Implement `IBCModule` interface `IBCMiddleware` is a struct that implements the [ICS-26 `IBCModule` interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go#L13-L118). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. > Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. As will be mentioned in the [integration section](/ibc/latest/ibc/middleware/integration), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. > Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. The `IBCModule` interface consists of the channel handshake callbacks and packet callbacks. Most of the custom logic will be performed in the packet callbacks, in the case of the channel handshake callbacks, introducing the middleware requires consideration to the version negotiation. ### Channel handshake callbacks #### Version negotiation In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "": "", "app_version": "" } ``` The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. > **NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. #### `OnChanOpenInit` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnChanOpenInit( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, counterparty channeltypes.Counterparty, version string, ) (string, error) { if version != "" { // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. metadata, err := Unmarshal(version) if err != nil { // Since it is valid for fee version to not be specified, // the above middleware version may be for another middleware. // Pass the entire version string onto the underlying application. return im.app.OnChanOpenInit( ctx, order, connectionHops, portID, channelID, counterparty, version, ) } else { metadata = { // set middleware version to default value MiddlewareVersion: defaultMiddlewareVersion, // allow application to return its default version AppVersion: "", } } } doCustomLogic() // if the version string is empty, OnChanOpenInit is expected to return // a default version string representing the version(s) it supports appVersion, err := im.app.OnChanOpenInit( ctx, order, connectionHops, portID, channelID, counterparty, metadata.AppVersion, // note we only pass app version here ) if err != nil { return "", err } version := constructVersion(metadata.MiddlewareVersion, appVersion) return version, nil } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L314-L324) an example implementation of this callback for the Callbacks Middleware module. #### `OnChanOpenTry` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, counterparty channeltypes.Counterparty, counterpartyVersion string, ) (string, error) { // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. cpMetadata, err := Unmarshal(counterpartyVersion) if err != nil { return app.OnChanOpenTry( ctx, order, connectionHops, portID, channelID, counterparty, counterpartyVersion, ) } doCustomLogic() // Call the underlying application's OnChanOpenTry callback. // The try callback must select the final app-specific version string and return it. appVersion, err := app.OnChanOpenTry( ctx, order, connectionHops, portID, channelID, counterparty, cpMetadata.AppVersion, // note we only pass counterparty app version here ) if err != nil { return "", err } // negotiate final middleware version middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) version := constructVersion(middlewareVersion, appVersion) return version, nil } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L327-L336) an example implementation of this callback for the Callbacks Middleware module. #### `OnChanOpenAck` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnChanOpenAck( ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string, ) error { // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. cpMetadata, err = UnmarshalJSON(counterpartyVersion) if err != nil { return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) } if !isCompatible(cpMetadata.MiddlewareVersion) { return error } doCustomLogic() // call the underlying application's OnChanOpenTry callback return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L339-L347) an example implementation of this callback for the Callbacks Middleware module. #### `OnChanOpenConfirm` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, ) error { doCustomLogic() return app.OnChanOpenConfirm(ctx, portID, channelID) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L350-L352) an example implementation of this callback for the Callbacks Middleware module. #### `OnChanCloseInit` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func OnChanCloseInit( ctx sdk.Context, portID, channelID string, ) error { doCustomLogic() return app.OnChanCloseInit(ctx, portID, channelID) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L355-L357) an example implementation of this callback for the Callbacks Middleware module. #### `OnChanCloseConfirm` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, ) error { doCustomLogic() return app.OnChanCloseConfirm(ctx, portID, channelID) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L360-L362) an example implementation of this callback for the Callbacks Middleware module. ### Packet callbacks The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. #### `OnRecvPacket` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnRecvPacket( ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, ) ibcexported.Acknowledgement { doCustomLogic(packet) ack := app.OnRecvPacket(ctx, channelVersion, packet, relayer) doCustomLogic(ack) // middleware may modify outgoing ack return ack } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L225-L265) an example implementation of this callback for the Callbacks Middleware module. #### `OnAcknowledgementPacket` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnAcknowledgementPacket( ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, ) error { doCustomLogic(packet, ack) return app.OnAcknowledgementPacket(ctx, channelVersion, packet, ack, relayer) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L139-L180) an example implementation of this callback for the Callbacks Middleware module. #### `OnTimeoutPacket` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnTimeoutPacket( ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, ) error { doCustomLogic(packet) return app.OnTimeoutPacket(ctx, channelVersion, packet, relayer) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L186-L218) an example implementation of this callback for the Callbacks Middleware module. ## ICS-04 wrappers Middleware must also wrap ICS-04 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. To ensure optimal generalisability, the `ICS4Wrapper` abstraction serves to abstract away whether a middleware is the topmost middleware (and thus directly calling into the ICS-04 `channelKeeper`) or itself being wrapped by another middleware. Remember that middleware can be stateful or stateless. When defining the stateful middleware's keeper, the `ics4Wrapper` field is included. Then the appropriate keeper can be passed when instantiating the middleware's keeper in `app.go` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Keeper struct { storeKey storetypes.StoreKey cdc codec.BinaryCodec ics4Wrapper porttypes.ICS4Wrapper channelKeeper types.ChannelKeeper portKeeper types.PortKeeper ... } ``` For stateless middleware, the `ics4Wrapper` can be passed on directly without having to instantiate a keeper struct for the middleware. [The interface](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go#L119-L143) looks as follows: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // This is implemented by ICS4 and all middleware that are wrapping base application. // The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them // which will call the next middleware until it reaches the core IBC handler. type ICS4Wrapper interface { SendPacket( ctx sdk.Context, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte, ) (sequence uint64, err error) WriteAcknowledgement( ctx sdk.Context, packet exported.PacketI, ack exported.Acknowledgement, ) error GetAppVersion( ctx sdk.Context, portID, channelID string, ) (string, bool) } ``` :warning: In the following paragraphs, the methods are presented in pseudo code which has been kept general, not stating whether the middleware is stateful or stateless. Remember that when the middleware is stateful, `ics4Wrapper` can be accessed through the keeper. Check out the references provided for an actual implementation to clarify, where the `ics4Wrapper` methods in `ibc_middleware.go` simply call the equivalent keeper methods where the actual logic resides. ### `SendPacket` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func SendPacket( ctx sdk.Context, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, appData []byte, ) (uint64, error) { // middleware may modify data data = doCustomLogic(appData) return ics4Wrapper.SendPacket( ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, ) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L92-L133) an example implementation of this function for the Callbacks Middleware module. ### `WriteAcknowledgement` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // only called for async acks func WriteAcknowledgement( ctx sdk.Context, packet exported.PacketI, ack exported.Acknowledgement, ) error { // middleware may modify acknowledgement ack_bytes = doCustomLogic(ack) return ics4Wrapper.WriteAcknowledgement(packet, ack_bytes) } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L272-L311) an example implementation of this function for the Callbacks Middleware module. ### `GetAppVersion` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // middleware must return the underlying application version func GetAppVersion( ctx sdk.Context, portID, channelID string, ) (string, bool) { version, found := ics4Wrapper.GetAppVersion(ctx, portID, channelID) if !found { return "", false } if !MiddlewareEnabled { return version, true } // unwrap channel version metadata, err := Unmarshal(version) if err != nil { panic(fmt.Errorf("unable to unmarshal version: %w", err)) } return metadata.AppVersion, true } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/ibc_middleware.go#L366-L368) an example implementation of this function for the Callbacks Middleware module. ## Wiring Interface Requirements Middleware must also implement the following functions so that they can be called in the stack builder in order to correctly wire the application stack together: `SetUnderlyingApplication` and `SetICS4Wrapper`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SetUnderlyingModule sets the underlying IBC module. This function may be used after // the middleware's initialization to set the ibc module which is below this middleware. SetUnderlyingApplication(IBCModule) // SetICS4Wrapper sets the ICS4Wrapper. This function may be used after // the module's initialization to set the middleware which is above this // module in the IBC application stack. // The ICS4Wrapper **must** be used for sending packets and writing acknowledgements // to ensure that the middleware can intercept and process these calls. // Do not use the channel keeper directly to send packets or write acknowledgements // as this will bypass the middleware. SetICS4Wrapper(wrapper ICS4Wrapper) ``` The middleware itself should have access to the `underlying app` (note this may be a base app or an application wrapped by layers of lower-level middleware(s)) and access to the higher layer `ICS4wrapper`. The `underlying app` gets called during the relayer initiated actions: `recvPacket`, `acknowledgePacket`, and `timeoutPacket`. The `ics4Wrapper` gets called on user-initiated actions like `sendPacket` and `writeAcknowledgement`. The functions above are used by the `StackBuilder` during application setup to wire the stack correctly. The stack must be wired first and have all of the wrappers and applications set correctly before transaction execution starts and packet processing begins. # Create and integrate IBC v2 middleware Source: https://docs.cosmos.network/ibc/latest/ibc/middleware/developIBCv2 1. Create a custom IBC v2 middleware 2. Implement IBCModule interface 3. WriteAckWrapper 4. Integrate IBC v2 Middleware 5. Security Model 6. Design Principles 1. [Create a custom IBC v2 middleware](#create-a-custom-ibc-v2-middleware) 2. [Implement `IBCModule` interface](#implement-ibcmodule-interface) 3. [WriteAckWrapper](#writeackwrapper) 4. [Integrate IBC v2 Middleware](#integrate-ibc-v2-middleware) 5. [Security Model](#security-model) 6. [Design Principles](#design-principles) ## Create a custom IBC v2 middleware IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.\[Must]MarshalJSON For middleware builders this means: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import transfertypes "github.com/cosmos/ibc-go/v11/modules/apps/transfer/types" transfertypes.ModuleCdc.[Must]MarshalJSON func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { return transfertypes.ModuleCdc.MarshalJSON(&ack) } ``` The interfaces a middleware must implement are found in [core/api](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11). Note that this interface has changed from IBC classic. An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // @ x/module_name/ibc_middleware.go // IBCMiddleware implements the IBCv2 middleware interface type IBCMiddleware struct { app api.PacketUnmarshalerModuleV2 // underlying app or middleware (must implement PacketUnmarshalerModuleV2) writeAckWrapper api.WriteAcknowledgementWrapper // writes acknowledgement for an async acknowledgement keeper types.Keeper // required for stateful middleware // Keeper may include middleware specific keeper and the ChannelKeeperV2 // additional middleware specific fields } // NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application func NewIBCMiddleware(app api.IBCModule, writeAckWrapper api.WriteAcknowledgementWrapper, k types.Keeper ) IBCMiddleware { return IBCMiddleware{ app: app, writeAckWrapper: writeAckWrapper, keeper: k, } } ``` The ICS4Wrapper has been removed in IBC v2 and there are no channel handshake callbacks, a writeAckWrapper has been added to the interface ## Implement `IBCModule` interface `IBCMiddleware` is a struct that implements the [`IBCModule` interface (`api.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11-L53). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. > Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. > Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. The `IBCModule` interface consists of the packet callbacks where cutom logic is performed. ### Packet callbacks The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware, for example acting as a filter for which tokens can be sent and recieved. #### `OnSendPacket` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnSendPacket( ctx sdk.Context, sourceClient string, destinationClient string, sequence uint64, payload channeltypesv2.Payload, signer sdk.AccAddress, ) error { // Middleware may choose to do custom preprocessing logic before calling the underlying app OnSendPacket // Middleware may return error early to reject the packet send // Middleware MUST NOT modify client identifiers and sequence doCustomPreProcessLogic() // call underlying app OnSendPacket err := im.app.OnSendPacket(ctx, sourceClient, destinationClient, sequence, payload, signer) if err != nil { return err } // may perform some post send logic and return error here return doCustomPostProcessLogic() } ``` #### `OnRecvPacket` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnRecvPacket( ctx sdk.Context, sourceClient string, destinationClient string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress, ) channeltypesv2.RecvPacketResult { // Middleware may choose to do custom preprocessing logic before calling the underlying app OnRecvPacket // Middleware may choose to error early and return a RecvPacketResult Failure // Middleware may choose to modify the payload before passing on to OnRecvPacket though this // should only be done to support very advanced custom behavior // Middleware MUST NOT modify client identifiers and sequence doCustomPreProcessLogic() // call underlying app OnRecvPacket recvResult := im.app.OnRecvPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) if recvResult.Status == channeltypesv2.PacketStatus_Async || recvResult.Status == channeltypesv2.PacketStatus_Failure { return recvResult } doCustomPostProcessLogic(recvResult) // middleware may modify recvResult return recvResult } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L161-L230) an example implementation of this callback for the Callbacks Middleware module. #### `OnAcknowledgementPacket` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnAcknowledgementPacket( ctx sdk.Context, sourceClient string, destinationClient string, sequence uint64, acknowledgement []byte, payload channeltypesv2.Payload, relayer sdk.AccAddress, ) error { // preprocessing logic may modify the acknowledgement before passing to // the underlying app though this should only be done in advanced cases // Middleware may return error early // it MUST NOT change the identifiers of the clients or the sequence doCustomPreProcessLogic(payload, acknowledgement) // call underlying app OnAcknowledgementPacket err := im.app.OnAcknowledgementPacket(ctx, sourceClient, destinationClient, sequence, acknowledgement, payload, relayer) if err != nil { return err } // may perform some post acknowledgement logic and return error here return doCustomPostProcessLogic() } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L236-L302) an example implementation of this callback for the Callbacks Middleware module. #### `OnTimeoutPacket` ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (im IBCMiddleware) OnTimeoutPacket( ctx sdk.Context, sourceClient string, destinationClient string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress, ) error { // Middleware may choose to do custom preprocessing logic before calling the underlying app OnTimeoutPacket // Middleware may return error early doCustomPreProcessLogic(payload) // call underlying app OnTimeoutPacket err := im.app.OnTimeoutPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) if err != nil { return err } // may perform some post timeout logic and return error here return doCustomPostProcessLogic() } ``` See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L309-L367) an example implementation of this callback for the Callbacks Middleware module. ### WriteAckWrapper Middleware must also wrap the `WriteAcknowledgement` interface so that any acknowledgement written by the application passes through the middleware first. This allows middleware to modify or delay writing an acknowledgment before committed to the IBC store. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // WithWriteAckWrapper sets the WriteAcknowledgementWrapper for the middleware. func (im *IBCMiddleware) WithWriteAckWrapper(writeAckWrapper api.WriteAcknowledgementWrapper) { im.writeAckWrapper = writeAckWrapper } // GetWriteAckWrapper returns the WriteAckWrapper func (im *IBCMiddleware) GetWriteAckWrapper() api.WriteAcknowledgementWrapper { return im.writeAckWrapper } ``` ### `WriteAcknowledgement` This is where the middleware acknowledgement handling is finalised. An example is shown in the [callbacks middleware](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L369-L454) ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // WriteAcknowledgement facilitates acknowledgment being written asynchronously // The call stack flows from the IBC application to the IBC core handler // Thus this function is called by the IBC app or a lower-level middleware func (im IBCMiddleware) WriteAcknowledgement( ctx sdk.Context, clientID string, sequence uint64, ack channeltypesv2.Acknowledgement, ) error { doCustomPreProcessLogic() // may modify acknowledgement return im.writeAckWrapper.WriteAcknowledgement( ctx, clientID, sequence, ack, ) } ``` ## Integrate IBC v2 Middleware Middleware should be registered within the module manager in `app.go`. The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. ### Example Integration The example integration is detailed for an IBC v2 stack using transfer and the callbacks middleware. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Middleware Stacks // initialising callbacks middleware maxCallbackGas := uint64(10_000_000) wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) // Create the transferv2 stack with transfer and callbacks middleware var ibcv2TransferStack ibcapi.IBCModule ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware(ibcv2TransferStack, app.IBCKeeper.ChannelKeeperV2, wasmStackIBCHandler, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas) // Create static IBC v2 router, add app routes, then set and seal it ibcRouterV2 := ibcapi.NewRouter() ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) app.IBCKeeper.SetRouterV2(ibcRouterV2) ``` ## Security Model IBC Middleware completely wraps all communication between IBC core and the application that it is wired with. Thus, the IBC Middleware has complete control to modify any packets and acknowledgements the underlying application receives or sends. Thus, if a chain chooses to wrap an application with a given middleware, that middleware is **completely trusted** and part of the application's security model. **Do not use middlewares that are untrusted.** ## Design Principles The middleware follows a decorator pattern that wraps an underlying application's connection to the IBC core handlers. Thus, when implementing a middleware for a specific purpose, it is recommended to be as **unintrusive** as possible in the middleware design while still accomplishing the intended behavior. The least intrusive middleware is stateless. They simply read the ICS26 callback arguments before calling the underlying app's callback and error if the arguments are not acceptable (e.g. whitelisting packets). Stateful middleware that are used solely for erroring are also very simple to build, an example of this would be a rate-limiting middleware that prevents transfer outflows from getting too high within a certain time frame. Middleware that directly interfere with the payload or acknowledgement before passing control to the underlying app are way more intrusive to the underyling app processing. This makes such middleware more error-prone when implementing as incorrect handling can cause the underlying app to break or worse execute unexpected behavior. Moreover, such middleware typically needs to be built for a specific underlying app rather than being generic. An example of this is the packet-forwarding middleware which modifies the payload and is specifically built for transfer. Middleware that modifies the payload or acknowledgement such that it is no longer readable by the underlying application is the most complicated middleware. Since it is not readable by the underlying apps, if these middleware write additional state into payloads and acknowledgements that get committed to IBC core provable state, there MUST be an equivalent counterparty middleware that is able to parse and intepret this additional state while also converting the payload and acknowledgment back to a readable form for the underlying application on its side. Thus, such middleware requires deployment on both sides of an IBC connection or the packet processing will break. This is the hardest type of middleware to implement, integrate and deploy. Thus, it is not recommended unless absolutely necessary to fulfill the given use case. # Integrating IBC middleware into a chain Source: https://docs.cosmos.network/ibc/latest/ibc/middleware/integration Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module with the module manager in `app.go`. All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. ## Example integration ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go pseudocode // middleware 1 and middleware 3 are stateful and maintain their own state. // Their keepers may accept channelKeeper for internal use (e.g. querying channels), // but do NOT store it as the ICS4Wrapper — that is wired by IBCStackBuilder. mw1Keeper := mw1.NewKeeper(storeKey1, ...) // used in stack 1 & 3 mw3Keeper1 := mw3.NewKeeper(storeKey3, ...) // used in stack 1 mw3Keeper2 := mw3.NewKeeper(storeKey3, ...) // used in stack 2 // Only create App Module **once** and register in module manager // if the module maintains independent state and/or processes sdk.Msgs app.moduleManager = module.NewManager( ... mw1.NewAppModule(mw1Keeper), mw3.NewAppModule(mw3Keeper1), mw3.NewAppModule(mw3Keeper2), transfer.NewAppModule(transferKeeper), custom.NewAppModule(customKeeper) ) // NOTE: IBC Modules may be initialized any number of times provided they use a separate // Keeper and underlying port. customKeeper1 := custom.NewKeeper(..., KeeperCustom1, ...) customKeeper2 := custom.NewKeeper(..., KeeperCustom2, ...) // initialize base IBC applications // if you want to create two different stacks with the same base application, // they must be given different Keepers and assigned different ports. transferIBCModule := transfer.NewIBCModule(transferKeeper) customIBCModule1 := custom.NewIBCModule(customKeeper1, "portCustom1") customIBCModule2 := custom.NewIBCModule(customKeeper2, "portCustom2") // create IBC stacks using IBCStackBuilder. // NewIBCStackBuilder takes the channel keeper as the top-level ICS4Wrapper. // Base() sets the bottom application; Next() adds middleware above it (bottom to top). // IBCStackBuilder wires SetUnderlyingApplication and SetICS4Wrapper automatically — // do NOT pass app or ics4Wrapper into middleware constructors directly. // NOTE: middleware2 is stateless so it does not require a Keeper. // stack 1 (top to bottom): mw1 -> mw3 -> transfer stack1 := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper). Base(transferIBCModule). Next(mw3.NewIBCMiddleware(mw3Keeper1)). Next(mw1.NewIBCMiddleware(mw1Keeper)). Build() // stack 2 (top to bottom): mw3 -> mw2 -> custom1 stack2 := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper). Base(customIBCModule1). Next(mw2.NewIBCMiddleware()). Next(mw3.NewIBCMiddleware(mw3Keeper2)). Build() // stack 3 (top to bottom): mw2 -> mw1 -> custom2 stack3 := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper). Base(customIBCModule2). Next(mw1.NewIBCMiddleware(mw1Keeper)). Next(mw2.NewIBCMiddleware()). Build() // associate each stack with the port name provided by the underlying application ibcRouter := porttypes.NewRouter() ibcRouter.AddRoute("transfer", stack1) ibcRouter.AddRoute("custom1", stack2) ibcRouter.AddRoute("custom2", stack3) app.IBCKeeper.SetRouter(ibcRouter) ``` # IBC middleware Source: https://docs.cosmos.network/ibc/latest/ibc/middleware/overview ## Synopsis Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks This documentation serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. After going through the overview they can consult respectively: * [documentation on developing custom middleware](/ibc/latest/ibc/middleware/develop) * [documentation on integrating middleware into a stack on a chain](/ibc/latest/ibc/middleware/integration) ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC Integration](/ibc/latest/ibc/integration) * [IBC Application Developer Guide](/ibc/latest/ibc/apps/apps) ## Why middleware? IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. **However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC.** Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. ## Definitions `Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. `Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. `Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. `Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. The diagram below gives an overview of a middleware stack consisting of two middleware (one stateless, the other stateful). middleware-stack.png Keep in mind that: * **The order of the middleware matters** (more on how to correctly define your stack in the code will follow in the [integration section](/ibc/latest/ibc/middleware/integration)). * Depending on the type of message, it will either be passed on from the base application up the middleware stack to core IBC or down the stack in the reverse situation (handshake and packet callbacks). * IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. **Middleware must be completely trusted by chain developers who wish to integrate them**, as this gives them complete flexibility in modifying the application(s) they wrap. # Overview Source: https://docs.cosmos.network/ibc/latest/ibc/overview ## Synopsis Learn about IBC, its components, and its use cases. ## What is the Inter-Blockchain Communication Protocol (IBC)? This document serves as a guide for developers who want to write their own Inter-Blockchain Communication Protocol (IBC) applications for custom use cases. > IBC applications must be written as self-contained modules. Due to the modular design of the IBC Protocol, IBC application developers do not need to be concerned with the low-level details of clients, connections, and proof verification. This brief explanation of the lower levels of the stack gives application developers a broad understanding of the IBC Protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. The requirements to have your module interact over IBC are: * Bind to a port or ports. * Define your packet data. * Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. * Standardize an encoding of the packet data. * Implement the `IBCModule` interface. * Implement the `UpgradableModule` interface (optional). Read on for a detailed explanation of how to write a self-contained IBC application module. ## Components overview ### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) IBC clients are on-chain light clients. Each light client is identified by a unique client ID. IBC clients track the consensus states of other blockchains, along with the proof spec necessary to properly verify proofs against the client's consensus state. A client can be associated with any number of connections to the counterparty chain. The client identifier is auto generated using the client type and the global client counter appended in the format: `{client-type}-{N}`. A `ClientState` should contain chain specific and light client specific information necessary for verifying updates and upgrades to the IBC client. The `ClientState` may contain information such as chain ID, latest height, proof specs, unbonding periods or the status of the light client. The `ClientState` should not contain information that is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` should be associated with a unique block and should be referenced using a height. IBC clients are given a client identifier prefixed store to store their associated client state and consensus states along with any metadata associated with the consensus states. Consensus states are stored using their associated height. The supported IBC clients are: * [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. * [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. * [Wasm client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/08-wasm): Proxy client useful for running light clients written in a Wasm-compilable language. * [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for testing, simulation, and relaying packets to modules on the same application. ### IBC client heights IBC Client Heights are represented by the struct: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Height struct { RevisionNumber uint64 RevisionHeight uint64 } ``` The `RevisionNumber` represents the revision of the chain that the height is representing. A revision typically represents a continuous, monotonically increasing range of block-heights. The `RevisionHeight` represents the height of the chain within the given revision. On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain, the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a block height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current revision of the chain (at revision `e`). `Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. `Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, **REGARDLESS** of the difference in revision heights. For example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} Height{ RevisionNumber: 3, RevisionHeight: 0 } > Height{ RevisionNumber: 2, RevisionHeight: 100000000000 } ``` When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number than the previous value. For example: * Before upgrade `chainID`: `gaiamainnet-3` * After upgrade `chainID`: `gaiamainnet-4` Clients that do not require revisions, such as the `06-solomachine` client, can simply hardcode `0` into the revision number whenever they need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. Other client types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and `Verify` functions respectively. The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in `02-client/types` and reproduced above. ### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) Connections encapsulate two [`ConnectionEnd`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L17) objects on two separate blockchains. Each `ConnectionEnd` is associated with a client of the other blockchain (for example, the counterparty blockchain). The connection handshake is responsible for verifying that the light clients on each chain are correct for their respective counterparties. Connections, once established, are responsible for facilitating all cross-chain verifications of IBC state. A connection can be associated with any number of channels. The connection handshake is a 4-step handshake. Briefly, if a given chain A wants to open a connection with chain B using already established light clients on both chains: 1. chain A sends a `ConnectionOpenInit` message to signal a connection initialization attempt with chain B. 2. chain B sends a `ConnectionOpenTry` message to try opening the connection on chain A. 3. chain A sends a `ConnectionOpenAck` message to mark its connection end state as open. 4. chain B sends a `ConnectionOpenConfirm` message to mark its connection end state as open. #### Time delayed connections Connections can be opened with a time delay by setting the `delay_period` field (in nanoseconds) in the [`MsgConnectionOpenInit`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/tx.proto#L45). The time delay is used to require that the underlying light clients have been updated to a certain height before commitment verification can be performed. `delayPeriod` is used in conjunction with the [`max_expected_time_per_block`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L113) parameter of the connection submodule to determine the `blockDelay`, which is number of blocks that the connection must be delayed by. When commitment verification is performed, the connection submodule will pass `delayPeriod` and `blockDelay` to the light client. It is up to the light client to determine whether the light client has been updated to the required height. Only the following light clients in `ibc-go` support time delayed connections: * `07-tendermint` * `08-wasm` (passed to the contact) ### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) In IBC, blockchains do not directly pass messages to each other over the network. Instead, to communicate, a blockchain commits some state to a specifically defined path that is reserved for a specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer process monitors for updates to these paths and relays messages by submitting the data stored under the path and a proof to the counterparty chain. Proofs are passed from core IBC to light clients as bytes. It is up to light client implementations to interpret these bytes appropriately. * The paths that all IBC implementations must use for committing IBC messages is defined in [ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). * The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/cosmos/ics23) implementation. ### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, binding a port returns a dynamic object capability. In order to take action on a particular port (for example, an open channel with its port ID), a module must provide the dynamic object capability to the IBC handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, IBC modules are responsible for claiming the capability that is returned on `BindPort`. ### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a single module. IBC packets are sent over channels. Just as IP packets contain the destination IP address and IP port, and the source IP address and source IP port, IBC packets contain the destination port ID and channel ID, and the source port ID and channel ID. This packet structure enables IBC to correctly route packets to the destination module while allowing modules receiving packets to know the sender module. A channel can be `ORDERED`, where packets from a sending module must be processed by the receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets from a sending module are processed in the order they arrive (might be in a different order than they were sent). Modules can choose which channels they wish to communicate over with, thus IBC expects modules to implement callbacks that are called during the channel handshake. These callbacks can do custom channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by returning errors on callbacks, modules can programmatically reject and accept channels. The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with chain B using an already established connection: 1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. 2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. 3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. 4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module associated with the `ChannelEnd` executes its callback. So on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. The channel identifier is auto derived in the format: `channel-{N}` where `N` is the next sequence to be used. #### Closing channels Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). Once a channel is closed, it cannot be reopened. The channel handshake steps are: **`ChanCloseInit`** closes a channel on the executing chain if * the channel exists and it is not already closed, * the connection it exists upon is `OPEN`, * the [IBC module callback `OnChanCloseInit`](/ibc/latest/ibc/apps/ibcmodule#channel-closing-callbacks) returns `nil`. `ChanCloseInit` can be initiated by any user by submitting a `MsgChannelCloseInit` transaction. Note that channels are automatically closed when a packet times out on an `ORDERED` channel. A timeout on an `ORDERED` channel skips the `ChanCloseInit` step and immediately closes the channel. **`ChanCloseConfirm`** is a response to a counterparty channel executing `ChanCloseInit`. The channel on the executing chain closes if * the channel exists and is not already closed, * the connection the channel exists upon is `OPEN`, * the executing chain successfully verifies that the counterparty channel has been closed * the [IBC module callback `OnChanCloseConfirm`](/ibc/latest/ibc/apps/ibcmodule#channel-closing-callbacks) returns `nil`. Currently, none of the IBC applications provided in ibc-go support `ChanCloseInit`. ### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) Modules communicate with each other by sending packets over IBC channels. All IBC packets contain the destination `portID` and `channelID` along with the source `portID` and `channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets contain a sequence to optionally enforce ordering. IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. Modules send custom application data to each other inside the `Data` `[]byte` field of the IBC packet. Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode their application-specific packet information into the `Data` field of packets. The receiver module must decode that `Data` back to the original application data. ### [Receipts and timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. * The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out. * The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out. If the timeout passes without the packet being successfully received, the packet can no longer be received on the destination chain. The sending module can timeout the packet and take appropriate actions. If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc). * In `ORDERED` channels, a timeout of a single packet in the channel causes the channel to close. * If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of `ORDERED` channels that packets are processed in the order that they are sent. * Since `ORDERED` channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. * In `UNORDERED` channels, the application-specific timeout logic for that packet is applied and the channel is not closed. * Packets can be received in any order. * IBC writes a packet receipt for each sequence received in the `UNORDERED` channel. This receipt does not contain information; it is simply a marker intended to signify that the `UNORDERED` channel has received a packet at the specified sequence. * To timeout a packet on an `UNORDERED` channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. For this reason, most modules should use `UNORDERED` channels as they require fewer liveness guarantees to function effectively for users of that channel. ### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: * Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. * Asynchronously if module processes packets at some later point after receiving the packet. This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. * After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS 20). * After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. ## Further readings and specs If you want to learn more about IBC, check the following specifications: * [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) # Permissioning Source: https://docs.cosmos.network/ibc/latest/ibc/permissioning IBC is designed at its base level to be a permissionless protocol. This does not mean that chains cannot add in permissioning on top of IBC. In ibc-go this can be accomplished by implementing and wiring an ante-decorator that checks if the IBC message is signed by a permissioned authority. If the signer address check passes, the tx can go through; otherwise it is rejected from the mempool. The antehandler runs before message-processing so it acts as a customizable filter that can reject messages before they get included in the block. The Cosmos SDK allows developers to write ante-decorators that can be stacked with others to add multiple independent customizable filters that run in sequence. Thus, chain developers that want to permission IBC messages are advised to implement their own custom permissioned IBC ante-decorator to add to the standard ante-decorator stack. ## Best practices `MsgCreateClient`: permissioning the client creation is the most important for permissioned IBC. This will prevent malicious relayers from creating clients to fake chains. If a chain wants to control which chains are connected to it directly over IBC, the best way to do this is by controlling which clients get created. The permissioned authority can create clients only of counterparties that the chain approves of. The permissioned authority can be the governance account, however `MsgCreateClient` contains a consensus state that can be expired by the time governance passes the proposal to execute the message. Thus, if the voting period is longer than the unbonding period of the counterparty, it is advised to use a permissioned authority that can immediately execute the transaction (e.g. a trusted multisig). `MsgConnectionOpenInit`: permissioning this message will give the chain control over the connections that are opened and also will control which connection identifier is associated with which counterparty. `MsgConnectionOpenTry`: permissioning this message through a permissioned address check is ill-advised because it will prevent relayers from easily completing the handshake that was initialized on the counterparty. However, if the chain does want strict control of exactly which connections are opened, it can permission this message. Be aware, if two chains with strict permissions try to open a connection it may take much longer than expected. `MsgChannelOpenInit`: permissioning this message will give the chain control over the channels that are opened and also will control which channel identifier is associated with which counterparty. `MsgChannelOpenTry`: permissioning this message through a permissioned address check is ill-advised because it will prevent relayers from easily completing the handshake that was initialized on the counterparty. However, if the chain does want strict control of exactly which channels are opened, it can permission this message. Be aware, if two chains with strict permissions try to open a channel it may take much longer than expected. It is not advised to permission any other message from ibc-go. Permissionless relayers should still be allowed to complete handshakes that were authorized by permissioned parties, and to relay user packets on channels that were also authorized by permissioned parties. This provides the maximum liveness provided by a permissionless relayer network with the safety guarantees provided by permissioned client, connection, and channel creation. ## Genesis setup Chains that are starting up from genesis have the option of initializing authorized clients, connections and channels from genesis. This allows chains to automatically connect to desired chains with a desired identifier. Note: The chain must be launched soon after the genesis file is created so that the client creation does not occur with an expired consensus state. The connections and channels must also simply have their `INIT` messages executed so that relayers can complete the rest of the handshake. # Relayer Source: https://docs.cosmos.network/ibc/latest/ibc/relayer ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [Events](/sdk/latest/learn/concepts/context-gas-events) ## Events Events are emitted for every transaction processed by the base application to indicate the execution of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in the IBC specification. In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, attribute key `action`, and an attribute value representing the type of message sent (`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries for transaction events, it can split message events using this event Type/Attribute Key pair. The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single message due to application callbacks. It can be assumed that any TAO logic executed will result in a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). ### Subscribing with Tendermint Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event ordering to be non-trivial, but still possible. A relayer should use the `message.action` key to extract the number of messages in the transaction and the type of IBC transactions sent. For every IBC transaction within the string array for `message.action`, the necessary information should be extracted from the other event fields. If `send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each piece of information needed to relay a packet. ## Example Implementations * [Golang Relayer](https://github.com/cosmos/relayer) * [Hermes](https://github.com/informalsystems/hermes) # IBC Client Developer Guide to Upgrades Source: https://docs.cosmos.network/ibc/latest/ibc/upgrades/developer-guide ## Synopsis Learn how to implement upgrade functionality for your custom IBC client. Please see the section [Handling upgrades](/ibc/latest/light-clients/developer-guide/upgrades) from the light client developer guide for more information. # Genesis Restart Upgrades Source: https://docs.cosmos.network/ibc/latest/ibc/upgrades/genesis-restart ## Synopsis Learn how to upgrade your chain and counterparty clients using genesis restarts. **NOTE**: Regular genesis restarts are currently unsupported by relayers! ## IBC Client Breaking Upgrades IBC client breaking upgrades are possible using genesis restarts. It is highly recommended to use the in-place migrations instead of a genesis restart. Genesis restarts should be used sparingly and as backup plans. Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. ### Step-by-Step Upgrade Process for SDK Chains If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](/ibc/latest/ibc/upgrades/quick-guide#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. 1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as `TrustingPeriod`). 2. Vote on and pass the governance proposal. 3. Halt the node after successful upgrade. 4. Export the genesis file. 5. Swap to the new binary. 6. Run migrations on the genesis file. 7. Remove the upgrade plan set by the governance proposal from the genesis file. This may be done by migrations. 8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. 9. Reset the node's data. 10. Start the chain. Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. ### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients These steps are identical to the regular [IBC client breaking upgrade process](/ibc/latest/ibc/upgrades/quick-guide#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). ## Non-IBC Client Breaking Upgrades While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. # Upgrading IBC Chains Overview Source: https://docs.cosmos.network/ibc/latest/ibc/upgrades/intro This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. 1. The [quick-guide](/ibc/latest/ibc/upgrades/quick-guide) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. 2. The [developer-guide](/ibc/latest/ibc/upgrades/developer-guide) is a guide for developers intending to develop IBC client implementations with upgrade functionality. # How to Upgrade IBC Chains and their Clients Source: https://docs.cosmos.network/ibc/latest/ibc/upgrades/quick-guide ## Synopsis Learn how to upgrade your chain and counterparty clients. The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. ## IBC Client Breaking Upgrades IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. 1. Changing the Chain-ID: **Supported** 2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. 3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. 4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store 5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. 6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. 7. Upgrading to a backwards compatible version of IBC: Supported 8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. 9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. ## Step-by-Step Upgrade Process for SDK chains If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. 1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) message which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients (chain-specified parameters) and zero out any client-customizable fields (such as `TrustingPeriod`). 2. Vote on and pass the governance proposal. Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. ## Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: 1. Wait for the upgrading chain to reach the upgrade height and halt 2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. 3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. 4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. 5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. # IBC-Go Documentation Source: https://docs.cosmos.network/ibc/latest/intro Welcome to the documentation for IBC-Go, the Golang implementation of the Inter-Blockchain Communication Protocol! Welcome to the documentation for IBC-Go, the Golang implementation of the Inter-Blockchain Communication Protocol! The Inter-Blockchain Communication Protocol (IBC) is a protocol that allows blockchains to talk to each other. Chains that speak IBC can share any type of data as long as it's encoded in bytes, enabling the industry’s most feature-rich cross-chain interactions. IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), and cross-chain account control. IBC is secure and permissionless. The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. **Notice** Since ibc-go v10, there are two versions of the protocol in the same release: IBC classic and IBC v2. The protocols are separate - a connection uses either IBC classic or IBC v2 ## Overview of IBC v2 IBC v2 is a streamlined redesign of the IBC Classic protocol. It reduces architectural complexity and expands IBC connectivity to gas-metered environments such as the EVM. The protocol is organized around three components: * **Clients** track the consensus state of a counterparty chain and serve as the primary identifier for a connection. A packet in IBC v2 references a source client and a destination client — not a channel — and is verified by proving the packet commitment against the counterparty's stored consensus state. * **Router** directs packets to the correct application module by port ID. It consolidates the routing logic that was previously spread across connections, channels, and the port router in IBC Classic into a single abstraction. * **Applications** implement the `IBCModule` interface to handle the packet lifecycle: `OnSendPacket`, `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket`. There are no channel handshake callbacks; application version and encoding are declared per-payload at send time. Packets in IBC v2 carry one or more **Payloads**. Each payload specifies the source port, destination port, application version, encoding (e.g. protobuf, JSON, or ABI), and the raw application data. A single packet can carry payloads for multiple applications simultaneously. Execution is atomic: if any payload fails, all state changes from that packet are rolled back. Notable features of IBC v2: * **Relayers** are off-chain processes that observe packet commitments on the sending chain and submit them with Merkle proofs to the receiving chain, and do the same in reverse for acknowledgements and timeouts. Relaying is permissionless by default; IBC v2 optionally allows chains to restrict relaying per client to an authorized allowlist. * **Client pairs are the connection primitive**: two chains communicate via a pair of light clients, one on each chain. Before packets can flow, each chain registers the other's client ID as its counterparty. This replaces the multi-step connection and channel handshakes of IBC Classic. * **Flexible client types**: clients are not restricted to light clients. Any verification model can be implemented — light clients, multi-sig, ZK-proof verifiers, or conditional clients. This enables cost-efficient light client verification on chains like Ethereum. * **No channel upgrades**: since application version and encoding are declared per-payload, applications can change their wire format without a channel upgrade coordination process. All application versions route through the same client connection. * **Timestamp-only timeout**: IBC v2 packets carry a single Unix timestamp timeout (in seconds). Block heights are chain-specific and don't translate across heterogeneous chains like Ethereum, so timestamps are used instead as a universal primitive. If a packet is not received before the timeout, a relayer can submit a proof of non-receipt and the sending chain times out the packet. For a detailed understanding of the protocol design, refer to the [IBC v2 specification.](/ibc/latest/spec/IBC_V2/README) For a high-level introduction, see the [IBC v2 announcement blog post.](https://ibcprotocol.dev/blog/ibc-v2-announcement) If you are interested in using IBC v2 to connect Cosmos chains and Ethereum, take a look at the [IBC Eureka](/skip-go/eureka/eureka-overview) documentation. ## High-level overview of IBC Classic The following diagram shows how IBC works at a high level: Dark Mode IBC Overview The transport layer (TAO) provides the necessary infrastructure to establish secure connections and authenticate data packets between chains. The application layer builds on top of the transport layer and defines exactly how data packets should be packaged and interpreted by the sending and receiving chains. IBC provides a reliable, permissionless, and generic base layer (allowing for the secure relaying of data packets), while allowing for composability and modularity with separation of concerns by moving application designs (interpreting and acting upon the packet data) to a higher-level layer. This separation is reflected in the categories: * **IBC/TAO** comprises the Transport, Authentication, and Ordering of packets, i.e. the infrastructure layer. * **IBC/APP** consists of the application handlers for the data packets being passed over the transport layer. These include but are not limited to fungible token transfers (ICS-20), NFT transfers (ICS-721), and interchain accounts (ICS-27). * **Application module:** groups any application, middleware or smart contract that may wrap downstream application handlers to provide enhanced functionality. Note three crucial elements in the diagram: * The chains depend on relayers to communicate. [Relayers](https://github.com/cosmos/ibc/blob/main/spec/relayer/ics-018-relayer-algorithms/README.md) are the "physical" connection layer of IBC: off-chain processes responsible for relaying data between two chains running the IBC protocol by scanning the state of each chain, constructing appropriate datagrams, and executing them on the opposite chain as is allowed by the protocol. * Many relayers can serve one or more channels to send messages between the chains. * Each side of the connection uses the light client of the other chain to quickly verify incoming messages. # Client State interface Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/client-state Learn how to implement the ClientState interface. Learn how to implement the [`ClientState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36) interface. ## `ClientType` method `ClientType` should return a unique string identifier of the light client. This will be used when generating a client identifier. The format is created as follows: `{client-type}-{N}` where `{N}` is the unique global nonce associated with a specific client (e.g `07-tendermint-0`). ## `Validate` method `Validate` should validate every client state field and should return an error if any value is invalid. The light client implementer is in charge of determining which checks are required. See the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/client_state.go#L111) as a reference. # Consensus State interface Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/consensus-state A ConsensusState is the snapshot of the counterparty chain, that an IBC client uses to verify proofs (e.g. a block). A `ConsensusState` is the snapshot of the counterparty chain, that an IBC client uses to verify proofs (e.g. a block). The further development of multiple types of IBC light clients and the difficulties presented by this generalization problem (see [ADR-006](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-006-02-client-refactor.md) for more information about this historical context) led to the design decision of each client keeping track of and set its own `ClientState` and `ConsensusState`, as well as the simplification of client `ConsensusState` updates through the generalized `ClientMessage` interface. The below [`ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L133) interface is a generalized interface for the types of information a `ConsensusState` could contain. For a reference `ConsensusState` implementation, please see the [Tendermint light client `ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/consensus_state.go). ## `ClientType` method This is the type of client consensus. It should be the same as the `ClientType` return value for the [corresponding `ClientState` implementation](/ibc/latest/light-clients/developer-guide/client-state). ## `GetTimestamp` method `GetTimestamp` should return the timestamp (in nanoseconds) of the consensus state snapshot. This function has been deprecated and will be removed in a future release. ## `ValidateBasic` method `ValidateBasic` should validate every consensus state field and should return an error if any value is invalid. The light client implementer is in charge of determining which checks are required. # Light Client Module interface Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/light-client-module Status must return the status of the client. ## `Status` method `Status` must return the status of the client. * An `Active` status indicates that clients are allowed to process packets. * A `Frozen` status indicates that misbehaviour was detected in the counterparty chain and the client is not allowed to be used. * An `Expired` status indicates that a client is not allowed to be used because it was not updated for longer than the trusting period. * An `Unknown` status indicates that there was an error in determining the status of a client. All possible `Status` types can be found [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L22-L32). This field is returned in the response of the gRPC [`ibc.core.client.v1.Query/ClientStatus`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/query.pb.go#L665) endpoint. ## `TimestampAtHeight` method `TimestampAtHeight` must return the timestamp for the consensus state associated with the provided height. This value is used to facilitate timeouts by checking the packet timeout timestamp against the returned value. ## `LatestHeight` method `LatestHeight` should return the latest block height that the client state represents. ## `Initialize` method Clients must validate the initial consensus state, and set the initial client state and consensus state in the provided client store. Clients may also store any necessary client-specific metadata. `Initialize` is called when a [client is created](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L30). ## `UpdateState` method `UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. See section [`UpdateState`](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour#updatestate) for more information. ## `UpdateStateOnMisbehaviour` method `UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. See section [`UpdateStateOnMisbehaviour`](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour#updatestateonmisbehaviour) for more information. ## `VerifyMembership` method `VerifyMembership` must verify the existence of a value at a given commitment path at the specified height. For more information about membership proofs see the [Existence and non-existence proofs section](/ibc/latest/light-clients/developer-guide/proofs). ## `VerifyNonMembership` method `VerifyNonMembership` must verify the absence of a value at a given commitment path at a specified height. For more information about non-membership proofs see the [Existence and non-existence proofs section](/ibc/latest/light-clients/developer-guide/proofs). ## `VerifyClientMessage` method `VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the ClientMessage fails to verify. See section [`VerifyClientMessage`](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour#verifyclientmessage) for more information. ## `CheckForMisbehaviour` method Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` has already been verified. See section [`CheckForMisbehaviour`](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour#checkformisbehaviour) for more information. ## `RecoverClient` method `RecoverClient` is used to recover an expired or frozen client by updating the client with the state of a substitute client. The method must verify that the provided substitute may be used to update the subject client. See section [Implementing `RecoverClient`](/ibc/latest/light-clients/proposals#implementing-recoverclient) for more information. ## `VerifyUpgradeAndUpdateState` method `VerifyUpgradeAndUpdateState` provides a path to upgrading clients given an upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. See section [Implementing `VerifyUpgradeAndUpdateState`](/ibc/latest/light-clients/developer-guide/upgrades#implementing-verifyupgradeandupdatestate) for more information. # Overview Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/overview ## Synopsis Learn how to build IBC light client modules and fulfill the interfaces required to integrate with core IBC. ## Pre-requisite readings * [IBC Overview](/ibc/latest/ibc/overview) * [IBC Transport, Authentication, and Ordering Layer - Clients](https://tutorials.cosmos.network/academy/3-ibc/4-clients.html) * [ICS-002 Client Semantics](https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics) IBC uses light clients in order to provide trust-minimized interoperability between sovereign blockchains. Light clients operate under a strict set of rules which provide security guarantees for state updates and facilitate the ability to verify the state of a remote blockchain using merkle proofs. The following aims to provide a high level IBC light client module developer guide. Access to IBC light clients are gated by the core IBC `MsgServer` which utilizes the abstractions set by the `02-client` submodule to call into a light client module. A light client module developer is only required to implement a set of interfaces as defined in the `modules/core/exported` package of ibc-go. A light client module developer should be concerned with three main interfaces: * [`LightClientModule`](#lightclientmodule) a module which manages many light client instances of a certain type. * [`ClientState`](#clientstate) encapsulates the light client implementation and its semantics. * [`ConsensusState`](#consensusstate) tracks consensus data used for verification of client updates, misbehaviour detection and proof verification of counterparty state. * [`ClientMessage`](#clientmessage) used for submitting block headers for client updates and submission of misbehaviour evidence using conflicting headers. Throughout this guide the `07-tendermint` light client module may be referred to as a reference example. ## Concepts and vocabulary ### `LightClientModule` `LightClientModule` is an interface defined by core IBC which allows for modular light client implementations. All light client implementations *must* implement the [`LightClientModule` interface](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51) so that core IBC may redirect calls to the light client module. For example a light client module may need to: * create clients * update clients * recover and upgrade clients * verify membership and non-membership The methods which make up this interface are detailed at a more granular level in the [`LightClientModule` section of this guide](/ibc/latest/light-clients/developer-guide/light-client-module). Please refer to the `07-tendermint`'s [`LightClientModule` definition](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/light-clients/07-tendermint/light_client_module.go#L17) for more information. ### `ClientState` `ClientState` is a term used to define the data structure which encapsulates opaque light client state. The `ClientState` contains all the information needed to verify a `ClientMessage` and perform membership and non-membership proof verification of counterparty state. This includes properties that refer to the remote state machine, the light client type and the specific light client instance. For example: * Constraints used for client updates. * Constraints used for misbehaviour detection. * Constraints used for state verification. * Constraints used for client upgrades. The `ClientState` type maintained within the light client module *must* implement the [`ClientState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L36) interface defined in `core/modules/exported/client.go`. The methods which make up this interface are detailed at a more granular level in the [`ClientState` section of this guide](/ibc/latest/light-clients/developer-guide/client-state). Please refer to the `07-tendermint` light client module's [`ClientState` definition](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L18) containing information such as chain ID, status, latest height, unbonding period and proof specifications. ### `ConsensusState` `ConsensusState` is a term used to define the data structure which encapsulates consensus data at a particular point in time, i.e. a unique height or sequence number of a state machine. There must exist a single trusted `ConsensusState` for each height. `ConsensusState` generally contains a trusted root, validator set information and timestamp. For example, the `ConsensusState` of the `07-tendermint` light client module defines a trusted root which is used by the `ClientState` to perform verification of membership and non-membership commitment proofs, as well as the next validator set hash used for verifying headers can be trusted in client updates. The `ConsensusState` type maintained within the light client module *must* implement the [`ConsensusState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L134) interface defined in `modules/core/exported/client.go`. The methods which make up this interface are detailed at a more granular level in the [`ConsensusState` section of this guide](/ibc/latest/light-clients/developer-guide/consensus-state). ### `Height` `Height` defines a monotonically increasing sequence number which provides ordering of consensus state data persisted through client updates. IBC light client module developers are expected to use the [concrete type](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/core/client/v1/client.proto#L89) provided by the `02-client` submodule. This implements the expectations required by the [`Height`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L156) interface defined in `modules/core/exported/client.go`. ### `ClientMessage` `ClientMessage` refers to the interface type [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) used for performing updates to a `ClientState` stored on chain. This may be any concrete type which produces a change in state to the IBC client when verified. The following are considered as valid update scenarios: * A block header which when verified inserts a new `ConsensusState` at a unique height. * A batch of block headers which when verified inserts `N` `ConsensusState` instances for `N` unique heights. * Evidence of misbehaviour provided by two conflicting block headers. Learn more in the [Handling update and misbehaviour](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour) section. # Existence/Non-Existence Proofs Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/proofs IBC uses merkle proofs in order to verify the state of a remote counterparty state machine given a trusted root, and ICS-23 is a general approach for verifying merkle trees which is used in ibc-go. IBC uses merkle proofs in order to verify the state of a remote counterparty state machine given a trusted root, and [ICS-23](https://github.com/cosmos/ics23/tree/master/go) is a general approach for verifying merkle trees which is used in ibc-go. Currently, all Cosmos SDK modules contain their own stores, which maintain the state of the application module in an IAVL (immutable AVL) binary merkle tree format. Specifically with regard to IBC, core IBC maintains its own IAVL store, and IBC apps (e.g. transfer) maintain their own dedicated stores. The Cosmos SDK multistore therefore creates a simple merkle tree of all of these IAVL trees, and from each of these individual IAVL tree root hashes it derives a root hash for the application state tree as a whole (the `AppHash`). For the purposes of ibc-go, there are two types of proofs which are important: existence and non-existence proofs, terms which have been used interchangeably with membership and non-membership proofs. For the purposes of this guide, we will stick with "existence" and "non-existence". ## Existence proofs Existence proofs are used in IBC transactions which involve verification of counterparty state for transactions which will result in the writing of provable state. For example, this includes verification of IBC store state for handshakes and packets. Put simply, existence proofs prove that a particular key and value exists in the tree. Under the hood, an IBC existence proof is comprised of two proofs: an IAVL proof that the key exists in IBC store/IBC root hash, and a proof that the IBC root hash exists in the multistore root hash. ## Non-existence proofs Non-existence proofs verify the absence of data stored within counterparty state and are used to prove that a key does NOT exist in state. As stated above, these types of proofs can be used to timeout packets by proving that the counterparty has not written a packet receipt into the store, meaning that a token transfer has NOT successfully occurred. Some trees (e.g. SMT) may have a sentinel empty child for non-existent keys. In this case, the ICS-23 proof spec should include this `EmptyChild` so that ICS-23 handles the non-existence proof correctly. In some cases, there is a necessity to "mock" non-existence proofs if the counterparty does not have ability to prove absence. Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. In these special cases, the proof provided will be an ICS-23 `Existence` proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height. ## State verification methods: `VerifyMembership` and `VerifyNonMembership` The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. From the [`LightClientModule` interface definition](https://github.com/cosmos/ibc-go/blob/main/modules/core/exported/client.go#L56), we find: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / VerifyMembership is a generic proof verification method which verifies / a proof of the existence of a value at a given CommitmentPath at the / specified height. The caller is expected to construct the full CommitmentPath / from a CommitmentPrefix and a standardized path (as defined in ICS 24). VerifyMembership( ctx sdk.Context, clientID string, height Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path Path, value []byte, ) error / VerifyNonMembership is a generic proof verification method which verifies / the absence of a given CommitmentPath at a specified height. The caller is / expected to construct the full CommitmentPath from a CommitmentPrefix and / a standardized path (as defined in ICS 24). VerifyNonMembership( ctx sdk.Context, clientStore sdk.KVStore, cdc codec.BinaryCodec, height Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path Path, ) error ``` Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS-24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the value marshalled as `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. Please refer to the [ICS-23 implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/23-commitment/types/merkle.go#L131-L205) for a concrete example. # Handling Proposals Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/proposals It is possible to update the client with the state of the substitute client through a governance proposal. This type of governance proposal is typically used to recover an expired or frozen client, as it can recover the entire state and therefore all existing channels built on top of the client. `RecoverClient` should be implemented to handle the proposal. ## Implementing `RecoverClient` In the [`LightClientModule` interface](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51), we find: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / RecoverClient must verify that the provided substitute / may be used to update the subject client. The light client / must set the updated client and consensus states within / the clientStore for the subject client. RecoverClient( ctx sdk.Context, clientID, substituteClientID string, ) error ``` Prior to updating, this function must verify that: * the substitute client is the same type as the subject client. For a reference implementation, please see the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L34). * the provided substitute may be used to update the subject client. This may mean that certain parameters must remain unaltered. For example, a [valid substitute Tendermint light client](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L86) must NOT change the chain ID, trust level, max clock drift, unbonding period, proof specs or upgrade path. Please note that `AllowUpdateAfterMisbehaviour` and `AllowUpdateAfterExpiry` have been deprecated (see ADR 026 for more information). After these checks are performed, the function must [set the updated client and consensus states](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/proposal_handle.go#L77) within the client store for the subject client. Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L79) for reference. # Setup Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/setup ## Synopsis Learn how to configure light client modules and create clients using core IBC and the `02-client` submodule. A last step to finish the development of the light client, is to implement the `AppModuleBasic` interface to allow it to be added to the chain's `app.go` alongside other light client types the chain enables. Finally, a succinct rundown is given of the remaining steps to make the light client operational, getting the light client type passed through governance and creating the clients. ## Configuring a light client module An IBC light client module must implement the [`AppModuleBasic`](https://github.com/cosmos/cosmos-sdk/blob/main/types/module/module.go#L50) interface in order to register its concrete types against the core IBC interfaces defined in `modules/core/exported`. This is accomplished via the `RegisterInterfaces` method which provides the light client module with the opportunity to register codec types using the chain's `InterfaceRegistry`. Please refer to the [`07-tendermint` codec registration](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/codec.go#L11). The `AppModuleBasic` interface may also be leveraged to install custom CLI handlers for light client module users. Light client modules can safely no-op for interface methods which it does not wish to implement. Please refer to the [core IBC documentation](/ibc/latest/ibc/integration#integrating-light-clients) for how to configure additional light client modules alongside `07-tendermint` in `app.go`. See below for an example of the `07-tendermint` implementation of `AppModuleBasic`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var _ module.AppModuleBasic = AppModuleBasic{ } / AppModuleBasic defines the basic application module used by the tendermint light client. / Only the RegisterInterfaces function needs to be implemented. All other function perform / a no-op. type AppModuleBasic struct{ } / Name returns the tendermint module name. func (AppModuleBasic) Name() string { return ModuleName } / RegisterLegacyAminoCodec performs a no-op. The Tendermint client does not support amino. func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) { } / RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC / to unmarshal tendermint light client types. func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { RegisterInterfaces(registry) } / DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client. func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { return nil } / ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client. func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { return nil } / RegisterGRPCGatewayRoutes performs a no-op. func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { } / GetTxCmd performs a no-op. Please see the 02-client cli commands. func (AppModuleBasic) GetTxCmd() *cobra.Command { return nil } / GetQueryCmd performs a no-op. Please see the 02-client cli commands. func (AppModuleBasic) GetQueryCmd() *cobra.Command { return nil } ``` ## Creating clients A client is created by executing a new `MsgCreateClient` transaction composed with a valid `ClientState` and initial `ConsensusState` encoded as protobuf `Any`s. Generally, this is performed by an off-chain process known as an [IBC relayer](https://github.com/cosmos/ibc/tree/main/spec/relayer/ics-018-relayer-algorithms) however, this is not a strict requirement. See below for a list of IBC relayer implementations: * [cosmos/relayer](https://github.com/cosmos/relayer) * [informalsystems/hermes](https://github.com/informalsystems/hermes) * [confio/ts-relayer](https://github.com/confio/ts-relayer) Stateless checks are performed within the [`ValidateBasic`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/msgs.go#L48) method of `MsgCreateClient`. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / MsgCreateClient defines a message to create an IBC client message MsgCreateClient { option (gogoproto.goproto_getters) = false; / light client state google.protobuf.Any client_state = 1 [(gogoproto.moretags) = "yaml:\"client_state\""]; / consensus state associated with the client that corresponds to a given / height. google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; / signer address string signer = 3; } ``` Leveraging protobuf `Any` encoding allows core IBC to [unpack](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/core/keeper/msg_server.go#L38) the `ClientState` into its respective interface type registered previously using the light client module's `RegisterInterfaces` method. Within the `02-client` submodule, the [`ClientState` is then initialized](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/core/02-client/keeper/client.go#L40-L42) with its own isolated key-value store, namespaced using a unique client identifier. In order to successfully create an IBC client using a new client type, it [must be supported](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L19-L25). Light client support in IBC is gated by on-chain governance. The allow list may be updated by submitting a new governance proposal to update the `02-client` parameter `AllowedClients`. See below for example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} %s tx gov submit-proposal --from ``` where `proposal.json` contains: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "IBC Clients Param Change", "summary": "Update allowed clients", "messages": [ { "@type": "/ibc.core.client.v1.MsgUpdateParams", "signer": "cosmos1...", / The gov module account address "params": { "allowed_clients": ["06-solomachine", "07-tendermint", "0x-new-client"] } } ], "metadata": "AQ==", "deposit": "100stake" } ``` If the `AllowedClients` list contains a single element that is equal to the wildcard `"*"`, then all client types are allowed and it is thus not necessary to submit a governance proposal to update the parameter. # Handling Updates and Misbehaviour Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour As mentioned before in the documentation about implementing the ConsensusState interface, ClientMessage is an interface used to update an IBC client. This update may be performed by: As mentioned before in the documentation about [implementing the `ConsensusState` interface](/ibc/latest/light-clients/developer-guide/consensus-state), [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) is an interface used to update an IBC client. This update may be performed by: * a single header, * a batch of headers, * evidence of misbehaviour, * or any type which when verified produces a change to the consensus state of the IBC client. This interface has been purposefully kept generic in order to give the maximum amount of flexibility to the light client implementer. ## Implementing the `ClientMessage` interface Find the `ClientMessage` interface in `modules/core/exported`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ClientMessage interface { proto.Message ClientType() string ValidateBasic() error } ``` The `ClientMessage` will be passed to the client to be used in [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48), which retrieves the `LightClientModule` by client type (parsed from the client ID available in `MsgUpdateClient`). This `LightClientModule` implements the [`LightClientModule` interface](/ibc/latest/light-clients/developer-guide/light-client-module) for its specific consenus type (e.g. Tendermint). `UpdateClient` will then handle a number of cases including misbehaviour and/or updating the consensus state, utilizing the specific methods defined in the relevant `LightClientModule`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg ClientMessage) error CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) bool UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) UpdateState(ctx sdk.Context, clientID string, clientMsg ClientMessage) []Height ``` ## Handling updates and misbehaviour The functions for handling updates to a light client and evidence of misbehaviour are all found in the [`LightClientModule`](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51) interface, and will be discussed below. > It is important to note that `Misbehaviour` in this particular context is referring to misbehaviour on the chain level intended to fool the light client. This will be defined by each light client. ## `VerifyClientMessage` `VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. To understand how to implement a `ClientMessage`, please refer to the [Implementing the `ClientMessage` interface](#implementing-the-clientmessage-interface) section. It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. For an example of a `VerifyClientMessage` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L23). ## `CheckForMisbehaviour` Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` has already been verified. For an example of a `CheckForMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/misbehaviour_handle.go#L22). > The Tendermint light client [defines `Misbehaviour`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/misbehaviour.go) as two different types of situations: a situation where two conflicting `Header`s with the same height have been submitted to update a client's `ConsensusState` within the same trusting period, or that the two conflicting `Header`s have been submitted at different heights but the consensus states are not in the correct monotonic time ordering (BFT time violation). More explicitly, updating to a new height must have a timestamp greater than the previous consensus state, or, if inserting a consensus at a past height, then time must be less than those heights which come after and greater than heights which come before. ## `UpdateStateOnMisbehaviour` `UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. This method should only be called when misbehaviour is detected, as it does not perform any misbehaviour checks. Notably, it should freeze the client so that calling the `Status` function on the associated client state no longer returns `Active`. For an example of a `UpdateStateOnMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L202). ## `UpdateState` `UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. It should perform a no-op on duplicate updates. It assumes the `ClientMessage` has already been verified. For an example of a `UpdateState` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L134). ## Putting it all together The `02-client` `Keeper` module in ibc-go offers a reference as to how these functions will be used to [update the client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48). ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} clientModule, found := k.router.GetRoute(clientID) if !found { return errorsmod.Wrap(types.ErrRouteNotFound, clientID) } if err := clientModule.VerifyClientMessage(ctx, clientID, clientMsg); err != nil { return err } foundMisbehaviour := clientModule.CheckForMisbehaviour(ctx, clientID, clientMsg) if foundMisbehaviour { clientModule.UpdateStateOnMisbehaviour(ctx, clientID, clientMsg) / emit misbehaviour event return } clientModule.UpdateState(ctx, clientID, clientMsg) / expects no-op on duplicate header / emit update event return ``` # Handling Upgrades Source: https://docs.cosmos.network/ibc/latest/light-clients/developer-guide/upgrades It is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. ## Implementing `VerifyUpgradeAndUpdateState` The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. This path is provided in the `VerifyUpgradeAndUpdateState` method: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last / height committed by the current revision. Clients are responsible for ensuring that the planned last / height of the current revision is somehow encoded in the proof verification process. / This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty / may be cancelled or modified before the last planned height. / If the upgrade is verified, the upgraded client and consensus states must be set in the client store. func (l LightClientModule) VerifyUpgradeAndUpdateState( ctx sdk.Context, clientID string, newClient []byte, newConsState []byte, upgradeClientProof, upgradeConsensusStateProof []byte, ) error ``` > Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/light_client_module.go#L257) as an example for implementation. It is important to note that light clients **must** handle all management of client and consensus states including the setting of updated `ClientState` and `ConsensusState` in the client store. This can include verifying that the submitted upgraded `ClientState` is of a valid `ClientState` type, that the height of the upgraded client is not greater than the height of the current client (in order to preserve BFT monotonic time), or that certain parameters which should not be changed have not been altered in the upgraded `ClientState`. Developers must ensure that the `MsgUpgradeClient` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `MsgUpgradeClient` should pass once and only once on all counterparty clients. ### Upgrade path Clients should have **prior knowledge of the merkle path** that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. > The Tendermint client implementation accomplishes this by including an `UpgradePath` in the `ClientState` itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. ## Chain specific vs client specific client parameters Developers should maintain the distinction between client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and client parameters that are customizable by each individual client (client-chosen parameters). When upgrading a client, developers must ensure that the new client adopts all of the new client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. ## Security Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a client with their desired parameters if no such client exists. However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. **We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model** since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc), while ensuring that the relayer submitting the `MsgUpgradeClient` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). ### Document potential client parameter conflicts during upgrades Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. # ClientState Source: https://docs.cosmos.network/ibc/latest/light-clients/localhost/client-state The 09-localhost client is stateless and has no types. The 09-localhost client is stateless and has no types. # Connection Source: https://docs.cosmos.network/ibc/latest/light-clients/localhost/connection The 09-localhost light client module integrates with core IBC through a single sentinel localhost connection. The sentinel ConnectionEnd is stored by default in the core IBC store. The 09-localhost light client module integrates with core IBC through a single sentinel localhost connection. The sentinel `ConnectionEnd` is stored by default in the core IBC store. This enables channel handshakes to be initiated out of the box by supplying the localhost connection identifier (`connection-localhost`) in the `connectionHops` parameter of `MsgChannelOpenInit`. The `ConnectionEnd` is created and set in store via the `InitGenesis` handler of the 03-connection submodule in core IBC. The `ConnectionEnd` and its `Counterparty` both reference the `09-localhost` client identifier, and share the localhost connection identifier `connection-localhost`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / CreateSentinelLocalhostConnection creates and sets the sentinel localhost connection end in the IBC store. func (k Keeper) CreateSentinelLocalhostConnection(ctx sdk.Context) { counterparty := types.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, commitmenttypes.NewMerklePrefix(k.GetCommitmentPrefix().Bytes())) connectionEnd := types.NewConnectionEnd(types.OPEN, exported.LocalhostClientID, counterparty, types.GetCompatibleVersions(), 0) k.SetConnection(ctx, exported.LocalhostConnectionID, connectionEnd) } ``` Note that connection handshakes are disallowed when using the `09-localhost` client type. # Integration Source: https://docs.cosmos.network/ibc/latest/light-clients/localhost/integration The 09-localhost light client module registers codec types within the core IBC module. This differs from other light client module implementations which are expected to register codec types using the AppModuleBasic interface. The 09-localhost light client module registers codec types within the core IBC module. This differs from other light client module implementations which are expected to register codec types using the `AppModuleBasic` interface. The localhost client is implicitly enabled by using the `AllowAllClients` wildcard (`"*"`) in the 02-client submodule default value for param [`allowed_clients`](https://github.com/cosmos/ibc-go/blob/v7.0.0/proto/ibc/core/client/v1/client.proto#L102). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / DefaultAllowedClients are the default clients for the AllowedClients parameter. / By default it allows all client types. var DefaultAllowedClients = []string{ AllowAllClients } ``` # Overview Source: https://docs.cosmos.network/ibc/latest/light-clients/localhost/overview ## Overview ## Synopsis Learn about the 09-localhost light client module. The 09-localhost light client module implements a stateless localhost loopback client with the ability to send and receive IBC packets to and from the same state machine. ### Context In a multichain environment, application developers will be used to developing cross-chain applications through IBC. From their point of view, whether or not they are interacting with multiple modules on the same chain or on different chains should not matter. The localhost client module enables a unified interface to interact with different applications on a single chain, using the familiar IBC application layer semantics. ### Implementation There exists a localhost light client module which can be invoked with the client identifier `09-localhost`. The light client is stateless, so the `ClientState` is constructed on demand when required. To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](/ibc/latest/light-clients/localhost/connection) state with the connection identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel connection which leverage the 09-localhost loopback functionality. [State verification](/ibc/latest/light-clients/localhost/state-verification) for channel state in handshakes or processing packets is reduced in complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths. ### Localhost vs *regular* client The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself). The table below lists some important differences: | | Regular client | Localhost | | -------------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost` | | Client creation | Relayer (permissionless) | Implicitly made available by the 02-client submodule in core IBC | | Client updates | Relayer submits headers using `MsgUpdateClient` | No client updates are required as the localhost implementation is stateless | | Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | | Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | | Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | | `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | | `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` | # State Verification Source: https://docs.cosmos.network/ibc/latest/light-clients/localhost/state-verification The localhost client handles state verification through the LightClientModule interface methods VerifyMembership and VerifyNonMembership by performing read-only operations directly on the core IBC store. The localhost client handles state verification through the `LightClientModule` interface methods `VerifyMembership` and `VerifyNonMembership` by performing read-only operations directly on the core IBC store. When verifying channel state in handshakes or processing packets the `09-localhost` client can simply compare bytes stored under the standardized key paths defined by [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). For existence proofs via `VerifyMembership` the 09-localhost client will retrieve the value stored under the provided key path and compare it against the value provided by the caller. In contrast, non-existence proofs via `VerifyNonMembership` assert the absence of a value at the provided key path. Relayers are expected to provide a sentinel proof when sending IBC messages. Submission of nil or empty proofs is disallowed in core IBC messaging. The 09-localhost light client module defines a `SentinelProof` as a single byte. Localhost client state verification will fail if the sentinel proof value is not provided. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var SentinelProof = []byte{0x01 } ``` The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`. # Governance Proposals Source: https://docs.cosmos.network/ibc/latest/light-clients/proposals In uncommon situations, a highly valued client may become frozen or expire due to uncontrollable circumstances. A highly valued client might have hundreds of channels being actively used. Some of those channels might have a significant amount of locked tokens used for ICS 20. ## Frozen Light Clients If the one third of the validator set of the chain the client represents decides to collude, they can sign off on two valid but conflicting headers each signed by the other one third of the honest validator set. The light client can now be updated with two valid, but conflicting headers at the same height. The light client cannot know which header is trustworthy and therefore evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. Frozen light clients cannot be updated under any circumstance except via a governance proposal. Since a quorum of validators can sign arbitrary state roots which may not be valid executions of the state machine, a governance proposal has been added to ease the complexity of unfreezing or updating clients which have become "stuck". Without this mechanism, validator sets would need to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels built upon that client. This may result in recovery of otherwise lost funds. ## Expired Light Clients Tendermint light clients may become expired if the trusting period has passed since their last update. This may occur if relayers stop submitting headers to update the clients. An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator set before the chain ID changes. In this situation, the validator set of the last valid update for the light client is never expected to produce another valid header since the chain ID has changed, which will ultimately lead the on-chain light client to become expired. # How to recover an expired client with a governance proposal > **Who is this information for?** > Although technically anyone can submit the governance proposal to recover an expired client, often it will be **relayer operators** (at least coordinating the submission). In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a governance proposal may be submitted to update this client, known as the subject client. The proposal includes the client identifier for the subject and the client identifier for a substitute client. Light client implementations may implement custom updating logic, but in most cases, the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry once the proposal passes. See also the relevant documentation: ADR-026, IBC client recovery mechanisms ## Preconditions * There exists an active client (with a known client identifier) for the same counterparty chain as the expired client. * The governance deposit. ## Steps ### Step 1 Check if the client is attached to the expected `chain_id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: ```text theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { client_id: 07-tendermint-146 client_state: '@type': /ibc.lightclients.tendermint.v1.ClientState allow_update_after_expiry: true allow_update_after_misbehaviour: true chain_id: akashnet-2 } ``` The client is attached to the expected Akash `chain_id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. ### Step 2 Anyone can submit the governance proposal to recover the client by executing the following via CLI. If the chain is on an ibc-go version older than v8, please see the [relevant documentation](/ibc/v7.8.x/ibc/proposals). * From ibc-go v8 onwards ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} tx gov submit-proposal [path-to-proposal-json] ``` where `proposal.json` contains: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "messages": [ { "@type": "/ibc.core.client.v1.MsgRecoverClient", "subject_client_id": "", "substitute_client_id": "", "signer": "" } ], "metadata": "", "deposit": "10stake" "title": "My proposal", "summary": "A short summary of my proposal", "expedited": false } ``` The `` identifier is the proposed client to be updated. This client must be either frozen or expired. The `` represents a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain ID). It should be continually updated during the voting period. After this, all that remains is deciding who funds the governance deposit and ensuring the governance proposal passes. If it does, the client on trial will be updated to the latest state of the substitute. ## Important considerations Please note that if the counterparty client is also expired, that client will also need to update. This process updates only one client. # Concepts Source: https://docs.cosmos.network/ibc/latest/light-clients/solomachine/concepts The ClientState for a solo machine light client stores the latest sequence, the frozen sequence, the latest consensus state, and client flag indicating if the client should be allowed to be updated after a governance proposal. ## Client State The `ClientState` for a solo machine light client stores the latest sequence, the frozen sequence, the latest consensus state, and client flag indicating if the client should be allowed to be updated after a governance proposal. If the client is not frozen then the frozen sequence is 0. ## Consensus State The consensus states stores the public key, diversifier, and timestamp of the solo machine light client. The diversifier is used to prevent accidental misbehaviour if the same public key is used across different chains with the same client identifier. It should be unique to the chain the light client is used on. ## Public Key The public key can be a single public key or a multi-signature public key. The public key type used must fulfill the tendermint public key interface (this will become the SDK public key interface in the near future). The public key must be registered on the application codec otherwise encoding/decoding errors will arise. The public key stored in the consensus state is represented as a protobuf `Any`. This allows for flexibility in what other public key types can be supported in the future. ## Counterparty Verification The solo machine light client can verify counterparty client state, consensus state, connection state, channel state, packet commitments, packet acknowledgements, packet receipt absence, and the next sequence receive. At the end of each successful verification call the light client sequence number will be incremented. Successful verification requires the current public key to sign over the proof. ## Proofs A solo machine proof should verify that the solomachine public key signed over some specified data. The format for generating marshaled proofs for the SDK's implementation of solo machine is as follows: 1. Construct the data using the associated protobuf definition and marshal it. For example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} data := &ClientStateData{ Path: []byte(path.String()), ClientState: protoAny, } dataBz, err := cdc.Marshal(data) ``` The helper functions `...DataBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this functionality. 2. Construct the `SignBytes` and marshal it. For example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} signBytes := &SignBytes{ Sequence: sequence, Timestamp: timestamp, Diversifier: diversifier, DataType: CLIENT, Data: dataBz, } signBz, err := cdc.Marshal(signBytes) ``` The helper functions `...SignBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this functionality. The `DataType` field is used to disambiguate what type of data was signed to prevent potential proto encoding overlap. 3. Sign the sign bytes. Embed the signatures into either `SingleSignatureData` or `MultiSignatureData`. Convert the `SignatureData` to proto and marshal it. For example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sig, err := key.Sign(signBz) sigData := &signing.SingleSignatureData{ Signature: sig, } protoSigData := signing.SignatureDataToProto(sigData) bz, err := cdc.Marshal(protoSigData) ``` 4. Construct a `TimestampedSignatureData` and marshal it. The marshaled result can be passed in as the proof parameter to the verification functions. For example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} timestampedSignatureData := &solomachine.TimestampedSignatureData{ SignatureData: sigData, Timestamp: solomachine.Time, } proof, err := cdc.Marshal(timestampedSignatureData) ``` NOTE: At the end of this process, the sequence associated with the key needs to be updated. The sequence must be incremented each time proof is generated. ## Updates By Header An update by a header will only succeed if: * the header provided is parseable to solo machine header * the header sequence matches the current sequence * the header timestamp is greater than or equal to the consensus state timestamp * the currently registered public key generated the proof If the update is successful: * the public key is updated * the diversifier is updated * the timestamp is updated * the sequence is incremented by 1 * the new consensus state is set in the client state ## Updates By Proposal An update by a governance proposal will only succeed if: * the substitute provided is parseable to solo machine client state * the new consensus state public key does not equal the current consensus state public key If the update is successful: * the subject client state is updated to the substitute client state * the subject consensus state is updated to the substitute consensus state * the client is unfrozen (if it was previously frozen) NOTE: Previously, `AllowUpdateAfterProposal` was used to signal the update/recovery options for the solo machine client. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of this parameter. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same. ## Misbehaviour Misbehaviour handling will only succeed if: * the misbehaviour provided is parseable to solo machine misbehaviour * the client is not already frozen * the current public key signed over two unique data messages at the same sequence and diversifier. If the misbehaviour is successfully processed: * the client is frozen by setting the frozen sequence to the misbehaviour sequence NOTE: Misbehaviour processing is data processing order dependent. A misbehaving solo machine could update to a new public key to prevent being frozen before misbehaviour is submitted. ## Upgrades Upgrades to solo machine light clients are not supported since an entirely different type of public key can be set using normal client updates. # Solomachine Source: https://docs.cosmos.network/ibc/latest/light-clients/solomachine/solomachine This paper defines the implementation of the ICS06 protocol on the Cosmos SDK. For the general specification please refer to the ICS06 Specification. ## Abstract This paper defines the implementation of the ICS06 protocol on the Cosmos SDK. For the general specification please refer to the [ICS06 Specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-006-solo-machine-client). This implementation of a solo machine light client supports single and multi-signature public keys. The client is capable of handling public key updates by header and governance proposals. The light client is capable of processing client misbehaviour. Proofs of the counterparty state are generated by the solo machine client by signing over the desired state with a certain sequence, diversifier, and timestamp. ## Contents 1. **[Concepts](/ibc/latest/light-clients/solomachine/concepts)** 2. **[State](/ibc/latest/light-clients/solomachine/state)** 3. **[State Transitions](/ibc/latest/light-clients/solomachine/state_transitions)** # State Source: https://docs.cosmos.network/ibc/latest/light-clients/solomachine/state The solo machine light client will only store consensus states for each update by a header or a governance proposal. The latest client state is also maintained in the store. The solo machine light client will only store consensus states for each update by a header or a governance proposal. The latest client state is also maintained in the store. # State Transitions Source: https://docs.cosmos.network/ibc/latest/light-clients/solomachine/state_transitions Successful state verification by a solo machine light client will result in: ## Client State Verification Functions Successful state verification by a solo machine light client will result in: * the sequence being incremented by 1. ## Update By Header A successful update of a solo machine light client by a header will result in: * the public key being updated to the new public key provided by the header. * the diversifier being updated to the new diviersifier provided by the header. * the timestamp being updated to the new timestamp provided by the header. * the sequence being incremented by 1 * the consensus state being updated (consensus state stores the public key, diversifier, and timestamp) ## Update By Governance Proposal A successful update of a solo machine light client by a governance proposal will result in: * the client state being updated to the substitute client state * the consensus state being updated to the substitute consensus state (consensus state stores the public key, diversifier, and timestamp) * the frozen sequence being set to zero (client is unfrozen if it was previously frozen). ## Upgrade Client udgrades are not supported for the solo machine light client. No state transition occurs. ## Misbehaviour Successful misbehaviour processing of a solo machine light client will result in: * the frozen sequence being set to the sequence the misbehaviour occurred at # Overview Source: https://docs.cosmos.network/ibc/latest/light-clients/tendermint/overview ## Overview ## Synopsis Learn about the 07-tendermint light client module. The Tendermint client is the first and most deployed light client in IBC. It implements the IBC [light client module interface](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/modules/core/exported/client.go#L41-L123) to track a counterparty running [CometBFT](https://github.com/cometbft/cometbft) consensus. Tendermint is the old name of CometBFT which has been retained in IBC to avoid expensive migration costs. The Tendermint client consists of two important structs that keep track of the state of the counterparty chain and allow for future updates. The `ClientState` struct contains all the parameters necessary for CometBFT header verification. The `ConsensusState`, on the other hand, is a compressed view of a particular header of the counterparty chain. Unlike off chain light clients, IBC does not store full header. Instead it stores only the information it needs to prove verification of key/value pairs in the counterparty state (i.e. the header `AppHash`), and the information necessary to use the consensus state as the next root of trust to add a new consensus state to the client (i.e. the header `NextValidatorsHash` and `Timestamp`). The relayer provides the full trusted header on `UpdateClient`, which will get checked against the compressed root-of-trust consensus state. If the trusted header matches a previous consensus state, and the trusted header and new header pass the CometBFT light client update algorithm, then the new header is compressed into a consensus state and added to the IBC client. Each Tendermint Client is composed of a single `ClientState` keyed on the client ID, and multiple consensus states which are keyed on both the clientID and header height. Relayers can use the consensus states to verify merkle proofs of packet commitments, acknowledgements, and receipts against the `AppHash` of the counterparty chain in order to enable verified packet flow. If a counterparty chain violates the CometBFT protocol in a way that is detectable to off-chain light clients, this misbehaviour can also be submitted to an IBC client by any off-chain actor. Upon verification of this misbehaviour, the Tendermint IBC Client will freeze, preventing any further packet flow from this malicious chain from occurring. Governance or some other out-of-band protocol may then be used to unwind any damage that has already occurred. ## Initialization The Tendermint light client is initialized with a `ClientState` that contains parameters necessary for CometBFT header verification along with a latest height and `ConsensusState` that encapsulates the application state root of a trusted header that will serve to verify future incoming headers from the counterparty. ```proto expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message ClientState { / human readable chain-id that will be included in header / and signed over by the validator set string chain_id = 1; / trust level is the fraction of the trusted validator set / that must sign over a new untrusted header before it is accepted / it can be a minimum of 1/3 and a maximum of 2/3 / Note these are the bounds of liveness. 1/3 is the minimum / honest stake needed to maintain liveness on a chain, / requiring more than 2/3 to sign over the new header would / break the BFT threshold of allowing 1/3 malicious validators Fraction trust_level = 2; / duration of the period since the LatestTimestamp during which the / submitted headers are valid for update google.protobuf.Duration trusting_period = 3; / duration of the staking unbonding period google.protobuf.Duration unbonding_period = 4; / defines how much new (untrusted) header's Time can drift / into the future relative to our local clock. google.protobuf.Duration max_clock_drift = 5; / Block height when the client was frozen due to a misbehaviour ibc.core.client.v1.Height frozen_height = 6; / Latest height the client was updated to ibc.core.client.v1.Height latest_height = 7; / Proof specifications used in verifying counterparty state repeated cosmos.ics23.v1.ProofSpec proof_specs = 8; / Path at which next upgraded client will be committed. / Each element corresponds to the key for a single CommitmentProof in the / chained proof. NOTE: ClientState must stored under / `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored / under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using / the default upgrade module, upgrade_path should be []string{"upgrade", / "upgradedIBCState"}` repeated string upgrade_path = 9; } ``` ```proto expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message ConsensusState { / timestamp that corresponds to the block height in which the ConsensusState / was stored. google.protobuf.Timestamp timestamp = 1; / commitment root (i.e app hash) that will be used / to verify proofs of packet flow messages ibc.core.commitment.v1.MerkleRoot root = 2; / hash of the next validator set that will be used as / a new updated source of trust to verify future updates bytes next_validators_hash = 3; } ``` ## Updates Once the initial client state and consensus state are submitted, future consensus states can be added to the client by submitting IBC [headers](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L76-L94). These headers contain all necessary information to run the CometBFT light client protocol. ```proto expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Header { / this is the new signed header that we want to add / as a new consensus state to the ibc client. / the signed header contains the commit signatures of the `validator_set` below .tendermint.types.SignedHeader signed_header = 1; / the validator set which signed the new header .tendermint.types.ValidatorSet validator_set = 2; / the trusted height of the consensus state which we are updating from ibc.core.client.v1.Height trusted_height = 3; / the trusted validator set, the hash of the trusted validators must be equal to / `next_validators_hash` of the current consensus state .tendermint.types.ValidatorSet trusted_validators = 4; } ``` For detailed information on the CometBFT light client protocol and its safety properties please refer to the [original Tendermint whitepaper](https://arxiv.org/abs/1807.04938). ## Proofs As consensus states are added to the client, they can be used for proof verification by relayers wishing to prove packet flow messages against a particular height on the counterparty. This uses the `VerifyMembership` and `VerifyNonMembership` methods on the Tendermint client. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / VerifyMembership is a generic proof verification method /which verifies a proof of the existence of a value at a / given CommitmentPath at the specified height. The caller / is expected to construct the full CommitmentPath from a / CommitmentPrefix and a standardized path (as defined in ICS 24). VerifyMembership( ctx sdk.Context, clientID string, height Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path Path, value []byte, ) error / VerifyNonMembership is a generic proof verification method / which verifies the absence of a given CommitmentPath at a / specified height. The caller is expected to construct the / full CommitmentPath from a CommitmentPrefix and a standardized / path (as defined in ICS 24). VerifyNonMembership( ctx sdk.Context, clientID string, height Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path Path, ) error ``` The Tendermint client is initialized with an ICS23 proof spec. This allows the Tendermint implementation to support many different merkle tree structures so long as they can be represented in an [`ics23.ProofSpec`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L145-L170). ## Misbehaviour The Tendermint light client directly tracks consensus of a CometBFT counterparty chain. So long as the counterparty is Byzantine Fault Tolerant, that is to say, the malicious subset of the bonded validators does not exceed the trust level of the client, then the client is secure. In case the malicious subset of the validators exceeds the trust level of the client, then the client can be deceived into accepting invalid blocks and the connection is no longer secure. The Tendermint client has some mitigations in place to prevent this. If there are two valid blocks signed by the counterparty validator set at the same height \[e.g. a valid block signed by an honest subset and an invalid block signed by a malicious one], then these conflicting headers can be submitted to the client as [misbehaviour](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L65-L74). The client will verify the headers and freeze the client; preventing any future updates and proof verification from succeeding. This effectively halts communication with the compromised counterparty while out-of-band social consensus can unwind any damage done. Similarly, if the timestamps of the headers are not monotonically increasing, this can also be evidence of malicious behaviour and cause the client to freeze. Thus, any consensus faults that are detectable by a light client are part of the misbehaviour protocol and can be used to minimize the damage caused by a compromised counterparty chain. ### Security model It is important to note that IBC is not a completely trustless protocol; it is **trust-minimized**. This means that the safety property of bilateral IBC communication between two chains is dependent on the safety properties of the two chains in question. If one of the chains is compromised completely, then the IBC connection to the other chain is liable to receive invalid packets from the malicious chain. For example, if a malicious validator set has taken over more than 2/3 of the validator power on a chain; that malicious validator set can create a single chain of blocks with arbitrary commitment roots and arbitrary commitments to the next validator set. This would seize complete control of the chain and prevent the honest subset from even being able to create a competing honest block. In this case, there is no ability for the IBC Tendermint client solely tracking CometBFT consensus to detect the misbehaviour and freeze the client. The IBC protocol would require out-of-band mechanisms to detect and fix such an egregious safety fault on the counterparty chain. Since the Tendermint light client is only tracking consensus and not also verifying the validity of state transitions, malicious behaviour from a validator set that is beyond the BFT fault threshold is an accepted risk of this light client implementation. The IBC protocol has principles of fault isolation (e.g. all tokens are prefixed by their channel, so tokens from different chains are not mutually fungible) and fault mitigation (e.g. ability to freeze the client if misbehaviour can be detected before complete malicious takeover) that make this risk as minimal as possible. # Client Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/client A user can query and interact with the 08-wasm module using the CLI. Use the --help flag to discover the available commands: ## CLI A user can query and interact with the `08-wasm` module using the CLI. Use the `--help` flag to discover the available commands: ### Transactions The `tx` commands allow users to interact with the `08-wasm` submodule. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx ibc-wasm --help ``` #### `store-code` The `store-code` command allows users to submit a governance proposal with a `MsgStoreCode` to store the byte code of a Wasm light client contract. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx ibc-wasm store-code [path/to/wasm-file] [flags] ``` `path/to/wasm-file` is the path to the `.wasm` or `.wasm.gz` file. #### `migrate-contract` The `migrate-contract` command allows users to broadcast a transaction with a `MsgMigrateContract` to migrate the contract for a given light client to a new byte code denoted by the given checksum. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx ibc-wasm migrate-contract [client-id] [checksum] [migrate-msg] ``` The migrate message must not be emptied and is expected to be a JSON-encoded string. ### Query The `query` commands allow users to query `08-wasm` state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-wasm --help ``` #### `checksums` The `checksums` command allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. The checksums are hex-encoded. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-wasm checksums [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-wasm checksums ``` Example Output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} checksums: - c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 pagination: next_key: null total: "1" ``` #### `code` The `code` command allows users to query the Wasm byte code of a light client contract given the provided input checksum. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ./simd q ibc-wasm code ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-wasm code c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 ``` Example Output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} code: AGFzb...AqBBE= ``` ## gRPC A user can query the `08-wasm` module using gRPC endpoints. ### `Checksums` The `Checksums` endpoint allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibc.lightclients.wasm.v1.Query/Checksums ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{}' \ localhost:9090 \ ibc.lightclients.wasm.v1.Query/Checksums ``` Example output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "checksums": [ "c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64" ], "pagination": { "total": "1" } } ``` ### `Code` The `Code` endpoint allows users to query the Wasm byte code of a light client contract given the provided input checksum. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibc.lightclients.wasm.v1.Query/Code ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"checksum":"c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64"}' \ localhost:9090 \ ibc.lightclients.wasm.v1.Query/Code ``` Example output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "code": AGFzb...AqBBE= } ``` # Concepts Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/concepts Learn about the differences between a proxy light client and a Wasm light client. Learn about the differences between a proxy light client and a Wasm light client. ## Proxy light client The `08-wasm` module is not a regular light client in the same sense as, for example, the 07-tendermint light client. `08-wasm` is instead a *proxy* light client module, and this means that the module acts a proxy to the actual implementations of light clients. The module will act as a wrapper for the actual light clients uploaded as Wasm byte code and will delegate all operations to them (i.e. `08-wasm` just passes through the requests to the Wasm light clients). Still, the `08-wasm` module implements all the required interfaces necessary to integrate with core IBC, so that 02-client can call into it as it would for any other light client module. These interfaces are `LightClientModule`, `ClientState`, `ConsensusState` and `ClientMessage`, and we will describe them in the context of `08-wasm` in the following sections. For more information about this set of interfaces, please read section [Overview of the light client module developer guide](/ibc/latest/light-clients/developer-guide/overview#overview). ### `LightClientModule` The `08-wasm`'s `LightClientModule` data structure contains two fields: * `keeper` is the `08-wasm` module keeper. * `storeProvider` encapsulates the IBC core store key and provides access to isolated prefix stores for each client so they can read/write in separate namespaces. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type LightClientModule struct { keeper wasmkeeper.Keeper storeProvider exported.ClientStoreProvider } ``` See section [`LightClientModule` of the light client module developer guide](/ibc/latest/light-clients/developer-guide/overview#lightclientmodule) for more information about the `LightClientModule` interface. ### `ClientState` The `08-wasm`'s `ClientState` data structure contains three fields: * `Data` contains the bytes of the Protobuf-encoded client state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA client state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L35-L60). * `Checksum` is the sha256 hash of the Wasm contract's byte code. This hash is used as an identifier to call the right contract. * `LatestHeight` is the latest height of the counterparty state machine (i.e. the height of the blockchain), whose consensus state the light client tracks. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ClientState struct { / bytes encoding the client state of the underlying / light client implemented as a Wasm contract Data []byte / sha256 hash of Wasm contract byte code Checksum []byte / latest height of the counterparty ledger LatestHeight types.Height } ``` See section [`ClientState` of the light client module developer guide](/ibc/latest/light-clients/developer-guide/overview#clientstate) for more information about the `ClientState` interface. ### `ConsensusState` The `08-wasm`'s `ConsensusState` data structure maintains one field: * `Data` contains the bytes of the Protobuf-encoded consensus state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA consensus state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L87-L94). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ConsensusState struct { / bytes encoding the consensus state of the underlying light client / implemented as a Wasm contract. Data []byte } ``` See section [`ConsensusState` of the light client module developer guide](/ibc/latest/light-clients/developer-guide/overview#consensusstate) for more information about the `ConsensusState` interface. ### `ClientMessage` `ClientMessage` is used for performing updates to a `ClientState` stored on chain. The `08-wasm`'s `ClientMessage` data structure maintains one field: * `Data` contains the bytes of the Protobuf-encoded header(s) or misbehaviour for the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes of either [header](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L96-L104) or [misbehaviour](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L106-L112) for a GRANDPA light client. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ClientMessage struct { / bytes encoding the header(s) or misbehaviour for the underlying light client / implemented as a Wasm contract. Data []byte } ``` See section [`ClientMessage` of the light client module developer guide](/ibc/latest/light-clients/developer-guide/overview#clientmessage) for more information about the `ClientMessage` interface. ## Wasm light client The actual light client can be implemented in any language that compiles to Wasm and implements the interfaces of a [CosmWasm](https://docs.cosmwasm.com/docs/) contract. Even though in theory other languages could be used, in practice (at least for the time being) the most suitable language to use would be Rust, since there is already good support for it for developing CosmWasm smart contracts. At the moment of writing there are two contracts available: one for [Tendermint](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics07-tendermint-cw) and one [GRANDPA](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics10-grandpa-cw) (which is being used in production in [Composable Finance's Centauri bridge](https://github.com/ComposableFi/composable-ibc)). And there are others in development (e.g. for Near). # Contracts Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/contracts Learn about the expected behaviour of Wasm light client contracts and the between with 08-wasm. Learn about the expected behaviour of Wasm light client contracts and the between with `08-wasm`. ## API The `08-wasm` light client proxy performs calls to the Wasm light client via the Wasm VM. The calls require as input JSON-encoded payload messages that fall in the three categories described in the next sections. ## `InstantiateMessage` This is the message sent to the contract's `instantiate` entry point. It contains the bytes of the protobuf-encoded client and consensus states of the underlying light client, both provided in [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L40-L52). Please note that the bytes contained within the JSON message are represented as base64-encoded strings. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type InstantiateMessage struct { ClientState []byte `json:"client_state"` ConsensusState []byte `json:"consensus_state"` Checksum []byte `json:"checksum" } ``` The Wasm light client contract is expected to store the client and consensus state in the corresponding keys of the client-prefixed store. ## `QueryMsg` `QueryMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `query` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type QueryMsg struct { Status *StatusMsg `json:"status,omitempty"` TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` } ``` ```rust theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} #[cw_serde] pub enum QueryMsg { Status(StatusMsg), TimestampAtHeight(TimestampAtHeightMsg), VerifyClientMessage(VerifyClientMessageRaw), CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), } ``` To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](/ibc/latest/light-clients/developer-guide/overview): * For `StatusMsg`, see the section [`Status` method](/ibc/latest/light-clients/developer-guide/client-state#status-method). * For `TimestampAtHeightMsg`, see the section [`GetTimestampAtHeight` method](/ibc/latest/light-clients/developer-guide/client-state#gettimestampatheight-method). * For `VerifyClientMessageMsg`, see the section [`VerifyClientMessage`](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour#verifyclientmessage). * For `CheckForMisbehaviourMsg`, see the section [`CheckForMisbehaviour` method](/ibc/latest/light-clients/developer-guide/client-state#checkformisbehaviour-method). ## `SudoMsg` `SudoMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `sudo` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. The `sudo` entry point is able to perform state-changing writes in the client-prefixed store. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type SudoMsg struct { UpdateState *UpdateStateMsg `json:"update_state,omitempty"` UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` } ``` ```rust theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} #[cw_serde] pub enum SudoMsg { UpdateState(UpdateStateMsgRaw), UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), VerifyMembership(VerifyMembershipMsgRaw), VerifyNonMembership(VerifyNonMembershipMsgRaw), MigrateClientStore(MigrateClientStoreMsgRaw), } ``` To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](/ibc/latest/light-clients/developer-guide/overview): * For `UpdateStateMsg`, see the section [`UpdateState`](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour#updatestate). * For `UpdateStateOnMisbehaviourMsg`, see the section [`UpdateStateOnMisbehaviour`](/ibc/latest/light-clients/developer-guide/updates-and-misbehaviour#updatestateonmisbehaviour). * For `VerifyUpgradeAndUpdateStateMsg`, see the section [`GetTimestampAtHeight` method](/ibc/latest/light-clients/developer-guide/upgrades#implementing-verifyupgradeandupdatestate). * For `VerifyMembershipMsg`, see the section [`VerifyMembership` method](/ibc/latest/light-clients/developer-guide/client-state#verifymembership-method). * For `VerifyNonMembershipMsg`, see the section [`VerifyNonMembership` method](/ibc/latest/light-clients/developer-guide/client-state#verifynonmembership-method). * For `MigrateClientStoreMsg`, see the section [Implementing `CheckSubstituteAndUpdateState`](/ibc/latest/light-clients/developer-guide/proposals#implementing-checksubstituteandupdatestate). ### Migration The `08-wasm` proxy light client exposes the `MigrateContract` RPC endpoint that can be used to migrate a given Wasm light client contract (specified by the client identifier) to a new Wasm byte code (specified by the hash of the byte code). The expected use case for this RPC endpoint is to enable contracts to migrate to new byte code in case the current byte code is found to have a bug or vulnerability. The Wasm byte code that contracts are migrated have to be uploaded beforehand using `MsgStoreCode` and must implement the `migrate` entry point. See section[`MsgMigrateContract`](/ibc/latest/apps/interchain-accounts/messages#msgmigratecontract) for information about the request message for this RPC endpoint. ## Expected behaviour The `08-wasm` proxy light client modules expects the following behaviour from the Wasm light client contracts when executing messages that perform state-changing writes: * The contract must not delete the client state from the store. * The contract must not change the client state to a client state of another type. * The contract must not change the checksum in the client state. Any violation of these rules will result in an error returned from `08-wasm` that will abort the transaction. # Events Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/events The 08-wasm module emits the following events: The `08-wasm` module emits the following events: ## `MsgStoreCode` | Type | Attribute Key | Attribute Value | | ----------------- | -------------- | ------------------------ | | store\_wasm\_code | wasm\_checksum | `{hex.Encode(checksum)}` | | message | module | 08-wasm | ## `MsgMigrateContract` | Type | Attribute Key | Attribute Value | | ----------------- | -------------- | --------------------------- | | migrate\_contract | client\_id | `{clientId}` | | migrate\_contract | wasm\_checksum | `{hex.Encode(checksum)}` | | migrate\_contract | new\_checksum | `{hex.Encode(newChecksum)}` | | message | module | 08-wasm | # Governance Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/governance Learn how to upload Wasm light client byte code on a chain, and how to migrate an existing Wasm light client contract. Learn how to upload Wasm light client byte code on a chain, and how to migrate an existing Wasm light client contract. ## Setting an authority Both the storage of Wasm light client byte code as well as the migration of an existing Wasm light client contract are permissioned (i.e. only allowed to an authority such as governance). The designated authority is specified when instantiating `08-wasm`'s keeper: both [`NewKeeperWithVM`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47) and [`NewKeeperWithConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) constructor functions accept an `authority` argument that must be the address of the authorized actor. For example, in `app.go`, when instantiating the keeper, you can pass the address of the governance module: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go import ( ... "github.com/cosmos/cosmos-sdk/runtime" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/keeper" ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/types" ... ) // app.go app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority wasmVM, app.GRPCQueryRouter(), ) ``` ## Storing new Wasm light client byte code If governance is the allowed authority, the governance v1 proposal that needs to be submitted to upload a new light client contract should contain the message [`MsgStoreCode`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L23-L30) with the base64-encoded byte code of the Wasm contract. Use the following CLI command and JSON as an example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-proposal --from ``` where `proposal.json` contains: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "Upload IBC Wasm light client", "summary": "Upload wasm client", "messages": [ { "@type": "/ibc.lightclients.wasm.v1.MsgStoreCode", "signer": "cosmos1...", / the authority address (e.g. the gov module account address) "wasm_byte_code": "YWJ...PUB+" / standard base64 encoding of the Wasm contract byte code } ], "metadata": "AQ==", "deposit": "100stake" } ``` To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](/sdk/latest/modules/gov/README#submit-proposal). Alternatively, the process of submitting the proposal may be simpler if you use the CLI command `store-code`. This CLI command accepts as argument the file of the Wasm light client contract and takes care of constructing the proposal message with `MsgStoreCode` and broadcasting it. ## Migrating an existing Wasm light client contract If governance is the allowed authority, the governance v1 proposal that needs to be submitted to migrate an existing new Wasm light client contract should contain the message [`MsgMigrateContract`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L52-L63) with the checksum of the Wasm byte code to migrate to. Use the following CLI command and JSON as an example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-proposal --from ``` where `proposal.json` contains: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "Migrate IBC Wasm light client", "summary": "Migrate wasm client", "messages": [ { "@type": "/ibc.lightclients.wasm.v1.MsgMigrateContract", "signer": "cosmos1...", / the authority address (e.g. the gov module account address) "client_id": "08-wasm-1", / client identifier of the Wasm light client contract that will be migrated "checksum": "a8ad...4dc0", / SHA-256 hash of the Wasm byte code to migrate to, previously stored with MsgStoreCode "msg": "{}" / JSON-encoded message to be passed to the contract on migration } ], "metadata": "AQ==", "deposit": "100stake" } ``` To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](/sdk/latest/modules/gov/README#submit-proposal). ## Removing an existing checksum If governance is the allowed authority, the governance v1 proposal that needs to be submitted to remove a specific checksum from the list of allowed checksums should contain the message [`MsgRemoveChecksum`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L39-L46) with the checksum (of a corresponding Wasm byte code). Use the following CLI command and JSON as an example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx gov submit-proposal --from ``` where `proposal.json` contains: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "title": "Remove checksum of Wasm light client byte code", "summary": "Remove checksum", "messages": [ { "@type": "/ibc.lightclients.wasm.v1.MsgRemoveChecksum", "signer": "cosmos1...", / the authority address (e.g. the gov module account address) "checksum": "a8ad...4dc0", / SHA-256 hash of the Wasm byte code that should be removed from the list of allowed checksums } ], "metadata": "AQ==", "deposit": "100stake" } ``` To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](/sdk/latest/modules/gov/README#submit-proposal). # Integration Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/integration Learn how to integrate the 08-wasm module in a chain binary and about the recommended approaches depending on whether the x/wasm module is already used in the chain. The following document only applies for Cosmos SDK chains. Learn how to integrate the `08-wasm` module in a chain binary and about the recommended approaches depending on whether the [`x/wasm` module](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) is already used in the chain. The following document only applies for Cosmos SDK chains. ## Importing the `08-wasm` module `08-wasm` has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible versions of `ibc-go` and `wasmvm`. To do so, run the following command with the desired git commit in your project: ```sh theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} go get github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11 ``` ## `app.go` setup The sample code below shows the relevant integration points in `app.go` required to set up the `08-wasm` module in a chain binary. Since `08-wasm` is a light client module itself, please check out as well the section [Integrating light clients](/ibc/latest/ibc/integration#integrating-light-clients) for more information: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go import ( ... "github.com/cosmos/cosmos-sdk/runtime" cmtos "github.com/cometbft/cometbft/libs/os" ibcwasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11" ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/keeper" ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/types" ... ) ... // Register the AppModule for the 08-wasm module ModuleBasics = module.NewBasicManager( ... ibcwasm.AppModuleBasic{ }, ... ) // Add 08-wasm Keeper type SimApp struct { ... WasmClientKeeper ibcwasmkeeper.Keeper ... } func NewSimApp( logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp), ) *SimApp { ... keys := sdk.NewKVStoreKeys( ... ibcwasmtypes.StoreKey, ) // Instantiate 08-wasm's keeper // This sample code uses a constructor function that // accepts a pointer to an existing instance of Wasm VM. // This is the recommended approach when the chain // also uses `x/wasm`, and then the Wasm VM instance // can be shared. app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmVM, app.GRPCQueryRouter(), ) wasmLightClientModule := wasm.NewLightClientModule(app.WasmClientKeeper) app.IBCKeeper.ClientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule) app.ModuleManager = module.NewManager( // SDK app modules ... ibcwasm.NewAppModule(app.WasmClientKeeper), ) app.ModuleManager.SetOrderBeginBlockers( ... ibcwasmtypes.ModuleName, ... ) app.ModuleManager.SetOrderEndBlockers( ... ibcwasmtypes.ModuleName, ... ) genesisModuleOrder := []string{ ... ibcwasmtypes.ModuleName, ... } app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) ... // initialize BaseApp app.SetInitChainer(app.InitChainer) ... // must be before Loading version if manager := app.SnapshotManager(); manager != nil { err := manager.RegisterExtensions( ibcwasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), ) if err != nil { panic(fmt.Errorf("failed to register snapshot extension: %s", err)) } } ... if loadLatest { ... ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{ }) // Initialize pinned codes in wasmvm as they are not persisted there if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) } } } ``` ## Keeper instantiation When it comes to instantiating `08-wasm`'s keeper, there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. ### If `x/wasm` is present If the chain where the module is integrated uses `x/wasm` then we recommend that both `08-wasm` and `x/wasm` share the same Wasm VM instance. Having two separate Wasm VM instances is still possible, but care should be taken to make sure that both instances do not share the directory when the VM stores blobs and various caches, otherwise unexpected behaviour is likely to happen (from `x/wasm` v0.51 and `08-wasm` v0.2.0+ibc-go-v8.3-wasmvm-v2.0 this will be forbidden anyway, since wasmvm v2.0.0 and above will not allow two different Wasm VM instances to shared the same data folder). In order to share the Wasm VM instance, please follow the guideline below. Please note that this requires `x/wasm` v0.41 or above. * Instantiate the Wasm VM in `app.go` with the parameters of your choice. * [Create an `Option` with this Wasm VM instance](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/options.go#L21-L25). * Add the option created in the previous step to a slice and [pass it to the `x/wasm NewKeeper` constructor function](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/keeper_cgo.go#L36). * Pass the pointer to the Wasm VM instance to `08-wasm` [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47). The code to set this up would look something like this: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go import ( ... "github.com/cosmos/cosmos-sdk/runtime" wasmvm "github.com/CosmWasm/wasmvm/v2" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/keeper" ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/types" ... ) ... // instantiate the Wasm VM with the chosen parameters wasmer, err := wasmvm.NewVM( dataDir, availableCapabilities, contractMemoryLimit, // default of 32 contractDebugMode, memoryCacheSize, ) if err != nil { panic(err) } // create an Option slice (or append to an existing one) // with the option to use a custom Wasm VM instance wasmOpts = []wasmkeeper.Option{ wasmkeeper.WithWasmEngine(wasmer), } // the keeper will use the provided Wasm VM instance, // instead of instantiating a new one app.WasmKeeper = wasmkeeper.NewKeeper( appCodec, keys[wasmtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.StakingKeeper, distrkeeper.NewQuerier(app.DistrKeeper), app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, scopedWasmKeeper, app.TransferKeeper, app.MsgServiceRouter(), app.GRPCQueryRouter(), wasmDir, wasmConfig, availableCapabilities, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmOpts..., ) app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor app.GRPCQueryRouter(), ) ... ``` ### If `x/wasm` is not present If the chain does not use [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm), even though it is still possible to use the method above from the previous section (e.g. instantiating a Wasm VM in app.go an pass it to 08-wasm's [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47), since there would be no need in this case to share the Wasm VM instance with another module, you can use the [`NewKeeperWithConfig` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) and provide the Wasm VM configuration parameters of your choice instead. A Wasm VM instance will be created in `NewKeeperWithConfig`. The parameters that can set are: * `DataDir` is the [directory for Wasm blobs and various caches](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L25). As an example, in `wasmd` this is set to the [`wasm` folder under the home directory](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L578). In the code snippet below we set this field to the `ibc_08-wasm_client_data` folder under the home directory. * `SupportedCapabilities` is a [list of capabilities supported by the chain](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L26). [`wasmd` sets this to all the available capabilities](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L586), but 08-wasm only requires `iterator`. * `MemoryCacheSize` sets [the size in MiB of an in-memory cache for e.g. module caching](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L29C16-L29C104). It is not consensus-critical and should be defined on a per-node basis, often in the range 100 to 1000 MB. [`wasmd` reads this value of](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L579). Default value is 256. * `ContractDebugMode` is a [flag to enable/disable printing debug logs from the contract to STDOUT](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L28). This should be false in production environments. Default value is false. Another configuration parameter of the Wasm VM is the contract memory limit (in MiB), which is [set to 32](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L8), [following the example of `wasmd`](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/x/wasm/keeper/keeper.go#L32-L34). This parameter is not configurable by users of `08-wasm`. The following sample code shows how the keeper would be constructed using this method: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go import ( ... "github.com/cosmos/cosmos-sdk/runtime" ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/keeper" ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/types" ... ) ... // homePath is the path to the directory where the data // directory for Wasm blobs and caches will be created wasmConfig := ibcwasmtypes.WasmConfig{ DataDir: filepath.Join(homePath, "ibc_08-wasm_client_data"), SupportedCapabilities: []string{"iterator" }, ContractDebugMode: false, } app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmConfig, app.GRPCQueryRouter(), ) ``` Check out also the [`WasmConfig` type definition](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L21-L31) for more information on each of the configurable parameters. Some parameters allow node-level configurations. There is additionally the function [`DefaultWasmConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L36-L42) available that returns a configuration with the default values. ### Options The `08-wasm` module comes with an options API inspired by the one in `x/wasm`. Currently the only option available is the `WithQueryPlugins` option, which allows registration of custom query plugins for the `08-wasm` module. The use of this API is optional and it is only required if the chain wants to register custom query plugins for the `08-wasm` module. #### `WithQueryPlugins` By default, the `08-wasm` module does not configure any querier options for light client contracts. However, it is possible to register custom query plugins for [`QueryRequest::Custom`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L48) and [`QueryRequest::Stargate`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L57-L65). Assuming that the keeper is not yet instantiated, the following sample code shows how to register query plugins for the `08-wasm` module. We first construct a [`QueryPlugins`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/querier.go#L78-L87) object with the desired query plugins: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} queryPlugins := ibcwasmtypes.QueryPlugins { Custom: MyCustomQueryPlugin(), // `myAcceptList` is a `[]string` containing the list of gRPC query paths that the chain wants to allow for the `08-wasm` module to query. // These queries must be registered in the chain's gRPC query router, be deterministic, and track their gas usage. // The `AcceptListStargateQuerier` function will return a query plugin that will only allow queries for the paths in the `myAcceptList`. // The query responses are encoded in protobuf unlike the implementation in `x/wasm`. Stargate: ibcwasmtypes.AcceptListStargateQuerier(myAcceptList), } ``` Note that the `Stargate` querier appends the user defined accept list of query routes to a default list defined by the `08-wasm` module. The `defaultAcceptList` defines a single query route: `"/ibc.core.client.v1.Query/VerifyMembership"`. This allows for light client smart contracts to delegate parts of their workflow to other light clients for auxiliary proof verification. For example, proof of inclusion of block and tx data by a data availability provider. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // defaultAcceptList defines a set of default allowed queries made available to the Querier. var defaultAcceptList = []string{ "/ibc.core.client.v1.Query/VerifyMembership", } ``` You may leave any of the fields in the `QueryPlugins` object as `nil` if you do not want to register a query plugin for that query type. Then, we pass the `QueryPlugins` object to the `WithQueryPlugins` option: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} querierOption := ibcwasmkeeper.WithQueryPlugins(&queryPlugins) ``` Finally, we pass the option to the `NewKeeperWithConfig` or `NewKeeperWithVM` constructor function during [Keeper instantiation](#keeper-instantiation): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmConfig, app.GRPCQueryRouter(), + querierOption, ) ``` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor app.GRPCQueryRouter(), + querierOption, ) ``` ## Updating `AllowedClients` If the chain's 02-client submodule parameter `AllowedClients` contains the single wildcard `"*"` element, then it is not necessary to do anything in order to allow the creation of `08-wasm` clients. However, if the parameter contains a list of client types (e.g. `["06-solomachine", "07-tendermint"]`), then in order to use the `08-wasm` module chains must update the [`AllowedClients` parameter](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/client.proto#L64) of core IBC. This can be configured directly in the application upgrade handler with the sample code below: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( ... ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v11/types" ... ) ... func CreateWasmUpgradeHandler( mm *module.Manager, configurator module.Configurator, clientKeeper clientkeeper.Keeper, ) upgradetypes.UpgradeHandler { return func(goCtx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { ctx := sdk.UnwrapSDKContext(goCtx) // explicitly update the IBC 02-client params, adding the wasm client type params := clientKeeper.GetParams(ctx) params.AllowedClients = append(params.AllowedClients, ibcwasmtypes.Wasm) clientKeeper.SetParams(ctx, params) return mm.RunMigrations(goCtx, configurator, vm) } } ``` Or alternatively the parameter can be updated via a governance proposal (see at the bottom of section [`Creating clients`](/ibc/latest/light-clients/developer-guide/setup#creating-clients) for an example of how to do this). ## Adding the module to the store As part of the upgrade migration you must also add the module to the upgrades store. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app SimApp) RegisterUpgradeHandlers() { ... if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := storetypes.StoreUpgrades{ Added: []string{ ibcwasmtypes.ModuleName, }, } // configure store loader that checks if version == upgradeHeight and applies store upgrades app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } } ``` ## Adding snapshot support In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. [This code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L775-L782) should be placed in `NewSimApp` function in `app.go`. ## Pin byte codes at start Wasm byte codes should be pinned to the WasmVM cache on every application start, therefore [this code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L825-L830) should be placed in `NewSimApp` function in `app.go`. # Messages Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/messages Uploading the Wasm light client contract to the Wasm VM storage is achieved by means of MsgStoreCode: ## `MsgStoreCode` Uploading the Wasm light client contract to the Wasm VM storage is achieved by means of `MsgStoreCode`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgStoreCode struct { / signer address Signer string / wasm byte code of light client contract. It can be raw or gzip compressed WasmByteCode []byte } ``` This message is expected to fail if: * `Signer` is an invalid Bech32 address, or it does not match the designated authority address. * `WasmByteCode` is empty or it exceeds the maximum size, currently set to 3MB. Only light client contracts stored using `MsgStoreCode` are allowed to be instantiated. An attempt to create a light client from contracts uploaded via other means (e.g. through `x/wasm` if the module shares the same Wasm VM instance with 08-wasm) will fail. Due to the idempotent nature of the Wasm VM's `StoreCode` function, it is possible to store the same byte code multiple times. When execution of `MsgStoreCode` succeeds, the checksum of the contract (i.e. the sha256 hash of the contract's byte code) is stored in an allow list. When a relayer submits [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L25-L37) with 08-wasm's `ClientState`, the client state includes the checksum of the Wasm byte code that should be called. Then 02-client calls [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/06fd8eb5ee1697e3b43be7528a6e42f5e4a4613c/modules/core/02-client/keeper/client.go#L40) (which is an interface function part of `LightClientModule`), and it will check that the checksum in the client state matches one of the checksums in the allow list. If a match is found, the light client is initialized; otherwise, the transaction is aborted. ## `MsgMigrateContract` Migrating a contract to a new Wasm byte code is achieved by means of `MsgMigrateContract`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgMigrateContract struct { / signer address Signer string / the client id of the contract ClientId string / the SHA-256 hash of the new wasm byte code for the contract Checksum []byte / the json-encoded migrate msg to be passed to the contract on migration Msg []byte } ``` This message is expected to fail if: * `Signer` is an invalid Bech32 address, or it does not match the designated authority address. * `ClientId` is not a valid identifier prefixed by `08-wasm`. * `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`), or it matches the current checksum of the contract. When a Wasm light client contract is migrated to a new Wasm byte code the checksum for the contract will be updated with the new checksum. ## `MsgRemoveChecksum` Removing a checksum from the list of allowed checksums is achieved by means of `MsgRemoveChecksum`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgRemoveChecksum struct { / signer address Signer string / Wasm byte code checksum to be removed from the store Checksum []byte } ``` This message is expected to fail if: * `Signer` is an invalid Bech32 address, or it does not match the designated authority address. * `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`). When a checksum is removed from the list of allowed checksums, then the corresponding Wasm byte code will not be available for instantiation in [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/core/02-client/keeper/client.go#L36). # Overview Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/overview Learn about the 08-wasm light client proxy module. ## Overview Learn about the `08-wasm` light client proxy module. ### Context Traditionally, light clients used by ibc-go have been implemented only in Go, and since ibc-go v7 (with the release of the 02-client refactor), they are first-class Cosmos SDK modules. This means that updating existing light client implementations or adding support for new light clients is a multi-step, time-consuming process involving on-chain governance: it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and have validators upgrade their nodes. ### Motivation To break the limitation of being able to write light client implementations only in Go, the `08-wasm` adds support to run light clients written in a Wasm-compilable language. The light client byte code implements the entry points of a [CosmWasm](https://docs.cosmwasm.com/docs/) smart contract, and runs inside a Wasm VM. The `08-wasm` module exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM, for execution. Adding a new light client to a chain is just as simple as submitting a governance proposal with the message that stores the byte code of the light client contract. No coordinated upgrade is needed. When the governance proposal passes and the message is executed, the contract is ready to be instantiated upon receiving a relayer-submitted `MsgCreateClient`. The process of creating a Wasm light client is the same as with a regular light client implemented in Go. ### Use cases * Development of light clients for non-Cosmos ecosystem chains: state machines in other ecosystems are, in many cases, implemented in Rust, and thus there are probably libraries used in their light client implementations for which there is no equivalent in Go. This makes the development of a light client in Go very difficult, but relatively simple to do it in Rust. Therefore, writing a CosmWasm smart contract in Rust that implements the light client algorithm becomes a lower effort. # SDK v0.43 to IBC-Go v1 Source: https://docs.cosmos.network/ibc/latest/migrations/sdk-to-v1 This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. ## Import Changes The most obvious changes is import name changes. We need to change: * applications -> apps * cosmos-sdk/x/ibc -> ibc-go On my GNU/Linux based machine I used the following commands, executed in order: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' ``` ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' ``` ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) Executing these commands out of order will cause issues. Feel free to use your own method for modifying import names. NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. Update the import paths before running `go mod tidy`. ## Chain Upgrades Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. **WARNING**: Please read at least the quick guide for [IBC client upgrades](/ibc/latest/ibc/upgrades/quick-guide) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. Both in-place store migrations and genesis migrations will: * migrate the solo machine client state from v1 to v2 protobuf definitions * prune all solo machine consensus states * prune all expired tendermint consensus states Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. ### In-Place Store Migrations The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. Ex: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { / set max expected block time parameter. Replace the default with your expected value app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) fromVM := map[string]uint64{ ... / other modules "ibc": 1, ... } return app.mm.RunMigrations(ctx, app.configurator, fromVM) }) ``` ### Genesis Migrations To perform genesis migrations, the following code must be added to your existing migration code. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / add imports as necessary import ( ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" ibchost "github.com/cosmos/ibc-go/modules/core/24-host" ) ... / add in migrate cmd function / expectedTimePerBlock is a new connection parameter newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) if err != nil { return err } ``` **NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. ## IBC Keeper Changes The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create IBC Keeper app.IBCKeeper = ibckeeper.NewKeeper( - appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, ) ``` ## Proposals ### UpdateClientProposal The `UpdateClient` has been modified to take in two client-identifiers and one initial height. ### UpgradeProposal A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. ### Proposal Handler Registration The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. It handles both `UpdateClientProposal`s and `UpgradeProposal`s. Add this import: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" ``` Please ensure the governance module adds the correct route: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) + AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) ``` NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` as shown in the diffs above. ### Proposal CLI Registration Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: Add the following import: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" ``` Register the cli commands: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} gov.NewAppModuleBasic( paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, + ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, ), ``` REST routes are not supported for these proposals. ## Proto file changes The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` ## IBC callback changes ### OnRecvPacket Application developers need to update their `OnRecvPacket` callback logic. The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](/ibc/latest/ibc/apps/apps#receiving-packets). The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. ## IBC Event changes The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. ## Relevant SDK changes * (codec) [#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: * `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) * `codec.BinaryMarshaler` → `codec.BinaryCodec` * `codec.JSONMarshaler` → `codec.JSONCodec` * Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) * Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) # Support transfer of coins whose base denom contains slashes Source: https://docs.cosmos.network/ibc/latest/migrations/support-denoms-with-slashes This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. There are four sections based on the four potential user groups of this document: * Chains * IBC Apps * Relayers * IBC Light Clients This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. ## Chains ### ICS20 - Transfer The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. ### Upgrade Proposal ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { / transfer module consensus version has been bumped to 2 return app.mm.RunMigrations(ctx, app.configurator, fromVM) }) ``` This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). ### Genesis Migration If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. The migration code required may look like: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { if appState[ibctransfertypes.ModuleName] != nil { transferGenState := &ibctransfertypes.GenesisState{ } clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) for i, dt := range transferGenState.DenomTraces { / replace all previous traces with the latest trace if validation passes / note most traces will have same value newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) if err := newTrace.Validate(); err != nil { substituteTraces[i] = dt } else { substituteTraces[i] = newTrace } } transferGenState.DenomTraces = substituteTraces / delete old genesis state delete(appState, ibctransfertypes.ModuleName) / set new ibc transfer genesis state appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) } return appState, nil } ``` For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). # Support the new StackBuilder primitive for Wiring Middlewares in the chain application Source: https://docs.cosmos.network/ibc/latest/migrations/support-stackbuilder The StackBuilder struct is a new primitive for wiring middleware in a simpler and less error-prone manner. It is not a breaking change thus the existing method of wiring middleware still works, though it is highly recommended to transition to the new wiring method. Refer to the [integration guide](/ibc/latest/ibc/middleware/integration) to understand how to use this new middleware to improve middleware wiring in the chain application setup. # Migrations for Application Developers In order to be wired with the new StackBuilder primitive, applications and middlewares must implement new methods as part of their respective interfaces. IBC Applications must implement a new `SetICS4Wrapper` which will set the `ICS4Wrapper` through which the application will call `SendPacket` and `WriteAcknowledgement`. It is recommended that IBC applications are initialized first with the IBC ChannelKeeper directly, and then modified with a middleware ICS4Wrapper during the stack wiring. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / SetICS4Wrapper sets the ICS4Wrapper. This function may be used after / the module's initialization to set the middleware which is above this / module in the IBC application stack. / The ICS4Wrapper **must** be used for sending packets and writing acknowledgements / to ensure that the middleware can intercept and process these calls. / Do not use the channel keeper directly to send packets or write acknowledgements / as this will bypass the middleware. SetICS4Wrapper(wrapper ICS4Wrapper) ``` Many applications have a stateful keeper that executes the logic for sending packets and writing acknowledgements. In this case, the keeper in the application must be a **pointer** reference so that it can be modified in place after initialization. The initialization should be modified to no longer take in an addition `ics4Wrapper` as this gets modified later by `SetICS4Wrapper`. The constructor function must also return a **pointer** reference so that it may be modified in-place by the stack builder. Below is an example IBCModule that supports the stack builder wiring. E.g. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type IBCModule struct { keeper *keeper.Keeper } / NewIBCModule creates a new IBCModule given the keeper func NewIBCModule(k *keeper.Keeper) *IBCModule { return &IBCModule{ keeper: k, } } / SetICS4Wrapper sets the ICS4Wrapper. This function may be used after / the module's initialization to set the middleware which is above this / module in the IBC application stack. func (im IBCModule) SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) { if wrapper == nil { panic("ICS4Wrapper cannot be nil") } im.keeper.WithICS4Wrapper(wrapper) } / Keeper file that has ICS4Wrapper internal to its own struct / Keeper defines the IBC fungible transfer keeper type Keeper struct { ... ics4Wrapper porttypes.ICS4Wrapper / Keeper is initialized with ICS4Wrapper / being equal to the top-level channelKeeper / this can be changed by calling WithICS4Wrapper / with a different middleware ICS4Wrapper channelKeeper types.ChannelKeeper ... } / WithICS4Wrapper sets the ICS4Wrapper. This function may be used after / the keepers creation to set the middleware which is above this module / in the IBC application stack. func (k *Keeper) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) { k.ics4Wrapper = wrapper } ``` # Migration for Middleware Developers Since Middleware is itself implement the IBC application interface, it must also implement `SetICS4Wrapper` in the same way as IBC applications. Additionally, IBC Middleware has an underlying IBC application that it calls into as well. Previously this application would be set in the middleware upon construction. With the stack builder primitive, the application is only set during upon calling `stack.Build()`. Thus, middleware is additionally responsible for implementing the new method: `SetUnderlyingApplication`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / SetUnderlyingModule sets the underlying IBC module. This function may be used after / the middleware's initialization to set the ibc module which is below this middleware. SetUnderlyingApplication(IBCModule) ``` The initialization should not include the ICS4Wrapper and application as this gets set later. The constructor function for Middlewares **must** be modified to return a **pointer** reference so that it can be modified in place by the stack builder. Below is an example middleware setup: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / IBCMiddleware implements the ICS26 callbacks type IBCMiddleware struct { app porttypes.PacketUnmarshalerModule ics4Wrapper porttypes.ICS4Wrapper / this is a stateful middleware with its own internal keeper mwKeeper *keeper.MiddlewareKeeper / this is a middleware specific field mwField any } / NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application. / NOTE: It **must** return a pointer reference so it can be / modified in place by the stack builder / NOTE: We do not pass in the underlying app and ICS4Wrapper here as this happens later func NewIBCMiddleware( mwKeeper *keeper.MiddlewareKeeper, mwField any, ) *IBCMiddleware { return &IBCMiddleware{ mwKeeper: mwKeeper, mwField, mwField, } } / SetICS4Wrapper sets the ICS4Wrapper. This function may be used after the / middleware's creation to set the middleware which is above this module in / the IBC application stack. func (im *IBCMiddleware) SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) { if wrapper == nil { panic("ICS4Wrapper cannot be nil") } im.mwKeeper.WithICS4Wrapper(wrapper) } / SetUnderlyingApplication sets the underlying IBC module. This function may be used after / the middleware's creation to set the ibc module which is below this middleware. func (im *IBCMiddleware) SetUnderlyingApplication(app porttypes.IBCModule) { if app == nil { panic(errors.New("underlying application cannot be nil")) } if im.app != nil { panic(errors.New("underlying application already set")) } im.app = app } ``` # IBC-Go v1 to v2 Source: https://docs.cosmos.network/ibc/latest/migrations/v1-to-v2 This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. There are four sections based on the four potential user groups of this document: * Chains * IBC Apps * Relayers * IBC Light Clients **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 ``` ## Chains * No relevant changes were made in this release. ## IBC Apps A new function has been added to the app module interface: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. / An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface / may decide to return an error in the event of the proposed version being incompatible with it's own NegotiateAppVersion( ctx sdk.Context, order channeltypes.Order, connectionID string, portID string, counterparty channeltypes.Counterparty, proposedVersion string, ) (version string, err error) ``` This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. ### `sdk.Result` removed `sdk.Result` has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. ## Relayers A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. ## IBC Light Clients * No relevant changes were made in this release. # IBC-Go v10 to v11 Source: https://docs.cosmos.network/ibc/latest/migrations/v10-to-v11 This guide provides instructions for migrating to a new version of ibc-go. This guide provides instructions for migrating to a new version of ibc-go. **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. Diff examples are shown after the list of overall changes: * Chains will need to remove the `ParamSubspace` arg from all calls to `Keeper` constructors ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.IBCKeeper = ibckeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), - app.GetSubspace(ibcexported.ModuleName), app.UpgradeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` The transfer module, the packet forward middleware, and the rate limiting middleware support custom address codecs. This feature is primarily added to support Cosmos EVM for IBC transfers. In a standard Cosmos SDK app, they are wired as follows: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, + app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.IBCKeeper.ChannelKeeper, app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.RateLimitKeeper = ratelimitkeeper.NewKeeper( appCodec, + app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[ratelimittypes.StoreKey]), app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ClientKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String() ) ``` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.PFMKeeper = packetforwardkeeper.NewKeeper( appCodec, + app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[packetforwardtypes.StoreKey]), app.TransferKeeper, app.IBCKeeper.ChannelKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String() ) ``` ## ICS27-GMP ICS27 General Message Passing (GMP) has been added as a supported IBC application of ibc-go. It has no parameters. ### Add `StoreUpgrades` for ICS27-GMP module For ICS27-GMP it is also necessary to [manually add store upgrades](https://docs.cosmos.network/sdk/v0.53/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new GMP module and then configure the store loader to apply those upgrades in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if upgradeInfo.Name == "v11" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := store.StoreUpgrades{ Added: []string{gmptypes.StoreKey}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } ``` This ensures that the new module's stores are added to the multistore before the migrations begin. If a chain chooses not to integrate the GMP module, it does not need to add the GMP key to the `Added` field. # IBC-Go v10 to v11.1 Source: https://docs.cosmos.network/ibc/latest/migrations/v10-to-v11-1 This guide provides instructions for migrating from ibc-go v10 to v11.1. ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. This guide is for chains upgrading directly from ibc-go v10 to ibc-go v11.1. It includes the changes from the ibc-go v11.0 migration and the Packet Forward Middleware changes introduced in ibc-go v11.1. The following changes from v10 to v11 are relevant to this upgrade: * Chains will need to remove the `ParamSubspace` arg from all calls to `Keeper` constructors ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.IBCKeeper = ibckeeper.NewKeeper( appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), - app.GetSubspace(ibcexported.ModuleName), app.UpgradeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` The transfer module, the packet forward middleware, and the rate limiting middleware support custom address codecs. This feature is primarily added to support Cosmos EVM for IBC transfers. In a standard Cosmos SDK app, they are wired as follows: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, + app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.IBCKeeper.ChannelKeeper, app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.RateLimitKeeper = ratelimitkeeper.NewKeeper( appCodec, + app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[ratelimittypes.StoreKey]), app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ClientKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String() ) ``` ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.PFMKeeper = packetforwardkeeper.NewKeeper( appCodec, + app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[packetforwardtypes.StoreKey]), app.TransferKeeper, app.IBCKeeper.ChannelKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String() ) ``` ## ICS27-GMP ICS27 General Message Passing (GMP) has been added as a supported IBC application of ibc-go. It has no parameters. ### Add `StoreUpgrades` for ICS27-GMP module If ICS27-GMP is being added to an existing chain, you must [manually add store upgrades](https://docs.cosmos.network/sdk/v0.53/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new GMP module and configure the store loader to apply those upgrades in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if upgradeInfo.Name == "v11_1" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := store.StoreUpgrades{ Added: []string{gmptypes.StoreKey}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } ``` This ensures that the new module's stores are added to the multistore before the migrations begin. If a chain does not integrate ICS27-GMP, it does not need to add the GMP key to the `Added` field. ## Packet Forward Middleware (New in v11.1) Packet Forward Middleware (PFM) has been moved from [cosmos/ibc-apps](https://github.com/cosmos/ibc-apps) to ibc-go in this release. PFM is now available under `modules/apps/packet-forward-middleware`. Your migration path depends on whether your chain already uses PFM from ibc-apps. ### If your chain already uses PFM from ibc-apps Replace all ibc-apps PFM imports with the new ibc-go imports and review the [integration instructions](/ibc/latest/middleware/packet-forward-middleware/integration). If your chain already has a PFM store from a previous upgrade, do not add `packetforwardtypes.StoreKey` as a new store in this upgrade. The ibc-go PFM module intentionally preserves the original module name and store key, including the `packetfowardmiddleware` spelling, for compatibility with the legacy implementation. The PFM module includes in-place migrations for existing PFM state. Before upgrading, ensure there are no non-refundable in-flight packets. The v3-to-v4 PFM migration removes the deprecated `nonrefundable` field from in-flight packet state and aborts if any stored in-flight packet has `nonrefundable=true`. ### If your chain is adding PFM for the first time First follow the [integration instructions](/ibc/latest/middleware/packet-forward-middleware/integration). If PFM is being added to an existing chain, you must [manually add store upgrades](https://docs.cosmos.network/sdk/latest/guides/upgrades/upgrade#adding-new-modules-during-an-upgrade) for the new PFM module and configure the store loader to apply those upgrades in `app.go`. If your upgrade also introduces ICS27-GMP, include both store keys: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if upgradeInfo.Name == "v11_1" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := store.StoreUpgrades{ Added: []string{gmptypes.StoreKey, packetforwardtypes.StoreKey}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } ``` This ensures that the new module's stores are added to the multistore before the migrations begin. If your chain already added the ICS27-GMP store key in a previous upgrade, do not add `gmptypes.StoreKey` again. If your chain does not integrate PFM, it does not need to add the PFM key to the `Added` field. # IBC-Go v11.0 to v11.1 Source: https://docs.cosmos.network/ibc/latest/migrations/v11-to-v11-1 This guide provides instructions for migrating from ibc-go v11.0 to v11.1. This guide assumes that your chain is already using ibc-go v11.0 and is upgrading to ibc-go v11.1. If you are upgrading directly from ibc-go v10 to ibc-go v11.1, see the [migration guide for v10 to v11](/ibc/latest/migrations/v10-to-v11). ## Packet Forward Middleware Packet Forward Middleware (PFM) has been moved from [cosmos/ibc-apps](https://github.com/cosmos/ibc-apps) to ibc-go in this release. PFM is now available under `modules/apps/packet-forward-middleware`. This guide assumes that your chain has not previously integrated PFM from ibc-apps. If your chain already uses PFM from ibc-apps, follow the [migration guide for v10 to v11](/ibc/latest/migrations/v10-to-v11), which includes guidance for preserving existing PFM state. If you want to enable PFM, first follow the [integration instructions](/ibc/latest/middleware/packet-forward-middleware/integration). ### Add `StoreUpgrades` for the PFM module If PFM is being added to an existing chain, you must [manually add store upgrades](https://docs.cosmos.network/sdk/latest/guides/upgrades/upgrade#adding-new-modules-during-an-upgrade) for the new PFM module and configure the store loader to apply those upgrades in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if upgradeInfo.Name == "v11_1" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := store.StoreUpgrades{ Added: []string{packetforwardtypes.StoreKey}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } ``` This ensures that the new module's stores are added to the multistore before the migrations begin. If a chain does not integrate PFM, it does not need to add the PFM key to the `Added` field. The PFM module name and store key intentionally preserve the original `packetfowardmiddleware` spelling for compatibility with the legacy implementation. # IBC-Go v2 to v3 Source: https://docs.cosmos.network/ibc/latest/migrations/v2-to-v3 This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. There are four sections based on the four potential user groups of this document: * Chains * IBC Apps * Relayers * IBC Light Clients **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 ``` No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. ## Chains ### ICS20 The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. ### ICS27 ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. Please see the [ICS27 documentation](/ibc/latest/apps/interchain-accounts/overview) for more information. ### Upgrade Proposal If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("v3", func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { / set the ICS27 consensus version so InitGenesis is not run fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() / create ICS27 Controller submodule params controllerParams := icacontrollertypes.Params{ ControllerEnabled: true, } / create ICS27 Host submodule params hostParams := icahosttypes.Params{ HostEnabled: true, AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ... }, } / initialize ICS27 module icamodule.InitModule(ctx, controllerParams, hostParams) ... return app.mm.RunMigrations(ctx, app.configurator, fromVM) }) ``` The host and controller submodule params only need to be set if the chain integrates those submodules. For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. #### Add `StoreUpgrades` for ICS27 module For ICS27 it is also necessary to [manually add store upgrades](/sdk/latest/guides/upgrades/upgrade#adding-new-modules-during-an-upgrade) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := store.StoreUpgrades{ Added: []string{ icacontrollertypes.StoreKey, icahosttypes.StoreKey }, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } ``` This ensures that the new module's stores are added to the multistore before the migrations begin. The host and controller submodule keys only need to be added if the chain integrates those submodules. For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. ### Genesis migrations If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. The migration code required may look like: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} controllerGenesisState := icatypes.DefaultControllerGenesis() / overwrite parameters as desired controllerGenesisState.Params = icacontrollertypes.Params{ ControllerEnabled: true, } hostGenesisState := icatypes.DefaultHostGenesis() / overwrite parameters as desired hostGenesisState.Params = icahosttypes.Params{ HostEnabled: true, AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ... }, } icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) / set new ics27 genesis state appState[icatypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(icaGenesisState) ``` ### Ante decorator The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type AnteDecorator struct { - k channelkeeper.Keeper + k *keeper.Keeper } - func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { + func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { return AnteDecorator{k: k} } ``` ## IBC Apps ### `OnChanOpenTry` must return negotiated application version The `OnChanOpenTry` application callback has been modified. The return signature now includes the application version. IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. Core IBC will set this version in the TRYOPEN channel. ### `OnChanOpenAck` will take additional `counterpartyChannelID` argument The `OnChanOpenAck` application callback has been modified. The arguments now include the counterparty channel id. ### `NegotiateAppVersion` removed from `IBCModule` interface Previously this logic was handled by the `NegotiateAppVersion` function. Relayers would query this function before calling `ChanOpenTry`. Applications would then need to verify that the passed in version was correct. Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. ### Channel state will not be set before application callback The channel handshake logic has been reorganized within core IBC. Channel state will not be set in state after the application callback is performed. Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. ### IBC application callbacks moved from `AppModule` to `IBCModule` Previously, IBC module callbacks were apart of the `AppModule` type. The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. The mock module go API has been broken in this release by applying the above format. The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/README.md#middleware-testing) for more information. Please review the [mock](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. ### IBC testing package `TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. ## Relayers `AppVersion` gRPC has been removed. The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. Relayers no longer need to determine the version to use on the `ChanOpenTry` step. IBC applications will determine the correct version using the counterparty version. ## IBC Light Clients The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. # IBC-Go v3 to v4 Source: https://docs.cosmos.network/ibc/latest/migrations/v3-to-v4 This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. There are four sections based on the four potential user groups of this document: * Chains * IBC Apps * Relayers * IBC Light Clients **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 ``` No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. ## Chains ### ICS27 - Interchain Accounts The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) + icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) ``` where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. ### ICS29 - Fee Middleware The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. Please read the Fee Middleware integration documentation for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. ### Migration to fix support for base denoms with slashes As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](/ibc/latest/migrations/support-denoms-with-slashes#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { / transfer module consensus version has been bumped to 2 return app.mm.RunMigrations(ctx, app.configurator, fromVM) }) ``` If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. ## IBC Apps ### ICS03 - Connection Crossing hellos have been removed from 03-connection handshake negotiation. `PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. `NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. ### ICS04 - Channel The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. The `OnChanOpenInit` application callback has been modified. The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). The `NewErrorAcknowledgement` method signature has changed. It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. Crossing hellos have been removed from 04-channel handshake negotiation. IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. `PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. `NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. ### ICS27 - Interchain Accounts The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} icaMetadata := icatypes.Metadata{ Version: icatypes.Version, ControllerConnectionId: controllerConnectionID, HostConnectionId: hostConnectionID, Encoding: icatypes.EncodingProtobuf, TxType: icatypes.TxTypeSDKMultiMsg, } appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) if err != nil { return err } if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { return err } ``` Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} icaMetadata := icatypes.Metadata{ Version: icatypes.Version, ControllerConnectionId: controllerConnectionID, HostConnectionId: hostConnectionID, Encoding: icatypes.EncodingProtobuf, TxType: icatypes.TxTypeSDKMultiMsg, } appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) if err != nil { return err } feeMetadata := feetypes.Metadata{ AppVersion: string(appVersion), FeeVersion: feetypes.Version, } feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) if err != nil { return err } if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { return err } ``` ## Relayers When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4 step process (INIT, TRY, ACK, CONFIRM). # IBC-Go v4 to v5 Source: https://docs.cosmos.network/ibc/latest/migrations/v4-to-v5 This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. There are four sections based on the four potential user groups of this document: * [Chains](#chains) * [IBC Apps](#ibc-apps) * [Relayers](#relayers) * [IBC Light Clients](#ibc-light-clients) **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} github.com/cosmos/ibc-go/v4 -> github.com/cosmos/ibc-go/v5 ``` ## Chains ### Ante decorator The `AnteDecorator` type in `core/ante` has been renamed to `RedundantRelayDecorator` (and the corresponding constructor function to `NewRedundantRelayDecorator`). Therefore in the function that creates the instance of the `sdk.AnteHandler` type (e.g. `NewAnteHandler`) the change would be like this: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { / parameter validation anteDecorators := []sdk.AnteDecorator{ / other ante decorators - ibcante.NewAnteDecorator(opts.IBCkeeper), + ibcante.NewRedundantRelayDecorator(options.IBCKeeper), } return sdk.ChainAnteDecorators(anteDecorators...), nil } ``` The `AnteDecorator` was actually renamed twice, but in [this PR](https://github.com/cosmos/ibc-go/pull/1820) you can see the changes made for the final rename. ## IBC Apps ### Core The `key` parameter of the `NewKeeper` function in `modules/core/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, paramSpace paramtypes.Subspace, stakingKeeper clienttypes.StakingKeeper, upgradeKeeper clienttypes.UpgradeKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, ) *Keeper ``` The `RegisterRESTRoutes` function in `modules/core` has been removed. ### ICS03 - Connection The `key` parameter of the `NewKeeper` function in `modules/core/03-connection/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, paramSpace paramtypes.Subspace, ck types.ClientKeeper ) Keeper ``` ### ICS04 - Channel The function `NewPacketId` in `modules/core/04-channel/types` has been renamed to `NewPacketID`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - func NewPacketId( + func NewPacketID( portID, channelID string, seq uint64 ) PacketId ``` The `key` parameter of the `NewKeeper` function in `modules/core/04-channel/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, clientKeeper types.ClientKeeper, connectionKeeper types.ConnectionKeeper, portKeeper types.PortKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, ) Keeper ``` ### ICS20 - Transfer The `key` parameter of the `NewKeeper` function in `modules/apps/transfer/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, paramSpace paramtypes.Subspace, ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, ) Keeper ``` The `amount` parameter of function `GetTransferCoin` in `modules/apps/transfer/types` is now of type `math.Int` (`"cosmossdk.io/math"`): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func GetTransferCoin( portID, channelID, baseDenom string, - amount sdk.Int + amount math.Int ) sdk.Coin ``` The `RegisterRESTRoutes` function in `modules/apps/transfer` has been removed. ### ICS27 - Interchain Accounts The `key` and `msgRouter` parameters of the `NewKeeper` functions in * `modules/apps/27-interchain-accounts/controller/keeper` * and `modules/apps/27-interchain-accounts/host/keeper` have changed type. The `key` parameter is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`), and the `msgRouter` parameter is now of type `*icatypes.MessageRouter` (where `icatypes` is an import alias for `"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"`): ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / NewKeeper creates a new interchain accounts controller Keeper instance func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, paramSpace paramtypes.Subspace, ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, - msgRouter *baseapp.MsgServiceRouter, + msgRouter *icatypes.MessageRouter, ) Keeper ``` ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / NewKeeper creates a new interchain accounts host Keeper instance func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, paramSpace paramtypes.Subspace, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, accountKeeper icatypes.AccountKeeper, scopedKeeper capabilitykeeper.ScopedKeeper, - msgRouter *baseapp.MsgServiceRouter, + msgRouter *icatypes.MessageRouter, ) Keeper ``` The new `MessageRouter` interface is defined as: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MessageRouter interface { Handler(msg sdk.Msg) baseapp.MsgServiceHandler } ``` The `RegisterRESTRoutes` function in `modules/apps/27-interchain-accounts` has been removed. An additional parameter, `ics4Wrapper` has been added to the `host` submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper`. This allows the `host` submodule to correctly unwrap the channel version for channel reopening handshakes in the `OnChanOpenTry` callback. ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, + ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, accountKeeper icatypes.AccountKeeper, scopedKeeper icatypes.ScopedKeeper, msgRouter icatypes.MessageRouter, ) Keeper ``` #### Cosmos SDK message handler responses in packet acknowledgement The construction of the transaction response of a message execution on the host chain has changed. The `Data` field in the `sdk.TxMsgData` has been deprecated and since Cosmos SDK 0.46 the `MsgResponses` field contains the message handler responses packed into `Any`s. For chains on Cosmos SDK 0.45 and below, the message response was constructed like this: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txMsgData := &sdk.TxMsgData{ Data: make([]*sdk.MsgData, len(msgs)), } for i, msg := range msgs { / message validation msgResponse, err := k.executeMsg(cacheCtx, msg) / return if err != nil txMsgData.Data[i] = &sdk.MsgData{ MsgType: sdk.MsgTypeURL(msg), Data: msgResponse, } } / emit events txResponse, err := proto.Marshal(txMsgData) / return if err != nil return txResponse, nil ``` And for chains on Cosmos SDK 0.46 and above, it is now done like this: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} txMsgData := &sdk.TxMsgData{ MsgResponses: make([]*codectypes.Any, len(msgs)), } for i, msg := range msgs { / message validation protoAny, err := k.executeMsg(cacheCtx, msg) / return if err != nil txMsgData.MsgResponses[i] = protoAny } / emit events txResponse, err := proto.Marshal(txMsgData) / return if err != nil return txResponse, nil ``` When handling the acknowledgement in the `OnAcknowledgementPacket` callback of a custom ICA controller module, then depending on whether `txMsgData.Data` is empty or not, the logic to handle the message handler response will be different. **Only controller chains on Cosmos SDK 0.46 or above will be able to write the logic needed to handle the response from a host chain on Cosmos SDK 0.46 or above.** ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ack channeltypes.Acknowledgement if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { return err } var txMsgData sdk.TxMsgData if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { return err } switch len(txMsgData.Data) { case 0: / for SDK 0.46 and above for _, msgResponse := range txMsgData.MsgResponses { / unmarshall msgResponse and execute logic based on the response } return nil default: / for SDK 0.45 and below for _, msgData := range txMsgData.Data { / unmarshall msgData and execute logic based on the response } } ``` See the corresponding documentation about authentication modules for more information. ### ICS29 - Fee Middleware The `key` parameter of the `NewKeeper` function in `modules/apps/29-fee` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, paramSpace paramtypes.Subspace, ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, ) Keeper ``` The `RegisterRESTRoutes` function in `modules/apps/29-fee` has been removed. ### IBC testing package The `MockIBCApp` type has been renamed to `IBCApp` (and the corresponding constructor function to `NewIBCApp`). This has resulted therefore in: * The `IBCApp` field of the `*IBCModule` in `testing/mock` to change its type as well to `*IBCApp`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type IBCModule struct { appModule *AppModule - IBCApp *MockIBCApp / base application of an IBC middleware stack + IBCApp *IBCApp / base application of an IBC middleware stack } ``` * The `app` parameter to `*NewIBCModule` in `testing/mock` to change its type as well to `*IBCApp`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewIBCModule( appModule *AppModule, - app *MockIBCApp + app *IBCApp ) IBCModule ``` The `MockEmptyAcknowledgement` type has been renamed to `EmptyAcknowledgement` (and the corresponding constructor function to `NewEmptyAcknowledgement`). The `TestingApp` interface in `testing` has gone through some modifications: * The return type of the function `GetStakingKeeper` is not the concrete type `stakingkeeper.Keeper` anymore (where `stakingkeeper` is an import alias for `"github.com/cosmos/cosmos-sdk/x/staking/keeper"`), but it has been changed to the interface `ibctestingtypes.StakingKeeper` (where `ibctestingtypes` is an import alias for `""github.com/cosmos/ibc-go/v5/testing/types"`). See this [PR](https://github.com/cosmos/ibc-go/pull/2028) for more details. The `StakingKeeper` interface is defined as: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type StakingKeeper interface { GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) } ``` * The return type of the function `LastCommitID` has changed to `storetypes.CommitID` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`). See the following `git diff` for more details: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type TestingApp interface { abci.Application / ibc-go additions GetBaseApp() *baseapp.BaseApp - GetStakingKeeper() stakingkeeper.Keeper + GetStakingKeeper() ibctestingtypes.StakingKeeper GetIBCKeeper() *keeper.Keeper GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper GetTxConfig() client.TxConfig / Implemented by SimApp AppCodec() codec.Codec / Implemented by BaseApp - LastCommitID() sdk.CommitID + LastCommitID() storetypes.CommitID LastBlockHeight() int64 } ``` The `powerReduction` parameter of the function `SetupWithGenesisValSet` in `testing` is now of type `math.Int` (`"cosmossdk.io/math"`): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func SetupWithGenesisValSet( t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, - powerReduction sdk.Int, + powerReduction math.Int, balances ...banktypes.Balance ) TestingApp ``` The `accAmt` parameter of the functions * `AddTestAddrsFromPubKeys` , * `AddTestAddrs` * and `AddTestAddrsIncremental` in `testing/simapp` are now of type `math.Int` (`"cosmossdk.io/math"`): ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func AddTestAddrsFromPubKeys( app *SimApp, ctx sdk.Context, pubKeys []cryptotypes.PubKey, - accAmt sdk.Int, + accAmt math.Int ) func addTestAddrs( app *SimApp, ctx sdk.Context, accNum int, - accAmt sdk.Int, + accAmt math.Int, strategy GenerateAccountStrategy ) []sdk.AccAddress func AddTestAddrsIncremental( app *SimApp, ctx sdk.Context, accNum int, - accAmt sdk.Int, + accAmt math.Int ) []sdk.AccAddress ``` The `RegisterRESTRoutes` function in `testing/mock` has been removed. ## Relayers * No relevant changes were made in this release. ## IBC Light Clients ### ICS02 - Client The `key` parameter of the `NewKeeper` function in `modules/core/02-client/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, - key sdk.StoreKey, + key storetypes.StoreKey, paramSpace paramtypes.Subspace, sk types.StakingKeeper, uk types.UpgradeKeeper ) Keeper ``` # IBC-Go v5 to v6 Source: https://docs.cosmos.network/ibc/latest/migrations/v5-to-v6 This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. There are four sections based on the four potential user groups of this document: * Chains * IBC Apps * Relayers * IBC Light Clients **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. ## Chains The `ibc-go/v6` release introduces a new set of migrations for `27-interchain-accounts`. Ownership of ICS27 channel capabilities is transferred from ICS27 authentication modules and will now reside with the ICS27 controller submodule moving forward. For chains which contain a custom authentication module using the ICS27 controller submodule this requires a migration function to be included in the chain upgrade handler. A subsequent migration handler is run automatically, asserting the ownership of ICS27 channel capabilities has been transferred successfully. This migration is not required for chains which *do not* contain a custom authentication module using the ICS27 controller submodule. This migration facilitates the addition of the ICS27 controller submodule `MsgServer` which provides a standardised approach to integrating existing forms of authentication such as `x/gov` and `x/group` provided by the Cosmos SDK. For more information please refer to the ICS27 controller submodule documentation. ### Upgrade proposal Please refer to [PR #2383](https://github.com/cosmos/ibc-go/pull/2383) for integrating the ICS27 channel capability migration logic or follow the steps outlined below: 1. Add the upgrade migration logic to chain distribution. This may be, for example, maintained under a package `app/upgrades/v6`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} package v6 import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" v6 "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/migrations/v6" ) const ( UpgradeName = "v6" ) func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, cdc codec.BinaryCodec, capabilityStoreKey *storetypes.KVStoreKey, capabilityKeeper *capabilitykeeper.Keeper, moduleName string, ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { if err := v6.MigrateICS27ChannelCapability(ctx, cdc, capabilityStoreKey, capabilityKeeper, moduleName); err != nil { return nil, err } return mm.RunMigrations(ctx, configurator, vm) } } ``` 2. Set the upgrade handler in `app.go`. The `moduleName` parameter refers to the authentication module's `ScopedKeeper` name. This is the name provided upon instantiation in `app.go` via the [`x/capability` keeper `ScopeToModule(moduleName string)`](https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/capability/keeper/keeper.go#L70) method. [See here for an example in `simapp`](https://github.com/cosmos/ibc-go/blob/v5.0.0/testing/simapp/app.go#L304). ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.UpgradeKeeper.SetUpgradeHandler( v6.UpgradeName, v6.CreateUpgradeHandler( app.mm, app.configurator, app.appCodec, app.keys[capabilitytypes.ModuleName], app.CapabilityKeeper, >>>> moduleName <<<<, ), ) ``` ## IBC Apps ### ICS27 - Interchain Accounts #### Controller APIs In previous releases of ibc-go, chain developers integrating the ICS27 interchain accounts controller functionality were expected to create a custom `Base Application` referred to as an authentication module, see the section [Building an authentication module](/ibc/latest/apps/interchain-accounts/auth-modules) from the documentation. The `Base Application` was intended to be composed with the ICS27 controller submodule `Keeper` and facilitate many forms of message authentication depending on a chain's particular use case. Prior to ibc-go v6 the controller submodule exposed only these two functions (to which we will refer as the legacy APIs): * [`RegisterInterchainAccount`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L19) * [`SendTx`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay.go#L18) However, these functions have now been deprecated in favour of the new controller submodule `MsgServer` and will be removed in later releases. Both APIs remain functional and maintain backwards compatibility in ibc-go v6, however consumers of these APIs are now recommended to follow the message passing paradigm outlined in Cosmos SDK [ADR 031](https://github.com/cosmos/cosmos-sdk/tree/main/docs/architecture#adr-031) and [ADR 033](https://github.com/cosmos/cosmos-sdk/tree/main/docs/architecture#adr-033). This is facilitated by the Cosmos SDK [`MsgServiceRouter`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/msg_service_router.go#L17) and chain developers creating custom application logic can now omit the ICS27 controller submodule `Keeper` from their module and instead depend on message routing. Depending on the use case, developers of custom authentication modules face one of three scenarios: auth-module-decision-tree.png **My authentication module needs to access IBC packet callbacks** Application developers that wish to consume IBC packet callbacks and react upon packet acknowledgements **must** continue using the controller submodule's legacy APIs. The authentication modules will not need a `ScopedKeeper` anymore, though, because the channel capability will be claimed by the controller submodule. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the authentication module's `ScopedKeeper` (`scopedICAAuthKeeper`) is not needed anymore and can be removed for the argument list of the keeper constructor function, as shown here: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ICAAuthKeeper = icaauthkeeper.NewKeeper( appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper, - scopedICAAuthKeeper, ) ``` Please note that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. Therefore the authentication module's `ScopedKeeper` cannot be completely removed from the chain code until the migration has run. In the future, the use of the legacy APIs for accessing packet callbacks will be replaced by IBC Actor Callbacks (see [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) for more details) and it will also be possible to access them with the `MsgServiceRouter`. **My authentication module does not need access to IBC packet callbacks** The authentication module can migrate from using the legacy APIs and it can be composed instead with the `MsgServiceRouter`, so that the authentication module is able to pass messages to the controller submodule's `MsgServer` to register interchain accounts and send packets to the interchain account. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the ICS27 controller submodule keeper (`ICAControllerKeeper`) and authentication module scoped keeper (`scopedICAAuthKeeper`) are not needed anymore and can be replaced with the `MsgServiceRouter`, as shown here: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ICAAuthKeeper = icaauthkeeper.NewKeeper( appCodec, keys[icaauthtypes.StoreKey], - app.ICAControllerKeeper, - scopedICAAuthKeeper, + app.MsgServiceRouter(), ) ``` In your authentication module you can route messages to the controller submodule's `MsgServer` instead of using the legacy APIs. For example, for registering an interchain account: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - if err := keeper.icaControllerKeeper.RegisterInterchainAccount( - ctx, - connectionID, - owner.String(), - version, - ); err != nil { - return err - } + msg := controllertypes.NewMsgRegisterInterchainAccount( + connectionID, + owner.String(), + version, + ) + handler := keeper.msgRouter.Handler(msg) + res, err := handler(ctx, msg) + if err != nil { + return err + } ``` where `controllertypes` is an import alias for `"github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types"`. In addition, in this use case the authentication module does not need to implement the `IBCModule` interface anymore. **I do not need a custom authentication module anymore** If your authentication module does not have any extra functionality compared to the default authentication module added in ibc-go v6 (the `MsgServer`), or if you can use a generic authentication module, such as the `x/auth`, `x/gov` or `x/group` modules from the Cosmos SDK (v0.46 and later), then you can remove your authentication module completely and use instead the gRPC endpoints of the `MsgServer` or the CLI added in ibc-go v6. Please remember that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. #### Host params The ICS27 host submodule default params have been updated to include the `AllowAllHostMsgs` wildcard `*`. This enables execution of any `sdk.Msg` type for ICS27 registered on the host chain `InterfaceRegistry`. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / AllowAllHostMsgs holds the string key that allows all message types on interchain accounts host module const AllowAllHostMsgs = "*" ... / DefaultParams is the default parameter configuration for the host submodule func DefaultParams() Params { - return NewParams(DefaultHostEnabled, nil) + return NewParams(DefaultHostEnabled, []string{AllowAllHostMsgs}) } ``` #### API breaking changes `SerializeCosmosTx` takes in a `[]proto.Message` instead of `[]sdk.Message`. This allows for the serialization of proto messages without requiring the fulfillment of the `sdk.Msg` interface. The `27-interchain-accounts` genesis types have been moved to their own package: `modules/apps/27-interchain-accounts/genesis/types`. This change facilitates the addition of the ICS27 controller submodule `MsgServer` and avoids cyclic imports. This should have minimal disruption to chain developers integrating `27-interchain-accounts`. The ICS27 host submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper` now includes an additional parameter of type `ICS4Wrapper`. This provides the host submodule with the ability to correctly unwrap channel versions in the event of a channel reopening handshake. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, - channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, + ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, accountKeeper icatypes.AccountKeeper, scopedKeeper icatypes.ScopedKeeper, msgRouter icatypes.MessageRouter, ) Keeper ``` ### ICS29 - `NewKeeper` API change The `NewKeeper` function of ICS29 has been updated to remove the `paramSpace` parameter as it was unused. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewKeeper( - cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, - ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, + cdc codec.BinaryCodec, key storetypes.StoreKey, + ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, ) Keeper { ``` ### ICS20 - `SendTransfer` is no longer exported The `SendTransfer` function of ICS20 has been removed. IBC transfers should now be initiated with `MsgTransfer` and routed to the ICS20 `MsgServer`. See below for example: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if handler := msgRouter.Handler(msgTransfer); handler != nil { if err := msgTransfer.ValidateBasic(); err != nil { return nil, err } res, err := handler(ctx, msgTransfer) if err != nil { return nil, err } } ``` ### ICS04 - `SendPacket` API change The `SendPacket` API has been simplified: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / SendPacket is called by a module in order to send an IBC packet on a channel func (k Keeper) SendPacket( ctx sdk.Context, channelCap *capabilitytypes.Capability, - packet exported.PacketI, -) error { + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { ``` Callers no longer need to pass in a pre-constructed packet. The destination port/channel identifiers and the packet sequence will be determined by core IBC. `SendPacket` will return the packet sequence. ### IBC testing package The `SendPacket` API has been simplified: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / SendPacket is called by a module in order to send an IBC packet on a channel func (k Keeper) SendPacket( ctx sdk.Context, channelCap *capabilitytypes.Capability, - packet exported.PacketI, -) error { + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { ``` Callers no longer need to pass in a pre-constructed packet. `SendPacket` will return the packet sequence. ## Relayers * No relevant changes were made in this release. ## IBC Light Clients * No relevant changes were made in this release. # IBC-Go v6 to v7 Source: https://docs.cosmos.network/ibc/latest/migrations/v6-to-v7 This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. Any changes that must be done by a user of ibc-go should be documented here. There are four sections based on the four potential user groups of this document: * Chains * IBC Apps * Relayers * IBC Light Clients **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. ## Chains Chains will perform automatic migrations to remove existing localhost clients and to migrate the solomachine to v3 of the protobuf definition. An optional upgrade handler has been added to prune expired tendermint consensus states. It may be used during any upgrade (from v7 onwards). Add the following to the function call to the upgrade handler in `app/app.go`, to perform the optional state pruning. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( / ... ibctmmigrations "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint/migrations" ) / ... app.UpgradeKeeper.SetUpgradeHandler( upgradeName, func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { / prune expired tendermint consensus states to save storage space _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, app.Codec, app.IBCKeeper.ClientKeeper) if err != nil { return nil, err } return app.mm.RunMigrations(ctx, app.configurator, fromVM) }, ) ``` Checkout the logs to see how many consensus states are pruned. ### Light client registration Chains must explicitly register the types of any light client modules it wishes to integrate. #### Tendermint registration To register the tendermint client, modify the `app.go` file to include the tendermint `AppModuleBasic`: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( / ... + ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" ) / ... ModuleBasics = module.NewBasicManager( ... ibc.AppModuleBasic{}, + ibctm.AppModuleBasic{}, ... ) ``` It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2825) which added the `AppModuleBasic` for the tendermint client. #### Solo machine registration To register the solo machine client, modify the `app.go` file to include the solo machine `AppModuleBasic`: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( / ... + solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" ) / ... ModuleBasics = module.NewBasicManager( ... ibc.AppModuleBasic{}, + solomachine.AppModuleBasic{}, ... ) ``` It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2826) which added the `AppModuleBasic` for the solo machine client. ### Testing package API The `SetChannelClosed` utility method in `testing/endpoint.go` has been updated to `SetChannelState`, which will take a `channeltypes.State` argument so that the `ChannelState` can be set to any of the possible channel states. ## IBC Apps * No relevant changes were made in this release. ## Relayers * No relevant changes were made in this release. ## IBC Light Clients ### `ClientState` interface changes The `VerifyUpgradeAndUpdateState` function has been modified. The client state and consensus state return values have been removed. Light clients **must** handle all management of client and consensus states including the setting of updated client state and consensus state in the client store. The `Initialize` method is now expected to set the initial client state, consensus state and any client-specific metadata in the provided store upon client creation. The `CheckHeaderAndUpdateState` method has been split into 4 new methods: * `VerifyClientMessage` verifies a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. * `CheckForMisbehaviour` checks for evidence of a misbehaviour in `Header` or `Misbehaviour` types. * `UpdateStateOnMisbehaviour` performs appropriate state changes on a `ClientState` given that misbehaviour has been detected and verified. * `UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. An error is returned if `ClientMessage` is of type `Misbehaviour`. Upon successful update, a list containing the updated consensus state height is returned. The `CheckMisbehaviourAndUpdateState` function has been removed from `ClientState` interface. This functionality is now encapsulated by the usage of `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour`. The function `GetTimestampAtHeight` has been added to the `ClientState` interface. It should return the timestamp for a consensus state associated with the provided height. Prior to ibc-go/v7 the `ClientState` interface defined a method for each data type which was being verified in the counterparty state store. The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS 24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the marshalled value `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. See below for an example of how ibc-go now performs channel state verification. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} merklePath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID)) merklePath, err := commitmenttypes.ApplyPrefix(connection.GetCounterparty().GetPrefix(), merklePath) if err != nil { return err } channelEnd, ok := channel.(channeltypes.Channel) if !ok { return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid channel type %T", channel) } bz, err := k.cdc.Marshal(&channelEnd) if err != nil { return err } if err := clientState.VerifyMembership( ctx, clientStore, k.cdc, height, 0, 0, / skip delay period checks for non-packet processing verification proof, merklePath, bz, ); err != nil { return sdkerrors.Wrapf(err, "failed channel state verification for client (%s)", clientID) } ``` ### `Header` and `Misbehaviour` `exported.Header` and `exported.Misbehaviour` interface types have been merged and renamed to `ClientMessage` interface. `GetHeight` function has been removed from `exported.Header` and thus is not included in the `ClientMessage` interface ### `ConsensusState` The `GetRoot` function has been removed from consensus state interface since it was not used by core IBC. ### Client keeper Keeper function `CheckMisbehaviourAndUpdateState` has been removed since function `UpdateClient` can now handle updating `ClientState` on `ClientMessage` type which can be any `Misbehaviour` implementations. ### SDK message `MsgSubmitMisbehaviour` is deprecated since `MsgUpdateClient` can now submit a `ClientMessage` type which can be any `Misbehaviour` implementations. The field `header` in `MsgUpdateClient` has been renamed to `client_message`. ## Solomachine The `06-solomachine` client implementation has been simplified in ibc-go/v7. In-place store migrations have been added to migrate solomachine clients from `v2` to `v3`. ### `ClientState` The `ClientState` protobuf message definition has been updated to remove the deprecated `bool` field `allow_update_after_proposal`. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message ClientState { option (gogoproto.goproto_getters) = false; uint64 sequence = 1; bool is_frozen = 2 [(gogoproto.moretags) = "yaml:\"is_frozen\""]; ConsensusState consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; - bool allow_update_after_proposal = 4 [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; } ``` ### `Header` and `Misbehaviour` The `06-solomachine` protobuf message `Header` has been updated to remove the `sequence` field. This field was seen as redundant as the implementation can safely rely on the `sequence` value maintained within the `ClientState`. ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Header { option (gogoproto.goproto_getters) = false; - uint64 sequence = 1; - uint64 timestamp = 2; - bytes signature = 3; - google.protobuf.Any new_public_key = 4 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; - string new_diversifier = 5 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; + uint64 timestamp = 1; + bytes signature = 2; + google.protobuf.Any new_public_key = 3 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; + string new_diversifier = 4 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; } ``` Similarly, the `Misbehaviour` protobuf message has been updated to remove the `client_id` field. ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Misbehaviour { option (gogoproto.goproto_getters) = false; - string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; - uint64 sequence = 2; - SignatureAndData signature_one = 3 [(gogoproto.moretags) = "yaml:\"signature_one\""]; - SignatureAndData signature_two = 4 [(gogoproto.moretags) = "yaml:\"signature_two\""]; + uint64 sequence = 1; + SignatureAndData signature_one = 2 [(gogoproto.moretags) = "yaml:\"signature_one\""]; + SignatureAndData signature_two = 3 [(gogoproto.moretags) = "yaml:\"signature_two\""]; } ``` ### `SignBytes` Most notably, the `SignBytes` protobuf definition has been modified to replace the `data_type` field with a new field, `path`. The `path` field is defined as `bytes` and represents a serialized [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements) standardized key path under which the `data` is stored. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message SignBytes { option (gogoproto.goproto_getters) = false; uint64 sequence = 1; uint64 timestamp = 2; string diversifier = 3; - DataType data_type = 4 [(gogoproto.moretags) = "yaml:\"data_type\""]; + bytes path = 4; bytes data = 5; } ``` The `DataType` enum and all associated data types have been removed, greatly reducing the number of message definitions and complexity in constructing the `SignBytes` message type. Likewise, solomachine implementations must now use the serialized `path` value when constructing `SignatureAndData` for signature verification of `SignBytes` data. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message SignatureAndData { option (gogoproto.goproto_getters) = false; bytes signature = 1; - DataType data_type = 2 [(gogoproto.moretags) = "yaml:\"data_type\""]; + bytes path = 2; bytes data = 3; uint64 timestamp = 4; } ``` For more information, please refer to [ADR-007](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/docs/architecture/adr-007-solomachine-signbytes.md). ### IBC module constants IBC module constants have been moved from the `host` package to the `exported` package. Any usages will need to be updated. ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( / ... - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" / ... ) - host.ModuleName + ibcexported.ModuleName - host.StoreKey + ibcexported.StoreKey - host.QuerierRoute + ibcexported.QuerierRoute - host.RouterKey + ibcexported.RouterKey ``` ## Upgrading to Cosmos SDK 0.47 The following should be considered as complementary to [Cosmos SDK v0.47 UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc2/UPGRADING.md). ### Protobuf Protobuf code generation, linting and formatting have been updated to leverage the `ghcr.io/cosmos/proto-builder:0.11.5` docker container. IBC protobuf definitions are now packaged and published to [buf.build/cosmos/ibc](https://buf.build/cosmos/ibc) via CI workflows. The `third_party/proto` directory has been removed in favour of dependency management using [buf.build](https://docs.buf.build/introduction). ### App modules Legacy APIs of the `AppModule` interface have been removed from ibc-go modules. For example, for ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - / Route implements the AppModule interface - func (am AppModule) Route() sdk.Route { - return sdk.Route{} - } - - / QuerierRoute implements the AppModule interface - func (AppModule) QuerierRoute() string { - return types.QuerierRoute - } - - / LegacyQuerierHandler implements the AppModule interface - func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { - return nil - } - - / ProposalContents doesn't return any content functions for governance proposals. - func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { - return nil - } ``` ### Imports Imports for ics23 have been updated as the repository have been migrated from confio to cosmos. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( / ... - ics23 "github.com/confio/ics23/go" + ics23 "github.com/cosmos/ics23/go" / ... ) ``` Imports for gogoproto have been updated. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} import ( / ... - "github.com/gogo/protobuf/proto" + "github.com/cosmos/gogoproto/proto" / ... ) ``` # IBC-Go v7 to v7.1 Source: https://docs.cosmos.network/ibc/latest/migrations/v7-to-v7_1 This guide provides instructions for migrating to version v7.1.0 of ibc-go. This guide provides instructions for migrating to version `v7.1.0` of ibc-go. There are four sections based on the four potential user groups of this document: * [Migrating from v7 to v7.1](#migrating-from-v7-to-v71) * [Chains](#chains) * [IBC Apps](#ibc-apps) * [Relayers](#relayers) * [IBC Light Clients](#ibc-light-clients) **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. ## Chains In the previous release of ibc-go, the localhost `v1` light client module was deprecated and removed. The ibc-go `v7.1.0` release introduces `v2` of the 09-localhost light client module. An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/core/module.go#L127-L145) is configured in the core IBC module to set the localhost `ClientState` and sentinel `ConnectionEnd` in state. In order to use the 09-localhost client chains must update the `AllowedClients` parameter in the 02-client submodule of core IBC. This can be configured directly in the application upgrade handler or alternatively updated via the legacy governance parameter change proposal. We **strongly** recommend chains to perform this action so that intra-ledger communication can be carried out using the familiar IBC interfaces. See the upgrade handler code sample provided below or [follow this link](https://github.com/cosmos/ibc-go/blob/v7.2.0/testing/simapp/upgrades/upgrades.go#L85) for the upgrade handler used by the ibc-go simapp. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func CreateV7LocalhostUpgradeHandler( mm *module.Manager, configurator module.Configurator, clientKeeper clientkeeper.Keeper, ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { / explicitly update the IBC 02-client params, adding the localhost client type params := clientKeeper.GetParams(ctx) params.AllowedClients = append(params.AllowedClients, exported.Localhost) clientKeeper.SetParams(ctx, params) return mm.RunMigrations(ctx, configurator, vm) } } ``` ### Transfer migration An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/apps/transfer/module.go#L111-L113) is configured in the transfer module to set the total amount in escrow for all denominations of coins that have been sent out. For each denomination a state entry is added with the total amount of coins in escrow regardless of the channel from which they were transferred. ## IBC Apps * No relevant changes were made in this release. ## Relayers The event attribute `packet_connection` (`connectiontypes.AttributeKeyConnection`) has been deprecated. Please use the `connection_id` attribute (`connectiontypes.AttributeKeyConnectionID`) which is emitted by all channel events. Only send packet, receive packet, write acknowledgement, and acknowledge packet events used `packet_connection` previously. ## IBC Light Clients * No relevant changes were made in this release. # IBC-Go v7 to v8 Source: https://docs.cosmos.network/ibc/latest/migrations/v7-to-v8 This guide provides instructions for migrating to version v8.0.0 of ibc-go. This guide provides instructions for migrating to version `v8.0.0` of ibc-go. There are four sections based on the four potential user groups of this document: * [Migrating from v7 to v8](#migrating-from-v7-to-v8) * [Chains](#chains) * [Cosmos SDK v0.50 upgrade](#cosmos-sdk-v050-upgrade) * [Authority](#authority) * [Testing package](#testing-package) * [Params migration](#params-migration) * [Governance V1 migration](#governance-v1-migration) * [Transfer migration](#transfer-migration) * [IBC Apps](#ibc-apps) * [ICS20 - Transfer](#ics20---transfer) * [ICS27 - Interchain Accounts](#ics27---interchain-accounts) * [Relayers](#relayers) * [IBC Light Clients](#ibc-light-clients) **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. ## Chains The type of the `PortKeeper` field of the IBC keeper have been changed to `*portkeeper.Keeper`: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Keeper defines each ICS keeper for IBC type Keeper struct { / implements gRPC QueryServer interface types.QueryServer cdc codec.BinaryCodec ClientKeeper clientkeeper.Keeper ConnectionKeeper connectionkeeper.Keeper ChannelKeeper channelkeeper.Keeper - PortKeeper portkeeper.Keeper + PortKeeper *portkeeper.Keeper Router *porttypes.Router authority string } ``` See [this PR](https://github.com/cosmos/ibc-go/pull/4703/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a) for the changes required in `app.go`. An extra parameter `totalEscrowed` of type `sdk.Coins` has been added to transfer module's [`NewGenesisState` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/types/genesis.go#L10). This parameter specifies the total amount of tokens that are in the module's escrow accounts. ### Cosmos SDK v0.50 upgrade Version `v8.0.0` of ibc-go upgrades to Cosmos SDK v0.50. Please follow the [Cosmos SDK v0.50 upgrading guide](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/UPGRADING.md) to account for its API breaking changes. ### Authority An authority identifier (e.g. an address) needs to be passed in the `NewKeeper` functions of the following keepers: * You must pass the `authority` to the ica/host keeper (implemented in [#3520](https://github.com/cosmos/ibc-go/pull/3520)). See [diff](https://github.com/cosmos/ibc-go/pull/3520/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go / ICA Host keeper app.ICAHostKeeper = icahostkeeper.NewKeeper( appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), app.IBCFeeKeeper, / use ics29 fee as ics4Wrapper in middleware stack app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` * You must pass the `authority` to the ica/controller keeper (implemented in [#3590](https://github.com/cosmos/ibc-go/pull/3590)). See [diff](https://github.com/cosmos/ibc-go/pull/3590/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go / ICA Controller keeper app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), app.IBCFeeKeeper, / use ics29 fee as ics4Wrapper in middleware stack app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, scopedICAControllerKeeper, app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` * You must pass the `authority` to the ibctransfer keeper (implemented in [#3553](https://github.com/cosmos/ibc-go/pull/3553)). See [diff](https://github.com/cosmos/ibc-go/pull/3553/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go / Create Transfer Keeper and pass IBCFeeKeeper as expected Channel and PortKeeper / since fee middleware will wrap the IBCKeeper for underlying application. app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), app.IBCFeeKeeper, / ISC4 Wrapper: fee IBC middleware app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` * You should pass the `authority` to the IBC keeper (implemented in [#3640](https://github.com/cosmos/ibc-go/pull/3640) and [#3650](https://github.com/cosmos/ibc-go/pull/3650)). See [diff](https://github.com/cosmos/ibc-go/pull/3640/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go / IBC Keepers app.IBCKeeper = ibckeeper.NewKeeper( appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` The authority determines the transaction signer allowed to execute certain messages (e.g. `MsgUpdateParams`). ### Testing package * The function `SetupWithGenesisAccounts` has been removed. * The function [`RelayPacketWithResults`](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/path.go#L66) has been added. This function returns the result of the packet receive transaction, the acknowledgement written on the receiving chain, an error if a relay step fails or the packet commitment does not exist on either chain. ### Params migration Params are now self managed in the following submodules: * ica/controller [#3590](https://github.com/cosmos/ibc-go/pull/3590) * ica/host [#3520](https://github.com/cosmos/ibc-go/pull/3520) * ibc/connection [#3650](https://github.com/cosmos/ibc-go/pull/3650) * ibc/client [#3640](https://github.com/cosmos/ibc-go/pull/3640) * ibc/transfer [#3553](https://github.com/cosmos/ibc-go/pull/3553) Each module has a corresponding `MsgUpdateParams` message with a `Params` which can be specified in full to update the modules' `Params`. Legacy params subspaces must still be initialised in app.go in order to successfully migrate from \`x/params\`\` to the new self-contained approach. See reference [this](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/simapp/app.go#L1007-L1012) for reference. For new chains which do not rely on migration of parameters from `x/params`, an expected interface has been added for each module. This allows chain developers to provide `nil` as the `legacySubspace` argument to `NewKeeper` functions. ### Governance V1 migration Proposals have been migrated to [gov v1 messages](/sdk/latest/modules/gov/README#messages) (see [#4620](https://github.com/cosmos/ibc-go/pull/4620)). The proposal `ClientUpdateProposal` has been deprecated and [`MsgRecoverClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L121-L134) should be used instead. Likewise, the proposal `UpgradeProposal` has been deprecated and [`MsgIBCSoftwareUpgrade`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L139-L154) should be used instead. Both proposals will be removed in the next major release. `MsgRecoverClient` and `MsgIBCSoftwareUpgrade` will only be allowed to be executed if the signer is the authority designated at the time of instantiating the IBC keeper. So please make sure that the correct authority is provided to the IBC keeper. Remove the `UpgradeProposalHandler` and `UpdateClientProposalHandler` from the `BasicModuleManager`: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.BasicModuleManager = module.NewBasicManagerFromManager( app.ModuleManager, map[string]module.AppModuleBasic{ genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), govtypes.ModuleName: gov.NewAppModuleBasic( []govclient.ProposalHandler{ paramsclient.ProposalHandler, - ibcclientclient.UpdateClientProposalHandler, - ibcclientclient.UpgradeProposalHandler, }, ), }) ``` Support for in-flight legacy recover client proposals (i.e. `ClientUpdateProposal`) will be made for v8, but chains should use `MsgRecoverClient` only afterwards to avoid in-flight client recovery failing when upgrading to v9. See [this issue](https://github.com/cosmos/ibc-go/issues/4721) for more information. Please note that ibc-go offers facilities to test an ibc-go upgrade: * All e2e tests of the repository can be [run with custom Docker chain images](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#running-tests-with-custom-images). * An [importable workflow](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#importable-workflow) that can be used from any other repository to test chain upgrades. ### Transfer migration An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/module.go#L136) is configured in the transfer module to set the [denomination metadata](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/proto/cosmos/bank/v1beta1/bank.proto#L96-L125) for the IBC denominations of all vouchers minted by the transfer module. ## IBC Apps ### ICS20 - Transfer * The function `IsBound` has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/keeper/keeper.go#L98) and made unexported. ### ICS27 - Interchain Accounts * Functions [`SerializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L32) and [`DeserializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L76) now accept an extra parameter `encoding` of type `string` that specifies the format in which the transaction messages are marshaled. Both [protobuf and proto3 JSON formats](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/metadata.go#L14-L17) are supported. * The function `IsBound` of controller submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/controller/keeper/keeper.go#L111) and made unexported. * The function `IsBound` of host submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/host/keeper/keeper.go#L94) and made unexported. ## Relayers * Getter functions in `MsgChannelOpenInitResponse`, `MsgChannelOpenTryResponse`, `MsgTransferResponse`, `MsgRegisterInterchainAccountResponse` and `MsgSendTxResponse` have been removed. The fields can be accessed directly. * `channeltypes.EventTypeTimeoutPacketOnClose` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) has been removed, since core IBC does not emit any event with this key. * Attribute with key `counterparty_connection_id` has been removed from event with key `connectiontypes.EventTypeConnectionOpenInit` (where `connectiontypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"`) and attribute with key `counterparty_channel_id` has been removed from event with key `channeltypes.EventTypeChannelOpenInit` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) since both (counterparty connection ID and counterparty channel ID) are empty on `ConnectionOpenInit` and `ChannelOpenInit` respectively. * As part of the migration to [governance V1 messages](#governance-v1-migration) the following changes in events have been made: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / IBC client events vars var ( EventTypeCreateClient = "create_client" EventTypeUpdateClient = "update_client" EventTypeUpgradeClient = "upgrade_client" EventTypeSubmitMisbehaviour = "client_misbehaviour" - EventTypeUpdateClientProposal = "update_client_proposal" - EventTypeUpgradeClientProposal = "upgrade_client_proposal" + EventTypeRecoverClient = "recover_client" + EventTypeScheduleIBCSoftwareUpgrade = "schedule_ibc_software_upgrade" EventTypeUpgradeChain = "upgrade_chain" ) ``` ## IBC Light Clients * Functions `Pretty` and `String` of type `MerklePath` have been [removed](https://github.com/cosmos/ibc-go/pull/4459/files#diff-dd94ec1dde9b047c0cdfba204e30dad74a81de202e3b09ac5b42f493153811af). # IBC-Go v7.2 to v7.3 Source: https://docs.cosmos.network/ibc/latest/migrations/v7_2-to-v7_3 This guide provides instructions for migrating to version v7.3.0 of ibc-go. This guide provides instructions for migrating to version `v7.3.0` of ibc-go. There are four sections based on the four potential user groups of this document: * [Migrating from v7.2 to v7.3](#migrating-from-v72-to-v73) * [Chains](#chains) * [IBC Apps](#ibc-apps) * [Relayers](#relayers) * [IBC Light Clients](#ibc-light-clients) **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. ## Chains * No relevant changes were made in this release. ## IBC Apps A set of interfaces have been added that IBC applications may optionally implement. Developers interested in integrating their applications with the [callbacks middleware](/ibc/latest/middleware/callbacks/overview) should implement these interfaces so that the callbacks middleware can retrieve the desired callback addresses on the source and destination chains and execute actions on packet lifecycle events. The interfaces are [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/05-port/types/module.go#L142-L147), [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L43-L52) and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L36-L41). Sample implementations are available for reference. For `transfer`: * [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/ibc_module.go#L303-L313), * [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L85-L105) * and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L74-L83). For `27-interchain-accounts`: * [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268), * [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L94-L114) * and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L78-L92). ## Relayers * No relevant changes were made in this release. ## IBC Light Clients ### 06-solomachine Solo machines are now expected to sign data on a path that 1) does not include a connection prefix (e.g `ibc`) and 2) does not escape any characters. See PR [#4429](https://github.com/cosmos/ibc-go/pull/4429) for more details. We recommend **NOT** using the solo machine light client of versions lower than v7.3.0. # IBC-Go v8 to v8.1 Source: https://docs.cosmos.network/ibc/latest/migrations/v8-to-v8_1 This guide provides instructions for migrating to version v8.1.0 of ibc-go. This guide provides instructions for migrating to version `v8.1.0` of ibc-go. There are four sections based on the four potential user groups of this document: * [Migrating from v8 to v8.1](#migrating-from-v8-to-v81) * [Chains](#chains) * [IBC apps](#ibc-apps) * [Relayers](#relayers) * [IBC light clients](#ibc-light-clients) **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. ## Chains ### `04-channel` params migration Self-managed [params](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/channel.proto#L183-L187) have been added for `04-channel` module. The params include the `upgrade_timeout` that is used in channel upgradability to specify the interval of time during which the counterparty chain must flush all in-flight packets on its end and move to `FLUSH_COMPLETE` state). An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/core/module.go#L162-L166) is configured in the `04-channel` module that sets the default params (with a default upgrade timeout of 10 minutes). The module has a corresponding [`MsgUpdateParams` message](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/tx.proto#L435-L447) with a `Params` field which can be specified in full to update the module's `Params`. ### Fee migration In ibc-go v8.1.0 an improved, more efficient escrow calculation of fees for packet incentivisation has been introduced (see [this issue](https://github.com/cosmos/ibc-go/issues/5509) for more information). Before v8.1.0 the amount escrowed was `(ReckFee + AckFee + TimeoutFee)`; from ibc-go v8.1.0, the calculation is changed to `Max(RecvFee + AckFee, TimeoutFee)`. In order to guarantee that the correct amount of fees are refunded for packets that are in-flight during the upgrade to ibc-go v8.1.0, an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/apps/29-fee/module.go#L113-L115) is configured in the `29-fee` module to refund the leftover fees (i.e `(ReckFee + AckFee + TimeoutFee) - Max(RecvFee + AckFee, TimeoutFee)`) that otherwise would not be refunded when the packet lifecycle completes and the new calculation is used. ## IBC apps * No relevant changes were made in this release. ## Relayers * No relevant changes were made in this release. ## IBC light clients * No relevant changes were made in this release. # IBC-Go v8.1 to v10 Source: https://docs.cosmos.network/ibc/latest/migrations/v8_1-to-v10 This guide provides instructions for migrating to a new version of ibc-go. This guide provides instructions for migrating to a new version of ibc-go. **Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. In addition, for this release, the 08-wasm module has been released as v10, and the callbacks middleware has been moved into the ibc-go module itself. Diff examples are shown after the list of overall changes: * To add support for IBC v2, Chains will need to wire up a new IBC v2 Transfer stack * Chains will need to wire up the new light client modules * Chains will need to update Keeper construction calls to comply with the new signatures * Chains will need to remove the route for the legacy proposal handler for 02-client from their `app/app.go` * Chains will need to remove the capability keeper and all related setup, including the scoped keepers from their `app/app.go` * Chains will need to remove ibc fee middleware (29-fee) * Chains will need, if using this module, to update their imports and usage of `github.com/cosmos/ibc-go/modules/light-clients/08-wasm/` to `github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10` * Chains will need, if using this module, to update their imports and usage of `github.com/cosmos/ibc-go/modules/apps/callbacks` to `github.com/cosmos/ibc-go/v10/modules/apps/callbacks` To add IBC v2 support, wire up a new transfer stack. Example below showing wired up with IBC Callbacks module: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + var ibcv2TransferStack ibcapi.IBCModule + ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) + ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware( + transferv2.NewIBCModule(app.TransferKeeper), + app.IBCKeeper.ChannelKeeperV2, + wasmStackIBCHandler, + app.IBCKeeper.ChannelKeeperV2, + maxCallbackGas, + ) ``` Wire up each light client as a separate module and add them to the client keeper router. Example below for 07-tendermint and 08-wasm: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} + / Light client modules + clientKeeper := app.IBCKeeper.ClientKeeper + storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() + + tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) + + wasmLightClientModule := ibcwasm.NewLightClientModule(app.WasmClientKeeper, storeProvider) + clientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule) ``` Remove ibc fee module name (if used) from module account permissions: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go ... / module account permissions var maccPerms = map[string][]string{ ... - ibcfeetypes.ModuleName: nil, ... } ``` Remove `CapabilityKeeper`, `IBCFeeKeeper` and all `capabilitykeeper.ScopedKeeper` Scoped keepers from the App struct: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / ChainApp extended ABCI application type ChainApp struct { ... - CapabilityKeeper *capabilitykeeper.Keeper ... - IBCFeeKeeper ibcfeekeeper.Keeper ... - ScopedIBCKeeper capabilitykeeper.ScopedKeeper - ScopedICAHostKeeper capabilitykeeper.ScopedKeeper - ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper - ScopedTransferKeeper capabilitykeeper.ScopedKeeper - ScopedIBCFeeKeeper capabilitykeeper.ScopedKeeper ... } ... - app.ScopedIBCKeeper = scopedIBCKeeper - app.ScopedTransferKeeper = scopedTransferKeeper - app.ScopedWasmKeeper = scopedWasmKeeper - app.ScopedICAHostKeeper = scopedICAHostKeeper - app.ScopedICAControllerKeeper = scopedICAControllerKeeper ``` Remove capability and ibc fee middleware store keys from the `NewKVStoreKeys` call: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ... keys := storetypes.NewKVStoreKeys( ... - capabilitytypes.StoreKey, - ibcfeetypes.StoreKey, ... } ``` Remove the in-memory store keys previously used by the capability module: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) ... - app.MountMemoryStores(memKeys) ``` Remove creation of the capability keeper: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - / add capability keeper and ScopeToModule for ibc module - app.CapabilityKeeper = capabilitykeeper.NewKeeper( - appCodec, - keys[capabilitytypes.StoreKey], - memKeys[capabilitytypes.MemStoreKey], - ) - scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) - scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) - scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) - scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) - scopedWasmKeeper := app.CapabilityKeeper.ScopeToModule(wasmtypes.ModuleName) - app.CapabilityKeeper.Seal() ``` Remove the legacy route for the client keeper: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ... govRouter.AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler). - AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). - AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) + AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)) ``` Update Core IBC Keeper constructor: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.IBCKeeper = ibckeeper.NewKeeper( appCodec, - keys[ibcexported.StoreKey], + runtime.NewKVStoreService(keys[ibcexported.StoreKey]), app.GetSubspace(ibcexported.ModuleName), - app.StakingKeeper, app.UpgradeKeeper, - scopedIBCKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` Update IBC Transfer keeper constructor: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, - keys[ibctransfertypes.StoreKey], + runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, - app.IBCKeeper.PortKeeper, + app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, - scopedTransferKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) ``` Update ICA Host keeper constructor, notice the removal of the `WithQueryRouter` call in particular: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ICAHostKeeper = icahostkeeper.NewKeeper( appCodec, - keys[icahosttypes.StoreKey], + runtime.NewKVStoreService(keys[icahosttypes.StoreKey]), app.GetSubspace(icahosttypes.SubModuleName), - app.IBCFeeKeeper, / use ics29 fee as ics4Wrapper in middleware stack app.IBCKeeper.ChannelKeeper, - app.IBCKeeper.PortKeeper, + app.IBCKeeper.ChannelKeeper, app.AccountKeeper, - scopedICAHostKeeper, app.MsgServiceRouter(), + app.GRPCQueryRouter(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) ``` Remove IBC Fee Module keeper: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( - appCodec, keys[ibcfeetypes.StoreKey], - app.IBCKeeper.ChannelKeeper, / may be replaced with IBC middleware - app.IBCKeeper.ChannelKeeper, - app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, - ) ``` Update Transfer stack to remove the fee middleware. The example below shows the correct way to wire up a middleware stack with the IBC callbacks middleware: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create Transfer Stack var transferStack porttypes.IBCModule transferStack = transfer.NewIBCModule(app.TransferKeeper) - transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, wasmStackIBCHandler, maxCallbackGas) - transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + / callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper + / i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper + / Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's + / WriteAcknowledgement. + cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) transferStack = packetforward.NewIBCMiddleware( - transferStack, + cbStack, app.PacketForwardKeeper, 0, packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, ) + app.TransferKeeper.WithICS4Wrapper(cbStack) ``` Remove ibc fee middleware and any empty IBCModule (often dubbed `noAuthzModule`) from the ICA Controller stack creation: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - var noAuthzModule porttypes.IBCModule - icaControllerStack = icacontroller.NewIBCMiddleware(noAuthzModule, app.ICAControllerKeeper) - icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) + icaControllerStack = icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) ``` Remove ibc fee middleware from ICA Host stack creation: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) - icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) ``` Update the module manager creation by removing the capability module, fee module and updating the tendermint app module constructor: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} app.ModuleManager = module.NewManager( ... - capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), ... - ibcfee.NewAppModule(app.IBCFeeKeeper), ... - ibctm.NewAppModule(), + ibctm.NewAppModule(tmLightClientModule), ... ) ``` Remove the capability module and ibc fee middleware from `SetOrderBeginBlockers`, `SetOrderEndBlockers`, `SetOrderInitGenesis` and `SetOrderExportGenesis`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - capabilitytypes.ModuleName, - ibcfeetypes.ModuleName, ``` If you use 08-wasm, you will need to update the go module that is used for `QueryPlugins` and `AcceptListStargateQuerier`. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - wasmLightClientQuerier := ibcwasmtypes.QueryPlugins{ + wasmLightClientQuerier := ibcwasmkeeper.QueryPlugins{ - Stargate: ibcwasmtypes.AcceptListStargateQuerier([]string{ + Stargate: ibcwasmkeeper.AcceptListStargateQuerier([]string{ "/ibc.core.client.v1.Query/ClientState", "/ibc.core.client.v1.Query/ConsensusState", "/ibc.core.connection.v1.Query/Connection", - }), + }, app.GRPCQueryRouter()), } ``` If you use 08-wasm, you will need to use the wasm client keeper rather than the go module to initialize pinned codes: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - if err := ibcwasmkeeper.InitializePinnedCodes(ctx); err != nil { - panic(fmt.Sprintf("ibcwasmkeeper failed initialize pinned codes %s", err)) + if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { + panic(fmt.Sprintf("WasmClientKeeper failed initialize pinned codes %s", err)) + } ``` # Active Channels Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/active-channels The Interchain Accounts module uses either ORDERED or UNORDERED channels. The Interchain Accounts module uses either [ORDERED or UNORDERED](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) channels. When using `ORDERED` channels, the order of transactions when sending packets from a controller to a host chain is maintained. When using `UNORDERED` channels, there is no guarantee that the order of transactions when sending packets from the controller to the host chain is maintained. If no ordering is specified in `MsgRegisterInterchainAccount`, then the default ordering for new ICA channels is `UNORDERED`. > A limitation when using ORDERED channels is that when a packet times out the channel will be closed. In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. When an Interchain Account is registered using `MsgRegisterInterchainAccount`, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (on controller & host chain respectively) the `Active Channel` for this interchain account is stored in state. It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{ connectionID }, icatypes.HostPortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) handler := keeper.msgRouter.Handler(msg) res, err := handler(ctx, msg) if err != nil { return err } ``` Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. It is important to note that once a channel has been opened for a given interchain account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. ## Future improvements Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new channel type that provides ordering of packets without the channel closing in the event of a packet timing out, thus removing the need for `Active Channels` entirely. The following is a list of issues which will provide the infrastructure to make this possible: * [IBC Channel Upgrades](https://github.com/cosmos/ibc-go/issues/1599) * [Implement ORDERED\_ALLOW\_TIMEOUT logic in 04-channel](https://github.com/cosmos/ibc-go/issues/1661) * [Add ORDERED\_ALLOW\_TIMEOUT as supported ordering in 03-connection](https://github.com/cosmos/ibc-go/issues/1662) * [Allow ICA channels to be opened as ORDERED\_ALLOW\_TIMEOUT](https://github.com/cosmos/ibc-go/issues/1663) # Authentication Modules Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/auth-modules ## Synopsis Authentication modules enable application developers to perform custom logic when interacting with the Interchain Accounts controller sumbmodule's `MsgServer`. The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. In ibc-go, authentication modules can communicate with the controller submodule by passing messages through `baseapp`'s `MsgServiceRouter`. To implement an authentication module, the `IBCModule` interface need not be fulfilled; it is only required to fulfill Cosmos SDK's `AppModuleBasic` interface, just like any regular Cosmos SDK application module. The authentication module must: * Authenticate interchain account owners. * Track the associated interchain account address for an owner. * Send packets on behalf of an owner (after authentication). ## Integration into `app.go` file To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](/ibc/latest/apps/interchain-accounts/integration#example-integration). # Client Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/client A user can query and interact with the Interchain Accounts module using the CLI. Use the --help flag to discover the available commands: ## CLI A user can query and interact with the Interchain Accounts module using the CLI. Use the `--help` flag to discover the available commands: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query interchain-accounts --help ``` > Please not that this section does not document all the available commands, but only the ones that deserved extra documentation that was not possible to fit in the command line documentation. ### Controller A user can query and interact with the controller submodule. #### Query The `query` commands allow users to query the controller submodule. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query interchain-accounts controller --help ``` #### Transactions The `tx` commands allow users to interact with the controller submodule. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts controller --help ``` #### `register` The `register` command allows users to register an interchain account on a host chain on the provided connection. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts controller register [connection-id] [flags] ``` During registration a new channel is set up between controller and host. There are two flags available that influence the channel that is created: * `--version` to specify the (JSON-formatted) version string of the channel. For example: `{\"version\":\"ics27-1\",\"encoding\":\"proto3\",\"tx_type\":\"sdk_multi_msg\",\"controller_connection_id\":\"connection-0\",\"host_connection_id\":\"connection-0\"}`. Passing a custom version string is useful if you want to specify, for example, the encoding format of the interchain accounts packet data (either `proto3` or `proto3json`). If not specified the controller submodule will generate a default version string. * `--ordering` to specify the ordering of the channel. Available options are `order_ordered` and `order_unordered` (default if not specified). Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts controller register connection-0 --ordering order_ordered --from cosmos1.. ``` #### `send-tx` The `send-tx` command allows users to send a transaction on the provided connection to be executed using an interchain account on the host chain. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts controller send-tx [connection-id] [path/to/packet_msg.json] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts controller send-tx connection-0 packet-data.json --from cosmos1.. ``` See below for example contents of `packet-data.json`. The CLI handler will unmarshal the following into `InterchainAccountPacketData` appropriately. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "TYPE_EXECUTE_TX", "data": "CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", "memo": "" } ``` Note the `data` field is a base64 encoded byte string as per the tx encoding agreed upon during the channel handshake. A helper CLI is provided in the host submodule which can be used to generate the packet data JSON using the counterparty chain's binary. See the [`generate-packet-data` command](#generate-packet-data) for an example. ### Host A user can query and interact with the host submodule. #### Query The `query` commands allow users to query the host submodule. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query interchain-accounts host --help ``` #### Transactions The `tx` commands allow users to interact with the controller submodule. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts host --help ``` ##### `generate-packet-data` The `generate-packet-data` command allows users to generate protobuf or proto3 JSON encoded interchain accounts packet data for input message(s). The packet data can then be used with the controller submodule's [`send-tx` command](#send-tx). The `--encoding` flag can be used to specify the encoding format (value must be either `proto3` or `proto3json`); if not specified, the default will be `proto3`. The `--memo` flag can be used to include a memo string in the interchain accounts packet data. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts host generate-packet-data [message] ``` Example: ```shell expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx interchain-accounts host generate-packet-data '[{ "@type":"/cosmos.bank.v1beta1.MsgSend", "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", "amount": [ { "denom": "stake", "amount": "1000" } ] }]' --memo memo ``` The command accepts a single `sdk.Msg` or a list of `sdk.Msg`s that will be encoded into the outputs `data` field. Example output: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "type": "TYPE_EXECUTE_TX", "data": "CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", "memo": "memo" } ``` ## gRPC A user can query the interchain account module using gRPC endpoints. ### Controller A user can query the controller submodule using gRPC endpoints. #### `InterchainAccount` The `InterchainAccount` endpoint allows users to query the controller submodule for the interchain account address for a given owner on a particular connection. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"owner":"cosmos1..","connection_id":"connection-0"}' \ localhost:9090 \ ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount ``` #### `Params` The `Params` endpoint users to query the current controller submodule parameters. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibc.applications.interchain_accounts.controller.v1.Query/Params ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ ibc.applications.interchain_accounts.controller.v1.Query/Params ``` ### Host A user can query the host submodule using gRPC endpoints. #### `Params` The `Params` endpoint users to query the current host submodule parameters. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibc.applications.interchain_accounts.host.v1.Query/Params ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ localhost:9090 \ ibc.applications.interchain_accounts.host.v1.Query/Params ``` # Development Use Cases Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/development The initial version of Interchain Accounts allowed for the controller submodule to be extended by providing it with an underlying application which would handle all packet callbacks. That functionality is now being deprecated in favor of alternative approaches. This document will outline potential use cases and redirect each use case to the appropriate documentation. ## Custom authentication Interchain accounts may be associated with alternative types of authentication relative to the traditional public/private key signing. If you wish to develop or use Interchain Accounts with a custom authentication module and do not need to execute custom logic on the packet callbacks, we recommend you use ibc-go v6 or greater and that your custom authentication module interacts with the controller submodule via the [`MsgServer`](/ibc/latest/apps/interchain-accounts/messages). If you wish to consume and execute custom logic in the packet callbacks, then please read the section [Packet callbacks](#packet-callbacks) below. ## Redirection to a smart contract It may be desirable to allow smart contracts to control an interchain account. To facilitate such an action, the controller submodule may be provided an underlying application which redirects to smart contract callers. An improved design has been suggested in [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) which performs this action via middleware. Implementers of this use case are recommended to follow the ADR 008 approach. The underlying application may continue to be used as a short term solution for ADR 008 and the [legacy API](/ibc/latest/apps/interchain-accounts/legacy/auth-modules) should continue to be utilized in such situations. ## Packet callbacks If a developer requires access to packet callbacks for their use case, then they have the following options: 1. Write a smart contract which is connected via an ADR 008 or equivalent IBC application (recommended). 2. Use the controller's underlying application to implement packet callback logic. In the first case, the smart contract should use the [`MsgServer`](/ibc/latest/apps/interchain-accounts/messages). In the second case, the underlying application should use the [legacy API](/ibc/latest/apps/interchain-accounts/legacy/keeper-api). # Integration Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/integration ## Synopsis Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller submodule entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. Interchain Account authentication modules (both custom or generic, such as the `x/gov` or `x/auth` Cosmos SDK modules) can send messages to the controller submodule's [`MsgServer`](/ibc/latest/apps/interchain-accounts/messages) to register interchain accounts and send packets to the interchain account. To accomplish this, the authentication module needs to be composed with `baseapp`'s `MsgServiceRouter`. ica-v6.png ## Example integration ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go / Register the AppModule for the Interchain Accounts module and the authentication module / Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module ModuleBasics = module.NewBasicManager( ... ica.AppModuleBasic{ }, icaauth.AppModuleBasic{ }, ... ) ... / Add module account permissions for the Interchain Accounts module / Only necessary for host chain functionality / Each Interchain Account created on the host chain is derived from the module account created maccPerms = map[string][]string{ ... icatypes.ModuleName: nil, } ... / Add Interchain Accounts Keepers for each submodule used and the authentication module / If a submodule is being statically disabled, the associated Keeper does not need to be added. type App struct { ... ICAControllerKeeper icacontrollerkeeper.Keeper ICAHostKeeper icahostkeeper.Keeper ICAAuthKeeper icaauthkeeper.Keeper ... } ... / Create store keys for each submodule Keeper and the authentication module keys := sdk.NewKVStoreKeys( ... icacontrollertypes.StoreKey, icahosttypes.StoreKey, icaauthtypes.StoreKey, ... ) ... / Create the Keeper for each submodule app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), app.IBCKeeper.ChannelKeeper, / may be replaced with middleware such as ics29 fee app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, app.MsgServiceRouter(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) app.ICAHostKeeper = icahostkeeper.NewKeeper( appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), app.IBCKeeper.ChannelKeeper, / may be replaced with middleware such as ics29 fee app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, app.AccountKeeper, app.MsgServiceRouter(), app.GRPCQueryRouter(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) / Create Interchain Accounts AppModule icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) / Create your Interchain Accounts authentication module app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) / ICA auth AppModule icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) / Create controller IBC application stack and host IBC module as desired icaControllerStack := icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) / Register host and authentication routes ibcRouter. AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) ... / Register Interchain Accounts and authentication module AppModule's app.moduleManager = module.NewManager( ... icaModule, icaAuthModule, ) ... / Add Interchain Accounts to begin blocker logic app.moduleManager.SetOrderBeginBlockers( ... icatypes.ModuleName, ... ) / Add Interchain Accounts to end blocker logic app.moduleManager.SetOrderEndBlockers( ... icatypes.ModuleName, ... ) / Add Interchain Accounts module InitGenesis logic app.moduleManager.SetOrderInitGenesis( ... icatypes.ModuleName, ... ) / initParamsKeeper init params keeper and its subspaces func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { ... paramsKeeper.Subspace(icahosttypes.SubModuleName) paramsKeeper.Subspace(icacontrollertypes.SubModuleName) ... } ``` If no custom athentication module is needed and a generic Cosmos SDK authentication module can be used, then from the sample integration code above all references to `ICAAuthKeeper` and `icaAuthModule` can be removed. That's it, the following code would not be needed: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create your Interchain Accounts authentication module app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) / ICA auth AppModule icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) ``` ### Using submodules exclusively As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](/ibc/latest/apps/interchain-accounts/parameters). The following snippets show basic examples of statically disabling submodules using `app.go`. #### Disabling controller chain functionality ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create Interchain Accounts AppModule omitting the controller keeper icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) / Create host IBC Module icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) / Register host route ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) ``` #### Disabling host chain functionality ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create Interchain Accounts AppModule omitting the host keeper icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) / Optionally instantiate your custom authentication module if needed, or not otherwise ... / Create controller IBC application stack icaControllerStack := icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) / Register controller route ibcRouter.AddRoute(icacontrollertypes.SubModuleName, icaControllerStack) ``` # Authentication Modules Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/legacy/auth-modules This document is deprecated and will be removed in future releases. ## Deprecation Notice **This document is deprecated and will be removed in future releases**. ## Synopsis Authentication modules play the role of the `Base Application` as described in [ICS-30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. In ibc-go, authentication modules are connected to the controller chain via a middleware stack. The controller submodule is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller submodule as the base application of the middleware stack. To implement an authentication module, the `IBCModule` interface must be fulfilled. By implementing the controller submodule as middleware, any amount of authentication modules can be created and connected to the controller submodule without writing redundant code. The authentication module must: * Authenticate interchain account owners. * Track the associated interchain account address for an owner. * Send packets on behalf of an owner (after authentication). ## `IBCModule` implementation The following `IBCModule` callbacks must be implemented with appropriate custom logic: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / OnChanOpenInit implements the IBCModule interface func (im IBCModule) OnChanOpenInit( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, counterparty channeltypes.Counterparty, version string, ) (string, error) { / perform custom logic return version, nil } / OnChanOpenAck implements the IBCModule interface func (im IBCModule) OnChanOpenAck( ctx sdk.Context, portID, channelID string, counterpartyVersion string, ) error { / perform custom logic return nil } / OnChanCloseConfirm implements the IBCModule interface func (im IBCModule) OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, ) error { / perform custom logic return nil } / OnAcknowledgementPacket implements the IBCModule interface func (im IBCModule) OnAcknowledgementPacket( ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, ) error { / perform custom logic return nil } / OnTimeoutPacket implements the IBCModule interface. func (im IBCModule) OnTimeoutPacket( ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, ) error { / perform custom logic return nil } ``` The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller submodule so they may error or panic. That is because in Interchain Accounts, the channel handshake is always initiated on the controller chain and packets are always sent to the host chain and never to the controller chain. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / OnChanOpenTry implements the IBCModule interface func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, counterparty channeltypes.Counterparty, counterpartyVersion string, ) (string, error) { panic("UNIMPLEMENTED") } / OnChanOpenConfirm implements the IBCModule interface func (im IBCModule) OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, ) error { panic("UNIMPLEMENTED") } / OnChanCloseInit implements the IBCModule interface func (im IBCModule) OnChanCloseInit( ctx sdk.Context, portID, channelID string, ) error { panic("UNIMPLEMENTED") } / OnRecvPacket implements the IBCModule interface. A successful acknowledgement / is returned if the packet data is successfully decoded and the receive application / logic returns without error. func (im IBCModule) OnRecvPacket( ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, ) ibcexported.Acknowledgement { panic("UNIMPLEMENTED") } ``` ## `OnAcknowledgementPacket` Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. The acknowledgement bytes contain either the response of the execution of the message(s) on the host chain or an error. They will be passed to the auth module via the `OnAcknowledgementPacket` callback. Auth modules are expected to know how to decode the acknowledgement. If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} var ack channeltypes.Acknowledgement if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { return err } txMsgData := &sdk.TxMsgData{ } if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { return err } ``` If the `txMsgData.Data` field is non nil, the host chain is using SDK version `<=` v0.45. The auth module should interpret the `txMsgData.Data` as follows: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} switch len(txMsgData.Data) { case 0: / see documentation below for SDK 0.46.x or greater default: for _, msgData := range txMsgData.Data { if err := handler(msgData); err != nil { return err } } ... } ``` A handler will be needed to interpret what actions to perform based on the message type sent. A router could be used, or more simply a switch statement. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func handler(msgData sdk.MsgData) error { switch msgData.MsgType { case sdk.MsgTypeURL(&banktypes.MsgSend{ }): msgResponse := &banktypes.MsgSendResponse{ } if err := proto.Unmarshal(msgData.Data, msgResponse }; err != nil { return err } handleBankSendMsg(msgResponse) case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{ }): msgResponse := &stakingtypes.MsgDelegateResponse{ } if err := proto.Unmarshal(msgData.Data, msgResponse }; err != nil { return err } handleStakingDelegateMsg(msgResponse) case sdk.MsgTypeURL(&transfertypes.MsgTransfer{ }): msgResponse := &transfertypes.MsgTransferResponse{ } if err := proto.Unmarshal(msgData.Data, msgResponse }; err != nil { return err } handleIBCTransferMsg(msgResponse) default: return } ``` If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. The auth module should interpret the `txMsgData.Responses` as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ... / switch statement from above case 0: for _, any := range txMsgData.MsgResponses { if err := handleAny(any); err != nil { return err } } } ``` A handler will be needed to interpret what actions to perform based on the type URL of the Any. A router could be used, or more simply a switch statement. It may be possible to deduplicate logic between `handler` and `handleAny`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func handleAny(any *codectypes.Any) error { switch any.TypeURL { case banktypes.MsgSend: msgResponse, err := unpackBankMsgSendResponse(any) if err != nil { return err } handleBankSendMsg(msgResponse) case stakingtypes.MsgDelegate: msgResponse, err := unpackStakingDelegateResponse(any) if err != nil { return err } handleStakingDelegateMsg(msgResponse) case transfertypes.MsgTransfer: msgResponse, err := unpackIBCTransferMsgResponse(any) if err != nil { return err } handleIBCTransferMsg(msgResponse) default: return } ``` ## Integration into `app.go` file To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](/ibc/latest/apps/interchain-accounts/legacy/integration#example-integration). # Integration Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/legacy/integration This document is deprecated and will be removed in future releases. ## Deprecation Notice **This document is deprecated and will be removed in future releases**. ## Synopsis Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. ica-pre-v6.png ## Example integration ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go / Register the AppModule for the Interchain Accounts module and the authentication module / Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module ModuleBasics = module.NewBasicManager( ... ica.AppModuleBasic{ }, icaauth.AppModuleBasic{ }, ... ) ... / Add module account permissions for the Interchain Accounts module / Only necessary for host chain functionality / Each Interchain Account created on the host chain is derived from the module account created maccPerms = map[string][]string{ ... icatypes.ModuleName: nil, } ... / Add Interchain Accounts Keepers for each submodule used and the authentication module / If a submodule is being statically disabled, the associated Keeper does not need to be added. type App struct { ... ICAControllerKeeper icacontrollerkeeper.Keeper ICAHostKeeper icahostkeeper.Keeper ICAAuthKeeper icaauthkeeper.Keeper ... } ... / Create store keys for each submodule Keeper and the authentication module keys := sdk.NewKVStoreKeys( ... icacontrollertypes.StoreKey, icahosttypes.StoreKey, icaauthtypes.StoreKey, ... ) ... ... / Create the Keeper for each submodule app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), app.IBCKeeper.ChannelKeeper, / may be replaced with middleware such as ics29 fee app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.MsgServiceRouter(), ) app.ICAHostKeeper = icahostkeeper.NewKeeper( appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), app.IBCKeeper.ChannelKeeper, / may be replaced with middleware such as ics29 fee app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.MsgServiceRouter(), ) / Create Interchain Accounts AppModule icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) / Create your Interchain Accounts authentication module app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) / ICA auth AppModule icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) / ICA auth IBC Module icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) / Create controller IBC application stack and host IBC module as desired icaControllerStack := icacontroller.NewIBCMiddlewareWithAuth(icaAuthIBCModule, app.ICAControllerKeeper) icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) / Register host and authentication routes ibcRouter. AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). AddRoute(icaauthtypes.ModuleName, icaControllerStack) / Note, the authentication module is routed to the top level of the middleware stack ... / Register Interchain Accounts and authentication module AppModule's app.moduleManager = module.NewManager( ... icaModule, icaAuthModule, ) ... / Add fee middleware to begin blocker logic app.moduleManager.SetOrderBeginBlockers( ... icatypes.ModuleName, ... ) / Add fee middleware to end blocker logic app.moduleManager.SetOrderEndBlockers( ... icatypes.ModuleName, ... ) / Add Interchain Accounts module InitGenesis logic app.moduleManager.SetOrderInitGenesis( ... icatypes.ModuleName, ... ) / initParamsKeeper init params keeper and its subspaces func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { ... paramsKeeper.Subspace(icahosttypes.SubModuleName) paramsKeeper.Subspace(icacontrollertypes.SubModuleName) ... ``` ## Using submodules exclusively As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](/ibc/latest/apps/interchain-accounts/parameters). The following snippets show basic examples of statically disabling submodules using `app.go`. ### Disabling controller chain functionality ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create Interchain Accounts AppModule omitting the controller keeper icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) / Create host IBC Module icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) / Register host route ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) ``` ### Disabling host chain functionality ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create Interchain Accounts AppModule omitting the host keeper icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) / Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) / Create controller IBC application stack icaControllerStack := icacontroller.NewIBCMiddlewareWithAuth(icaAuthIBCModule, app.ICAControllerKeeper) / Register controller and authentication routes ibcRouter. AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). AddRoute(icaauthtypes.ModuleName, icaControllerStack) / Note, the authentication module is routed to the top level of the middleware stack ``` # Keeper API Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/legacy/keeper-api This document is deprecated and will be removed in future releases. ## Deprecation Notice **This document is deprecated and will be removed in future releases**. The controller submodule keeper exposes two legacy functions that allow respectively for custom authentication modules to register interchain accounts and send packets to the interchain account. ## `RegisterInterchainAccount` The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version, channeltypes.UNORDERED); err != nil { return err } return nil ``` The `version` argument is used to support ICS-29 fee middleware for relayer incentivization of ICS-27 packets. The `ordering` argument allows to specify the ordering of the channel that is created; if `NONE` is passed, then the default ordering will be `UNORDERED`. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} icaMetadata := icatypes.Metadata{ Version: icatypes.Version, ControllerConnectionId: controllerConnectionID, HostConnectionId: hostConnectionID, Encoding: icatypes.EncodingProtobuf, TxType: icatypes.TxTypeSDKMultiMsg, } appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) if err != nil { return err } if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion), channeltypes.UNORDERED); err != nil { return err } ``` Similarly, if the application stack is configured to route through ICS-29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS-29 `Metadata` type: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} icaMetadata := icatypes.Metadata{ Version: icatypes.Version, ControllerConnectionId: controllerConnectionID, HostConnectionId: hostConnectionID, Encoding: icatypes.EncodingProtobuf, TxType: icatypes.TxTypeSDKMultiMsg, } appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) if err != nil { return err } feeMetadata := feetypes.Metadata{ AppVersion: string(appVersion), FeeVersion: feetypes.Version, } feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) if err != nil { return err } if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion), channeltypes.UNORDERED); err != nil { return err } ``` ## `SendTx` The authentication module can attempt to send a packet by calling `SendTx`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Authenticate owner / perform custom logic / Construct controller portID based on interchain account owner address portID, err := icatypes.NewControllerPortID(owner.String()) if err != nil { return err } / Obtain data to be sent to the host chain. / In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. / The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. / If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. msg := &banktypes.MsgSend{ FromAddress: fromAddr, ToAddress: toAddr, Amount: amt } data, err := icatypes.SerializeCosmosTx(keeper.cdc, []proto.Message{ msg }) if err != nil { return err } / Construct packet data packetData := icatypes.InterchainAccountPacketData{ Type: icatypes.EXECUTE_TX, Data: data, } / Obtain timeout timestamp / An appropriate timeout timestamp must be determined based on the usage of the interchain account. / If the packet times out, the channel will be closed requiring a new channel to be created. timeoutTimestamp := obtainTimeoutTimestamp() / Send the interchain accounts packet, returning the packet sequence seq, err = keeper.icaControllerKeeper.SendTx(ctx, portID, packetData, timeoutTimestamp) ``` The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not supported by the host chain, the packet will not be successfully received. # Messages Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/messages An Interchain Accounts channel handshake can be initiated using MsgRegisterInterchainAccount: ## `MsgRegisterInterchainAccount` An Interchain Accounts channel handshake can be initiated using `MsgRegisterInterchainAccount`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgRegisterInterchainAccount struct { Owner string ConnectionID string Version string Ordering channeltypes.Order } ``` This message is expected to fail if: * `Owner` is an empty string or contains more than 2048 bytes. * `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). This message will construct a new `MsgChannelOpenInit` on chain and route it to the core IBC message server to initiate the opening step of the channel handshake. The controller submodule will generate a new port identifier. The caller is expected to provide an appropriate application version string. For example, this may be an ICS-27 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L11) type or an ICS-29 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/fee/v1/metadata.proto#L11) type with a nested application version. If the `Version` string is omitted, the controller submodule will construct a default version string in the `OnChanOpenInit` handshake callback. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgRegisterInterchainAccountResponse struct { ChannelID string PortId string } ``` The `ChannelID` and `PortID` are returned in the message response. ## `MsgSendTx` An Interchain Accounts transaction can be executed on a remote host chain by sending a `MsgSendTx` from the corresponding controller chain: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgSendTx struct { Owner string ConnectionID string PacketData InterchainAccountPacketData RelativeTimeout uint64 } ``` This message is expected to fail if: * `Owner` is an empty string or contains more than 2048 bytes. * `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). * `PacketData` contains an `UNSPECIFIED` type enum, the length of `Data` bytes is zero or the `Memo` field exceeds 256 characters in length. * `RelativeTimeout` is zero. This message will create a new IBC packet with the provided `PacketData` and send it via the channel associated with the `Owner` and `ConnectionID`. The `PacketData` is expected to contain a list of serialized `[]sdk.Msg` in the form of `CosmosTx`. Please note the signer field of each `sdk.Msg` must be the interchain account address. When the packet is relayed to the host chain, the `PacketData` is unmarshalled and the messages are authenticated and executed. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgSendTxResponse struct { Sequence uint64 } ``` The packet `Sequence` is returned in the message response. ### Queries It is possible to use [`MsgModuleQuerySafe`](https://github.com/cosmos/ibc-go/blob/eecfa5c09a4c38a5c9f2cc2a322d2286f45911da/proto/ibc/applications/interchain_accounts/host/v1/tx.proto#L41-L51) to execute a list of queries on the host chain. This message can be included in the list of encoded `sdk.Msg`s of `InterchainPacketData`. The host chain will return on the acknowledgment the responses for all the queries. Please note that only module safe queries can be executed ([deterministic queries that are safe to be called from within the state machine](/sdk/latest/learn/concepts/modules#queries)). The queries available from Cosmos SDK are: ```plaintext expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /cosmos.auth.v1beta1.Query/Accounts /cosmos.auth.v1beta1.Query/Account /cosmos.auth.v1beta1.Query/AccountAddressByID /cosmos.auth.v1beta1.Query/Params /cosmos.auth.v1beta1.Query/ModuleAccounts /cosmos.auth.v1beta1.Query/ModuleAccountByName /cosmos.auth.v1beta1.Query/AccountInfo /cosmos.bank.v1beta1.Query/Balance /cosmos.bank.v1beta1.Query/AllBalances /cosmos.bank.v1beta1.Query/SpendableBalances /cosmos.bank.v1beta1.Query/SpendableBalanceByDenom /cosmos.bank.v1beta1.Query/TotalSupply /cosmos.bank.v1beta1.Query/SupplyOf /cosmos.bank.v1beta1.Query/Params /cosmos.bank.v1beta1.Query/DenomMetadata /cosmos.bank.v1beta1.Query/DenomMetadataByQueryString /cosmos.bank.v1beta1.Query/DenomsMetadata /cosmos.bank.v1beta1.Query/DenomOwners /cosmos.bank.v1beta1.Query/SendEnabled /cosmos.circuit.v1.Query/Account /cosmos.circuit.v1.Query/Accounts /cosmos.circuit.v1.Query/DisabledList /cosmos.staking.v1beta1.Query/Validators /cosmos.staking.v1beta1.Query/Validator /cosmos.staking.v1beta1.Query/ValidatorDelegations /cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations /cosmos.staking.v1beta1.Query/Delegation /cosmos.staking.v1beta1.Query/UnbondingDelegation /cosmos.staking.v1beta1.Query/DelegatorDelegations /cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations /cosmos.staking.v1beta1.Query/Redelegations /cosmos.staking.v1beta1.Query/DelegatorValidators /cosmos.staking.v1beta1.Query/DelegatorValidator /cosmos.staking.v1beta1.Query/HistoricalInfo /cosmos.staking.v1beta1.Query/Pool /cosmos.staking.v1beta1.Query/Params ``` And the query available from ibc-go is: ```plaintext theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} /ibc.core.client.v1.Query/VerifyMembership ``` The following code block shows an example of how `MsgModuleQuerySafe` can be used to query the account balance of an account on the host chain. The resulting packet data variable is used to set the `PacketData` of `MsgSendTx`. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} balanceQuery := banktypes.NewQueryBalanceRequest("cosmos1...", "uatom") queryBz, err := balanceQuery.Marshal() / signer of message must be the interchain account on the host queryMsg := icahosttypes.NewMsgModuleQuerySafe("cosmos2...", []icahosttypes.QueryRequest{ { Path: "/cosmos.bank.v1beta1.Query/Balance", Data: queryBz, }, }) bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{ queryMsg }, icatypes.EncodingProtobuf) packetData := icatypes.InterchainAccountPacketData{ Type: icatypes.EXECUTE_TX, Data: bz, Memo: "", } ``` ## Atomicity As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](/sdk/latest/learn/concepts/store#cachemultistore-transaction-isolation) architecture provided by the [`Context`](/sdk/latest/learn/concepts/context-gas-events) type. This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. # Overview Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/overview Interchain Accounts is only compatible with IBC Classic, not IBC v2 ## Synopsis Learn about what the Interchain Accounts module is ## What is the Interchain Accounts module? Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. * How does an interchain account differ from a regular account? Regular accounts use a private key to sign transactions. Interchain Accounts are instead controlled programmatically by counterparty chains via IBC packets. ## Concepts `Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. Cosmos SDK messages) for which the interchain account will execute. `Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. `Interchain Account`: An account on a host chain created using the ICS-27 protocol. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account should execute. `Authentication Module`: A custom application module on the controller chain that uses the Interchain Accounts module to build custom logic for the creation & management of interchain accounts. It can be either an IBC application module using the [legacy API](/ibc/latest/apps/interchain-accounts/legacy/keeper-api), or a regular Cosmos SDK application module sending messages to the controller submodule's `MsgServer` (this is the recommended approach from ibc-go v6 if access to packet callbacks is not needed). Please note that the legacy API will eventually be removed and IBC applications will not be able to use them in later releases. ## SDK security model SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. The implementation of ICS-27 in ibc-go uses this assumption in its security considerations. The implementation assumes other IBC application modules will not bind to ports within the ICS-27 namespace. ## Channel Closure The provided interchain account host and controller implementations do not support `ChanCloseInit`. However, they do support `ChanCloseConfirm`. This means that the host and controller modules cannot close channels, but they will confirm channel closures initiated by other implementations of ICS-27. In the event of a channel closing (due to a packet timeout in an ordered channel, for example), the interchain account associated with that channel can become accessible again if a new channel is created with a (JSON-formatted) version string that encodes the exact same `Metadata` information of the previous channel. The channel can be reopened using either [`MsgRegisterInterchainAccount`](/ibc/latest/apps/interchain-accounts/messages#msgregisterinterchainaccount) or `MsgChannelOpenInit`. If `MsgRegisterInterchainAccount` is used, then it is possible to leave the `version` field of the message empty, since it will be filled in by the controller submodule. If `MsgChannelOpenInit` is used, then the `version` field must be provided with the correct JSON-encoded `Metadata` string. See section [Understanding Active Channels](/ibc/latest/apps/interchain-accounts/active-channels#understanding-active-channels) for more information. When reopening a channel with the default controller submodule, the ordering of the channel cannot be changed. # Parameters Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/parameters The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: ## Controller Submodule Parameters | Name | Type | Default Value | | ------------------- | ---- | ------------- | | `ControllerEnabled` | bool | `true` | ### ControllerEnabled The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: * `OnChanOpenInit` * `OnChanOpenAck` * `OnChanCloseConfirm` * `OnAcknowledgementPacket` * `OnTimeoutPacket` ## Host Submodule Parameters | Name | Type | Default Value | | --------------- | --------- | ------------- | | `HostEnabled` | bool | `true` | | `AllowMessages` | \[]string | `["*"]` | ### HostEnabled The `HostEnabled` parameter controls a chains ability to service ICS-27 host specific logic. This includes the following ICS-26 callback handlers: * `OnChanOpenTry` * `OnChanOpenConfirm` * `OnChanCloseConfirm` * `OnRecvPacket` ### AllowMessages The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message type URL format. For example, a Cosmos SDK-based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} "params": { "host_enabled": true, "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] } ``` There is also a special wildcard `"*"` value which allows any type of message to be executed by the interchain account. This must be the only value in the `allow_messages` array. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} "params": { "host_enabled": true, "allow_messages": ["*"] } ``` # Transaction Encoding Source: https://docs.cosmos.network/ibc/latest/apps/interchain-accounts/tx-encoding When orchestrating an interchain account transaction, which comprises multiple sdk.Msg objects represented as Any types, the transactions must be encoded as bytes within InterchainAccountPacketData. When orchestrating an interchain account transaction, which comprises multiple `sdk.Msg` objects represented as `Any` types, the transactions must be encoded as bytes within [`InterchainAccountPacketData`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L21-L26). ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / InterchainAccountPacketData is comprised of a raw transaction, type of transaction and optional memo field. message InterchainAccountPacketData { Type type = 1; bytes data = 2; string memo = 3; } ``` The `data` field must be encoded as a [`CosmosTx`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L28-L31). ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / CosmosTx contains a list of sdk.Msg's. It should be used when sending transactions to an SDK host chain. message CosmosTx { repeated google.protobuf.Any messages = 1; } ``` The encoding method for `CosmosTx` is determined during the channel handshake process. If the channel version [metadata's `encoding` field](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L22) is marked as `proto3`, then `CosmosTx` undergoes protobuf encoding. Conversely, if the field is set to `proto3json`, then [proto3 json](https://protobuf.dev/programming-guides/proto3/#json) encoding takes place, which generates a JSON representation of the protobuf message. ## Protobuf Encoding Protobuf encoding serves as the standard encoding process for `CosmosTx`. This occurs if the channel handshake initiates with an empty channel version metadata or if the `encoding` field explicitly denotes `proto3`. In Golang, the protobuf encoding procedure utilizes the `proto.Marshal` function. Every protobuf autogenerated Golang type comes equipped with a `Marshal` method that can be employed to encode the message. ## (Protobuf) JSON Encoding The proto3 JSON encoding presents an alternative encoding technique for `CosmosTx`. It is selected if the channel handshake begins with the channel version metadata `encoding` field labeled as `proto3json`. In Golang, the Proto3 canonical encoding in JSON is implemented by the `"github.com/cosmos/gogoproto/jsonpb"` package. Within Cosmos SDK, the `ProtoCodec` structure implements the `JSONCodec` interface, leveraging the `jsonpb` package. This method generates a JSON format as follows: ```json expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "messages": [ { "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "cosmos1...", "to_address": "cosmos1...", "amount": [ { "denom": "uatom", "amount": "1000000" } ] } ] } ``` Here, the `"messages"` array is populated with transactions. Each transaction is represented as a JSON object with the `@type` field denoting the transaction type and the remaining fields representing the transaction's attributes. # IBC v2 Transfer Source: https://docs.cosmos.network/ibc/latest/apps/transfer/IBCv2-transfer Much of the core business logic of sending and recieving tokens between chains is unchanged between IBC Classic and IBC v2. Some of the key differences to pay attention to are detailed below. Much of the core business logic of sending and recieving tokens between chains is unchanged between IBC Classic and IBC v2. Some of the key differences to pay attention to are detailed below. ## No Channel Handshakes, New Packet Format and Encoding Support * IBC v2 does not establish connection between applications with a channel handshake. Channel identifiers represent Client IDs and are included in the `Payload` * The source and destination port must be `"transfer"` * The channel IDs [must be valid client IDs](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/v2/ibc_module.go#L46-L47) of the format `{clientID}-{sequence}`, e.g. 08-wasm-007 * The [`Payload`](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.pb.go#L146-L158) contains the [`FungibleTokenPacketData`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/packet.pb.go#L28-L39) for a token transfer. The code snippet shows the `Payload` struct. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) type Payload struct { / specifies the source port of the packet, e.g. transfer SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` / specifies the destination port of the packet, e.g. trasnfer DestinationPort string `protobuf:"bytes,2,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` / version of the specified application Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` / the encoding used for the provided value, for transfer this could be JSON, protobuf or ABI Encoding string `protobuf:"bytes,4,opt,name=encoding,proto3" json:"encoding,omitempty"` / the raw bytes for the payload. Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` } ``` The code snippet shows the structure of the `Payload` bytes for token transfer ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / FungibleTokenPacketData defines a struct for the packet payload / See FungibleTokenPacketData spec: / https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures type FungibleTokenPacketData struct { / the token denomination to be transferred Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` / the token amount to be transferred Amount string `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` / the sender address Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` / the recipient address on the destination chain Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` / optional memo Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"` } ``` ## Base Denoms cannot contain slashes With the new [`Denom`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/token.pb.go#L81-L87) struct, the base denom, i.e. uatom, is seperated from the trace - the path the token has travelled. The trace is presented as an array of [`Hop`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/token.pb.go#L136-L140)s. Because IBC v2 no longer uses channels, it is no longer possible to rely on a fixed format for an identifier so using a base denom that contains a "/" is dissallowed. ## Changes to the application module interface Instead of implementing token transfer for `port.IBCModule`, IBC v2 uses the new application interface `api.IBCModule`. More information on the interface differences can be found in the [application section](/ibc/latest/ibc/apps/ibcv2apps). ## MsgTransfer Entrypoint The `MsgTransfer` entrypoint has been retained in order to retain support for the common entrypoint integrated in most existing frontends. If `MsgTransfer` is used with a clientID as the `msg.SourceChannel` then the handler will automatically use the IBC v2 protocol. It will internally call the `MsgSendPacket` endpoint so that the execution flow is the same in the state machine for all IBC v2 packets while still presenting the same endpoint for users. Of course, we want to still retain support for sending v2 packets on existing channels. The denominations of tokens once they leave the origin chain are prefixed by the port and channel ID in IBC v1. Moreover, the transfer escrow accounts holding the original tokens are generated from the channel IDs. Thus, if we wish to interact these remote tokens using IBC v2, we must still use the v1 channel identifiers that they were originally sent with. Thus, `MsgTransfer` has an additional `UseAliasing` boolean field to indicate that we wish to use IBC v2 protocol while still using the old v1 channel identifiers. This enables users to interact with the same tokens, DEX pools, and cross-chain DEFI protocols using the same denominations that they had previously with the IBC v2 protocol. To use the `MsgTransfer` with aliasing we can submit the message like so: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} MsgTransfer{ SourcePort: "transfer", SourceChannel: "channel-4", /note: we are using an existing v1 channel identiifer Token: "uatom", Sender: { senderAddr }, Receiver: { receiverAddr }, TimeoutHeight: ZeroHeight, / note: IBC v2 does not use timeout height TimeoutTimestamp: 100_000_000, Memo: "", UseAliasing: true, / set aliasing to true so the handler uses IBC v2 instead of IBC v1 } ``` # Authorizations Source: https://docs.cosmos.network/ibc/latest/apps/transfer/authorizations `TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](/sdk/latest/modules/authz/README) for more details on granting privileges via the `x/authz` module. More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel. For the specified channel, the granter must be able to specify a spend limit of a specific denomination they wish to allow the grantee to be able to transfer. The granter may be able to specify the list of addresses that they allow to receive funds. If empty, then all addresses are allowed. It takes: * a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred. * a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transferred, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. * an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`. * an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in `memo` field will be allowed. Setting a `TransferAuthorization` is expected to fail if: * the spend limit is nil * the denomination of the spend limit is an invalid coin type * the source port ID is invalid * the source channel ID is invalid * there are duplicate entries in the `AllowList` * the `memo` field is not allowed by `AllowedPacketData` Below is the `TransferAuthorization` message: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { return &TransferAuthorization{ Allocations: allocations, } } type Allocation struct { / the port on which the packet will be sent SourcePort string / the channel by which the packet will be sent SourceChannel string / spend limitation on the channel SpendLimit sdk.Coins / allow list of receivers, an empty allow list permits any receiver address AllowList []string / allow list of memo strings, an empty list prohibits all memo strings; / a list only with "*" permits any memo string AllowedPacketData []string } ``` # Client Source: https://docs.cosmos.network/ibc/latest/apps/transfer/client A user can query and interact with the transfer module using the CLI. Use the --help flag to discover the available commands: ## CLI A user can query and interact with the `transfer` module using the CLI. Use the `--help` flag to discover the available commands: ### Query The `query` commands allow users to query `transfer` state. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-transfer --help ``` #### Transactions The `tx` commands allow users to interact with the controller submodule. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx ibc-transfer --help ``` #### `transfer` The `transfer` command allows users to execute cross-chain token transfers from the source port ID and channel ID on the sending chain. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd tx ibc-transfer transfer [src-port] [src-channel] [receiver] [coins] [flags] ``` The `coins` parameter accepts the amount and denomination (e.g. `100uatom`) of the tokens to be transferred. The additional flags that can be used with the command are: * `--packet-timeout-height` to specify the timeout block height in the format `{revision}-{height}`. The default value is `0-0`, which effectively disables the timeout. Timeout height can only be absolute, therefore this option must be used in combination with `--absolute-timeouts` set to true. On IBC v1 protocol, either `--packet-timeout-height` or `--packet-timeout-timestamp` must be set. On IBC v2 protocol `--packet-timeout-timestamp` must be set. * `--packet-timeout-timestamp` to specify the timeout timestamp in nanoseconds. The timeout can be either relative (from the current UTC time) or absolute. The default value is 10 minutes (and thus relative). On IBC v1 protocol, either `--packet-timeout-height` or `--packet-timeout-timestamp` must be set. On IBC v2 protocol `--packet-timeout-timestamp` must be set. * `--absolute-timeouts` to interpret the timeout timestamp as an absolute value (when set to true). The default value is false (and thus the timeout is considered relative to current UTC time). * `--memo` to specify the memo string to be sent along with the transfer packet. If forwarding is used, then the memo string will be carried through the intermediary chains to the final destination. #### `total-escrow` The `total-escrow` command allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-transfer total-escrow [denom] [flags] ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-transfer total-escrow samoleans ``` Example Output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} amount: "100" ``` ## gRPC A user can query the `transfer` module using gRPC endpoints. ### `TotalEscrowForDenom` The `TotalEscrowForDenom` endpoint allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibc.applications.transfer.v1.Query/TotalEscrowForDenom ``` Example: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} grpcurl -plaintext \ -d '{"denom":"samoleans"}' \ localhost:9090 \ ibc.applications.transfer.v1.Query/TotalEscrowForDenom ``` Example output: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "amount": "100" } ``` # Events Source: https://docs.cosmos.network/ibc/latest/apps/transfer/events ## `MsgTransfer` | Type | Attribute Key | Attribute Value | | ------------- | ------------- | --------------- | | ibc\_transfer | sender | `{sender}` | | ibc\_transfer | receiver | `{receiver}` | | ibc\_transfer | denom | `{denom}` | | ibc\_transfer | denom\_hash | `{denom\_hash}` | | ibc\_transfer | amount | `{amount}` | | ibc\_transfer | memo | `{memo}` | | message | module | transfer | ## `OnRecvPacket` callback | Type | Attribute Key | Attribute Value | | ----------------------- | ------------- | --------------- | | fungible\_token\_packet | sender | `{sender}` | | fungible\_token\_packet | receiver | `{receiver}` | | fungible\_token\_packet | denom | `{denom}` | | fungible\_token\_packet | denom\_hash | `{denom\_hash}` | | fungible\_token\_packet | amount | `{amount}` | | fungible\_token\_packet | memo | `{memo}` | | fungible\_token\_packet | success | `{ackSuccess}` | | fungible\_token\_packet | error | `{ackError}` | | message | module | transfer | ## `OnAcknowledgePacket` callback | Type | Attribute Key | Attribute Value | | ----------------------- | --------------- | ---------------- | | fungible\_token\_packet | sender | `{sender}` | | fungible\_token\_packet | receiver | `{receiver}` | | fungible\_token\_packet | denom | `{denom}` | | fungible\_token\_packet | denom\_hash | `{denom\_hash}` | | fungible\_token\_packet | amount | `{amount}` | | fungible\_token\_packet | memo | `{memo}` | | fungible\_token\_packet | acknowledgement | `{ack.String()}` | | fungible\_token\_packet | success / error | `{ack.Response}` | | message | module | transfer | ## `OnTimeoutPacket` callback | Type | Attribute Key | Attribute Value | | ------- | ---------------- | --------------- | | timeout | refund\_receiver | `{receiver}` | | timeout | refund\_tokens | `{jsonTokens}` | | timeout | denom | `{denom}` | | timeout | denom\_hash | `{denom\_hash}` | | timeout | memo | `{memo}` | | message | module | transfer | # Messages Source: https://docs.cosmos.network/ibc/latest/apps/transfer/messages A fungible token cross chain transfer is achieved by using the MsgTransfer: ## `MsgTransfer` A fungible token cross chain transfer is achieved by using the `MsgTransfer`: ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MsgTransfer struct { SourcePort string / with IBC v2 SourceChannel will be a client ID SourceChannel string Token sdk.Coin Sender string Receiver string / If you are sending with IBC v1 protocol, either timeout_height or timeout_timestamp must be set. / If you are sending with IBC v2 protocol, timeout_timestamp must be set, and timeout_height must be omitted. TimeoutHeight ibcexported.Height / Timeout timestamp in absolute nanoseconds since unix epoch. TimeoutTimestamp uint64 / optional Memo field Memo string / optional Encoding field Encoding string } ``` This message is expected to fail if: * `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). * `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). * `Token` is invalid: * `Amount` is not positive. * `Denom` is not a valid IBC denomination. * `Sender` is empty. * `Receiver` is empty or contains more than 2048 bytes. * `Memo` contains more than 32768 bytes. * `TimeoutHeight` and `TimeoutTimestamp` are both zero for IBC Classic. * Note that `TimeoutHeight` as a concept is removed in IBC v2, hence this must always be emitted and only `TimeoutTimestamp` used. This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. Note that in IBC v2 a pair of clients are connected and the `SourceChannel` is referring to the source `ClientID`. The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corresponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. ### Memo The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. For example, the following memo field is used by the callbacks middleware to attach a source callback to a transfer packet: ```jsonc theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "src_callback": { "address": "callbackAddressString", / optional "gas_limit": "userDefinedGasLimitString", } } ``` You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). ### Encoding In IBC v2, the encoding method used by an application has more flexibility as it is specified within a `Payload`, rather than negotiated and fixed during an IBC classic channel handshake. Certain encoding types may be more suited to specific blockchains, e.g. ABI encoding is more gas efficient to decode in an EVM than JSON or Protobuf. Within ibc-go, JSON, protobuf and ABI encoding are supported and can be used, see the [transfer packet types](https://github.com/cosmos/ibc-go/blob/14bc17e26ad12cee6bdb99157a05296fcf58b762/modules/apps/transfer/types/packet.go#L36-L40). # Metrics Source: https://docs.cosmos.network/ibc/latest/apps/transfer/metrics The IBC transfer application module exposes the following set of metrics. The IBC transfer application module exposes the following set of [metrics](/sdk/latest/guides/testing/telemetry). | Metric | Description | Unit | Type | | :---------------------------- | :---------------------------------------------------------------------------------------- | :------- | :------ | | `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | | `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | | `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | | `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | # Overview Source: https://docs.cosmos.network/ibc/latest/apps/transfer/overview ## Synopsis Learn about what the token Transfer module is ## What is the Transfer module? Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. ## Concepts ### Acknowledgements ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). A successful receive of a transfer packet will result in a Result Acknowledgement being written with the value `[]byte{byte(1)}` in the `Response` field. An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written with the error message in the `Response` field. ### Denomination trace The denomination trace corresponds to the information that allows a token to be traced back to its origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to the oldest in the timeline of transfers. When using transfer with IBC v2 connecting to e.g. Ethereum, the source channel identifier will be the source client identifier instead. This information is included on the token's base denomination field in the form of a hash to prevent an unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. The human readable denomination is stored using `x/bank` module's [denom metadata](/sdk/latest/modules/bank/README#denom-metadata) feature. You may display the human readable denominations by querying balances with the `--resolve-denom` flag, as in: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query bank balances [address] --resolve-denom ``` Each send to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This causes trace to be added to the token's history and the destination port and destination channel to be prefixed to the denomination. In these instances the sender chain is acting as the "source zone". When the token is sent back to the chain it previously received from, the prefix is removed. This is a backwards movement in the token's timeline and the sender chain is acting as the "sink zone". It is strongly recommended to understand the implications and context of the IBC token representations. ## UX suggestions for clients For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: ### Direct connection If the denomination trace contains a single identifier prefix pair (as in the example above), then the easiest way to retrieve the chain and light client identifier is to map the trace information directly. In summary, this requires querying the channel from the denomination trace identifiers, and then the counterparty client state using the counterparty port and channel identifiers from the retrieved channel. A general pseudo algorithm would look like the following: 1. Query the full denomination trace. 2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the token. 3. Query the client state using the identifiers pair. Note that this query will return a `"Not Found"` response if the current chain is not connected to this channel. 4. Retrieve the client identifier or chain identifier from the client state (eg: on Tendermint clients) and store it locally. Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: 1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` 2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` 3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` 4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. ### Multiple hops The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). Thus the proposed solution for clients that the IBC team recommends are the following: * **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to perform the queries outlined in the [direct connection](#direct-connection) section to each relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, clients should always be able to find all the relevant identifiers. This comes at the tradeoff that the client must connect to nodes on each of the chains in order to perform the queries. * **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in order to allow clients to optionally verify the path timeline correctness for themselves by running light clients. If the proofs are not verified, they should be considered as trusted third parties services. Additionally, client would be advised in the future to use RaaS that support the largest number of connections between chains in the ecosystem. Unfortunately, none of the existing public relayers (in [Golang](https://github.com/cosmos/relayer) and [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. ## Locked funds In some exceptional cases, a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. To mitigate this, a client update governance proposal can be submitted to update the frozen client with a new valid header. Once the proposal passes the client state will be unfrozen and the funds from the associated channels will then be unlocked. This mechanism only applies to clients that allow updates via governance, such as Tendermint clients. In addition to this, it's important to mention that a token must be sent back along the exact route that it took originally in order to return it to its original form on the source chain (eg: the Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will **not** move the token back across its timeline. If a channel in the chain history closes before the token can be sent back across that channel, then the token will not be returnable to its original form. ## Security considerations For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC transfer module needs a subset of the denomination space that only it can create tokens in. ## Channel Closure The IBC transfer module does not support channel closure. # Params Source: https://docs.cosmos.network/ibc/latest/apps/transfer/params The IBC transfer application module contains the following parameters: The IBC transfer application module contains the following parameters: | Name | Type | Default Value | | ---------------- | ---- | ------------- | | `SendEnabled` | bool | `true` | | `ReceiveEnabled` | bool | `true` | The IBC transfer module stores its parameters under its `StoreKey` ## `SendEnabled` The `SendEnabled` parameter controls send cross-chain transfer capabilities for all fungible tokens. To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: * For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. * For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. Doing so will prevent the token from being transferred between any accounts in the blockchain. ## `ReceiveEnabled` The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: * For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. * For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. Doing so will prevent the token from being transferred between any accounts in the blockchain. ## Queries Current parameter values can be queried via a query message. ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / proto/ibc/applications/transfer/v1/query.proto / QueryParamsRequest is the request type for the Query/Params RPC method. message QueryParamsRequest {} / QueryParamsResponse is the response type for the Query/Params RPC method. message QueryParamsResponse { / params defines the parameters of the module. Params params = 1; } ``` To execute the query in `simd`, you use the following command: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ibc-transfer params ``` ## Changing Parameters To change the parameter values, you must make a governance proposal that executes the `MsgUpdateParams` message. ```protobuf expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / proto/ibc/applications/transfer/v1/tx.proto / MsgUpdateParams is the Msg/UpdateParams request type. message MsgUpdateParams { / signer address (it may be the address that controls the module, which defaults to x/gov unless overwritten). string signer = 1; / params defines the transfer parameters to update. / / NOTE: All parameters must be supplied. Params params = 2 [(gogoproto.nullable) = false]; } / MsgUpdateParamsResponse defines the response structure for executing a / MsgUpdateParams message. message MsgUpdateParamsResponse {} ``` # State Source: https://docs.cosmos.network/ibc/latest/apps/transfer/state The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information. The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information. * `PortKey`: `0x01 -> ProtocolBuffer(string)` * `DenomTraceKey`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(Denom)` * `DenomKey` : `0x03 | []bytes(traceHash) -> ProtocolBuffer(Denom)` # State Transitions Source: https://docs.cosmos.network/ibc/latest/apps/transfer/state-transitions A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: ## Send fungible tokens A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: 1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: * The coins are transferred to an escrow address (i.e locked) on the sender chain. * The coins are transferred to the receiving chain through IBC TAO logic. 2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: * The coins (vouchers) are burned on the sender chain. * The coins are transferred to the receiving chain through IBC TAO logic. ## Receive fungible tokens A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: 1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: * The leftmost port and channel identifier pair is removed from the token denomination prefix. * The tokens are unescrowed and sent to the receiving address. 2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: * Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. * The receiving chain stores the new trace information in the store (if not set already). * The vouchers are sent to the receiving address. # README Source: https://docs.cosmos.network/ibc/latest/architecture/README # Architecture Decision Records (ADR) This is a location to record all high-level architecture decisions in the ibc-go project. You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t). An ADR should provide: * Context on the relevant goals and the current state * Proposed changes to achieve the goals * Summary of pros and cons * References * Changelog Note the distinction between an ADR and a spec. The ADR provides the context, intuition, reasoning, and justification for a change in architecture, or for the architecture of something new. The spec is much more compressed and streamlined summary of everything as it is or should be. If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match. Note the context/background should be written in the present tense. To suggest an ADR, please make use of the [ADR template](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr.template.md) provided. ## Table of Contents | ADR # | Description | Status | | ------------------------------------------------------------------------- | ------------------------------------------------------- | --------------------- | | [001](/ibc/latest/architecture/adr-001-coin-source-tracing) | ICS-20 coin denomination format | Accepted, Implemented | | [002](/ibc/latest/architecture/adr-002-go-module-versioning) | Go module versioning | Accepted | | [003](/ibc/latest/architecture/adr-003-ics27-acknowledgement) | ICS27 acknowledgement format | Accepted | | [004](/ibc/latest/architecture/adr-004-ics29-lock-fee-module) | ICS29 module locking upon escrow out of balance | Accepted | | [005](/ibc/latest/architecture/adr-005-consensus-height-events) | `UpdateClient` events - `ClientState` consensus heights | Accepted | | [006](/ibc/latest/architecture/adr-006-02-client-refactor) | ICS02 client refactor | Accepted | | [007](/ibc/latest/architecture/adr-007-solomachine-signbytes) | ICS06 Solo machine sign bytes | Accepted | | [008](/ibc/latest/architecture/adr-008-app-caller-cbs) | Callback to IBC Actors | Accepted | | [009](/ibc/latest/architecture/adr-009-v6-ics27-msgserver) | ICS27 message server addition | Accepted | | [010](/ibc/latest/architecture/adr-010-light-clients-as-sdk-modules) | IBC light clients as SDK modules | Accepted | | [011](/ibc/latest/architecture/adr-011-transfer-total-escrow-state-entry) | ICS20 state entry for total amount of tokens in escrow | Accepted | | [015](/ibc/latest/architecture/adr-015-ibc-packet-receiver) | IBC Packet Routing | Accepted | | [025](/ibc/latest/architecture/adr-025-ibc-passive-channels) | IBC passive channels | Deprecated | | [026](/ibc/latest/architecture/adr-026-ibc-client-recovery-mechanisms) | IBC client recovery mechanisms | Accepted | | [027](/ibc/latest/architecture/adr-027-ibc-wasm) | Wasm based light clients | Accepted | # ADR 001: Coin Source Tracing Source: https://docs.cosmos.network/ibc/latest/architecture/adr-001-coin-source-tracing ## Changelog * 09-07-2020: Initial Draft * 11-08-2020: Implementation changes ## Status Accepted, Implemented ## Context The specification for IBC cross-chain fungible token transfers ([ICS20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer)), needs to be aware of the origin of any token denomination in order to relay a `Packet` which contains the sender and recipient addresses in the [`FungibleTokenPacketData`](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures). The Packet relay sending works based in 2 cases (per [specification](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay) and [Colin Axnér](https://github.com/colin-axner)'s description): 1. Sender chain is acting as the source zone. The coins are transferred to an escrow address (i.e locked) on the sender chain and then transferred to the receiving chain through IBC TAO logic. It is expected that the receiving chain will mint vouchers to the receiving address. 2. Sender chain is acting as the sink zone. The coins (vouchers) are burned on the sender chain and then transferred to the receiving chain through IBC TAO logic. It is expected that the receiving chain, which had previously sent the original denomination, will unescrow the fungible token and send it to the receiving address. Another way of thinking of source and sink zones is through the token's timeline. Each send to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This causes trace to be added to the token's history and the destination port and destination channel to be prefixed to the denomination. In these instances the sender chain is acting as the source zone. When the token is sent back to the chain it previously received from, the prefix is removed. This is a backwards movement in the token's timeline and the sender chain is acting as the sink zone. ### Example Assume the following channel connections exist and that all channels use the port ID `transfer`: * chain `A` has channels with chain `B` and chain `C` with the IDs `channelToB` and `channelToC`, respectively * chain `B` has channels with chain `A` and chain `C` with the IDs `channelToA` and `channelToC`, respectively * chain `C` has channels with chain `A` and chain `B` with the IDs `channelToA` and `channelToB`, respectively These steps of transfer between chains occur in the following order: `A -> B -> C -> A -> C`. In particular: 1. `A -> B`: sender chain is source zone. `A` sends packet with `denom` (escrowed on `A`), `B` receives `denom` and mints and sends voucher `transfer/channelToA/denom` to recipient. 2. `B -> C`: sender chain is source zone. `B` sends packet with `transfer/channelToA/denom` (escrowed on `B`), `C` receives `transfer/channelToA/denom` and mints and sends voucher `transfer/channelToB/transfer/channelToA/denom` to recipient. 3. `C -> A`: sender chain is source zone. `C` sends packet with `transfer/channelToB/transfer/channelToA/denom` (escrowed on `C`), `A` receives `transfer/channelToB/transfer/channelToA/denom` and mints and sends voucher `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom` to recipient. 4. `A -> C`: sender chain is sink zone. `A` sends packet with `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom` (burned on `A`), `C` receives `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom`, and unescrows and sends `transfer/channelToB/transfer/channelToA/denom` to recipient. The token has a final denomination on chain `C` of `transfer/channelToB/transfer/channelToA/denom`, where `transfer/channelToB/transfer/channelToA` is the trace information. In this context, upon a receive of a cross-chain fungible token transfer, if the sender chain is the source of the token, the protocol prefixes the denomination with the port and channel identifiers in the following format: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} prefix + denom = {destPortN}/{destChannelN}/.../{destPort0}/{destChannel0}/denom ``` Example: transferring `100 uatom` from port `HubPort` and channel `HubChannel` on the Hub to Ethermint's port `EthermintPort` and channel `EthermintChannel` results in `100 EthermintPort/EthermintChannel/uatom`, where `EthermintPort/EthermintChannel/uatom` is the new denomination on the receiving chain. In the case those tokens are transferred back to the Hub (i.e the **source** chain), the prefix is trimmed and the token denomination updated to the original one. ### Problem The problem of adding additional information to the coin denomination is twofold: 1. The ever increasing length if tokens are transferred to zones other than the source: If a token is transferred `n` times via IBC to a sink chain, the token denom will contain `n` pairs of prefixes, as shown on the format example above. This poses a problem because, while port and channel identifiers have a maximum length of 64 each, the SDK `Coin` type only accepts denoms up to 64 characters. Thus, a single cross-chain token, which again, is composed by the port and channels identifiers plus the base denomination, can exceed the length validation for the SDK `Coins`. This can result in undesired behaviours such as tokens not being able to be transferred to multiple sink chains if the denomination exceeds the length or unexpected `panics` due to denomination validation failing on the receiving chain. 2. The existence of special characters and uppercase letters on the denomination: In the SDK every time a `Coin` is initialized through the constructor function `NewCoin`, a validation of a coin's denom is performed according to a [Regex](https://github.com/cosmos/cosmos-sdk/blob/a940214a4923a3bf9a9161cd14bd3072299cd0c9/types/coin.go#L583), where only lowercase alphanumeric characters are accepted. While this is desirable for native denominations to keep a clean UX, it presents a challenge for IBC as ports and channels might be randomly generated with special and uppercase characters as per the [ICS 024 - Host Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements#paths-identifiers-separators) specification. ## Decision The issues outlined above, are applicable only to SDK-based chains, and thus the proposed solution are do not require specification changes that would result in modification to other implementations of the ICS20 spec. Instead of adding the identifiers on the coin denomination directly, the proposed solution hashes the denomination prefix in order to get a consistent length for all the cross-chain fungible tokens. This will be used for internal storage only, and when transferred via IBC to a different chain, the denomination specified on the packed data will be the full prefix path of the identifiers needed to trace the token back to the originating chain, as specified on ICS20. The new proposed format will be the following: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} ibcDenom = "ibc/" + hash(trace path + "/" + base denom) ``` The hash function will be a SHA256 hash of the fields of the `DenomTrace`: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing // information message DenomTrace { // chain of port/channel identifiers used for tracing the source of the fungible token string path = 1; // base denomination of the relayed fungible token string base_denom = 2; } ``` The `IBCDenom` function constructs the `Coin` denomination used when creating the ICS20 fungible token packet data: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields using the following formula: // // hash = sha256(tracePath + "/" + baseDenom) func (dt DenomTrace) Hash() tmbytes.HexBytes { return tmhash.Sum(dt.Path + "/" + dt.BaseDenom) } // IBCDenom a coin denomination for an ICS20 fungible token in the format 'ibc/{hash(tracePath + baseDenom)}'. // If the trace is empty, it will return the base denomination. func (dt DenomTrace) IBCDenom() string { if dt.Path != "" { return fmt.Sprintf("ibc/%s", dt.Hash()) } return dt.BaseDenom } ``` ### `x/ibc-transfer` Changes In order to retrieve the trace information from an IBC denomination, a lookup table needs to be added to the `ibc-transfer` module. These values need to also be persisted between upgrades, meaning that a new `[]DenomTrace` `GenesisState` field state needs to be added to the module: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // GetDenomTrace retrieves the full identifiers trace and base denomination from the store. func (k Keeper) GetDenomTrace(ctx Context, denomTraceHash []byte) (DenomTrace, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.KeyDenomTrace(traceHash)) if bz == nil { return &DenomTrace, false } var denomTrace DenomTrace k.cdc.MustUnmarshalBinaryBare(bz, &denomTrace) return denomTrace, true } // HasDenomTrace checks if a the key with the given trace hash exists on the store. func (k Keeper) HasDenomTrace(ctx Context, denomTraceHash []byte) bool { store := ctx.KVStore(k.storeKey) return store.Has(types.KeyTrace(denomTraceHash)) } // SetDenomTrace sets a new {trace hash -> trace} pair to the store. func (k Keeper) SetDenomTrace(ctx Context, denomTrace DenomTrace) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryBare(&denomTrace) store.Set(types.KeyTrace(denomTrace.Hash()), bz) } ``` The `MsgTransfer` will validate that the `Coin` denomination from the `Token` field contains a valid hash, if the trace info is provided, or that the base denominations matches: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (msg MsgTransfer) ValidateBasic() error { // ... return ValidateIBCDenom(msg.Token.Denom) } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ValidateIBCDenom validates that the given denomination is either: // // - A valid base denomination (eg: 'uatom') // - A valid fungible token representation (i.e 'ibc/{hash}') per ADR 001 https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md func ValidateIBCDenom(denom string) error { denomSplit := strings.SplitN(denom, "/", 2) switch { case strings.TrimSpace(denom) == "", len(denomSplit) == 1 && denomSplit[0] == "ibc", len(denomSplit) == 2 && (denomSplit[0] != "ibc" || strings.TrimSpace(denomSplit[1]) == ""): return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) case denomSplit[0] == denom && strings.TrimSpace(denom) != "": return sdk.ValidateDenom(denom) } if _, err := ParseHexHash(denomSplit[1]); err != nil { return Wrapf(err, "invalid denom trace hash %s", denomSplit[1]) } return nil } ``` The denomination trace info only needs to be updated when token is received: * Receiver is **source** chain: The receiver created the token and must have the trace lookup already stored (if necessary *ie* native token case wouldn't need a lookup). * Receiver is **not source** chain: Store the received info. For example, during step 1, when chain `B` receives `transfer/channelToA/denom`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SendTransfer // ... fullDenomPath := token.Denom // deconstruct the token denomination into the denomination trace info // to determine if the sender is the source chain if strings.HasPrefix(token.Denom, "ibc/") { fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) if err != nil { return err } } if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) { //... ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash // component. func (k Keeper) DenomPathFromHash(ctx sdk.Context, denom string) (string, error) { hexHash := denom[4:] hash, err := ParseHexHash(hexHash) if err != nil { return "", Wrap(ErrInvalidDenomForTransfer, err.Error()) } denomTrace, found := k.GetDenomTrace(ctx, hash) if !found { return "", Wrap(ErrTraceNotFound, hexHash) } fullDenomPath := denomTrace.GetFullDenomPath() return fullDenomPath, nil } ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // OnRecvPacket // ... // This is the prefix that would have been prefixed to the denomination // on sender chain IF and only if the token originally came from the // receiving chain. // // NOTE: We use SourcePort and SourceChannel here, because the counterparty // chain would have prefixed with DestPort and DestChannel when originally // receiving this coin as seen in the "sender chain is the source" condition. if ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) { // sender chain is not the source, unescrow tokens // remove prefix added by sender chain voucherPrefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) unprefixedDenom := data.Denom[len(voucherPrefix):] token := sdk.NewCoin(unprefixedDenom, sdk.NewIntFromUint64(data.Amount)) // unescrow tokens escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) return k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token)) } // sender chain is the source, mint vouchers // since SendPacket did not prefix the denomination, we must prefix denomination here sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) // NOTE: sourcePrefix contains the trailing "/" prefixedDenom := sourcePrefix + data.Denom // construct the denomination trace from the full raw denomination denomTrace := types.ParseDenomTrace(prefixedDenom) // set the value to the lookup table if not stored already traceHash := denomTrace.Hash() if !k.HasDenomTrace(ctx, traceHash) { k.SetDenomTrace(ctx, traceHash, denomTrace) } voucherDenom := denomTrace.IBCDenom() voucher := sdk.NewCoin(voucherDenom, sdk.NewIntFromUint64(data.Amount)) // mint new tokens if the source of the transfer is the same chain if err := k.bankKeeper.MintCoins( ctx, types.ModuleName, sdk.NewCoins(voucher), ); err != nil { return err } // send to receiver return k.bankKeeper.SendCoinsFromModuleToAccount( ctx, types.ModuleName, receiver, sdk.NewCoins(voucher), ) ``` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func NewDenomTraceFromRawDenom(denom string) DenomTrace{ denomSplit := strings.Split(denom, "/") trace := "" if len(denomSplit) > 1 { trace = strings.Join(denomSplit[:len(denomSplit)-1], "/") } return DenomTrace{ BaseDenom: denomSplit[len(denomSplit)-1], Trace: trace, } } ``` One final remark is that the `FungibleTokenPacketData` will remain the same, i.e with the prefixed full denomination, since the receiving chain may not be an SDK-based chain. ### Coin Changes The coin denomination validation will need to be updated to reflect these changes. In particular, the denomination validation function will now: * Accept slash separators (`"/"`) and uppercase characters (due to the `HexBytes` format) * Bump the maximum character length to 128, as the hex representation used by Tendermint's `HexBytes` type contains 64 characters. Additional validation logic, such as verifying the length of the hash, the may be added to the bank module in the future if the [custom base denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755) is integrated into the SDK. ### Positive * Clearer separation of the source tracing behaviour of the token (transfer prefix) from the original `Coin` denomination * Consistent validation of `Coin` fields (i.e no special characters, fixed max length) * Cleaner `Coin` and standard denominations for IBC * No additional fields to SDK `Coin` ### Negative * Store each set of tracing denomination identifiers on the `ibc-transfer` module store * Clients will have to fetch the base denomination every time they receive a new relayed fungible token over IBC. This can be mitigated using a map/cache for already seen hashes on the client side. Other forms of mitigation, would be opening a websocket connection subscribe to incoming events. ### Neutral * Slight difference with the ICS20 spec * Additional validation logic for IBC coins on the `ibc-transfer` module * Additional genesis fields * Slightly increases the gas usage on cross-chain transfers due to access to the store. This should be inter-block cached if transfers are frequent. ## References * [ICS 20 - Fungible token transfer](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) * [Custom Coin Denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755) # ADR 002: Go Module Versioning Source: https://docs.cosmos.network/ibc/latest/architecture/adr-002-go-module-versioning ## Changelog * 05-01-2022: initial draft ## Status Accepted ## Context The IBC module was originally developed in the Cosmos SDK and released during the Stargate release series (v0.42). It was subsequently migrated to its own repository, ibc-go. The first official release on ibc-go was v1.0.0. v1.0.0 was decided to be used instead of v0.1.0 primarily for the following reasons: * Maintaining compatibility with the IBC specification v1 requires stronger support/guarantees. * Using the major, minor, and patch numbers allows for easier communication of what breaking changes are included in a release. * The IBC module is being used by numerous high value projects which require stability. ### Problems #### Go module version must be incremented When a Go module is released under v1.0.0, all following releases must follow Go semantic versioning. Thus when the go API is broken, the Go module major version **must** be incremented. For example, changing the go package version from `v2` to `v3` bumps the import from `github.com/cosmos/ibc-go/v2` to `github.com/cosmos/ibc-go/v3`. If the Go module version is not incremented then attempting to go get a module @v3.0.0 without the suffix results in: `invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3` Version validation was added in Go 1.13. This means that in order to release a v3.0.0 git tag without a /v3 suffix on the module definition, the tag must explicitly **not** contain a go.mod file. Not including a go.mod in our release is not a viable option. #### Attempting to import multiple go module versions for ibc-go Attempting to import two versions of ibc-go, such as `github.com/cosmos/ibc-go/v2` and `github.com/cosmos/ibc-go/v3`, will result in multiple issues. The Cosmos SDK does global registration of error and governance proposal types. The errors and proposals used in ibc-go would need to now register their naming based on the go module version. The more concerning problem is that protobuf definitions will also reach a namespace collision. ibc-go and the Cosmos SDK in general rely heavily on using extended functions for go structs generated from protobuf definitions. This requires the go structs to be defined in the same package as the extended functions. Thus, bumping the import versioning causes the protobuf definitions to be generated in two places (in v2 and v3). When registering these types at compile time, the go compiler will panic. The generated types need to be registered against the proto codec, but there exist two definitions for the same name. The protobuf conflict policy can be overridden via the environment variable `GOLANG_PROTOBUF_REGISTRATION_CONFLICT`, but it is possible this could lead to various runtime errors or unexpected behaviour (see [here](https://github.com/protocolbuffers/protobuf-go/blob/master/reflect/protoregistry/registry.go#L46)). More information [here](https://developers.google.com/protocol-buffers/docs/reference/go/faq#namespace-conflict) on namespace conflicts for protobuf versioning. ### Potential solutions #### Changing the protobuf definition version The protobuf definitions all have a type URL containing the protobuf version for this type. Changing the protobuf version would solve the namespace collision which arise from importing multiple versions of ibc-go, but it leads to new issues. In the Cosmos SDK, `Any`s are unpacked and decoded using the type URL. Changing the type URL thus is creating a distinctly different type. The same registration on the proto codec cannot be used to unpack the new type. For example: All Cosmos SDK messages are packed into `Any`s. If we incremented the protobuf version for our IBC messages, clients which submitted the v1 of our Cosmos SDK messages would now be rejected since the old type is not registered on the codec. The clients must know to submit the v2 of these messages. This pushes the burden of versioning onto relayers and wallets. A more serious problem is that the `ClientState` and `ConsensusState` are packed as `Any`s. Changing the protobuf versioning of these types would break compatibility with IBC specification v1. #### Moving protobuf definitions to their own go module The protobuf definitions could be moved to their own go module which uses 0.x versioning and will never go to 1.0. This prevents the Go module version from being incremented with breaking changes. It also requires all extended functions to live in the same Go module, disrupting the existing code structure. The version that implements this change will still be incompatible with previous versions, but future versions could be imported together without namespace collisions. For example, let's say this solution is implemented in v3. Then `github.com/cosmos/ibc-go/v2` cannot be imported with any other ibc-go version `github.com/cosmos/ibc-go/v3` cannot be imported with any previous ibc-go versions `github.com/cosmos/ibc-go/v4` may be imported with ibc-go versions v3+ `github.com/cosmos/ibc-go/v5` may be imported with ibc-go versions v3+ ## Decision Supporting importing multiple versions of ibc-go requires a non-trivial amount of complexity. It is unclear when a user of the ibc-go code would need multiple versions of ibc-go. Until there is an overwhelming reason to support importing multiple versions of ibc-go: **Major releases cannot be imported simultaneously**. Releases should focus on keeping backwards compatibility for go code clients, within reason. Old functionality should be marked as deprecated and there should exist upgrade paths between major versions. Deprecated functionality may be removed when no clients rely on that functionality. How this is determined is to be decided. **Error and proposal type registration will not be changed between go module version increments**. This explicitly stops external clients from trying to import two major versions (potentially risking a bug due to the instability of proto name collisions override). ## Consequences This only affects clients relying directly on the go code. ### Positive ### Negative Multiple ibc-go versions cannot be imported. ### Neutral # ADR 003: ICS-27 Acknowledgement Format Source: https://docs.cosmos.network/ibc/latest/architecture/adr-003-ics27-acknowledgement ## Changelog * 28-01-2022: Initial Draft ## Status Accepted ## Context Upon receiving an IBC packet, an IBC application can optionally return an acknowledgement. This acknowledgement will be hashed and written into state. Thus any changes to the information included in an acknowledgement are state machine breaking. ICS27 executes transactions on behalf of a controller chain. Information such as the message result or message error may be returned from other SDK modules outside the control of the ICS27 module. It might be very valuable to return message execution information inside the ICS27 acknowledgement so that controller chain interchain account auth modules can act upon this information. Only deterministic information returned from the message execution is allowed to be returned in the packet acknowledgement otherwise the network will halt due to a fork in the expected app hash. ## Decision At the time of this writing, Tendermint includes the following information in the [ABCI.ResponseDeliverTx](https://github.com/tendermint/tendermint/blob/release/v0.34.13/types/results.go#L47-#L53): ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // deterministicResponseDeliverTx strips non-deterministic fields from // ResponseDeliverTx and returns another ResponseDeliverTx. func deterministicResponseDeliverTx(response *abci.ResponseDeliverTx) *abci.ResponseDeliverTx { return &abci.ResponseDeliverTx{ Code: response.Code, Data: response.Data, GasWanted: response.GasWanted, GasUsed: response.GasUsed, } } ``` ### Successful acknowledgements Successful acknowledgements should return information about the transaction execution. Given the deterministic fields in the `abci.ResponseDeliverTx`, the transaction `Data` can be used to indicate information about the transaction execution. The `abci.ResponseDeliverTx.Data` will be set in the ICS27 packet acknowledgement upon successful transaction execution. The format for the `abci.ResponseDeliverTx.Data` is constructed by the SDK. At the time of this writing, the next major release of the SDK will change the format for constructing the transaction response data. #### v0.45 format The current version, v0.45 constructs the transaction response as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} proto.Marshal(&sdk.TxMsgData{ Data: []*sdk.MsgData{msgResponses...}, } ``` Where `msgResponses` is a slice of `*sdk.MsgData`. The `MsgData.MsgType` contains the `sdk.MsgTypeURL` of the `sdk.Msg` being executed. The `MsgData.Data` contains the proto marshaled `MsgResponse` for the associated message executed. #### Next major version format The next major version will construct the transaction response as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} proto.Marshal(&sdk.TxMsgData{ MsgResponses: []*codectypes.Any{msgResponses...}, } ``` Where `msgResponses` is a slice of the `MsgResponse`s packed into `Any`s. #### Forwards compatible approach A forwards compatible approach was deemed infeasible. The `handler` provided by the `MsgServiceRouter` will only include the `*sdk.Result` and an error (if one occurred). In v0.45 of the SDK, the `*sdk.Result.Data` will contain the MsgResponse marshaled data. However, the MsgResponse is not packed and marshaled as a `*codectypes.Any`, thus making it impossible from a generalized point of view to unmarshal the bytes. If the bytes could be unmarshaled, then they could be packed into an `*codectypes.Any` in anticipation of the upcoming format. Intercepting the MsgResponse before it becomes marshaled requires replicating this [code](https://github.com/cosmos/cosmos-sdk/blob/dfd47f5b449f558a855da284a9a7eabbfbad435d/baseapp/msg_service_router.go#L109-#L128). It may not even be possible to replicate the linked code. The method handler would need to be accessed somehow. For these reasons it is deemed infeasible to attempt a forwards compatible approach. ICA auth developers can interpret which format was used when constructing the transaction response by checking if the `sdk.TxMsgData.Data` field is non-empty. If the `sdk.TxMsgData.Data` field is not empty then the format for v0.45 was used, otherwise ICA auth developers can assume the transaction response uses the newer format. #### Decision Replicate the transaction response format as provided by the current SDK version. When the SDK version changes, adjust the transaction response format to use the updated transaction response format. Include the transaction response bytes in the result channel acknowledgement. A test has been [written](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/ibc_module_test.go#L716-#L774) to fail if the `MsgResponse` is no longer included in consensus. ### Error acknowledgements As indicated above, the `abci.ResponseDeliverTx.Code` is deterministic. Upon transaction execution errors, an error acknowledgement should be returned including the abci code. A test has been [written](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/types/ack_test.go#L41-#L82) to fail if the ABCI code is no longer deterministic. ## Consequences > This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. ### Positive * interchain account auth modules can act upon transaction results without requiring a query module * transaction results align with those returned by execution of a normal SDK message. ### Negative * the security assumptions of this decision rest on the inclusion of the ABCI error code and the Msg response in the ResponseDeliverTx hash created by Tendermint * events are non-deterministic and cannot be included in the packet acknowledgement ### Neutral No neutral consequences. # ADR 004: Lock Fee Module Upon Escrow Out Of Balance Source: https://docs.cosmos.network/ibc/latest/architecture/adr-004-ics29-lock-fee-module ## Changelog * 03-03-2022: initial draft ## Status Accepted ## Context The fee module maintains an escrow account for all fees escrowed to incentivize packet relays. It also tracks each packet fee escrowed separately from the escrow account. This is because the escrow account only maintains a total balance. It has no reference for which coins belonged to which packet fee. In the presence of a severe bug, it is possible the escrow balance will become out of sync with the packet fees marked as escrowed. The ICS29 module should be capable of elegantly handling such a scenario. ## Decision We will allow for the ICS29 module to become "locked" if the escrow balance is determined to be out of sync with the packet fees marked as escrowed. A "locked" fee module will not allow for packet escrows to occur nor will it distribute fees. All IBC callbacks will skip performing fee logic, similar to fee disabled channels. Manual intervention will be needed to unlock the fee module. ### Sending side Special behaviour will have to be accounted for in `OnAcknowledgementPacket`. Since the counterparty will continue to send incentivized acknowledgements for fee enabled channels, the acknowledgement will still need to be unmarshalled into an incentivized acknowledgement before calling the underlying application `OnAcknowledgePacket` callback. When distributing fees, a cached context should be used. If the escrow account balance would become negative, the current state changes should be discarded and the fee module should be locked using the uncached context. This prevents fees from being partially distributed for a given packetID. ### Receiving side `OnRecvPacket` should remain unaffected by the fee module becoming locked since escrow accounts only affect the sending side. ## Consequences ### Positive The fee module can be elegantly disabled in the presence of severe bugs. ### Negative Extra logic is added to account for edge cases which are only possible in the presence of bugs. ### Neutral ## References Issues: * [#821](https://github.com/cosmos/ibc-go/issues/821) * [#860](https://github.com/cosmos/ibc-go/issues/860) PR's: * [#1031](https://github.com/cosmos/ibc-go/pull/1031) * [#1029](https://github.com/cosmos/ibc-go/pull/1029) * [#1056](https://github.com/cosmos/ibc-go/pull/1056) # ADR 005: UpdateClient Events ClientState Consensus Heights Source: https://docs.cosmos.network/ibc/latest/architecture/adr-005-consensus-height-events ## Changelog * 25-04-2022: initial draft ## Status Accepted ## Context The `ibc-go` implementation leverages the [Cosmos-SDK's EventManager](https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/docs/core/events.md#EventManager) to provide subscribers a method of reacting to application specific events. Some IBC relayers depend on the [`consensus_height`](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/core/02-client/keeper/events.go#L33) attribute emitted as part of `UpdateClient` events in order to run `07-tendermint` misbehaviour detection by cross-checking the details of the *Header* emitted at a given consensus height against those of the *Header* from the originating chain. This includes such details as: * The `SignedHeader` containing the commitment root. * The `ValidatorSet` that signed the *Header*. * The `TrustedHeight` seen by the client at less than or equal to the height of *Header*. * The last `TrustedValidatorSet` at the trusted height. Following the refactor of the `02-client` submodule and associated `ClientState` interfaces, it will now be possible for light client implementations to perform such actions as batch updates, inserting `N` number of `ConsensusState`s into the application state tree with a single `UpdateClient` message. This flexibility is provided in `ibc-go` by the usage of the [Protobuf `Any`](https://developers.google.com/protocol-buffers/docs/proto3#any) field contained within the [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/v3.0.0/proto/ibc/core/client/v1/tx.proto#L44) message. For example, a batched client update message serialized as a Protobuf `Any` type for the `07-tendermint` lightclient implementation could be defined as follows: ```protobuf theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message BatchedHeaders { repeated Header headers = 1; } ``` To complement this flexibility, the `UpdateClient` handler will now support the submission of [client misbehaviour](https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics#misbehaviour) by consolidating the `Header` and `Misbehaviour` interfaces into a single `ClientMessage` interface type: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ClientMessage is an interface used to update an IBC client. // The update may be done by a single header, a batch of headers, misbehaviour, or any type which when verified produces // a change to state of the IBC client type ClientMessage interface { proto.Message ClientType() string ValidateBasic() error } ``` To support this functionality the `GetHeight()` method has been omitted from the new `ClientMessage` interface. Emission of standardised events from the `02-client` submodule now becomes problematic and is two-fold: 1. The `02-client` submodule previously depended upon the `GetHeight()` method of `Header` types in order to [retrieve the updated consensus height](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/core/02-client/keeper/client.go#L90). 2. Emitting a single `consensus_height` event attribute is not sufficient in the case of a batched client update containing multiple *Headers*. ## Decision The following decisions have been made in order to provide flexibility to consumers of `UpdateClient` events in a non-breaking fashion: 1. Return a list of updated consensus heights `[]exported.Height` from the new `UpdateState` method of the `ClientState` interface. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState. // Upon successful update, a list of consensus heights is returned. It assumes the ClientMessage has already been verified. UpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, ClientMessage) []Height ``` 2. Maintain the `consensus_height` event attribute emitted from the `02-client` update handler, but mark as deprecated for future removal. For example, with tendermint lightclients this will simply be `consensusHeights[0]` following a successful update using a single *Header*. 3. Add an additional `consensus_heights` event attribute, containing a comma separated list of updated heights. This provides flexibility for emitting a single consensus height or multiple consensus heights in the example use-case of batched header updates. ## Consequences ### Positive * Subscribers of IBC core events can act upon `UpdateClient` events containing one or more consensus heights. * Deprecation of the existing `consensus_height` attribute allows consumers to continue to process `UpdateClient` events as normal, with a path to upgrade to using the `consensus_heights` attribute moving forward. ### Negative * Consumers of IBC core `UpdateClient` events are forced to make future code changes. ### Neutral ## References Discussions: * [#1208](https://github.com/cosmos/ibc-go/pull/1208#discussion_r839691927) Issues: * [#594](https://github.com/cosmos/ibc-go/issues/594) PRs: * [#1285](https://github.com/cosmos/ibc-go/pull/1285) # ADR 006: 02-Client Refactor Source: https://docs.cosmos.network/ibc/latest/architecture/adr-006-02-client-refactor ## Changelog * 01-08-2022: Initial Draft ## Status Accepted and applied in v7 of ibc-go ## Context During the initial development of the 02-client submodule, each light client supported (06-solomachine, 07-tendermint, 09-localhost) was referenced through hardcoding. Here is an example of the [code](https://github.com/cosmos/cosmos-sdk/commit/b93300288e3a04faef9c0774b75c13b24450ba1c#diff-c5f6b956947375f28d611f18d0e670cf28f8f305300a89c5a9b239b0eeec5064R83) that existed in the 02-client submodule: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) (exported.ClientState, error) { ... switch clientType { case exported.Tendermint: clientState, consensusState, err = tendermint.CheckValidityAndUpdateState( clientState, header, ctx.BlockTime(), ) case exported.Localhost: // override client state and update the block height clientState = localhosttypes.NewClientState( ctx.ChainID(), // use the chain ID from context since the client is from the running chain (i.e self). ctx.BlockHeight(), ) default: err = types.ErrInvalidClientType } ``` To add additional light clients, code would need to be added directly to the 02-client submodule. Evidently, this would likely become problematic as IBC scaled to many chains using consensus mechanisms beyond the initial supported light clients. Issue [#6064](https://github.com/cosmos/cosmos-sdk/issues/6064) on the SDK addressed this problem by creating a more modular 02-client submodule. The 02-client submodule would now interact with each light client via an interface. While, this change was positive in development, increasing the flexibility and adoptability of IBC, it also opened the door to new problems. The difficulty of generalizing light clients became apparent once changes to those light clients were required. Each light client represents a different consensus algorithm which may contain a host of complexity and nuances. Here are some examples of issues which arose for light clients that are not applicable to all the light clients supported (06-solomachine, 07-tendermint, 09-localhost): ### Tendermint non-zero height upgrades Before the launch of IBC, it was determined that the golang implementation of [tendermint](https://github.com/tendermint/tendermint) would not be capable of supporting non-zero height upgrades. This implies that any upgrade would require changing of the chain ID and resetting the height to 0. A chain is uniquely identified by its chain-id and validator set. Two different chain ID's can be viewed as different chains and thus a normal update produced by a validator set cannot change the chain ID. To work around the lack of support for non-zero height upgrades, an abstract height type was created along with an upgrade mechanism. This type would indicate the revision number (the number of times the chain ID has been changed) and revision height (the current height of the blockchain). Refs: * Issue [#439](https://github.com/cosmos/ibc/issues/439) on IBC specification repository. * Specification changes in [#447](https://github.com/cosmos/ibc/pull/447) * Implementation changes for the abstract height type, [SDK#7211](https://github.com/cosmos/cosmos-sdk/pull/7211) ### Tendermint requires misbehaviour detection during updates The initial release of the IBC module and the 07-tendermint light client implementation did not support misbehaviour detection during update nor did it prevent overwriting of previous updates. Despite the fact that we designed the `ClientState` interface and developed the 07-tendermint client, we failed to detect even a duplicate update that constituted misbehaviour and thus should freeze the client. This was fixed in PR [#141](https://github.com/cosmos/ibc-go/pull/141) which required light client implementations to be aware that they must handle duplicate updates and misbehaviour detection. Misbehaviour detection during updates is not applicable to the solomachine nor localhost. It is also not obvious that `CheckHeaderAndUpdateState` should be performing this functionality. ### Localhost requires access to the entire client store The localhost has been broken since the initial version of the IBC module. The localhost tried to be developed underneath the 02-client interfaces without special exception, but this proved to be impossible. The issues were outlined in [#27](https://github.com/cosmos/ibc-go/issues/27) and further discussed in the attempted ADR in [#75](https://github.com/cosmos/ibc-go/pull/75). Unlike all other clients, the localhost requires access to the entire IBC store and not just the prefixed client store. ### Solomachine doesn't set consensus states The 06-solomachine does not set the consensus states within the prefixed client store. It has a single consensus state that is stored within the client state. This causes setting of the consensus state at the 02-client level to use unnecessary storage. It also causes timeouts to fail with solo machines. Previously, the timeout logic within IBC would obtain the consensus state at the height a timeout is being proved. This is problematic for the solo machine as no consensus state is set. See issue [#562](https://github.com/cosmos/ibc/issues/562) on the IBC specification repo. ### New clients may want to do batch updates New light clients may not function in a similar fashion to 06-solomachine and 07-tendermint. They may require setting many consensus states in a single update. As @seunlanlege [states](https://github.com/cosmos/ibc-go/issues/284#issuecomment-1005583679): > I'm in support of these changes for 2 reasons: > > * This would allow light clients to handle batch header updates in CheckHeaderAndUpdateState, for the special case of 11-beefy proving the finality for a batch of headers is much more space and time efficient than the space/time complexity of proving each individual headers in that batch, combined. > > * This also allows for a single light client instance of 11-beefy be used to prove finality for every parachain connected to the relay chain (Polkadot/Kusama). We achieve this by setting the appropriate ConsensusState for individual parachain headers in CheckHeaderAndUpdateState ## Decision ### Require light clients to set client and consensus states The IBC specification states: > If the provided header was valid, the client MUST also mutate internal state to store now-finalised consensus roots and update any necessary signature authority tracking (e.g. changes to the validator set) for future calls to the validity predicate. The initial version of the IBC go SDK based module did not fulfill this requirement. Instead, the 02-client submodule required each light client to return the client and consensus state which should be updated in the client prefixed store. This decision lead to the issues "Solomachine doesn't set consensus states" and "New clients may want to do batch updates". Each light client should be required to set its own client and consensus states on any update necessary. The go implementation should be changed to match the specification requirements. This will allow more flexibility for light clients to manage their own internal storage and do batch updates. ### Merge `Header`/`Misbehaviour` interface and rename to `ClientMessage` Remove `GetHeight()` from the header interface (as light clients now set the client/consensus states). This results in the `Header`/`Misbehaviour` interfaces being the same. To reduce complexity of the codebase, the `Header`/`Misbehaviour` interfaces should be merged into `ClientMessage`. `ClientMessage` will provide the client with some authenticated information which may result in regular updates, misbehaviour detection, batch updates, or other custom functionality a light client requires. ### Split `CheckHeaderAndUpdateState` into 4 functions See [#668](https://github.com/cosmos/ibc-go/issues/668). Split `CheckHeaderAndUpdateState` into 4 functions: * `VerifyClientMessage` * `CheckForMisbehaviour` * `UpdateStateOnMisbehaviour` * `UpdateState` `VerifyClientMessage` checks the that the structure of a `ClientMessage` is correct and that all authentication data provided is valid. `CheckForMisbehaviour` checks to see if a `ClientMessage` is evidence of misbehaviour. `UpdateStateOnMisbehaviour` freezes the client and updates its state accordingly. `UpdateState` performs a regular update or a no-op on duplicate updates. The code roughly looks like: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error { ... if err := clientState.VerifyClientMessage(clientMessage); err != nil { return err } foundMisbehaviour := clientState.CheckForMisbehaviour(clientMessage) if foundMisbehaviour { clientState.UpdateStateOnMisbehaviour(header) // emit misbehaviour event return } clientState.UpdateState(clientMessage) // expects no-op on duplicate header // emit update event return } ``` ### Add `GetTimestampAtHeight` to the client state interface By adding `GetTimestampAtHeight` to the ClientState interface, we allow light clients which do non-traditional consensus state/timestamp storage to process timeouts correctly. This fixes the issues outlined for the solo machine client. ### Add generic verification functions As the complexity and the functionality grows, new verification functions will be required for additional paths. This was explained in [#684](https://github.com/cosmos/ibc/issues/684) on the specification repo. These generic verification functions would be immediately useful for the new paths added in connection/channel upgradability as well as for custom paths defined by IBC applications such as Interchain Queries. The old verification functions (`VerifyClientState`, `VerifyConnection`, etc) should be removed in favor of the generic verification functions. ## Consequences ### Positive * Flexibility for light client implementations * Well defined interfaces and their required functionality * Generic verification functions * Applies changes necessary for future client/connection/channel upgrabability features * Timeout processing for solo machines * Reduced code complexity ### Negative * The refactor touches on sensitive areas of the ibc-go codebase * Changing of established naming (`Header`/`Misbehaviour` to `ClientMessage`) ### Neutral No notable consequences ## References Issues: * [#284](https://github.com/cosmos/ibc-go/issues/284) PRs: * [#1871](https://github.com/cosmos/ibc-go/pull/1871) # ADR 007: Solo Machine Sign Bytes Source: https://docs.cosmos.network/ibc/latest/architecture/adr-007-solomachine-signbytes ## Changelog * 02-08-2022: Initial draft ## Status Accepted, applied in v7 ## Context The `06-solomachine` implementation up until ibc-go v7 constructed sign bytes using a `DataType` which described what type of data was being signed. This design decision arose from a misunderstanding of the security implications. It was noted that the proto definitions do not [provide uniqueness](https://github.com/cosmos/cosmos-sdk/pull/7237#discussion_r484264573) which is a necessity for ensuring two signatures over different data types can never be the same. What was missed is that the uniqueness is not provided by the proto definition, but by the usage of the proto definition. The path provided by core IBC will be unique and is already encoded into the signature data. Thus two different paths with the same data values will encode differently which provides signature uniqueness. Furthermore, the current construction does not support the proposed changes in the spec repo to support [Generic Verification functions](https://github.com/cosmos/ibc/issues/684). This is because in order to verify a new path, a new `DataType` must be added for that path. ## Decision Remove `DataType` and change the `DataType` in the `SignBytes` and `SignatureAndData` to be `Path`. The new `Path` field should be bytes. Remove all `...Data` proto definitions except for `HeaderData` These `...Data` definitions were created previously for each `DataType`. The proto version of the solo machine proto definitions should be bumped to `v3`. This removes an extra layer of complexity from signature construction and allows for support of generic verification. ## Consequences ### Positive * Simplification of solo machine signature construction * Support for generic verification ### Negative * Breaks existing signature construction in a non-backwards compatible way * Solo machines must update to handle the new format * Migration required for solo machine client and consensus states ### Neutral No notable consequences ## References * [#1141](https://github.com/cosmos/ibc-go/issues/1141) # ADR 008: Callback to IBC Actors Source: https://docs.cosmos.network/ibc/latest/architecture/adr-008-app-caller-cbs ## Changelog * 10-08-2022: Initial Draft * 22-03-2023: Merged * 13-09-2023: Updated with decisions made in implementation * 24-02-2025: RecvPacket callback error now returns error acknowledgement ## Status Accepted, middleware implemented ## Context IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC. When the result of the packet lifecycle eventually resolved into either an acknowledgement or a timeout, core IBC called a callback on the IBC application so that the IBC application could take action on the basis of the result (e.g. unescrow tokens for ICS-20). This setup worked well for off-chain users interacting with IBC applications. We are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on the basis of the packet result. Or to receive a packet from IBC and do some logic upon receipt. Example Usecases: * Send an ICS-20 packet, and if it is successful, then send an ICA-packet to swap tokens on LP and return funds to sender * Execute some logic upon receipt of token transfer to a smart contract address This requires a second layer of callbacks. The IBC application already gets the result of the packet from core IBC, but currently there is no standardized way to pass this information on to an actor module/smart contract. ## Definitions * Actor: an actor is an on-chain module (this may be a hardcoded module in the chain binary or a smart contract) that wishes to execute custom logic whenever IBC receives a packet flow that it has either sent or received. It **must** be addressable by a string value. ## Decision Create a middleware that can interface between IBC applications and smart contract VMs. The IBC applications and smart contract VMs will implement respective interfaces that will then be composed together by the callback middleware to allow a smart contract of any compatible VM to interact programmatically with an IBC application. ## Data structures The `CallbackPacketData` struct will get constructed from custom callback data in the application packet. The `CallbackAddress` is the IBC Actor address on which the callback should be called on. The `SenderAddress` is also provided to optionally allow a VM to ensure that the sender is the same as the callback address. The struct also defines a `CommitGasLimit` which is the maximum gas a callback is allowed to use. If the callback exceeds this limit, the callback will panic and the tx will commit without the callback's state changes. The `ExecutionGasLimit` is the practical limit of the tx execution that is set in the context gas meter. It is the minimum of the `CommitGasLimit` and the gas left in the context gas meter which is determined by the relayer's choice of tx gas limit. If `ExecutionGasLimit < CommitGasLimit`, then an out-of-gas error will revert the entire transaction without committing anything, allowing for a different relayer to retry with a larger tx gas limit. Any middleware targeting this interface for callback handling should define a global limit that caps the gas that a callback is allowed to take (especially on AcknowledgePacket and TimeoutPacket) so that a custom callback does not prevent the packet lifecycle from completing. However, since this is a global cap it is likely to be very large. Thus, users may specify a smaller limit to cap the amount of fees a relayer must pay in order to complete the packet lifecycle on the user's behalf. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Implemented by any packet data type that wants to support PacketActor callbacks // PacketActor's will be unable to act on any packet data type that does not implement // this interface. type CallbackPacketData struct { CallbackAddress: string ExecutionGasLimit: uint64 SenderAddress: string CommitGasLimit: uint64 } ``` IBC Apps or middleware can then call the IBCActor callbacks like so in their own callbacks: ### Callback Middleware The CallbackMiddleware wraps an underlying IBC application along with a contractKeeper that delegates the callback to a virtual machine. This allows the Callback middleware to interface any compatible IBC application with any compatible VM (e.g. EVM, WASM) so long as the application implements the `CallbacksCompatibleModule` interface and the VM implements the `ContractKeeper` interface. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // IBCMiddleware implements the ICS26 callbacks for the ibc-callbacks middleware given // the underlying application. type IBCMiddleware struct { app types.CallbacksCompatibleModule ics4Wrapper porttypes.ICS4Wrapper contractKeeper types.ContractKeeper // maxCallbackGas defines the maximum amount of gas that a callback actor can ask the // relayer to pay for. If a callback fails due to insufficient gas, the entire tx // is reverted if the relayer hadn't provided the minimum(userDefinedGas, maxCallbackGas). // If the actor hasn't defined a gas limit, then it is assumed to be the maxCallbackGas. maxCallbackGas uint64 } ``` ### Callback-Compatible IBC Application The `CallbacksCompatibleModule` extends `porttypes.IBCModule` to include an `UnmarshalPacketData` function that allows the middleware to request that the underlying app unmarshal the packet data. This will then allow the middleware to retrieve the callback specific data from an arbitrary set of IBC application packets. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CallbacksCompatibleModule is an interface that combines the IBCModule and PacketDataUnmarshaler // interfaces to assert that the underlying application supports both. type CallbacksCompatibleModule interface { porttypes.IBCModule porttypes.PacketDataUnmarshaler } // PacketDataUnmarshaler defines an optional interface which allows a middleware to // request the packet data to be unmarshaled by the base application. type PacketDataUnmarshaler interface { // UnmarshalPacketData unmarshals the packet data into a concrete type // ctx, portID, channelID are provided as arguments, so that (if needed) // the packet data can be unmarshaled based on the channel version. // the version of the underlying app is also returned. UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{}, string, error) } ``` The application's packet data must additionally implement the following interfaces: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PacketData defines an optional interface which an application's packet data structure may implement. type PacketData interface { // GetPacketSender returns the sender address of the packet data. // If the packet sender is unknown or undefined, an empty string should be returned. GetPacketSender(sourcePortID string) string } // PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. // An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. // A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. // This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. type PacketDataProvider interface { // GetCustomPacketData returns the packet data held on behalf of another application. // The name the information is stored under should be provided as the key. // If no custom packet data exists for the key, nil should be returned. GetCustomPacketData(key string) interface{} } ``` The callback data can be embedded in an application packet by providing custom packet data for source and destination callback in the custom packet data under the appropriate key. ```jsonc theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Custom Packet data embedded as a JSON object in the packet data // src callback custom data { "src_callback": { "address": "callbackAddressString", // optional "gas_limit": "userDefinedGasLimitString", } } // dest callback custom data { "dest_callback": { "address": "callbackAddressString", // optional "gas_limit": "userDefinedGasLimitString", } } // src and dest callback custom data embedded together { "src_callback": { "address": "callbackAddressString", // optional "gas_limit": "userDefinedGasLimitString", }, "dest_callback": { "address": "callbackAddressString", // optional "gas_limit": "userDefinedGasLimitString", } } ``` ## ContractKeeper The `ContractKeeper` interface must be implemented by any VM that wants to support IBC callbacks. This allows for separation of concerns between the middleware which is handling logic intended for all VMs (e.g. setting gas meter, extracting callback data, emitting events), while the ContractKeeper can handle the specific details of calling into the VM in question. The `ContractKeeper` **may** impose additional checks such as ensuring that the contract address is the same as the packet sender in source callbacks. It may also disable certain callback methods by simply performing a no-op. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract type ContractKeeper interface { // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The // packetSenderAddress is determined by the underlying module, and may be empty if the sender is // unknown or undefined. The contract is expected to handle the callback within the user defined // gas limit, and handle any errors, or panics gracefully. // This entry point is called with a cached context. If an error is returned, then the changes in // this context will not be persisted, and the error will be propagated to the underlying IBC // application, resulting in a packet send failure. // // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform // validation on the origin of a given packet. It is recommended to perform the same validation // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. // // The version provided is the base application version for the given packet send. This allows // contracts to determine how to unmarshal the packetData. IBCSendPacketCallback( cachedCtx sdk.Context, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, packetData []byte, contractAddress, packetSenderAddress string, version string, ) error // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement // is received. The packetSenderAddress is determined by the underlying module, and may be empty if // the sender is unknown or undefined. The contract is expected to handle the callback within the // user defined gas limit, and handle any errors, or panics gracefully. // This entry point is called with a cached context. If an error is returned, then the changes in // this context will not be persisted, but the packet lifecycle will not be blocked. // // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform // validation on the origin of a given packet. It is recommended to perform the same validation // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. // // The version provided is the base application version for the given packet send. This allows // contracts to determine how to unmarshal the packetData. IBCOnAcknowledgementPacketCallback( cachedCtx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, contractAddress, packetSenderAddress string, version string, ) error // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before // the timeout height. The packetSenderAddress is determined by the underlying module, and may be // empty if the sender is unknown or undefined. The contract is expected to handle the callback // within the user defined gas limit, and handle any error, out of gas, or panics gracefully. // This entry point is called with a cached context. If an error is returned, then the changes in // this context will not be persisted, but the packet lifecycle will not be blocked. // // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform // validation on the origin of a given packet. It is recommended to perform the same validation // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. // // The version provided is the base application version for the given packet send. This allows // contracts to determine how to unmarshal the packetData. IBCOnTimeoutPacketCallback( cachedCtx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, contractAddress, packetSenderAddress string, version string, ) error // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. // The contract is expected to handle the callback within the user defined gas limit. // This entry point is called with a cached context. If an error is returned, then the error // will be written as an error acknowledgement. This will cause the context changes made by the contract // to be reverted along with any state changes made by the underlying application. // The error acknowledgement will then be relayed to the sending application which can perform // its error acknowledgement logic (e.g. refunding tokens back to user) // // The version provided is the base application version for the given packet send. This allows // contracts to determine how to unmarshal the packetData. IBCReceivePacketCallback( cachedCtx sdk.Context, packet ibcexported.PacketI, ack ibcexported.Acknowledgement, contractAddress string, version string, ) error } ``` ### PacketCallbacks The packet callbacks implemented in the middleware will first call the underlying application and then route to the IBC actor callback in the post-processing step. It will extract the callback data from the application packet and set the callback gas meter depending on the global limit, the user limit, and the gas left in the transaction gas meter. The callback will then be routed through the callback keeper which will either panic or return a result (success or failure). In the event of a (non-oog) panic or an error, the callback state changes are discarded and the transaction is committed. If the relayer-defined gas limit is exceeded before the user-defined gas limit or global callback gas limit is exceeded, then the entire transaction is reverted to allow for resubmission. If the chain-defined or user-defined gas limit is reached, the callback state changes are reverted and the transaction is committed. For the `SendPacket` callback, we will revert the entire transaction on any kind of error or panic. This is because the packet lifecycle has not yet started, so we can revert completely to avoid starting the packet lifecycle if the callback is not successful. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // SendPacket implements source callbacks for sending packets. // It defers to the underlying application and then calls the contract callback. // If the contract callback returns an error, panics, or runs out of gas, then // the packet send is rejected. func (im IBCMiddleware) SendPacket( ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte, ) (uint64, error) { // run underlying app logic first // IBCActor logic will postprocess seq, err := im.ics4Wrapper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) if err != nil { return 0, err } // use underlying app to get source callback information from packet data callbackData, err := types.GetSourceCallbackData(im.app, data, sourcePort, ctx.GasMeter().GasRemaining(), im.maxCallbackGas) // SendPacket is not blocked if the packet does not opt-in to callbacks if err != nil { return seq, nil } callbackExecutor := func(cachedCtx sdk.Context) error { return im.contractKeeper.IBCSendPacketCallback( cachedCtx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, callbackData.CallbackAddress, callbackData.SenderAddress, ) } err = im.processCallback(ctx, types.CallbackTypeSendPacket, callbackData, callbackExecutor) // contract keeper is allowed to reject the packet send. if err != nil { return 0, err } types.EmitCallbackEvent(ctx, sourcePort, sourceChannel, seq, types.CallbackTypeSendPacket, callbackData, nil) return seq, nil } // WriteAcknowledgement implements the ReceivePacket destination callbacks for the ibc-callbacks middleware // during asynchronous packet acknowledgement. // It defers to the underlying application and then calls the contract callback. // If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are // reverted via a panic. func (im IBCMiddleware) WriteAcknowledgement( ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement, ) error { // run underlying app logic first // IBCActor logic will postprocess err := im.ics4Wrapper.WriteAcknowledgement(ctx, chanCap, packet, ack) if err != nil { return err } // use underlying app to get destination callback information from packet data callbackData, err := types.GetDestCallbackData( im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, ) // WriteAcknowledgement is not blocked if the packet does not opt-in to callbacks if err != nil { return nil } callbackExecutor := func(cachedCtx sdk.Context) error { return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress) } // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions err = im.processCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor) // emit events types.EmitCallbackEvent( ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), types.CallbackTypeAcknowledgementPacket, callbackData, err, ) return nil } // Call the IBCActor recvPacket callback after processing the packet // if the recvPacket callback exists. If the callback returns an error // then return an error ack to revert all packet data processing. func (im IBCMiddleware) OnRecvPacket( ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, ) (ack exported.Acknowledgement) { // run underlying app logic first // IBCActor logic will postprocess ack := im.app.OnRecvPacket(ctx, packet, relayer) // if ack is nil (asynchronous acknowledgements), then the callback will be handled in WriteAcknowledgement // if ack is not successful, all state changes are reverted. If a packet cannot be received, then there is // no need to execute a callback on the receiving chain. if ack == nil || !ack.Success() { return ack } // use underlying app to get destination callback information from packet data callbackData, err := types.GetDestCallbackData( im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, ) // OnRecvPacket is not blocked if the packet does not opt-in to callbacks if err != nil { return ack } callbackExecutor := func(cachedCtx sdk.Context) error { return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress) } // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions err = im.processCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor) types.EmitCallbackEvent( ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), types.CallbackTypeReceivePacket, callbackData, err, ) if err != nil { return channeltypes.NewErrorAcknowledgement(err) } return ack } // Call the IBCActor acknowledgementPacket callback after processing the packet // if the ackPacket callback exists and returns an error // DO NOT return the error upstream. The acknowledgement must complete for the packet // lifecycle to end, so the custom callback cannot block completion. // Instead we emit error events and set the error in state // so that users and on-chain logic can handle this appropriately func (im IBCModule) OnAcknowledgementPacket( ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, ) error { // we first call the underlying app to handle the acknowledgement // IBCActor logic will postprocess err := im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) if err != nil { return err } // use underlying app to get source callback information from packet data callbackData, err := types.GetSourceCallbackData( im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, ) // OnAcknowledgementPacket is not blocked if the packet does not opt-in to callbacks if err != nil { return nil } callbackExecutor := func(cachedCtx sdk.Context) error { return im.contractKeeper.IBCOnAcknowledgementPacketCallback( cachedCtx, packet, acknowledgement, relayer, callbackData.CallbackAddress, callbackData.SenderAddress, ) } // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions err = im.processCallback(ctx, types.CallbackTypeAcknowledgementPacket, callbackData, callbackExecutor) types.EmitCallbackEvent( ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), types.CallbackTypeAcknowledgementPacket, callbackData, err, ) return nil } // Call the IBCActor timeoutPacket callback after processing the packet // if the timeoutPacket callback exists and returns an error // DO NOT return the error upstream. The timeout must complete for the packet // lifecycle to end, so the custom callback cannot block completion. // Instead we emit error events and set the error in state // so that users and on-chain logic can handle this appropriately func (im IBCModule) OnTimeoutPacket( ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, ) error { // application-specific onTimeoutPacket logic err := im.app.OnTimeoutPacket(ctx, packet, relayer) if err != nil { return err } // use underlying app to get source callback information from packet data callbackData, err := types.GetSourceCallbackData( im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, ) // OnTimeoutPacket is not blocked if the packet does not opt-in to callbacks if err != nil { return nil } callbackExecutor := func(cachedCtx sdk.Context) error { return im.contractKeeper.IBCOnTimeoutPacketCallback(cachedCtx, packet, relayer, callbackData.CallbackAddress, callbackData.SenderAddress) } // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions err = im.processCallback(ctx, types.CallbackTypeTimeoutPacket, callbackData, callbackExecutor) types.EmitCallbackEvent( ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), types.CallbackTypeTimeoutPacket, callbackData, err, ) return nil } // processCallback executes the callbackExecutor and reverts contract changes if the callbackExecutor fails. // // Error Precedence and Returns: // - oogErr: Takes the highest precedence. If the callback runs out of gas, an error wrapped with types.ErrCallbackOutOfGas is returned. // - panicErr: Takes the second-highest precedence. If a panic occurs and it is not propagated, an error wrapped with types.ErrCallbackPanic is returned. // - callbackErr: If the callbackExecutor returns an error, it is returned as-is. // // panics if // - the contractExecutor panics for any reason, and the callbackType is SendPacket, or // - the contractExecutor runs out of gas and the relayer has not reserved gas grater than or equal to // CommitGasLimit. func (IBCMiddleware) processCallback( ctx sdk.Context, callbackType types.CallbackType, callbackData types.CallbackData, callbackExecutor func(sdk.Context) error, ) (err error) { cachedCtx, writeFn := ctx.CacheContext() cachedCtx = cachedCtx.WithGasMeter(storetypes.NewGasMeter(callbackData.ExecutionGasLimit)) defer func() { // consume the minimum of g.consumed and g.limit ctx.GasMeter().ConsumeGas(cachedCtx.GasMeter().GasConsumedToLimit(), fmt.Sprintf("ibc %s callback", callbackType)) // recover from all panics except during SendPacket callbacks if r := recover(); r != nil { if callbackType == types.CallbackTypeSendPacket { panic(r) } err = errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, r) } // if the callback ran out of gas and the relayer has not reserved enough gas, then revert the state if cachedCtx.GasMeter().IsPastLimit() { if callbackData.AllowRetry() { panic(storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)}) } err = errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType) } // allow the transaction to be committed, continuing the packet lifecycle }() err = callbackExecutor(cachedCtx) if err == nil { writeFn() } return err } ``` Chains are expected to specify a `maxCallbackGas` to ensure that callbacks do not consume an arbitrary amount of gas. Thus, it should always be possible for a relayer to complete the packet lifecycle even if the actor callbacks cannot run successfully. ## Consequences ### Positive * IBC Actors can now programmatically execute logic that involves sending a packet and then performing some additional logic once the packet lifecycle is complete * Middleware implementing ADR-8 can be generally used for any application * Leverages a similar callback architecture to the one used between core IBC and IBC applications ### Negative * Callbacks may now have unbounded gas consumption since the actor may execute arbitrary logic. Chains implementing this feature should take care to place limitations on how much gas an actor callback can consume. * The relayer pays for the callback gas instead of the IBCActor ### Neutral * Application packets that want to support ADR-8 must additionally have their packet data implement `PacketDataProvider` and `PacketData` interfaces. * Applications must implement `PacketDataUnmarshaler` interface * Callback receiving module must implement the `ContractKeeper` interface ## References * [Original issue](https://github.com/cosmos/ibc-go/issues/1660) * [CallbackPacketData interface implementation](https://github.com/cosmos/ibc-go/pull/3287) * [ICS 20, ICS 27 implementations of the CallbackPacketData interface](https://github.com/cosmos/ibc-go/pull/3287) # ADR 009: ICS27 Message Server Addition Source: https://docs.cosmos.network/ibc/latest/architecture/adr-009-v6-ics27-msgserver ## Changelog * 07-09-2022: Initial draft ## Status Accepted, implemented in v6 of ibc-go ## Context ICS 27 (Interchain Accounts) brought a cross-chain account management protocol built upon IBC. It enabled chains to programmatically create accounts on behalf of counterparty chains which may enable a variety of authentication methods for this interchain account. The initial release of ICS 27 focused on enabling authentication schemes that may not require signing with a private key, such as via on-chain mechanisms like governance. Following the initial release of ICS 27 it became evident that: * a default authentication module would enable more usage of ICS 27 * generic authentication modules should be capable of authenticating an interchain account registration * application logic which wraps ICS 27 packet sends does not need to be associated with the authentication logic ## Decision The controller module should be simplified to remove the correlation between the authentication logic for an interchain account and the application logic for an interchain account. To minimize disruption to developers working on the original design of the ICS 27 controller module, all changes will be made in a backwards compatible fashion. ### Msg server To achieve this, as stated by [@damiannolan](https://github.com/cosmos/ibc-go/issues/2026#issue-1341640594), it was proposed to: > Add a new `MsgServer` to `27-interchain-accounts` which exposes two distinct rpc endpoints: > > * `RegisterInterchainAccount` > * `SendTx` This will enable any SDK (authentication) module to register interchain accounts and send transactions on their behalf. Examples of existing SDK modules which would benefit from this change include: * x/auth * x/gov * x/group The existing go functions: `RegisterInterchainAccount()` and `SendTx()` will remain to operate as they did in previous release versions. This will be possible for SDK v0.46.x and above. ### Allow `nil` underlying applications Authentication modules should interact with the controller module via the message server and should not be associated with application logic. For now, it will be allowed to set a `nil` underlying application. A future version may remove the underlying application entirely. See issue [#2040](https://github.com/cosmos/ibc-go/issues/2040) ### Channel capability claiming The controller module will now claim the channel capability in `OnChanOpenInit`. Underlying applications will be passed a `nil` capability in `OnChanOpenInit`. Channel capability migrations will be added in two steps: * Upgrade handler migration which modifies the channel capability owner from the underlying app to the controller module * ICS 27 module automatic migration which asserts the upgrade handler channel capability migration has been performed successfully See issue [#2033](https://github.com/cosmos/ibc-go/issues/2033) ### Middleware enabled channels In order to maintain backwards compatibility and avoid requiring underlying application developers to account for interchain accounts they did not register, a boolean mapping has been added to track the behaviour of how an account was created. If the account was created via the legacy API, then the underlying application callbacks will be executed. If the account was created with the new API (message server), then the underlying application callbacks will not be executed. See issue [#2145](https://github.com/cosmos/ibc-go/issues/2145) ### Future considerations [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) proposes the creation of a middleware which enables callers of an IBC packet send to perform application logic in conjunction with the IBC application. The underlying application can be removed at the availability of such a middleware as that will be the preferred method for executing application logic upon a ICS 27 packet send. ### Miscellaneous In order to avoid import cycles, the genesis types have been moved to their own directory. A new protobuf package has been created for the genesis types. See PR [#2133](https://github.com/cosmos/ibc-go/pull/2133) An additional field has been added to the `ActiveChannel` type to store the `IsMiddlewareEnabled` field upon genesis import/export. See issue [#2165](https://github.com/cosmos/ibc-go/issues/2165) ## Consequences ### Positive * default authentication modules are provided (x/auth, x/group, x/gov) * any SDK authentication module may now be used with ICS 27 * separation of authentication from application logic in relation to ICS 27 * minimized disruption to existing development around ICS 27 controller module * underlying applications no longer have to handle capabilities * removal of the underlying application upon the creation of ADR 008 may be done in a minimally disruptive fashion * only underlying applications which registered the interchain account will perform application logic for that account (underlying applications do not need to be aware of accounts they did not register) ### Negative * the security model has been reduced to that of the SDK. SDK modules may send packets for any interchain account. * additional maintenance of the messages added and the middleware enabled flag * underlying applications which will become ADR 008 modules are not required to be aware of accounts they did not register * calling legacy API vs the new API results in different behaviour for ICS 27 application stacks which have an underlying application ### Neutral * A major release is required # ADR 010: IBC Light Clients As SDK Modules Source: https://docs.cosmos.network/ibc/latest/architecture/adr-010-light-clients-as-sdk-modules ## Changelog * 12-12-2022: initial draft ## Status Proposed ## Context ibc-go has 3 main consumers: * IBC light clients * IBC applications * relayers Relayers listen and respond to events emitted by ibc-go while IBC light clients and applications are invoked by core IBC. Currently there exists two different approaches to callbacks being invoked by core IBC. IBC light clients currently are invoked by a `ClientState` and `ConsensusState` interface as defined by [core IBC](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36). The 02-client submodule will retrieve the `ClientState` or `ConsensusState` from the IBC store in order to perform callbacks to the light client. This design requires all required information for the light client to function to be stored in the `ClientState` or `ConsensusState` or potentially under metadata keys for a specific client instance. Additional information may be provided by core IBC via the defined interface arguments if that information is generic enough to be useful to all IBC light clients. This constraint has proved problematic as pass through clients (such as wasm) cannot maintain easy access to a VM instance. In addition, without increasing the size of the defined `ClientState` interface, light clients are unable to take advantage of basic built-in SDK functionality such as genesis import/export and migrations. The other approach used to perform callback logic is via registered SDK modules. This approach is used by core IBC to interact with IBC applications. IBC applications will register their callbacks on the IBC router at compile time. When a packet comes in, core IBC will use the IBC router to lookup the registered callback functions for the provided packet. The benefit of registered callbacks opposed to interface functions is that additional information may be accessed via external keepers. Because the IBC applications are also SDK modules, they additionally get access to a host of functionality provided by the SDK. This includes: genesis import/export, migrations, query/transaction CLI commands, type registration, gRPC query registration, and message server registration. As described in [ADR 006](./adr-006-02-client-refactor.md), generalizing light client behaviour is difficult. IBC light clients will obtain greater flexibility and control via the registered SDK module approach. ## Decision Instead of using two different approaches to invoking callbacks, IBC light clients should be invoked as SDK modules. Over time and as necessary, core IBC should adjust its interactions with light clients such that they are SDK modules as opposed to interfaces. One immediate decision that has already been applied is to formalize light client type registration via the inclusion of an `AppModuleBasic` within the `ModuleManager` for a chain. The [tendermint](https://github.com/cosmos/ibc-go/pull/2825) and [solo machine](https://github.com/cosmos/ibc-go/pull/2826) clients were refactored to include this `AppModuleBasic` implementation and core IBC will no longer include either type as registered by default. Longer term solutions include using internal module communication as described in [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md) on the SDK. The following functions should become callbacks invoked via intermodule communication: * `Status` * `GetTimestampAtHeight` * `VerifyMembership` * `VerifyNonMembership` * `Initialize` * `VerifyClientMessage` * `CheckForMisbehaviour` * `UpdateStateOnMisbehaviour` * `UpdateState` * `CheckSubstituteAndUpdateState` * `VerifyUpgradeAndUpdateState` The ClientState interface should eventually be trimmed down to something along the lines of: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ClientState interface { proto.Message ClientType() string GetLatestHeight() Height Validate() error ZeroCustomFields() ClientState // ADDITION Route() string // route used for intermodule communication } ``` For the most part, any functions which require access to the client store should likely not be an interface function of the `ClientState`. `ExportMetadata` should eventually be replaced by a light client's ability to import/export it's own genesis information. ### Intermodule communication To keep the transition from interface callbacks to SDK module callbacks as simple as possible, intermodule communication (when available) should be used to route to light client modules. Without intermodule communication, a routing system would need to be developed/maintained to register callbacks. This functionality of routing to another SDK module should and will be provided by the SDK. Once it is possible to route to SDK modules, a `ClientState` type could expose the function `Route` which returns the callback route used to call the light client module. ## Consequences ### Positive * use a single approach for interacting with callbacks * greater flexibility and control for IBC light clients * does not require developing another routing system ### Negative * requires breaking changes * requires waiting for intermodule communication ### Neutral N/A # ADR 011: ICS-20 Transfer State Entry For Total Amount Of Tokens In Escrow Source: https://docs.cosmos.network/ibc/latest/architecture/adr-011-transfer-total-escrow-state-entry ## Changelog * 24-05-2023: Initial draft ## Status Accepted and applied in v7.1 of ibc-go ## Context Every ICS-20 transfer channel has its own escrow bank account. This account is used to lock tokens that are transferred out of a chain that acts as the source of the tokens (i.e. when the tokens being transferred have not returned to the originating chain). This design makes it easy to query the balance of the escrow accounts and find out the total amount of tokens in escrow in a particular channel. However, there are use cases where it would be useful to determine the total escrowed amount of a given denomination across all channels where those tokens have been transferred out. For example: assuming that there are three channels between Cosmos Hub to Osmosis and 10 ATOM have been transferred from the Cosmos Hub to Osmosis on each of those channels, then we would like to know that 30 ATOM have been transferred (i.e. are locked in the escrow accounts of each channel) without needing to iterate over each escrow account to add up the balances of each. For a sample use case where this feature would be useful, please refer to Osmosis' rate limiting use case described in [#2664](https://github.com/cosmos/ibc-go/issues/2664). ## Decision ### State entry denom -> amount The total amount of tokens in escrow (across all transfer channels) for a given denomination is stored in state in an entry keyed by the denomination: `totalEscrowForDenom/{denom}`. ### Panic if amount is negative If a negative amount is ever attempted to be stored, then the keeper function will panic: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if coin.Amount.IsNegative() { panic(fmt.Sprintf("amount cannot be negative: %s", coin.Amount)) } ``` ### Delete state entry if amount is zero When setting the amount for a particular denomination, the value might be zero if all tokens that were transferred out of the chain have been transferred back. If this happens, then the state entry for this particular denomination will be deleted, since Cosmos SDK's `x/bank` module prunes any non-zero balances: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if coin.Amount.IsZero() { store.Delete(key) // delete the key since Cosmos SDK x/bank module will prune any non-zero balances return } ``` ### Bundle escrow/unescrow with setting state entry Two new functions are implemented that bundle together the operations of escrowing/unescrowing and setting the total escrow amount in state, since these operations need to be executed together. For escrowing tokens: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // escrowToken will send the given token from the provided sender to the escrow address. It will also // update the total escrowed amount by adding the escrowed token to the current total escrow. func (k Keeper) escrowToken(ctx sdk.Context, sender, escrowAddress sdk.AccAddress, token sdk.Coin) error { if err := k.bankKeeper.SendCoins(ctx, sender, escrowAddress, sdk.NewCoins(token)); err != nil { // failure is expected for insufficient balances return err } // track the total amount in escrow keyed by denomination to allow for efficient iteration currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) newTotalEscrow := currentTotalEscrow.Add(token) k.SetTotalEscrowForDenom(ctx, newTotalEscrow) return nil } ``` For unescrowing tokens: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // unescrowToken will send the given token from the escrow address to the provided receiver. It will also // update the total escrow by deducting the unescrowed token from the current total escrow. func (k Keeper) unescrowToken(ctx sdk.Context, escrowAddress, receiver sdk.AccAddress, token sdk.Coin) error { if err := k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token)); err != nil { // NOTE: this error is only expected to occur given an unexpected bug or a malicious // counterparty module. The bug may occur in bank or any part of the code that allows // the escrow address to be drained. A malicious counterparty module could drain the // escrow address by allowing more tokens to be sent back then were escrowed. return errorsmod.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module") } // track the total amount in escrow keyed by denomination to allow for efficient iteration currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) newTotalEscrow := currentTotalEscrow.Sub(token) k.SetTotalEscrowForDenom(ctx, newTotalEscrow) return nil } ``` When tokens need to be escrowed in `sendTransfer`, then `escrowToken` is called; when tokens need to be unescrowed on execution of the `OnRecvPacket`, `OnAcknowledgementPacket` or `OnTimeoutPacket` callbacks, then `unescrowToken` is called. ### gRPC query endpoint and CLI to retrieve amount A gRPC query endpoint is added so that it is possible to retrieve the total amount for a given denomination: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // TotalEscrowForDenom returns the total amount of tokens in escrow based on the denom. rpc TotalEscrowForDenom(QueryTotalEscrowForDenomRequest) returns (QueryTotalEscrowForDenomResponse) { option (google.api.http).get = "/ibc/apps/transfer/v1/denoms/{denom=**}/total_escrow"; } // QueryTotalEscrowForDenomRequest is the request type for TotalEscrowForDenom RPC method. message QueryTotalEscrowForDenomRequest { string denom = 1; } // QueryTotalEscrowForDenomResponse is the response type for TotalEscrowForDenom RPC method. message QueryTotalEscrowForDenomResponse { cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false]; } ``` And a CLI query is also available to retrieve the total amount via the command line: ```shell theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} query ibc-transfer total-escrow [denom] ``` ## Consequences ### Positive * Possibility to retrieve the total amount of a particular denomination in escrow across all transfer channels without iteration. ### Negative No notable consequences ### Neutral * A new entry is added to state for every denomination that is transferred out of the chain. ## References Issues: * [#2664](https://github.com/cosmos/ibc-go/issues/2664) PRs: * [#3019](https://github.com/cosmos/ibc-go/pull/3019) * [#3558](https://github.com/cosmos/ibc-go/pull/3558) # ADR 015: IBC Packet Receiver Source: https://docs.cosmos.network/ibc/latest/architecture/adr-015-ibc-packet-receiver ## Changelog * 22-10-2019: Initial Draft ## Context [ICS 26 - Routing Module](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module) defines a function [`handlePacketRecv`](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module#packet-relay). In ICS 26, the routing module is defined as a layer above each application module which verifies and routes messages to the destination modules. It is possible to implement it as a separate module, however, we already have the functionality to route messages upon the destination identifiers in the baseapp. This ADR suggests to utilize existing `baseapp.router` to route packets to application modules. Generally, routing module callbacks have two separate steps in them, verification and execution. This corresponds to the `AnteHandler`-`Handler` model inside the SDK. We can do the verification inside the `AnteHandler` in order to increase developer ergonomics by reducing boilerplate verification code. For atomic multi-message transaction, we want to keep the IBC related state modification to be preserved even the application side state change reverts. One of the example might be IBC token sending message following with stake delegation which uses the tokens received by the previous packet message. If the token receiving fails for any reason, we might not want to keep executing the transaction, but we also don't want to abort the transaction or the sequence and commitment will be reverted and the channel will be stuck. This ADR suggests new `CodeType`, `CodeTxBreak`, to fix this problem. ## Decision `PortKeeper` will have the capability key that is able to access only the channels bound to the port. Entities that hold a `PortKeeper` will be able to call the methods on it which are corresponding with the methods with the same names on the `ChannelKeeper`, but only with the allowed port. `ChannelKeeper.Port(string, ChannelChecker)` will be defined to easily construct a capability-safe `PortKeeper`. This will be addressed in another ADR and we will use insecure `ChannelKeeper` for now. `baseapp.runMsgs` will break the loop over the messages if one of the handlers returns `!Result.IsOK()`. However, the outer logic will write the cached store if `Result.IsOK() || Result.Code.IsBreak()`. `Result.Code.IsBreak()` if `Result.Code == CodeTxBreak`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (app *BaseApp) runTx(tx Tx) (result Result) { msgs := tx.GetMsgs() // AnteHandler if app.anteHandler != nil { anteCtx, msCache := app.cacheTxContext(ctx) newCtx, err := app.anteHandler(anteCtx, tx) if !newCtx.IsZero() { ctx = newCtx.WithMultiStore(ms) } if err != nil { // error handling logic return res } msCache.Write() } // Main Handler runMsgCtx, msCache := app.cacheTxContext(ctx) result = app.runMsgs(runMsgCtx, msgs) // BEGIN modification made in this ADR if result.IsOK() || result.IsBreak() { // END msCache.Write() } return result } ``` The Cosmos SDK will define an `AnteDecorator` for IBC packet receiving. The `AnteDecorator` will iterate over the messages included in the transaction, type `switch` to check whether the message contains an incoming IBC packet, and if so verify the Merkle proof. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ProofVerificationDecorator struct { clientKeeper ClientKeeper channelKeeper ChannelKeeper } func (pvr ProofVerificationDecorator) AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (Context, error) { for _, msg := range tx.GetMsgs() { var err error switch msg := msg.(type) { case client.MsgUpdateClient: err = pvr.clientKeeper.UpdateClient(msg.ClientID, msg.Header) case channel.MsgPacket: err = pvr.channelKeeper.RecvPacket(msg.Packet, msg.Proofs, msg.ProofHeight) case channel.MsgAcknowledgement: err = pvr.channelKeeper.AcknowledgementPacket(msg.Acknowledgement, msg.Proof, msg.ProofHeight) case channel.MsgTimeoutPacket: err = pvr.channelKeeper.TimeoutPacket(msg.Packet, msg.Proof, msg.ProofHeight, msg.NextSequenceRecv) case channel.MsgChannelOpenInit; err = pvr.channelKeeper.CheckOpen(msg.PortID, msg.ChannelID, msg.Channel) default: continue } if err != nil { return ctx, err } } return next(ctx, tx, simulate) } ``` Where `MsgUpdateClient`, `MsgPacket`, `MsgAcknowledgement`, `MsgTimeoutPacket` are `sdk.Msg` types correspond to `handleUpdateClient`, `handleRecvPacket`, `handleAcknowledgementPacket`, `handleTimeoutPacket` of the routing module, respectively. The side effects of `RecvPacket`, `VerifyAcknowledgement`, `VerifyTimeout` will be extracted out into separated functions, `WriteAcknowledgement`, `DeleteCommitment`, `DeleteCommitmentTimeout`, respectively, which will be called by the application handlers after the execution. `WriteAcknowledgement` writes the acknowledgement to the state that can be verified by the counter-party chain and increments the sequence to prevent double execution. `DeleteCommitment` will delete the commitment stored, `DeleteCommitmentTimeout` will delete the commitment and close channel in case of ordered channel. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (keeper ChannelKeeper) WriteAcknowledgement(ctx Context, packet Packet, ack []byte) { keeper.SetPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack) keeper.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) } func (keeper ChannelKeeper) DeleteCommitment(ctx Context, packet Packet) { keeper.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) } func (keeper ChannelKeeper) DeleteCommitmentTimeout(ctx Context, packet Packet) { k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) if channel.Ordering == types.ORDERED [ channel.State = types.CLOSED k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel) } } ``` Each application handler should call respective finalization methods on the `PortKeeper` in order to increase sequence (in case of packet) or remove the commitment (in case of acknowledgement and timeout). Calling those functions implies that the application logic has successfully executed. However, the handlers can return `Result` with `CodeTxBreak` after calling those methods which will persist the state changes that has been already done but prevent any further messages to be executed in case of semantically invalid packet. This will keep the sequence increased in the previous IBC packets(thus preventing double execution) without proceeding to the following messages. In any case the application modules should never return state reverting result, which will make the channel unable to proceed. `ChannelKeeper.CheckOpen` method will be introduced. This will replace `onChanOpen*` defined under the routing module specification. Instead of define each channel handshake callback functions, application modules can provide `ChannelChecker` function with the `AppModule` which will be injected to `ChannelKeeper.Port()` at the top level application. `CheckOpen` will find the correct `ChannelChecker` using the `PortID` and call it, which will return an error if it is unacceptable by the application. The `ProofVerificationDecorator` will be inserted to the top level application. It is not safe to make each module responsible to call proof verification logic, whereas application can misbehave(in terms of IBC protocol) by mistake. The `ProofVerificationDecorator` should come right after the default sybil attack resistant layer from the current `auth.NewAnteHandler`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // add IBC ProofVerificationDecorator to the Chain of func NewAnteHandler( ak keeper.AccountKeeper, supplyKeeper types.SupplyKeeper, ibcKeeper ibc.Keeper, sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler { return sdk.ChainAnteDecorators( NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first ... NewIncrementSequenceDecorator(ak), ibcante.ProofVerificationDecorator(ibcKeeper.ClientKeeper, ibcKeeper.ChannelKeeper), // innermost AnteDecorator ) } ``` The implementation of this ADR will also create a `Data` field of the `Packet` of type `[]byte`, which can be deserialised by the receiving module into its own private type. It is up to the application modules to do this according to their own interpretation, not by the IBC keeper. This is crucial for dynamic IBC. Example application-side usage: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type AppModule struct {} // CheckChannel will be provided to the ChannelKeeper as ChannelKeeper.Port(module.CheckChannel) func (module AppModule) CheckChannel(portID, channelID string, channel Channel) error { if channel.Ordering != UNORDERED { return ErrUncompatibleOrdering() } if channel.CounterpartyPort != "bank" { return ErrUncompatiblePort() } if channel.Version != "" { return ErrUncompatibleVersion() } return nil } func NewHandler(k Keeper) Handler { return func(ctx Context, msg Msg) Result { switch msg := msg.(type) { case MsgTransfer: return handleMsgTransfer(ctx, k, msg) case ibc.MsgPacket: var data PacketDataTransfer if err := types.ModuleCodec.UnmarshalBinaryBare(msg.GetData(), &data); err != nil { return err } return handlePacketDataTransfer(ctx, k, msg, data) case ibc.MsgTimeoutPacket: var data PacketDataTransfer if err := types.ModuleCodec.UnmarshalBinaryBare(msg.GetData(), &data); err != nil { return err } return handleTimeoutPacketDataTransfer(ctx, k, packet) // interface { PortID() string; ChannelID() string; Channel() ibc.Channel } // MsgChanInit, MsgChanTry implements ibc.MsgChannelOpen case ibc.MsgChannelOpen: return handleMsgChannelOpen(ctx, k, msg) } } } func handleMsgTransfer(ctx Context, k Keeper, msg MsgTransfer) Result { err := k.SendTransfer(ctx,msg.PortID, msg.ChannelID, msg.Amount, msg.Sender, msg.Receiver) if err != nil { return sdk.ResultFromError(err) } return sdk.Result{} } func handlePacketDataTransfer(ctx Context, k Keeper, packet Packet, data PacketDataTransfer) Result { err := k.ReceiveTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data) if err != nil { // TODO: Source chain sent invalid packet, shutdown channel } k.ChannelKeeper.WriteAcknowledgement([]byte{0x00}) // WriteAcknowledgement increases the sequence, preventing double spending return sdk.Result{} } func handleCustomTimeoutPacket(ctx Context, k Keeper, packet CustomPacket) Result { err := k.RecoverTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data) if err != nil { // This chain sent invalid packet or cannot recover the funds panic(err) } k.ChannelKeeper.DeleteCommitmentTimeout(ctx, packet) // packet timeout should not fail return sdk.Result{} } func handleMsgChannelOpen(sdk.Context, k Keeper, msg MsgOpenChannel) Result { k.AllocateEscrowAddress(ctx, msg.ChannelID()) return sdk.Result{} } ``` ## Status Proposed ## Consequences ### Positive * Intuitive interface for developers - IBC handlers do not need to care about IBC authentication * State change commitment logic is embedded into `baseapp.runTx` logic ### Negative * Cannot support dynamic ports, routing is tied to the baseapp router ### Neutral * Introduces new `AnteHandler` decorator. * Dynamic ports can be supported using hierarchical port identifier, see #5290 for detail ## References * Relevant comment: [cosmos/ics#289](https://github.com/cosmos/ibc/issues/289#issuecomment-544533583) * [ICS26 - Routing Module](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module) # ADR 025: IBC Passive Channels Source: https://docs.cosmos.network/ibc/latest/architecture/adr-025-ibc-passive-channels ## Changelog * 23-04-2021: Change status to "deprecated" * 23-05-2020: Provide sample Go code and more details * 18-05-2020: Initial Draft ## Status *deprecated* ## Context The current "naive" IBC Relayer strategy currently establishes a single predetermined IBC channel atop a single connection between two clients (each potentially of a different chain). This strategy then detects packets to be relayed by watching for `send_packet` and `recv_packet` events matching that channel, and sends the necessary transactions to relay those packets. We wish to expand this "naive" strategy to a "passive" one which detects and relays both channel handshake messages and packets on a given connection, without the need to know each channel in advance of relaying it. In order to accomplish this, we propose adding more comprehensive events to expose channel metadata for each transaction sent from the `x/ibc/core/04-channel/keeper/handshake.go` and `x/ibc/core/04-channel/keeper/packet.go` modules. Here is an example of what would be in `ChanOpenInit`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const ( EventTypeChannelMeta = "channel_meta" AttributeKeyAction = "action" AttributeKeyHops = "hops" AttributeKeyOrder = "order" AttributeKeySrcPort = "src_port" AttributeKeySrcChannel = "src_channel" AttributeKeySrcVersion = "src_version" AttributeKeyDstPort = "dst_port" AttributeKeyDstChannel = "dst_channel" AttributeKeyDstVersion = "dst_version" ) // ... // Emit Event with Channel metadata for the relayer to pick up and // relay to the other chain // This appears immediately before the successful return statement. ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeChannelMeta, sdk.NewAttribute(types.AttributeKeyAction, "open_init"), sdk.NewAttribute(types.AttributeKeySrcConnection, connectionHops[0]), sdk.NewAttribute(types.AttributeKeyHops, strings.Join(connectionHops, ",")), sdk.NewAttribute(types.AttributeKeyOrder, order.String()), sdk.NewAttribute(types.AttributeKeySrcPort, portID), sdk.NewAttribute(types.AttributeKeySrcChannel, channelID), sdk.NewAttribute(types.AttributeKeySrcVersion, version), sdk.NewAttribute(types.AttributeKeyDstPort, counterparty.GetPortID()), sdk.NewAttribute(types.AttributeKeyDstChannel, counterparty.GetChannelID()), // The destination version is not yet known, but a value is necessary to pad // the event attribute offsets sdk.NewAttribute(types.AttributeKeyDstVersion, ""), ), }) ``` These metadata events capture all the "header" information needed to route IBC channel handshake transactions without requiring the client to query any data except that of the connection ID that it is willing to relay. It is intended that `channel_meta.src_connection` is the only event key that needs to be indexed for a passive relayer to function. ### Handling Channel Open Attempts In the case of the passive relayer, when one chain sends a `ChanOpenInit`, the relayer should inform the other chain of this open attempt and allow that chain to decide how (and if) it continues the handshake. Once both chains have actively approved the channel opening, then the rest of the handshake can happen as it does with the current "naive" relayer. To implement this behavior, we propose replacing the `cbs.OnChanOpenTry` callback with a new `cbs.OnAttemptChanOpenTry` callback which explicitly handles the `MsgChannelOpenTry`, usually by resulting in a call to `keeper.ChanOpenTry`. The typical implementation, in `x/ibc-transfer/module.go` would be compatible with the current "naive" relayer, as follows: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func (am AppModule) OnAttemptChanOpenTry( ctx sdk.Context, chanKeeper channel.Keeper, portCap *capability.Capability, msg channel.MsgChannelOpenTry, ) (*sdk.Result, error) { // Require portID is the portID transfer module is bound to boundPort := am.keeper.GetPort(ctx) if boundPort != msg.PortID { return nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", msg.PortID, boundPort) } // BEGIN NEW CODE // Assert our protocol version, overriding the relayer's suggestion. msg.Version = types.Version // Continue the ChanOpenTry. res, chanCap, err := channel.HandleMsgChannelOpenTry(ctx, chanKeeper, portCap, msg) if err != nil { return nil, err } // END OF NEW CODE // ... the rest of the callback is similar to the existing OnChanOpenTry // but uses msg.* directly. ``` Here is how this callback would be used, in the implementation of `x/ibc/handler.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // ... case channel.MsgChannelOpenTry: // Lookup module by port capability module, portCap, err := k.PortKeeper.LookupModuleByPort(ctx, msg.PortID) if err != nil { return nil, sdkerrors.Wrap(err, "could not retrieve module from port-id") } // Retrieve callbacks from router cbs, ok := k.Router.GetRoute(module) if !ok { return nil, sdkerrors.Wrapf(port.ErrInvalidRoute, "route not found to module: %s", module) } // Delegate to the module's OnAttemptChanOpenTry. return cbs.OnAttemptChanOpenTry(ctx, k.ChannelKeeper, portCap, msg) ``` The reason we do not have a more structured interaction between `x/ibc/handler.go` and the port's module (to explicitly negotiate versions, etc) is that we do not wish to constrain the app module to have to finish handling the `MsgChannelOpenTry` during this transaction or even this block. ## Decision * Expose events to allow "passive" connection relayers. * Enable application-initiated channels via such passive relayers. * Allow port modules to control how to handle open-try messages. ## Consequences ### Positive Makes channels into a complete application-level abstraction. Applications have full control over initiating and accepting channels, rather than expecting a relayer to tell them when to do so. A passive relayer does not have to know what kind of channel (version string, ordering constraints, firewalling logic) the application supports. These are negotiated directly between applications. ### Negative Increased event size for IBC messages. ### Neutral More IBC events are exposed. ## References * The Agoric VM's IBC handler currently [accommodates `attemptChanOpenTry`](https://github.com/Agoric/agoric-sdk/blob/904b3a0423222a1b32893453e44bbde598473960/packages/cosmic-swingset/lib/ag-solo/vats/ibc.js#L546) # ADR 026: IBC Client Recovery Mechanisms Source: https://docs.cosmos.network/ibc/latest/architecture/adr-026-ibc-client-recovery-mechanisms ## Changelog * 23-06-2020: Initial version * 06-08-2020: Revisions per review & to reference version * 15-01-2021: Revision to support substitute clients for unfreezing * 20-05-2021: Revision to simplify consensus state copying, remove initial height * 08-04-2022: Revision to deprecate AllowUpdateAfterExpiry and AllowUpdateAfterMisbehaviour * 15-07-2022: Revision to allow updating of TrustingPeriod * 05-09-2023: Revision to migrate from gov v1beta1 to gov v1 ## Status *Accepted* ## Context ### Summary At launch, IBC will be a novel protocol, without an experienced user-base. At the protocol layer, it is not possible to distinguish between client expiry or misbehaviour due to genuine faults (Byzantine behaviour) and client expiry or misbehaviour due to user mistakes (failing to update a client, or accidentally double-signing). In the base IBC protocol and ICS 20 fungible token transfer implementation, if a client can no longer be updated, funds in that channel will be permanently locked and can no longer be transferred. To the degree that it is safe to do so, it would be preferable to provide users with a recovery mechanism which can be utilised in these exceptional cases. ### Exceptional cases The state of concern is where a client associated with connection(s) and channel(s) can no longer be updated. This can happen for several reasons: 1. The chain which the client is following has halted and is no longer producing blocks/headers, so no updates can be made to the client 2. The chain which the client is following has continued to operate, but no relayer has submitted a new header within the unbonding period, and the client has expired 1. This could be due to real misbehaviour (intentional Byzantine behaviour) or merely a mistake by validators, but the client cannot distinguish these two cases 3. The chain which the client is following has experienced a misbehaviour event, and the client has been frozen & thus can no longer be updated ### Security model Two-thirds of the validator set (the quorum for governance, module participation) can already sign arbitrary data, so allowing governance to manually force-update a client with a new header after a delay period does not substantially alter the security model. ## Decision We elect not to deal with chains which have actually halted, which is necessarily Byzantine behaviour and in which case token recovery is not likely possible anyways (in-flight packets cannot be timed-out, but the relative impact of that is minor). 1. Require Tendermint light clients (ICS 07) to be created with the following additional flags 1. `allow_update_after_expiry` (boolean, default true). Note that this flag has been deprecated, it remains to signal intent but checks against this value will not be enforced. 2. Require Tendermint light clients (ICS 07) to expose the following additional internal query functions 1. `Expired() boolean`, which returns whether or not the client has passed the trusting period since the last update (in which case no headers can be validated) 3. Require Tendermint light clients (ICS 07) & solo machine clients (ICS 06) to be created with the following additional flags 1. `allow_update_after_misbehaviour` (boolean, default true). Note that this flag has been deprecated, it remains to signal intent but checks against this value will not be enforced. 4. Require Tendermint light clients (ICS 07) to expose the following additional state mutation functions 1. `Unfreeze()`, which unfreezes a light client after misbehaviour and clears any frozen height previously set 5. Add a new governance proposal with `MsgRecoverClient`. 1. Create a new Msg with two client identifiers (`string`) and a signer. 2. The first client identifier is the proposed client to be updated. This client must be either frozen or expired. 3. The second client is a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain-id). It should be continually updated during the voting period. 4. If this governance proposal passes, the client on trial will be updated to the latest state of the substitute. 5. The signer must be the authority set for the ibc module. Previously, `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehaviour` were used to signal the recovery options for an expired or frozen client, and governance proposals were not allowed to overwrite the client if these parameters were set to false. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of these parameters. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same. In addition, `TrustingPeriod` was initially not allowed to be updated by a client upgrade proposal. However, due to the number of situations experienced in production where the `TrustingPeriod` of a client should be allowed to be updated because of ie: initial misconfiguration for a canonical channel, governance should be allowed to update this client parameter. In versions older than ibc-go v8, `MsgRecoverClient` was a governance proposal type `ClientUpdateProposal`. It has been removed and replaced by `MsgRecoverClient` in the migration from governance v1beta1 to governance v1. Note that this should NOT be lightly updated, as there may be a gap in time between when misbehaviour has occurred and when the evidence of misbehaviour is submitted. For example, if the `UnbondingPeriod` is 2 weeks and the `TrustingPeriod` has also been set to two weeks, a validator could wait until right before `UnbondingPeriod` finishes, submit false information, then unbond and exit without being slashed for misbehaviour. Therefore, we recommend that the trusting period for the 07-tendermint client be set to 2/3 of the `UnbondingPeriod`. Note that clients frozen due to misbehaviour must wait for the evidence to expire to avoid becoming refrozen. This ADR does not address planned upgrades, which are handled separately as per the [specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-007-tendermint-client#upgrades). ## Consequences ### Positive * Establishes a mechanism for client recovery in the case of expiry * Establishes a mechanism for client recovery in the case of misbehaviour * Constructing an ClientUpdate Proposal is as difficult as creating a new client ### Negative * Additional complexity in client creation which must be understood by the user * Coping state of the substitute adds complexity * Governance participants must vote on a substitute client ### Neutral No neutral consequences. ## References * [Prior discussion](https://github.com/cosmos/ibc/issues/421) * [Epoch number discussion](https://github.com/cosmos/ibc/issues/439) * [Upgrade plan discussion](https://github.com/cosmos/ibc/issues/445) * [Migration from gov v1beta1 to gov v1](https://github.com/cosmos/ibc-go/issues/3672) # ADR 27: Add Support For Wasm Based Light Client Source: https://docs.cosmos.network/ibc/latest/architecture/adr-027-ibc-wasm ## Changelog * 26-11-2020: Initial Draft * 26-05-2023: Update after 02-client refactor and re-implementation by Strangelove * 13-12-2023: Update after upstreaming of module to ibc-go ## Status *Accepted and applied in v0.1.0 of 08-wasm* ## Abstract In the Cosmos SDK light clients are currently hardcoded in Go. This makes upgrading existing IBC light clients or adding support for new light client a multi step process involving on-chain governance which is time-consuming. To remedy this, we are proposing a Wasm VM to host light client bytecode, which allows easier upgrading of existing IBC light clients as well as adding support for new IBC light clients without requiring a code release and corresponding hard-fork event. ## Context Currently in ibc-go light clients are defined as part of the codebase and are implemented as modules under `modules/light-clients`. Adding support for new light clients or updating an existing light client in the event of a security issue or consensus update is a multi-step process which is both time-consuming and error-prone. In order to enable new IBC light client implementations it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and validators upgrade their nodes. Another problem stemming from the above process is that if a chain wants to upgrade its own consensus, it will need to convince every chain or hub connected to it to upgrade its light client in order to stay connected. Due to the time-consuming process required to upgrade a light client, a chain with lots of connections needs to be disconnected for quite some time after upgrading its consensus, which can be very expensive in terms of time and effort. We are proposing simplifying this workflow by integrating a Wasm light client module that makes adding support for new light clients a simple governance-gated transaction. The light client bytecode, written in Wasm-compilable Rust, runs inside a Wasm VM. The Wasm light client submodule exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM for execution. With the Wasm light client module, anybody can add new IBC light client in the form of Wasm bytecode (provided they are able to submit the governance proposal transaction and that it passes) as well as instantiate clients using any created client type. This allows any chain to update its own light client in other chains without going through the steps outlined above. ## Decision We decided to implement the Wasm light client module as a light client proxy that will interface with the actual light client uploaded as Wasm bytecode. To enable usage of the Wasm light client module, users need to add it to the list of allowed clients by updating the `AllowedClients` parameter in the 02-client submodule of core IBC. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} params := clientKeeper.GetParams(ctx) params.AllowedClients = append(params.AllowedClients, exported.Wasm) clientKeeper.SetParams(ctx, params) ``` Adding a new light client contract is governance-gated. To upload a new light client users need to submit a [governance v1 proposal](/sdk/latest/modules/gov/README#proposals) that contains the `sdk.Msg` for storing the Wasm contract's bytecode. The required message is `MsgStoreCode` and the bytecode is provided in the field `wasm_byte_code`: ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // MsgStoreCode defines the request type for the StoreCode rpc. message MsgStoreCode { // signer address string signer = 1; // wasm byte code of light client contract. It can be raw or gzip compressed bytes wasm_byte_code = 2; } ``` The RPC handler processing `MsgStoreCode` will make sure that the signer of the message matches the address of authority allowed to submit this message (which is normally the address of the governance module). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // StoreCode defines a rpc handler method for MsgStoreCode func (k Keeper) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) { if k.GetAuthority() != msg.Signer { return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) } ctx := sdk.UnwrapSDKContext(goCtx) checksum, err := k.storeWasmCode(ctx, msg.WasmByteCode, ibcwasm.GetVM().StoreCode) if err != nil { return nil, errorsmod.Wrap(err, "failed to store wasm bytecode") } emitStoreWasmCodeEvent(ctx, checksum) return &types.MsgStoreCodeResponse{ Checksum: checksum, }, nil } ``` The contract's bytecode is not stored in state (it is actually unnecessary and wasteful to store it, since the Wasm VM already stores it and can be queried back, if needed). The checksum is simply the hash of the bytecode of the contract and it is stored in state in an entry with key `checksums` that contains the checksums for the bytecodes that have been stored. ### How light client proxy works? The light client proxy behind the scenes will call a CosmWasm smart contract instance with incoming arguments serialized in JSON format with appropriate environment information. Data returned by the smart contract is deserialized and returned to the caller. Consider the example of the `VerifyClientMessage` function of `ClientState` interface. Incoming arguments are packaged inside a payload object that is then JSON serialized and passed to `queryContract`, which executes `WasmVm.Query` and returns the slice of bytes returned by the smart contract. This data is deserialized and passed as return argument. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type QueryMsg struct { Status *StatusMsg `json:"status,omitempty"` ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"` TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` } type verifyClientMessageMsg struct { ClientMessage *ClientMessage `json:"client_message"` } // VerifyClientMessage must verify a ClientMessage. // A ClientMessage could be a Header, Misbehaviour, or batch update. // It must handle each type of ClientMessage appropriately. // Calls to CheckForMisbehaviour, UpdateStaåte, and UpdateStateOnMisbehaviour // will assume that the content of the ClientMessage has been verified // and can be trusted. An error should be returned // if the ClientMessage fails to verify. func (cs ClientState) VerifyClientMessage( ctx sdk.Context, _ codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage ) error { clientMessage, ok := clientMsg.(*ClientMessage) if !ok { return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected type: %T, got: %T", &ClientMessage{}, clientMsg) } payload := QueryMsg{ VerifyClientMessage: &VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, } _, err := wasmQuery[EmptyResult](ctx, clientStore, &cs, payload) return err } ``` ### Global Wasm VM variable The 08-wasm keeper structure keeps a reference to the Wasm VM instantiated in the keeper constructor function. The keeper uses the Wasm VM to store the bytecode of light client contracts. However, the Wasm VM is also needed in the 08-wasm implementations of some of the `ClientState` interface functions to initialise a contract, execute calls on the contract and query the contract. Since the `ClientState` functions do not have access to the 08-wasm keeper, then it has been decided to keep a global pointer variable that points to the same instance as the one in the 08-wasm keeper. This global pointer variable is then used in the implementations of the `ClientState` functions. ## Consequences ### Positive * Adding support for new light client or upgrading existing light client is way easier than before and only requires single transaction instead of a hard-fork. * Improves maintainability of ibc-go, since no change in codebase is required to support new client or upgrade it. * The existence of support for Rust dependencies in light clients which may not exist in Go. ### Negative * Light clients written in Rust need to be written in a subset of Rust which could compile in Wasm. * Introspecting light client code is difficult as only compiled bytecode exists in the blockchain. # Changelog Source: https://docs.cosmos.network/ibc/latest/changelog/release-notes Release history and changelog for Cosmos IBC This page tracks releases and changes for v11.0.0. For the full release history, see the [CHANGELOG](https://github.com/cosmos/ibc-go/blob/main/CHANGELOG.md) on GitHub. ## Features * [#8573](https://github.com/cosmos/ibc-go/pull/8573) Support custom address codecs in transfer, PFM, and rate limiting. * [#8285](https://github.com/cosmos/ibc-go/pull/8285) Packet forward middleware. * [#8545](https://github.com/cosmos/ibc-go/pull/8545) Support sending multiple payloads in the same packet for atomic payload execution. * [#8473](https://github.com/cosmos/ibc-go/pull/8473) Support sending v2 packets on v1 channel identifiers using aliasing. ## Improvements * [#8734](https://github.com/cosmos/ibc-go/pull/8734) Add extra validation for ProtoJSON unmarshalling in ICS-27 ICA. * [#8774](https://github.com/cosmos/ibc-go/pull/8774) Add length validation to `MsgCreateClient` and `CounterpartyMerklePrefix`. ## Dependencies * [#8451](https://github.com/cosmos/ibc-go/pull/8451) Bump **go** to **1.24** * (light-clients/08-wasm)[#8500](https://github.com/cosmos/ibc-go/pull/8500) Bump **github.com/prysmaticlabs/prysm/v5** to **github.com/OffchainLabs/prysm/v6\@v6.0.4** ## API Breaking * [#8820](https://github.com/cosmos/ibc-go/pull/8820) Bump ibc-go to v11 and use `sdk.ValidateAuthority` in msg\_server handlers. * (apps) [#8476](https://github.com/cosmos/ibc-go/pull/8476) Remove `ParamSubspace` from all `Keeper` constructors * (light-clients/08-wasm) [#8511](https://github.com/cosmos/ibc-go/pull/8511) Remove deprecated `Checksums` type * (core/02-client) [#8516](https://github.com/cosmos/ibc-go/pull/8516) Remove deprecated `SubmitMisbehaviour` message handler ## Testing API * [#8366](https://github.com/cosmos/ibc-go/pull/8366) - Replaced the deprecated `codec.ProtoMarshaler` interface with `proto.Message`. # Migrations Source: https://docs.cosmos.network/ibc/latest/light-clients/wasm/migrations This guide provides instructions for migrating 08-wasm versions. This guide provides instructions for migrating 08-wasm versions. Please note that the following releases are retracted. Please refer to the appropriate migrations section for upgrading. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} v0.3.1-0.20240717085919-bb71eef0f3bf => v0.3.0+ibc-go-v8.3-wasmvm-v2.0 v0.2.1-0.20240717085554-570d057959e3 => v0.2.0+ibc-go-v7.6-wasmvm-v1.5 v0.2.1-0.20240523101951-4b45d1822fb6 => v0.2.0+ibc-go-v8.3-wasmvm-v2.0 v0.1.2-0.20240412103620-7ee2a2452b79 => v0.1.1+ibc-go-v7.3-wasmvm-v1.5 v0.1.1-0.20231213092650-57fcdb9a9a9d => v0.1.0+ibc-go-v8.0-wasmvm-v1.5 v0.1.1-0.20231213092633-b306e7a706e1 => v0.1.0+ibc-go-v7.3-wasmvm-v1.5 ``` ## From ibc-go v8.4.x to ibc-go v9.0.x ### Chains * The `Initialize`, `Status`, `GetTimestampAtHeight`, `GetLatestHeight`, `VerifyMembership`, `VerifyNonMembership`, `VerifyClientMessage`, `UpdateState` and `UpdateStateOnMisbehaviour` functions in `ClientState` have been removed and all their logic has been moved to functions of the `LightClientModule`. * The `MigrateContract` function has been removed from `ClientState`. * The `VerifyMembershipMsg` and `VerifyNonMembershipMsg` payloads for `SudoMsg` have been modified. The `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath`. The new `v2.MerklePath` field contains a `KeyPath` of `[][]byte` as opposed to `[]string`. This supports proving values stored under keys which contain non-utf8 encoded symbols. As a result, the JSON field `path` containing `key_path` of both messages will marshal elements as a base64 encoded bytestrings. This is a breaking change for 08-wasm client contracts and they should be migrated to correctly support deserialisation of the `v2.MerklePath` field. * The `ExportMetadataMsg` struct has been removed and is no longer required for contracts to implement. Core IBC will handle exporting all key/value's written to the store by a light client contract. * The `ZeroCustomFields` interface function has been removed from the `ClientState` interface. Core IBC only used this function to set tendermint client states when scheduling an IBC software upgrade. The interface function has been replaced by a type assertion. * The `MaxWasmByteSize` function has been removed in favor of the `MaxWasmSize` constant. * The `HasChecksum`, `GetAllChecksums` and `Logger` functions have been moved from the `types` package to a method on the `Keeper` type in the `keeper` package. * The `InitializePinnedCodes` function has been moved to a method on the `Keeper` type in the `keeper` package. * The `CustomQuerier`, `StargateQuerier` and `QueryPlugins` types have been moved from the `types` package to the `keeper` package. * The `NewDefaultQueryPlugins`, `AcceptListStargateQuerier` and `RejectCustomQuerier` functions has been moved from the `types` package to the `keeper` package. * The `NewDefaultQueryPlugins` function signature has changed to take an argument: `queryRouter ibcwasm.QueryRouter`. * The `AcceptListStargateQuerier` function signature has changed to take an additional argument: `queryRouter ibcwasm.QueryRouter`. * The `WithQueryPlugins` function signature has changed to take in the `QueryPlugins` type from the `keeper` package (previously from the `types` package). * The `VMGasRegister` variable has been moved from the `types` package to the `keeper` package. ## From v0.3.0+ibc-go-v8.3-wasmvm-v2.0 to v0.4.1-ibc-go-v8.4-wasmvm-v2.0 ### Contract developers Contract developers are required to update their JSON API message structure for the `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg`. The `path` field on both JSON API messages has been renamed to `merkle_path`. A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of these fields. ## From v0.2.0+ibc-go-v7.3-wasmvm-v1.5 to v0.3.1-ibc-go-v7.4-wasmvm-v1.5 ### Contract developers Contract developers are required to update their JSON API message structure for the `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg`. The `path` field on both JSON API messages has been renamed to `merkle_path`. A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of these fields. ## From v0.2.0+ibc-go-v8.3-wasmvm-v2.0 to v0.3.0-ibc-go-v8.3-wasmvm-v2.0 ### Contract developers The `v0.3.0` release of 08-wasm for ibc-go `v8.3.x` and above introduces a breaking change for client contract developers. The contract API `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg` have been modified. The encoding of the `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath` to support proving values stored under keys which contain non-utf8 encoded symbols. As a result, the `Path` field now contains a `MerklePath` composed of `key_path` of `[][]byte` as opposed to `[]string`. The JSON field `path` containing `key_path` of both `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs will now marshal elements as base64 encoded bytestrings. See below for example JSON diff. ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "verify_membership": { "height": { "revision_height": 1 }, "delay_time_period": 0, "delay_block_period": 0, "proof":"dmFsaWQgcHJvb2Y=", "path": { + "key_path":["L2liYw==","L2tleS9wYXRo"] - "key_path":["/ibc","/key/path"] }, "value":"dmFsdWU=" } } ``` A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of `key_path` from `[]string` to `[][]byte`. Contract developers should familiarise themselves with the migration path offered by 08-wasm [here](/ibc/latest/light-clients/wasm/governance#migrating-an-existing-wasm-light-client-contract). An example of the required changes in a client contract may look like: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} #[cw_serde] pub struct MerklePath { + pub key_path: Vec, - pub key_path: Vec, } ``` Please refer to the [`cosmwasm_std`](https://docs.rs/cosmwasm-std/2.0.4/cosmwasm_std/struct.Binary.html) documentation for more information. ## From v0.1.1+ibc-go-v7.3-wasmvm-v1.5 to v0.2.0-ibc-go-v7.3-wasmvm-v1.5 ### Contract developers The `v0.2.0` release of 08-wasm for ibc-go `v7.6.x` and above introduces a breaking change for client contract developers. The contract API `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg` have been modified. The encoding of the `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath` to support proving values stored under keys which contain non-utf8 encoded symbols. As a result, the `Path` field now contains a `MerklePath` composed of `key_path` of `[][]byte` as opposed to `[]string`. The JSON field `path` containing `key_path` of both `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs will now marshal elements as base64 encoded bytestrings. See below for example JSON diff. ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "verify_membership": { "height": { "revision_height": 1 }, "delay_time_period": 0, "delay_block_period": 0, "proof":"dmFsaWQgcHJvb2Y=", "path": { + "key_path":["L2liYw==","L2tleS9wYXRo"] - "key_path":["/ibc","/key/path"] }, "value":"dmFsdWU=" } } ``` A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of `key_path` from `[]string` to `[][]byte`. Contract developers should familiarise themselves with the migration path offered by 08-wasm [here](/ibc/latest/light-clients/wasm/governance#migrating-an-existing-wasm-light-client-contract). An example of the required changes in a client contract may look like: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} #[cw_serde] pub struct MerklePath { + pub key_path: Vec, - pub key_path: Vec, } ``` Please refer to the [`cosmwasm_std`](https://docs.rs/cosmwasm-std/2.0.4/cosmwasm_std/struct.Binary.html) documentation for more information. ## From ibc-go v7.3.x to ibc-go v8.0.x ### Chains In the 08-wasm versions compatible with ibc-go v7.3.x and above from the v7 release line, the checksums of the uploaded Wasm bytecodes are all stored under a single key. From ibc-go v8.0.x the checksums are stored using [`collections.KeySet`](/sdk/latest/guides/state/collections#keyset), whose full functionality became available in Cosmos SDK v0.50. There is therefore an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/module.go#L115-L118) configured in the 08-wasm module to migrate the stored checksums to `collections.KeySet`. ## From v0.1.0+ibc-go-v8.0-wasmvm-v1.5 to v0.2.0-ibc-go-v8.3-wasmvm-v2.0 The `WasmEngine` interface has been updated to reflect changes in the function signatures of Wasm VM: ```diff expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type WasmEngine interface { - StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) + StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) Instantiate( checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) (*wasmvmtypes.Response, uint64, error) + ) (*wasmvmtypes.ContractResult, uint64, error) Query( checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) ([]byte, uint64, error) + ) (*wasmvmtypes.QueryResult, uint64, error) Migrate( checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) (*wasmvmtypes.Response, uint64, error) + ) (*wasmvmtypes.ContractResult, uint64, error) Sudo( checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction, - ) (*wasmvmtypes.Response, uint64, error) + ) (*wasmvmtypes.ContractResult, uint64, error) GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) Pin(checksum wasmvm.Checksum) error Unpin(checksum wasmvm.Checksum) error } ``` Similar changes were required in the functions of `MockWasmEngine` interface. ### Chains The `SupportedCapabilities` field of `WasmConfig` is now of type `[]string`: ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type WasmConfig struct { DataDir string - SupportedCapabilities string + SupportedCapabilities []string ContractDebugMode bool } ``` # Callbacks with IBC v2 Source: https://docs.cosmos.network/ibc/latest/middleware/callbacks/callbacks-IBCv2 This page highlights some of the differences between IBC v2 and IBC classic relevant for the callbacks middleware and how to use the module with IBC v2. More details on middleware for IBC v2 can be found in the middleware section. This page highlights some of the differences between IBC v2 and IBC classic relevant for the callbacks middleware and how to use the module with IBC v2. More details on middleware for IBC v2 can be found in the [middleware section](/ibc/latest/ibc/middleware/developIBCv2). ## Interfaces Some of the interface differences are: * The callbacks middleware for IBC v2 requires the [`Underlying Application`](/ibc/latest/middleware/callbacks/overview) to implement the new [`CallbacksCompatibleModuleV2`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/types/callbacks.go#L53-L58) interface. * `channeltypesv2.Payload` is now used instead of `channeltypes.Packet` * With IBC classic, the `OnRecvPacket` callback returns the `ack`, whereas v2 returns the `recvResult` which is the [status of the packet](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.pb.go#L26-L38): unspecified, success, failue or asynchronous * `api.WriteAcknowledgementWrapper` is used instead of `ICS4Wrapper.WriteAcknowledgement`. It is only needed if the lower level application is going to write an asynchronous acknowledgement. ## Contract Developers The wasmd contract keeper enables cosmwasm developers to use the callbacks middleware. The [cosmwasm documentation](https://cosmwasm.cosmos.network/ibc/extensions/callbacks) provides information for contract developers. The IBC v2 callbacks implementation uses a `Payload` but reconstructs an IBC classic `Packet` to preserve the cosmwasm contract keeper interface. Additionally contracts must now handle the IBC v2 `ErrorAcknowledgement` sentinel value in the case of a failure. The callbacks middleware can be used for transfer + action workflows, for example a transfer and swap on recieve. These workflows require knowledge of the ibc denom that has been recieved. To assist with parsing the ics20 packet, [helper functions](https://github.com/cosmos/solidity-ibc-eureka/blob/a8870b023e58622fb7b3f733572c684851f8e5ee/packages/cosmwasm/ibc-callbacks-helpers/src/ics20.rs#L7-L41) can be found in the solidity-ibc-eureka repository. ## Integration An example integration of the callbacks middleware in a transfer stack that is using IBC v2 can be found in the [ibc-go integration section](/ibc/latest/ibc/integration) # End Users Source: https://docs.cosmos.network/ibc/latest/middleware/callbacks/end-users This section explains how to use the callbacks middleware from the perspective of an IBC Actor. Callbacks middleware provides two types of callbacks: This section explains how to use the callbacks middleware from the perspective of an IBC Actor. Callbacks middleware provides two types of callbacks: * Source callbacks: * `SendPacket` callback * `OnAcknowledgementPacket` callback * `OnTimeoutPacket` callback * Destination callbacks: * `ReceivePacket` callback For a given channel, the source callbacks are supported if the source chain has the callbacks middleware wired up in the channel's IBC stack. Similarly, the destination callbacks are supported if the destination chain has the callbacks middleware wired up in the channel's IBC stack. Callbacks are always executed after the packet has been processed by the underlying IBC module. If the underlying application module is doing an asynchronous acknowledgement on packet receive (for example, if the [packet forward middleware](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware) is in the stack, and is being used by this packet), then the callbacks middleware will execute the `ReceivePacket` callback after the acknowledgement has been received. ## Source Callbacks Source callbacks are natively supported in the following ibc modules (if they are wrapped by the callbacks middleware): * `transfer` * `icacontroller` To have your source callbacks be processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: ```jsonc theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "src_callback": { "address": "callbackAddressString", / optional "gas_limit": "userDefinedGasLimitString", } } ``` ## Destination Callbacks Destination callbacks are natively only supported in the transfer module. Note that wrapping icahost is not supported. This is because icahost should be able to execute an arbitrary transaction anyway, and can call contracts or modules directly. To have your destination callbacks processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: ```jsonc theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "dest_callback": { "address": "callbackAddressString", / optional "gas_limit": "userDefinedGasLimitString", } } ``` Note that a packet can have both a source and destination callback. ```jsonc expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "src_callback": { "address": "callbackAddressString", / optional "gas_limit": "userDefinedGasLimitString", }, "dest_callback": { "address": "callbackAddressString", / optional "gas_limit": "userDefinedGasLimitString", } } ``` # User Defined Gas Limit User defined gas limit was added for the following reasons: * To prevent callbacks from blocking packet lifecycle. * To prevent relayers from being able to DOS the callback execution by sending a packet with a low amount of gas. There is a chain wide parameter that sets the maximum gas limit that a user can set for a callback. This is to prevent a user from setting a gas limit that is too high for relayers. If the `"gas_limit"` is not set in the packet memo, then the maximum gas limit is used. These goals are achieved by creating a minimum gas amount required for callback execution. If the relayer provides at least the minimum gas limit for the callback execution, then the packet lifecycle will not be blocked if the callback runs out of gas during execution, and the callback cannot be retried. If the relayer does not provided the minimum amount of gas and the callback executions runs out of gas, the entire tx is reverted and it may be executed again. `SendPacket` callback is always reverted if the callback execution fails or returns an error for any reason. This is so that the packet is not sent if the callback execution fails. # Events Source: https://docs.cosmos.network/ibc/latest/middleware/callbacks/events An overview of all events related to the callbacks middleware. There are two types of events, "ibcsrccallback" and "ibcdestcallback". An overview of all events related to the callbacks middleware. There are two types of events, `"ibc_src_callback"` and `"ibc_dest_callback"`. ## Shared Attributes Both of these event types share the following attributes: | **Attribute Key** | **Attribute Values** | **Optional** | | :--------------------------: | :-----------------------------------------------------------------------------------------: | :----------------: | | module | "ibccallbacks" | | | callback\_type | **One of**: "send\_packet", "acknowledgement\_packet", "timeout\_packet", "receive\_packet" | | | callback\_address | string | | | callback\_exec\_gas\_limit | string (parsed from uint64) | | | callback\_commit\_gas\_limit | string (parsed from uint64) | | | packet\_sequence | string (parsed from uint64) | | | callback\_result | **One of**: "success", "failure" | | | callback\_error | string (parsed from callback err) | Yes, if err != nil | ## `ibc_src_callback` Attributes | **Attribute Key** | **Attribute Values** | | :------------------: | :----------------------: | | packet\_src\_port | string (sourcePortID) | | packet\_src\_channel | string (sourceChannelID) | ## `ibc_dest_callback` Attributes | **Attribute Key** | **Attribute Values** | | :-------------------: | :--------------------: | | packet\_dest\_port | string (destPortID) | | packet\_dest\_channel | string (destChannelID) | # Gas Management Source: https://docs.cosmos.network/ibc/latest/middleware/callbacks/gas Executing arbitrary code on a chain can be arbitrarily expensive. In general, a callback may consume infinite gas (think of a callback that loops forever). This is problematic for a few reasons: ## Overview Executing arbitrary code on a chain can be arbitrarily expensive. In general, a callback may consume infinite gas (think of a callback that loops forever). This is problematic for a few reasons: * It can block the packet lifecycle. * It can be used to consume all of the relayer's funds and gas. * A relayer can DOS the callback execution by sending a packet with a low amount of gas. To prevent these, the callbacks middleware introduces two gas limits: a chain wide gas limit (`maxCallbackGas`) and a user defined gas limit. ### Chain Wide Gas Limit Since the callbacks middleware does not have a keeper, it does not use a governance parameter to set the chain wide gas limit. Instead, the chain wide gas limit is passed in as a parameter to the callbacks middleware during initialization. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / app.go maxCallbackGas := uint64(10_000_000) var transferStack porttypes.IBCModule transferStack = transfer.NewIBCModule(app.TransferKeeper) transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.MockContractKeeper, maxCallbackGas) / Add transfer stack to IBC Router ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) ``` ### User Defined Gas Limit The user defined gas limit is set by the IBC Actor during packet creation. The user defined gas limit is set in the packet memo. If the user defined gas limit is not set or if the user defined gas limit is greater than the chain wide gas limit, then the chain wide gas limit is used as the user defined gas limit. ```jsonc expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "src_callback": { "address": "callbackAddressString", / optional "gas_limit": "userDefinedGasLimitString", }, "dest_callback": { "address": "callbackAddressString", / optional "gas_limit": "userDefinedGasLimitString", } } ``` ## Gas Limit Enforcement During a callback execution, there are three types of gas limits that are enforced: * User defined gas limit * Chain wide gas limit * Context gas limit (amount of gas that the relayer has left for this execution) Chain wide gas limit is used as a maximum to the user defined gas limit as explained in the [previous section](#user-defined-gas-limit). It may also be used as a default value if no user gas limit is provided. Therefore, we can ignore the chain wide gas limit for the rest of this section and work with the minimum of the chain wide gas limit and user defined gas limit. This minimum is called the commit gas limit. The gas limit enforcement is done by executing the callback inside a cached context with a new gas meter. The gas meter is initialized with the minimum of the commit gas limit and the context gas limit. This minimum is called the execution gas limit. We say that retries are allowed if `context gas limit < commit gas limit`. Otherwise, we say that retries are not allowed. If the callback execution fails due to an out of gas error, then the middleware checks if retries are allowed. If retries are not allowed, then it recovers from the out of gas error, consumes execution gas limit from the original context, and continues with the packet life cycle. If retries are allowed, then it panics with an out of gas error to revert the entire tx. The packet can then be submitted again with a higher gas limit. The out of gas panic descriptor is shown below. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit) } ``` If the callback execution does not fail due to an out of gas error then the callbacks middleware does not block the packet life cycle regardless of whether retries are allowed or not. # Integration Source: https://docs.cosmos.network/ibc/latest/middleware/callbacks/integration Learn how to integrate the callbacks middleware with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. Learn how to integrate the callbacks middleware with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. An example integration for an IBC v2 transfer stack using the callbacks middleware can be found in the [ibc-go module integration](/ibc/latest/ibc/integration) section The callbacks middleware is a minimal and stateless implementation of the IBC middleware interface. It does not have a keeper, nor does it store any state. It simply routes IBC middleware messages to the appropriate callback function, which is implemented by the secondary application. Therefore, it doesn't need to be registered as a module, nor does it need to be added to the module manager. It only needs to be added to the IBC application stack. ## Pre-requisite Readings * [IBC middleware development](/ibc/latest/ibc/middleware/develop) * [IBC middleware integration](/ibc/latest/ibc/middleware/integration) The callbacks middleware, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. ## Configuring an application stack with the callbacks middleware As mentioned in [IBC middleware development](/ibc/latest/ibc/middleware/develop) an application stack may be composed of many or no middlewares that nest a base application. These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. For example, an application stack may just be a single base application like `transfer`, however, the same application stack composed with `packet-forward-middleware` and `callbacks` will nest the `transfer` base application twice by wrapping it with the callbacks module and then packet forward middleware. The callbacks middleware also **requires** a secondary application that will receive the callbacks to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/types/expected_keepers.go#L12-L100). The wasmd contract keeper has been implemented [here](https://github.com/CosmWasm/wasmd/tree/main/x/wasm/keeper) and is referenced as the `WasmKeeper`. ### Transfer See below for an example of how to create an application stack using `transfer`, `packet-forward-middleware`, and `callbacks`. Feel free to omit the `packet-forward-middleware` if you do not want to use it. The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. The in-line comments describe the execution flow of packets between the application stack and IBC core. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / Create Transfer Stack / SendPacket, since it is originating from the application to core IBC: / transferKeeper.SendPacket -> callbacks.SendPacket -> feeKeeper.SendPacket -> channel.SendPacket / RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way / channel.RecvPacket -> fee.OnRecvPacket -> callbacks.OnRecvPacket -> transfer.OnRecvPacket / transfer stack contains (from top to bottom): / - IBC Packet Forward Middleware / - IBC Callbacks Middleware / - Transfer / initialise the gas limit for callbacks, recommended to be 10M for use with cosmwasm contracts maxCallbackGas := uint64(10_000_000) / the keepers for the callbacks middleware wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) / create IBC module from bottom to top of stack / Create Transfer Stack var transferStack porttypes.IBCModule transferStack = transfer.NewIBCModule(app.TransferKeeper) / callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper / i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper / Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's / WriteAcknowledgement. cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) transferStack = packetforward.NewIBCMiddleware( cbStack, app.PacketForwardKeeper, 0, packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, ) app.TransferKeeper.WithICS4Wrapper(cbStack) / Create static IBC router, add app routes, then set and seal it ibcRouter := porttypes.NewRouter() ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) ibcRouter.AddRoute(wasmtypes.ModuleName, wasmStackIBCHandler) app.IBCKeeper.SetRouter(ibcRouter) ``` # Interfaces Source: https://docs.cosmos.network/ibc/latest/middleware/callbacks/interfaces The callbacks middleware requires certain interfaces to be implemented by the underlying IBC applications and the secondary application. If you're simply wiring up the callbacks middleware to an existing IBC application stack and a secondary application such as `icacontroller` and `x/wasm`, you can skip this section. ## Interfaces for developing the Underlying IBC Application ### `PacketDataUnmarshaler` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / PacketDataUnmarshaler defines an optional interface which allows a middleware to / request the packet data to be unmarshaled by the base application. type PacketDataUnmarshaler interface { / UnmarshalPacketData unmarshals the packet data into a concrete type / ctx, portID, channelID are provided as arguments, so that (if needed) / the packet data can be unmarshaled based on the channel version. / The version of the underlying app is also returned. UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{ }, string, error) } ``` The callbacks middleware **requires** the underlying ibc application to implement the [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/05-port/types/module.go#L142-L147) interface so that it can unmarshal the packet data bytes into the appropriate packet data type. This allows usage of interface functions implemented by the packet data type. The packet data type is expected to implement the `PacketDataProvider` interface (see section below), which is used to parse the callback data that is currently stored in the packet memo field for `transfer` and `ica` packets as a JSON string. See its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/ibc_module.go#L303-L313) and [`icacontroller`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268) modules for reference. If the underlying application is a middleware itself, then it can implement this interface by simply passing the function call to its underlying application. See its implementation in the [`fee middleware`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/29-fee/ibc_middleware.go#L368-L378) for reference. ### `PacketDataProvider` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. / An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. / A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. / This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. type PacketDataProvider interface { / GetCustomPacketData returns the packet data held on behalf of another application. / The name the information is stored under should be provided as the key. / If no custom packet data exists for the key, nil should be returned. GetCustomPacketData(key string) interface{ } } ``` The callbacks middleware also **requires** the underlying ibc application's packet data type to implement the [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L43-L52) interface. This interface is used to retrieve the callback data from the packet data (using the memo field in the case of `transfer` and `ica`). For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L85-L105) module. Since middlewares do not have packet types, they do not need to implement this interface. ### `PacketData` ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / PacketData defines an optional interface which an application's packet data structure may implement. type PacketData interface { / GetPacketSender returns the sender address of the packet data. / If the packet sender is unknown or undefined, an empty string should be returned. GetPacketSender(sourcePortID string) string } ``` [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L36-L41) is an optional interface that can be implemented by the underlying ibc application's packet data type. It is used to retrieve the packet sender address from the packet data. The callbacks middleware uses this interface to retrieve the packet sender address and pass it to the callback function during a source callback. If this interface is not implemented, then the callbacks middleware passes and empty string as the sender address. For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L74-L83) and [`ica`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/types/packet.go#L78-L92) module. This interface was added so that secondary applications can retrieve the packet sender address to perform custom authorization logic if needed. Since middlewares do not have packet types, they do not need to implement this interface. ## Interfaces for developing the Secondary Application ### `ContractKeeper` The callbacks middleware requires the secondary application to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/callbacks/types/expected_keepers.go#L11-L83) interface. The contract keeper will be invoked at each step of the packet lifecycle. When a packet is sent, if callback information is provided, the contract keeper will be invoked via the `IBCSendPacketCallback`. This allows the contract keeper to prevent packet sends when callback information is provided, for example if the sender is unauthorized to perform callbacks on the given information. If the packet send is successful, the contract keeper on the destination (if present) will be invoked when a packet has been received and the acknowledgement is written, this will occur via `IBCReceivePacketCallback`. At the end of the packet lifecycle, when processing acknowledgements or timeouts, the source contract keeper will be invoked either via `IBCOnAcknowledgementPacket` or `IBCOnTimeoutPacket`. Once a packet has been sent, each step of the packet lifecycle can be processed given that a relayer sets the gas limit to be more than or equal to the required `CommitGasLimit`. State changes performed in the callback will only be committed upon successful execution. ```go expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} / ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract type ContractKeeper interface { / IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The / packetSenderAddress is determined by the underlying module, and may be empty if the sender is / unknown or undefined. The contract is expected to handle the callback within the user defined / gas limit, and handle any errors, or panics gracefully. / This entry point is called with a cached context. If an error is returned, then the changes in / this context will not be persisted, and the error will be propagated to the underlying IBC / application, resulting in a packet send failure. / / Implementations are provided with the packetSenderAddress and MAY choose to use this to perform / validation on the origin of a given packet. It is recommended to perform the same validation / on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This / defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. / / The version provided is the base application version for the given packet send. This allows / contracts to determine how to unmarshal the packetData. IBCSendPacketCallback( cachedCtx sdk.Context, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, packetData []byte, contractAddress, packetSenderAddress string, version string, ) error / IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement / is received. The packetSenderAddress is determined by the underlying module, and may be empty if / the sender is unknown or undefined. The contract is expected to handle the callback within the / user defined gas limit, and handle any errors, or panics gracefully. / This entry point is called with a cached context. If an error is returned, then the changes in / this context will not be persisted, but the packet lifecycle will not be blocked. / / Implementations are provided with the packetSenderAddress and MAY choose to use this to perform / validation on the origin of a given packet. It is recommended to perform the same validation / on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This / defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. / / The version provided is the base application version for the given packet send. This allows / contracts to determine how to unmarshal the packetData. IBCOnAcknowledgementPacketCallback( cachedCtx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, contractAddress, packetSenderAddress string, version string, ) error / IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before / the timeout height. The packetSenderAddress is determined by the underlying module, and may be / empty if the sender is unknown or undefined. The contract is expected to handle the callback / within the user defined gas limit, and handle any error, out of gas, or panics gracefully. / This entry point is called with a cached context. If an error is returned, then the changes in / this context will not be persisted, but the packet lifecycle will not be blocked. / / Implementations are provided with the packetSenderAddress and MAY choose to use this to perform / validation on the origin of a given packet. It is recommended to perform the same validation / on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This / defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. / / The version provided is the base application version for the given packet send. This allows / contracts to determine how to unmarshal the packetData. IBCOnTimeoutPacketCallback( cachedCtx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, contractAddress, packetSenderAddress string, version string, ) error / IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. / The contract is expected to handle the callback within the user defined gas limit, and handle any errors, / out of gas, or panics gracefully. / This entry point is called with a cached context. If an error is returned, then the changes in / this context will not be persisted, but the packet lifecycle will not be blocked. / / The version provided is the base application version for the given packet send. This allows / contracts to determine how to unmarshal the packetData. IBCReceivePacketCallback( cachedCtx sdk.Context, packet ibcexported.PacketI, ack ibcexported.Acknowledgement, contractAddress string, version string, ) error } ``` These are the callback entry points exposed to the secondary application. The secondary application is expected to execute its custom logic within these entry points. The callbacks middleware will handle the execution of these callbacks and revert the state if needed. Note that the source callback entry points are provided with the `packetSenderAddress` and MAY choose to use this to perform validation on the origin of a given packet. It is recommended to perform the same validation on all source chain callbacks (SendPacket, AcknowledgePacket, TimeoutPacket). This defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. # Overview Source: https://docs.cosmos.network/ibc/latest/middleware/callbacks/overview Learn about what the Callbacks Middleware is, and how to build custom modules that utilize the Callbacks Middleware functionality Learn about what the Callbacks Middleware is, and how to build custom modules that utilize the Callbacks Middleware functionality ## What is the Callbacks Middleware? IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC, and receive a callback on every step of that packet's lifecycle. This allows IBC applications to be built on top of core IBC, and to be able to execute custom logic on packet lifecycle events (e.g. unescrow tokens for ICS-20). This setup worked well for off-chain users interacting with IBC applications. However, we are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. The Callbacks Middleware allows for this functionality by allowing the packets of the underlying IBC applications to register callbacks to secondary applications for lifecycle events. These callbacks are then executed by the Callbacks Middleware when the corresponding packet lifecycle event occurs. After much discussion, the design was expanded to an ADR, and the Callbacks Middleware is an implementation of that ADR. ## Concepts Callbacks Middleware was built with smart contracts in mind, but can be used by any secondary application that wants to allow IBC packets to call into it. Think of the Callbacks Middleware as a bridge between core IBC and a secondary application. We have the following definitions: * `Underlying IBC application`: The IBC application that is wrapped by the Callbacks Middleware. This is the IBC application that is actually sending and receiving packet lifecycle events from core IBC. For example, the transfer module, or the ICA controller submodule. * `IBC Actor`: IBC Actor is an on-chain or off-chain entity that can initiate a packet on the underlying IBC application. For example, a smart contract, an off-chain user, or a module that sends a transfer packet are all IBC Actors. * `Secondary application`: The application that is being called into by the Callbacks Middleware for packet lifecycle events. This is the application that is receiving the callback directly from the Callbacks Middleware module. For example, the `x/wasm` module. * `Callback Actor`: The on-chain smart contract or module that is registered to receive callbacks from the secondary application. For example, a Wasm smart contract (gatekeeped by the `x/wasm` module). Note that the Callback Actor is not necessarily the same as the IBC Actor. For example, an off-chain user can initiate a packet on the underlying IBC application, but the Callback Actor could be a smart contract. The secondary application may want to check that the IBC Actor is allowed to call into the Callback Actor, for example, by checking that the IBC Actor is the same as the Callback Actor. * `Callback Address`: Address of the Callback Actor. This is the address that the secondary application will call into when a packet lifecycle event occurs. For example, the address of the Wasm smart contract. * `Maximum gas limit`: The maximum amount of gas that the Callbacks Middleware will allow the secondary application to use when it executes its custom logic. * `User defined gas limit`: The amount of gas that the IBC Actor wants to allow the secondary application to use when it executes its custom logic. This is the gas limit that the IBC Actor specifies when it sends a packet to the underlying IBC application. This cannot be greater than the maximum gas limit. Think of the secondary application as a bridge between the Callbacks Middleware and the Callback Actor. The secondary application is responsible for executing the custom logic of the Callback Actor when a packet lifecycle event occurs. The secondary application is also responsible for checking that the IBC Actor is allowed to call into the Callback Actor. Note that it is possible that the IBC Actor, Secondary Application, and Callback Actor are all the same entity. In which case, the Callback Address should be the secondary application's module address. The following diagram shows how a typical `RecvPacket`, `AcknowledgementPacket`, and `TimeoutPacket` execution flow would look like: callbacks-middleware And the following diagram shows how a typical `SendPacket` and `WriteAcknowledgement` execution flow would look like: callbacks-middleware ## Known Limitations * Callbacks are always executed after the underlying IBC application has executed its logic. * Maximum gas limit is hardcoded manually during wiring. It requires a coordinated upgrade to change the maximum gas limit. * The receive packet callback does not pass the relayer address to the secondary application. This is so that we can use the same callback for both synchronous and asynchronous acknowledgements. * The receive packet callback does not pass IBC Actor's address, this is because the IBC Actor lives in the counterparty chain and cannot be trusted. # Example Flows Source: https://docs.cosmos.network/ibc/latest/middleware/packet-forward-middleware/example-usage This document outlines some example flows leveraging packet forward middleware and formats of the memo field. This document outlines some example flows leveraging packet forward middleware and formats of the memo field. ## Example Scenarios ### Successful Transfer forwarding through chain B ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram autonumber Chain A ->> Chain B: Send PFM transfer Chain B ->> Chain C: Forward Chain C ->> Chain B: ACK Chain B ->> Chain A: ACK ``` ### Memo for simple forward * The packet-forward-middleware integrated on Chain B. * The packet data `receiver` for the `MsgTransfer` on Chain A is set to `"pfm"` or some other invalid bech32 string.\* * The packet `memo` is included in `MsgTransfer` by user on Chain A. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "forward": { "receiver": "chain-c-bech32-address", "port": "transfer", "channel": "channel-123" } } ``` ### Error on Forwarding Hop, Refund to A ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram autonumber Chain A ->> Chain B: PFM transfer Chain B ->> Chain C: Forward Chain C ->> Chain B: ACK error Chain B ->> Chain A: ACK error ``` ### Forwarding with Retry and Timeout Logic ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram autonumber Chain A ->> Chain B: PFM transfer Chain B ->> Chain C: Forward Chain C --x Chain B: Timeout Chain B ->> Chain C: Retry forward Chain C --x Chain B: Timeout Chain B ->> Chain A: ACK error ``` ### Multi-hop Transfer A → B → C → D (full success) ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram autonumber Chain A ->> Chain B: PFM transfer Chain B --> Chain B: recv_packet Chain B ->> Chain C: forward Chain C --> Chain C: recv_packet Chain C ->> Chain D: forward Chain D --> Chain D: recv_packet Chain D ->> Chain C: ack Chain C ->> Chain B: ack Chain B ->> Chain A: ack ``` ### Multi-hop Transfer A → B → C → D (error at D, refund to A) ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram autonumber Chain A ->> Chain B: PFM transfer Chain B --> Chain B: recv_packet Chain B ->> Chain C: forward Chain C --> Chain C: recv_packet Chain C ->> Chain D: forward Chain D --> Chain D: recv_packet (error) Chain D ->> Chain C: ACK error Chain C ->> Chain B: ACK error Chain B ->> Chain A: ACK error ``` ### A -> B -> C full success 1. `A` This sends packet over underlying ICS-004 wrapper with memo as is. 2. `B` This receives packet and parses it into ICS-020 packet. 3. `B` Validates `forward` packet on this step, return `ACK` error if fails. 4. `B` If other middleware not yet called ICS-020, call it and ACK error on fail. Tokens minted or unescrowed here. 5. `B` Handle denom. If denom prefix matches the source chain's (counterparty, A's) port/channel, remove it (path-unwind). If not — add `B`'s port/channel as prefix. 6. `B` Create new ICS-004 packet with timeout from forward for next step, and remaining inner `memo`. 7. `B` Send transfer to `C` with parameters obtained from `memo`. Tokens burnt or escrowed here. 8. `B` Store tracking `in flight packet` under next `(channel, port, ICS-20 transfer sequence)`, do not `ACK` packet yet. 9. `C` Handle ICS-020 packet as usual. 10. `B` On ICS-020 ACK from `C` find `in flight packet`, delete it and write `ACK` for original packet from `A`. 11. `A` Handle ICS-020 `ACK` as usual [Example](https://mintscan.io/osmosis-testnet/txs/FAB912347B8729FFCA92AC35E6B1E83BC8169DE7CC2C254A5A3F70C8EC35D771?height=3788973) of USDC transfer from Osmosis -> Noble -> Sei ### A -> B -> C with C error ACK 10. `B` On ICS-020 error `ACK` from `C`, find the `in flight packet` and delete it. 11. `B` reverses the tokens it moved when forwarding, refunding them toward `A`. 12. `B` writes an error `ACK` for the original packet from `A`. 13. `A` handles the error `ACK` (a refund) as usual. Same behavior in case of timeout on `C` ### A packet timeouts on B before C timeouts packet from B 10. `A` Cannot timeout because `in flight packet` has proof on `B` of packet inclusion. 11. `B` waits for ACK or timeout from `C`. 12. `B` timeout from `C` becomes fail `ACK` on `B` for `A` 13. `A` receives success or fail `ACK`, but not timeout In this case `A` assets `hang` until final hop timeouts or ACK. ### Memo for Retry and Timeout Logic, with Nested Memo (2 forwards) * The packet-forward-middleware integrated on Chain B and Chain C. * The packet data `receiver` for the `MsgTransfer` on Chain A is set to `"pfm"` or some other invalid bech32 string. * The forward metadata `receiver` for the hop from Chain B to Chain C is set to `"pfm"` or some other invalid bech32 string. * The packet `memo` is included in `MsgTransfer` by user on Chain A. * A packet timeout of 10 minutes and 2 retries is set for both forwards. In the case of a timeout after 10 minutes for either forward, the packet would be retried up to 2 times, afterwards an error ack would be written to issue a refund on the prior chain. `next` is the `memo` to pass to the next transfer hop. Provide it either as a nested JSON object or as an escaped JSON string; PFM accepts both forms and re-serializes the value for the next hop. `next` as JSON ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "forward": { "receiver": "pfm", "port": "transfer", "channel": "channel-123", "timeout": "10m", "retries": 2, "next": { "forward": { "receiver": "chain-d-bech32-address", "port": "transfer", "channel": "channel-234", "timeout": "10m", "retries": 2 } } } } ``` `next` as escaped JSON string ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "forward": { "receiver": "pfm", "port": "transfer", "channel": "channel-123", "timeout": "10m", "retries": 2, "next": "{\"forward\":{\"receiver\":\"chain-d-bech32-address\",\"port\":\"transfer\",\"channel\":\"channel-234\",\"timeout\":\"10m\",\"retries\":2}}" } } ``` ## Intermediate Address Security Intermediate chains don’t need a valid receiver address. Instead, they derive a secure address from the packet’s sender and channel, preventing users from forwarding tokens to arbitrary accounts. To avoid accidental transfers to chains without PFM, use an invalid bech32 address (e.g., "pfm") for intermediate receivers. # Integration Source: https://docs.cosmos.network/ibc/latest/middleware/packet-forward-middleware/integration This document provides instructions on integrating and configuring the Packet Forward Middleware (PFM) within your existing chain implementation. This document provides instructions on integrating and configuring the Packet Forward Middleware (PFM) within your existing chain implementation. The integration steps include the following: 1. [Import the PFM, initialize the PFM Module & Keeper, initialize the store keys, and initialize the Begin/End Block logic and InitGenesis order](#example-integration-of-the-packet-forward-middleware) 2. [Configure the IBC application stack including the transfer module](#configuring-the-transfer-application-stack-with-packet-forward-middleware) 3. [Configuration of additional options such as timeout period and number of retries on timeout](#configurable-options-in-the-packet-forward-middleware) Integration of the PFM should take approximately 20 minutes. ## Example integration of the Packet Forward Middleware ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go // Import the packet forward middleware import ( packetforward "github.com/cosmos/ibc-go/v11/modules/apps/packet-forward-middleware" packetforwardkeeper "github.com/cosmos/ibc-go/v11/modules/apps/packet-forward-middleware/keeper" packetforwardtypes "github.com/cosmos/ibc-go/v11/modules/apps/packet-forward-middleware/types" ) ... // Register the AppModule for the packet forward middleware module ModuleBasics = module.NewBasicManager( ... packetforward.AppModuleBasic{}, ... ) ... // Add packet forward middleware Keeper type App struct { ... PFMKeeper *packetforwardkeeper.Keeper ... } ... // Create store keys keys := storetypes.NewKVStoreKeys( ... packetforwardtypes.StoreKey, ... ) ... // Initialize the transfer module Keeper first (PFM Keeper requires it) app.TransferKeeper = ibctransferkeeper.NewKeeper(...) // Initialize the packet forward middleware Keeper app.PFMKeeper = packetforwardkeeper.NewKeeper( appCodec, app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[packetforwardtypes.StoreKey]), app.TransferKeeper, app.IBCKeeper.ChannelKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) // See the section below for configuring an application stack with the packet forward middleware ... // Register packet forward middleware AppModule app.moduleManager = module.NewManager( ... packetforward.NewAppModule(app.PFMKeeper), ) ... // Add packet forward middleware to begin blocker logic app.moduleManager.SetOrderBeginBlockers( ... packetforwardtypes.ModuleName, ... ) // Add packet forward middleware to end blocker logic app.moduleManager.SetOrderEndBlockers( ... packetforwardtypes.ModuleName, ... ) // Add packet forward middleware to init genesis logic app.moduleManager.SetOrderInitGenesis( ... packetforwardtypes.ModuleName, ... ) ``` ## Configuring the transfer application stack with Packet Forward Middleware Here is an example of how to create an application stack using `transfer` and `packet-forward-middleware`. The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. The in-line comments describe the execution flow of packets between the application stack and IBC core. For more information on configuring an IBC application stack see the [middleware development guide](/ibc/latest/ibc/middleware/develop). ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Create Transfer Stack // SendPacket, since it is originating from the application to core IBC: // transferKeeper.SendPacket -> packetforward.SendPacket -> channel.SendPacket // RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way // channel.RecvPacket -> packetforward.OnRecvPacket -> transfer.OnRecvPacket // transfer stack contains (from top to bottom): // - Packet Forward Middleware // - Transfer // create IBC module from bottom to top of stack transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper) transferStack.Base(transfer.NewIBCModule(app.TransferKeeper)). Next(packetforward.NewIBCMiddleware( app.PFMKeeper, 0, // retries on timeout packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, // forward timeout )) // Add transfer stack to IBC Router ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack.Build()) ``` ## Configurable options in the Packet Forward Middleware The Packet Forward Middleware has several configurable options available when initializing the IBC application stack. You can see these passed in as arguments to `packetforward.NewIBCMiddleware` and they include the number of retries that will be performed on a forward timeout, and the timeout period that will be used for a forward. * Retries On Timeout - how many times will a forward be re-attempted in the case of a timeout. * Timeout Period - how long can a forward be in progress before giving up. # Overview Source: https://docs.cosmos.network/ibc/latest/middleware/packet-forward-middleware/overview Packet Forward Middleware enables multi-hop IBC token transfers through intermediate chains. Packet forward middleware is only compatible with IBC classic, not IBC v2 Learn about packet forward middleware, a middleware that can be used in combination with token transfers (ICS-20) ## What is Packet Forward Middleware? Packet Forward Middleware enables multi-hop token transfers by forwarding IBC packets through intermediate chains, which may not be directly connected. It supports: * **Path-Unwinding Functionality:** Because the fungibility of tokens transferred between chains is determined by [the path the tokens have travelled](/ibc/latest/apps/transfer/overview#denomination-trace), i.e. the same token sent from chain A to chain B is not fungible with the same token sent from chain A, to chain C and then to chain B, packet forward middleware also enables routing tokens back through their source, before sending onto the final destination. * **Asynchronous Acknowledgements:** Multi-hop sequences are atomic: acknowledgements are only written to the origin chain after all forwarding steps succeed or fail, so users only need to monitor the source chain for the result. * **Retry and Timeout Handling:** The middleware can be configured to retry forwarding in the case that there was a timeout. * **Forwarding across multiple chains with nested memos:** Instructions on which route to take to forward a packet across more than one chain can be set within a nested JSON with the memo field. ## How it works 1. User initiates a `MsgTransfer` with a memo JSON payload containing forwarding instructions. 2. Intermediate chains (with PFM enabled) parse the memo and forward the packet to the destination specified. 3. Acknowledgements are passed back step-by-step to the origin chain after the final hop succeeds or fails, along the same path used for forwarding. In practise, it can be challenging to correctly format the memo for the desired route. It is recommended to use the Skip API to correctly format the memo needed in `MsgTransfer` to make this easy. # Integration Source: https://docs.cosmos.network/ibc/latest/middleware/rate-limit-middleware/integration Wire the rate limiting middleware into a chain for IBC v1 and v2. This guide wires the rate limiting middleware into a chain's `app.go`, using simapp as the reference. It assumes an ibc-go application that already has the transfer module wired. Wire the middleware for IBC v1, IBC v2, or both. The middleware only understands ICS-20 transfer packets, so it must wrap the transfer application. It cannot sit on a non-transfer route. ## Before you begin Confirm the application already has the middleware's dependencies: * The transfer keeper and, for IBC v2, the transfer v2 module. * The IBC keeper, including its `ChannelKeeper`, `ClientKeeper`, and for v2 its `ChannelKeeperV2`. * The bank keeper. * A governance authority address, the only account allowed to change limits. The module needs no manual genesis. It defaults to empty state: no limits, no whitelist, and no blacklist. To preload a whitelist or blacklist, see [configure rate limits](/ibc/latest/middleware/rate-limit-middleware/setting-limits). ## Wire rate limiting for IBC v1 These changes give a complete v1 integration. They add the keeper, register the middleware at the top of the transfer stack, and add the module to the manager. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go import ( // ... porttypes "github.com/cosmos/ibc-go/v11/modules/core/05-port/types" ratelimiting "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting" ratelimitkeeper "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/keeper" ratelimittypes "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/types" ) // Add the keeper to the application struct. type App struct { // ... RateLimitKeeper *ratelimitkeeper.Keeper } // Register the module's store key. keys := storetypes.NewKVStoreKeys( // ... ratelimittypes.StoreKey, ) // Create the rate limit keeper. app.RateLimitKeeper = ratelimitkeeper.NewKeeper( appCodec, app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[ratelimittypes.StoreKey]), app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ClientKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority ) // Build the transfer stack with rate limiting on top. // // The stack, from top to bottom: // - core IBC // - rate limiting // - transfer transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper) transferStack. Base(transfer.NewIBCModule(app.TransferKeeper)). Next(ratelimiting.NewIBCMiddleware(app.RateLimitKeeper)) // Add the stack to the IBC router. ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack.Build()) // Register the app module with the module manager. app.ModuleManager = module.NewManager( // ... ratelimiting.NewAppModule(app.RateLimitKeeper), ) // Add the module to the begin blocker, end blocker, and genesis orders. app.ModuleManager.SetOrderBeginBlockers( // ... ratelimittypes.ModuleName, ) app.ModuleManager.SetOrderEndBlockers( // ... ratelimittypes.ModuleName, ) genesisModuleOrder := []string{ // ... ratelimittypes.ModuleName, } app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) ``` Key details: * The last keeper argument is the governance authority. The keeper stores it and enforces it on every message. A chain that uses the standard governance module passes `authtypes.NewModuleAddress(govtypes.ModuleName).String()`. * The module runs a begin-block hook and has genesis state, so it must appear in the begin-blocker order and the init and export genesis orderings. * If the chain already wires other transfer middleware, add rate limiting to the same stack with another `Next` call rather than creating a new stack. See the packet forward example below. ### With packet forward middleware If the chain runs packet forward middleware, place rate limiting above it in the same stack, so limits apply to the final inbound transfer before it is forwarded: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // transfer stack, from top to bottom: // - rate limiting // - packet forward // - transfer // // SendPacket: transfer -> packet forward -> rate limiting -> core IBC // RecvPacket: core IBC -> rate limiting -> packet forward -> transfer transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper) transferStack. Base(transfer.NewIBCModule(app.TransferKeeper)). Next(packetforward.NewIBCMiddleware( app.PFMKeeper, 0, // retries on timeout packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, )). Next(ratelimiting.NewIBCMiddleware(app.RateLimitKeeper)) ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack.Build()) ``` ## Wire rate limiting for IBC v2 These changes give a complete v2 integration. IBC v2 uses a separate router, and the middleware wraps the transfer v2 module directly. The keeper, store key, module registration, and ordering are shared across versions. If the chain already wired v1 rate limiting above, those parts are done. Only the v2 router wiring is new, so skip to the step that builds `transferModuleV2`. ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app.go import ( // ... ratelimiting "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting" ratelimitkeeper "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/keeper" ratelimittypes "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/types" ratelimitingv2 "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/v2" ) // Add the keeper to the application struct. type App struct { // ... RateLimitKeeper *ratelimitkeeper.Keeper } // Register the module's store key. keys := storetypes.NewKVStoreKeys( // ... ratelimittypes.StoreKey, ) // Create the rate limit keeper. app.RateLimitKeeper = ratelimitkeeper.NewKeeper( appCodec, app.AccountKeeper.AddressCodec(), runtime.NewKVStoreService(keys[ratelimittypes.StoreKey]), app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ClientKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority ) // Wrap the transfer v2 module with rate limiting. transferModuleV2 := ratelimitingv2.NewIBCMiddleware( *app.RateLimitKeeper, // v2 takes the keeper by value, so dereference it transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, // write-acknowledgement wrapper app.IBCKeeper.ChannelKeeperV2, // v2 channel keeper ) // Add the wrapped module to the v2 router. ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferModuleV2) // Register the app module with the module manager. app.ModuleManager = module.NewManager( // ... ratelimiting.NewAppModule(app.RateLimitKeeper), ) // Add the module to the begin blocker, end blocker, and genesis orders. app.ModuleManager.SetOrderBeginBlockers( // ... ratelimittypes.ModuleName, ) app.ModuleManager.SetOrderEndBlockers( // ... ratelimittypes.ModuleName, ) genesisModuleOrder := []string{ // ... ratelimittypes.ModuleName, } app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) ``` The v2 constructor takes four arguments: the keeper by value, the module being wrapped, a write-acknowledgement wrapper, and the v2 channel keeper. In simapp the last two are both `app.IBCKeeper.ChannelKeeperV2`. Both are required, and the constructor panics if either is nil. The v2 middleware enforces the same limits as v1. It converts each v2 payload to the internal packet form and calls the same keeper logic, so a denom's limit applies identically regardless of which IBC version carried the transfer. The transfer v2 route through rate limiting is added by [pull request #8984](https://github.com/cosmos/ibc-go/pull/8984). On an ibc-go version from before that change, only the v1 stack is wired. Confirm the version in use includes it before wiring v2. ## What can go wrong * The application panics on start. The store key is likely unregistered, or a nil dependency reached the v2 constructor. * Limits never trigger. Confirm the middleware is on the route the transfers use, and that the module is in the begin-blocker order so windows advance. ## Next steps * [Configure rate limits](/ibc/latest/middleware/rate-limit-middleware/setting-limits) through governance. * Review how limits are evaluated in the [rate limiting overview](/ibc/latest/middleware/rate-limit-middleware/overview). # Migration Source: https://docs.cosmos.network/ibc/latest/middleware/rate-limit-middleware/migration Move an existing ibc-apps rate limiting integration to ibc-go. The rate limiting middleware moved from the standalone `ibc-apps` repository into ibc-go. Migrating is a code change across `app.go`. The import paths, the package name, the keeper constructor, and both middleware constructors all change. This guide moves an integration from `ibc-apps` v10 to ibc-go v11.2. For the full current wiring, see [integrate the rate limiting middleware](/ibc/latest/middleware/rate-limit-middleware/integration). ## Update the imports The module path changes, and the root package is renamed from `ratelimit` to `ratelimiting`. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - ratelimit "github.com/cosmos/ibc-apps/modules/rate-limiting/v10" - ratelimitkeeper "github.com/cosmos/ibc-apps/modules/rate-limiting/v10/keeper" - ratelimittypes "github.com/cosmos/ibc-apps/modules/rate-limiting/v10/types" - ratelimitv2 "github.com/cosmos/ibc-apps/modules/rate-limiting/v10/v2" + ratelimiting "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting" + ratelimitkeeper "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/keeper" + ratelimittypes "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/types" + ratelimitingv2 "github.com/cosmos/ibc-go/v11/modules/apps/rate-limiting/v2" ``` ## Update the keeper The keeper field becomes a pointer. The constructor drops the parameter subspace and the trailing ICS4 wrapper argument, adds an address codec, and moves the authority to the end. It returns a pointer, so drop the dereference. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // app struct field - RateLimitKeeper ratelimitkeeper.Keeper + RateLimitKeeper *ratelimitkeeper.Keeper // keeper construction - app.RateLimitKeeper = *ratelimitkeeper.NewKeeper( - appCodec, - runtime.NewKVStoreService(keys[ratelimittypes.StoreKey]), - app.GetSubspace(ratelimittypes.ModuleName), - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - app.BankKeeper, - app.IBCKeeper.ChannelKeeper, - app.IBCKeeper.ClientKeeper, - app.IBCKeeper.ChannelKeeper, // ICS4Wrapper - ) + app.RateLimitKeeper = ratelimitkeeper.NewKeeper( + appCodec, + app.AccountKeeper.AddressCodec(), + runtime.NewKVStoreService(keys[ratelimittypes.StoreKey]), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ClientKeeper, + app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) ``` ## Update the v1 wiring The v1 middleware no longer takes the wrapped app as an argument. Build the stack with `NewIBCStackBuilder`, which injects the underlying app and sets the ICS4 wrapper, replacing the ICS4 argument the old keeper constructor took. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - var transferStack ibcporttypes.IBCModule = transfer.NewIBCModule(app.TransferKeeper) - transferStack = ratelimit.NewIBCMiddleware(app.RateLimitKeeper, transferStack) + transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper) + transferStack. + Base(transfer.NewIBCModule(app.TransferKeeper)). + Next(ratelimiting.NewIBCMiddleware(app.RateLimitKeeper)) + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack.Build()) ``` This example shows a minimal stack. If the chain already runs other transfer middleware, such as packet forward, keep those layers as additional `.Next(...)` calls; only the rate-limiting layer changes. ## Update the v2 wiring The v2 constructor gains two required arguments, a write-acknowledgement wrapper and the v2 channel keeper, and takes the keeper by value. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - transferStackV2 := ratelimitv2.NewIBCMiddleware( - app.RateLimitKeeper, - transferv2.NewIBCModule(app.TransferKeeper), - ) + transferModuleV2 := ratelimitingv2.NewIBCMiddleware( + *app.RateLimitKeeper, + transferv2.NewIBCModule(app.TransferKeeper), + app.IBCKeeper.ChannelKeeperV2, + app.IBCKeeper.ChannelKeeperV2, + ) + ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferModuleV2) ``` ## Update the app module `NewAppModule` no longer takes the codec. ```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} - ratelimit.NewAppModule(appCodec, app.RateLimitKeeper) + ratelimiting.NewAppModule(app.RateLimitKeeper) ``` ## Add store upgrades If rate limiting is being added to a chain for the first time, you must [manually add store upgrades](/sdk/latest/guides/upgrades/upgrade#adding-new-modules-during-an-upgrade) for the new module and configure the store loader to apply them in `app.go`: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} if upgradeInfo.Name == "v11_2" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { storeUpgrades := store.StoreUpgrades{ Added: []string{ratelimittypes.StoreKey}, } app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } ``` This ensures the new module's stores are added to the multistore before the migrations begin. ## Verify the migration After the app compiles, confirm the module is active by querying the existing limits. ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ratelimiting list-rate-limits ``` ## Next steps * Confirm limits behave as expected using the [setting rate limits](/ibc/latest/middleware/rate-limit-middleware/setting-limits) queries. * Review the full wiring in [integrate the rate limiting middleware](/ibc/latest/middleware/rate-limit-middleware/integration). # Overview Source: https://docs.cosmos.network/ibc/latest/middleware/rate-limit-middleware/overview A governance-configured middleware that caps net token flow across IBC transfers. Rate limiting is a governance-configured middleware that caps net token flow across IBC transfers, protecting a chain from draining faster than governance allows. It sits on top of the ICS-20 fungible token transfer application. It measures how much of a token moves over a given path, in each direction, within a rolling time window. When a transfer would push the running total past its configured cap, the middleware rejects it. To wire it into a chain, see [integrate the rate limiting middleware](/ibc/latest/middleware/rate-limit-middleware/integration). To configure limits through governance, see [configure rate limits](/ibc/latest/middleware/rate-limit-middleware/setting-limits). ## How it works A rate limit is a ceiling on how much value can leave or enter a chain over one path in one window. When tokens start moving abnormally fast, the limit throttles the flow without freezing normal transfer activity, buying governance time to respond. The middleware applies to ICS-20 transfers only. Limits are defined and configured independently per pair of denom and path. A path is identified by a channel ID (IBC v1) or a client ID (IBC v2). Flow limits are set by chain governance. ### Rate limiting and transfers The middleware wraps the transfer application. Every transfer passes through its accounting before reaching core IBC on the way out or the application on the way in. On a typical chain, the outbound path runs from the transfer module, up through any packet forward middleware, through rate limiting, and finally to the IBC core that sends the packet. The inbound path runs in reverse. ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram participant Core as IBC core participant RL as Rate limiting participant App as Transfer app Note over Core,App: Outbound (send) App->>RL: SendPacket RL->>RL: check and record outflow RL->>Core: SendPacket (if allowed) Note over Core,App: Inbound (receive) Core->>RL: OnRecvPacket RL->>RL: check and record inflow RL->>App: OnRecvPacket (if allowed) ``` The middleware acts on four points in a packet's life: * It checks the outflow when a packet is sent. * It checks the inflow when a packet is received. * It reverts a recorded outflow when a sent packet fails. * It reverts a recorded outflow when a sent packet times out. A blocked outbound transfer fails at the source, so the sender's tokens are never escrowed. A blocked inbound transfer returns an error acknowledgement, so the sending chain refunds its user. ### Quotas, flows, and channel value Three pieces decide every transfer: * Quota: the outbound cap, the inbound cap, and the window length. * Flow: the inflow so far, the outflow so far, and the channel value for this window. * Channel value: the total on-chain supply of the denom, read from the bank module. It is the denominator that turns a percentage cap into an absolute amount. The caps are percentages of the channel value. The middleware reads the channel value when the limit is created and again at each window reset, not on every packet. A single window therefore measures flow against a fixed supply figure. Governance configures the quota fields directly: ```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Quota struct { MaxPercentSend math.Int // outbound cap as a percent of channel value MaxPercentRecv math.Int // inbound cap as a percent of channel value DurationHours uint64 // window length before the flow resets } ``` The middleware measures net flow, not gross flow: * Net outflow is the outflow minus the inflow plus the new amount. * Net inflow is the inflow minus the outflow plus the new amount. Transfers in the opposite direction free up headroom, so balanced traffic does not throttle a chain that both sends and receives a token. A transfer is denied when its net flow is strictly greater than the threshold. The threshold is the channel value times the cap percent, divided by 100. Consider a denom with a total supply of 1,000,000 and a `MaxPercentSend` of 10. The outbound threshold is 100,000, which is 10% of supply. Suppose the current window has recorded 60,000 of outflow and 20,000 of inflow. A new outbound transfer is allowed only if the net outflow stays at or below 100,000: | New send | Net outflow (60,000 - 20,000 + send) | Result | | -------- | ------------------------------------ | ----------------------- | | 70,000 | 110,000 | Denied, exceeds 100,000 | | 50,000 | 90,000 | Allowed | ### Rolling windows and resets A flow accumulates within a fixed window and returns to zero when the window elapses. The window length is the quota's duration in hours. The module tracks time as an hourly epoch that advances once per block, and each limit resets when the elapsed epoch count is a multiple of its duration. A limit with a 24-hour duration therefore resets once a day. A reset does three things: * It zeroes the inflow and outflow. * It re-reads the channel value from current supply, so the caps track the token's supply as it changes. * It clears the record of packets still in flight for that path. ### Reversal on failed acknowledgements and timeouts An outbound transfer counts against the flow the moment it is sent, before the receiving chain has confirmed it. That count is provisional: * If the packet is acknowledged successfully, the outflow stands. * If the acknowledgement carries an error or the packet times out, the middleware reverts the outflow so a failed transfer does not consume the limit. The reversal is conditional on the window. The middleware reverts an outflow only if the packet was sent during the window that is still open. A packet sent in an earlier window that has already reset is not reverted, because that window's totals are already gone. ### Whitelists and blacklists Two lists override the quota logic at the edges: * A whitelist entry is a specific sender and receiver address pair. Transfers between a whitelisted pair skip rate limiting entirely and do not update the flow. This suits trusted or protocol-controlled routes. * A blacklist entry is a denom. Any transfer of a blacklisted denom is rejected outright, regardless of quota. The module evaluates a transfer in a fixed order: 1. It rejects a blacklisted denom. 2. It allows any path that has no limit configured. 3. It exempts a whitelisted address pair. 4. It applies the quota. Whitelist and blacklist entries are set in genesis rather than through governance messages. To configure them, see [configure rate limits](/ibc/latest/middleware/rate-limit-middleware/setting-limits). ## Next steps * [Integrate the rate limiting middleware](/ibc/latest/middleware/rate-limit-middleware/integration) into a chain for IBC v1 and v2. * [Configure rate limits](/ibc/latest/middleware/rate-limit-middleware/setting-limits) through governance. # Setting Rate Limits Source: https://docs.cosmos.network/ibc/latest/middleware/rate-limit-middleware/setting-limits Configure rate limits through governance, and set whitelists and blacklists in genesis. This guide covers configuring limits on a chain that already has the middleware wired. Every change to a limit is a governance message, submitted inside [a Cosmos SDK governance proposal](https://docs.cosmos.network/sdk/latest/modules/gov/README), and it takes effect only if the proposal passes. This guide assumes familiarity with submitting and voting on proposals. ## The four governance messages The module accepts [four governance messages](https://github.com/cosmos/ibc-go/blob/2619d6ec8543b2e0cbc1a234dad38b4ae2299a27/proto/ibc/applications/rate_limiting/v1/tx.proto), each requiring the governance authority as the signer. Together they cover the full life of a limit: * `MsgAddRateLimit` creates a limit. * `MsgUpdateRateLimit` replaces a limit's quota and resets its flow. * `MsgRemoveRateLimit` deletes a limit. * `MsgResetRateLimit` keeps a limit but zeroes its flow and clears its in-flight packet records. Each field means: | Field | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------ | | `denom` | The token the limit applies to, as it appears on this chain. Use the `ibc/...` denom for a non-native token. | | `channel_or_client_id` | The channel ID under IBC v1, or the client ID under IBC v2, that the limit applies to. | | `max_percent_send` | The outbound cap, as a percent of channel value. For example, 10 means 10%. | | `max_percent_recv` | The inbound cap, as a percent of channel value. For example, 10 means 10%. | | `duration_hours` | The window length in hours before the flow resets. For example, 24 means daily. | `MsgAddRateLimit` and `MsgUpdateRateLimit` take all five fields. `MsgRemoveRateLimit` and `MsgResetRateLimit` take only `denom` and `channel_or_client_id`. Every message also carries `signer`, the governance authority. Fields are validated before a message is accepted: * `channel_or_client_id` is a channel in the form `channel-` or a valid client ID. * Each percentage is between 0 and 100. * At least one of send or receive is greater than zero. * `duration_hours` is greater than zero. ## Choose quota values The caps are percentages of the denom's total supply on the chain. A `max_percent_send` of 10 caps net outflow at 10 percent of supply per window. To limit only one direction, set the unused side to 0 and keep the other above zero. To set a daily limit, set `duration_hours` to 24. ## Whitelist and blacklist entries The module supports two overrides beyond quotas: * A whitelist exempts a specific sender and receiver address pair from rate limiting. * A blacklist blocks a denom outright. Neither has a governance message. Set both in the module's genesis state, under `app_state.ratelimit`, in the `whitelisted_address_pairs` and `blacklisted_denoms` fields. Change them only at chain launch or through a coordinated upgrade. ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "app_state": { "ratelimit": { "whitelisted_address_pairs": [ { "sender": "cosmos1sender...", "receiver": "cosmos1receiver..." } ], "blacklisted_denoms": ["uatom"] } } } ``` ## Verify a limit The module exposes query commands under the `ratelimiting` subcommand. Use them to confirm a proposal took effect. List every limit on the chain: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ratelimiting list-rate-limits ``` Query one limit by path and denom: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ratelimiting rate-limit [channel-or-client-id] --denom [denom] ``` Query the limits that target a given chain: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ratelimiting rate-limits-by-chain [chain-id] ``` Read the current whitelist: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ratelimiting list-whitelisted-addresses ``` Read the current blacklist: ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} simd query ratelimiting list-blacklisted-denoms ``` ## Next steps * Review how limits are evaluated in the [rate limiting overview](/ibc/latest/middleware/rate-limit-middleware/overview). # Security Audits Source: https://docs.cosmos.network/ibc/latest/security-audits Comprehensive security audit reports for IBC-Go protocol and features ## Overview The IBC-Go protocol has undergone multiple comprehensive security audits by leading blockchain security firms. These audits cover various components and features of the IBC protocol, ensuring robust security across all major functionality areas. Each audit provides an independent assessment of code quality, potential vulnerabilities, and architectural design. ## Available Audit Reports ### IBC v2 Protocol Audit **Auditor**: Collaborative Audit Team **Completion Date**: April 2025 **Pages**: 74 **Audited Commit**: `79218a531e769bb5c29022d50ef017bd81e4bd9b` **Scope**: IBC v2 protocol implementation This comprehensive audit covers the IBC v2 protocol implementation that simplifies the IBC protocol by removing channel and connection handshakes, minimizing the application interface, and enabling connectivity with new domains like Ethereum while maintaining backward compatibility with existing IBC channels. Complete security assessment of IBC v2 protocol implementation (74 pages) ### ICS-20 Token Transfer v2 **Auditor**: Atredis Partners **Completion Date**: September 2024 **Pages**: 41 **Features Covered**: * Multi-denomination support * Memo field enhancements * Forwarding middleware * Path unwinding capabilities Security assessment of ICS-20 v2 token transfer features (41 pages) ### Channel Upgrades **Auditor**: Atredis Partners **Completion Date**: March 2024 **Version**: Report v1.1 **Pages**: 38 **Features Covered**: * Channel upgrade handshakes * Timeout mechanisms * State machine verification * Upgrade cancellation logic Assessment of IBC channel upgrade functionality (38 pages) ### 08-WASM Light Client **Multiple Audits Available**: ##### Halborn Security Audit **Auditor**: Halborn **Completion Date**: February 2023 **Pages**: 55 **Focus**: WASM light client implementation security Halborn security assessment of WASM light client (55 pages) ##### Ethan Frey Review **Reviewer**: Ethan Frey **Type**: Technical Review **Focus**: WASM client architecture and implementation Technical review of WASM client implementation ### Interchain Accounts (ICS-27) **Auditor**: Trail of Bits **Pages**: 42 **Features Covered**: * Controller and host chain implementations * Authentication mechanisms * Message routing and execution * Security boundaries and access controls Trail of Bits assessment of Interchain Accounts (42 pages) ## Key Security Areas These audits collectively cover: ### Protocol Security * Core IBC protocol mechanics * Handshake protocols and state machines * Timeout and error handling * Proof verification systems ### Feature Security * Token transfer mechanisms * Cross-chain account control * Light client implementations * Channel upgrade procedures ### Implementation Security * Memory safety and resource management * Cryptographic operations * State consistency guarantees * Access control and permissions ## Recommendations for Developers When building with IBC-Go: 1. **Review Relevant Audits**: Consult the audit reports for features you're implementing 2. **Follow Security Patterns**: Adopt the security practices recommended in the audits 3. **Test Thoroughly**: Include security testing based on audit findings 4. **Stay Updated**: Monitor for security advisories and updates 5. **Report Vulnerabilities**: Follow responsible disclosure practices ## Continuous Security The IBC-Go team maintains an ongoing commitment to security through: * Regular audits of new features and major releases * Rapid response to security disclosures * Transparent communication via security advisories * Active collaboration with security researchers * Continuous improvement based on audit findings ## Security Disclosure For security-related inquiries or to report potential vulnerabilities, please follow the [IBC-Go Security Policy](https://github.com/cosmos/ibc-go/security/policy). ## Additional Resources * [IBC Protocol Specification](https://github.com/cosmos/ibc) * [IBC-Go GitHub Repository](https://github.com/cosmos/ibc-go) * [Security Best Practices](/ibc/latest/ibc/best-practices) # IBC v2 Source: https://docs.cosmos.network/ibc/latest/spec/IBC_V2/README ### Introduction IBC v2 is an end-to-end protocol for reliable, authenticated communication between modules on separate distributed ledgers. IBC makes NO assumptions about the consensus algorithm or the state machine. So long as the distributed ledger satisfies the minimal requirements in [ICS-24 Host Requirements](../core/ics-024-host-requirements/README), it can support the IBC v2 protocol and communicate across any application in the IBC v2 network. The IBC v2 protocol can be conceptualized in three distinct layers: **IBC CLIENTS**, **IBC CORE**, and **IBC APPS**. **IBC APPS** are the modules that wish to communicate with each other across different ledgers in the IBC v2 network. On example is ICS-20 fungible token transfer which facilitates sending tokens securely from one ledger to another by sending token packet data using **IBC CORE** and executing escrow/mint logic upon sending/receiving the ICS-20 token packet data from counterparty ICS20 applications. **IBC CLIENTS** identifies and verifies the state of the counterparty ledger. An **IBC CLIENT** is responsible for tracking updates to the state machine and verifying state against a given update. **IBC CORE** is the handler that implements the transport, authentication, and ordering semantics (hereafter `IBC/TAO`), it **uses** the **IBC CLIENT** to authenticate the packet and then sends the application packet data to the **IBC APP** which will handle the application data. Thus, each layer has a specific isolated responsibility. The **IBC CLIENT** only needs to verify key/value proofs of the counterparty state. The **IBC APP** only needs to process application data coming from a counterparty application. **IBC CORE** enables authenticated IBC packet flow of `SendPacket`, `RecvPacket`, `AcknowledgePacket`, `TimeoutPacket` for the **IBC APP** using the **IBC CLIENT** as a verification oracle. The goal of this document is to provide a basic overview of the IBC V2 protocol. Where appropriate, distinctions from IBC v1 will be highlighted. For a detailed specification of each layer please refer to the ICS-standards. ### Specification ### IBC Clients The IBC Client keeps track of counterparty state updates and exposes a verifier of the counterparty state to **IBC CORE**. An IBC client implementation achieves this through the use of two distinct structures: the `ClientState` and the `ConsensusState`. From the perspective of IBC, these are opaque bytes and are defined by the specific light client implementation. The `ClientState` is intended to encapsulate parameters of the counterparty consensus that SHOULD NOT change across heights, this can include a chain identifier and security parameters like a staking unbonding period. The `ConsensusState` on the other hand is a **view** or a snapshot of the counterparty consensus at a particular height. This **view** in almost all cases will be a highly compressed view of the counterparty consensus. The **IBC CLIENT** will not store the entire state of the counterparty chain, nor will it execute all transactions of the counterparty chain as this would be equivalent to hosting a full node. A common pattern is to have the counterparty Consensus to create a Merklized commitment of the counterparty state on each state update. The **IBC CLIENT** can add the merkle root hash to the `ConsensusState` and then verify membership/nonmembership proofs against the root hash in the stored consensus state. The **IBC CLIENT** encapsulates a particular security model, this can be anything from a multisign bridge committee to a fully verified light client of the counterparty consensus algorithm. It is up to users sending packets on the client to decide whether the security model is acceptable to them or not. The IBC client is responsible for taking an initial view of the counterparty consensus, and updating that view from this trusted point using the security model instantiated in the client. This initial counterparty consensus is trusted axiomatically. Thus, IBC does not have any **in-protocol** awareness of which chain a particular client is verifying. A user can inspect a client and verify for themselves that the client is tracking the chain that they care about (i.e. validating a specific consensus state matches the consensus output of the desired counterparty chain) and that the parameters of the security model encoded in the `ClientState` are satisfactory. A user need only verify the client once, either by themselves or through social consensus, once that initial trust is established; the **IBC CLIENT** MUST continue updating the view of the counterparty state from previously trusted views given the parameterized security model. Thus, once a user trusts a light client they can be guaranteed that the trust will not be violated by the client. If the security model is violated by counterparty consensus, the **IBC CLIENT** implementation MUST provide the ability to freeze the client and prevent further updates and verification. The evidence for this violation is called `Misbehaviour` and upon verification the client is frozen and all packet processing against the client is paused. Any damage already done cannot be automatically reverted in-protocol, however this mechanism ensures the attack is stopped as soon as possible so that an out-of-band recovery mechanism can intervene (e.g. governance). This recovery mechanism should ensure the consensus violation is corrected on the counterparty and any invalid state is reverted to the extent possible before resuming packet processing by unfreezing the client. The **IBC CLIENT** **must** have external endpoints for relayers (off-chain processes that have full-node access to other chains in the network) to initialize a client, update the client, and submit misbehaviour. The implementation of each of these endpoints will be specific to the particular consensus mechanism targeted. The choice of consensus algorithm itself is arbitrary, it may be a Proof-of-Stake algorithm like CometBFT, or a multisig of trusted authorities, or a rollup that relies on an additional underlying client in order to verify its consensus. However, a light client must have the ability to define finality for a given snapshot of the state machine, this may be either through single-slot finality or a finality gadget. Thus, the endpoints themselves should accept arbitrary bytes for the arguments passed into these client endpoints as it is up to each individual client implementation to unmarshal these bytes into the structures they expect. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // initializes client with a starting client state containing all light client parameters // and an initial consensus state that will act as a trusted seed from which to verify future headers function createClient( clientState: bytes, consensusState: bytes, ): (id: bytes, err: error) // once a client has been created, it can be referenced with the identifier and passed the header // to keep the client up-to-date. In most cases, this will cause a new consensus state derived from the header // to be stored in the client function updateClient( clientId: bytes, header: bytes, ): error // once a client has been created, relayers can submit misbehaviour that proves the counterparty chain violated the trust model. // The light client must verify the misbehaviour using the trust model of the consensus mechanism // and execute some custom logic such as freezing the client from accepting future updates and proof verification. function submitMisbehaviour( clientId: bytes, misbehaviour: bytes, ): error ``` As relayers keep the client up-to-date and add `ConsensusState`s to the client, **IBC CORE** will use the exposed verification endpoints: `VerifyMembership` and `VerifyNonMembership` to verify incoming packet-flow messages coming from the counterparty. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // verifies a membership of a path and value in the counterparty chain identified by the provided clientId // against a particular ConsensusState identified by the provided height function verifyMembership( clientId: bytes, height: Number, proof: bytes, path: CommitmentPath, value: bytes ): error // verifies the nonmembership of a path in the counterparty chain identified by the provided clientId // against a particular ConsensusState identified by the provided height function verifyNonMembership( cliendId: bytes, height: Number, proof: bytes, path: CommitmentPath ): error ``` ### Core IBC Functionality IBC in its essence is the ability for applications on different blockchains with different consensus mechanisms to communicate with each other through client backed security. Thus, IBC needs the client described above and the IBC applications that define the packet data they wish to send and receive. In addition to these layers, IBC v1 introduced the connection and channel abstractions to connect these two fundamental layers. IBC v2 intends to compress only the necessary aspects of connection and channel layers into a single packet handler with no handshakes but before doing this it is critical to understand what service they currently provide. Properties of IBC v1 Connection: * Verifies the validity of the counterparty client * Establishes a unique identifier on each side for a shared abstract understanding (the connection) * Establishes an agreement on the IBC version and supported features * Allows multiple connections to be built against the same client pair * Establishes the delay period so this security parameter can be instantiated differently for different connections against the same client pairing. * Defines which channel orderings are supported Properties of IBC v1 Channel: * Separates applications into dedicated 1-1 communication channels. This prevents applications from writing into each other's channels. * Allows applications to come to agreement on the application parameters (version negotiation). Ensures that each side can understand the other's communication and that they are running mutually compatible logic. This version negotiation is a multi-step process that allows the finalized version to differ substantially from the one initially proposed * Establishes the ordering of the channel * Establishes unique identifiers for the applications on either chain to use to reference each other when sending and receiving packets. * The application protocol can be continually upgraded over time by using the upgrade handshake which allows the same channel which may have accumulated state to use new mutually agreed upon application packet data format(s) and associated new logic. * Ensures exactly-once delivery of packet flow datagrams (Send, Receive, Acknowledge, Timeout) * Ensures valid packet flow (Send => Receive => Acknowledge) XOR (Send => Timeout) ### Identifying Counterparties In core IBC, the connection and channel handshakes serve to ensure the validity of counterparty clients, ensure the IBC and application versions are mutually compatible, as well as providing unique identifiers for each side to refer to the counterparty. Since we are removing handshakes in IBC V2, we must have a different way to provide the chain with knowledge of the counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us; we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace; but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. Thus the counterparty information we must have includes both its identifier for our chain as well as the key prefix under which it will write the provable ICS-24 paths. Thus, IBC V2 will introduce a new message RegisterCounterparty that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the RegisterCounterparty message is submitted to both sides correctly. Then both sides have mirrored \ pairs that can be treated as identifiers for the sender and receiver chains the packet is associated with. Assuming they are correct, the client on each side is unique and provides an authenticated stream of packet data between the two chains. If the RegisterCounterparty message submits the wrong clientID, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid \ pairs that represent a connection between the desired chains of the user; just as we rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain in IBC V1. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Counterparty { clientId: bytes counterpartyPrefix: []bytes } ``` This `Counterparty` will be keyed on the client identifier existing on our chain. Thus, both sides get access to each other's client identifier. This effectively creates a connection with unique identifiers on both sides that reference each other's consensus. Thus, the resulting `client, client` pairing in IBC V2 replaces the separate connection layer that existed in IBC V1. The `RegisterCounterparty` method allows for authentication that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. This is equivalent to the `validateSelfClient` logic performed in the connection handshake. A simpler but weaker authentication would simply be to check that the `RegisterCounterparty` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using identifiers to send a packet. In practice, this is verified by social consensus. ### IBC V2 Packet Processing IBC V2 will simply provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in [ICS-02](../core/ics-002-client-semantics/README) with application packet data being routed to their specific IBC applications with packet-flow semantics as specified in [ICS-04](../core/ics-004-channel-and-packet-semantics/README). The packet clientIDs as mentioned above will tell the IBC router which chain to send the packets to and which chain a received packet came from, while the portIDs in the payload specifies which application on the router the packet should be sent to. Thus, once two chains have set up clients for each other with specific Identifiers, they can send IBC packets like so. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Packet { sequence: uint64 timeoutTimestamp: uint64 sourceClientId: Identifier // identifier of the destination client on sender chain destClientId: Identifier // identifier of the sender client on the destination chain payload: []Payload } ``` Since the packets are addressed **directly** with the underlying light clients, there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct `` pair. Sending a packet with the wrong source client is equivalent to sending a packet with the wrong source channel. Sending a packet on a client with the wrong provided counterparty is an error and will cause the packet to be rejected. If the counterparty is set incorrectly for the new client, this is a misconfiguration in the IBC V2 setup process. Unexpected behavior may occur in this case, though it is expected that users validate the counterparty configurations on both sides are correct before sending packets using the client identifiers. This validation may be done directly or through social consensus. If the client and counterparty identifiers are setup correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If the counterparty is misconfigured, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out. The Payload contains all the application specific information. This includes the opaque application data that the sender application wishes to send to the receiving application; it also includes the `Encoding` and `Version` that should be used to decode and process the application data. Note that this is a departure from IBC V1 where this metadata about how to process the application data was negotiated in the channel handshake. Here, each packet carries the information about how its individual data should be processed. This allows the `Version` and `Encoding` to change from packet to packet; allowing applications to upgrade asynchronously and optimistically send new packet encodings and versions to their counterparties. If the counterparty application can support receiving the new payload, it will successfully be processed; otherwise the receive will simply error and the sending application reverts state upon receiving the `ErrorAcknowledgement`. This increases the possibility for errors to occur during an application's packet processing but massively increases the flexibility of IBC applications to upgrade and evolve over time. Similarly, the portIDs on the sender and receiver application are no longer prenegotiated in the channel handshake and instead are in the payload. Thus in IBC v2; a sending application can route its packet to ANY OTHER application on the receiving application by simply specifying its portID in the payload as a receiver. It is incumbent on applications to restrict which counterparty applications it wishes to communicate with by validating the source and destination portIDs provided in the payload. Thus, the per-packet `Payload` replaces the separate channel layer that existed in IBC V1. For more details on the Payload structure, see [ICS-04](../core/ics-004-channel-and-packet-semantics/README). ### Registering IBC applications on the router **IBC CORE** contains routers mapping reserved application portIDs to individual IBC applications as well as a mapping from clientIDs to individual IBC clients. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type IBCRouter struct { apps: portID -> IBCApp clients: clientId -> IBCClient } ``` ### Packet Flow For a detailed specification of the packet flow, please refer to [ICS-04](../core/ics-004-channel-and-packet-semantics/README). The packet-flow messages defined by IBC are: `SendPacket`, `ReceivePacket`, `AcknowledgePacket` and `TimeoutPacket`. `SendPacket` will most often be triggered by user-action that wants to initiate a cross-chain action (e.g. token transfer) by sending a packet from an application on the sender chain to an application on the destination chain. Every other message is the result of counterparty action, thus they must be submitted by an off-chain relayer that can submit a proof of counterparty that authenticates the message is valid. For example, the `RecvPacket` message can only be submitted if the relayer can prove that the counterparty did send a packet to our chain by submitting a proof to our on-chain client. The source chain commits the packet under the ICS-04 standardized commitment path which is constructed with `packet.sourceClientId` and `packet.sequence`. Since the `packet.sourceClientId` is a unique reference to the destination chain on the source chain, a packet commitment stored on this path is guaranteed to be a packet the source chain intends to send to the destination chain. The destination chain can verify this path using its on-chain client identified by `packet.destClientId` Similarly, `AcknowledgePacket` and `TimeoutPacket` are messages that get sent back to the sending chain after the an attempted packet receipt. If the packet receipt is successful, an application-specific acknowledgement will be written to the ICS-04 standardized acknowledgement path under the `packet.destClientId` and `packet.sequence`. The sending chain can verify that the relayer-provided acknowledgment was committed to by the receiving chain by verifying this path using the on-chain client identified by `packet.sourceClientId`. Since the `packet.destClientId` is a unique reference to the sending chain on the destination chain and the `sequence` is unique in the stream of packets from source chain to destination; we can be guaranteed that the acknowledgement was written for the packet we previously sent with the provided `sourceClientId` and `sequence`. This acknowledgement is then given to the sending application to perform appropriate application logic for the given acknowledgement. The `TimeoutPacket` is called if the packet receipt is unsuccessful. All compliant implementations must write a sentinel non-empty value into the standardized ICS-04 receipt path if it successfully receives a packet. This receipt path is constructed using the `packet.destClientId` and `packet.sequence`. Thus, if the value does not exist after the packet timeout has been passed, we can be guaranteed that the packet has timed out. The sending chain verifies a relayer-provided `NonMembership` proof for the receipt path of the given packet, if it succeeds then the timeout is verified and the timeout logic for the sending application is executed. Note the nonmembership proof MUST be verified against a consensus state that is executed past the timeout timestamp of the packet, and packet receiving MUST fail on the destination after the timeout has elapsed. This ensures that a packet cannot be timed out on the source chain and received on the destination simultaneously. Thus, the packet handler implements the handlers for these messages by constructing the necessary path and value to authenticate the message as specified in [ICS-04](../core/ics-004-channel-and-packet-semantics/README); it then routes the verification of the membership/nonmembership proof to the relevant [ICS-02](../core/ics-002-client-semantics/README) client as specified in the packet. If the IBC TAO checks succeed and the client verification succeeds; then the packet message is authenticated and the application data in the payload can be processed by the application as trusted data. The packet sequence ensures that the stream of packets from a source chain to destination chain are all uniquely identified and prevents replay attacks. More detailed specification of the IBC TAO checks and packet handler behaviour can be found in [ICS-04](../core/ics-004-channel-and-packet-semantics/README). ### Correctness Claim: If the clients are setup correctly, then a chain can always verify packet flow messages sent by a valid counterparty. If the clients are correct, then they can verify any key/value membership proof as well as a key non-membership proof. All packet flow message (SendPacket, RecvPacket, and TimeoutPacket) are sent with the full packet. The packet contains both sender and receiver identifiers. Thus on packet flow messages sent to the receiver (RecvPacket), we use the receiver identifier in the packet to retrieve our local client and the source identifier to determine which path the sender stored the packet under. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty. Similarly, for packet flow messages sent to the sender (AcknowledgePacket, TimeoutPacket); the packet is provided again. This time, we use the sender identifier to retrieve the local client and the destination identifier to determine the key path that the receiver must have written to when it received the packet. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty. In the case of timeout, if the packet receipt wasn't written to the receipt path determined by the destination identifier this can be verified by our retrieved client using the key nonmembership proof. ### Soundness Claim: If the clients are setup correctly, then a chain cannot mistake a packet flow message intended for a different chain as a valid message from a valid counterparty. We must note that client identifiers are unique to each chain but are not globally unique. Let us first consider a user that correctly specifies the source and destination identifiers in the packet. We wish to ensure that well-formed packets (i.e. packets with correctly setup client ids) cannot have packet flow messages succeed on third-party chains. Ill-formed packets (i.e. packets with invalid client ids) may in some cases complete in invalid states; however we must ensure that any completed state from these packets cannot mix with the state of other valid packets. We are guaranteed that the source identifier is unique on the source chain, the destination identifier is unique on the destination chain. Additionally, the destination identifier points to a valid client of the source chain, and the source identifier points to a valid client of the destination chain. Suppose the RecvPacket is sent to a chain other than the one identified by the sourceClient on the source chain. In the packet flow messages sent to the receiver (RecvPacket), the packet send is verified using the client on the destination chain (retrieved using destination identifier) with the packet commitment path derived by the source identifier. This verification check can only pass if the chain identified by the destination client committed the packet we received under the source client identifier. This is only possible if the destination client is pointing to the original source chain, or if it is pointing to a different chain that committed the exact same packet. Pointing to the original source chain would mean we sent the packet to the correct . Since the sender only sends packets intended for the destination chain by setting to a unique source identifier, we can be sure the packet was indeed intended for us. Since our client on the receiver is also correctly pointing to the sender chain, we are verifying the proof against a specific consensus algorithm that we assume to be honest. If the packet is committed to the wrong key path, then we will not accept the packet. Similarly, if the packet is committed by the wrong chain then we will not be able to verify correctly. # Client Semantics Source: https://docs.cosmos.network/ibc/latest/spec/IBC_V2/core/ics-002-client-semantics/README ## Synopsis The IBC protocol provides secure packet flow between applications on different ledgers by verifying the packet messages using clients of the counterparty state machines. While ICS-4 defines the core packet flow logic between two chains and the provable commitments they must make in order to communicate, this standard ICS-2 specifies **how** a chain verifies the IBC provable commitments of the counterparty which is crucial to securely receive and process a packet flow message arriving from the counterparty. This standard focuses on how to keep track of the counterparty consensus and verify the state machine; it also specifies the properties that consensus algorithms of state machines implementing the inter-blockchain communication (IBC) protocol are required to satisfy. These properties are necessary for efficient and safe verification in the higher-level protocol abstractions. The algorithm utilised in IBC to verify the state updates of a remote state machine is referred to as a *validity predicate*. Pairing a validity predicate with a trusted state (i.e., a state that the verifier assumes to be correct), implements the functionality of a *light client* (often shortened to *client*) for a remote state machine on the host state machine. In addition to state update verification, every light client is able to detect consensus misbehaviours through a *misbehaviour predicate*. Beyond the properties described in this specification, IBC does not impose any requirements on the internal operation of the state machines and their consensus algorithms. A state machine may consist of a single process signing operations with a private key (the so-called "solo machine"), a quorum of processes signing in unison, many processes operating a Byzantine fault-tolerant consensus algorithm (e.g., Tendermint), or other configurations yet to be invented — from the perspective of IBC, a state machine is defined entirely by its light client validation and misbehaviour detection logic. This standard also specifies how the light client's functionality is registered and how its data is stored and updated by the IBC protocol. The stored client instances can be introspected by a third party actor, such as a user inspecting the state of the state machine and deciding whether or not to send an IBC packet. ### Motivation The IBC protocol needs to be able to verify updates to the state of another state machine (i.e., the *remote state machine*). This entails accepting *only* the state updates that were agreed upon by the remote state machine's consensus algorithm. A light client of the remote state machine is the algorithm that enables the actor to verify state updates of that state machine. Note that light clients will generally not include validation of the entire state transition logic (as that would be equivalent to simply executing the other state machine), but may elect to validate parts of state transitions in particular cases. This standard formalises the light client model and requirements. As a result, the IBC protocol can easily be integrated with new state machines running new consensus algorithms, as long as the necessary light client algorithms fulfilling the listed requirements are provided. The IBC protocol can be used to interact with probabilistic-finality consensus algorithms. In such cases, different validity predicates may be required by different applications. For probabilistic-finality consensus, a validity predicate is defined by a finality threshold (e.g., the threshold defines how many block needs to be on top of a block in order to consider it finalized). As a result, clients could act as *thresholding views* of other clients: One *write-only* client could be used to store state updates (without the ability to verify them), while many *read-only* clients with different finality thresholds (confirmation depths after which state updates are considered final) are used to verify state updates. Client interfaces should also be constructed so that custom validation logic can be provided safely to define a custom client at runtime, as long as the underlying state machine can provide an appropriate gas metering mechanism to charge for compute and storage. On a host state machine which supports WASM execution, for example, the validity predicate and misbehaviour predicate could be provided as executable WASM functions when the client instance is created. ### Definitions * `Consensus` is a state update generating algorithm. It takes the previous state of a state machine together with a set of messages (i.e., state machine transactions) and generates a valid state update of the state machine. Every state machine MUST have a `Consensus` that generates a unique, ordered list of state updates starting from a genesis state. This specification expects that the state updates generated by `Consensus` satisfy the following properties: * Every state update MUST NOT have more than one direct successor in the list of state updates. In other words, the state machine MUST guarantee *finality* and *safety*. * Every state update MUST eventually have a successor in the list of state updates. In other words, the state machine MUST guarantee *liveness*. * Every state update MUST be valid (i.e., valid state transitions). In other words, `Consensus` MUST be *honest*, e.g., in the case `Consensus` is a Byzantine fault-tolerant consensus algorithm, such as Tendermint, less than a third of block producers MAY be Byzantine. Unless the state machine satisfies all of the above properties, the IBC protocol may not work as intended, e.g., users' assets might be stolen. Note that specific client types may require additional properties. * `Height` specifies the order of the state updates of a state machine, e.g., a sequence number. This entails that each state update is mapped to a `Height`. * `ClientMessage` is an arbitrary message defined by the client type that relayers can submit in order to update the client. The ClientMessage may be intended as a regular update which may add new consensus state for proof verification, or it may contain misbehaviour which should freeze the client. * `ValidityPredicate` is a function that validates a ClientMessage sent by a relayer in order to update the client. Using the `ValidityPredicate` SHOULD be more computationally efficient than executing `Consensus`. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ValidityPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number) => (newConsensusState: bytes, newHeight: Number, err: Error) ``` * `ConsensusState` is the *trusted view* of the state of a state machine at a particular `Height`. It MUST contain sufficient information to enable the `ValidityPredicate` to validate future state updates, which can then be used to generate new `ConsensusState`s. * `ClientState` is the state of a client. It MUST expose an interface to higher-level protocol abstractions, e.g., functions to verify proofs of the existence of particular values at particular paths at particular `Height`s. * `MisbehaviourPredicate` is a function that checks whether the rules of `Consensus` were broken, in which case the client MUST be *frozen*, i.e., no subsequent `ConsensusState`s can be generated. Verification against the client after it is frozen will also fail. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type MisbehaviourPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number, misbehaviour: bytes) => bool ``` * `Misbehaviour` is the proof needed by the `MisbehaviourPredicate` to determine whether a violation of the consensus protocol occurred. For example, in the case the state machine is a blockchain, a `Misbehaviour` might consist of two signed block headers with different `ConsensusState` for the same `Height`. ### Desired Properties Light clients MUST provide state verification functions that provide a secure way to verify the state of the remote state machines using the existing `ConsensusState`s. These state verification functions enable higher-level protocol abstractions to verify sub-components of the state of the remote state machines. `ValidityPredicate`s MUST reflect the behaviour of the remote state machine and its `Consensus`, i.e., `ValidityPredicate`s accept *only* state updates that contain state updates generated by the `Consensus` of the remote state machine. In case of misbehavior, the behaviour of the `ValidityPredicate` might differ from the behaviour of the remote state machine and its `Consensus` (since clients do not execute the `Consensus` of the remote state machine). In this case, a `Misbehaviour` SHOULD be submitted to the host state machine, which would result in the client being frozen. Once the client is frozen, a recovery mechanism to address the situation must occur before client processing can presume. This recovery mechanism is out-of-scope of the IBC protocol as the specific recovery needed is highly case-dependent. ## Technical Specification This specification outlines what each *client type* must define. A client type is a set of definitions of the data structures, initialisation logic, validity predicate, and misbehaviour predicate required to operate a light client. State machines implementing the IBC protocol can support any number of client types, and each client type can be instantiated with different initial consensus states in order to track different consensus instances. Specific client types and their specifications are defined in the light clients section of this repository. ### Data Structures #### `Height` `Height` is an opaque data structure defined by a client type. It must form a partially ordered set & provide operations for comparison. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type Height ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} enum Ord { LT EQ GT } type compare = (h1: Height, h2: Height) => Ord ``` A height is either `LT` (less than), `EQ` (equal to), or `GT` (greater than) another height. `>=`, `>`, `===`, `<`, `<=` are defined through the rest of this specification as aliases to `compare`. There must also be a zero-element for a height type, referred to as `0`, which is less than all non-zero heights. #### `ConsensusState` `ConsensusState` is an opaque data structure defined by a client type, used by the validity predicate to verify new commits & state roots. Likely the structure will contain the last commit produced by the consensus process, including signatures and validator set metadata. `ConsensusState` MUST be generated from an instance of `Consensus`, which assigns unique heights for each `ConsensusState` (such that each height has exactly one associated consensus state). There MUST NOT be two valid `ConensusState`s for the same height. Such an event is called an "equivocation" and MUST be classified as misbehaviour. Should one occur, a proof should be generated and submitted so that the client can be frozen and previous state roots invalidated as necessary. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ConsensusState = bytes ``` The `ConsensusState` MUST define a `getTimestamp()` method which returns the timestamp **in seconds** associated with that consensus state. This timestamp MUST be the timestamp used in the counterparty state machine and agreed to by `Consensus`. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type getTimestamp = ConsensusState => uint64 ``` #### `ClientState` `ClientState` is an opaque data structure defined by a client type. It may keep arbitrary internal state to track verified roots and past misbehaviours. Light clients are representation-opaque — different consensus algorithms can define different light client update algorithms — but they must expose this common set of query functions to the IBC handler. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ClientState = bytes ``` Client types MUST define a method to initialise a client state with the provided client identifier, client state and consensus state, writing to internal state as appropriate. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type initialise = (identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) => Void ``` Client types MUST define a method to fetch the current height (height of the most recent validated state update). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type latestClientHeight = ( clientState: ClientState) => Height ``` Client types MUST define a method on the client state to fetch the timestamp at a given height ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type getTimestampAtHeight = ( clientState: ClientState, height: Height ) => uint64 ``` #### `ClientMessage` A `ClientMessage` is an opaque data structure defined by a client type which provides information to update the client. `ClientMessage`s can be submitted to an associated client to add new `ConsensusState`(s) and/or update the `ClientState`. They likely contain a height, a proof, a commitment root, and possibly updates to the validity predicate. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type ClientMessage = bytes ``` #### `CommitmentProof` `CommitmentProof` is an opaque data structure defined by the client type. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type CommitmentProof = bytes ``` It is utilised to verify presence or absence of a particular key/value pair in state at a particular finalised height (necessarily associated with a particular commitment root). ### State verification Client types must define functions to authenticate internal state of the state machine which the client tracks. Internal implementation details may differ (for example, a loopback client could simply read directly from the state and require no proofs). `verifyMembership` is a generic proof verification method which verifies a proof of the existence of a value at a given `CommitmentPath` at the specified height. It MUST return an error if the verification is not successful. The caller is expected to construct the full `CommitmentPath` from a `CommitmentPrefix` and a standardized path (as defined in [ICS 4](../ics-004-packet-semantics/PACKET)). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type verifyMembership = ( clientState: ClientState, height: Height, proof: CommitmentProof, path: CommitmentPath, value: bytes) => Error ``` `verifyNonMembership` is a generic proof verification method which verifies a proof of absence of a given `CommitmentPath` at the specified height. It MUST return an error if the verification is not successful. The caller is expected to construct the full `CommitmentPath` from a `CommitmentPrefix` and a standardized path (as defined in [ICS 24](../ics-024-host-requirements/README#path-space)). Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. Thus in these special cases, the proof provided will be an Existence proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type verifyNonMembership = ( clientState: ClientState, height: Height, proof: CommitmentProof, path: CommitmentPath) => Error ``` #### Implementation strategies ##### Loopback A loopback client of a local state machine merely reads from the local state, to which it must have access. ##### Simple signatures A client of a solo state machine with a known public key checks signatures on messages sent by that local state machine, which are provided as the `Proof` parameter. The `height` parameter can be used as a replay protection nonce. Multi-signature or threshold signature schemes can also be used in such a fashion. ##### Proxy clients Proxy clients verify another (proxy) state machine's verification of the target state machine, by including in the proof first a proof of the client state on the proxy state machine, and then a secondary proof of the sub-state of the target state machine with respect to the client state on the proxy state machine. This allows the proxy client to avoid storing and tracking the consensus state of the target state machine itself, at the cost of adding security assumptions of proxy state machine correctness. ##### Merklized state trees For clients of state machines with Merklized state trees, these functions can be implemented as MerkleTree Existence and NonExistence proofs. Client implementations may choose to implement these methods for the specific tree used by the counterparty chain or they can use the tree-generic [ICS-23](https://github.com/cosmos/ics23) `verifyMembership` or `verifyNonMembership` methods, using a verified Merkle root stored in the `ClientState`, to verify presence or absence of particular key/value pairs in state at particular heights for any ICS-23 compliant tree given a ProofSpec that describes how the tree is constructed. In this case, the ICS-23 `ProofSpec` MUST be provided to the client on initialization. ### Sub-protocols IBC handlers MUST implement the functions defined below. #### Identifier validation Clients are stored under a unique `Identifier` prefix. This ICS does not require that client identifiers be generated in a particular manner, only that they be unique. However, it is possible to restrict the space of `Identifier`s if required. The validation function `validateClientIdentifier` MAY be provided. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type validateClientIdentifier = (id: Identifier) => boolean ``` If not provided, the default `validateClientIdentifier` will always return `true`. ##### Utilising past roots To avoid race conditions between client updates (which change the state root) and proof-carrying transactions in handshakes or packet receipt, many IBC handler functions allow the caller to specify a particular past root to reference, which is looked up by height. IBC handler functions which do this must ensure that they also perform any requisite checks on the height passed in by the caller to ensure logical correctness. #### CreateClient Calling `createClient` with the client state and initial consensus state creates a new client. The intiator of this client is responsible for setting all of the initial parameters of the `ClientState` and the initial root-of-trust `ConsensusState`. The client implementation is then responsible for executing the light client `ValidityPredicate` against these initial parameters. Thus, once a root-of-trust is instantiated; the light client guarantees to preserve that trust within the confines of the security model as parameterized by the `ClientState`. If a user verifies that a client is a valid client of the counterparty chain once, they can be guaranteed that it will remain a valid client into the future so long as the `MisbehaviourPredicate` is not triggered. If the `MisbehaviourPredicate` is triggered however, this can be submitted as misbehaviour to freeze the IBC light client operations. CreateClient Inputs: `clientType: string`: This is the client-type that references a particular light client implementation on the chain. The `CreateClient` message will create a new instance of the given client-type. `ClientState: bytes`: This is the opaque client state as defined for the given client type. It will contain any parameters needed for verifying client updates and proof verification against a `ConsensusState`. The `ClientState` parameterizes the security model as implemented by the client type. `ConsensusState: bytes`: This is the opaque consensus state as defined for the given client type. It is the initial consensus state provided and MUST be capable of being used by the `ValidityPredicate` to add new `ConsensusState`s to the client. The initial `ConsensusState` MAY also be used for proof verification but it is not necessary. `Height: Number`: This is the height that is associated with the initial consensus state. CreateClient Preconditions: * The provided `clientType` is supported by the chain and can be routed to by the IBC handler. CreateClient PostConditions: * A unique identifier `clientId` is generated for the client * The provided `ClientState` is persisted to state and retrievable given the `clientId`. * The provided `ConsensusState` is persisted to state and retrievable given the `clientId` and `height`. CreateClient ErrorConditions: * The provided `ClientState` is invalid given the client type. * The provided `ConsensusState` is invalid given the client type. * The `Height` is not a positive number. #### RegisterCounterparty IBC Version 2 introduces a `registerCounterparty` procedure. Calling `registerCounterparty` with the clientId will register the counterparty clientId that the counterparty will use to write packet messages intended for our chain. All ICS24 provable paths to our chain will be keyed on the counterparty clientId, so each client must be aware of the counterparty's identifier in order to construct the path for key verification and ensure there is an authenticated stream of packet data between the clients that do not get written to by other clients. The `registerCounterparty` also includes the `CommitmentPrefix` to use for the counterparty chain. Most chains will not store the ICS24 directly under the root of a MerkleTree and will instead store the standardized paths under a custom prefix, thus the counterparty client must be given this information to verify proofs correctly. The `CommitmentPrefix` is defined as an array of byte arrays to support nested Merkle trees. In this case, each element of the outer array is a key for each tree in the nested structure ordered from the top-most tree to the lowest level tree. In this case, the ICS24 path is appended to the key of the lowest-level tree (i.e. the last element of the commitment prefix) in order to get the full `CommitmentPath` for proof verification. RegisterCounterparty Inputs: `clientId: bytes`: The clientId on the executing chain. `counterpartyClientId: bytes`: The identifier of the client used by the counterparty chain to verify the executing chain. `counterpartyCommitmentPrefix: []bytes`: The prefix used by the counterparty chain. RegisterCounterparty Preconditions: * A client has already been created for the `clientId` RegisterCounterparty Postconditions: * The `counterpartyClientId` is retrievable given the `clientId`. * The `counterpartyCommitmentPrefix` is retrievable given the `clientId`. RegisterCounterparty ErrorConditions: * There does not exist a client for the given `clientId` * `RegisterCounterparty` has already been called for the given `clientId` NOTE: Once the clients and counterparties have been registered on both sides, the connection between the clients is established and packet flow between the clients may commence. Users are expected to verify that the clients and counterparties are set correctly before using the connection to send packets. They may do this directly themselves or through social consensus. NOTE: `RegisterCounterparty` is setting information that will be crucial for proper proof verification of IBC messages using our client. Thus, it must be authenticated properly. The `RegisterCounterparty` message can be permissionless in which case the fields must be authenticated against the counterparty chain using the client which may prove difficult and cumbersome. It is RECOMMENDED to simply ensure that the client creator address is the same as the one that registers the counterparty. Once the client and counterparty are set by the same creator, users can decide if the configuration is secure out-of-band. #### Update Updating a client is done by submitting a new `ClientMessage`. The `Identifier` is used to point to the stored `ClientState` that the logic will update. When a new `ClientMessage` is verified using the `ValidityPredicate` with the stored `ClientState` and a previously stored `ConsensusState`, the client MUST then add a new `ConsensusState` with a new `Height`. If a client can no longer be updated (if, for example, the trusting period has passed), then new packet flow will not be able to be processed. Manual intervention must take place to reset the client state or migrate the client. This cannot safely be done completely automatically, but chains implementing IBC could elect to allow governance mechanisms to perform these actions (perhaps even per-client/connection/channel in a multi-sig or contract). UpdateClient Inputs: `clientId: bytes`: The identifier of the client being updated. `clientMessage: bytes`: The opaque clientMessage to update the client as defined by the given `clientType`. It MUST include the `trustedHeight` we wish to update from. This `trustedHeight` will be used to retrieve a trusted ConsensusState which we will use to update to a new consensus state using the `ValidityPredicate`. UpdateClient Preconditions: * A client has already been created for the `clientId` UpdateClient Postconditions: * A new `ConsensusState` is added to the client and persisted with a new `Height` * Implementations MAY automatically detect misbehaviour in `UpdateClient` if the update itself is proof of misbehaviour (e.g. There is already a different `ConsensusState` for the given height, or time monotonicity is broken). It is recommended to automatically freeze the client in this case to avoid having to send a redundant `submitMisbehaviour` message. UpdateClient ErrorConditions: * The trusted `ConsensusState` referenced in the `ClientMessage` does not exist in state * `ValidityPredicate(clientState, trustedConsensusState, trustedHeight)` returns an error #### Misbehaviour If `Consensus` of the counterparty chain is violated, then the relayer can submit proof of this as misbehaviour. Once the client is frozen, no updates may take place and all proof verification will fail. The client may be unfrozen by an out-of-band protocol once trust in the counterparty `Consensus` is restored and any invalid state caused by the break in `Consensus` is reverted on the executing chain. SubmitMisbehaviour Inputs: `clientId: bytes`: The identifier of the client being frozen. `clientMessage: bytes`: The opaque clientMessage to freeze the client as defined by the given `clientType`. It MUST include the `trustedHeight` we wish to verify misbehaviour from. This `trustedHeight` will be used to retrieve a trusted ConsensusState which we will use to freeze the client given the `MisbehaviourPredicate`. It MUST also include the misbehaviour being submitted. SubmitMisbehaviour Preconditions: * A client has already been created for the `clientId`. SubmitMisbehaviour Postconditions: * The client is frozen, update and proof verification will fail until client is unfrozen again. SubmitMisbehaviour ErrorConditions: * The trusted `ConsensusState` referenced in the `ClientMessage` does not exist in state. * `MisbehaviourPredicate(clientState, trustedConsensusState, trustedHeight, misbehaviour)` returns `false`. ### VerifyMembership and VerifyNonmembership The IBC core packet handler uses the consensus states created in `UpdateClient` to verify ICS-4 standardized paths to authenticate packet messages. In order to do this, the IBC packet handler constructs the expected key/value for the given packet flow message and sends the expected path and value to the client along with the relayer-provided proof to the client for verification. Note that the proof is relayer provided, but the path and value are constructed by the IBC packet handler for the given packet. Thus, the relayer cannot forge proofs for packets that did not get sent. IBC Packet handler must also have the ability to prove nonmembership of a given path in order to enable timeout processing. Thus, clients must expose the following `verifyMembership` and `verifyNonMembership` methods: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type verifyMembership = (ClientState, Height, CommitmentProof, Path, Value) => boolean ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type verifyNonMembership = (ClientState, Height, CommitmentProof, Path) => boolean ``` ProofVerification Inputs: * `clientId: bytes`: The identifier of the client that will verify the proof. * `Height: Number`: The height for the consensus state that the proof will be verified against. * `Path: CommitmentPath`: The path of the key being proven. In the IBC protocol, this will be an ICS24 standardized path prefixed by the `CommitmentPrefix` registered on the counterparty. The `Path` MUST be constructed by the IBC handler given the IBC message, it MUST NOT be provided by the relayer as the relayer is untrusted. * `Value: Optional`: The value being proven. If it is non-empty this is a membership proof. If the value is nil, this is a non-membership proof. ProofVerification Preconditions: * A client has already been created for the `clientId`. * A `ConsensusState` is stored for the given `Height`. ProofVerification Postconditions: * Proof verification should be stateless in most cases. In the case that the proof verification is a signature check, we may wish to increment a nonce to prevent replay attacks. ProofVerification Errorconditions: * `CommitmentProof` does not successfully verify with the provided `CommitmentPath` and `Value` with the retrieved `ConsensusState` for the provided `Height`. ### Properties & Invariants * Client identifiers are immutable & first-come-first-serve. Clients cannot be deleted (allowing deletion would potentially allow future replay of past packets if identifiers were re-used). ## Backwards Compatibility Not applicable. ## Forwards Compatibility New client types can be added by IBC implementations at-will as long as they conform to this interface. ## Example Implementations Please see the ibc-go implementations of light clients for examples of how to implement your own: ([https://github.com/cosmos/ibc-go/blob/main/modules/light-clients](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients)). ## History Mar 5, 2019 - Initial draft finished and submitted as a PR May 29, 2019 - Various revisions, notably multiple commitment-roots Aug 15, 2019 - Major rework for clarity around client interface Jan 13, 2020 - Revisions for client type separation & path alterations Jan 26, 2020 - Addition of query interface Jul 27, 2022 - Addition of `verifyClientState` function, and move `ClientState` to the `provableStore` August 4, 2022 - Changes to ClientState interface and associated handler to align with changes in 02-client-refactor ADR: ([https://github.com/cosmos/ibc-go/pull/1871](https://github.com/cosmos/ibc-go/pull/1871)) August 22, 2024 - [Changes for IBC/TAO V2](https://github.com/cosmos/ibc/pull/1147) ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # Packet Structure and Provable Commitment Specification Source: https://docs.cosmos.network/ibc/latest/spec/IBC_V2/core/ics-004-packet-semantics/PACKET ## Packet V2 Structure The IBC packet sends application data from a source chain to a destination chain with a timeout that specifies when the packet is no longer valid. The packet will be committed to by the source chain as specified in the ICS-24 specification. The receiver chain will then verify the packet commitment under the ICS-24 specified packet commitment path. If the proof succeeds, the IBC handler sends the application data(s) to the relevant application(s). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Packet { // identifier for the destination-chain client existing on source chain sourceClientId: bytes, // identifier for the source-chain client existing on destination chain destClientId: bytes, // the sequence uniquely identifies this packet // in the stream of packets from source to dest chain sequence: uint64, // the timeout is the timestamp in seconds on the destination chain // at which point the packet is no longer valid. // It cannot be received on the destination chain and can // be timed out on the source chain timeout: uint64, // the data includes the messages that are intended // to be sent to application(s) on the destination chain // from application(s) on the source chain // IBC core handlers will route the payload to the desired // application using the port identifiers but the rest of the // payload will be processed by the application data: [Payload] } interface Payload { // sourcePort identifies the sending application on the source chain sourcePort: bytes, // destPort identifies the receiving application on the dest chain destPort: bytes, // version identifies the version that sending application // expects destination chain to use in processing the message // if dest chain does not support the version, the payload must // be rejected with an error acknowledgement version: string, // encoding allows the sending application to specify which // encoding was used to encode the app data // the receiving applicaton will decode the appData into // the strucure expected given the version provided // if the encoding is not supported, receiving application // must be rejected with an error acknowledgement. // the encoding string MUST be in MIME format encoding: string, // appData is the opaque content sent from the source application // to the dest application. It will be decoded and interpreted // as specified by the version and encoding fields appData: bytes, } ``` The source and destination client identifiers at the top-level of the packet identify the chains communicating. The `sourceClientId` identifier **must** be unique on the source chain and is a pointer to the destination chain client on the source chain. The `destClientId` identifier **must** be a unique identifier on the destination chain and is a pointer to the source chain client on the destination chain. The sequence is a monotonically incrementing nonce to uniquely identify packets sent between the source and destination chain. The timeout is the UNIX timestamp in seconds that must be passed on the **destination** chain before the packet is invalid and no longer capable of being received. Note that the timeout timestamp is assessed against the destination chain's clock which may drift relative to the clocks of the sender chain or a third party observer. If a packet is received on the destination chain after the timeout timestamp has passed relative to the destination chain's clock; the packet must be rejected so that it can be safely timed out and reverted by the sender chain. In version 2 of the IBC specification, implementations **MAY** support multiple application data within the same packet. This can be represented by a list of payloads. Implementations may choose to only support a single payload per packet, in which case they can just reject incoming packets sent with multiple payloads. Each payload will include its own `Encoding` and `AppVersion` that will be sent to the application to instruct it how to decode and interpret the opaque application data. The application must be able to support the provided `Encoding` and `AppVersion` in order to process the `AppData`. If the receiving application does not support the encoding or app version, then the application **must** return an error to IBC core. If the receiving application does support the provided encoding and app version, then the application must decode the application as specified by the `Encoding` string and then process the application as expected by the counterparty given the agreed-upon app version. Since the `Encoding` and `AppVersion` are now in each packet they can be changed on a per-packet basis and an application can simultaneously support many encodings and app versions from a counterparty. This is in stark contrast to IBC version 1 where the channel prenegotiated the channel version (which implicitly negotiates the encoding as well); so that changing the app version after channel opening is very difficult. All implementations must commit the packet in the standardized IBC commitment format to satisfy the protocol. In order to do this we must first commit the packet data and timeout. The timeout is encoded in LittleEndian format. The packet data which is a list of payloads is committed to by hashing each individual field of the payload and successively concatenating them together. This ensures a standard unambigious commitment for a given packet. Thus a given packet will always create the exact same commitment by all compliant implementations and two different packets will never create the same commitment by a compliant implementation. This commitment value is then stored under the standardized provable packet commitment key as defined below: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func packetCommitmentPath(packet: Packet): bytes { return packet.sourceClientId + byte(0x01) + bigEndian(packet.sequence) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // commitPayload hashes all the fields of the packet data to create a standard size // preimage before committing it in the packet. func commitPayload(payload: Payload): bytes { buffer = sha256.Hash(payload.sourcePort) buffer = append(sha256.Hash(payload.destPort)) buffer = append(sha256.Hash(payload.version)) buffer = append(sha256.Hash(payload.encoding)) buffer = append(sha256.Hash(payload.appData)) return sha256.Hash(buffer) } // commitV2Packet commits to all fields in the packet // by hashing each individual field and then hashing these fields together // Note: SourceClient and the sequence are omitted since they will be included in the key // Every other field of the packet is committed to in the packet which will be stored in the // packet commitment value // The final preimage will be prepended by the byte 0x02 before hashing in order to clearly define the protocol version // and allow for future upgradability func commitV2Packet(packet: Packet) { timeoutBytes = LittleEndian(packet.timeout) var appBytes: bytes for p in packet.payload { appBytes = append(appBytes, commitPayload(p)) } buffer = sha256.Hash(packet.destClient) buffer = append(buffer, sha256.hash(timeoutBytes)) buffer = append(buffer, sha256.hash(appBytes)) buffer = append([]byte{0x02}, buffer) return sha256.Hash(buffer) } ``` ## Acknowledgement V2 The acknowledgement in the version 2 specification is also modified to support multiple payloads in the packet that will each go to separate applications that can write their own acknowledgements. Each acknowledgment will be contained within the final packet acknowledgment in the same order that they were received in the original packet. Thus if a packet contains payloads for modules `A` and `B` in that order; the receiver will write an acknowledgment with the app acknowledgements `A` and `B` in the same order. The acknowledgement which is itself a list of app acknowledgement bytes must be committed to by hashing each individual acknowledgement and concatenating them together and hashing the result. This ensures that all compliant implementations reach the same acknowledgment commitment and that two different acknowledgements never create the same commitment. An application may not need to return an acknowledgment. In this case, it may return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT` which will be the single byte in the byte array: `bytes(0x01)`. In this case, the IBC `acknowledgePacket` handler will still do the core IBC acknowledgment logic but it will not call the application's acknowledgePacket callback. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Acknowledgement { // Each app in the payload will have an acknowledgment in this list in the same order // that they were received in the payload // If an app does not need to send an acknowledgement, there must be a SENTINEL_ACKNOWLEDGEMENT // in its place // The app acknowledgement must be encoded in the same manner specified in the payload it received // and must be created and processed in the manner expected by the version specified in the payload. appAcknowledgement: [bytes] } ``` All acknowledgements must be committed to and stored under the standardized acknowledgment path. Note that since each acknowledgement is associated with a given received packet, the acnowledgement path is constructed using the packet `destClientId` and its `sequence` to generate a unique key for the acknowledgement. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func acknowledgementPath(packet: Packet) { return packet.destClientId + byte(0x02) + bigEndian(packet.Sequence) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // commitV2Acknowledgement hashes each app acknowledgment and hashes them together // the final preimage will be prepended with the byte 0x02 before hashing in order to clearly define the protocol version // and allow for future upgradability func commitV2Acknowledgment(ack: Acknowledgement) { var buffer: bytes for appAck in ack.appAcknowledgement { buffer = append(buffer, sha256.Hash(appAck)) } buffer = append([]byte{0x02}, buffer) return sha256.Hash(buffer) } ``` ## Packet Receipt V2 A packet receipt will only tell the sending chain that the counterparty has successfully received the packet. Thus we just need a provable boolean flag uniquely associated with the sent packet. Thus, the receiver chain stores the packet receipt keyed on the destination identifier and the sequence to uniquely identify the packet. For chains that support nonexistence proofs of their own state, they can simply write a `SENTINEL_RECEIPT_VALUE` under the receipt path. This `SENTINEL_RECEIPT_PATH` can be any non-nil value so it is recommended to write a single byte. The receipt path is standardized as below. Similar to the acknowledgement, each receipt is associated with a given received packet the receipt path is constructed using the packet `destClientId` and its `sequence` to generate a unique key for the receipt. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} func receiptPath(packet: Packet) { return packet.destClientId + byte(0x03) + bigEndian(packet.Sequence) } ``` ## Provable Path-space IBC/TAO implementations MUST implement the following paths for the `provableStore` in the exact format specified. This is because counterparty IBC/TAO implementations will construct the paths according to this specification and send it to the light client to verify the IBC specified value stored under the IBC specified path. The `provableStore` is specified in [ICS24 Host Requirements](../ics-024-host-requirements/README) Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler. | Value | Path format | | -------------------------- | ----------- | | Packet Commitment | 0x1 | | Packet Receipt | 0x2 | | Acknowledgement Commitment | 0x3 | Note that the IBC protocol ensures that the packet `(sourceClientId, sequence)` tuple uniquely identifies a packet on the sending chain, and the `(destClientId, sequence)` tuple uniquely identifies a packet on the receiving chain. This property along with the byte separator between the client identifier and sequence in the standardized paths ensures that commitments, receipts, and acknowledgements are each written to different paths for the same packet. Thus, so long as the host requirements specified in ICS24 are respected; a provable key written to state by the IBC handler for a given packet will never be overwritten with a different value. This ensures secure and correct communication between chains in the IBC ecosystem. # IBC Packet Handler Source: https://docs.cosmos.network/ibc/latest/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER The packet handler specification defines the semantics and behavior that implementations must enforce in order to support IBC v2 protocol. ## Packet Structure A `Packet` in the interblockchain communication protocol is the primary interface by which applications will send data to counterparty applications on other chains. It is defined as follows: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Packet { sourceClientId: bytes // identifier of the client on the sending chain destClientId: bytes // identifier of the client on the receiving chain sequence: uint64 // unique number identifying this packet in the stream of packets from sourceClientId to destClientId timeoutTimestamp: uint64, // indicates the timeout as a UNIX timestamp in seconds. If the timeout timestamp is reached on destination chain, it is no longer receivable data: Payload[] // a list of payloads intended for applications on the receiving chain } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Payload { sourcePort: bytes, // identifier of the sending application on the sending chain destPort: bytes, // identifier of the receiving application on the receiving chain version: string, // payload version only interpretable by sending/receiving applications encoding: string, // payload encoding only interpretable by sending/receiving applications value: bytes // application-specific data that can be parsed by receiving application given the version and encoding } ``` The packet is never directly serialised and sent to counterparty chains. Instead a standardized non-malleable committment to the packet data is stored under the standardized unique key for the packet as defined in ICS-24. Thus, implementations MAY make individual choices on the exact packet structure and serialization scheme they use internally so long as they respect the standardized commitment defined by the IBC protocol when writing to the provable store. Packet Invariants: * None of the packet fields are allowed to be empty * For every payload included, none of the payload fields are allowed to be empty ## Receipt A `Receipt` is a sentinel byte that is stored under the standardized provable ReceiptPath of a given packet by the receiving chain when it successfully receives the packet. This prevents replay attacks and also the possibility of timing out a packet on the sender chain when the packet has already been received. The specific value of the receipt does not matter so long as its not empty. ## Acknowledgement Structure An `Acknowledgement` is the interface that will be used by receiving applications to return application specific information back to the sender. If every application successfully received its payload, then each receiving application will return their custom acknowledgement bytes which will be appended to the acknowledgement array. If **any** application returns an error, then the acknowledgement will have a single element with a sentinel error acknowledgement. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} const ErrorAcknowledgement = sha256("UNIVERSAL_ERROR_ACKNOWLEDGEMENT") interface Acknowledgement { appAcknowledgement bytes[] // array of an array of bytes. Each element of the array contains an acknowledgement from a specific application } ``` Acknowledgement Invariants: * If the acknowledgement interface includes an error acknowledgement then there must be only a single element in the array with the error acknowledgement * There CANNOT be multiple app acknowledgements where an element is the error acknowledgement * If there are multiple app acknowledgements, the length of the app acknowledgements is the same length as the payloads in the associated packet and each acknowledgement is associated with the payload in the same position in the payload array. ## SendPacket SendPacket is called by users to execute an inter-blockchain flow. The user submits a message with a payload(s) for each IBC application they wish to interact with. The SendPacket handler must call the sendPacket logic of each sending application as identified by the sourcePort of the payload. If none of the sending applications error, then the sendPacket handler must construct the packet with the user-provided sourceClient, payloads, and timeout and the destinationClient it retrieves from the counterparty storage given the sourceClient and a generated sequence that is unique for the sourceClientId. It will commit the packet with the ICS24 commitment function under the ICS24 path. The sending chain MAY store the ICS24 path under a custom prefix in the provable store. In this case, the counterparty must have knowledge of the custom prefix as provided by the relayer on setup. The sending chain SHOULD check the provided timestamp against an authenticated time oracle (local BFT time or destination client latest timestamp) and preemptively reject a user-provided packet with a timestamp that has already passed. The user may be an off-chain process or an on-chain actor. In either case, the user is not trusted by the IBC protocol. The IBC application is responsible for properly authenticating that the user is allowed to send the requested app data using the IBC application's port as specified in the source port of the payload. The IBC application is also responsible for executing any app-specific logic that must run before the IBC packet can be sent (e.g. escrowing user's tokens before sending a fungible token transfer packet). SendPacket Inputs: `payloads: Payload[]`: List of payloads that are to be sent from source applications on sending chain to corresponding destination applications on the receiving chain. Implementations MAY choose to only support a single payload per packet. `sourceClientId: bytes`: Identifier of the receiver chain client that exists on the sending chain. `timeoutTimestamp: uint64`: The timeout in UNIX seconds after which the packet is no longer receivable on the receiving chain. NOTE: This timestamp is evaluated against the **receiving chain** clock as there may be drift between the sending chain and receiving chain clocks SendPacket Preconditions: * A valid client exists on the sending chain with the `sourceClientId` * There exists a mapping on the sending chain from `sourceClientId` to `Counterparty` SendPacket Postconditions: * The sending application(s) as identified by the source port(s) in the payload(s) have all executed their sendPacket logic successfully * The following packet gets committed and stored under the packet commitment path as specified by ICS24: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Packet { sourceClientId: sourceClientId, destClientId: getCounterparty(sourceClientId).ClientId, // destClientId should be filled in with the registered counterparty id for provided sourceClientId sequence: generateUniqueSequence(sourceClientId), timeoutTimestamp: timeoutTimestamp data: payloads } ``` * Since the packet is committed to with a hash in-state, implementations must provide the packet fields for relayers to reconstruct. This can be emitted in an event system or stored in state as the full packet under an auxilliary key if the implementing platform does not have an event system. SendPacket Errorconditions: * Any of the sending applications returns an error during its sendPacket logic execution * The sending client is invalid (expired or frozen) SendPacket Invariants: * The sourceClientId MUST exist on the sending chain * The destClientId MUST be the registered counterparty of the sourceClientId on the sending chain * The sending chain MUST NOT have sent a previous packet with the same `sourceClientId` and `sequence` ## RecvPacket RecvPacket is called by relayers once a packet has been committed on the sender chain in order to process the packet on the receiving chain. Since the relayer is not trusted, the relayer must provide a proof that the sender chain had indeed committed the provided packet which will be verified against the `destClient` on the receiving chain. If the proof succeeds, and the packet passes replay and timeout checks; then each payload is sent to the receiving application as part of the receiving application callback. RecvPacket Inputs: `packet: Packet`: The packet sent from the sending chain to our chain `proof: bytes`: An opaque proof that will be sent to the destination client. The destination client is responsible for interpreting the bytes as a proof and verifying the packet commitment key/value provided by the packet handler against the provided proof. `proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the destination client for the proof to verify correctly. RecvPacket Preconditions: * A valid client exists on the receiving chain with `destClientId` * There exists a mapping from `destClientId` to `Counterparty` RecvPacket Postconditions: * A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence` * All receiving application(s) as identified by the destPort(s) in the payload(s) have executed their recvPacket logic. If **any** of the payloads return an error during processing, then all application state changes for all payloads **must** be reverted. If all payloads are processed successfully, then all applications state changes are written. This ensures atomic execution for the payloads batched together in a single packet. * If any payload returns an error, then the single `SENTINEL ERROR ACKNOWLEDGEMENT` is written using `WriteAcknowledgment`. If all payloads succeed and return an app-specific acknowledgement, then each app acknowledgement is included in the list of `AppAcknowledgement` in the final packet `Acknowledgement` in the **exact** order that their corresponding payloads were included in the packet. NOTE: It is possible for applications to process their payload asynchronously to the `RecvPacket` transaction execution. In this case, the IBC core handler **must** await all applications returning their individual application acknowledgements before writing the acknowledgement with app acknowledgements in the order of their corresponding payloads in the original packet **not** the order in which the applications return their asynchronous acknowledgements which may be different orders. IBC allows multiple payloads intended for the same application to be batched in the same packet. Thus, if an implementation wishes to support multiple payloads and asynchronous acknowledgements together, then there must be a way for core IBC to know which payload a particular acknowledgment is being written for. This may be done by providing the index of the payload list during `recvPacket` application callback, so that the application can return the same index when writing the acknowledgment so that it can be placed in the right order. Otherwise, implementations may simply block asynchronous acknowledgment support for multi-payload packets RecvPacket Errorconditions: * `Counterparty.ClientId` != `packet.sourceClientId` ensures that packet was sent by expected counterparty * `packet.TimeoutTimestamp` >= `chain.BlockTime()` ensures we cannot receive successfully if packet can be timed out on sending chain * Packet receipt does not already exist in state for the `destClientId` and `sequence`. This prevents replay attacks * Membership proof does not successfully verify ## WriteAcknowledgement WriteAcknowledgement Inputs: `destClientId: bytes`: Identifier of the sender chain client that exist on the receiving chain `sequence: uint64`: Unique sequence identifying the packet from sending chain to receiving chain `ack: Acknowledgement`: Acknowledgement collected by receiving chain from all receiving applications after they have returned their individual acknowledgement. If any individual application errors, the entire acknowledgement MUST have a single element with just the SENTINEL ERROR ACKNOWLEDGEMENT. If all applications successfully received, then every application must have its own acknowledgement set in the `Acknowledgement` in the same order that they existed in the payload of the sending packet. WriteAcknowledgement Preconditions: * A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence` * An acknowledgement for the `destClientId` and `sequence` has not already been written under the ICS24 path WriteAcknowledgement Postconditions: * The acknowledgement is committed and written to the acknowledgement path as specified in ICS24 * Since the acknowledgement is being hashed, the full acknowledgement fields should be made available for relayers to reconstruct. This can be emitted in an event system or stored in state as the full packet under an auxilliary key if the implementing platform does not have an event system. * Implementors SHOULD also emit the full packet again in `WriteAcknowledgement` since the sender chain is only expected to store the packet commitment and not the full packet; relayers are expected to pass the packet back to the sender chain to process the acknowledgement. Thus, in order to support stateless relayers it is helpful to re-emit the packet fields on `WriteAcknowledgement` so the relayer can reconstruct the packet. * If the acknowledgement is successful, then all receiving applications must have executed their recvPacket logic and written state * If the acknowledgement is unsuccessful (ie ERROR ACK), any state changes made by the receiving applications MUST all be reverted. This ensure atomic execution of the multi-payload packet. ## AcknowledgePacket AcknowledgePacket Inputs: `packet: Packet`: The packet that was originally sent by our chain `acknowledgement: Acknowledgement`: The acknowledgement written by the receiving chain for the packet `proof: bytes`: An opaque proof that will be sent to the source client. The source client is responsible for interpreting the proof and verifying it against the acknowledgement key/value provided by the packet handler. `proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the source client for the proof to verify correctly. AcknowledgePacket Preconditions: * A valid client exists on the sending chain with the `sourceClientId` * There exists a mapping on the sending chain from `sourceClientId` to `Counterparty` * A packet commitment has been stored under the ICS24 packet path with `sourceClientId` and `sequence` AcknowledgePacket Postconditions: * All sending applications execute the ackPacket logic with the payload and the individual acknowledgement for that payload or the universal `ErrorAcknowledgement`. * Stored commitment for the packet is deleted AcknowledgePacket Errorconditions: * `packet.destClient` != `counterparty.ClientId`. This should never happen if the second error condition is not true, since we constructed the packet correctly earlier * The packet provided by the relayer does not commit to the stored commitment we have stored for the `sourceClientId` and `sequence` * Membership proof of the acknowledgement commitment on the receiving chain as standardized by ICS24 does not verify * Any of the applications return an error during the `AcknowledgePacket` callback for their payload. Applications should generally not error on AcknowledgePacket. If this occurs, it is most likely a bug so the error should revert the transaction and allow for the bug to be patched before resubmitting the transaction. ## TimeoutPacket TimeoutPacket Inputs: `packet: Packet`: The packet that was originally sent by our chain `proof: bytes`: An opaque non-existence proof that will be sent to the source client. The source client is responsible for interpreting the proof and verifying it against the receipt key provided by the packet handler. `proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the source client for the proof to verify correctly. TimeoutPacket Preconditions: * A valid client exists on the sending chain with the `sourceClientId` * There exists a mapping on the sending chain from `sourceClientId` to `Counterparty` * A packet commitment has been stored under the ICS24 packet path with `sourceClientId` and `sequence` TimeoutPacket Postconditions: * All sending applications execute the timeoutPacket logic with the payload. * Stored commitment for the packet is deleted TimeoutPacket Errorconditions: * `packet.destClient` != `counterparty.ClientId`. This should never happen if the second error condition is not true, since we constructed the packet correctly earlier * The packet provided by the relayer does not commit to the stored commitment we have stored for the `sourceClientId` and `sequence` * Non-Membership proof of the packet receipt on the receiving chain as standardized by ICS24 does not verify * Any of the applications return an error during the `TimeoutPacket` callback for their payload. Applications should generally not error on TimeoutPacket. If this occurs, it is most likely a bug so the error should revert the transaction and allow for the bug to be patched before resubmitting the transaction. # Port Allocation Source: https://docs.cosmos.network/ibc/latest/spec/IBC_V2/core/ics-005-port-allocation/README ## Synopsis This standard specifies the port allocation system by which modules can bind to uniquely named ports allocated by the IBC handler. The port identifiers in the packet defines which application to route the packet callback to. The source portID is an identifier of the application sending the packet, thus it will also receive the `AcknowledgePacket` and `TimeoutPacket` callback. The destination portID is the identifier of the application receiving the packet and will receive the `ReceivePacket` callback. Modules may register multiple ports on a state machine and send from any of their registered ports to any arbitrary port on a remote state machine. Each port on a state machine must be mapped to a specific IBC module as defined by [ICS-26](../ics-026-application-callbacks/README). Thus the IBC application to portID mapping is one-to-many. NOTE: IBC v1 included a channel along with a channel handshake that explicitly associated a unique channel between two portIDs on counterparty chains. Thus, the portIDs on both sides were tightly coupled such that no other application other than the ones bound by the portIDs were allowed to send packets on the dedicated channel. IBC v2 removed the concept of a channel and all packet flow is between chains rather than being isolated module-module communication. Thus, an application on a sending chain is allowed to send a packet to ANY other application on a destination chain by identifying the application with the portIDs in the packet. Thus, it is now the responsibility of applications to restrict which applications are allowed to send packets to them by checking the portID in the callback and rejecting any packet that comes from an unauthorized application. ### Motivation The interblockchain communication protocol is designed to facilitate module-to-module communication, where modules are independent, possibly mutually distrusted, self-contained elements of code executing on sovereign ledgers. ## Technical Specification ### Registering a port The IBC handler MUST provide a way for applications to register their callbacks on a portID. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function registerPort(portId: Identifier, cbs: ICS26App) => void ``` RegisterPort Preconditions: * There is no other application that is registered on the port router for the given `portId`. RegisterPort Postconditions: * The ICS26 application is registered on the provided `portId`. * Any incoming packet flow message addressed to the `portId` is routed to the ICS26 application. Any outgoing packet flow message addressed by the `portId` MUST come from the ICS26 application ### Authenticating and Routing Packet Flow Messages Once an application is registered with a port, it is the port router's responsibility to properly route packet flow messages to the appropriate application identified by the portId in the payload. Similarly when the application sends packet flow messages to the port router, the router MUST ensure that the application is authenticated to send the packet flow message by checking if the payload portIDs are registered to the application. For packet flow messages on the packet sending chain (e.g. `SendPacket`, `AcknowledgePacket`, `TimeoutPacket`); the port router MUST do this authentication and routing using the packet payload's `sourcePortId`. For packet flow messages on the packet receiving chain (e.g. `RecvPacket` and optionally the asynchronous `WriteAcknowledgement`); the port router MUST do this authentication and routing using the packet payload's `destPortId`. [ICS-4](../ics-004-packet-semantics/PACKET_HANDLER) defines the packet flow messages and the expected behavior of their respected handlers. When the packet flow message arrives from the core ICS-4 handler to the application (e.g. `RecvPacket`, `AcknowledgePacket`, `TimeoutPacket`); then the portRouter acts as a router routing the message from the core handler to the ICS26 application. When the packet flow message arrives from the application to the core ICS-4 handler (e.g. `SendPacket`, or the optional `WriteAcknowledgement`); then the portRouter acts as an authenticator by checking that the calling application is registered as the owner of port they wish to send the message on before sending the message to the ICS-4 handler. NOTE: It is possible for implementations to change the order of execution flow so long as they still respect all the expected semantics and behavior defined in ICS-4. In this case, the port router's role as router or authenticator will change accordingly. # Host State Machine Requirements Source: https://docs.cosmos.network/ibc/latest/spec/IBC_V2/core/ics-024-host-requirements/README ## Synopsis This specification defines the minimal set of properties which must be fulfilled by a state machine hosting an implementation of the interblockchain communication protocol. IBC relies on a key-value provable store for cross-chain communication. In version 2 of the specification, the expected key-value storage will only be for the keys that are relevant for packet processing. ### Motivation IBC is designed to be a common standard which will be hosted by a variety of blockchains & state machines and must clearly define the requirements of the host. ### Definitions ### Desired Properties IBC should require as simple an interface from the underlying state machine as possible to maximise the ease of correct implementation. ## Technical Specification ### Module system The host state machine must support a module system, whereby self-contained, potentially mutually distrusted packages of code can safely execute on the same ledger, control how and when they allow other modules to communicate with them, and be identified and manipulated by a "master module" or execution environment. The IBC core handlers as defined in ICS-4 must have ### Paths, identifiers, separators An `Identifier` is a bytestring used as a key for an object stored in state, such as a packet commitment, acknowledgement, or receipt. Identifiers MUST be non-empty (of positive integer length). Identifiers MUST consist of characters in one of the following categories only: * Alphanumeric * `.`, `_`, `+`, `-`, `#` * `[`, `]`, `<`, `>` A `Path` is a bytestring used as the key for an object stored in state. Paths MUST contain only identifiers, constant bytestrings, and the separator `"/"`. Identifiers are not intended to be valuable resources — to prevent name squatting, minimum length requirements or pseudorandom generation MAY be implemented, but particular restrictions are not imposed by this specification. The separator `"/"` is used to separate and concatenate two identifiers or an identifier and a constant bytestring. Identifiers MUST NOT contain the `"/"` character, which prevents ambiguity. By default, identifiers have the following minimum and maximum lengths in characters: | Port identifier | Client identifier | | --------------- | ----------------- | | 2 - 128 | 2 - 64 | ### Key/value Store The host state machine MUST provide a key/value store interface with three functions that behave in the standard way: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type get = (path: Path) => Value | void ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type set = (path: Path, value: Value) => void ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type queryProof = (path: Path) => (CommitmentProof, Value) ``` `queryProof` will return a `Membership` proof if there exists a value for that path in the key/value store and a `NonMembership` proof if there is no value stored for the path. The host state machine SHOULD provide an interface for deleting a Path from the key/value store as well though it is not required: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type delete = (path: Path) => void ``` `Path` is as defined above. `Value` is an arbitrary bytestring encoding of a particular data structure. The specific Path and Values required to be written to the provable store are defined in [ICS-4](../ics-004-packet-semantics/PACKET). These functions MUST be permissioned to the IBC packet handler module (the implementation of which is described in [ICS-4](../ics-004-packet-semantics/PACKET_HANDLER)) only, so only the IBC handler module can `set` or `delete` the paths that can be read by `get`. In most cases, this will be implemented as a sub-store (prefixed key-space) of a larger key/value store used by the entire state machine. This is why ICS-2 defines a `counterpartyCommitmentPrefix` that is associated with the client. The IBC handler will prefix the `counterpartyCommitmentPrefix` to the ICS-4 standardized path before proof verification against a `ConsensusState` in the client. ### Provable Path-space IBC/TAO implementations MUST implement the following paths for the `provableStore` in the exact format specified. This is because counterparty IBC/TAO implementations will construct the paths according to this specification and send it to the light client to verify the IBC specified value stored under the IBC specified path. Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler. | Value | Path format | | -------------------------- | ----------- | | Packet Commitment | 0x1 | | Packet Receipt | 0x2 | | Acknowledgement Commitment | 0x3 | IBC V2 only proves commitments related to packet handling, thus the commitments and how to construct them are specifed in [ICS-4](../ics-004-packet-semantics/PACKET). As mentioned above, the provable path space controlled by the IBC handler may be prefixed in a global provable key/value store. In this case, the prefix must be appended by the IBC handler before the proof is verified. The provable store MUST be capable of providing `MembershipProof` for a key/value pair that exists in the store. It MUST also be capable of providing a `NonMembership` proof for a key that does not exist in the store. In the case, the state machine does not support `NonMembership` proofs; a client may get around this restriction by associating a `SENTINEL_ABSENCE_VALUE` with meaning the key does not exist and treating a `MembershipProof` with a `SENTINEL_ABSENCE_VALUE` as a `NonMembershipProof`. In this case, the state machine is responsible for ensuring that there is a way to write a `SENTINEL_ABSENCE_VALUE` to the keys that IBC needs to prove nonmembership for and it MUST ensure that an actor cannot set the `SENTINEL_ABSENCE_VALUE` directly for a key accidentally. These requirements and how to implement them are outside the scope of this specification and remain the responsibility of the bespoke IBC implementation. ### Finality The state machine MUST make updates sequentially so that all state updates happen in order and can be associated with a unique `Height` in that order. Each state update at a height `h` MUST be eventually **finalized** at a finite timestamp `t` such that the order of state updates from the initial state up to `h` will never change after time `t`. IBC handlers will only accept packet-flow messages from state updates which are already deemed to be finalized. In cases where the finality property is probabilistically guaranteed, this probabilitic guarantee must be handled within the ICS-2 client in order to provide a final view of the remote state machine for the ICS-4 packet handler. ### Time As the state updates are applied to the state machine over time, the state update algorithm MUST itself have secure access to the current timestamp at which the state update is being applied. This is needed for IBC handlers to process timeouts correctly. If the state machine update mechanism does not itself provide a timestamp to the state machine handler, then there must be a time oracle updates as part of the state machine update itself. In this case, the security model of IBC will also include the security model of the time oracle. This timestamp for a state update MUST be monotonically increasing and it MUST be the greater than or equal to the timestamp that the counterparty client will return for the `ConsensusState` associated with that state update. ## Backwards Compatibility Not applicable. ## Forwards Compatibility Key/value store functionality and consensus state type are unlikely to change during operation of a single host state machine. `submitDatagram` can change over time as relayers should be able to update their processes. ## Example Implementations ## History Aug 21, 2024 - [Initial draft](https://github.com/cosmos/ibc/pull/1144) ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # IBC Application Callbacks Source: https://docs.cosmos.network/ibc/latest/spec/IBC_V2/core/ics-026-application-callbacks/README ## Synopsis IBC enables module to module communication across remote state machines by providing a secure packet flow authenticated by the ICS-4 packet handler. The IBC core protocol is responsible for TAO (transport, authentication, ordering) of packets between two chains. These packets contain payload(s) that carry the application-specific information that is being communicated between two ICS26 applications. The data in the payload is itself opaque to the IBC core protocol, IBC core only verifies that it was correctly sent by the sender and then provides that data to the receiver for application-specific interpretation and processing. This specification standardizes the interface between ICS-4 (core IBC/TAO) and an IBC application (i.e. ICS26 app) for all the packet flow messages. The default IBC handler uses a receiver call pattern, where modules must individually call the IBC handler in order to send packets. In turn, the IBC handler verifies incoming packet flow messages like `ReceivePacket`, `AcknowledgePacket` and `TimeoutPacket` and calls into the appropriate ICS26 application as described in [ICS5 Port Allocation](../ics-005-port-allocation/README). ## Technical Specification ### Payload Structure The payload structure is reproduced from [ICS-4](../ics-004-packet-semantics/PACKET) since all of the following application functions are operating on the payloads that are being sent in the packets. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Payload { sourcePort: bytes, // identifier of the sending application on the sending chain destPort: bytes, // identifier of the receiving application on the receiving chain version: string, // payload version only interpretable by sending/receiving applications encoding: string, // payload encoding only interpretable by sending/receiving applications value: bytes // application-specific data that can be parsed by receiving application given the version and encoding } ``` ### Core Handler Interface Exposed to ICS26 Applications The IBC core handler MUST expose the following function signature to the ICS26 applications registered on the port router, so that the application can send packets. #### SendPacket SendPacket Inputs: `payloads: Payload`: This is the payload that the application wishes to send to an application on the receiver chain. `sourceClientId: bytes`: Identifier of the receiver chain client that exists on the sending chain. `timeoutTimestamp: uint64`: The timeout in UNIX seconds after which the packet is no longer receivable on the receiving chain. NOTE: This timestamp is evaluated against the **receiving chain** clock as there may be drift between the sending chain and receiving chain clocks SendPacket Preconditions: * The application is registered on the port router with `payload.SourcePortId` * The application MUST have successfully conducted any application specific logic necessary for sending the given payload. * The sending client exists for `sourceClientId` SendPacket Postconditions: * The following packet gets committed and stored under the packet commitment path as specified by ICS24: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Packet { sourceClientId: sourceClientId, destClientId: getCounterparty(sourceClientId).ClientId, // destClientId should be filled in with the registered counterparty id for provided sourceClientId sequence: generateUniqueSequence(sourceClientId), timeoutTimestamp: msg.timeoutTimestamp data: msg.Payloads } ``` * The sequence is returned to the ICS26 application SendPacket ErrorConditions: * The sending client is invalid (expired or frozen) * The provided `timeoutTimstamp` has already elapsed * The sending application is not allowed to send the provided payload to the requested receiving application as identified by `payload.DestPort` NOTE: IBC v2 allows multiple payloads coming from multiple applications to be sent in the same packet. If an implementation chooses to support this feature, they may either provide an entrypoint in the core handler to send multiple packets, which must then call each individual application `OnSendPacket` callback to validate their individual payload and do application-specific sending logic; or they may queue the payloads coming from each application until the packet is ready to be committed. #### WriteAcknowledgement The IBC core handler MAY expose the following function signature to the ICS26 applications registed on the port router, so that the application can write acknowledgements asynchronously. This is only necessary if the implementation supports processing packets asynchronously. In this case, an application may process the packet asynchronously from when the IBC core handler receives the packet. Thus, the acknowledgement cannot be returned as part of the `OnRecvPacket` callback and must be submitted to the core IBC handler by the ICS26 application at a later time. Thus, we must introduce a new endpoint on the IBC handler for the ICS26 application to call when it is done processing a receive packet and wants to write the acknowledgement. WriteAcknowledgement Inputs: `destClientId: bytes`: Identifier of the sender chain client that exist on the receiving chain (i.e. executing chain) `sequence: uint64`: Unique sequence identifying the packet from sending chain to receiving chain `ack: bytes`: Acknowledgement from the receiving application for the payload it was sent by the application. If the receive was unsuccessful, the `ack` must be the `SENTINEL_ERROR_ACKNOWLEDGEMENT`, otherwise it may be some application-specific data. WriteAcknowledgement Preconditions: * A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence` * An acknowledgement for the `destClientId` and `sequence` has not already been written under the ICS24 path WriteAcknowledgement Postconditions: * The acknowledgement is committed and written to the acknowledgement path as specified in ICS24 * If the acknowledgement is successful, then all receiving applications must have executed their recvPacket logic and written state * If the acknowledgement is unsuccessful (ie ERROR ACK), any state changes made by the receiving applications MUST all be reverted. This ensure atomic execution of the multi-payload packet. NOTE: In the case that the packet contained multiple payloads, the IBC core handler MUST wait for all applications to return their individual acknowledgements for the packet before commiting the acknowledgment. If ANY application returns the error acknowledgement, then the acknowledgement for the entire packet only contains the `ERROR_SENTINEL_ACKNOWLEDGEMENT`. Otherwise, the acknowledgment is a list containing each applications individual acknowledgment in the same order that their associated payload existed in the packet. ### ICS26 Interface Exposed to Core Handler Modules must expose the following function signatures to the routing module, which are called upon the receipt of various datagrams: #### OnRecvPacket OnRecvPacket Inputs: `sourceClientId: bytes`: This is the identifier of the client on the sending chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain. `destClientId: bytes`: This is the identifier of the receiving chain (i.e. executing chain) `sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(destClientId, sequence)` uniquely identifies the packet on this chain. `payload: Payload`. This is the payload that an application registered by `payload.SourcePort` on the sending chain sends to the executing application OnRecvPacket Preconditions: * The application is registered on the port router with `payload.DestPort` * The destination client exists for `destClientId` * All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives a packet; it can be guaranteed of its authenticity and need only perform the relevant application logic for the given payload. OnRecvPacket Postconditions: * The application has executed all app-specific logic for the given payload and made the appropriate state changes * The application returns an app acknowledgment `ack: bytes` to the core IBC handler to be written as an acknowledgement of the payload in this packet. OnRecvPacket ErrorConditions: * The sending application as identified by `payload.SourcePortId` is not allowed to send a payload to the receiving application * The requested version as identified by `payload.Version` is unsupported * The requested encoding as identified by `payload.Encoding` is unsupported * An error occured while processing the `payload.Value` after decoding with `payload.Encoding` and processing the payload in the manner expected by `payload.Version`. IMPORTANT: If the `OnRecvPacket` callback errors for any reason, the state changes made during the callback MUST be reverted and the IBC core handler MUST write the `SENTINEL_ERROR_ACKNOWLEDGEMENT` for this packet even if other payloads in the packet are received successfully. #### OnAcknowledgePacket OnAcknowledgePacket Inputs: `sourceClientId: bytes`: This is the identifier of the client on the sending chain (i.e. executing chain). `destClientId: bytes`: This is the identifier of the receiving chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain. `sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(sourceClientId, sequence)` uniquely identifies the packet on this chain. `acknowledgement: bytes`: This is the acknowledgement that the receiving application sent for the payload that we previously sent. It may be a successful acknowledgement with app-specific information or it may be the `SENTINEL_ERROR_ACKNOWLEDGEMENT` in which case we should handle any app-specific logic needed for a packet that failed to be sent. `payload: Payload`: This is the original payload that we previously sent OnAcknowledgementPreconditions: * This application had previously sent the provided payload in a packet with the provided `sourceClientId` and `sequence`. * All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives an acknowledgement; it can be guaranteed of its authenticity and need only perform the relevant application logic for the given acknowledgement and payload. OnAcknowledgement Postconditions: * The application has executed all app-specific logic for the given payload and acknowledgment and made the appropriate state changes * If the acknowledgement was the `SENTINEL_ERROR_ACKNOWLEDGEMENT`, this will usually involve reverting whatever application state changes were made during `SendPacket` (e.g. unescrowing tokens for transfer) OnAcknowledgement Errorconditions: * Application specific errors may occur while processing the acknowledgement. The packet lifecycle is already complete. Implementations MAY choose to allow retries or not. #### OnTimeoutPacket OnTimeoutPacket Inputs: `sourceClientId: bytes`: This is the identifier of the client on the sending chain (i.e. executing chain). `destClientId: bytes`: This is the identifier of the receiving chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain. `sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(sourceClientId, sequence)` uniquely identifies the packet on this chain. `payload: Payload`: This is the original payload that we previously sent OnTimeoutPacket Preconditions: * This application had previously sent the provided payload in a packet with the provided `sourceClientId` and `sequence`. * All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives an timeout; it can be guaranteed of its authenticity and need only perform the relevant application timeout logic for the given payload. OnTimeoutPacket Postconditions: * The application has executed all app-specific logic for the given payload and made the appropriate state changes. This will usually involve reverting whatever application state changes were made during `SendPacket` (e.g. unescrowing tokens for transfer) OnTimeoutPacket Errorconditions: * Application specific errors may occur while processing the timeout. The packet lifecycle is already complete. Implementations MAY choose to allow retries or not. # Fungible Token Transfer Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-020-fungible-token-transfer/README ## Synopsis This standard document specifies packet data structure, state machine handling logic, and encoding details for the transfer of fungible tokens over an IBC channel between two modules on separate chains. The state machine logic presented allows for safe multi-chain denomination handling with permissionless channel opening. This logic constitutes a "fungible token transfer bridge module", interfacing between the IBC routing module and an existing asset tracking module on the host state machine. ### Motivation Users of a set of chains connected over the IBC protocol might wish to utilise an asset issued on one chain on another chain, perhaps to make use of additional features such as exchange or privacy protection, while retaining fungibility with the original asset on the issuing chain. This application-layer standard describes a protocol for transferring fungible tokens between chains connected with IBC which preserves asset fungibility, preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning. ### Definitions The IBC handler interface & IBC routing module interface are as defined in [ICS 25](../../core/ics-025-handler-interface) and [ICS 26](../../core/ics-026-routing-module), respectively. ### Desired Properties * Preservation of fungibility (two-way peg). * Preservation of total supply (constant or inflationary on a single source chain & module). * Permissionless token transfers, no need to whitelist connections, modules, or denominations. * Symmetric (all chains implement the same logic, no in-protocol differentiation of hubs & zones). * Fault containment: prevents Byzantine-inflation of tokens originating on chain `A`, as a result of chain `B`'s Byzantine behaviour (though any users who sent tokens to chain `B` may be at risk). ## Technical Specification ### Data Structures Only one packet data type is required: `FungibleTokenPacketData`, which specifies the denomination, amount, sending account, and receiving account. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface FungibleTokenPacketData { denom: string amount: uint256 sender: string receiver: string memo: string } ``` As tokens are sent across chains using the ICS 20 protocol, they begin to accrue a record of channels for which they have been transferred across. This information is encoded into the `denom` field. The ICS 20 token denominations are represented by the form `{ics20Port}/{ics20Channel}/{denom}`, where `ics20Port` and `ics20Channel` are an ICS 20 port and channel on the current chain for which the funds exist. The prefixed port and channel pair indicate which channel the funds were previously sent through. Implementations are responsible for correctly parsing the IBC trace information from the base denomination. The way the reference ICS 20 implementation in ibc-go handles this is by taking advantage of the fact that it automatically generates channel identifiers with the format `channel-{n}`, where `n` is a integer greater or equal than 0. It can then correctly parse out the IBC trace information from the base denom which may have slashes, but will not have a substring of the form `{transfer-port-name}/channel-{n}`. If this assumption is broken, the trace information will be parsed incorrectly (i.e. part of the base denom will be misinterpreted as trace information). Thus chains must make sure that base denominations do not have the ability to create arbitrary prefixes that can mock the ICS 20 logic. A sending chain may be acting as a source or sink zone. When a chain is sending tokens across a port and channel which are not equal to the last prefixed port and channel pair, it is acting as a source zone. When tokens are sent from a source zone, the destination port and channel will be prefixed onto the denomination (once the tokens are received) adding another hop to a tokens record. When a chain is sending tokens across a port and channel which are equal to the last prefixed port and channel pair, it is acting as a sink zone. When tokens are sent from a sink zone, the last prefixed port and channel pair on the denomination is removed (once the tokens are received), undoing the last hop in the tokens record. A more complete explanation is present in the [ibc-go implementation](https://github.com/cosmos/ibc-go/blob/457095517b7832c42ecf13571fee1e550fec02d0/modules/apps/transfer/keeper/relay.go#L18-L49) and the [ADR 001](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md). The following sequence diagram exemplifies the multi-chain token transfer dynamics. This process encapsulates the steps involved in transferring tokens in a cycle that begins and ends on the same chain, traversing through Chain A, Chain B, and Chain C. The order of operations is outlined as `A -> B -> C -> A -> C -> B -> A`. ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram Note over chain A,chain B: A is source zone: A -> B chain A->>chain A: Lock (escrow) tokens ("denom") chain A->>chain B: Send transfer packet with tokens ("denom") chain B->>chain B: Mint vouchers ("transfer/ChannelToA/denom") Note over chain B,chain C: B is source zone: B -> C chain B->>chain B: Lock (escrow) vouchers ("transfer/ChannelToA/denom") chain B->>chain C: Send transfer packet with vouchers ("transfer/ChannelToA/denom") chain C->>chain C: Mint vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") Note over chain A,chain C: C is source zone: C -> A chain C->>chain C: Lock (escrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain C->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain A->>chain A: Mint vouchers ("tansfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom") Note over chain A,chain C: A is sink zone: A -> C chain A->>chain A: Burn vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom") chain A->>chain C: Send transfer packet with vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom") chain C->>chain C: Unlock (unescrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") Note over chain B,chain C: C is sink zone: C -> B chain C->>chain C: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain C->>chain B: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain B->>chain B: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom") Note over chain B,chain A: B is sink zone: B -> A chain B->>chain B: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain B->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain A->>chain A: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom") ``` The acknowledgement data type describes whether the transfer succeeded or failed, and the reason for failure (if any). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type FungibleTokenPacketAcknowledgement = FungibleTokenPacketSuccess | FungibleTokenPacketError; interface FungibleTokenPacketSuccess { // This is binary 0x01 base64 encoded result: "AQ==" } interface FungibleTokenPacketError { error: string } ``` Note that both the `FungibleTokenPacketData` as well as `FungibleTokenPacketAcknowledgement` must be JSON-encoded (not Protobuf encoded) when they serialized into packet data. Also note that `uint256` is string encoded when converted to JSON, but must be a valid decimal number of the form `[0-9]+`. The fungible token transfer bridge module tracks escrow addresses associated with particular channels in state. Fields of the `ModuleState` are assumed to be in scope. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ModuleState { channelEscrowAddresses: Map } ``` ### Sub-protocols The sub-protocols described herein should be implemented in a "fungible token transfer bridge" module with access to a bank module and to the IBC routing module. #### Port & channel setup The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function setup() { capability = routingModule.bindPort("transfer", ModuleCallbacks{ onChanOpenInit, onChanOpenTry, onChanOpenAck, onChanOpenConfirm, onChanCloseInit, onChanCloseConfirm, onRecvPacket, onTimeoutPacket, onAcknowledgePacket, onTimeoutPacketClose }) claimCapability("port", capability) } ``` Once the `setup` function has been called, channels can be created through the IBC routing module between instances of the fungible token transfer module on separate chains. An administrator (with the permissions to create connections & channels on the host state machine) is responsible for setting up connections to other state machines & creating channels to other instances of this module (or another module supporting this interface) on other chains. This specification defines packet handling semantics only, and defines them in such a fashion that the module itself doesn't need to worry about what connections or channels might or might not exist at any point in time. #### Routing module callbacks ##### Channel lifecycle management Both machines `A` and `B` accept new channels from any module on another machine, if and only if: * The channel being created is unordered. * The version string is `ics20-1`. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenInit( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string) => (version: string, err: Error) { // only unordered channels allowed abortTransactionUnless(order === UNORDERED) // assert that version is "ics20-1" or empty // if empty, we return the default transfer version to core IBC // as the version for this channel abortTransactionUnless(version === "ics20-1" || version === "") // allocate an escrow address channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier) return "ics20-1", nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenTry( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string) => (version: string, err: Error) { // only unordered channels allowed abortTransactionUnless(order === UNORDERED) // assert that version is "ics20-1" abortTransactionUnless(counterpartyVersion === "ics20-1") // allocate an escrow address channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier) // return version that this chain will use given the // counterparty version return "ics20-1", nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string) { // port has already been validated // assert that counterparty selected version is "ics20-1" abortTransactionUnless(counterpartyVersion === "ics20-1") } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // accept channel confirmations, port has already been validated, version has already been validated } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanCloseInit( portIdentifier: Identifier, channelIdentifier: Identifier) { // always abort transaction abortTransactionUnless(FALSE) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanCloseConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // no action necessary } ``` ##### Packet relay In plain English, between chains `A` and `B`: * When acting as the source zone, the bridge module escrows an existing local asset denomination on the sending chain and mints vouchers on the receiving chain. * When acting as the sink zone, the bridge module burns local vouchers on the sending chains and unescrows the local asset denomination on the receiving chain. * When a packet times-out, local assets are unescrowed back to the sender or vouchers minted back to the sender appropriately. * Acknowledgement data is used to handle failures, such as invalid denominations or invalid destination accounts. Returning an acknowledgement of failure is preferable to aborting the transaction since it more easily enables the sending chain to take appropriate action based on the nature of the failure. `sendFungibleTokens` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function sendFungibleTokens( denomination: string, amount: uint256, sender: string, receiver: string, sourcePort: string, sourceChannel: string, timeoutHeight: Height, timeoutTimestamp: uint64, // in unix nanoseconds ): uint64 { prefix = "{sourcePort}/{sourceChannel}/" // we are the source if the denomination is not prefixed source = denomination.slice(0, len(prefix)) !== prefix if source { // determine escrow account escrowAccount = channelEscrowAddresses[sourceChannel] // escrow source tokens (assumed to fail if balance insufficient) bank.TransferCoins(sender, escrowAccount, denomination, amount) } else { // receiver is source chain, burn vouchers bank.BurnCoins(sender, denomination, amount) } // create FungibleTokenPacket data data = FungibleTokenPacketData{denomination, amount, sender, receiver} // send packet using the interface defined in ICS4 sequence = handler.sendPacket( getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, json.marshal(data) // json-marshalled bytes of packet data ) return sequence } ``` `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onRecvPacket(packet: Packet) { FungibleTokenPacketData data = packet.data assert(data.denom !== "") assert(data.amount > 0) assert(data.sender !== "") assert(data.receiver !== "") // construct default acknowledgement of success FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null} prefix = "{packet.sourcePort}/{packet.sourceChannel}/" // we are the source if the packets were prefixed by the sending chain source = data.denom.slice(0, len(prefix)) === prefix if source { // receiver is source chain: unescrow tokens // determine escrow account escrowAccount = channelEscrowAddresses[packet.destChannel] // unescrow tokens to receiver (assumed to fail if balance insufficient) err = bank.TransferCoins(escrowAccount, data.receiver, data.denom.slice(len(prefix)), data.amount) if (err !== nil) ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"} } else { prefix = "{packet.destPort}/{packet.destChannel}/" prefixedDenomination = prefix + data.denom // sender was source, mint vouchers to receiver (assumed to fail if balance insufficient) err = bank.MintCoins(data.receiver, prefixedDenomination, data.amount) if (err !== nil) ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"} } return ack } ``` `onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onAcknowledgePacket( packet: Packet, acknowledgement: bytes) { // if the transfer failed, refund the tokens if (!acknowledgement.success) refundTokens(packet) } ``` `onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that it will not be received on the destination chain). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onTimeoutPacket(packet: Packet) { // the packet timed-out, so refund the tokens refundTokens(packet) } ``` `refundTokens` is called by both `onAcknowledgePacket`, on failure, and `onTimeoutPacket`, to refund escrowed tokens to the original sender. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function refundTokens(packet: Packet) { FungibleTokenPacketData data = packet.data prefix = "{packet.sourcePort}/{packet.sourceChannel}/" // we are the source if the denomination is not prefixed source = data.denom.slice(0, len(prefix)) !== prefix if source { // sender was source chain, unescrow tokens back to sender escrowAccount = channelEscrowAddresses[packet.srcChannel] bank.TransferCoins(escrowAccount, data.sender, data.denom, data.amount) } else { // receiver was source chain, mint vouchers back to sender bank.MintCoins(data.sender, data.denom, data.amount) } } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onTimeoutPacketClose(packet: Packet) { // can't happen, only unordered channels allowed } ``` #### Using the Memo Field Note: Since earlier versions of this specification did not include a `memo` field, implementations must ensure that the new packet data is still compatible with chains that expect the old packet data. A legacy implementation MUST be able to unmarshal a new packet data with an empty string memo into the legacy `FungibleTokenPacketData` struct. Similarly, an implementation supporting `memo` must be able to unmarshal a legacy packet data into the current struct with the `memo` field set to the empty string. The `memo` field is not used within transfer, however it may be used either for external off-chain users (i.e. exchanges) or for middleware wrapping transfer that can parse and execute custom logic on the basis of the passed in memo. If the memo is intended to be parsed and interpreted by higher-level middleware, then these middleware are advised to namespace their additions to the memo string so that they do not overwrite each other. Chains should ensure that there is some length limit on the entire packet data to ensure that the packet does not become a DOS vector. However, these do not need to be protocol-defined limits. If the receiver cannot accept a packet because of length limitations, this will lead to a timeout on the sender side. Memos that are intended to be read by higher level middleware for custom execution must be structured so that different middleware can read relevant data in the memo intended for them without interfering with data intended for other middlewares. Thus, for any memo that is meant to be interpreted by the state machine; it is recommended that the memo is a JSON object with each middleware reserving a key that it can read into and retrieve relevant data. This way the memo can be constructed to pass in information such that multiple middleware can read the memo without interference from each other. Example: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "wasm": { "address": "contractAddress", "arguments": "marshalledArguments", }, "callback": "contractAddress", "router": "routerArgs", } ``` Here, the "wasm", "callback", and "router" fields are all intended for separate middlewares that will exclusively read those fields respectively in order to execute their logic. This allows multiple modules to read from the memo. Middleware should take care to reserve a unique key so that they do not accidentally read data intended for a different module. This issue can be avoided by some off-chain registry of keys already in-use in the JSON object. #### Reasoning ##### Correctness This implementation preserves both fungibility & supply. Fungibility: If tokens have been sent to the counterparty chain, they can be redeemed back in the same denomination & amount on the source chain. Supply: Redefine supply as unlocked tokens. All send-recv pairs sum to net zero. Source chain can change supply. ##### Multi-chain notes This specification does not directly handle the "diamond problem", where a user sends a token originating on chain A to chain B, then to chain D, and wants to return it through D -> C -> A — since the supply is tracked as owned by chain B (and the denomination will be "////denom"), chain C cannot serve as the intermediary. It is not yet clear whether that case should be dealt with in-protocol or not — it may be fine to just require the original path of redemption (and if there is frequent liquidity and some surplus on both paths the diamond path will work most of the time). Complexities arising from long redemption paths may lead to the emergence of central chains in the network topology. In order to track all of the denominations moving around the network of chains in various paths, it may be helpful for a particular chain to implement a registry which will track the "global" source chain for each denomination. End-user service providers (such as wallet authors) may want to integrate such a registry or keep their own mapping of canonical source chains and human-readable names in order to improve UX. #### Optional addenda * Each chain, locally, could elect to keep a lookup table to use short, user-friendly local denominations in state which are translated to and from the longer denominations when sending and receiving packets. * Additional restrictions may be imposed on which other machines may be connected to & which channels may be established. ## Backwards Compatibility Not applicable. ## Forwards Compatibility This initial standard uses version "ics20-1" in the channel handshake. A future version of this standard could use a different version in the channel handshake, and safely alter the packet data format & packet handler semantics. ## Example Implementations * Implementation of ICS 20 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go). * Implementation of ICS 20 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs). ## History Jul 15, 2019 - Draft written Jul 29, 2019 - Major revisions; cleanup Aug 25, 2019 - Major revisions, more cleanup Feb 3, 2020 - Revisions to handle acknowledgements of success & failure Feb 24, 2020 - Revisions to infer source field, inclusion of version string July 27, 2020 - Re-addition of source field Nov 11, 2022 - Addition of a memo field ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # Fungible Token Transfer Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-020-fungible-token-transfer/deprecated/README ## Synopsis This standard document specifies packet data structure, state machine handling logic, and encoding details for the transfer of fungible tokens over an IBC channel between two modules on separate chains. The state machine logic presented allows for safe multi-chain denomination handling with permissionless channel opening. This logic constitutes a "fungible token transfer bridge module", interfacing between the IBC routing module and an existing asset tracking module on the host state machine. ### Motivation Users of a set of chains connected over the IBC protocol might wish to utilise an asset issued on one chain on another chain, perhaps to make use of additional features such as exchange or privacy protection, while retaining fungibility with the original asset on the issuing chain. This application-layer standard describes a protocol for transferring fungible tokens between chains connected with IBC which preserves asset fungibility, preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning. ### Definitions The IBC handler interface & IBC routing module interface are as defined in [ICS 25](../../../core/ics-025-handler-interface) and [ICS 26](../../../core/ics-026-routing-module), respectively. ### Desired Properties * Preservation of fungibility (two-way peg). * Preservation of total supply (constant or inflationary on a single source chain & module). * Permissionless token transfers, no need to whitelist connections, modules, or denominations. * Symmetric (all chains implement the same logic, no in-protocol differentiation of hubs & zones). * Fault containment: prevents Byzantine-inflation of tokens originating on chain `A`, as a result of chain `B`'s Byzantine behaviour (though any users who sent tokens to chain `B` may be at risk). ## Technical Specification ### Data Structures Only one packet data type is required: `FungibleTokenPacketData`, which specifies the denomination, amount, sending account, and receiving account or `FungibleTokenPacketDataV2` which specifies multiple tokens being sent between sender and receiver along with an optional forwarding path that can forward tokens further beyond the initial receiving chain. A v2 supporting chain can optionally convert a v1 packet for channels that are still on version 1. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface FungibleTokenPacketData { denom: string amount: uint256 sender: string receiver: string memo: string } interface FungibleTokenPacketDataV2 { tokens: []Token sender: string receiver: string memo: string // a struct containing the list of next hops, // determining where the tokens must be forwarded next, // and the memo for the final hop forwarding: Forwarding } interface Token { denom: Denom amount: uint256 } interface Denom { base: string // base denomination trace: []Hop } interface Forwarding { hops: []Hop memo: string } interface Hop { portId: string channelId: string } ``` As tokens are sent across chains using the ICS 20 protocol, they begin to accrue a record of channels for which they have been transferred across. This information is encoded into the `trace` field in the token. The ICS 20 token traces are represented by a list of `ics20Port` and `ics20Channel` pairs, which are an ICS 20 port and channel on the current chain for which the funds exist. The port and channel pair indicate which channel the funds were previously sent through. Implementations are responsible for correctly parsing the IBC trace information and encoding it into the final on-chain denomination so that the same base denominations sent through different paths are not treated as being fungible. A sending chain may be acting as a source or sink zone. When a chain is sending tokens across a port and channel which are not equal to the last prefixed port and channel pair, it is acting as a source zone. When tokens are sent from a source zone, the destination port and channel will be prepended to the trace (once the tokens are received) adding another hop to a tokens record. When a chain is sending tokens across a port and channel which are equal to the last prefixed port and channel pair, it is acting as a sink zone. When tokens are sent from a sink zone, the first element of the trace, which was the last port and channel pair added to the trace is removed (once the tokens are received), undoing the last hop in the tokens record. A more complete explanation is [present in the ibc-go implementation](https://github.com/cosmos/ibc-go/blob/457095517b7832c42ecf13571fee1e550fec02d0/modules/apps/transfer/keeper/relay.go#L18-L49). The following sequence diagram exemplifies the multi-chain token transfer dynamics. This process encapsulates the steps involved in transferring tokens in a cycle that begins and ends on the same chain, traversing through chain A, chain B, and chain C. The order of operations is outlined as `A -> B -> C -> A -> C -> B -> A`. ```mermaid theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} sequenceDiagram Note over chain A,chain B: A is source zone: A -> B chain A->>chain A: Lock (escrow) tokens ("denom") chain A->>chain B: Send transfer packet with tokens ("denom") chain B->>chain B: Mint vouchers ("transfer/ChannelToA/denom") Note over chain B,chain C: B is source zone: B -> C chain B->>chain B: Lock (escrow) vouchers ("transfer/ChannelToA/denom") chain B->>chain C: Send transfer packet with vouchers ("transfer/ChannelToA/denom") chain C->>chain C: Mint vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") Note over chain A,chain C: C is source zone: C -> A chain C->>chain C: Lock (escrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain C->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain A->>chain A: Mint vouchers ("tansfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom") Note over chain A,chain C: A is sink zone: A -> C chain A->>chain A: Burn vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom") chain A->>chain C: Send transfer packet with vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom") chain C->>chain C: Unlock (unescrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") Note over chain B,chain C: C is sink zone: C -> B chain C->>chain C: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain C->>chain B: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain B->>chain B: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom") Note over chain B,chain A: B is sink zone: B -> A chain B->>chain B: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain B->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom") chain A->>chain A: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom") ``` The forwarding path in the `v2` packet tells the receiving chain where to send the tokens to next. This must be constructed as a list of portID/channelID pairs with each element concatenated as `portID/channelID`. This allows users to automatically route tokens through the interchain. A common usecase might be to unwind the trace of the tokens back to the original source chain before sending it forward to the final intended destination. Here are examples of the transfer packet data: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // V1 example of transfer packet data FungibleTokenPacketData { denom: "transfer/channel-1/transfer/channel-4/uatom", amount: 500, sender: cosmosexampleaddr1, receiver: cosmosexampleaddr2, memo: "exampleMemo", } // V2 example of transfer packet data FungibleTokenPacketDataV2 { tokens: [ Token{ denom: Denom{ base: "uatom", trace: [ Hop{ portId: "transfer", channelId: "channel-1", }, Hop{ portId: "transfer", channelId: "channel-4", } ], }, amount: 500, }, Token{ denom: Denom{ base: "btc", trace: [ Hop{ portId: "transfer", channelId: "channel-3", } ], }, amount: 7, } ], sender: cosmosexampleaddr1, receiver: cosmosexampleaddr2, memo: "", forwarding: { hops: [ Hop{portId: "transfer", channelId: "channel-7"}, Hop{portId: "transfer", channelId: "channel-13"}, ], memo: "swap: {...}" }, // provide hops in order and the memo intended for final hop } ``` The acknowledgement data type describes whether the transfer succeeded or failed, and the reason for failure (if any). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type FungibleTokenPacketAcknowledgement = FungibleTokenPacketSuccess | FungibleTokenPacketError; interface FungibleTokenPacketSuccess { // This is binary 0x01 base64 encoded result: "AQ==" } interface FungibleTokenPacketError { error: string } ``` Note that both the `FungibleTokenPacketData` as well as `FungibleTokenPacketAcknowledgement` must be JSON-encoded (not Protobuf encoded) when they serialized into packet data. Also note that `uint256` is string encoded when converted to JSON, but must be a valid decimal number of the form `[0-9]+`. The fungible token transfer bridge module tracks escrow addresses associated with particular channels in state. Fields of the `ModuleState` are assumed to be in scope. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ModuleState { channelEscrowAddresses: Map channelForwardingAddresses: Map } ``` ### Store paths #### Packet forward path The `v2` packets that have non-empty forwarding information and should thus be forwarded, must be stored in the private store, so that an acknowledgement can be written for them when receiving an acknowledgement or timeout for the forwarded packet. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function packetForwardPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path { return "forwardedPackets/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}" } ``` ### Sub-protocols The sub-protocols described herein should be implemented in a "fungible token transfer bridge" module with access to a bank module and to the IBC routing module. #### Port & channel setup The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function setup() { capability = routingModule.bindPort("transfer", ModuleCallbacks{ onChanOpenInit, onChanOpenTry, onChanOpenAck, onChanOpenConfirm, onChanCloseInit, onChanCloseConfirm, onRecvPacket, onTimeoutPacket, onAcknowledgePacket, onTimeoutPacketClose }) claimCapability("port", capability) } ``` Once the `setup` function has been called, channels can be created through the IBC routing module between instances of the fungible token transfer module on separate chains. An administrator (with the permissions to create connections & channels on the host state machine) is responsible for setting up connections to other state machines & creating channels to other instances of this module (or another module supporting this interface) on other chains. This specification defines packet handling semantics only, and defines them in such a fashion that the module itself doesn't need to worry about what connections or channels might or might not exist at any point in time. #### Routing module callbacks ##### Channel lifecycle management Both machines `A` and `B` accept new channels from any module on another machine, if and only if: * The channel being created is unordered. * The version string is `ics20-1` or `ics20-2`. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenInit( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string) => (version: string, err: Error) { // only unordered channels allowed abortTransactionUnless(order === UNORDERED) // assert that version is "ics20-1" or "ics20-2" or empty // if empty, we return the default transfer version to core IBC // as the version for this channel abortTransactionUnless(version === "ics20-2" || version === "ics20-1" || version === "") // allocate an escrow address channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier) if version == "" { // default to latest supported version return "ics20-2", nil } // If the version is not empty and is among those supported, we return the version return version, nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenTry( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string) => (version: string, err: Error) { // only unordered channels allowed abortTransactionUnless(order === UNORDERED) // assert that version is "ics20-1" or "ics20-2" abortTransactionUnless(counterpartyVersion === "ics20-1" || counterpartyVersion === "ics20-2") // allocate an escrow address channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier) // return the same version as counterparty version so long as we support it return counterpartyVersion, nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string) { // port has already been validated // assert that counterparty selected version is the same as our version channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(counterpartyVersion === channel.version) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // accept channel confirmations, port has already been validated, version has already been validated } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanCloseInit( portIdentifier: Identifier, channelIdentifier: Identifier) { // always abort transaction abortTransactionUnless(FALSE) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanCloseConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // no action necessary } ``` ##### Packet relay In plain English, between chains `A` and `B`: * When acting as the source zone, the bridge module escrows an existing local asset denomination on the sending chain and mints vouchers on the receiving chain. * When acting as the sink zone, the bridge module burns local vouchers on the sending chains and unescrows the local asset denomination on the receiving chain. * When a packet times-out, local assets are unescrowed back to the sender or vouchers minted back to the sender appropriately. * Acknowledgement data is used to handle failures, such as invalid denominations or invalid destination accounts. Returning an acknowledgement of failure is preferable to aborting the transaction since it more easily enables the sending chain to take appropriate action based on the nature of the failure. Note: `constructOnChainDenom` is a helper function that will construct the local on-chain denomination for the bridged token. It **must** encode the trace and base denomination to ensure that tokens coming over different paths are not treated as fungible. The original trace and denomination must be retrievable by the state machine so that they can be passed in their original forms when constructing a new IBC path for the bridged token. The ibc-go implementation handles this by creating a local denomination: `hash(trace+base_denom)`. `sendFungibleTokens` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function sendFungibleTokens( tokens: []Token, sender: string, receiver: string, memo: string, forwarding: Forwarding, sourcePort: string, sourceChannel: string, timeoutHeight: Height, timeoutTimestamp: uint64, // in unix nanoseconds ): uint64 { // memo and forwarding cannot both be non-empty abortTransactionUnless(memo != "" && forwarding != nil) for token in tokens onChainDenom = constructOnChainDenom(token.denom.trace, token.denom.base) // if the token is not prefixed by our channel end's port and channel identifiers // then we are sending as a source zone if !isTracePrefixed(sourcePort, sourceChannel, token) { // determine escrow account escrowAccount = channelEscrowAddresses[sourceChannel] // escrow source tokens (assumed to fail if balance insufficient) bank.TransferCoins(sender, escrowAccount, onChainDenom, token.amount) } else { // receiver is source chain, burn vouchers bank.BurnCoins(sender, onChainDenom, token.amount) } } var dataBytes bytes channel = provableStore.get(channelPath(sourcePort, sourceChannel)) // getAppVersion returns the transfer version that is embedded in the channel version // as the channel version may contain additional app or middleware version(s) transferVersion = getAppVersion(channel.version) if transferVersion == "ics20-1" { abortTransactionUnless(len(tokens) == 1) token = tokens[0] // abort if forwarding defined abortTransactionUnless(forwarding == nil) // create v1 denom of the form: port1/channel1/port2/channel2/port3/channel3/denom v1Denom = constructOnChainDenom(token.denom.trace, token.denom.base) // v1 packet data does not support forwarding fields data = FungibleTokenPacketData{v1Denom, token.amount, sender, receiver, memo} // JSON-marshal packet data into bytes dataBytes = json.marshal(data) } else if transferVersion == "ics20-2" { // create FungibleTokenPacket data data = FungibleTokenPacketDataV2{tokens, sender, receiver, memo, forwarding} // protobuf-marshal packet data into bytes dataBytes = protobuf.marshal(data) } else { // should never be reached as transfer version must be negotiated to be either // ics20-1 or ics20-2 during channel handshake abortTransactionUnless(false) } // send packet using the interface defined in ICS4 sequence = handler.sendPacket( getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, dataBytes, ) return sequence } ``` `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. Note: Function `parseICS20V1Denom` is a helper function that will take the full IBC denomination and extract the base denomination (i.e. native denomination in the chain of origin) and the trace information (if any) for the received token. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onRecvPacket(packet: Packet) { channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) // getAppVersion returns the transfer version that is embedded in the channel version // as the channel version may contain additional app or middleware version(s) transferVersion = getAppVersion(channel.version) var tokens []Token var sender string var receiver string // address to send tokens to on this chain var finalReceiver string // final intended address in forwarding case if transferVersion == "ics20-1" { FungibleTokenPacketData data = json.unmarshal(packet.data) // convert full denom string to denom struct with base denom and trace denom = parseICS20V1Denom(data.denom) token = Token{ denom: denom amount: data.amount } tokens = []Token{token} sender = data.sender receiver = data.receiver } else if transferVersion == "ics20-2" { FungibleTokenPacketDataV2 data = protobuf.unmarshal(packet.data) tokens = data.tokens sender = data.sender // if we need to forward the tokens onward // overwrite the receiver to temporarily send to the // channel escrow address of the intended receiver if len(data.forwarding.hops) > 0 { // memo must be empty abortTransactionUnless(data.memo == "") if channelForwardingAddress[packet.destChannel] == "" { channelForwardingAddress[packet.destChannel] = newAddress() } receiver = channelForwardingAddresses[packet.destChannel] finalReceiver = data.receiver } else { receiver = data.receiver } } else { // should never be reached as transfer version must be negotiated // to be either ics20-1 or ics20-2 during channel handshake abortTransactionUnless(false) } assert(sender !== "") assert(receiver !== "") // construct default acknowledgement of success FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null} receivedTokens = []Token for token in tokens { assert(token.denom !== nil) assert(token.amount > 0) var onChainTrace []Hop // we are the source if the packets were prefixed by the sending chain // if the sender sends the tokens prefixed with their channel end's // port and channel identifiers then we are receiving tokens we // previously had sent to the sender, thus we are receiving the tokens // as a source zone if isTracePrefixed(packet.sourcePort, packet.sourceChannel, token) { // since we are receiving back to source we remove the prefix from the trace onChainTrace = token.trace[1:] onChainDenom = constructOnChainDenom(onChainTrace, token.denom.base) // receiver is source chain: unescrow tokens // determine escrow account escrowAccount = channelEscrowAddresses[packet.destChannel] // unescrow tokens to receiver (assumed to fail if balance insufficient) err = bank.TransferCoins(escrowAccount, receiver, onChainDenom, token.amount) if (err != nil) { ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"} // break out of for loop on first error break } } else { // since we are receiving to a new sink zone we prepend the prefix to the trace prefixTrace = Hop{portId: packet.destPort, channelId: packet.destChannel} onChainTrace = append([]Hop{prefixTrace}, token.denom.trace...) onChainDenom = constructOnChainDenom(onChainTrace, token.denom.base) // sender was source, mint vouchers to receiver (assumed to fail if balance insufficient) err = bank.MintCoins(receiver, onChainDenom, token.amount) if (err !== nil) { ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"} // break out of for loop on first error break } } // add the received token to the received tokens list recvToken = Token{ denom: Denom{base: token.denom.base, trace: onChainTrace}, amount: token.amount, } receivedTokens = append(receivedTokens, recvToken) } // if there is an error ack return immediately and do not forward further if !ack.Success() { return ack } // if acknowledgement is successful and forwarding path set // then start forwarding if len(forwarding.hops) > 0 { //check that next channel supports token forwarding channel = provableStore.get(channelPath(forwarding.hops[0].portId, forwarding.hops[0].channelId)) if channel.version != "ics20-2" && len(forwarding.hops) > 1 { ack = FungibleTokenPacketAcknowledgement(false, "next hop in path cannot support forwarding onward") return ack } memo = "" nextForwarding = Forwarding{ hops: forwarding.hops[1:] memo: forwarding.memo } if len(forwarding.hops) == 1 { // we're on the last hop, we can set memo and clear // the next forwarding memo = forwarding.memo nextForwarding = nil } // send the tokens we received above to the next port and channel // on the forwarding path // and reduce the forwarding by the first element packetSequence = sendFungibleTokens( receivedTokens, receiver, // sender of next packet finalReceiver, // receiver of next packet memo, nextForwarding, forwarding.hops[0].portId, forwarding.hops[0].channelId, Height{}, currentTime() + DefaultHopTimeoutPeriod, ) // store packet for future sending ack privateStore.set(packetForwardPath(forwarding.hops[0].portId, forwarding.hops[0].channelId, packetSequence), packet) // use async ack until we get successful acknowledgement from further down the line. return nil } return ack } ``` `onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onAcknowledgePacket( packet: Packet, acknowledgement: bytes) { // if the transfer failed, refund the tokens // to the sender account. In case of a packet sent for a // forwarded packet, the sender is the forwarding // address for the destination channel of the forwarded packet. if !(acknowledgement.success) { refundTokens(packet) } // check if the packet that was sent is from a previously forwarded packet prevPacket = privateStore.get(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) if prevPacket != nil { if acknowledgement.success { FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, "forwarded packet succeeded"} handler.writeAcknowledgement( prevPacket, ack, ) } else { // the forwarded packet has failed, thus the funds have been refunded to the forwarding address. // we must revert the changes that came from successfully receiving the tokens on our chain // before propogating the error acknowledgement back to original sender chain revertInFlightChanges(packet, prevPacket) // write error acknowledgement FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{false, "forwarded packet failed"} handler.writeAcknowledgement( prevPacket, ack, ) } // delete the forwarded packet that triggered sending this packet privateStore.delete(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) } } ``` `onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that it will not be received on the destination chain). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onTimeoutPacket(packet: Packet) { // the packet timed-out, so refund the tokens // to the sender account. In case of a packet sent for a // forwarded packet, the sender is the forwarding // address for the destination channel of the forwarded packet. refundTokens(packet) // check if the packet sent is from a previously forwarded packet prevPacket = privateStore.get(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) if prevPacket != nil { // the forwarded packet has failed, thus the funds have been refunded to the forwarding address. // we must revert the changes that came from successfully receiving the tokens on our chain // before propogating the error acknowledgement back to original sender chain revertInFlightChanges(packet, prevPacket) // write error acknowledgement FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{false, "forwarded packet timed out"} handler.writeAcknowledgement( prevPacket, ack, ) // delete the forwarded packet that triggered sending this packet privateStore.delete(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence)) } } ``` Given three chains and a transfer from chain A to chain C through chain B, the following diagrams summarize the core logic of the protocol regarding the handling of tokens in the middle chain, both for the success case (i.e. tokens received on chain C) and failure case (i.e. tokens cannot be received on chain C and an error acknowledgement is written): Forwarding success case Forwarding failure case ##### Helper functions ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // helper function that returns true if the first element of the trace of the // token is matches the provided portId and channelId; otherwise it returns false function isTracePrefixed(portId: string, channelId: string, token: Token) boolean { trace = token.trace[0] return trace.portId == portId && trace.channelId == channelId } ``` `refundTokens` is called by both `onAcknowledgePacket`, on failure, and `onTimeoutPacket`, to refund escrowed tokens to the original sender. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function refundTokens(packet: Packet) { channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) // getAppVersion returns the transfer version that is embedded in the channel version // as the channel version may contain additional app or middleware version(s) transferVersion = getAppVersion(channel.version) if transferVersion == "ics20-1" { FungibleTokenPacketData data = json.unmarshal(packet.data) // convert full denom string to denom struct with base denom and trace denom = parseICS20V1Denom(data.denom) token = Token{ denom: denom amount: data.amount } tokens = []Token{token} } else if transferVersion == "ics20-2" { FungibleTokenPacketDataV2 data = protobuf.unmarshal(packet.data) tokens = data.tokens } else { // should never be reached as transfer version must be negotiated to be either // ics20-1 or ics20-2 during channel handshake abortTransactionUnless(false) } for token in tokens { onChainDenom = constructOnChainDenom(token.denom.trace, token.denom.base) // Since this is refunding an outgoing packet, we can check if the tokens // were originally from the receiver by checking if the tokens were prefixed // by our channel end's identifiers. if !isTracePrefixed(packet.sourcePort, packet.sourceChannel, token) { // sender was source chain, unescrow tokens back to sender escrowAccount = channelEscrowAddresses[packet.sourceChannel] bank.TransferCoins(escrowAccount, data.sender, onChainDenom, token.amount) } else { // receiver was source chain, mint vouchers back to sender bank.MintCoins(data.sender, onChainDenom, token.amount) } } } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // revertInFlightChanges reverts the receive packet // that occurs in the middle chains during a packet forwarding // If an error occurs further down the line, the state changes // on this chain must be reverted before sending back the error acknowledgement // to ensure atomic packet forwarding function revertInFlightChanges(sentPacket: Packet, receivedPacket: Packet) { forwardingAddress = channelForwardingAddress[receivedPacket.destChannel] reverseEscrow = channelEscrowAddresses[receivedPacket.destChannel] // the token on our chain is the token in the sentPacket for token in sentPacket.tokens { // we are checking if the tokens that were sent out by our chain in the // sentPacket were source tokens with respect to the original receivedPacket. // If the tokens in sentPacket were prefixed by our channel end's port and channel // identifiers, then it was a minted voucher and we need to burn it. // Otherwise, it was an original token from our chain and we must give the tokens // back to the escrow account. if !isTracePrefixed(receivedPacket.destinationPort, receivedPacket.destinationChannel, token) { // receive sent tokens from the received escrow account to the forwarding account // so we must send the tokens back from the forwarding account to the received escrow account bank.TransferCoins(forwardingAddress, reverseEscrow, token.denom, token.amount) } else { // receive minted vouchers and sent to the forwarding account // so we must burn the vouchers from the forwarding account bank.BurnCoins(forwardingAddress, token.denom, token.amount) } } } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onTimeoutPacketClose(packet: Packet) { // can't happen, only unordered channels allowed } ``` #### Using the Memo Field Note: Since earlier versions of this specification did not include a `memo` field, implementations must ensure that the new packet data is still compatible with chains that expect the old packet data. A legacy implementation MUST be able to unmarshal a new packet data with an empty string memo into the legacy `FungibleTokenPacketData` struct. Similarly, an implementation supporting `memo` must be able to unmarshal a legacy packet data into the current struct with the `memo` field set to the empty string. The `memo` field is not used within transfer, however it may be used either for external off-chain users (i.e. exchanges) or for middleware wrapping transfer that can parse and execute custom logic on the basis of the passed in memo. If the memo is intended to be parsed and interpreted by higher-level middleware, then these middleware are advised to namespace their additions to the memo string so that they do not overwrite each other. Chains should ensure that there is some length limit on the entire packet data to ensure that the packet does not become a DOS vector. However, these do not need to be protocol-defined limits. If the receiver cannot accept a packet because of length limitations, this will lead to a timeout on the sender side. Memos that are intended to be read by higher level middleware for custom execution must be structured so that different middleware can read relevant data in the memo intended for them without interfering with data intended for other middlewares. Thus, for any memo that is meant to be interpreted by the state machine; it is recommended that the memo is a JSON object with each middleware reserving a key that it can read into and retrieve relevant data. This way the memo can be constructed to pass in information such that multiple middleware can read the memo without interference from each other. Example: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "wasm": { "address": "contractAddress", "arguments": "marshalledArguments", }, "callback": "contractAddress", "router": "routerArgs", } ``` Here, the "wasm", "callback", and "router" fields are all intended for separate middlewares that will exclusively read those fields respectively in order to execute their logic. This allows multiple modules to read from the memo. Middleware should take care to reserve a unique key so that they do not accidentally read data intended for a different module. This issue can be avoided by some off-chain registry of keys already in-use in the JSON object. #### Reasoning ##### Correctness This implementation preserves both fungibility & supply. Fungibility: If tokens have been sent to the counterparty chain, they can be redeemed back in the same denomination & amount on the source chain. Supply: Redefine supply as unlocked tokens. All send-recv pairs sum to net zero. Source chain can change supply. ##### Multi-chain notes This specification does not directly handle the "diamond problem", where a user sends a token originating on chain A to chain B, then to chain D, and wants to return it through D -> C -> A — since the supply is tracked as owned by chain B (and the denomination will be "////denom"), chain C cannot serve as the intermediary. It is not yet clear whether that case should be dealt with in-protocol or not — it may be fine to just require the original path of redemption (and if there is frequent liquidity and some surplus on both paths the diamond path will work most of the time). Complexities arising from long redemption paths may lead to the emergence of central chains in the network topology. In order to track all of the denominations moving around the network of chains in various paths, it may be helpful for a particular chain to implement a registry which will track the "global" source chain for each denomination. End-user service providers (such as wallet authors) may want to integrate such a registry or keep their own mapping of canonical source chains and human-readable names in order to improve UX. #### Optional addenda * Each chain, locally, could elect to keep a lookup table to use short, user-friendly local denominations in state which are translated to and from the longer denominations when sending and receiving packets. * Additional restrictions may be imposed on which other machines may be connected to & which channels may be established. ## Backwards Compatibility Not applicable. ## Forwards Compatibility This initial standard uses version "ics20-1" in the channel handshake. A future version of this standard could use a different version in the channel handshake, and safely alter the packet data format & packet handler semantics. ## Example Implementations * Implementation of ICS 20 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go). * Implementation of ICS 20 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs). ## History Jul 15, 2019 - Draft written Jul 29, 2019 - Major revisions; cleanup Aug 25, 2019 - Major revisions, more cleanup Feb 3, 2020 - Revisions to handle acknowledgements of success & failure Feb 24, 2020 - Revisions to infer source field, inclusion of version string July 27, 2020 - Re-addition of source field Nov 11, 2022 - Addition of a memo field Sep 22, 2023 - [Support for multi-token packets](https://github.com/cosmos/ibc/pull/1020) March 5, 2024 - [Support for path forwarding](https://github.com/cosmos/ibc/pull/1090) June 18, 2024 - [Support for data protobuf encoding](https://github.com/cosmos/ibc/pull/1118) ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # Interchain Accounts Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-027-interchain-accounts/README ## Synopsis This standard document specifies packet data structure, state machine handling logic, and encoding details for the account management system over an IBC channel between separate chains. ### Motivation ICS-27 Interchain Accounts outlines a cross-chain account management protocol built upon IBC. ICS-27 enabled chains can programmatically create accounts on other ICS-27 enabled chains & control these accounts via IBC transactions (instead of signing with a private key). Interchain accounts retain all of the capabilities of a normal account (i.e. stake, send, vote) but instead are managed by a separate chain via IBC in a way such that the owner account on the controller chain retains full control over any interchain account(s) it registers on host chain(s). ### Definitions * `Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which contain instructions (e.g. cosmos SDK messages) that the interchain account will execute. * `Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. * `Interchain Account`: An account on a host chain. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account must execute. * `Interchain Account Owner`: An account on the controller chain. Every interchain account on a host chain has a respective owner account on the controller chain. The IBC handler interface & IBC relayer module interface are as defined in [ICS-25](../../core/ics-025-handler-interface) and [ICS-26](../../core/ics-026-routing-module), respectively. ### Desired properties * Permissionless: An interchain account may be created by any actor without the approval of a third party (e.g. chain governance). Note: Individual implementations may implement their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. * Fault isolation: A controller chain must not be able to control accounts registered by other controller chains. For example, in the case of a fork attack on a controller chain, only the interchain accounts registered by the forked chain will be vulnerable. * The ordering of transactions sent to an interchain account on a host chain must be maintained. Transactions must be executed by an interchain account in the order in which they are sent by the controller chain. * If a channel closes, the controller chain must be able to regain access to registered interchain accounts by simply opening a new channel. * Each interchain account is owned by a single account on the controller chain. Only the owner account on the controller chain is authorized to control the interchain account. The controller chain is responsible for enforcing this logic. * The controller chain must store the account address of any owned interchain accounts registered on host chains. * A host chain must have the ability to limit interchain account functionality on its chain as necessary (e.g. a host chain can decide that interchain accounts registered on the host chain cannot take part in staking). ## Technical specification ### General design A chain can utilize one or both parts of the interchain accounts protocol (*controlling* and *hosting*). A controller chain that registers accounts on other host chains (that support interchain accounts) does not necessarily have to allow other controller chains to register accounts on its chain, and vice versa. This specification defines the general way to register an interchain account and send tx bytes to be executed on behalf of the owner account. The host chain is responsible for deserializing and executing the tx bytes and the controller chain must know how the host chain will handle the tx bytes in advance of sending a packet, thus this must be negotiated during channel creation. ### Controller chain contract #### **RegisterInterchainAccount** `RegisterInterchainAccount` is the entry point to registering an interchain account. It generates a new controller portID using the owner account address. It will bind to the controller portID and call 04-channel `ChanOpenInit`. An error is returned if the controller portID is already in use. A `ChannelOpenInit` event is emitted which can be picked up by an offchain process such as a relayer. The account will be registered during the `OnChanOpenTry` step on the host chain. This function must be called after an `OPEN` connection is already established with the given connection identifier. The caller must provide the complete channel version. This MUST include the ICA version with complete metadata and it MAY include versions of other middleware that is wrapping ICA on both sides of the channel. Note this will require contextual information on what middleware is enabled on either end of the channel. Thus it is recommended that an ICA-auth application construct the ICA version automatically and allow for users to optionally enable additional middleware versioning. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function RegisterInterchainAccount(connectionId: Identifier, owner: string, version: string) returns (error) { } ``` #### **SendTx** `SendTx` is used to send an IBC packet containing instructions (messages) to an interchain account on a host chain for a given interchain account owner. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function SendTx( capability: CapabilityKey, connectionId: Identifier, portId: Identifier, icaPacketData: InterchainAccountPacketData, timeoutTimestamp uint64 ): uint64 { // check if there is a currently active channel for // this portId and connectionId, which also implies an // interchain account has been registered using // this portId and connectionId activeChannelID, found = GetActiveChannelID(portId, connectionId) abortTransactionUnless(found) // validate timeoutTimestamp abortTransactionUnless(timeoutTimestamp <= currentTimestamp()) // validate icaPacketData abortTransactionUnless(icaPacketData.type == EXECUTE_TX) abortTransactionUnless(icaPacketData.data != nil) // send icaPacketData to the host chain on the active channel sequence = handler.sendPacket( capability, portId, // source port ID activeChannelID, // source channel ID 0, timeoutTimestamp, protobuf.marshal(icaPacketData) // protobuf-marshalled bytes of packet data ) return sequence } ``` ### Host chain contract #### **RegisterInterchainAccount** `RegisterInterchainAccount` is called on the `OnChanOpenTry` step during the channel creation handshake. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function RegisterInterchainAccount(counterpartyPortId: Identifier, connectionID: Identifier) returns (nil) { // checks to make sure the account has not already been registered // creates a new address on chain deterministically given counterpartyPortId and underlying connectionID // calls SetInterchainAccountAddress() } ``` #### **AuthenticateTx** `AuthenticateTx` is called before `ExecuteTx`. `AuthenticateTx` checks that the signer of a particular message is the interchain account associated with the counterparty portID of the channel that the IBC packet was sent on. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function AuthenticateTx(msgs []Any, connectionId string, portId string) returns (error) { // GetInterchainAccountAddress(portId, connectionId) // if interchainAccountAddress != msgSigner return error } ``` #### **ExecuteTx** Executes each message sent by the owner account on the controller chain. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function ExecuteTx(sourcePort: Identifier, channel Channel, msgs []Any) returns (resultString, error) { // validate each message // retrieve the interchain account for the given channel by passing in source port and channel's connectionID // verify that interchain account is authorized signer of each message // execute each message // return result of transaction } ``` ### Utility functions ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Sets the active channel for a given portID and connectionID. function SetActiveChannelID(portId: Identifier, connectionId: Identifier, channelId: Identifier) returns (error){ } // Returns the ID of the active channel for a given portID and connectionID, if present. function GetActiveChannelID(portId: Identifier, connectionId: Identifier) returns (Identifier, boolean){ } // Stores the address of the interchain account in state. function SetInterchainAccountAddress(portId: Identifier, connectionId: Identifier, address: string) returns (string) { } // Retrieves the interchain account from state. function GetInterchainAccountAddress(portId: Identifier, connectionId: Identifier) returns (string, bool){ } ``` ### Register & controlling flows #### Register account flow To register an interchain account we require an off-chain process (relayer) to listen for `ChannelOpenInit` events with the capability to finish a channel creation handshake on a given connection. 1. The controller chain binds a new IBC port with the controller portID for a given *interchain account owner address*. This port will be used to create channels between the controller & host chain for a specific owner/interchain account pair. Only the account with `{owner-account-address}` matching the bound port will be authorized to send IBC packets over channels created with the controller portID. It is up to each controller chain to enforce this port registration and access on the controller side. 2. The controller chain emits an event signaling to open a new channel on this port given a connection. 3. A relayer listening for `ChannelOpenInit` events will continue the channel creation handshake. 4. During the `OnChanOpenTry` callback on the host chain an interchain account will be registered and a mapping of the interchain account address to the owner account address will be stored in state (this is used for authenticating transactions on the host chain at execution time). 5. During the `OnChanOpenAck` callback on the controller chain a record of the interchain account address registered on the host chain during `OnChanOpenTry` is set in state with a mapping from (controller portID, controller connectionID) -> interchain account address. See [metadata negotiation](#metadata-negotiation) section below for how to implement this. 6. During the `OnChanOpenAck` & `OnChanOpenConfirm` callbacks on the controller & host chains respectively, the [active-channel](#active-channels) for this interchain account/owner pair, is set in state. #### Active channels The controller and host chain must keep track of an `active-channel` for each registered interchain account. The `active-channel` is set during the channel creation handshake process. This is a safety mechanism that allows a controller chain to regain access to an interchain account on a host chain in case of a channel closing. An example of an active channel on the controller chain can look like this: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { // Controller Chain SourcePortId: `icacontroller-`, SourceChannelId: ``, // Host Chain CounterpartyPortId: `icahost`, CounterpartyChannelId: ``, } ``` In the event of a channel closing, the active channel may be replaced by starting a new channel handshake with the same port identifiers on the same underlying connection of the original active channel. ICS-27 channels can only be closed in the event of a timeout (if the implementation uses ordered channels) or in the unlikely event of a light client attack. Controller chains must retain the ability to open new ICS-27 channels and reset the active channel for a particular portID (containing `{owner-account-address}`) and connectionID pair. The controller and host chains must verify that any new channel maintains the same metadata as the previous active channel to ensure that the parameters of the interchain account remain the same even after replacing the active channel. The `Address` of the metadata should not be verified since it is expected to be empty at the INIT stage, and the host chain will regenerate the exact same address on TRY, because it is expected to generate the interchain account address deterministically from the controller portID and connectionID (both of which must remain the same). #### **Metadata negotiation** ICS-27 takes advantage of [ICS-04 channel version negotiation](/ibc/latest/spec/core/ics-004-channel-and-packet-semantics/README) to negotiate metadata and channel parameters during the channel handshake. The metadata will contain the encoding format along with the transaction type so that the counterparties can agree on the structure and encoding of the interchain transactions. The metadata sent from the host chain on the TRY step will also contain the interchain account address, so that it can be relayed to the controller chain. At the end of the channel handshake, both the controller and host chains will store a mapping of (controller chain portID, controller/host connectionID) to the newly registered interchain account address ([account registration flow](#register-account-flow)). ICS-04 allows for each channel version negotiation to be application-specific. In the case of interchain accounts, the channel version will be a string of a JSON struct containing all the relevant metadata intended to be relayed to the counterparty during the channel handshake step ([see summary below](#metadata-negotiation-summary)). Combined with the one channel per interchain account approach, this method of metadata negotiation allows us to pass the address of the interchain account back to the controller chain and create a mapping from (controller portID, controller connection ID) -> interchain account address during the `OnChanOpenAck` callback. As outlined in the [controlling flow](#controlling-flow), a controller chain will need to know the address of a registered interchain account in order to send transactions to the account on the host chain. #### **Metadata negotiation summary** `interchain-account-address` is the address of the interchain account registered on the host chain by the controller chain. * **INIT** Initiator: Controller Datagram: ChanOpenInit Chain Acted Upon: Controller Version: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "Version": "ics27-1", "ControllerConnectionId": "self_connection_id", "HostConnectionId": "counterparty_connection_id", "Address": "", "Encoding": "requested_encoding_type", "TxType": "requested_tx_type", } ``` Comments: The address is left empty since this will be generated and relayed back by the host chain. The connection identifiers must be included to ensure that if a new channel needs to be opened (in case active channel times out), then we can ensure that the new channel is opened on the same connection. This will ensure that the interchain account is always connected to the same counterparty chain. * **TRY** Initiator: Relayer Datagram: ChanOpenTry Chain Acted Upon: Host Version: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "Version": "ics27-1", "ControllerConnectionId": "counterparty_connection_id", "HostConnectionId": "self_connection_id", "Address": "interchain_account_address", "Encoding": "negotiated_encoding_type", "TxType": "negotiated_tx_type", } ``` Comments: The ICS-27 application on the host chain is responsible for returning this version given the counterparty version set by the controller chain in INIT. The host chain must agree with the single encoding type and a single tx type that is requested by the controller chain (ie. included in counterparty version). If the requested encoding or tx type is not supported, then the host chain must return an error and abort the handshake. The host chain must also generate the interchain account address and populate the address field in the version with the interchain account address string. * **ACK** Initiator: Relayer Datagram: ChanOpenAck Chain Acted Upon: Controller CounterpartyVersion: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "Version": "ics27-1", "ControllerConnectionId": "self_connection_id", "HostConnectionId": "counterparty_connection_id", "Address": "interchain_account_address", "Encoding": "negotiated_encoding_type", "TxType": "negotiated_tx_type", } ``` Comments: On the ChanOpenAck step, the ICS27 application on the controller chain must verify the version string chosen by the host chain on ChanOpenTry. The controller chain must verify that it can support the negotiated encoding and tx type selected by the host chain. If either is unsupported, then it must return an error and abort the handshake. If both are supported, then the controller chain must store a mapping from the channel's portID to the provided interchain account address and return successfully. #### Controlling flow Once an interchain account is registered on the host chain a controller chain can begin sending instructions (messages) to the host chain to control the account. 1. The controller chain calls `SendTx` and passes message(s) that will be executed on the host side by the associated interchain account (determined by the controller side port identifier) Cosmos SDK pseudo-code example: ```golang theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // connectionId is the identifier for the controller connection interchainAccountAddress := GetInterchainAccountAddress(portId, connectionId) msg := &banktypes.MsgSend{FromAddress: interchainAccountAddress, ToAddress: ToAddress, Amount: amount} icaPacketData = InterchainAccountPacketData{ Type: types.EXECUTE_TX, Data: serialize(msg), Memo: "memo", } // Sends the message to the host chain, where it will eventually be executed SendTx(ownerAddress, connectionId, portID, data, timeout) ``` 2. The host chain upon receiving the IBC packet will call `DeserializeTx`. 3. The host chain will then call `AuthenticateTx` and `ExecuteTx` for each message and return an acknowledgment containing a success or error. Messages are authenticated on the host chain by taking the controller side port identifier and calling `GetInterchainAccountAddress(controllerPortId, hostConnectionId)` to get the expected interchain account address for the current controller port and connection identifier. If the signer of this message does not match the expected account address then authentication will fail. ### Packet Data `InterchainAccountPacketData` contains an array of messages that an interchain account can execute and a memo string that is sent to the host chain as well as the packet `type`. ICS-27 version 1 has only one type `EXECUTE_TX`. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message InterchainAccountPacketData { enum type bytes data = 1; string memo = 2; } ``` The acknowledgment packet structure is defined as in [ics4](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto#L135-L148). If an error occurs on the host chain the acknowledgment contains the error message. ```proto theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} message Acknowledgement { // response contains either a result or an error and must be non-empty oneof response { bytes result = 21; string error = 22; } } ``` ### Custom logic ICS-27 relies on [ICS-30 middleware architecture](../ics-030-middleware) to provide the option for application developers to apply custom logic on the success or fail of ICS-27 packets. Controller chains will wrap `OnAcknowledgementPacket` & `OnTimeoutPacket` to handle the success or fail cases for ICS-27 packets. ### Port & channel setup The interchain account module on a host chain must always bind to a port with the id `icahost`. Controller chains will bind to ports dynamically, as specified in the identifier format [section](#identifier-formats). The example below assumes a module is implementing the entire `InterchainAccountModule` interface. The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialized) to bind to the appropriate port. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function setup() { capability = routingModule.bindPort("icahost", ModuleCallbacks{ onChanOpenInit, onChanOpenTry, onChanOpenAck, onChanOpenConfirm, onChanCloseInit, onChanCloseConfirm, onChanUpgradeInit, // read-only onChanUpgradeTry, // read-only onChanUpgradeAck, // read-only onChanUpgradeOpen, onRecvPacket, onTimeoutPacket, onAcknowledgePacket, onTimeoutPacketClose }) claimCapability("port", capability) } ``` Once the `setup` function has been called, channels can be created via the IBC routing module. ### Channel lifecycle management An interchain account module will accept new channels from any module on another machine, if and only if the channel initialization step is being invoked from the controller chain. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller Chain by InitInterchainAccount function onChanOpenInit( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string ): (version: string, err: Error) { // validate port format abortTransactionUnless(validateControllerPortParams(portIdentifier)) // only allow channels to be created on the "icahost" port on the counterparty chain abortTransactionUnless(counterpartyPortIdentifier === "icahost") // retrieve channel and connection to access connection ID and counterparty connection ID channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) connectionId = channel.connectionHops[0] connection = provableStore.get(connectionPath(connectionId)) if version != "" { // validate metadata metadata = UnmarshalJSON(version) abortTransactionUnless(metadata.Version === "ics27-1") // all elements in encoding list and tx type list must be supported abortTransactionUnless(IsSupportedEncoding(metadata.Encoding)) abortTransactionUnless(IsSupportedTxType(metadata.TxType)) abortTransactionUnless(metadata.ControllerConnectionId === connectionId) abortTransactionUnless(metadata.HostConnectionId === connection.counterpartyConnectionIdentifier) } else { // construct default metadata metadata = { Version: "ics27-1", ControllerConnectionId: connectionId, HostConnectionId: counterpartyConnectionId, // implementation may choose a default encoding and TxType // e.g. DefaultEncoding=protobuf, DefaultTxType=sdk.MultiMsg Encoding: DefaultEncoding, TxType: DefaultTxType, } version = marshalJSON(metadata) } // only open the channel if: // - there is no active channel already set (with status OPEN) // OR // - there is already an active channel (with status CLOSED) AND // the metadata matches exactly the existing metadata in the // version string of the active channel AND the ordering of the // new channel matches the ordering of the active channel. activeChannelId, activeChannelFound = GetActiveChannelID(portId, connectionId) if activeChannelFound { activeChannel = provableStore.get(channelPath(portId, activeChannelId)) abortTransactionUnless(channel !== null) abortTransactionUnless(activeChannel.state === CLOSED) previousOrder = activeChannel.order abortTransactionUnless(previousOrder === order) previousMetadata = UnmarshalJSON(activeChannel.version) abortTransactionUnless(previousMetadata === metadata) } return version, nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Host Chain by Relayer function onChanOpenTry( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string ): (version: string, err: Error) { // validate port ID abortTransactionUnless(portIdentifier === "icahost") // retrieve channel and connection to access connection ID and counterparty connection ID channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) connectionId = channel.connectionHops[0] connection = provableStore.get(connectionPath(connectionId)) // create the interchain account with the counterpartyPortIdentifier // and the underlying connectionID on the host chain. address = RegisterInterchainAccount(counterpartyPortIdentifier, connectionId) // state change to keep track of successfully registered interchain account SetInterchainAccountAddress(counterpartyPortIdentifier, connectionId, address) cpMetadata = UnmarshalJSON(counterpartyVersion) // it's not mandatory for the controller to fill in the host connection ID, since // it could not be possible for it to know it. ibc-go's implementation of the // controller does fill it in, but an CosmWasm controller implementation would // not be able. For that reason, the host fills in here its own connection ID. cpMetadata.HostConnectionId = connectionId abortTransactionUnless(cpMetadata.Version === "ics27-1") // If encoding or txType requested by initializing chain is not supported by host chain then // fail handshake and abort transaction abortTransactionUnless(IsSupportedEncoding(cpMetadata.Encoding)) abortTransactionUnless(IsSupportedTxType(cpMetadata.TxType)) abortTransactionUnless(cpMetadata.ControllerConnectionId === connection.counterpartyConnectionIdentifier) abortTransactionUnless(cpMetadata.HostConnectionId === connectionId) metadata = { "Version": "ics27-1", "ControllerConnectionId": cpMetadata.ControllerConnectionId, "HostConnectionId": cpMetadata.HostConnectionId, "Address": address, "Encoding": cpMetadata.Encoding, "TxType": cpMetadata.TxType, } return string(MarshalJSON(metadata)), nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller Chain by Relayer function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyChannelIdentifier, counterpartyVersion: string ) { // retrieve channel and connection to access connection ID and counterparty connection ID channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) connectionId = channel.connectionHops[0] connection = provableStore.get(connectionPath(connectionId)) // validate counterparty metadata decided by host chain metadata = UnmarshalJSON(version) abortTransactionUnless(metadata.Version === "ics27-1") abortTransactionUnless(IsSupportedEncoding(metadata.Encoding)) abortTransactionUnless(IsSupportedTxType(metadata.TxType)) abortTransactionUnless(metadata.ControllerConnectionId === connectionId) abortTransactionUnless(metadata.HostConnectionId === connection.counterpartyConnectionIdentifier) // state change to keep track of successfully registered interchain account SetInterchainAccountAddress(portID, metadata.ControllerConnectionId, metadata.Address) // set the active channel for this owner/interchain account pair SetActiveChannelID(portIdentifier, metadata.ControllerConnectionId, channelIdentifier) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Host Chain by Relayer function onChanOpenConfirm( portIdentifier: Identifier, channelIdentifier: Identifier ) { channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel !== null) // set the active channel for this owner/interchain account pair SetActiveChannelID(channel.counterpartyPortIdentifier, channel.connectionHops[0], channelIdentifier) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // The controller portID must have the format: `icacontroller-{ownerAddress}` function validateControllerPortParams(portIdentifier: Identifier) { split(portIdentifier, "-") abortTransactionUnless(portIdentifier[0] === "icacontroller") abortTransactionUnless(IsValidAddress(portIdentifier[1])) } ``` ### Closing handshake ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanCloseInit( portIdentifier: Identifier, channelIdentifier: Identifier) { // disallow user-initiated channel closing for interchain account channels abortTransactionUnless(FALSE) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanCloseConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { } ``` ### Upgrade handshake ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller Chain by Authority function onChanUpgradeInit( portIdentifier: Identifier, channelIdentifier: Identifier, order: ChannelOrder, connectionHops: [Identifier], upgradeSequence: uint64, version: string ): (version: string, err: Error) { // new version proposed in the upgrade abortTransactionUnless(version !== "") metadata = UnmarshalJSON(version) // retrieve the existing channel version. // In ibc-go, for example, this is done using the GetAppVersion // function of the ICS4Wrapper interface. // See https://github.com/cosmos/ibc-go/blob/ac6300bd857cd2bd6915ae51e67c92848cbfb086/modules/core/05-port/types/module.go#L128-L132 channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel !== null) currentMetadata = UnmarshalJSON(channel.version) // validate metadata abortTransactionUnless(metadata.Version === "ics27-1") // all elements in encoding list and tx type list must be supported abortTransactionUnless(IsSupportedEncoding(metadata.Encoding)) abortTransactionUnless(IsSupportedTxType(metadata.TxType)) // the interchain account address on the host chain // must remain the same after the upgrade. abortTransactionUnless(currentMetadata.Address === metadata.Address) // at the moment it is not supported to perform upgrades that // change the connection ID of the controller or host chains. // therefore these connection IDs much remain the same as before. abortTransactionUnless(currentMetadata.ControllerConnectionId === metadata.ControllerConnectionId) abortTransactionUnless(currentMetadata.HostConnectionId === metadata.HostConnectionId) // the proposed connection hop must not change abortTransactionUnless(currentMetadata.ControllerConnectionId === connectionHops[0]) return version, nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Host Chain by Relayer function onChanUpgradeTry( portIdentifier: Identifier, channelIdentifier: Identifier, order: ChannelOrder, connectionHops: [Identifier], upgradeSequence: uint64, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string ): (version: string, err: Error) { // validate port ID abortTransactionUnless(portIdentifier === "icahost") // upgrade version proposed by counterparty abortTransactionUnless(counterpartyVersion !== "") metadata = UnmarshalJSON(counterpartyVersion) // retrieve the existing channel version. // In ibc-go, for example, this is done using the GetAppVersion // function of the ICS4Wrapper interface. // See https://github.com/cosmos/ibc-go/blob/ac6300bd857cd2bd6915ae51e67c92848cbfb086/modules/core/05-port/types/module.go#L128-L132 channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel !== null) currentMetadata = UnmarshalJSON(channel.version) // validate metadata abortTransactionUnless(metadata.Version === "ics27-1") // all elements in encoding list and tx type list must be supported abortTransactionUnless(IsSupportedEncoding(metadata.Encoding)) abortTransactionUnless(IsSupportedTxType(metadata.TxType)) // the interchain account address on the host chain // must remain the same after the upgrade. abortTransactionUnless(currentMetadata.Address === metadata.Address) // at the moment it is not supported to perform upgrades that // change the connection ID of the controller or host chains. // therefore these connection IDs much remain the same as before. abortTransactionUnless(currentMetadata.ControllerConnectionId === metadata.ControllerConnectionId) abortTransactionUnless(currentMetadata.HostConnectionId === metadata.HostConnectionId) // the proposed connection hop must not change abortTransactionUnless(currentMetadata.HostConnectionId === connectionHops[0]) return counterpartyVersion, nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller Chain by Relayer function onChanUpgradeAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyVersion: string ): Error { // final upgrade version proposed by counterparty abortTransactionUnless(counterpartyVersion !== "") metadata = UnmarshalJSON(counterpartyVersion) // retrieve the existing channel version. // In ibc-go, for example, this is done using the GetAppVersion // function of the ICS4Wrapper interface. // See https://github.com/cosmos/ibc-go/blob/ac6300bd857cd2bd6915ae51e67c92848cbfb086/modules/core/05-port/types/module.go#L128-L132 channel = provableStore.get(channelPath(portIdentifier, channelIdentifier)) abortTransactionUnless(channel !== null) currentMetadata = UnmarshalJSON(channel.version) // validate metadata abortTransactionUnless(metadata.Version === "ics27-1") // all elements in encoding list and tx type list must be supported abortTransactionUnless(IsSupportedEncoding(metadata.Encoding)) abortTransactionUnless(IsSupportedTxType(metadata.TxType)) // the interchain account address on the host chain // must remain the same after the upgrade. abortTransactionUnless(currentMetadata.Address === metadata.Address) // at the moment it is not supported to perform upgrades that // change the connection ID of the controller or host chains. // therefore these connection IDs much remain the same as before. abortTransactionUnless(currentMetadata.ControllerConnectionId === metadata.ControllerConnectionId) abortTransactionUnless(currentMetadata.HostConnectionId === metadata.HostConnectionId) return nil } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller and Host Chains by Relayer function onChanUpgradeOpen( portIdentifier: Identifier, channelIdentifier: Identifier) { // no-op } ``` ### Packet relay `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Host Chain by Relayer function onRecvPacket(packet Packet) { ack = NewResultAcknowledgement([]byte{byte(1)}) // only attempt the application logic if the packet data // was successfully decoded switch data.Type { case types.EXECUTE_TX: msgs, err = types.DeserializeTx(data.Data) if err != nil { return NewErrorAcknowledgement(err) } // ExecuteTx calls the AuthenticateTx function defined above result, err = ExecuteTx(ctx, packet.sourcePort, packet.destPort, packet.destChannel, msgs) if err != nil { // NOTE: The error string placed in the acknowledgement must be consistent across all // nodes in the network or there will be a fork in the state machine. return NewErrorAcknowledgement(err) } // return acknowledgement containing the transaction result after executing on host chain return NewAcknowledgement(result) default: return NewErrorAcknowledgement(ErrUnknownDataType) } } ``` `onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller Chain by Relayer function onAcknowledgePacket( packet: Packet, acknowledgement: bytes ) { // call underlying app's OnAcknowledgementPacket callback // see ICS-30 middleware for more information } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller Chain by Relayer function onTimeoutPacket(packet: Packet) { // call underlying app's OnTimeoutPacket callback // see ICS-30 middleware for more information } ``` Note that interchain accounts controller modules should not execute any logic upon packet receipt, i.e. the `OnRecvPacket` callback should not be called, and in case it is called, it should simply return an error acknowledgement: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Called on Controller Chain by Relayer function onRecvPacket(packet Packet) { return NewErrorAcknowledgement(ErrInvalidChannelFlow) } ``` ### Identifier formats These are the default formats that the port identifiers on each side of an interchain accounts channel. The controller portID **must** include the owner address so that when a message is sent to the controller module, the sender of the message can be verified against the portID before sending the ICA packet. The controller chain is responsible for proper access control to ensure that the sender of the ICA message has successfully authenticated before the message reaches the controller module. Controller Port Identifier: optional prefix `icacontroller-` + mandatory `{owner-account-address}` Host Port Identifier: `icahost` The `icacontroller-` prefix on the controller port identifier is optional and host chains **must** not enforce that the counterparty port identifier includes it. Controller chains may decide to include it and validate that it is present in their own port identifier. ## Example Implementations * Implementation of ICS 27 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go). ## Future Improvements A future version of interchain accounts may be greatly simplified by the introduction of an IBC channel type that is ORDERED but does not close the channel on timeouts, and instead proceeds to accept and receive the next packet. If such a channel type is made available by core IBC, Interchain accounts could require the use of this channel type and remove all logic and state pertaining to "active channels". The metadata format can also be simplified to remove any reference to the underlying connection identifiers. The "active channel" setting and unsetting is currently necessary to allow interchain account owners to create a new channel in case the current active channel closes during channel timeout. The connection identifiers are part of the metadata to ensure that any new channel that gets opened are established on top of the original connection. All of this logic becomes unnecessary once the channel is ordered **and** unclosable, which can only be achieved by the introduction of a new channel type to core IBC. ## History Aug 1, 2019 - Concept discussed Sep 24, 2019 - Draft suggested Nov 8, 2019 - Major revisions Dec 2, 2019 - Minor revisions (Add more specific description & Add interchain account on Ethereum) July 14, 2020 - Major revisions April 27, 2021 - Redesign of ics27 specification November 11, 2021 - Update with latest changes from implementation December 14, 2021 - Revisions to spec based on audits and maintainer reviews August 1, 2023 - Implemented channel upgrades callbacks ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # Cross-Chain Validation Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-028-cross-chain-validation/README # Synopsis This standard document specifies packet data structure, state machine handling logic, and encoding details for Cross-Chain Validation (CCV). CCV is the specific IBC level protocol that enables *Interchain Security*, a Cosmos-specific category of *Shared Security*. At a high level, CCV enables a *provider chain* (e.g., the Cosmos Hub) to provide *security* to multiple *consumer chains*. This means that the validator sets on the consumer chains are chosen from the validator set of the provider chain (for more details, see the [Security Model](/ibc/latest/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts#security-model) section). The communication between the provider and the consumer chains is done through the IBC protocol over a *unique*, *ordered* channel (one for each consumer chain). > Throughout this document, we will use the terms chain and blockchain interchangeably. ## Contents * [Overview and Basic Concepts](/ibc/latest/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts) * [System Model and Properties](/ibc/latest/spec/app/ics-028-cross-chain-validation/system_model_and_properties) * [Technical Specification: Data Structures and Methods](/ibc/latest/spec/app/ics-028-cross-chain-validation/technical_specification) * [Data Structures](/ibc/latest/spec/app/ics-028-cross-chain-validation/data_structures) * [Methods](/ibc/latest/spec/app/ics-028-cross-chain-validation/methods) ## Example Implementations * Interchain Security [Go implementation](https://github.com/cosmos/interchain-security). ## History Jun 27, 2022 - Draft written Aug 3, 2022 - Revision of *Bond-Based Consumer Voting Power* property Aug 29, 2022 - Notify Staking module of matured unbondings in `EndBlock()` Dec 2, 2022 - Enable existing chains to become consumer chains Dec 7, 2022 - Add provider-based timeouts ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # CCV: Technical Specification - Data Structures Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-028-cross-chain-validation/data_structures ## Outline * [External Data Structures](#external-data-structures) * [CCV Data Structures](#ccv-data-structures) * [CCV Packets](#ccv-packets) * [CCV State](#ccv-state) * [State on Provider Chain](#state-on-provider-chain) * [State on Consumer Chain](#state-on-consumer-chain) ## External Data Structures [↑ Back to Outline](#outline) This section describes external data structures used by the CCV module. The CCV module uses the ABCI `ValidatorUpdate` data structure, which consists of a validator and its power (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/abci/abci.md#data-types)), i.e., ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ValidatorUpdate { pubKey: PublicKey power: int64 } ``` The provider chain sends to the consumer chain a list of `ValidatorUpdate`s, containing an entry for every validator that had its power updated. The data structures required for creating clients (i.e., `ClientState`, `ConsensusState`) are defined in [ICS 2](../../core/ics-002-client-semantics). In the context of CCV, every chain is uniquely defined by their chain ID and the validator set. Thus, CCV requires the `ClientState` to contain the chain ID and the `ConsensusState` for a particular height to contain the validator set at that height. In addition, the `ClientState` should contain the `UnbondingPeriod`. For an example, take a look at the `ClientState` and `ConsensusState` defined in [ICS 7](../../client/ics-007-tendermint-client). ## CCV Data Structures [↑ Back to Outline](#outline) The CCV module is initialized through the `InitGenesis` method when the chain is first started. The initialization is done from a genesis state. This is the case for both provider and consumer chains: * On the provider chain, the genesis state is described by the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ProviderGenesisState { // a list of existing consumer chains consumerStates: [ConsumerState] } ``` with `ConsumerState` defined as ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ConsumerState { chainId: string channelId: Identifier } ``` * On the consumer chain, the genesis state is described by the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ConsumerGenesisState { preCCV: Bool unbondingPeriod: Duration connId: Identifier providerClientState: ClientState providerConsensusState: ConsensusState counterpartyClientId: Identifier initialValSet: [ValidatorUpdate] transferChannelId: Identifier } ``` * `preCCV` is a flag indicating whether the consumer CCV module starts in pre-CCV state. In pre-CCV state the consumer CCV module MUST NOT pass validator updates to the underlying consensus engine. If `preCCV == true`, then `connId` must be set. * `unbondingPeriod` is the unbonding period on the consumer chain. * `connId` is the ID of the connection end on the consumer chain on top of which the CCV channel will be established. If `connId == ""`, a new client of the provider chain and a new connection on top of this client are created. * `providerClientState` is the client state used to create a new client of the provider chain (as defined in [ICS 2](../../core/ics-002-client-semantics)). If `connId != ""`, then `providerClientState` is ignored. * `providerConsensusState` is the consensus state used to create a new client of the provider chain (as defined in [ICS 2](../../core/ics-002-client-semantics)). If `connId != ""`, then `providerConsensusState` is ignored. * `counterpartyClientId` is the ID of the client of the consumer chain on the provider chain. Note that `counterpartyClientId` is only needed to allow the consumer CCV module to initiate the connection opening handshake. If `connId != ""`, then `counterpartyClientId` is ignored. * `initialValSet` is the first validator set that will start validating on this consumer chain. * `transferChannelId` is the ID of a token transfer channel (as defined in [ICS 20](../../app/ics-020-fungible-token-transfer)) used for the Reward Distribution sub-protocol. If `transferChannelId == ""`, a new token transfer channel is created on top of the same connection as the CCV channel. The provider CCV module handles governance proposals to add new consumer chains and to remove existing consumer chains. While the structure of governance proposals is specific to every ABCI application (for an example, see the `Proposal` interface in the [Governance module documentation](/sdk/latest/modules/gov/README) of Cosmos SDK), this specification expects the following fields to be part of the proposals to add new consumer chains (i.e., `ConsumerAdditionProposal`) and to remove existing ones (i.e., `ConsumerRemovalProposal`): ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ConsumerAdditionProposal { chainId: string spawnTime: Timestamp connId: Identifier unbondingPeriod: Duration transferChannelId: Identifier lockUnbondingOnTimeout: Bool } ``` * `chainId` is the proposed chain ID of the new consumer chain. It must be different from all other consumer chain IDs of the executing provider chain. * `spawnTime` is the time on the provider chain at which the consumer chain genesis is finalized and all validators are responsible to start their consumer chain validator node. * `connId` is the ID of the connection end on the provider chain on top of which the CCV channel will be established. If `connId == ""`, a new client of the consumer chain and a new connection on top of this client are created. Note that a sovereign chain can transition to a consumer chain while maintaining existing IBC channels to other chains by providing a valid `connId`. * `unbondingPeriod` is the unbonding period on the consumer chain. * `transferChannelId` is the ID of a token transfer channel (as defined in [ICS 20](../../app/ics-020-fungible-token-transfer)) used for the Reward Distribution sub-protocol. If `transferChannelId == ""`, a new token transfer channel is created on top of the same connection as the CCV channel. Note that `transferChannelId` is the ID of the channel end on the consumer chain. * `lockUnbondingOnTimeout` is a boolean value that indicates whether the funds corresponding to the outstanding unbonding operations are to be released in case of a timeout. If `lockUnbondingOnTimeout == true`, a governance proposal to stop the timed out consumer chain would be necessary to release the locked funds. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface ConsumerRemovalProposal { chainId: string stopTime: Timestamp } ``` * `chainId` is the chain ID of the consumer chain to be removed. It must be the ID of an existing consumer chain of the executing provider chain. * `stopTime` is the time on the provider chain at which all validators are responsible to stop their consumer chain validator node. During the CCV channel opening handshake, the provider chain adds the address of its distribution module account to the channel version as metadata (as described in [ICS 4](../../core/ics-004-channel-and-packet-semantics/README#definitions)). The metadata structure is described by the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface CCVHandshakeMetadata { providerDistributionAccount: string // the account's address version: string } ``` This specification assumes that the provider CCV module has access to the address of the distribution module account through the `GetDistributionAccountAddress()` method. For an example, take a look at the [auth module](/sdk/latest/modules/auth/auth) of Cosmos SDK. ## CCV Packets [↑ Back to Outline](#outline) The structure of the packets sent through the CCV channel is defined by the `Packet` interface in [ICS 4](../../core/ics-004-channel-and-packet-semantics). The following packet data types are required by the CCV module: * `VSCPacketData` contains a list of validator updates, i.e., ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface VSCPacketData { // the id of this VSC id: uint64 // validator updates updates: [ValidatorUpdate] // downtime slash requests acknowledgements, // i.e., list of validator addresses downtimeSlashAcks: [string] } ``` * `VSCMaturedPacketData` contains the ID of the VSC that reached maturity, i.e., ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface VSCMaturedPacketData { id: uint64 // the id of the VSC that reached maturity } ``` * `SlashPacketData` contains a request to slash a validator, i.e., ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface SlashPacketData { valAddress: string // validator address, i.e., the hash of its public key valPower: int64 vscId: uint64 downtime: Bool } ``` > Note that for brevity we use e.g., `VSCPacket` to refer to a packet with `VSCPacketData` as its data. Packets are acknowledged by the remote side by sending back an `Acknowledgement` that contains either a result (in case of success) or an error (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics/README#acknowledgement-envelope)). The following acknowledgement types are required by the CCV module: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} type VSCPacketAcknowledgement = VSCPacketSuccess | VSCPacketError; type VSCMaturedPacketAcknowledgement = VSCMaturedPacketSuccess | VSCMaturedPacketError; type SlashPacketAcknowledgement = SlashPacketSuccess | SlashPacketError; type PacketAcknowledgement = PacketSuccess | PacketError; // general ack ``` ## CCV State [↑ Back to Outline](#outline) This section describes the internal state of the CCV module. For simplicity, the state is described by a set of variables; for each variable, both the type and a brief description is provided. In practice, all the state (except for hardcoded constants, e.g., `ProviderPortId`) is stored in a key/value store (KVS). The host state machine provides a KVS interface with three functions, i.e., `get()`, `set()`, and `delete()` (as defined in [ICS 24](../../core/ics-024-host-requirements)). * `ccvVersion = "ccv-1"` is the CCV expected version. Both the provider and the consumer chains need to agree on this version. * `zeroTimeoutHeight = {0,0}` is the `timeoutHeight` (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)) used by CCV for sending packets. Note that CCV uses `ccvTimeoutTimestamp` for sending CCV packets and `transferTimeoutTimestamp` for transferring tokens. * `ccvTimeoutTimestamp: uint64` is the `timeoutTimestamp` (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)) for sending CCV packets. The CCV protocol is responsible of setting `ccvTimeoutTimestamp` such that the *Correct Relayer* assumption is feasible. * `transferTimeoutTimestamp: uint64` is the `timeoutTimestamp` (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)) for transferring tokens. ### State on Provider Chain [↑ Back to Outline](#outline) * `ProviderPortId = "provider"` is the port ID the provider CCV module is expected to bind to. * `initTimeout: uint64` is the maximum time duration the Channel Initialization subprotocol may execute, i.e., for any consumer chain, if the CCV channel is not established within `initTimeout` since the consumer chain was registered, then the consumer chain is removed. * `vscTimeout: uint64` is the maximum time duration between sending any `VSCPacket` to any consumer chain and receiving the corresponding `VSCMaturedPacket`, without timing out the consumer chain and consequently removing it. * `pendingConsumerAdditionProposals: [ConsumerAdditionProposal]` is a list of pending governance proposals to add new consumer chains. * `pendingConsumerRemovalProposals: [ConsumerRemovalProposal]` is a list of pending governance proposals to remove existing consumer chains. Both lists of pending governance proposals expose the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface [Proposal] { // append a proposal to the list; the list is modified Append(p: Proposal) // remove a proposal from the list; the list is modified Remove(p: Proposal) } ``` * `lockUnbondingOnTimeout: Map` is a mapping from consumer chain IDs to the boolean values indicating whether the funds corresponding to the in progress unbonding operations are to be released in case of a timeout. * `chainToClient: Map` is a mapping from consumer chain IDs to the associated client IDs. * `chainToConnection: Map` is a mapping from consumer chain IDs to the associated connection IDs. * `chainToChannel: Map` is a mapping from consumer chain IDs to the CCV channel IDs. * `channelToChain: Map` is a mapping from CCV channel IDs to consumer chain IDs. * `initTimeoutTimestamps: Map` is a mapping from consumer chain IDs to init timeout timestamps, see `initTimeout`. * `pendingVSCPackets: Map` is a mapping from consumer chain IDs to a list of pending `VSCPacketData`s that must be sent to the consumer chain once the CCV channel is established. The map exposes the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Map { // append a VSCPacketData to the list mapped to chainId; // the list is modified Append(chainId: string, data: VSCPacketData) // remove all the VSCPacketData mapped to chainId; // the list is modified Remove(chainId: string) } ``` * `vscId: uint64` is a monotonic strictly increasing and positive ID that is used to uniquely identify the VSCs sent to the consumer chains. Note that `0` is used as a special ID for the mapping from consumer heights to provider heights. * `vscSendTimestamps: Map<(string, uint64), uint64>` is a mapping from `(chainId, vscId)` tuples to the timestamps of sending `VSCPacket`s. * `initialHeights: Map` is a mapping from consumer chain IDs to the heights on the provider chain. For every consumer chain, the mapping stores the height when the CCV channel to that consumer chain is established. Note that the provider validator set at this height matches the validator set at the height when the first VSC is provided to that consumer chain. It enables the mapping from consumer heights to provider heights. * `VSCtoH: Map` is a mapping from VSC IDs to heights on the provider chain. It enables the mapping from consumer heights to provider heights, i.e., the voting power at height `VSCtoH[id]` on the provider chain was last updated by the validator updates contained in the VSC with ID `id`. * `unbondingOps: Map` is a mapping that enables accessing for every unbonding operation the list of consumer chains that are still unbonding. When unbonding operations are initiated, the Staking module calls the `AfterUnbondingInitiated()` [hook](./methods#ccv-pcf-hook-afubopcr1)); this leads to the creation of a new `UnbondingOperation`, which is defined as ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface UnbondingOperation { id: uint64 // list of consumer chain IDs that are still unbonding unbondingChainIds: [string] } ``` * `vscToUnbondingOps: Map<(string, uint64), [uint64]>` is a mapping from `(chainId, vscId)` tuples to a list of unbonding operation IDs. It enables the provider CCV module to match a `VSCMaturedPacket{vscId}`, received from a consumer chain with `chainId`, with the corresponding unbonding operations. As a result, `chainId` can be removed from the list of consumer chains that are still unbonding these operations. For more details see how received `VSCMaturedPacket`s [are handled](./methods#ccv-pcf-rcvmat1). * `maturedUnbondingOps: [uint64]` is a list of IDs of matured unbonding operations (from the perspective of the consumer chains), for which notifications can be sent to the Staking module (see `stakingKeeper.UnbondingCanComplete`). Note that `maturedUnbondingOps` is emptied at the end of each block. * `downtimeSlashRequests: Map` is a mapping from `chainId`s to lists of validator addresses, i.e., `downtimeSlashRequests[chainId]` contains all the validator addresses for which the provider chain received slash requests for downtime from the consumer chain with `chainId`. ### State on Consumer Chain [↑ Back to Outline](#outline) * `ConsumerPortId = "consumer"` is the port ID the consumer CCV module is expected to bind to. * `ConsumerUnbondingPeriod: Duration` is the unbonding period on the consumer chain. * `preCCV: Bool` is a flag indicating whether the consumer CCV module starts in pre-CCV state. In pre-CCV state, the consumer CCV module MUST NOT pass validator updates to the underlying consensus engine. * `providerClientId: Identifier` identifies the client of the provider chain (on the consumer chain) that the CCV channel is build upon. * `providerChannel: Identifier` identifies the consumer's channel end of the CCV channel. * `ccvValidatorSet: ` is a mapping that stores the validators in the validator set of the consumer chain. * `receivedVSCs: [VSCPacketData]` is a list of data items (i.e., `VSCPacketData`) received in `VSCPacket`s that are not yet applied. * `HtoVSC: Map` is a mapping from consumer chain heights to VSC IDs. It enables the mapping from consumer heights to provider heights., i.e., * if `HtoVSC[h] == 0`, then the voting power on the consumer chain at height `h` was setup at genesis during Channel Initialization; * otherwise, the voting power on the consumer chain at height `h` was updated by the VSC with ID `HtoVSC[h]`. * `maturingVSCs: [(uint64, uint64)]` is a list of `(id, ts)` tuples, where `id` is the ID of a VSC received via a `VSCPacket` and `ts` is the timestamp at which the VSC reaches maturity on the consumer chain. The list is used to keep track of when unbonding operations are matured on the consumer chain. It exposes the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface [(uint64, uint64)] { // add a VSC id with its maturity timestamp to the list; // the list is modified Add(id: uint64, ts: uint64) // return the list sorted by the maturity timestamps; // the original list is not modified SortedByMaturityTime(): [(uint64, uint64)] // remove (id, ts) from the list; // the list is modified Remove(id: uint64, ts: uint64) } ``` * `pendingSlashRequests: [SlashRequest]` is a list of pending `SlashRequest`s that must be sent to the provider chain once the CCV channel is established. A `SlashRequest` consist of a `SlashPacketData` and a flag indicating whether the request is for downtime slashing. The list exposes the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface SlashRequest { data: SlashPacketData downtime: Bool } interface [SlashRequest] { // append a SlashRequest to the list; // the list is modified Append(data: SlashRequest) // return the reverse list, i.e., latest SlashRequest first; // the original list is not modified Reverse(): [SlashRequest] // remove all the SlashRequest; // the list is modified RemoveAll() } ``` * `outstandingDowntime: ` is a mapping from validator addresses to boolean values. `outstandingDowntime[valAddr] == TRUE` entails that the consumer chain sent a request to slash for downtime the validator with address `valAddr`. `outstandingDowntime[valAddr]` is set to false once the consumer chain receives a confirmation that the downtime slash request was received by the provider chain, i.e., a `VSCPacket` that contains `valAddr` in `downtimeSlashAcks`. The mapping enables the consumer CCV module to avoid sending to the provider chain multiple slashing requests for the same downtime infraction. * `providerDistributionAccount: string` is the address of the distribution module account on the provider chain. It enables the consumer chain to transfer rewards to the provider chain. * `distributionChannelId: Identifier` is the ID of the distribution token transfer channel used for sending rewards to the provider chain. * `BlocksPerDistributionTransfer: int64` is the interval (in number of blocks) between two distribution token transfers. * `lastDistributionTransferHeight: Height` is the block height of the last distribution token transfer. * `ccvAccount: string` is the address of the CCV module account where a fraction of the consumer chain rewards are collected before being transferred to the provider chain. # CCV: Technical Specification - Methods Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-028-cross-chain-validation/methods ## Outline * [General Methods](#general-methods) * [BeginBlock and EndBlock](#beginblock-and-endblock) * [Packet Relay](#packet-relay) * [Sub-protocols](#sub-protocols) * [Initialization](#initialization) * [Consumer Chain Removal](#consumer-chain-removal) * [Validator Set Update](#validator-set-update) * [Consumer Initiated Slashing](#consumer-initiated-slashing) * [Reward Distribution](#reward-distribution) ## General Methods [↑ Back to Outline](#outline) To express the error conditions, the following specification of the sub-protocols uses the exception system of the host state machine, which is exposed through two functions (as defined in [ICS 24](../../core/ics-024-host-requirements)): `abortTransactionUnless` and `abortSystemUnless`. ### BeginBlock and EndBlock [↑ Back to Outline](#outline) The functions `BeginBlock()` and `EndBlock()` (see [Implemented Interfaces](./technical_specification#implemented-interfaces)) are split across the CCV sub-protocols. #### **\[CCV-PCF-BBLOCK.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the AppModule interface function BeginBlock() { BeginBlockInit() BeginBlockCCR() } ``` * **Caller** * The ABCI application. * **Trigger Event** * A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * `BeginBlockInit()` is invoked (see [\[CCV-PCF-BBLOCK-INIT.1\]](#ccv-pcf-bblock-init1), i.e., it contains the `BeginBlock()` logic needed for the Initialization sub-protocol). * `BeginBlockCCR()` is invoked (see [\[CCV-PCF-BBLOCK-CCR.1\]](#ccv-pcf-bblock-ccr1), i.e., it contains the `BeginBlock()` logic needed for the Consumer Chain Removal sub-protocol). * **Error Condition** * None. #### **\[CCV-PCF-EBLOCK.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the AppModule interface function EndBlock(): [ValidatorUpdate] { EndBlockCIS() EndBlockCCR() EndBlockVSU() // do not return anything to the consensus engine return [] } ``` * **Caller** * The ABCI application. * **Trigger Event** * An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * `EndBlockCIS()` is invoked (see [\[CCV-PCF-EBLOCK-CIS.1\]](#ccv-pcf-eblock-cis1), i.e., it contains the `EndBlock()` logic needed for the Consumer Initiated Slashing sub-protocol). * `EndBlockCCR()` is invoked (see [\[CCV-PCF-EBLOCK-CCR.1\]](#ccv-pcf-eblock-ccr1), i.e., it contains the `EndBlock()` logic needed for the Consumer Chain Removal sub-protocol). * `EndBlockVSU()` is invoked (see [\[CCV-PCF-EBLOCK-VSU.1\]](#ccv-pcf-eblock-vsu1), i.e., it contains the `EndBlock()` logic needed for the Validator Set Update sub-protocol). * **Error Condition** * None. > **Note**: The provider CCV module expects the provider Staking module to update its view of the validator set before the `EndBlock()` of the provider CCV module is invoked. > A solution is for the provider Staking module to update its view during `EndBlock()` and then, the `EndBlock()` of the provider Staking module to be executed before the `EndBlock()` of the provider CCV module. *** #### **\[CCV-CCF-BBLOCK.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the AppModule interface function BeginBlock() { BeginBlockInit() BeginBlockCCR() BeginBlockCIS() } ``` * **Caller** * The ABCI application. * **Trigger Event** * A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * `BeginBlockInit()` is invoked (see [\[CCV-CCF-BBLOCK-INIT.1\]](#ccv-ccf-bblock-init1), i.e., it contains the `BeginBlock()` logic needed for the Channel Initialization sub-protocol). * `BeginBlockCCR()` is invoked (see [\[CCV-CCF-BBLOCK-CCR.1\]](#ccv-ccf-bblock-ccr1), i.e., it contains the `BeginBlock()` logic needed for the Consumer Chain Removal sub-protocol). * `BeginBlockCIS()` is invoked (see [\[CCV-CCF-BBLOCK-CIS.1\]](#ccv-ccf-bblock-cis1), i.e., it contains the `BeginBlock()` logic needed for the Consumer Initiated Slashing sub-protocol). * **Error Condition** * None. #### **\[CCV-CCF-EBLOCK.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the AppModule interface function EndBlock(): [ValidatorUpdate] { EndBlockRD() // return the validator set updates to the consensus engine return EndBlockVSU() } ``` * **Caller** * The ABCI application. * **Trigger Event** * An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block. * **Precondition** * True. x * **Postcondition** * `EndBlockRD()` is invoked (see [\[CCV-PCF-EBLOCK-RD.1\]](#ccv-ccf-eblock-rd1), i.e., it contains the `EndBlock()` logic needed for the Reward Distribution sub-protocol). * `EndBlockVSU()` is invoked and the return value is returned to the consensus engine (see [\[CCV-CCF-EBLOCK-VSU.1\]](#ccv-ccf-eblock-vsu1), i.e., it contains the `EndBlock()` logic needed for the Validator Set Update sub-protocol). * **Error Condition** * None. ### Packet Relay [↑ Back to Outline](#outline) #### **\[CCV-PCF-RCVP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onRecvPacket(packet: Packet): bytes { switch typeof(packet.data) { case VSCMaturedPacketData: return onRecvVSCMaturedPacket(packet) case SlashPacketData: return onRecvSlashPacket(packet) default: // unexpected packet type return PacketError } } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives a packet on a channel owned by the provider CCV module. * **Precondition** * True. * **Postcondition** * If the packet is a `VSCMaturedPacket`, the acknowledgement obtained from invoking the `onRecvVSCMaturedPacket` method is returned. * If the packet is a `SlashPacket`, the acknowledgement obtained from invoking the `onRecvSlashPacket` method is returned. * Otherwise, an error acknowledgement is returned. * **Error Condition** * None. #### **\[CCV-PCF-ACKP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onAcknowledgePacket(packet: Packet, ack: bytes) { switch typeof(packet.data) { case VSCPacketData: onAcknowledgeVSCPacket(packet, ack) default: // unexpected packet type abortTransactionUnless(FALSE) } } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives an acknowledgement on a channel owned by the provider CCV module. * **Precondition** * True. * **Postcondition** * If the acknowledgement is for a `VSCPacket`, the `onAcknowledgeVSCPacket` method is invoked. * Otherwise, the transaction is aborted. * **Error Condition** * None. #### **\[CCV-PCF-TOP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onTimeoutPacket(packet Packet) { switch typeof(packet.data) { case VSCPacketData: onTimeoutVSCPacket(packet) default: // unexpected packet type abortTransactionUnless(FALSE) } } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * A packet sent on a channel owned by the provider CCV module timed out as a result of either * the timeout height or timeout timestamp passing on the consumer chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end)); * or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)). * **Precondition** * The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section). * **Postcondition** * If the timeout is for a `VSCPacket`, the `onTimeoutVSCPacket` method is invoked. * Otherwise, the transaction is aborted. * **Error Condition** * None. *** #### **\[CCV-CCF-RCVP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onRecvPacket(packet: Packet): bytes { switch typeof(packet.data) { case VSCPacketData: return onRecvVSCPacket(packet) default: // unexpected packet type return PacketError } } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives a packet on a channel owned by the consumer CCV module. * **Precondition** * True. * **Postcondition** * If the packet is a `VSCPacket`, the acknowledgement obtained from invoking the `onRecvVSCPacket` method is returned. * Otherwise, an error acknowledgement is returned. * **Error Condition** * None. #### **\[CCV-CCF-ACKP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onAcknowledgePacket(packet: Packet, ack: bytes) { switch typeof(packet.data) { case VSCMaturedPacketData: onAcknowledgeVSCMaturedPacket(packet, ack) case SlashPacketData: onAcknowledgeSlashPacket(packet, ack) default: // unexpected packet type abortTransactionUnless(FALSE) } } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives an acknowledgement on a channel owned by the consumer CCV module. * **Precondition** * True. * **Postcondition** * If the acknowledgement is for a `VSCMaturedPacket`, the `onAcknowledgeVSCMaturedPacket` method is invoked. * If the acknowledgement is for a `SlashPacket`, the `onAcknowledgeSlashPacket` method is invoked. * Otherwise, the transaction is aborted. * **Error Condition** * None. #### **\[CCV-CCF-TOP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onTimeoutPacket(packet Packet) { switch typeof(packet.data) { case VSCMaturedPacketData: onTimeoutVSCMaturedPacket(packet) case SlashPacketData: onTimeoutSlashPacket(packet) default: // unexpected packet type abortTransactionUnless(FALSE) } } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * A packet sent on a channel owned by the consumer CCV module timed out as a result of either * the timeout height or timeout timestamp passing on the provider chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end)); * or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)). * **Precondition** * The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section). * **Postcondition** * If the timeout is for a `VSCMaturedPacket`, the `onTimeoutVSCMaturedPacket` method is invoked. * If the timeout is for a `SlashPacket`, the `onTimeoutSlashPacket` method is invoked. * Otherwise, the transaction is aborted. * **Error Condition** * None. ## Sub-protocols ### Initialization [↑ Back to Outline](#outline) The *initialization* sub-protocol enables a provider chain and a consumer chain to create a CCV channel -- a unique, ordered IBC channel for exchanging packets. As a prerequisite, the initialization sub-protocol MUST create two IBC clients, one on the provider chain to the consumer chain and one on the consumer chain to the provider chain. This is necessary to verify the identity of the two chains (as long as the clients are trusted). #### **\[CCV-PCF-INITG.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the AppModule interface function InitGenesis(state: ProviderGenesisState): [ValidatorUpdate] { // bind to ProviderPortId port err = portKeeper.bindPort(ProviderPortId) // check whether the capability for the port can be claimed abortSystemUnless(err == nil) foreach cs in state.consumerStates { abortSystemUnless(validateChannelIdentifier(cs.channelId)) chainToChannel[cs.chainId] = cs.channelId channelToChain[cs.channelId] = cc.chainId } // do not return anything to the consensus engine return [] } ``` * **Caller** * The ABCI application. * **Trigger Event** * An `InitChain` message is received from the consensus engine; the `InitChain` message is sent when the provider chain is first started. * **Precondition** * The provider CCV module is in the initial state. * **Postcondition** * The capability for the port `ProviderPortId` is claimed. * For each consumer state in the `ProviderGenesisState`, the initial state is set, i.e., the following mappings `chainToChannel`, `channelToChain` are set. * **Error Condition** * The capability for the port `ProviderPortId` cannot be claimed. * For any consumer state in the `ProviderGenesisState`, the channel ID is not valid (cf. the validation function defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)). #### **\[CCV-PCF-HCAPROP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements governance proposal Handler function HandleConsumerAdditionProposal(p: ConsumerAdditionProposal) { // store the proposal as a pending addition proposal pendingConsumerAdditionProposals.Append(p) } ``` * **Caller** * `EndBlock()` method of Governance module. * **Trigger Event** * A governance proposal `ConsumerAdditionProposal` has passed (i.e., it got the necessary votes). * **Precondition** * True. * **Postcondition** * The proposal is appended to the list of pending addition proposals, i.e., `pendingConsumerAdditionProposals`. * **Error Condition** * None. #### **\[CCV-PCF-BBLOCK-INIT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function BeginBlockInit() { // iterate over the pending addition proposals and create // the consumer client if the spawn time has passed foreach p IN pendingConsumerAdditionProposals { if currentTimestamp() > p.spawnTime { CreateConsumerClient(p) pendingConsumerAdditionProposals.Remove(p) } } } ``` * **Caller** * The `BeginBlock()` method. * **Trigger Event** * A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * For each `ConsumerAdditionProposal` `p` in the list of pending addition proposals `pendingConsumerAdditionProposals`, if `currentTimestamp() > p.spawnTime`, then * `CreateConsumerClient(p)` is invoked; * `p` is removed from `pendingConsumerAdditionProposals`. * **Error Condition** * None. #### **\[CCV-PCF-CRCLIENT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // Utility method function CreateConsumerClient(p: ConsumerAdditionProposal) { // check that no other consumer chain with the same chain ID exists if p.chainId IN chainToClient.Keys() { // ignore governance proposal return } // set consumer chain initial validator set, i.e., // the validator set is the same as the validator set // from own consensus state at current height // // TODO: ownConsensusState.validatorSet VS consensusState.nextValidatorsHash // specify which validator set is used as the initial val set ownConsensusState = getConsensusState(getCurrentHeight()) initialValSet = ownConsensusState.validatorSet if p.connId != "" { // connection ID provided // check validity connectionEnd = provableStore.get("connections/{p.connId}") if connectionEnd == nil { // invalid proposal: cannot find connection return } clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState") if clientState.chainID != p.chainId { // invalid proposal: connection not to expected chain ID return } // store client ID chainToClient[p.chainId] = connectionEnd.clientIdentifier // store connection ID chainToConnection[p.chainId] = connId // create and store ConsumerGenesisState consumerGenesisState[p.chainId] = ConsumerGenesisState { // consumer chain MUST start in pre-CCV state, i.e., // the consumer CCV module MUST NOT pass validator updates // to the underlying consensus engine preCCV: true, unbondingPeriod: p.unbondingPeriod, connId: connectionEnd.counterpartyConnectionIdentifier, providerClientState: nil, providerConsensusState: nil, counterpartyClientId: "", initialValSet: initialValSet, transferChannelId: p.transferChannelId, } } else { // create client state clientState = ClientState{ chainId: p.chainId, unbondingPeriod: p.unbondingPeriod, // the height when the client was last updated is set to the first possible height; // for example, in the case of a Tendermint Client, this is Height{0, 1} (see ICS-7) latestHeight: 0, } // create consensus state consensusState = ConsensusState{ validatorSet: initialValSet, } // create consumer chain client and store it clientId = clientKeeper.CreateClient(clientState, consensusState) chainToClient[p.chainId] = clientId // create and store ConsumerGenesisState consumerGenesisState[p.chainId] = ConsumerGenesisState { // consumer chain MUST NOT start in pre-CCV state, i.e., // the consumer CCV module MUST pass validator updates // to the underlying consensus engine preCCV: false, unbondingPeriod: p.unbondingPeriod, connId: "", providerClientState: getHostClientState(getCurrentHeight()), providerConsensusState: ownConsensusState, counterpartyClientId: clientId, initialValSet: initialValSet, transferChannelId: p.transferChannelId, } } // store lockUnbondingOnTimeout flag lockUnbondingOnTimeout[p.chainId] = p.lockUnbondingOnTimeout // add init timeout timestamp for this consumer chain initTimeoutTimestamps[p.chainId] = currentTimestamp().Add(initTimeout) } ``` * **Caller** * Either `HandleConsumerAdditionProposal` (see [CCV-PCF-HCAPROP.1](#ccv-pcf-hcaprop1)) or `BeginBlockInit()` (see [CCV-PCF-BBLOCK-INIT.1](#ccv-pcf-bblock-init1)). * **Trigger Event** * A governance proposal `ConsumerAdditionProposal` `p` has passed (i.e., it got the necessary votes). * **Precondition** * `currentTimestamp() > p.spawnTime`. * **Postcondition** * If a client for `p.chainId` already exists, the state is not changed. * Otherwise, * the validator set of the provider chain own consensus state at current height is set as the initial validator set of the consumer chain; * if `p.connId` is set, then * if a connection end with ID `p.connId` cannot be found, the state is not changed; * otherwise, * if the connection with ID `p.connId` is not to the chain with ID `p.chainId`, the state is not changed; * otherwise, * both the client ID and connection ID are stored; * a `ConsumerGenesisState` is created and stored; * otherwise, * otherwise, * a client state is created with `chainId = p.chainId` and `unbondingPeriod = p.unbondingPeriod`; * a consensus state is created with `validatorSet` set to the initial validator set of the consumer chain; * a client of the consumer chain is created and the client ID is stored; * a `ConsumerGenesisState` is created and stored; * `lockUnbondingOnTimeout[p.chainId]` is set to `p.lockUnbondingOnTimeout`. * The init timeout timestamp is computed and stored in `initTimeoutTimestamps[p.chainId]`. * **Error Condition** * None. > **Note:** For the case when the `clientId` field of the `ConsumerAdditionProposal` is not set, creating a client of a remote chain requires a `ClientState` and a `ConsensusState` (for an example, take a look at [ICS 7](../../client/ics-007-tendermint-client)). > `ConsensusState` requires setting a validator set of the remote chain. > The provider chain uses the fact that the validator set of the consumer chain is the same as its own validator set. > > **Note:** Bootstrapping the consumer CCV module requires a `ConsumerGenesisState` (see the [CCV Data Structures](./data_structures#ccv-data-structures) section). The provider CCV module creates such a `ConsumerGenesisState` when handling a governance proposal `ConsumerAdditionProposal`. > > **Note:** If the channel initialization for a consumer chain exceeds the `initTimeout` period, then the provider chain removes that consumer. > As a result, all further attempts on the consumer side to established the CCV channel will fail. > This means that the consumer chain requires some sort of social consensus to either restart the process of becoming a consumer chain or transitioning back to a sovereign chain. #### **\[CCV-PCF-COINIT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenInit( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string): string { // the channel handshake MUST be initiated by consumer chain abortTransactionUnless(FALSE) } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives a `ChanOpenInit` message on a port the provider CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is always aborted; hence, the state is not changed. * **Error Condition** * None. #### **\[CCV-PCF-COTRY.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenTry( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string): string { // validate parameters: // - only ordered channels allowed abortTransactionUnless(order == ORDERED) // - require the portIdentifier to be the port ID the CCV module is bound to abortTransactionUnless(portIdentifier == ProviderPortId) // assert that the counterpartyPortIdentifier matches // the expected consumer port ID abortTransactionUnless(counterpartyPortIdentifier == ConsumerPortId) // assert that the counterpartyVersion matches the expected version abortTransactionUnless(counterpartyVersion == ccvVersion) // get the client state associated with the underlying client channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}") abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1) connId = channelEnd.connectionHops[0] connectionEnd = provableStore.get("connections/{connId}") clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState") if clientState.chainId IN chainToConnection.Keys() { // if a connection is stored for this consumer chain, // verify that the underlying connection is the expected one abortTransactionUnless(chainToConnection[clientState.chainId] == connId) } // verify that the underlying client is the expected client of the consumer chain abortTransactionUnless(chainToClient[clientState.chainId] == connectionEnd.clientIdentifier) // require that no other CCV channel exists for this consumer chain abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys()) return CCVHandshakeMetadata{ providerDistributionAccount: GetDistributionAccountAddress(), version: ccvVersion } } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives a `ChanOpenTry` message on a port the provider CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is aborted if any of the following conditions are true: * the channel is not ordered; * `portIdentifier != ProviderPortId`; * `counterpartyPortIdentifier != ConsumerPortId`; * `counterpartyVersion != ccvVersion`; * no channel with `portIdentifier` and `channelIdentifier` exists; * the channel has more than one connection hop; * a connection is stored for this consumer chain and doesn't match the underlying connection of this channel; * the channel is not built on top of the client created for this consumer chain; * another CCV channel for this consumer chain already exists. * A `CCVHandshakeMetadata` is returned, with `providerDistributionAccount` set to the address of the distribution module account on the provider chain and `version` set to `ccvVersion`. * The state is not changed. * **Error Condition** * None. #### **\[CCV-PCF-COACK.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyVersion: string) { // the channel handshake MUST be initiated by consumer chain abortTransactionUnless(FALSE) } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives a `ChanOpenAck` message on a port the provider CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is always aborted; hence, the state is not changed. * **Error Condition** * None. #### **\[CCV-PCF-COCONFIRM.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // get the client state associated with the underlying client channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}") abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1) connId = channelEnd.connectionHops[0] connectionEnd = provableStore.get("connections/{connId}") clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState") // require that no other CCV channel exists for this consumer chain; // note: this is a sanity check; this check should always pass by construction abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys()) // set channel mappings chainToConnection[clientState.chainId] = connId chainToChannel[clientState.chainId] = channelIdentifier channelToChain[channelIdentifier] = clientState.chainId // set initialHeights for this consumer chain initialHeights[chainId] = getCurrentHeight() // remove init timeout timestamp initTimeoutTimestamps.Remove(clientState.chainId) } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives a `ChanOpenConfirm` message on a port the provider CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is aborted if any of the following conditions are true: * no channel with `portIdentifier` and `channelIdentifier` exists; * the channel has more than one connection hop; * another CCV channel for this consumer chain already exists. * The connection mapping is set, i.e., `chainToConnection`. * The channel mappings are set, i.e., `chainToChannel` and `channelToChain`. * `initialHeights[chainId]` is set to the current height. * The init timeout timestamp for the consumer chain with ID `clientState.chainId` is removed. * **Error Condition** * None. *** #### **\[CCV-CCF-INITG.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the AppModule interface function InitGenesis(gs: ConsumerGenesisState): [ValidatorUpdate] { // ValidateGenesis // - contains a non-empty initial validator set abortSystemUnless(gs.initialValSet NOT empty) if gs.preCCV { // - contains a valid connId connectionEnd = provableStore.get("connections/{gs.connId}") abortSystemUnless(connectionEnd != nil) } else { // - contains a valid providerClientState abortSystemUnless(gs.providerClientState != nil AND gs.providerClientState.Valid()) // - contains a valid providerConsensusState abortSystemUnless(gs.providerConsensusState != nil AND gs.providerConsensusState.Valid()) // - contains an initial validator set that matches // the validator set in the providerConsensusState (e.g., ICS 7) abortSystemUnless(gs.initialValSet == gs.providerConsensusState.validatorSet) } if gs.transferChannelId != "" { // - if transferChannelId is provided, it must the ID // of a channel connected to the "transfer" port channelEnd = provableStore.get("channelEnds/ports/transfer/channels/{gs.transferChannelId}") abortSystemUnless(channelEnd != nil) } // bind to ConsumerPortId port err = portKeeper.bindPort(ConsumerPortId) // check whether the capability for the port can be claimed abortSystemUnless(err == nil) // set pre-CCV state preCCV = gs.preCCV if preCCV { // start consumer chain in pre-CCV state; // store the ID of the client of the provider chain providerClientId = connectionEnd.clientIdentifier } else { // start consumer chain in normal CCV state; // create client of the provider chain and store the ID providerClientId = clientKeeper.CreateClient(gs.providerClientState, gs.providerConsensusState) } // set the consumer unbonding period ConsumerUnbondingPeriod = gs.unbondingTime // set default value for HtoVSC HtoVSC[getCurrentHeight()] = 0 // set the initial validator set for the consumer chain foreach val IN gs.initialValSet { ccvValidatorSet[hash(val.pubKey)] = val } // set distribution channel ID distributionChannelId = gs.transferChannelId // initiate handshake if preCCV { // initiate CCV channel opening handshake // i.e., use handleChanOpenInit as defined in ICS-26 datagram = ChanOpenInit{ order: ORDERED, connectionHops: [gs.connId], portIdentifier: ConsumerPortId, counterpartyPortIdentifier: ProviderPortId, version: ccvVersion, } handleChanOpenInit(datagram) } else { // initiate connection opening handshake // i.e., use handleConnOpenInit as defined in ICS-26 datagram = ConnOpenInit{ clientIdentifier: providerClientId, counterpartyClientIdentifier: gs.counterpartyClientId, version: "ccv" } connId = handleConnOpenInit(datagram) // initiate CCV channel opening handshake // i.e., use handleChanOpenInit as defined in ICS-26 datagram = ChanOpenInit{ order: ORDERED, connectionHops: [connId], portIdentifier: ConsumerPortId, counterpartyPortIdentifier: ProviderPortId, version: ccvVersion, } handleChanOpenInit(datagram) } return gs.initialValSet } ``` * **Caller** * The ABCI application. * **Trigger Event** * An `InitChain` message is received from the consensus engine; the `InitChain` message is sent when the consumer chain is first started. * **Precondition** * The consumer CCV module is in the initial state. * **Postcondition** * The capability for the port `ConsumerPortId` is claimed. * `preCCV` is set to `gs.preCCV`. * If `preCCV == true`, the ID of the client on which the connection with `gs.connId` is built is stored into `providerClientId`. * Otherwise, a client of the provider chain is created and the client ID is stored into `providerClientId`. * `ConsumerUnbondingPeriod` is set to `gs.unbondingPeriod`. * `HtoVSC` for the current block is set to `0`. * The `ccvValidatorSet` mapping is populated with the initial validator set. * The ID of the distribution token transfer channel is set to `gs.transferChannelId`. * If `preCCV == true`, the CCV channel opening handshake is initialized. * Otherwise, the connection opening handshake is initialized. * The initial validator set is returned to the consensus engine. * **Error Condition** * The genesis state contains an empty initial validator set. * If the genesis state `preCCV` field is set to `true`, then the genesis state contains no valid connection ID. * Otherwise, * the genesis state contains no valid provider client state, where the validity is defined in the corresponding client specification (e.g., [ICS 7](../../client/ics-007-tendermint-client); * the genesis state contains no valid provider consensus state, where the validity is defined in the corresponding client specification (e.g., [ICS 7](../../client/ics-007-tendermint-client)); * the genesis state contains an initial validator set that does not match the validator set in the provider consensus state; * The genesis state contains an invalid distribution channel ID. * The capability for the port `ConsumerPortId` cannot be claimed. > **Note**: CCV assumes that all the correct validators in the initial validator set of the consumer chain receive the *same* consumer chain binary and consumer chain genesis state. > Although the mechanism of disseminating the binary and the genesis state is outside the scope of this specification, a possible approach would entail including this information in the governance proposal on the provider chain. #### **\[CCV-CCF-COINIT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenInit( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string): string { // ensure provider channel hasn't already been created abortTransactionUnless(providerChannel == "") // validate parameters: // - only ordered channels allowed abortTransactionUnless(order == ORDERED) // - require the portIdentifier to be the port ID the CCV module is bound to abortTransactionUnless(portIdentifier == ConsumerPortId) // - require the version to be the expected version abortTransactionUnless(version == "" OR version == ccvVersion) // assert that the counterpartyPortIdentifier matches // the expected consumer port ID abortTransactionUnless(counterpartyPortIdentifier == ProviderPortId) // require that the client ID of the client associated // with this channel matches the expected provider client id channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}") abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1) connId = channelEnd.connectionHops[0] connectionEnd = provableStore.get("connections/{connId}") abortTransactionUnless(providerClientId != connectionEnd.clientIdentifier) return ccvVersion } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives a `ChanOpenInit` message on a port the consumer CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is aborted if any of the following conditions are true: * `providerChannel` is already set; * `portIdentifier != ConsumerPortId`; * `version` is set but not to the expected version; * `counterpartyPortIdentifier != ProviderPortId`; * the client associated with this channel is not the expected provider client. * `ccvVersion` is returned. * The state is not changed. * **Error Condition** * None. #### **\[CCV-CCF-COTRY.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenTry( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string): string { // the channel handshake MUST be initiated by consumer chain abortTransactionUnless(FALSE) } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives a `ChanOpenTry` message on a port the consumer CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is always aborted; hence, the state is not changed. * **Error Condition** * None. #### **\[CCV-CCF-COACK.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyVersion: string) { // ensure provider channel hasn't already been created abortTransactionUnless(providerChannel == "") // the version must be encoded in JSON format (as defined in ICS4) md = UnmarshalJSON(counterpartyVersion) // assert that the counterpartyVersion matches the expected version abortTransactionUnless(md.version == ccvVersion) // set the address of the distribution module account on the provider chain providerDistributionAccount = md.providerDistributionAccount if distributionChannelId == "" { // initiate opening handshake for the distribution token transfer channel // over the same connection as the CCV channel // i.e., use handleChanOpenInit as defined in ICS-26 datagram = ChanOpenInit{ order: UNORDERED, connectionHops: channelKeeper.GetConnectionHops(channelIdentifier), // same as the CCV channel portIdentifier: "transfer", counterpartyPortIdentifier: "transfer", version: "ics20-1", } distributionChannelId = handleChanOpenInit(datagram) } // set the channel as the provider channel providerChannel = channelIdentifier // send pending slash requests; // note: this can happen only if preCCV == false, as the ABCI application // can invoke SendSlashRequest only once the chain is upgraded to // a consumer chain, see BeginBlockInit below SendPendingSlashRequests() if preCCV { // replace valset with initial valset stakingKeeper.ReplaceValset(ccvValidatorSet.Values()) } } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives a `ChanOpenAck` message on a port the consumer CCV module is bounded to. * **Precondition** * True. * **Postcondition** * `counterpartyVersion` is unmarshaled into a `CCVHandshakeMetadata` structure `md`. * The transaction is aborted if any of the following conditions are true: * `providerChannel` is already set; * `md.version != ccvVersion`. * The address of the distribution module account on the provider chain is set to `md.providerDistributionAccount`. * If `distributionChannelId` is not set, the distribution token transfer channel opening handshake is initiated and `distributionChannelId` is set to the resulting channel ID. * The CCV channel is marked as established, i.e., `providerChannel` is set to this channel. * The pending slash requests are sent to the provider chain (see [\[CCV-CCF-SNDPESLASH.1\]](#ccv-ccf-sndpeslash1)). Note that this can happen only if `preCCV == false`, as the ABCI application can invoke `SendSlashRequest` only once the chain is upgraded to a consumer chain (see [\[CCV-CCF-BBLOCK-INIT.1\]](#ccv-ccf-bblock-init1)). * If `preCCV == true`, the valset in the staking module is replaced with the `ccvValidatorSet`, i.e., the initial validator set. * **Error Condition** * None. #### **\[CCV-CCF-COCONFIRM.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanOpenConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // the channel handshake MUST be initiated by consumer chain abortTransactionUnless(FALSE) } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives a `ChanOpenConfirm` message on a port the consumer CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is always aborted; hence, the state is not changed. * **Error Condition** * None. #### **\[CCV-CCF-BBLOCK-INIT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function BeginBlockInit() { if preCCV { ownConsensusState = getConsensusState(getCurrentHeight()) if ownConsensusState.validatorSet == ccvValidatorSet.Values() { // pre-CCV state is over; upgrade chain to consumer chain // - set preCCV to false // - the existing staking module no longer provides // validator updates to the underlying consensus engine // - the CCV module starts providing validator updates // to the underlying consensus engine // - for safety, the existing staking module must be kept // for at least the unbonding period } } } ``` * **Caller** * The `BeginBlock()` method. * **Trigger Event** * A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * If `preCCV == true` and the current validator set matches the `ccvValidatorSet` (i.e., the initial validator set), then the chain MUST be upgraded to a full consumer chain. The upgrade mechanism is outside the scope of this specification. * **Error Condition** * None. ### Consumer Chain Removal [↑ Back to Outline](#outline) #### **\[CCV-PCF-HCRPROP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements governance proposal Handler function HandleConsumerRemovalProposal(p: ConsumerRemovalProposal) { // store the proposal as a pending removal proposal pendingConsumerRemovalProposals.Append(p) } ``` * **Caller** * `EndBlock()` method of Governance module. * **Trigger Event** * A governance proposal `ConsumerRemovalProposal` has passed (i.e., it got the necessary votes). * **Precondition** * True. * **Postcondition** * The proposal is appended to the list of pending removal proposals, i.e., `pendingConsumerRemovalProposals`. * **Error Condition** * None. #### **\[CCV-PCF-BBLOCK-CCR.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function BeginBlockCCR() { // iterate over the pending removal proposals // and stop the consumer chain foreach p IN pendingConsumerRemovalProposals { if currentTimestamp() > p.stopTime { // stop the consumer chain and do not lock the unbonding StopConsumerChain(p.chainId, false) pendingConsumerRemovalProposals.Remove(p) } } } ``` * **Caller** * The `BeginBlock()` method. * **Trigger Event** * A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * For each `ConsumerRemovalProposal` `p` in the list of pending removal proposals `pendingConsumerRemovalProposals`, if `currentTimestamp() > p.stopTime`, then * `StopConsumerChain(p.chainId, false)` is invoked; * `p` is removed from `pendingConsumerRemovalProposals`. * **Error Condition** * None. #### **\[CCV-PCF-STCC.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function StopConsumerChain(chainId: string, lockUnbonding: Bool) { // check that a client for chainId exists if chainId NOT IN chainToClient.Keys() { return } // cleanup state chainToClient.Remove(chainId) lockUnbondingOnTimeout.Remove(chainId) if chainId IN chainToChannel.Keys() { // CCV channel is established channelToChain.Remove(chainToChannel[chainId]) channelKeeper.ChanCloseInit(chainToChannel[chainId]) chainToChannel.Remove(chainId) } pendingVSCPackets.Remove(chainId) initialHeights.Remove(chainId) downtimeSlashRequests.Remove(chainId) initTimeoutTimestamps.Remove(chainId) vscSendTimestamps.Remove((chainId, *)) if !lockUnbonding { // remove chainId form all outstanding unbonding operations foreach id IN vscToUnbondingOps[(chainId, _)] { unbondingOps[id].unbondingChainIds.Remove(chainId) // if the unbonding operation has unbonded on all consumer chains if unbondingOps[id].unbondingChainIds.IsEmpty() { // append the id of the unbonding to maturedUnbondingOps maturedUnbondingOps.Append(id) // remove unbonding operation unbondingOps.Remove(id) } } // clean up vscToUnbondingOps mapping vscToUnbondingOps.Remove((chainId, _)) } } ``` * **Caller** * `HandleConsumerRemovalProposal` (see [CCV-PCF-HCRPROP.1](#ccv-pcf-hcrprop1)) or `BeginBlockCCR()` (see [CCV-PCF-BBLOCK-CCR.1](#ccv-pcf-bblock-ccr1)) or `onTimeoutVSCPacket()` (see [CCV-PCF-TOVSC.1](#ccv-pcf-tovsc1)) or `EndBlockCCR()` (see [CCV-PCF-EBLOCK-CCR.1](#ccv-pcf-eblock-ccr1)). * **Trigger Event** * One of the following events: * a governance proposal to stop the consumer chain with `chainId` has passed (i.e., it got the necessary votes); * a `VSCPacket` sent on the CCV channel to the consumer chain with `chainId` has timed out; * the channel initialization has timed out. * **Precondition** * True. * **Postcondition** * If a client for `p.chainId` does not exist, the state is not changed. * Otherwise, * the client ID mapped to `chainId` in `chainToClient` is removed; * the value mapped to `chainId` in `lockUnbondingOnTimeout` is removed; * if the CCV channel to the consumer chain with `chainId` is established, then * the chain ID mapped to `chainToChannel[chainId]` in `channelToChain` is removed; * the channel closing handshake is initiated for the CCV channel; * the channel ID mapped to `chainId` in `chainToChannel` is removed. * all the `VSCPacketData` mapped to `chainId` in `pendingVSCPackets` are removed; * the height mapped to `chainId` in `initialHeights` is removed; * `downtimeSlashRequests[chainId]` is emptied; * if `lockUnbonding == false`, then * `chainId` is removed from all outstanding unbonding operations; * if an outstanding unbonding operation has matured on all consumer chains, * the matured unbonding operation is added to `maturedUnbondingOps`; * the matured unbonding operation is removed from `unbondingOps`; * all the entries with `chainId` are removed from the `vscToUnbondingOps` mapping. * **Error Condition** * None > **Note**: Invoking `StopConsumerChain(chainId, lockUnbonding)` with `lockUnbonding == FALSE` entails that all outstanding unbonding operations can complete before `ConsumerUnbondingPeriod` elapses on the consumer chain with `chainId`. > Thus, invoking `StopConsumerChain(chainId, false)` for any `chainId` MAY violate the *Bond-Based Consumer Voting Power* and *Slashable Consumer Misbehavior* properties (see the [System Properties](./system_model_and_properties#system-properties) section). > > `StopConsumerChain(chainId, false)` is invoked in two scenarios (see Trigger Event above). > > * In the first scenario (i.e., a governance proposal to stop the consumer chain with `chainId`), the validators on the provider chain MUST make sure that it is safe to stop the consumer chain. > Since a governance proposal needs a majority of the voting power to pass, the safety of invoking `StopConsumerChain(chainId, false)` is ensured by the *Safe Blockchain* assumption (see the [Assumptions](./system_model_and_properties#assumptions) section). > > * The second scenario (i.e., a timeout) is only possible if the *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section), > which is necessary to guarantee both the *Bond-Based Consumer Voting Power* and *Slashable Consumer Misbehavior* properties (see the [Assumptions](./system_model_and_properties#correctness-reasoning) section). #### **\[CCV-PCF-EBLOCK-CCR.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function EndBlockCCR() { // iterate over vscSendTimestamps for (chainId, vscId) IN vscSendTimestamps.Keys() { // check get first timestamp, i.e., the smallest if currentTimestamp() > vscSendTimestamps[(chainId, vscId)] + vscTimeout { // vscTimeout expired: // stop the consumer chain and use lockUnbondingOnTimeout // to decide whether to lock the unbonding StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId]) } } // iterate over initTimeoutTimestamps for chainId IN initTimeoutTimestamps.Keys() { if currentTimestamp() > initTimeoutTimestamps[chainId] { // initTimeout expired: // stop the consumer chain and unlock the unbonding StopConsumerChain(chainId, false) } } } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * For each consumer chain ID `chainId` in `vscSendTimestamps.Keys()`, * if `vscSendTimestamps[(chainId, vscId)] + vscTimeout` is smaller than the current timestamp, then the consumer chain with ID `chainId` is stopped. * For each consumer chain ID `chainId` in `initTimeoutTimestamps.Keys()`, * if the timestamp in `initTimeoutTimestamps[chainId]` is smaller than the current timestamp, then the consumer chain with ID `chainId` is stopped. * **Error Condition** * None. > **Note**: To avoid false positives where a consumer chain is unnecessarily removed, > `vscTimeout` MUST be larger than `consumerUnbondingPeriod` and > SHOULD account for the time needed to relay the `VSCPacket` to the consumer and the corresponding `VSCMaturedPacket` back to the provider. #### **\[CCV-PCF-CCINIT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanCloseInit( portIdentifier: Identifier, channelIdentifier: Identifier) { // Disallow user-initiated channel closing abortTransactionUnless(FALSE) } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives a `ChanCloseInit` message on a port the provider CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The transaction is always aborted; hence, the state is not changed. * **Error Condition** * None. #### **\[CCV-PCF-CCCONFIRM.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanCloseConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // do nothing } ``` * **Caller** * The provider IBC routing module. * **Trigger Event** * The provider IBC routing module receives a `ChanCloseConfirm` message on a port the provider CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The state is not changed. * **Error Condition** * None. *** #### **\[CCV-CCF-BBLOCK-CCR.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function BeginBlockCCR() { if providerChannel != "" AND channelKeeper.GetChannelState(providerChannel) == CLOSED { // the CCV channel was established, but it was then closed; // the consumer chain is no longer safe // cleanup state, e.g., // providerChannel = "" // shut down consumer chain abortSystemUnless(FALSE) } } ``` * **Caller** * The `BeginBlock()` method. * **Trigger Event** * A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * If the CCV was established, but then was moved to the `CLOSED` state, then the state of the consumer CCV module is cleaned up, e.g., the `providerChannel` is unset. * **Error Condition** * If the CCV was established, but then was moved to the `CLOSED` state. > **Note**: Once the CCV channel is closed, the provider chain can no longer provider security. As a result, the consumer chain MUST be shut down. > For an example of how to do this in practice, see the Cosmos SDK [implementation](https://github.com/cosmos/cosmos-sdk/blob/0c0b4da114cf73ef5ae1ac5268241d69e8595a60/x/upgrade/abci.go#L71). #### **\[CCV-CCF-CCINIT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanCloseInit( portIdentifier: Identifier, channelIdentifier: Identifier) { // allow relayers to close duplicate OPEN channels, // if the provider channel has already been established if providerChannel == "" || providerChannel == channelIdentifier { // user cannot close channel abortTransactionUnless(FALSE) } } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives a `ChanCloseInit` message on a port the consumer CCV module is bounded to. * **Precondition** * True. * **Postcondition** * If `providerChannel` is not set or `providerChannel` matches the ID of the channel the `ChanCloseInit` message was received on, then the transaction is aborted. * The state is not changed. * **Error Condition** * None. #### **\[CCV-CCF-CCCONFIRM.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // implements the ModuleCallbacks interface defined in ICS26 function onChanCloseConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // do nothing } ``` * **Caller** * The consumer IBC routing module. * **Trigger Event** * The consumer IBC routing module receives a `ChanCloseConfirm` message on a port the consumer CCV module is bounded to. * **Precondition** * True. * **Postcondition** * The state is not changed. * **Error Condition** * None. ### Validator Set Update [↑ Back to Outline](#outline) The *validator set update* sub-protocol enables the provider chain * to update the consumer chain on the voting power granted to validators on the provider chain * and to ensure the correct completion of unbonding operations for validators that produce blocks on the consumer chain. #### **\[CCV-PCF-EBLOCK-VSU.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function EndBlockVSU() { // notify the Staking module to complete all matured unbondings for id IN maturedUnbondingOps { stakingKeeper.UnbondingCanComplete(id) } maturedUnbondingOps.RemoveAll() // get list of validator updates from the provider Staking module valUpdates = stakingKeeper.GetValidatorUpdates() // iterate over all consumer chains registered with this provider chain foreach chainId IN chainToClient.Keys() { // check whether there are changes in the validator set; // note that this also entails unbonding operations // w/o changes in the voting power of the validators in the validator set if len(valUpdates) != 0 OR len(vscToUnbondingOps[(chainId, vscId)]) != 0 { // create VSCPacket data data = VSCPacketData{ id: vscId, updates: valUpdates, downtimeSlashAcks: downtimeSlashRequests[chainId] } downtimeSlashRequests.Remove(chainId) // add VSCPacket data to the list of pending VSCPackets pendingVSCPackets.Append(chainId, data) } // check whether there is an established CCV channel to the consumer chain if chainId IN chainToChannel.Keys() { // get the channel ID for the given consumer chain ID channelId = chainToChannel[chainId] foreach data IN pendingVSCPackets[chainId] { // send data using the interface exposed by ICS-4 channelKeeper.sendPacket( portKeeper.getCapability(portKeeper.portPath(ProviderPortId)), ProviderPortId, // source port ID channelId, // source channel ID zeroTimeoutHeight, ccvTimeoutTimestamp, data ) // add VSC send timestamp to vscSendTimestamps vscSendTimestamps[(vscId, chainId)] = currentTimestamp() } // remove pending VSCPackets pendingVSCPackets.Remove(chainId) } } // increment VSC ID vscId++ } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * For every matured unbonding operation in `maturedUnbondingOps`, the Staking module is notified that the unbonding can complete. * All unbonding operation in `maturedUnbondingOps` are removed. * A list of validator updates `valUpdates` is obtained from the provider Staking module. * For every consumer chain with `chainId` * If either `valUpdates` is not empty or there were unbonding operations initiated during this block, then * a `VSCPacket` data `data` is created such that `data.id = vscId`, `data.updates = valUpdates`, and `data.downtimeSlashAcks = downtimeSlashRequests[chainId]`; * `downtimeSlashRequests[chainId]` is emptied; * `packetData` is appended to the list of pending `VSCPacket`s associated to `chainId`, i.e., `pendingVSCPackets[chainId]`. * If there is an established CCV channel for the consumer chain with `chainId`, then * for each `VSCPacketData` in the list of pending VSCPackets associated to `chainId` * a packet with the `VSCPacketData` is sent on the channel associated with the consumer chain with `chainId`; * `vscSendTimestamps[(vscId, chainId)]` is set to the current timestamp; * all the pending VSCPackets associated to `chainId` are removed. * `vscId` is incremented. * **Error Condition** * None. #### **\[CCV-PCF-ACKVSC.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function onAcknowledgeVSCPacket(packet: Packet, ack: bytes) { // providing the VSC with id packet.data.id can fail, // i.e., ack == VSCPacketError, // only if the VSCPacket was sent on a channel // other than the established CCV channel; // that should never happen, see EndBlock() abortSystemUnless(ack != VSCPacketError) } ``` * **Caller** * The `onAcknowledgePacket()` method. * **Trigger Event** * The provider IBC routing module receives an acknowledgement of a `VSCPacket` on a channel owned by the provider CCV module. * **Precondition** * True. * **Postcondition** * The state is not changed. * **Error Condition** * The acknowledgement is `VSCPacketError`. #### **\[CCV-PCF-TOVSC.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function onTimeoutVSCPacket(packet: Packet) { // cleanup state abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys()) chainId = channelToChain[packet.getDestinationChannel()] // stop the consumer chain and use lockUnbondingOnTimeout // to decide whether to lock the unbonding StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId]) } ``` * **Caller** * The `onTimeoutPacket()` method. * **Trigger Event** * A `VSCPacket` sent on a channel owned by the provider CCV module timed out as a result of either * the timeout height or timeout timestamp passing on the consumer chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end)); * or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)). * **Precondition** * The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section). * **Postcondition** * The transaction is aborted if the ID of the channel on which the packet was sent is not mapped to a chain ID (in `channelToChain`). * `StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])` is invoked, where `chainId = channelToChain[packet.getDestinationChannel()]`. * **Error Condition** * None #### **\[CCV-PCF-RCVMAT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function onRecvVSCMaturedPacket(packet: Packet): bytes { // get the ID of the consumer chain mapped to this channel ID abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys()) chainId = channelToChain[packet.getDestinationChannel()] // iterate over the unbonding operations mapped to // this chainId and vscId (i.e., packet.data.id) foreach op in GetUnbondingsFromVSC(chainId, packet.data.id) { // remove the consumer chain from // the list of consumer chain that are still unbonding op.unbondingChainIds.Remove(chainId) // if the unbonding operation has unbonded on all consumer chains if op.unbondingChainIds.IsEmpty() { // append the id of the unbonding to maturedUnbondingOps maturedUnbondingOps.Append(op.id) // remove unbonding operation unbondingOps.Remove(op.id) } } // clean up vscToUnbondingOps mapping vscToUnbondingOps.Remove((chainId, vscId)) // clean up vscSendTimestamps mapping vscSendTimestamps.Remove((chainId, vscId)) return VSCMaturedPacketSuccess } ``` * **Caller** * The `onRecvPacket()` method. * **Trigger Event** * The provider IBC routing module receives a `VSCMaturedPacket` on a channel owned by the provider CCV module. * **Precondition** * True. * **Postcondition** * The transaction is aborted if the channel on which the packet was received is not an established CCV channel (i.e., not in `channelToChain`). * `chainId` is set to the ID of the consumer chain mapped to the channel on which the packet was received. * For each unbonding operation `op` returned by `GetUnbondingsFromVSC(chainId, packet.data.id)` * `chainId` is removed from `op.unbondingChainIds`; * if `op.unbondingChainIds` is empty, * `op.id` is added to `maturedUnbondingOps`; * `op.id` is removed from `unbondingOps`. * `(chainId, vscId)` is removed from `vscToUnbondingOps`. * `(chainId, vscId)` is removed from `vscSendTimestamps`. * A successful acknowledgment is returned. * **Error Condition** * None. #### **\[CCV-PCF-GETUBS.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // Utility method function GetUnbondingsFromVSC( chainId: Identifier, _vscId: uint64): [UnbondingOperation] { // get all unbonding operations associated with (chainId, _vscId) ops = [] foreach id in vscToUnbondingOps[(chainId, _vscId)] { // get the unbonding operation with this ID op = unbondingOps[id] // append the operation to the list of operations to be returned ops.Append(op) } return ops } ``` * **Caller** * The `onRecvVSCMaturedPacket()` method. * **Trigger Event** * The provider IBC routing module receives a `VSCMaturedPacket` on a channel owned by the provider CCV module. * **Precondition** * The provider CCV module received a `VSCMaturedPacket` `P` from a consumer chain with ID `chainId`, such that `P.data.id == _vscId`. * **Postcondition** * Return the list of unbonding operations mapped to `(chainId, _vscId)`. * **Error Condition** * None. #### **\[CCV-PCF-HOOK-AFUBOPCR.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function // implements a Staking module hook function AfterUnbondingInitiated(opId: uint64) { // get the IDs of all consumer chains registered with this provider chain; // note: this includes also consumer chains in the pre-CCV state chainIds = chainToClient.Keys() if len(chainIds) > 0 { // create and store a new unbonding operation unbondingOps[opId] = UnbondingOperation{ id: opId, unbondingChainIds: chainIds } // add the unbonding operation id to vscToUnbondingOps foreach chainId in chainIds { vscToUnbondingOps[(chainId, vscId)].Append(opId) } // ask the Staking module to wait for this operation // to reach maturity on the consumer chains stakingKeeper.PutUnbondingOnHold(opId) } } ``` * **Caller** * The Staking module. * **Trigger Event** * An unbonding operation with id `opId` is initiated. * **Precondition** * True. * **Postcondition** * `chainIds` is set to the list of all consumer chains registered with this provider chain, i.e., `chainToClient.Keys()`. * If there is at least one consumer chain in `chainIds`, then * an `UnbondingOperation` `op` is created and added to `unbondingOps`, such that `op.id = opId` and `op.unbondingChainIds = chainIds`. * `opId` is appended to every list in `vscToUnbondingOps[(chainId, vscId)]`, where `chainId` is an ID of a consumer chains registered with this provider chain and `vscId` is the current VSC ID. * the `PutUnbondingOnHold(opId)` of the Staking module is invoked. * **Error Condition** * None. *** #### **\[CCV-CCF-RCVVSC.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function onRecvVSCPacket(packet: Packet): bytes { // check whether the packet was sent on the CCV channel if providerChannel != "" && providerChannel != packet.getDestinationChannel() { // packet sent on a channel other than the established provider channel; // return error acknowledgement return VSCPacketError } // set HtoVSC mapping HtoVSC[getCurrentHeight() + 1] = packet.data.id // store the packet data receivedVSCs.Append(packet.data) return VSCPacketSuccess } ``` * **Caller** * The `onRecvPacket()` method. * **Trigger Event** * The consumer IBC routing module receives a `VSCPacket` on a channel owned by the consumer CCV module. * **Precondition** * True. * **Postcondition** * If `providerChannel` is set and does not match the channel (with ID `packet.getDestinationChannel()`) on which the packet was received, then an error acknowledgement is returned. * Otherwise, * the height of the subsequent block is mapped to `packet.data.id` (i.e., the `HtoVSC` mapping) ; * `packet.data` is appended to `receivedVSCs`. * a successful acknowledgement is returned. * **Error Condition** * None. #### **\[CCV-CCF-ACKMAT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function onAcknowledgeVSCMaturedPacket(packet: Packet, ack: bytes) { // notifications of VSC maturity cannot fail by construction abortSystemUnless(ack != VSCMaturedPacketError) } ``` * **Caller** * The `onAcknowledgePacket()` method. * **Trigger Event** * The consumer IBC routing module receives an acknowledgement of a `VSCMaturedPacket` on a channel owned by the consumer CCV module. * **Precondition** * True. * **Postcondition** * The state is not changed. * **Error Condition** * The acknowledgement is `VSCMaturedPacketError`. #### **\[CCV-CCF-TOMAT.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function onTimeoutVSCMaturedPacket(packet Packet) { // the CCV channel state is changed to CLOSED // by the IBC handler (since the channel is ORDERED) } ``` * **Caller** * The `onTimeoutPacket()` method. * **Trigger Event** * A `VSCMaturedPacket` sent on a channel owned by the consumer CCV module timed out as a result of either * the timeout height or timeout timestamp passing on the provider chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end)); * or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)). * **Precondition** * The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section). * **Postcondition** * The state is not changed. * **Error Condition** * None #### **\[CCV-CCF-EBLOCK-VSU.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function EndBlockVSU(): [ValidatorUpdate] { // unbond mature packets if the CCV channel is established if providerChannel != "" { UnbondMaturePackets() } if preCCV { // do nothing return [] } else { // handle received VSCs changes = HandleReceivedVSCs() // update ccvValidatorSet UpdateValidatorSet(changes) // return the validator set updates return changes } } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * If `providerChannel != ""`, `UnbondMaturePackets()` is invoked; * If `preCCV == true`, the state is not changed. * Otherwise, * the data items in `receivedVSCs` are handled (see [\[CCV-CCF-HAREVSC.1\]](#ccv-ccf-harevsc1)), which results in a list `changes` of validator updates; * `UpdateValidatorSet(changes)` is invoked; * `changes` is returned. * **Error Condition** * None. #### **\[CCV-CCF-HAREVSC.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function HandleReceivedVSCs(): [ValidatorUpdate] { changes = [] foreach data IN receivedVSCs { // store the list of updates changes.Append(data.updates) // calculate and store the maturity timestamp for the VSC maturityTimestamp = currentTimestamp().Add(ConsumerUnbondingPeriod) maturingVSCs.Add(data.id, maturityTimestamp) // reset outstandingDowntime for validators in data.downtimeSlashAcks foreach valAddr IN data.downtimeSlashAcks { outstandingDowntime[valAddr] = FALSE } } // remove all entries receivedVSCs = [] // aggregate the updates, // i.e., keep only the latest update per validator; // note: in the implementation, the aggregation is done directly // when receiving a VSCPacket via the AccumulateChanges method return changes.Aggregate() } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine. * **Precondition** * `preCCV == false`. * **Postcondition** * For each `data` item in the list `receivedVSCs`, * `data.updates` are appended to `changes`, where `changes` is initially an empty list of validator updates; * `(data.id, maturityTimestamp)` is added to `maturingVSCs`, where `maturityTimestamp = currentTimestamp() + ConsumerUnbondingPeriod`; * for each `valAddr` in the slash acknowledgments received from the provider chain, `outstandingDowntime[valAddr]` is set to false. * `receivedVSCs` is emptied. * The updates in `changes` are aggregated, i.e., only the latest update per validator is kept, and returned. * **Error Condition** * None. #### **\[CCV-CCF-UPVALS.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function UpdateValidatorSet(changes: [ValidatorUpdate]) { foreach update IN changes { addr := hash(update.pubKey) if addr NOT IN ccvValidatorSet.Keys() { // new validator bonded; // note that due changes.Aggregate(), // a validator can be added to the valset and // then removed in the subsequent block, // resulting in update.power == 0 if update.power > 0 { // add new validator to validator set ccvValidatorSet[addr] = update // call AfterCCValidatorBonded hook AfterCCValidatorBonded(addr) } } else if update.power == 0 { // existing validator begins unbonding ccvValidatorSet.Remove(addr) // call AfterCCValidatorBeginUnbonding hook AfterCCValidatorBeginUnbonding(addr) } else { ccvValidatorSet[addr].power = update.power } } } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine. * **Precondition** * `preCCV == false`. * **Postcondition** * For each validator `update` in `changes`, * if the validator is not in the validator set and `update.power > 0`, then * a new validator is added to `ccvValidatorSet`; * the `AfterCCValidatorBonded` hook is called; * otherwise, if the validator's new power is `0`, then, * the validator is removed from `ccvValidatorSet`; * the `AfterCCValidatorBeginUnbonding` hook is called; * otherwise, the validator's power is updated. * **Error Condition** * None. #### **\[CCV-CCF-UMP.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function UnbondMaturePackets() { foreach (id, ts) in maturingVSCs.SortedByMaturityTime() { if currentTimestamp() < ts { break // stop loop } // create VSCMaturedPacketData packetData = VSCMaturedPacketData{id: id} // send VSCMaturedPacketData using the interface exposed by ICS-4 channelKeeper.sendPacket( portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)), ConsumerPortId, // source port ID providerChannel, // source channel ID zeroTimeoutHeight, ccvTimeoutTimestamp, packetData ) // remove entry from the list maturingVSCs.Remove(id, ts) } } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine. * **Precondition** * The CCV channel to the provider chain is established, i.e., `providerChannel != ""`. * **Postcondition** * For each `(id, ts)` in the list of maturing VSCs sorted by maturity timestamps * if `currentTimestamp() < ts`, the loop is stopped; * a `VSCMaturedPacketData` packet data is created; * a packet with the created `VSCMaturedPacketData` is sent to the provider chain; * the tuple `(id, ts)` is removed from `maturingVSCs`. * **Error Condition** * None. ### Consumer Initiated Slashing [↑ Back to Outline](#outline) #### **\[CCV-PCF-EBLOCK-CIS.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function EndBlockCIS() { // set VSCtoH mapping VSCtoH[vscId] = getCurrentHeight() + 1 } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * `vscId` is mapped to the height of the subsequent block. * **Error Condition** * None. #### **\[CCV-PCF-RCVSLASH.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // PCF: Provider Chain Function function onRecvSlashPacket(packet: Packet): bytes { // check whether the packet was received on an established CCV channel if packet.getDestinationChannel() NOT IN channelToChain.Keys() { // packet received on a non-established channel; incorrect behavior return SlashPacketError } // get the height that maps to the VSC ID in the packet data if packet.data.vscId == 0 { // the infraction happened before sending any VSC to this chain chainId = channelToChain[packet.getDestinationChannel()] infractionHeight = initialHeights[chainId] } else { infractionHeight = VSCtoH[packet.data.vscId] } // request the Slashing module to slash the validator // using the slashFactor set on the provider chain slashFactor = slashingKeeper.GetSlashFactor(packet.data.downtime) slashingKeeper.Slash( packet.data.valAddress, infractionHeight, packet.data.valPower, slashFactor)) // request the Slashing module to jail the validator // using the jailTime set on the provider chain jailTime = slashingKeeper.GetJailTime(packet.data.downtime) slashingKeeper.JailUntil(packet.data.valAddress, currentTimestamp() + jailTime) if packet.data.downtime { // add validator to list of downtime slash requests for chainId downtimeSlashRequests[chainId].Append(packet.data.valAddress) } return SlashPacketSuccess } ``` * **Caller** * The `onRecvPacket()` method. * **Trigger Event** * The provider IBC routing module receives a `SlashPacket` on a channel owned by the provider CCV module. * **Precondition** * True. * **Postcondition** * If the channel the packet was received on is not an established CCV channel, then an error acknowledgment is returned. * Otherwise, * if `packet.data.vscId == 0`, `infractionHeight` is set to `initialHeights[chainId]`, with `chainId = channelToChain[packet.getDestinationChannel()]`, i.e., the height when the CCV channel to this consumer chain is established; * otherwise, `infractionHeight` is set to `VSCtoH[packet.data.vscId]`, i.e., the height at which the voting power was last updated by the validator updates in the VSC with ID `packet.data.vscId`; * a request is made to the Slashing module to slash `slashFactor` of the tokens bonded at `infractionHeight` by the validator with address `packet.data.valAddress`, where `slashFactor` is the slashing factor set on the provider chain; * a request is made to the Slashing module to jail the validator with address `packet.data.valAddress` for a period `jailTime`, where `jailTime` is the jailing time set on the provider chain; * if the slash request is for downtime, the validator's address `packet.data.valAddress` is added to the list of downtime slash requests from this `chainId`; * a successful acknowledgment is returned. * **Error Condition** * None. *** #### **\[CCV-CCF-BBLOCK-CIS.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function BeginBlockCIS() { HtoVSC[getCurrentHeight() + 1] = HtoVSC[getCurrentHeight()] } ``` * **Caller** * The `BeginBlock()` method. * **Trigger Event** * A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * `HtoVSC` for the subsequent block height is set to the same VSC ID as the current block height. * **Error Condition** * None. #### **\[CCV-CCF-ACKSLASH.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function onAcknowledgeSlashPacket(packet: Packet, ack: bytes) { // slash request fail, i.e., ack == SlashPacketError, // only if the SlashPacket was sent on a channel // other than the established CCV channel; // that should never happen, // see SendSlashRequest() and SendPendingSlashRequests() abortSystemUnless(ack != SlashPacketError) } ``` * **Caller** * The `onAcknowledgePacket()` method. * **Trigger Event** * The consumer IBC routing module receives an acknowledgement of a `SlashPacket` on a channel owned by the consumer CCV module. * **Precondition** * True. * **Postcondition** * The state is not changed. * **Error Condition** * The acknowledgement is `SlashPacketError`. #### **\[CCV-CCF-TOSLASH.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function onTimeoutSlashPacket(packet Packet) { // the CCV channel state is changed to CLOSED // by the IBC handler (since the channel is ORDERED) } ``` * **Caller** * The `onTimeoutPacket()` method. * **Trigger Event** * A `SlashPacket` sent on a channel owned by the consumer CCV module timed out as a result of either * the timeout height or timeout timestamp passing on the provider chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end)); * or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)). * **Precondition** * The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section). * **Postcondition** * The state is not changed. * **Error Condition** * None #### **\[CCV-CCF-SNDSLASH.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // Enables consumer initiated slashing function SendSlashRequest( valAddress: string, power: int64, infractionHeight: Height, downtime: Bool) { if downtime AND outstandingDowntime[data.valAddress] { // do not send multiple requests for the same downtime return } // create SlashPacket data packetData = SlashPacketData{ valAddress: valAddress, valPower: power, vscId: HtoVSC[infractionHeight], downtime: downtime } // check whether the CCV channel to the provider chain is established if providerChannel != "" { // send SlashPacket data using the interface exposed by ICS-4 channelKeeper.sendPacket( portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)), ConsumerPortId, // source port ID providerChannel, // source channel ID zeroTimeoutHeight, ccvTimeoutTimestamp, packetData ) if downtime { // set outstandingDowntime for this validator outstandingDowntime[data.valAddress] = TRUE } } else { // add SlashPacket data to the list of pending SlashPackets req := SlashRequest{data: packetData, downtime: downtime} pendingSlashRequests.Append(req) } } ``` * **Caller** * The ABCI application (e.g., the Slashing module). * **Trigger Event** * Evidence of misbehavior for a validator with address `valAddress` was received. * **Precondition** * True. * **Postcondition** * If the request is for downtime and there is an outstanding request to slash this validator for downtime, then the state is not changed. * Otherwise, * a `SlashPacket` data `packetData` is created, such that `packetData.vscId = VSCtoH[infractionHeight]`; * if the CCV channel to the provider chain is established, then * a packet with the `packetData` is sent to the provider chain; * if the request is for downtime, `outstandingDowntime[data.valAddress]` is set to true; * otherwise `SlashRequest{data: packetData, downtime: downtime}` is appended to `pendingSlashRequests`. * **Error Condition** * None. > **Note**: The ABCI application MUST subtract `ValidatorUpdateDelay` from the infraction height before invoking `SendSlashRequest`, > where `ValidatorUpdateDelay` is a delay (in blocks) between when validator updates are returned to the consensus-engine and when they are applied. > For example, if `ValidatorUpdateDelay = x` and a validator set update is returned with new validators at the end of block `10`, > then the new validators are expected to sign blocks beginning at block `11+x` > (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/abci/abci.md#endblock)). > > Consequently, the consumer CCV module expects the `infractionHeight` parameter of the `SendSlashRequest()` to be set accordingly. > > **Note**: In the context of single-chain validation, slashing for downtime is an ***atomic operation***, i.e., once the downtime is detected, the misbehaving validator is slashed and jailed immediately. > Consequently, once a validator is punished for downtime, it is removed from the validator set and cannot be punished again for downtime. > Since validators are not automatically added back to the validator set, it entails that the validator is aware of the punishment before it can rejoin and be potentially punished again. > > In the context of CCV, slashing for downtime is no longer atomic, i.e., downtime is detected on the consumer chain, but the jailing happens on the provider chain. > To avoid sending multiple slash requests for the same downtime infraction, the consumer CCV module uses an `outstandingDowntime` flag per validator. > CCV assumes that the consumer ABCI application (e.g., the slashing module) is not including the downtime of a validator with `outstandingDowntime == TRUE` in the evidence for downtime. #### **\[CCV-CCF-SNDPESLASH.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function // Utility method function SendPendingSlashRequests() { // iterate over every pending SlashRequest in reverse order foreach req IN pendingSlashRequests.Reverse() { if !req.downtime OR !outstandingDowntime[req.data.valAddress] { // send req.data using the interface exposed by ICS-4 channelKeeper.sendPacket( portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)), ConsumerPortId, // source port ID providerChannel, // source channel ID zeroTimeoutHeight, ccvTimeoutTimestamp, req.data ) if req.downtime { // set outstandingDowntime for this validator outstandingDowntime[req.data.valAddress] = TRUE } } } // remove pending SlashRequest pendingSlashRequests.RemoveAll() } ``` * **Caller** * The `onRecvVSCPacket()` method (see [CCV-CCF-RCVVSC.1](#ccv-ccf-rcvvsc1)). * **Trigger Event** * The first `VSCPacket` is received from the provider chain. * **Precondition** * `providerChannel != ""`. * **Postcondition** * For each slash request `req` in `pendingSlashRequests` in reverse order, such that either the slash request is not for downtime or there is no outstanding slash request for downtime, * a packet with the data `req.data` is sent to the provider chain; * if the request is for downtime, `outstandingDowntime[req.data.valAddress]` is set to true. * All the pending `SlashRequest`s are removed. * **Error Condition** * None. > **Note**: Iterating over pending `SlashRequest`s in reverse order ensures that validators that are down for multiple blocks during channel initialization will be slashed for the latest downtime evidence. ### Reward Distribution [↑ Back to Outline](#outline) #### **\[CCV-CCF-EBLOCK-RD.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function EndBlockRD() { if getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer { DistributeRewards() } } ``` * **Caller** * The `EndBlock()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block. * **Precondition** * True. * **Postcondition** * If `getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer`, the `DistributeRewards()` method is invoked. * **Error Condition** * None. #### **\[CCV-CCF-DISTRREW\.1]** ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // CCF: Consumer Chain Function function DistributeRewards() { // iterate over all different tokens in ccvAccount foreach (denomination, amount) IN ccvAccount.GetAllBalances() { // transfer token using ICS20 transferKeeper.sendFungibleTokens( denomination, amount, ccvAccount, // sender providerDistributionAccount, // receiver "transfer", // transfer port distributionChannelId, // transfer channel ID zeroTimeoutHeight, // timeoutHeight transferTimeoutTimestamp // timeoutTimestamp ) } lastDistributionTransferHeight = getCurrentHeight() } ``` * **Caller** * The `EndBlockRD()` method. * **Trigger Event** * An `EndBlock` message is received from the consensus engine. * **Precondition** * `getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer` * **Postcondition** * For each token type defined as a pair `(denomination, amount)` in `ccvAccount`, a transfer token (as defined in [ICS 20](../ics-020-fungible-token-transfer/README)) is initiated. * `lastDistributionTransferHeight` is set to the current height. * **Error Condition** * None. # CCV: Overview and Basic Concepts Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts ## Outline * [Security Model](#security-model) * [Motivation](#motivation) * [Definition](#definition) * [Overview](#overview) * [Channel Initialization](#channel-initialization) * [Channel Initialization: New Chains](#channel-initialization-new-chains) * [Channel Initialization: Existing Chains](#channel-initialization-existing-chains) * [Validator Set Update](#validator-set-update) * [Completion of Unbonding Operations](#completion-of-unbonding-operations) * [Consumer Initiated Slashing](#consumer-initiated-slashing) * [Reward Distribution](#reward-distribution) ## Security Model [↑ Back to Outline](#outline) We consider chains that use a proof of stake mechanism based on the model of [weak subjectivity](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/) in order to strengthen the assumptions required by the underlying consensus engine (e.g., [Tendermint](https://arxiv.org/pdf/1807.04938.pdf) requires that less than a third of the voting power is Byzantine). > **Background**: The next block in a blockchain is *validated* and *voted* upon by a set of pre-determined *full nodes*; these pre-determined full nodes are also known as *validators*. > We refer to the validators eligible to validate a block as that block's *validator set*. > To be part of the validator set, a validator needs to *bond* (i.e., lock, stake) an amount of tokens for a (minimum) period of time, known as the *unbonding period*. > The amount of tokens bonded gives a validator's *voting power*. > When a validator starts unbonding some of its tokens, its voting power is reduced immediately, > but the tokens are unbonded (i.e., unlocked) only after the unbonding period has elapsed. > If a validator misbehaves (e.g., validates two different blocks at the same height), then the system can slash the validator's bonded tokens that gave its voting power during the misbehavior. > This prevents validators from misbehaving and immediately exiting with their tokens, > i.e., the unbonding period enables the system to punish misbehaving validators after the misbehaviors are committed. > For more details, take a look at the [Tendermint Specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/core/data_structures.md) > and the [Light Client Specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/light-client/verification/verification_002_draft.md#part-i---tendermint-blockchain). In the context of CCV, the validator sets of the consumer chains are chosen based on the tokens validators bonded on the provider chain, i.e., are chosen from the validator set of the provider chain. When validators misbehave on the consumer chains, their tokens bonded on the provider chain are slashed. As a result, the security gained from the value of the tokens bonded on the provider chain is shared with the consumer chains. Similarly to the single-chain approach, when a validator starts unbonding some of its bonded tokens, its voting power is reduced on all chains (i.e., provider chain and consumer chains); yet, due to delays in the communication over the IBC protocol (e.g., due to relaying packets), the voting power is not reduced immediately on the consumer chains. A further consequence of CCV is that the tokens are unbonded only after the unbonding period has elapsed on all chains starting from the moment the corresponding voting power was reduced. Thus, CCV may delay the unbonding of tokens validators bonded on the provider chain. ## Motivation [↑ Back to Outline](#outline) CCV is a primitive (i.e., a building block) that enables arbitrary shared security models: The security of a chain can be composed of security transferred from multiple provider chains including the chain itself (a consumer chain can be its own provider). As a result, CCV enables chains to borrow security from more established chains (e.g., Cosmos Hub), in order to boost their own security, i.e., increase the cost of attacking their networks. > **Intuition**: For example, for chains based on Tendermint consensus, a variety of attacks against the network are possible if an attacker acquire 1/3+ or 2/3+ of all bonded tokens. Since the market cap of newly created chains could be relatively low, an attacker could realistically acquire sufficient tokens to pass these thresholds. As a solution, CCV allows the newly created chains to use validators that have stake on chains with a much larger market cap and, as a result, increase the cost an attacker would have to pay. Moreover, CCV enables *hub minimalism*. In a nutshell, hub minimalism entails keeping a hub in the Cosmos network (e.g., the Cosmos Hub) as simple as possible, with as few features as possible in order to decrease the attack surface. CCV enables moving distinct features (e.g., DEX) to independent chains that are validated by the same set of validators as the hub. > **Versioning**: Note that CCV will be developed progressively. > This standard document specifies the V1 release, which will require the validator set of a consumer chain to be entirely provided by the provider chain. > In other words, once a provider chain agrees to provide security to a consumer chain, the entire validator set of the provider chain MUST validate also on the consumer chain. > > For more details on the planned releases, take a look at the [Interchain Security light paper](https://cosmos.github.io/interchain-security/introduction/overview). ## Definition [↑ Back to Outline](#outline) This section defines the new terms and concepts introduced by CCV. * **Provider Chain**: The blockchain that provides security, i.e., manages the validator set of the consumer chain. * **Consumer Chain**: The blockchain that consumes security, i.e., enables the provider chain to manage its validator set. > **Note**: In this specification, the validator set of the consumer chain is entirely provided by the provider chain. Both the provider and the consumer chains are [application-specific blockchains](/sdk/latest/learn/intro/overview), i.e., each blockchain's state machine is typically connected to the underlying consensus engine via a *blockchain interface*, such as [ABCI](https://github.com/tendermint/spec/tree/v0.7.1/spec/abci). The blockchain interface MUST enable the state machine to provide to the underlying consensus engine a set of validator updates, i.e., changes in the voting power granted to validators. Although this specification is not dependent on ABCI, for ease of presentation, we refer to the state machines as ABCI applications. Also, this specification considers a modular paradigm, i.e., the functionality of each ABCI application is separated into multiple modules, like the approach adopted by [Cosmos SDK](/sdk/latest/learn/concepts/modules#modules). * **CCV Module**: The module that implements the CCV protocol. Both the provider and the consumer chains have each their own CCV module. Furthermore, the functionalities provided by the CCV module differ between the provider chain and the consumer chains. For brevity, we use *provider CCV module* and *consumer CCV module* to refer to the CCV modules on the provider chain and on the consumer chains, respectively. * **CCV Channel**: A unique, ordered IBC channel that is used by the provider CCV module to exchange IBC packets with a consumer CCV module. Note that there is a separate CCV channel for every consumer chain. The IBC handler interface, the IBC relayer module interface, and both IBC channels and IBC packets are as defined in [ICS 25](../../core/ics-025-handler-interface), [ICS 26](../../core/ics-026-routing-module), and [ICS 4](../../core/ics-004-channel-and-packet-semantics), respectively. * **Validator Set Change (VSC)**: A change in the validator set of the provider chain that must be reflected in the validator sets of the consumer chains. Every VSC consists of a batch of validator updates provided to the consensus engine of the provider chain. > **Background**: In the context of single-chain validation, the changes of the validator set are triggered by the *Staking module*, > i.e., a module of the ABCI application that implements the proof of stake mechanism needed by the [security model](#security-model). > For an example, take a look at the [Staking module documentation](/sdk/latest/modules/staking/README) of Cosmos SDK. Some of the validator updates can decrease the voting power granted to validators. These decreases may be a consequence of unbonding operations (e.g., unbonding delegations) on the provider chain. which MUST NOT complete before reaching maturity on both the provider and all the consumer chains, i.e., the *unbonding period* (denoted as `UnbondingPeriod`) has elapsed on both the provider and all the consumer chains. Thus, a *VSC reaching maturity* on a consumer chain means that all the unbonding operations that resulted in validator updates included in that VSC have matured on the consumer chain. > **Background**: An *unbonding operation* is any operation of unbonding an amount of the tokens a validator bonded. Note that the bonded tokens correspond to the validator's voting power. We distinguish between three types of unbonding operations: > > * *undelegation* - a delegator unbonds tokens it previously delegated to a validator; > * *redelegation* - a delegator instantly redelegates tokens from a source validator to a different validator (the destination validator); > * *validator unbonding* - a validator is removed from the validator set; note that although validator unbondings do not entail unbonding tokens, they behave similarly to other unbonding operations. > > Regardless of the type, unbonding operations have two components: > > * The *initiation*, e.g., a delegator requests their delegated tokens to be unbonded. The initiation of an operation of unbonding an amount of the tokens a validator bonded results in a change in the voting power of that validator. > * The *completion*, e.g., the tokens are actually unbonded and transferred back to the delegator. To complete, unbonding operations must reach *maturity*, i.e., `UnbondingPeriod` must elapse since the operations were initiated. > > For more details, take a look at the [Cosmos SDK documentation](/sdk/latest/modules/staking/README). > > **Note**: Time periods are measured in terms of the block time, i.e., `currentTimestamp()` (as defined in [ICS 24](../../core/ics-024-host-requirements)). > As a result, a consumer chain MAY start the unbonding period for every VSC that it applies in a block at any point during that block. * **Slash Request**: A request by a consumer chain to *slash* the tokens bonded by a validator on the provider chain as a consequence of that validator misbehavior on the consumer chain. A slash request MAY also result in the misbehaving validator being *jailed* for a period of time, during which it cannot be part of the validator set. > **Background**: In the context of single-chain validation, slashing and jailing misbehaving validators is handled by the *Slashing module*, > i.e., a module of the ABCI application that enables the application to discourage misbehaving validators. > For an example, take a look at the [Slashing module documentation](/sdk/latest/modules/slashing/README) of Cosmos SDK. ## Overview [↑ Back to Outline](#outline) CCV must handle the following types of operations: * **Channel Initialization**: Create unique, ordered IBC channels between the provider chain and every consumer chain. * **Validator Set Update**: It is a two-part operation, i.e., * update the validator sets of all the consumer chains based on the information obtained from the *provider Staking module* (i.e., the Staking module on the provider chain) on the amount of tokens bonded by validators on the provider chain; * and enable the timely completion (cf. the unbonding periods on the consumer chains) of unbonding operations (i.e., operations of unbonding bonded tokens). * **Consumer Initiated Slashing**: Enable the provider chain to slash and jail bonded validators that misbehave while validating on the consumer chain. * **Reward Distribution**: Enable the distribution of block production rewards and transaction fees from the consumer chains to the validators on the provider chain. ### Channel Initialization [↑ Back to Outline](#outline) The CCV Channel initialization differentiates between chains that start directly as consumer chains and existing chains that transition to consumer chains. In both cases, consumer chains are created through governance proposals. For an example of how governance proposals work, take a look at the [Governance module documentation](/sdk/latest/modules/gov/README) of Cosmos SDK. #### Channel Initialization: New Chains The following figure shows an overview of the CCV Channel initialization for new chains. Channel Initialization Overview: New Chain The channel initialization for new chains consists of three phases: * **Create clients**: Once the provider CCV module receives a proposal to add a new consumer chain with an empty connection ID, it creates a client of the consumer chain (as defined in [ICS 2](../../core/ics-002-client-semantics)) and a genesis state of the consumer CCV module. Then, the operators of validators in the validator set of the provider chain must each query the provider for the CCV genesis state and start a validator node of the consumer chain. Once the consumer chain starts, the application receives an `InitChain` message from the consensus engine (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/abci/abci.md#initchain)). The `InitChain` message triggers the call to the `InitGenesis()` method of the consumer CCV module, which creates a client of the provider chain. For client creation, both a `ClientState` and a `ConsensusState` are necessary (as defined in [ICS 2](../../core/ics-002-client-semantics)); both are contained in the genesis state of the consumer CCV module. The genesis state is distributed to all operators that need to start a full node of the consumer chain (the mechanism of distributing the genesis state is outside the scope of this specification). Finally, the consumer CCV module initiates both the connection handshake (as defined in [ICS 3](../../core/ics-003-connection-semantics)) and the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)). > Note that at genesis, the validator set of the consumer chain matches the validator set of the provider chain. * **Connection handshake**: A relayer (as defined in [ICS 18](../../relayer/ics-018-relayer-algorithms)) is responsible for completing the connection handshake (as defined in [ICS 3](../../core/ics-003-connection-semantics)). * **Channel handshake**: A relayer is responsible for completing the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)). The handshake consists of four messages that need to be received for a channel built on top of the expected clients. Note that the channel handshake is initiated on the consumer chain. * *OnChanOpenInit*: On receiving a `ChanOpenInit` message, the consumer CCV module verifies that the underlying client associated with this channel is the expected client of the provider chain (i.e., created during genesis). * *OnChanOpenTry*: On receiving a `ChanOpenTry` message, the provider CCV module verifies that the underlying client associated with this channel is the expected client of the consumer chain (i.e., created when handling the governance proposal). * *OnChanOpenAck*: On receiving the *FIRST* `ChanOpenAck` message, the consumer CCV module considers its side of the CCV channel to be established. Also, if a transfer channel ID was not provided in the governance proposal, the consumer CCV module initiates the opening handshake for the token transfer channel required by the Reward Distribution operation (see the [Reward Distribution](#reward-distribution) section). * *OnChanOpenConfirm*: On receiving the *FIRST* `ChanOpenConfirm` message, the provider CCV module considers its side of the CCV channel to be established. #### Channel Initialization: Existing Chains The following figure shows an overview of the CCV Channel initialization for existing chains. Channel Initialization Overview: Existing Chain The channel initialization for existing chains consists of three phases: * **Start consumer CCV module**: Once the provider CCV module receives a proposal to add a new consumer chain with a valid *connection ID*, it creates a genesis state of the consumer CCV module. Then, the existing chain must upgrade by adding the consumer CCV module and initialize it using the CCV genesis state created by the provider. Once the consumer CCV module starts, it initiates the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)). * **Channel handshake**: A relayer is responsible for completing the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)). The handshake consists of four messages that need to be received for a channel built on top of the expected clients (i.e., the clients used by the connection provided in the governance proposal). Note that the channel handshake is initiated on the consumer chain. * *OnChanOpenInit*: On receiving a `ChanOpenInit` message, the consumer CCV module verifies that the underlying client associated with this channel is the expected client of the provider chain. * *OnChanOpenTry*: On receiving a `ChanOpenTry` message, the provider CCV module verifies that the underlying client associated with this channel is the expected client of the consumer chain. * *OnChanOpenAck*: On receiving the *FIRST* `ChanOpenAck` message, the consumer CCV module considers its side of the CCV channel to be established. Also, if a transfer channel ID was not provided in the governance proposal, the consumer CCV module initiates the opening handshake for the token transfer channel required by the Reward Distribution operation (see the [Reward Distribution](#reward-distribution) section). Finally, the consumer CCV module makes a requests to the Staking module of the existing chain to replace its validator set with the initial validator set from the CCV genesis state created by the provider. > Note that this is the same as the provider validator set when the governance proposal was handled. * *OnChanOpenConfirm*: On receiving the *FIRST* `ChanOpenConfirm` message, the provider CCV module considers its side of the CCV channel to be established. * **Transition to consumer chain**: Once the validator set on the existing chain is replace by the initial validator set (from the CCV genesis state created by the provider), the existing chain becomes a consumer chain. > **Note**: For both new and existing chains, as long as the [assumptions required by CCV](./system_model_and_properties#assumptions) hold (e.g., *Correct Relayer*), every governance proposal to spawn a new consumer chain that passes on the provider chain results eventually in a CCV channel being created. > Furthermore, the "*FIRST*" keyword in the above description ensures the uniqueness of the CCV channel, i.e., all subsequent attempts to create another CCV channel to the same consumer chain will fail. > > **Note**: For both new and existing chains, until the CCV channel is established, the initial validator set of the consumer chain cannot be updated (see the [Validator Set Update](#validator-set-update) section) and the validators from this initial set cannot be slashed (see the [Consumer Initiated Slashing](#consumer-initiated-slashing) section). > This means that the consumer chain is *not yet secured* by the provider chain. > Thus, to reduce the attack surface during channel initialization, the consumer chain SHOULD enable user transactions only after the CCV channel is established (i.e., after receiving the first VSC). > As a consequence, a malicious initial validator set can only influence the initialization of the CCV channel. For a more detailed description of Channel Initialization, take a look at the [technical specification](./methods#initialization). ### Validator Set Update [↑ Back to Outline](#outline) In the context of VSCs, the CCV module enables the following functionalities: * On the provider chain, * **provide** VSCs to the consumer chains, for them to update their validator sets according to the validator set of the provider chain; providing VSCs entails sending `VSCPacket`s to all consumer chains; * **register** VSC maturity notifications from the consumer chain. * On every consumer chain, * **apply** the VSCs provided by the provider chain to the validator set of the consumer chain; * **notify** the provider chain that the provided VSCs have matured on this consumer chain; notifying of VSCs maturity entails sending `VSCMaturedPacket`s to the provider chain. These functionalities are depicted in the following figure that shows an overview of the Validator Set Update operation of CCV. For a more detailed description of Validator Set Update, take a look at the [technical specification](./methods#validator-set-update). Validator Set Update Overview #### Completion of Unbonding Operations In the context of single-chain validation, the completion of any unbonding operation requires the `UnbondingPeriod` to elapse since the operations was initiated (i.e., the operation MUST reach maturity). In the context of CCV, the completion MUST require also the unbonding operation to reach maturity on **all** consumer chains (for the [Security Model](#security-model) to be preserved). Therefore, the provider Staking module needs to be aware of the VSC maturity notifications registered by the provider CCV module. The ***provider chain*** achieves this through the following approach: * The Staking module is notifying the CCV module when any unbonding operation is initiated. As a result, the CCV module maps all the unbonding operations to the corresponding VSCs. * When the CCV module registers maturity notifications for a VSC from all consumer chains, it notifies the Staking module of the maturity of all unbonding operations mapped to this VSC. This enables the Staking module to complete the unbonding operations only when they reach maturity on both the provider chain and on all the consumer chains. This approach is depicted in the following figure that shows an overview of the interface between the provider CCV module and the provider Staking module in the context of the Validator Set Update operation of CCV: * In `Block 1`, two unbonding operations are initiated (i.e., `undelegate-1` and `redelegate-1`) in the provider Staking module. For each operation, the provider Staking module notifies the provider CCV module. As a result, the provider CCV module maps these to operation to `vscId`, which is the ID of the following VSC (i.e., `VSC1`). The provider CCV module provides `VSC1` to all consumer chains. * In `Block 2`, the same approach is used for `undelegate-2`. * In `Block j`, `UnbondingPeriod` has elapsed since `Block 1`. In the meantime, the provider CCV module registered maturity notifications for `VSC1` from all consumer chains and, consequently, notified the provider Staking module of the maturity of both `undelegate-1` and `redelegate-1`. As a result, the provider Staking module completes both unbonding operations in `Block j`. * In `Block k`, `UnbondingPeriod` has elapsed since `Block 2`. In the meantime, the provider CCV module has NOT yet registered maturity notifications for `VSC2` from all consumer chains. As a result, the provider Staking module CANNOT complete `undelegate-2` in `Block k`. The unbonding operation is completed later once the provider CCV module registered maturity notifications for `VSC2` from all consumer chains. Completion of Unbonding Operations ### Consumer Initiated Slashing [↑ Back to Outline](#outline) For the [Security Model](#security-model) to be preserved, misbehaving validators MUST be slashed (and MAY be jailed, i.e., removed from the validator set). A prerequisite to slashing validators is to receive valid evidence of their misbehavior. Thus, when slashing a validator, we distinguish between three events and the heights when they occur: * `infractionHeight`, the height at which the misbehavior (or infraction) happened; * `evidenceHeight`, the height at which the evidence of misbehavior is received; * `slashingHeight`, the height at which the validator is slashed (and jailed). > **Note**: In the context of single-chain validation, usually `evidenceHeight = slashingHeight`. The [Security Model](#security-model) guarantees that any misbehaving validator can be slashed for at least the unbonding period, i.e., as long as that validator's tokens are not unbonded yet, they can be slashed. However, if the tokens start unbonding before `infractionHeight` (i.e., the tokens did not contribute to the voting power that committed the infraction) then the tokens MUST NOT be slashed. In the context of CCV, validators (with tokens bonded on the provider chain) MUST be slashed for infractions committed on the consumer chains at heights for which they have voting power. Thus, although the infractions are committed on the consumer chains and evidence of these infractions is submitted to the consumer chains, the slashing happens on the provider chain. As a result, the Consumer Initiated Slashing operation requires, for every consumer chain, a mapping from consumer chain block heights to provider chain block heights. The following figure shows the intuition behind such a mapping using the provided VSCs. The four unbonding operations (i.e., undelegations) occur on the provider chain and, as a consequence, the provider chain provides VSCs to the consumer chain, e.g., `undelegate-3` results in `VSC3` being provided. The four colors (i.e., red, blue, green, and yellow) indicate the mapping of consumer chain heights to provider chain heights. Note that on the provider chain there is only one block of a given color. Also, note that the three white blocks between the green and the yellow blocks on the provider chain have the same validator set.\ As a result, a validator misbehaving on the consumer chain, e.g., in either of the two green blocks, is slashed the same as if misbehaving on the provider chain, e.g., in the green block. This ensures that once unbonding operations are initiated, the corresponding unbonding tokens are not slashed for infractions committed in the subsequent blocks, e.g., the tokens unbonding due to `undelegate-3` are not slashed for infractions committed in or after the green blocks. Intuition of Mapping Between Provider and Consumer Heights The following figure shows describes how CCV creates the mapping from consumer chain heights to provider chain heights. For clarity, we use `Hp*` and `Hc*` to denote block heights on the provider chain and consumer chain, respectively. Mapping Between Provider and Consumer Heights * For every block, the provider CCV module maps the ID of the VSC it provides to the consumer chains to the height of the subsequent block, i.e., `VSCtoH[VSC.id] = Hp + 1`, for a VSC provided at height `Hp`. Intuitively, this means that the validator updates in a provided VSC will update the voting power at height `VSCtoH[VSC.id]`. * For every block, every consumer CCV module maps the height of the subsequent block to the ID of the latest received VSC, e.g., `HtoVSC[Hc2 + 1] = VSC1.id`. Intuitively, this means that the voting power on the consumer chain during a block `Hc` was updated by the VSC with ID `HtoVSC[Hc]`. > **Note**: It is possible for multiple VSCs to be received by the consumer chain within the same block. For more details, take a look at the [Validator sets, validator updates and VSCs](./system_model_and_properties#validator-sets-validator-updates-and-vscs) section. * By default, every consumer CCV module maps any block height to `0` (i.e., VSC IDs start from `1`). Intuitively, this means that the voting power on the consumer chain at height `Hc` with `HtoVSC(Hc) = 0` was setup at genesis during Channel Initialization. * For every consumer chain, the provider CCV module sets `VSCtoH[0]` to the height when it establishes the CCV channel to this consumer chain. Note that the validator set on the provider chain at height `VSCtoH[0]` matches the validator set at the height when the first VSC is provided to this consumer chain. This means that this validator set on the provider chain matches the validator set on the consumer chain at all heights `Hc` with `HtoVSC[Hc] = 0`. The following figure shows an overview of the Consumer Initiated Slashing operation of CCV. Consumer Initiated Slashing * At (evidence) height `Hc2`, the consumer chain receives evidence that a validator `V` misbehaved at (infraction) height `Hc1`. As a result, the consumer CCV module sends a `SlashPacket` to the provider chain: It makes a request to slash `V`, but it replaces the infraction height `Hc1` with `HtoVSC[Hc1]`, i.e., the ID of the VSC that updated the "misbehaving voting power" or `0` if such a VSC does not exist. * The provider CCV module receives at (slashing) height `Hp1` the `SlashPacket` with `vscId = HtoVSC[Hc1]`. As a result, it requests the provider Slashing module to slash `V`, but it set the infraction height to `VSCtoH[vscId]`, i.e., * if `vscId != 0`, the height on the provider chain where the voting power was updated by the VSC with ID `vscId`; * otherwise, the height at which the CCV channel to this consumer chain was established. > **Note**: As a consequence of slashing (and potentially jailing) `V`, the Staking module updates accordingly `V`'s voting power. This update MUST be visible in the next VSC provided to the consumer chains. For a more detailed description of Consumer Initiated Slashing, take a look at the [technical specification](./methods#consumer-initiated-slashing). ### Reward Distribution [↑ Back to Outline](#outline) In the context of single-chain validation, the *Distribution module*, i.e., a module of the ABCI application, handles the distribution of rewards (i.e., block production rewards and transaction fees) to every validator account based on their total voting power; these rewards are then further distributed to the delegators. For an example, take a look at the [Distribution module documentation](/sdk/latest/modules/distribution/README) of Cosmos SDK. At the beginning of every block, the rewards for the previous block are pooled into a distribution module account. The Reward Distribution operation of CCV enables every consumer chain to transfer a fraction of these rewards to the provider chain. The operation consists of two steps that are depicted in the following figure: Reward Distribution * At the beginning of every block on the consumer chain, a fraction of the rewards are transferred to an account on the consumer CCV module. * At regular intervals (e.g., every `1000` blocks), the consumer CCV module sends the accumulated rewards to the distribution module account on the provider chain through an IBC token transfer packet (as defined in [ICS 20](../ics-020-fungible-token-transfer/README)). Note that the IBC transfer packet is sent over a separate unordered channel. As a result, the reward distribution is not synchronized with the other CCV operations, e.g., some validators may miss out on some rewards by unbonding before an IBC transfer packet is received, while other validators may get some extra rewards by bonding before an IBC transfer packet is received. > **Note**: From the perspective of the distribution module account on the provider chain, the rewards coming from the consumer chains are indistinguishable from locally collected rewards and thus, are distributed to all the validators and their delegators. As a prerequisite of this approach, every consumer chain must open a token transfer channel to the provider chain and be made aware of the address of the distribution module account on the provider chain, both of which happen during channel initialization. * On receiving a `ChanOpenAck` message, the consumer CCV module initiates the opening handshake for the token transfer channel using the same client and connection as for the CCV channel. * On receiving a `ChanOpenTry` message, the provider CCV module adds the address of the distribution module account to the channel version as metadata (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics/README#definitions)). # CCV: System Model and Properties Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-028-cross-chain-validation/system_model_and_properties ## Outline * [Assumptions](#assumptions) * [Desired Properties](#desired-properties) * [System Properties](#system-properties) * [CCV Channel](#ccv-channel) * [Validator Sets, Validator Updates and VSCs](#validator-sets-validator-updates-and-vscs) * [Staking Module Interface](#staking-module-interface) * [Validator Set Update](#validator-set-update) * [Consumer Initiated Slashing](#consumer-initiated-slashing) * [Reward Distribution](#reward-distribution) * [Correctness Reasoning](#correctness-reasoning) ## Assumptions [↑ Back to Outline](#outline) As part of a modular ABCI application, CCV interacts with both the consensus engine (via ABCI) and other application modules (e.g, the Staking module). As an IBC application, CCV interacts with external relayers (defined in [ICS 18](../../relayer/ics-018-relayer-algorithms)). In this section we specify what we assume about these other components. A more thorough discussion of the environment in which CCV operates is given in the section [Placing CCV within an ABCI Application](./technical_specification#placing-ccv-within-an-abci-application). > **Intuition**: > > CCV safety relies on the *Safe Blockchain* assumption, > i.e., neither *Live Blockchain* and *Correct Relayer* are required for safety. > Note though that CCV liveness relies on both *Live Blockchain* and *Correct Relayer* assumptions; > furthermore, the *Correct Relayer* assumption relies on both *Safe Blockchain* and *Live Blockchain* assumptions. > > The *Validator Update Provision*, *Unbonding Safety*, *Slashing Warranty*, and *Distribution Warranty* assumptions define what is needed from the ABCI application of the provider chain. > > The *Evidence Provision* assumptions defines what is needed from the ABCI application of the consumer chains. * ***Safe Blockchain***: Both the provider and the consumer chains are *safe*. This means that, for every chain, the underlying consensus engine satisfies safety (e.g., the chain does not fork) and the execution of the state machine follows the described protocol. * ***Live Blockchain***: Both the provider and the consumer chains are *live*. This means that, for every chain, the underlying consensus engine satisfies liveness (i.e., new blocks are eventually added to the chain). > **Note**: Both *Safe Blockchain* and *Live Blockchain* assumptions require the consensus engine's assumptions to hold, e.g., less than a third of the voting power is Byzantine. For an example, take a look at the [Tendermint Paper](https://arxiv.org/pdf/1807.04938.pdf). * ***Correct Relayer***: There is at least one *correct*, *live* relayer between the provider and consumer chains. This assumption has the following implications. * The opening handshake messages on the CCV channel are relayed before the Channel Initialization subprotocol times out (see `initTimeout`). * Every packet sent on the CCV channel is relayed to the receiving end before the packet timeout elapses (see both `vscTimeout` and `ccvTimeoutTimestamp`). * A correct relayer will eventually relay packets on the token transfer channel. Clearly, the CCV protocol is responsible of setting the timeouts (see `ccvTimeoutTimestamp`, `vscTimeout`, `initTimeout` in the [CCV State](./data_structures#ccv-state)), such that the *Correct Relayer* assumption is feasible. > **Discussion**: IBC relies on timeouts to signal that a sent packet is not going to be received on the other end. > Once an ordered IBC channel timeouts, the channel is closed (see [ICS 4](../../core/ics-004-channel-and-packet-semantics)). > The *Correct Relayer* assumption is necessary to ensure that the CCV channel **cannot** ever timeout and, as a result, cannot transit to the closed state. > > **In practice**, the *Correct Relayer* assumption is realistic since any validator could play the role of the relayer and it is in the best interest of correct validators to successfully relay packets. > > The following strategy is a practical example of how to ensure the *Correct Relayer* assumption holds. > Let S denote the sending chain and D the destination chain; > and let `drift(S,D)` be the time drift between S and D, > i.e., `drift(S,D) = S.currentTimestamp() - D.currentTimestamp()` (`drift(S,D) > 0` means that S is "ahead" of D). > For every packet, S only sets `timeoutTimestamp = S.currentTimestamp() + to`, with `to` an application-level parameter. > The `timeoutTimestamp` indicates *a timestamp on the destination chain* after which the packet will no longer be processed (cf. [ICS 4](../../core/ics-004-channel-and-packet-semantics)). > Therefore, the packet MUST be relayed within a time period of `to - drift(S,D)`, > i.e., `to - drift(S,D) > RTmax`, where `RTmax` is the maximum relaying time across all packet. > Theoretically, choosing the value of `to` requires knowing the value of `drift(S,D)` (i.e., `to > drift(S,D)`); > yet, `drift(S,D)` is not known at a chain level. > In practice, choosing `to` such that `to >> drift(S,D)` and `to >> RTmax`, e.g., `to = 4 weeks`, makes the *Correct Relayer* assumption feasible. * ***Validator Update Provision***: Let `{U1, U2, ..., Ui}` be a batch of validator updates applied (by the provider Staking module) to the validator set of the provider chain at block height `h`. Then, the batch of validator updates obtained (by the provider CCV module) from the provider Staking module at height `h` MUST be exactly the batch `{U1, U2, ..., Ui}`. * ***Unbonding Safety***: Let `uo` be any unbonding operation that starts with an unbonding transaction being executed and completes with the event that returns the corresponding stake; let `U(uo)` be the validator update caused by initiating `uo`; let `vsc(uo)` be the VSC that contains `U(uo)`. Then, * (*unbonding initiation*) the provider CCV module MUST be notified of `uo`'s initiation before receiving `U(uo)`; * (*unbonding completion*) `uo` MUST NOT complete on the provider chain before the provider chain registers notifications of `vsc(uo)`'s maturity from all consumer chains. > **Note**: Depending on the implementation, the (*unbonding initiation*) part of the *Unbonding Safety* MAY NOT be necessary for validator unbonding operations. * ***Slashing Warranty***: If the provider ABCI application (e.g., the Slashing module) receives a request to slash a validator `val` that misbehaved at block height `h`, then it slashes the amount of tokens `val` had bonded at height `h` except the amount that has already completely unbonded. * ***Evidence Provision***: If the consumer ABCI application receives a valid evidence of misbehavior at block height `h`, then it MUST submit it to the consumer CCV module *exactly once* and at the same height `h`. Furthermore, the consumer ABCI application MUST NOT submit invalid evidence to the consumer CCV module. > **Note**: What constitutes a valid evidence of misbehavior depends on the type of misbehavior and it is outside the scope of this specification. * ***Distribution Warranty***: The provider ABCI application (e.g., the Distribution module) distributes the tokens from the distribution module account among the validators that are part of the validator set. ## Desired Properties The following properties are concerned with **one provider chain** providing security to **multiple consumer chains**. Between the provider chain and each consumer chain, a separate (unique) CCV channel is established. > **Note**: Except for liveness properties -- *Channel Liveness*, *Apply VSC Liveness*, *Register Maturity Liveness*, and *Distribution Liveness* -- none of the properties of CCV require the *Correct Relayer* assumption to hold. > Nonetheless, the *Correct Relayer* assumption is necessary to guarantee the systems properties (except for *Validator Set Replication*) -- *Bond-Based Consumer Voting Power*, *Slashable Consumer Misbehavior*, and *Consumer Rewards Distribution*. ### System Properties [↑ Back to Outline](#outline) We use the following notations: * `ts(h)` is the timestamp of a block with height `h`, i.e., `ts(h) = B.currentTimestamp()`, where `B` is the block at height `h`; * `pBonded(h,val)` is the number of tokens bonded by validator `val` on the provider chain at block height `h`; * `pUnbonding(h,val)` is the number of tokens a validator `val` starts unbonding on the provider at block height `h`; * `VP(T)` is the voting power associated to a number `T` of tokens; * `Power(c,h,val)` is the voting power granted to a validator `val` on a chain `c` at block height `h`; * `Token(power)` is the amount of tokens necessary to be bonded (on the provider chain) by a validator to be granted `power` voting power, i.e., `Token(VP(T)) = T`; * `slash(val, h, hi, sf)` is the amount of token slashed from a validator `val` on the provider chain (i.e., `pc`) at height `h` for an infraction (with a slashing fraction of `sf`) committed at (provider) height `hi`, i.e., `slash(val, h, hi, sf) = sf * Token(Power(pc,hi,val))`; note that the infraction can be committed also on a consumer chain, in which case `hi` is the corresponding height on the provider chain. Also, we use `ha << hb` to denote an order relation between heights, i.e., the block at height `ha` *happens before* the block at height `hb`. For heights on the same chain, `<<` is equivalent to `<`, i.e., `ha << hb` entails `hb` is larger than `ha`. For heights on two different chains, `<<` is establish by the packets sent over an order channel between two chains, i.e., if a chain `A` sends at height `ha` a packet to a chain `B` and `B` receives it at height `hb`, then `ha << hb`. > **Note**: `<<` is transitive, i.e., `ha << hb` and `hb << hc` entail `ha << hc`. > > **Note**: The block on the proposer chain that handles a governance proposal to spawn a new consumer chain `cc` *happens before* all the blocks of `cc`. CCV provides the following system properties. * ***Validator Set Replication***: Every validator set on any consumer chain MUST either be or have been a validator set on the provider chain. * ***Bond-Based Consumer Voting Power***: Let `val` be a validator, `cc` be a consumer chain, both `hc` and `hc'` be heights on `cc`, and both `hp` and `hp'` be heights on the provider chain, such that * `val` has `Power(cc,hc,val)` voting power on `cc` at height `hc`; * `hc'` is the smallest height on `cc` that satisfies `ts(hc') >= ts(hc) + UnbondingPeriod`, i.e., `val` cannot completely unbond on `cc` before `hc'`; * `hp` is the largest height on the provider chain that satisfies `hp << hc`, i.e., `Power(pc,hp,val) = Power(cc,hc,val)`, where `pc` is the provider chain; * `hp'` is the smallest height on the provider chain that satisfies `hc' << hp'`, i.e., `val` cannot completely unbond on the provider chain before `hp'`; * `sumUnbonding(hp, h, val)` is the sum of all tokens of `val` that start unbonding on the provider at all heights `hu` and are still unbonding at height `h`, such that `hp < hu <= h` * `sumSlash(hp, h, val)` is the sum of the slashes of `val` at all heights `hs` for infractions committed at `hp`, such that `hp < hs <= h`. Then for all heights `h` on the provider chain, ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} hp <= h < hp': Power(cc,hc,val) <= VP( pBonded(h,val) + sumUnbonding(hp, h, val) + sumSlash(hp, h, val) ) ``` > **Note**: The reason for `+ sumUnbonding(hp, h, val)` in the above inequality is that tokens that `val` start unbonding after `hp` have contributed to the power granted to `val` at height `hc` on `cc` (i.e., `Power(cc,hc,val)`). > As a result, these tokens should be available for slashing until `hp'`. > > **Note**: The reason for `+ sumSlash(hp, h, val)` in the above inequality is that slashing `val` reduces its locked tokens (i.e., `pBonded(h,val)` and `sumUnbonding(hp, h, val)`), however it does not reduce the power already granted to it at height `hc` on `cc` (i.e., `Power(cc,hc,val)`). > > **Intuition**: The *Bond-Based Consumer Voting Power* property ensures that validators that validate on the consumer chains have enough tokens bonded on the provider chain for a sufficient amount of time such that the security model holds. > This means that if the validators misbehave on the consumer chains, their tokens bonded on the provider chain can be slashed during the unbonding period. > For example, if one unit of voting power requires `1.000.000` bonded tokens (i.e., `VP(1.000.000)=1`), > then a validator that gets one unit of voting power on a consumer chain must have at least `1.000.000` tokens bonded on the provider chain until the unbonding period elapses on the consumer chain. > > **Note**: When an existing chain becomes a consumer chain (see [Channel Initialization: Existing Chains](./overview_and_basic_concepts#channel-initialization-existing-chains)), the existing validator set is replaced by the provider validator set. > For safety, the stake bonded by the existing validator set must remain bonded until the unbonding period elapses. > Thus, the existing Staking module must be kept for at least the unbonding period. * ***Slashable Consumer Misbehavior***: If a validator `val` commits an infraction, with a slashing fraction of `sf`, on a consumer chain `cc` at a block height `hi`, then any evidence of misbehavior that is received by `cc` at height `he`, such that `ts(he) < ts(hi) + UnbondingPeriod`, MUST results in *exactly* the amount of tokens `sf*Token(Power(cc,hi,val))` to be slashed on the provider chain. Furthermore, `val` MUST NOT be slashed more than once for the same misbehavior. > **Note:** Unlike in single-chain validation, in CCV the tokens `sf*Token(Power(cc,hi,val))` MAY be slashed even if the evidence of misbehavior is received at height `he` such that `ts(he) >= ts(hi) + UnbondingPeriod`, > since unbonding operations need to reach maturity on both the provider and all the consumer chains. > > **Note:** The *Slashable Consumer Misbehavior* property also ensures that if a delegator starts unbonding an amount `x` of tokens from `val` before height `hi`, then `x` will not be slashed, since `x` is not part of `Token(Power(c,hi,val))`. * ***Consumer Rewards Distribution***: If a consumer chain sends to the provider chain an amount `T` of tokens as reward for providing security, then * `T` (equivalent) tokens MUST be eventually minted on the provider chain and then distributed among the validators that are part of the validator set; * the total supply of tokens MUST be preserved, i.e., the `T` (original) tokens are escrowed on the consumer chain. ### CCV Channel [↑ Back to Outline](#outline) * ***Channel Uniqueness***: The channel between the provider chain and a consumer chain MUST be unique. * ***Channel Validity***: If a packet `P` is received by one end of a CCV channel, then `P` MUST have been sent by the other end of the channel. * ***Channel Order***: If a packet `P1` is sent over a CCV channel before a packet `P2`, then `P2` MUST NOT be received by the other end of the channel before `P1`. * ***Channel Liveness***: Every packet sent over a CCV channel MUST eventually be received by the other end of the channel. ### Validator Sets, Validator Updates and VSCs [↑ Back to Outline](#outline) In this section, we provide a short discussion on how the validator set, the validator updates, and the VSCs relates in the context of multiple chains. Every chain consists of a sequence of blocks. At the end of each block, validator updates (i.e., changes in the validators voting power) results in changes in the validator set of the next block. Thus, the sequence of blocks produces a sequence of validator updates and a sequence of validator sets. Furthermore, the sequence of validator updates on the provider chain results in a sequence of VSCs to all consumer chains. Ideally, this sequence of VSCs is applied by every consumer chain, resulting in a sequence of validator sets identical to the one on the provider chain. However, in general this need not be the case. The reason is twofold: * first, given any two chains `A` and `B`, we cannot assume that `A`'s rate of adding new block is the same as `B`'s rate (i.e., we consider the sequences of blocks of any two chains to be completely asynchronous); * and second, due to relaying delays, we cannot assume that the rate of sending VSCs matches the rate of receiving VSCs. As a result, it is possible for multiple VSCs to be received by a consumer chain within the same block and be applied together at the end of the block, i.e., the validator updates within the VSCs are being *aggregated* by keeping only the latest update per validator. As a consequence, some validator sets on the provider chain are not existing on all consumer chains. In other words, the validator sets on each consumer chain form a *subsequence* of the validator sets on the provider chain. Nonetheless, as a **requirement of CCV**, *all the validator updates on the provider chain MUST be included in the sequence of validator sets on all consumer chains*. This is possible since every validator update contains *the absolute voting power* of that validator. Given a validator `val`, the sequence of validator updates targeting `val` (i.e., updates of the voting power of `val`) is the prefix sum of the sequence of relative changes of the voting power of `val`. Thus, given a validator update `U` targeting `val` that occurs at a block height `h`, `U` *sums up* all the relative changes of the voting power of `val` that occur until height `h`, i.e., `U = c_1+c_2+...+c_i`, such that `c_i` is the last relative change that occurs by `h`. Note that relative changes are integer values. As a consequence, CCV can rely on the following property: * ***Validator Update Inclusion***: Let `U1` and `U2` be two validator updates targeting the same validator `val`. If `U1` occurs before `U2`, then `U2` sums up all the changes of the voting power of `val` that are summed up by `U1`, i.e., * `U1 = c_1+c_2+...+c_i` and * `U2 = c_1+c_2+...+c_i+c_(i+1)+...+c_j`. The *Validator Update Inclusion* property enables CCV to aggregate multiple VSCs. It is sufficient for the consumer chains to apply only the last update per validator. Since the last update of a validator *includes* all the previous updates of that validator, once it is applied, all the previous updates are also applied. ### Staking Module Interface [↑ Back to Outline](#outline) The following properties define the guarantees of CCV on *providing* VSCs to the consumer chains as a consequence of validator updates on the provider chain. * ***Validator Update To VSC Validity***: Every VSC provided to a consumer chain MUST contain only validator updates that were applied to the validator set of the provider chain (i.e., resulted from a change in the amount of bonded tokens on the provider chain). * ***Validator Update To VSC Order***: Let `U1` and `U2` be two validator updates on the provider chain. If `U1` occurs before `U2`, then `U2` MUST NOT be included in a provided VSC before `U1`. Note that the order within a single VSC is not relevant. * ***Validator Update To VSC Liveness***: Every update of a validator in the validator set of the provider chain MUST eventually be included in a VSC provided to all consumer chains. Note that as a consequence of the *Validator Update To VSC Liveness* property, CCV guarantees the following property: * **Provide VSC uniformity**: If the provider chain provides a VSC to a consumer chain, then it MUST eventually provide that VSC to all consumer chains. ### Validator Set Update [↑ Back to Outline](#outline) The provider chain providing VSCs to the consumer chains has two desired outcomes: the consumer chains apply the VSCs; and the provider chain registers VSC maturity notifications from every consumer chain. Thus, for clarity, we split the properties of VSCs in two: properties of applying provided VSCs on the consumer chains; and properties of registering VSC maturity notifications on the provider chain. For simplicity, we focus on a single consumer chain. The following properties define the guarantees of CCV on *applying* on the consumer chain VSCs *provided* by the provider chain. * ***Apply VSC Validity***: Every VSC applied by the consumer chain MUST be provided by the provider chain. * ***Apply VSC Order***: If a VSC `vsc1` is provided by the provider chain before a VSC `vsc2`, then the consumer chain MUST NOT apply the validator updates included in `vsc2` before the validator updates included in `vsc1`. * ***Apply VSC Liveness***: If the provider chain provides a VSC `vsc`, then the consumer chain MUST eventually apply all validator updates included in `vsc`. The following properties define the guarantees of CCV on *registering* on the provider chain maturity notifications (from the consumer chain) of VSCs *provided* by the provider chain to the consumer chain. * ***Register Maturity Validity***: If the provider chain registers a maturity notification of a VSC from the consumer chain, then the provider chain MUST have provided that VSC to the consumer chain. * ***Register Maturity Timeliness***: The provider chain MUST NOT register a maturity notification of a VSC `vsc` before `UnbondingPeriod` has elapsed on the consumer chain since the consumer chain applied `vsc`. * ***Register Maturity Order***: If a VSC `vsc1` was provided by the provider chain before another VSC `vsc2`, then the provider chain MUST NOT register the maturity notification of `vsc2` before the maturity notification of `vsc1`. * ***Register Maturity Liveness***: If the provider chain provides a VSC `vsc` to the consumer chain, then the provider chain MUST eventually register a maturity notification of `vsc` from the consumer chain. ### Consumer Initiated Slashing [↑ Back to Outline](#outline) * ***Consumer Slashing Warranty***: Let `cc` be a consumer chain, such that its CCV module receives at height `he` evidence that a validator `val` misbehaved on `cc` at height `hi`. Let `hv` be the height when the CCV module of `cc` receives the first VSC from the provider CCV module, i.e., the height when the CCV channel is established. Then, the CCV module of `cc` MUST send (to the provider CCV module) *exactly one* `SlashPacket` `P`, such that * `P` is sent at height `h = max(he, hv)`; * `P.val = val` and `P.id = HtoVSC[hi]`, i.e., the ID of the latest VSC that updated the validator set on `cc` at height `hi` or `0` if such a VSC does not exist (if `hi < hv`). > **Note**: A consequence of the *Consumer Slashing Warranty* property is that the initial validator set on a consumer chain cannot be slashed during the initialization of the CCV channel. > Therefore, consumer chains *SHOULD NOT allow user transactions before the CCV channel is established*. > Note that once the CCV channel is established (i.e., a VSC is received from the provider CCV module), CCV enables the slashing of the initial validator set for infractions committed during channel initialization. * ***Provider Slashing Warranty***: If the provider CCV module receives from a consumer chain `cc` a `SlashPacket` containing a validator `val` and a VSC ID `vscId`, then it MUST make *exactly one* request to the provider Slashing module to slash `val` for misbehaving at height `h`, such that * if `vscId = 0`, `h` is the height of the block when the provider chain established a CCV channel to `cc`; * otherwise, `h` is the height of the block immediately subsequent to the block when the provider chain provided to `cc` the VSC with ID `vscId`. Furthermore, the provider CCV module MUST make this slash request before registering any maturity notifications received from `cc` after the `SlashPacket`. * ***VSC Maturity and Slashing Order***: If a consumer chain sends to the provider chain a `SlashPacket` before a maturity notification of a VSC, then the provider chain MUST NOT receive the maturity notification before the `SlashPacket`. > **Note**: *VSC Maturity and Slashing Order* requires the VSC maturity notifications to be sent through their own IBC packets (i.e., `VSCMaturedPacket`s) instead of e.g., through acknowledgements of `VSCPacket`s. ### Reward Distribution [↑ Back to Outline](#outline) * ***Distribution Liveness***: If the CCV module on a consumer chain sends to the distribution module account on the provider chain an amount `T` of tokens as reward for providing security, then `T` (equivalent) tokens are eventually minted in the distribution module account on the provider chain. ## Correctness Reasoning [↑ Back to Outline](#outline) In this section we argue the correctness of the CCV protocol described in the [Technical Specification](./technical_specification), i.e., we informally prove the properties described in the [previous section](#desired-properties). * ***Channel Uniqueness***: The consumer chain side of the CCV channel is established when the consumer CCV module receives the *first* `ChanOpenAck` message that is successfully executed; all subsequent `ChanOpenAck` messages will fail (cf. *Safe Blockchain*). Let `ccvChannel` denote this channel. Then, `ccvChannel` is the only `OPEN` channel that can be connected to a port owned by the consumer CCV module. The provider chain side of the CCV channel is established when the provider CCV module receives the *first* `ChanOpenConfirm` message that is successfully executed; all subsequent `ChanOpenConfirm` messages will fail (cf. *Safe Blockchain*). The `ccvChannel` is the only channel for which `ChanOpenConfirm` can be successfully executed (cf. *Safe Blockchain*, i.e., IBC channel opening handshake guarantee). As a result, `ccvChannel` is unique. Moreover, ts existence is guaranteed by the *Correct Relayer* assumption. * ***Channel Validity***: Follows directly from the *Safe Blockchain* assumption. * ***Channel Order***: The provider chain accepts only ordered channels when receiving a `ChanOpenTry` message (cf. *Safe Blockchain*). Similarly, the consumer chain accepts only ordered channels when receiving `ChanOpenInit` messages (cf. *Safe Blockchain*). Thus, the property follows directly from the fact that the CCV channel is ordered. * ***Channel Liveness***: The property follows from the *Correct Relayer* assumption. * ***Validator Update To VSC Validity***: The provider CCV module provides only VSCs that contain validator updates obtained from the Staking module, i.e., by calling the `GetValidatorUpdates()` method (cf. *Safe Blockchain*). Furthermore, these validator updates were applied to the validator set of the provider chain (cf. *Validator Update Provision*). * ***Validator Update To VSC Order***: We prove the property through contradiction. Given two validator updates `U1` and `U2`, with `U1` occurring on the provider chain before `U2`, we assume `U2` is included in a provided VSC before `U1`. However, `U2` could not have been obtained by the provider CCV module before `U1` (cf. *Validator Update Provision*). Thus, the provider CCV module could not have provided a VSC that contains `U2` before a VSC that contains `U1` (cf. *Safe Blockchain*), which contradicts the initial assumption. * ***Validator Update To VSC Liveness***: The provider CCV module eventually provides to all consumer chains VSCs containing all validator updates obtained from the provider Staking module (cf. *Safe Blockchain*, *Life Blockchain*). Thus, it is sufficient to prove that every update of a validator in the validator set of the provider chain MUST eventually be obtained from the provider Staking module. We prove this through contradiction. Given a validator update `U` that is applied to the validator set of the provider chain at the end of a block `B` with height `h`, we assume `U` is never obtained by the provider CCV module. However, at height `h`, the provider CCV module tries to obtain a new batch of validator updates from the provider Staking module (cf. *Safe Blockchain*). Thus, this batch of validator updates MUST contain all validator updates applied to the validator set of the provider chain at the end of block `B`, including `U` (cf. *Validator Update Provision*), which contradicts the initial assumption. * ***Apply VSC Validity***: The property follows from the following two assertions. * The consumer chain only applies VSCs received in `VSCPacket`s through the CCV channel (cf. *Safe Blockchain*). * The provider chain only sends `VSCPacket`s containing provided VSCs (cf. *Safe Blockchain*). * ***Apply VSC Order***: We prove the property through contradiction. Given two VSCs `vsc1` and `vsc2` such that the provider chain provides `vsc1` before `vsc2`, we assume the consumer chain applies the validator updates included in `vsc2` before the validator updates included in `vsc1`. The following sequence of assertions leads to a contradiction. * The provider chain could not have sent a `VSCPacket` `P2` containing `vsc2` before a `VSCPacket` `P1` containing `vsc1` (cf. *Safe Blockchain*). * The consumer chain could not have received `P2` before `P1` (cf. *Channel Order*). * Given the *Safe Blockchain* assumption, we distinguish two cases. * First, the consumer chain receives `P1` during block `B1` and `P2` during block `B2` (with `B1` \< `B2`). Then, it applies the validator updates included in `vsc1` at the end of `B1` and the validator updates included in `vsc2` at the end of `B2` (cf. *Validator Update Inclusion*), which contradicts the initial assumption. * Second, the consumer chain receives both `P1` and `P2` during the same block. Then, it applies the validator updates included in both `vsc1` and `vsc2` at the end of the block. Thus, it could not have apply the validator updates included in `vsc2` before. * ***Apply VSC Liveness***: The provider chain eventually sends over the CCV channel a `VSCPacket` containing `vsc` (cf. *Safe Blockchain*, *Life Blockchain*). As a result, the consumer chain eventually receives this packet (cf. *Channel Liveness*). Then, the consumer chain aggregates all received VSCs at the end of the block and applies all the aggregated updates (cf. *Safe Blockchain*, *Life Blockchain*). As a result, the consumer chain applies all validator updates in `vsc` (cf. *Validator Update Inclusion*). * ***Register Maturity Validity***: The property follows from the following sequence of assertions. * The provider chain only registers VSC maturity notifications when receiving on the CCV channel a `VSCMaturedPacket`s notifying the maturity of those VSCs (cf. *Safe Blockchain*). * The provider chain receives on the CCV channel only packets sent by the consumer chain (cf. *Channel Validity*). * The consumer chain only sends `VSCMaturedPacket`s matching the `VSCPacket`s it receives on the CCV channel (cf. *Safe Blockchain*). * The consumer chain receives on the CCV channel only packets sent by the provider chain (cf. *Channel Validity*). * The provider chain only sends `VSCPacket`s containing provided VSCs (cf. *Safe Blockchain*). * ***Register Maturity Timeliness***: We prove the property through contradiction. Given a VSC `vsc` provided by the provider chain to the consumer chain, we assume that the provider chain registers a maturity notification of `vsc` before `UnbondingPeriod` has elapsed on the consumer chain since the consumer chain applied `vsc`. The following sequence of assertions leads to a contradiction. * The provider chain could not have register a maturity notification of `vsc` before receiving on the CCV channel a `VSCMaturedPacket` `P` with `P.id = vsc.id` (cf. *Safe Blockchain*). * The provider chain could not have received `P` on the CCV channel before the consumer chain sent it (cf. *Channel Validity*). * The consumer chain could not have sent `P` before at least `UnbondingPeriod` has elapsed since receiving a `VSCPacket` `P'` with `P'.id = P.id` on the CCV channel (cf. *Safe Blockchain*). Note that since time is measured in terms of the block time, the time of receiving `P'` is the same as the time of applying `vsc`. * The consumer chain could not have received `P'` on the CCV channel before the provider chain sent it (cf. *Channel Validity*). * The provider chain could not have sent `P'` before providing `vsc`. * Since the duration of sending packets through the CCV channel cannot be negative, the provider chain could not have registered a maturity notification of `vsc` before `UnbondingPeriod` has elapsed on the consumer chain since the consumer chain applied `vsc`. * ***Register Maturity Order***: We prove the property through contradiction. Given two VSCs `vsc1` and `vsc2` such that the provider chain provides `vsc1` before `vsc2`, we assume the provider chain registers the maturity notification of `vsc2` before the maturity notification of `vsc1`. The following sequence of assertions leads to a contradiction. * The provider chain could not have sent a `VSCPacket` `P2`, with `P2.updates = C2`, before a `VSCPacket` `P1`, with `P1.updates = C1` (cf. *Safe Blockchain*). * The consumer chain could not have received `P2` before `P1` (cf. *Channel Order*). * The consumer chain could not have sent a `VSCMaturedPacket` `P2'`, with `P2'.id = P2.id`, before a `VSCMaturedPacket` `P1'`, with `P1'.id = P1.id` (cf. *Safe Blockchain*). * The provider chain could not have received `P2'` before `P1'` (cf. *Channel Order*). * The provider chain could not have registered the maturity notification of `vsc2` before the maturity notification of `vsc1` (cf. *Safe Blockchain*). * ***Register Maturity Liveness***: The property follows from the following sequence of assertions. * The provider chain eventually sends on the CCV channel a `VSCPacket` `P`, with `P.updates = C` (cf. *Safe Blockchain*, *Life Blockchain*). * The consumer chain eventually receives `P` on the CCV channel (cf. *Channel Liveness*). * The consumer chain eventually sends on the CCV channel a `VSCMaturedPacket` `P'`, with `P'.id = P.id` (cf. *Safe Blockchain*, *Life Blockchain*). * The provider chain eventually receives `P'` on the CCV channel (cf. *Channel Liveness*). * The provider chain eventually registers the maturity notification of `vsc` (cf. *Safe Blockchain*, *Life Blockchain*). * ***Consumer Slashing Warranty***: Follows directly from *Safe Blockchain*. * ***Provider Slashing Warranty***: Follows directly from *Safe Blockchain*. * ***VSC Maturity and Slashing Order***: Follows directly from *Channel Order*. * ***Distribution Liveness***: The CCV module on the consumer chain sends to the provider chain an amount `T` of tokens through an IBC token transfer packet (as defined in [ICS 20](../ics-020-fungible-token-transfer/README)). Thus, if the packet is relayed within the timeout period, then `T` (equivalent) tokens are minted on the provider chain. Otherwise, the `T` tokens are refunded to the consumer CCV module account. In this case, the `T` tokens will be part of the next token transfer packet. Eventually, a correct relayer will relay a token transfer packet containing the `T` tokens (cf. *Correct Relayer*, *Life Blockchain*). As a result, `T` (equivalent) tokens are eventually minted on the provider chain. * ***Validator Set Replication***: The property follows from the *Safe Blockchain* assumption and both the *Apply VSC Validity* and *Validator Update To VSC Validity* properties. * ***Bond-Based Consumer Voting Power***: The existence of `hp` is given by construction, i.e., the block on the proposer chain that handles a governance proposal to spawn a new consumer chain `cc` *happens before* all the blocks of `cc`. The existence of `hc'` and `hp'` is given by *Life Blockchain* and *Channel Liveness*. To prove the *Bond-Based Consumer Voting Power* property, we use the following property that follows directly from the design of the protocol (cf. *Safe Blockchain*, *Life Blockchain*). * *Property1*: Let `val` be a validator; let `Ua` and `Ub` be two updates of `val` that are applied subsequently by a consumer chain `cc`, at block heights `ha` and `hb`, respectively (i.e., no other updates of `val` are applied in between). Then, `Power(cc,ha,val) = Power(cc,h,val)`, for all block heights `h`, such that `ha <= h < hb` (i.e., the voting power granted to `val` on `cc` in the period between `ts(ha)` and `ts(hb)` is constant). We prove the *Bond-Based Consumer Voting Power* property through contradiction. We assume there exist a height `h` on the provider chain between `hp` and `hp'` such that `Power(cc,hc,val) > VP( pBonded(h,val) + sumUnbonding(hp, h, val) + sumSlash(hp, h, val) )`. The following sequence of assertions leads to a contradiction. * Let `U1` be the latest update of `val` that is applied by `cc` before or not later than block height `hc` (i.e., `U1` is the update that sets `Power(cc,hc,val)` for `val`). Let `hp1` be the height at which `U1` occurs on the provider chain; let `hc1` be the height at which `U1` is applied on `cc`. Then, `hp1 << hc1 <= hc`, `hp1 <= hp`, and `Power(cc,hc,val) = Power(cc,hc1,val) = VP(pBonded(hp1,val))`. This means that some of the tokens bonded by `val` at height `hp1` were *completely* unbonded before or not later than height `hp'` (cf. `Power(cc,hc,val) > VP( pBonded(h,val) + sumUnbonding(hp, h, val) + sumSlash(hp, h, val) )`). * Let `uo` be the first such unbonding operation that is initiated on the provider chain at height `hp2`, such that `hp1 < hp2 <= hp'`. Note that at height `hp2`, the tokens unbonded by `uo` are part of `pUnbonding(hp2,val)`. Let `U2` be the validator update caused by initiating `uo`. Let `hc2` be the height at which `U2` is applied on `cc`; clearly, `Power(cc,hc2,val) < Power(cc,hc,val)`. Note that the existence of `hc2` is ensured by *Validator Update To VSC Liveness* and *Apply VSC Liveness*. Then, `hc2 > hc1` (cf. `hp2 > hp1`, *Validator Update To VSC Order*, *Apply VSC Order*). * `Power(cc,hc,val) = Power(cc,hc1,val) = Power(cc,h,val)`, for all heights `h`, such that `hc1 <= h < hc2` (cf. *Property1*). Thus, `hc2 > hc` (cf. `Power(cc,hc2,val) < Power(cc,hc,val)`). * `uo` cannot complete before `ts(hc2) + UnbondingPeriod`, which means it cannot complete before `hc'` and thus it cannot complete before `hp'` (cf. `hc' << hp'`). * ***Slashable Consumer Misbehavior***: The second part of the *Slashable Consumer Misbehavior* property (i.e., `val` is not slashed more than once for the same misbehavior) follows directly from *Evidence Provision*, *Channel Validity*, *Consumer Slashing Warranty*, *Provider Slashing Warranty*. To prove the first part of the *Slashable Consumer Misbehavior* property (i.e., exactly the amount of tokens `sf*Token(Power(cc,hi,val))` are slashed on the provider chain), we consider the following sequence of statements. * The CCV module of `cc` receives at height `he` the evidence that `val` misbehaved on `cc` at height `hi` (cf. *Evidence Provision*, *Safe Blockchain*, *Life Blockchain*). * Let `hv` be the height when the CCV module of `cc` receives the first VSC from the provider CCV module. Then, the CCV module of `cc` sends at height `h = max(he, hv)` to the provider chain a `SlashPacket` `P`, such that `P.val = val` and `P.id = HtoVSC[hi]` (cf. *Consumer Slashing Warranty*). * The provider CCV module eventually receives `P` (cf. *Channel Liveness*). * The provider CCV module requests the provider Slashing module to slash `val` for misbehaving at height `hp = VSCtoH[P.id]` before handling any further maturity notifications received from the CCV module of `cc` (cf. *Provider Slashing Warranty*). * The provider Slashing module slashes the amount of tokens `val` had bonded at height `hp` except the amount that has already completely unbonded (cf. *Slashing Warranty*). Thus, it remains to be proven that `Token(Power(cc,hi,val)) = pBonded(hp,val)`, with `hp = VSCtoH[HtoVSC[hi]]`. We distinguish two cases: * `HtoVSC[hi] != 0`, which means that by definition `HtoVSC[hi]` is the ID of the last VSC that update `Power(cc,hi,val)`. Also by definition, this VSC contains the last updates to the voting power at height `VSCtoH[HtoVSC[hi]]` on the provider. Thus, `Token(Power(cc,hi,val)) = pBonded(hp,val)`. * `HtoVSC[hi] == 0`, which means that by definition `Power(cc,hi,val)` was setup at genesis during Channel Initialization. Also by definition, this is the same voting power of the provider chain block when the first VSC was provided to that consumer chain, i.e., `VSCtoH[HtoVSC[hi]]`. Thus, `Token(Power(cc,hi,val)) = pBonded(hp,val)`. * ***Consumer Rewards Distribution***: The first part of the *Consumer Rewards Distribution* property (i.e., the tokens are eventually minted on the provider chain and then distributed among the validators) follows directly from *Distribution Liveness* and *Distribution Warranty*. The second part of the *Consumer Rewards Distribution* property (i.e., the total supply of tokens is preserved) follows directly from the *Supply* property of the Fungible Token Transfer protocol (see [ICS 20](../ics-020-fungible-token-transfer/README)). # CCV: Technical Specification Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-028-cross-chain-validation/technical_specification ## Outline * [Placing CCV within an ABCI Application](#placing-ccv-within-an-abci-application) * [Implemented Interfaces](#implemented-interfaces) * [Interfacing Other Modules](#interfacing-other-modules) * [Data Structures and Methods](#data-structures-and-methods) ## Placing CCV within an ABCI Application [↑ Back to Outline](#outline) Before describing the data structures and sub-protocols of the CCV protocol, we provide a short overview of the interfaces the CCV module implements and the interactions with the other ABCI application modules. ### Implemented Interfaces * CCV is an **ABCI application module**, which means it MUST implement the logic to handle some of the messages received from the consensus engine via ABCI, e.g., `InitChain`, `BeginBlock`, `EndBlock` (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/tree/v0.7.1/spec/abci)). In this specification we define the following methods that handle messages that are of particular interest to the CCV protocol: * `InitGenesis()` -- Called when the chain is first started, on receiving an `InitChain` message from the consensus engine. This is also where the application can inform the underlying consensus engine of the initial validator set. * `BeginBlock()` -- Contains logic that is automatically triggered at the beginning of each block. * `EndBlock()` -- Contains logic that is automatically triggered at the end of each block. This is also where the application can inform the underlying consensus engine of changes in the validator set. * CCV is an **IBC module**, which means it MUST implement the module callbacks interface defined in [ICS 26](../../core/ics-026-routing-module/README#module-callback-interface). The interface consists of a set of callbacks for * channel opening handshake, which we describe in the [Initialization](./methods#initialization) section; * channel closing handshake, which we describe in the [Consumer Chain Removal](./methods#consumer-chain-removal) section; * and packet relay, which we describe in the [Packet Relay](./methods#packet-relay) section. ### Interfacing Other Modules * As an ABCI application module, the CCV module interacts with the underlying consensus engine through ABCI: * On the provider chain, * it initializes the application (e.g., binds to the expected IBC port) in the `InitGenesis()` method. * On the consumer chain, * it initializes the application (e.g., binds to the expected IBC port, creates a client of the provider chain) in the `InitGenesis()` method; * it provides the validator updates in the `EndBlock()` method. * As an IBC module, the CCV module interacts with Core IBC for functionalities regarding * port allocation ([ICS 5](../../core/ics-005-port-allocation)) via `portKeeper`; * channels and packet semantics ([ICS 4](../../core/ics-004-channel-and-packet-semantics)) via `channelKeeper`; * connection semantics ([ICS 3](../../core/ics-003-connection-semantics)) via `connectionKeeper`; * client semantics ([ICS 2](../../core/ics-002-client-semantics)) via `clientKeeper`. * The consumer CCV module interacts with the IBC Token Transfer module ([ICS 20](../ics-020-fungible-token-transfer/README)) via `transferKeeper`. * For the [Initialization sub-protocol](./methods#initialization), the provider CCV module interacts with a Governance module by handling governance proposals to add new consumer chains. If such proposals pass, then all validators on the provider chain MUST validate the consumer chain at spawn time; otherwise they get slashed. For an example of how governance proposals work, take a look at the [Governance module documentation](/sdk/latest/modules/gov/README) of Cosmos SDK. * The consumer pre-CCV module (i.e., the CCV module with `preCCV == true`) interacts with a Staking module on the consumer chain. Note that once `preCCV` is set to `false`, the Staking module MUST no longer provide validator updates to the underlying consensus engine. For an example of how staking works, take a look at the [Staking module documentation](/sdk/latest/modules/staking/README) of Cosmos SDK. The interaction is defined by the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface StakingKeeper { // replace the validator set with valset ReplaceValset(valset: [ValidatorUpdate]) } ``` * The provider CCV module interacts with a Staking module on the provider chain. For an example of how staking works, take a look at the [Staking module documentation](/sdk/latest/modules/staking/README) of Cosmos SDK. The interaction is defined by the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface StakingKeeper { // get UnbondingPeriod from the provider Staking module UnbondingTime(): Duration // get validator updates from the provider Staking module GetValidatorUpdates(): [ValidatorUpdate] // request the Staking module to put on hold // the completion of an unbonding operation PutUnbondingOnHold(id: uint64) // notify the Staking module of an unboding operation that // has matured from the perspective of the consumer chains UnbondingCanComplete(id: uint64) } ``` * The provider CCV module interacts with a Slashing module on the provider chain. For an example of how slashing works, take a look at the [Slashing module documentation](/sdk/latest/modules/slashing/README) of Cosmos SDK. The interaction is defined by the following interface: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface SlashingKeeper { // query the Slashing module for the slashing factor, // which may be different for downtime infractions GetSlashFactor(downtime: Bool): int64 // request the Slashing module to slash a validator Slash(valAddress: string, infractionHeight: int64, power: int64, slashFactor: int64) // query the Slashing module for the jailing time, // which may be different for downtime infractions GetJailTime(downtime: Bool): int64 // request the Slashing module to jail a validator until time JailUntil(valAddress: string, time: uint64) } ``` * The following hook enables the provider CCV module to register operations to be execute when certain events occur within the provider Staking module: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // invoked by the Staking module after // initiating an unbonding operation function AfterUnbondingInitiated(opId: uint64); ``` * The consumer CCV module defines the following hooks that enable other modules to register operations to execute when certain events have occurred within CCV: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // invoked after a new validator is added to the validator set function AfterCCValidatorBonded(valAddress: string); // invoked after a validator is removed from the validator set function AfterCCValidatorBeginUnbonding(valAddress: string); ``` ## Data Structures and Methods [↑ Back to Outline](#outline) The remainder of this technical specification is split into [Data Structures](./data_structures) and [Methods](./methods). # General Fee Payment Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-029-fee-payment/README ## Synopsis This standard document specifies packet data structure, state machine handling logic, and encoding details for handling fee payments on top of any ICS application protocol. It requires some changes to the acknowledgement, but can be adopted by any application, without forcing other applications to use this implementation. ### Motivation There has been much discussion on a general incentivization mechanism for relayers. A simple proposal was created to [extend ICS-20 to incentivize relaying](https://github.com/cosmos/ibc/pull/577) on the destination chain. However, it was very specific to ICS-20 and would not work for other protocols. This was then extended to a more [general fee payment design](https://github.com/cosmos/ibc/issues/578) that could be adopted by any ICS application protocol. In general, the Interchain dream will never scale unless there is a clear way to incentivize relayers. We seek to define a clear interface that can be easily adopted by any application, but not preclude chains that don't use tokens. ### Desired Properties * Incentivize timely delivery of the packet (`recvPacket` called) * Incentivize relaying acks for these packets (`acknowledgePacket` called) * Incentivize relaying timeouts for these packets when the timeout has expired before packet is delivered (for example as receive fee was too low) (`timeoutPacket` called) * Produces no extra IBC packets * One direction works, even when destination chain does not support concept of fungible tokens * Opt-in for each chain implementing this. e.g. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. * Standardized interface for each chain implementing this extension * Support custom fee-handling logic within the same framework * Relayer addresses should not be forgeable * Enable permissionless or permissioned relaying ### Definitions `forward relayer`: The relayer that submits the `recvPacket` message for a given packet `reverse relayer`: The relayer that submits the `acknowledgePacket` message for a given packet `timeout relayer`: The relayer that submits the `timeoutPacket` or `timeoutOnClose` message for a given packet `receive fee`: The fee paid for submitting the `recvPacket` message for a given packet `ack fee`: The fee paid for submitting the `acknowledgePacket` message for a given packet `timeout fee`: The fee paid for submitting the `timeoutPacket` or `timeoutOnClose` message for a given packet `source address`: The payee address selected by a relayer on the chain that sent the packet `destination address`: The address of a relayer on the chain that receives the packet ## Technical Specification ### General Design In order to avoid extra fee packets on the order of the number of application packets, as well as provide an opt-in approach, we store all fee payment info only on the source chain. The source chain is the one location where the sender can provide tokens to incentivize the packet. The fee distribution may be implementation specific and thus does not need to be in the IBC spec (just high-level requirements are needed in this doc). We require that the [relayer address is exposed to application modules](https://github.com/cosmos/ibc/pull/579) for all packet-related messages, so the modules are able to incentivize the packet relayer. `acknowledgePacket`, `timeoutPacket`, and `timeoutOnClose` messages will therefore have the relayer address and be capable of sending escrowed tokens to such address. However, we need a way to reliably get the address of the relayer that submitted `recvPacket` on the destination chain to the source chain. In fact, we need a *source address* for this relayer to pay out to, not the *destination address* that signed the packet. The fee payment mechanism will be implemented as IBC Middleware (see ICS-30) in order to provide maximum flexibility for application developers and blockchains. Given this, the flow would be: 1. Relayer registers their destination address to source address mapping on the destination chain's fee middleware. 2. User/module submits a send packet on the `source` chain, along with a message to the fee middleware module with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module. 3. RelayerA submits `RecvPacket` on the `destination` chain. 4. Destination fee middleware will retrieve the source address for the given relayer's destination address (this mapping is already registered) and include it in the acknowledgement. 5. RelayerB submits `AcknowledgePacket` which provides the *reverse relayer* address on the source chain in the message sender, along with the source address of the *forward relayer* embedded in the acknowledgement. 6. Source fee middleware can distribute the tokens escrowed in (1) to both the *forward* and the *reverse* relayers and refund remainder tokens to original fee payer(s). Alternate flow: 1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them 2. Relayer submits `OnTimeout` which provides its address on the source chain 3. Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return remainder tokens to the original fee payer(s). ### Fee details For an example implementation in the Cosmos SDK, we consider 3 potential fee payments, which may be defined. Each one may be paid out in a different token. Imagine a connection between IrisNet and the Cosmos Hub. To incentivize a packet from IrisNet to the Cosmos Hub, they may define: * ReceiveFee: 0.003 channel-7/ATOM vouchers (ATOMs already on IrisNet via ICS20) * AckFee: 0.001 IRIS * TimeoutFee: 0.002 IRIS Ideally the fees can easily be redeemed in native tokens on both sides, but relayers may select others. In this example, the relayer collects a fair bit of IRIS, covering its costs there and more. It also collects channel-7/ATOM vouchers from many packets. After relaying a few thousand packets, the account on the Cosmos Hub is running low, so the relayer will send those channel-7/ATOM vouchers back over channel-7 to it's account on the Hub to replenish the supply there. The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS from the fee payers' account. In the case that a forward relayer submits the `recvPacket` and a reverse relayer submits the `ackPacket`, the forward relayer is rewarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS while 0.002 IRIS is refunded to the original fee payer. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer. The logic involved in collecting fees from users and then paying it out to the relevant relayers is encapsulated by a separate fee module and may vary between implementations. However, all fee modules must implement a uniform interface such that the ICS-4 handlers can correctly pay out fees to the right relayers, and so that relayers themselves can easily determine the fees they can expect for relaying a packet. ### Data Structures The incentivized acknowledgment written on the destination chain includes: * raw bytes of the acknowledgement from the underlying application, * the source address of the forward relayer, * and a boolean indicative of receive operation success on the underlying application. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Acknowledgement { appAcknowledgement: []byte forwardRelayerAddress: string underlyingAppSuccess: boolean } ``` ### Store Paths #### Relayer Address for Async Ack Path The forward relayer addresses are stored under a store path prefix unique to a combination of port identifier, channel identifier and sequence. This may be stored in the private store. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function relayerAddressForAsyncAckPath(packet: Packet): Path { return "forwardRelayer/{packet.destPort}/{packet.destChannel}/{packet.sequence}" } ``` ### Fee Middleware Contract While the details may vary between fee modules, all fee modules **must** ensure they does the following: * It must allow relayers to register their counterparty payee address (i.e. source address). * It must have in escrow the maximum fees that all outstanding packets may pay out (or it must have ability to mint required amount of tokens) * It must pay the receive fee for a packet to the forward relayer specified in `PayFee` callback (if unspecified, it must refund forward fee to original fee payer(s)) * It must pay the ack fee for a packet to the reverse relayer specified in `PayFee` callback * It must pay the timeout fee for a packet to the timeout relayer specified in `PayTimeoutFee` callback * It must refund any remainder fees in escrow to the original fee payer(s) if applicable ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // RegisterCounterpartyPayee is called by the relayer on each channelEnd and // allows them to specify their counterparty payee address before relaying. // This ensures they will be properly compensated for forward relaying since // destination chain must send back relayer's source address (counterparty // payee address) in acknowledgement. // This function may be called more than once by relayer, in which case, latest // counterparty payee address is always used. function RegisterCounterpartyPayee(relayer: string, counterPartyAddress: string) { // set mapping between relayer address and counterparty payee address } // EscrowPacketFee is an open callback that may be called by any module/user // that wishes to escrow funds in order to incentivize the relaying of the // given packet. // NOTE: These fees are escrowed in addition to any previously escrowed amount // for the packet. In the case where the previous amount is zero, the provided // fees are the initial escrow amount. // They may set a separate receiveFee, ackFee, and timeoutFee to be paid // for each step in the packet flow. The caller must send max(receiveFee+ackFee, timeoutFee) // to the fee module to be locked in escrow to provide payout for any potential // packet flow. // The caller may optionally specify an array of relayer addresses. This MAY be // used by the fee module to modify fee payment logic based on ultimate relayer // address. For example, fee module may choose to only pay out relayer if the // relayer address was specified in the `EscrowPacketFee`. function EscrowPacketFee(packet: Packet, receiveFee: Fee, ackFee: Fee, timeoutFee: Fee, relayers: []string) { // escrow max(receiveFee+ackFee, timeoutFee) for this packet // do custom logic with provided relayer addresses if necessary } // PayFee is a callback implemented by fee module called by the ICS-4 AcknowledgePacket handler. function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string) { // pay the forward fee to the forward relayer address // pay the reverse fee to the reverse relayer address // refund extra tokens to original fee payer(s) // NOTE: if forward relayer address is empty, then refund the forward fee to original fee payer(s). } // PayTimeoutFee is a callback implemented by fee module called by the ICS-4 TimeoutPacket handler. function PayTimeoutFee(packet: Packet, timeout_relayer: string) { // pay the timeout fee to the timeout relayer address // refund extra tokens to original fee payer(s) } ``` The fee module should also expose the following queries so that relayers may query their expected fee: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Gets the fee expected for submitting RecvPacket msg for the given packet // Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s). function GetReceiveFee(portID, channelID, sequence, relayer) Fee // Gets the fee expected for submitting AcknowledgePacket msg for the given packet // Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s). function GetAckFee(portID, channelID, sequence, relayer) Fee // Gets the fee expected for submitting TimeoutPacket msg for the given packet // Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s). function GetTimeoutFee(portID, channelID, sequence, relayer) Fee ``` Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. A default representation will have the following structure: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface Fee { denom: string, amount: uint256, } ``` ### IBC Module Wrapper The fee middleware will implement its own ICS-26 callbacks that wrap the application-specific module callbacks as well as the ICS-4 handler functions called by the underlying application. This fee middleware will ensure that the counterparty module supports incentivization and will implement all fee-specific logic. It will then pass on the request to the embedded application module for further callback processing. In this way, custom fee-handling logic can be hooked up to the IBC packet flow logic without placing the code in the ICS-4 handlers or the application code. This is valuable since the ICS-4 handlers should only be concerned with correctness of core IBC (transport, authentication, and ordering), and the application handlers should not be handling fee logic that is universal amongst all other incentivized applications. In fact, a given application module should be able to be hooked up to any fee module with no further changes to the application itself. #### Fee Protocol Negotiation The fee middleware will negotiate its fee protocol version with the counterparty module by including its own version next to the application version. The channel version will be a string of a JSON struct containing the fee middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. Channel Version: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"fee_version":"","app_version":""} ``` Ex: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} {"fee_version":"ics29-1","app_version":"ics20-1"} ``` The fee middleware's handshake callbacks ensure that both modules agree on compatible fee protocol version(s), and then pass the application-specific version string to the embedded application's handshake callbacks. #### Handshake Callbacks ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenInit( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string): (version: string, err: Error) { if version != "" { // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. metadata, err = UnmarshalJSON(version) if err != nil { // call the underlying applications OnChanOpenInit callback return app.onChanOpenInit( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, version, ) } // check that feeVersion is supported if !isSupported(metadata.feeVersion) { return "", error } } else { // enable fees by default if relayer does not specify otherwise metadata = { feeVersion: "ics29-1", appVersion: "", } } // call the underlying application's OnChanOpenInit callback. // if the version string is empty, OnChanOpenInit is expected to return // a default version string representing the version(s) it supports appVersion, err = app.onChanOpenInit( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, metadata.appVersion, ) if err != nil { return "", err } // a new version string is constructed with the app version returned // by the underlying application, in case it is different than the // one passed by the caller version = constructVersion(metadata.feeVersion, appVersion) return version, nil } function onChanOpenTry( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string): (version: string, err: Error) { // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. cpMetadata, err = UnmarshalJSON(counterpartyVersion) if err != nil { // call the underlying application's OnChanOpenTry callback return app.onChanOpenTry( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, counterpartyVersion, ) } // select mutually compatible fee version if !isCompatible(cpMetadata.feeVersion) { return "", error } feeVersion = selectFeeVersion(cpMetadata.feeVersion) // call the underlying application's OnChanOpenTry callback appVersion, err = app.onChanOpenTry( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, cpMetadata.appVersion, ) if err != nil { return "", err } // a new version string is constructed with the final fee version // that is selected and the app version returned by the underlying // application (which may be different than the one passed by the caller) version = constructVersion(feeVersion, appVersion) return version, nil } function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string) { cpMetadata, err = UnmarshalJSON(counterpartyVersion) if err != nil { // call the underlying application's OnChanOpenAck callback return app.onChanOpenAck( portIdentifier, channelIdentifier, counterpartyChannelIdentifier, counterpartyVersion, ) } if !isSupported(cpMetadata.feeVersion) { return error } // call the underlying application's OnChanOpenAck callback return app.onChanOpenAck( portIdentifier, channelIdentifier, counterpartyChannelIdentifier, cpMetadata.appVersion, ) } function onChanOpenConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { // fee middleware performs no-op on ChanOpenConfirm, // just call underlying callback return app.onChanOpenConfirm(portIdentifier, channelIdentifier) } ``` #### Packet Callbacks ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onRecvPacket(packet: Packet, relayer: string): bytes { app_acknowledgement = app.onRecvPacket(packet, relayer) // in case of asynchronous acknowledgement, we must store the relayer // address. It will be retrieved later and used to get the source // address that will be written in the acknowledgement. if app_acknowledgement == nil { privateStore.set(relayerAddressForAsyncAckPath(packet), relayer) } // get source address by retrieving counterparty payee address of // this relayer stored in fee middleware. // NOTE: source address may be empty or invalid, counterparty // must refund fee in these cases sourceAddress = getCounterpartyPayeeAddress(relayer) // wrap the acknowledgement with forward relayer and return marshalled bytes // constructIncentivizedAck takes: // - the app-specific acknowledgement, // - the receive-packet relayer (forward relayer) // - and a boolean indicative of receive operation success, // and constructs the incentivized acknowledgement struct with // the forward relayer and app-specific acknowledgement embedded. ack = constructIncentivizedAck(app_acknowledgment, sourceAddress, app_acknowledgment.success) return marshal(ack) } function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { // the acknowledgement is a marshalled struct containing: // - the forward relayer address as a string (called forward_relayer) // - and the raw acknowledgement bytes returned by the counterparty application module (called app_ack). // get the forward relayer from the (incentivized) acknowledgement // and pay fees to forward and reverse relayers. // reverse_relayer is submitter of acknowledgement message // provided in function arguments // NOTE: Fee may be zero ack = unmarshal(acknowledgement) forward_relayer = getForwardRelayer(ack) PayFee(packet, forward_relayer, relayer) // unwrap the raw acknowledgement bytes sent by counterparty application // and pass it to the application callback. app_ack = getAppAcknowledgement(acknowledgement) app.OnAcknowledgePacket(packet, app_ack, relayer) } function onTimeoutPacket(packet: Packet, relayer: string) { // get the timeout relayer from function arguments // and pay timeout fee. // NOTE: Fee may be zero PayTimeoutFee(packet, relayer) app.OnTimeoutPacket(packet, relayer) } function onTimeoutPacketClose(packet: Packet, relayer: string) { // get the timeout relayer from function arguments // and pay timeout fee. // NOTE: Fee may be zero PayTimeoutFee(packet, relayer) app.onTimeoutPacketClose(packet, relayer) } function constructIncentivizedAck( app_ack: bytes, forward_relayer: string, success: boolean): Acknowledgement { return Acknowledgement{ appAcknowledgement: app_ack, forwardRelayerAddress: relayer, underlyingAppSuccess: success, } } function getForwardRelayer(ack: Acknowledgement): string { ack.forwardRelayerAddress } function getAppAcknowledgement(ack: Acknowledgement): bytes { ack.appAcknowledgement } ``` #### Embedded applications calling into ICS-4 Note that if the embedded application uses asynchronous acks then, the `WriteAcknowledgement` call in the application must call the fee middleware's `WriteAcknowledgement` rather than calling the ICS-4 handler's `WriteAcknowledgement` function directly. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Fee Middleware writeAcknowledgement function function writeAcknowledgement( packet: Packet, acknowledgement: bytes) { // retrieve the relayer that was stored in `onRecvPacket` relayer = privateStore.get(relayerAddressForAsyncAckPath(packet)) // get source address by retrieving counterparty payee address // of this relayer stored in fee middleware. sourceAddress = getCounterpartyPayeeAddress(relayer) ack = constructIncentivizedAck(acknowledgment, sourceAddress, acknowledgment.success) ack_bytes = marshal(ack) // ics4Wrapper may be core IBC or higher-level middleware return ics4Wrapper.writeAcknowledgement(packet, ack_bytes) } // Fee Middleware sendPacket function just forwards data to ics-4 handler function sendPacket( capability: CapabilityKey, sourcePort: Identifier, sourceChannel: Identifier, timeoutHeight: Height, timeoutTimestamp: uint64, data: bytes): uint64 { // ics4Wrapper may be core IBC or higher-level middleware return ics4Wrapper.sendPacket( capability, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) } ``` ### User Interaction with Fee Middleware **User sending Packets** A user may specify a fee to incentivize the relaying during packet submission, by submitting a fee payment message atomically with the application-specific "send packet" message (e.g. ICS-20 `MsgTransfer`). The fee middleware will escrow the fee for the packet that is created atomically with the escrow. The fee payment message itself is not specified in this document as it may vary greatly across implementations. In some middleware, there may be no fee payment message at all if the fees are being paid out from an altruistic pool. Since the fee middleware does not need to modify the outgoing packet, the fee payment message may be placed before or after the send packet message. However in order to maintain consistency with other middleware messages, it is recommended that fee middleware require their messages to be placed before the send packet message and escrow fees for the **next sequence** on the given channel. This way when the messages are atomically committed, the next sequence on the channel is the send packet message sent by the user, and the user escrows their fee for the created packet. In case a user wants to pay fees on a packet after it has already been created, the fee middleware SHOULD provide a message that allows users to pay fees on a packet with the specified sequence, channel and port identifiers. This allows the user to uniquely identify a packet that has already been created, so that the fee middleware can escrow fees for that packet after the fact. **Relayers sending RecvPacket** Before a relayer starts relaying on a channel, they should register their counterparty message using the standardized message: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface RegisterCounterpartyPayeeMsg { portID: string channelID: string relayer: string // destination address of the forward relayer counterpartyPayee: string // source address of the forward relayer } ``` It is the responsibility of the receiving chain to authenticate that the message was received from owner of `relayer`. The receiving chain must store the mapping from: `relayer -> counterpartyPayee` for the given channel. Then, `onRecvPacket` of the destination fee middleware can query for the counterparty payee address of the `recvPacket` message sender in order to get the source address of the forward relayer. This source address is what will get embedded in the acknowledgement. If the relayer does not register their counterparty payee address (or registers an invalid address), then the acknowledgment will still be received and processed but the forward fee will be refunded to the original fee payer(s). #### Backwards Compatibility Maintaining backwards compatibility with an unincentivized chain directly in the fee module, would require the top-level fee module to negotiate versions that do not contain a fee version and communicate with both incentivized and unincentivized modules. This pattern causes unnecessary complexity as the layers of nested applications increase. Instead, the fee module will only connect to a counterparty fee module. This simplifies the fee module logic, and doesn't require it to mimic the underlying nested application(s). In order for an incentivized chain to maintain backwards compatibility with an unincentivized chain for a given application (e.g. ICS-20), the incentivized chain should host both a top-level ICS-20 module and a top-level fee module that nests an ICS-20 application each of which should bind to unique ports. #### Reasoning This proposal satisfies the desired properties. All parts of the packet flow (receive/acknowledge/timeout) can be properly incentivized and rewarded. The protocol does not specify the relayer beforehand, thus the incentivization can be permissionless or permissioned. The escrowing and distribution of funds is completely handled on source chain, thus there is no need for additional IBC packets or the use of ICS-20 in the fee protocol. The fee protocol only assumes existence of fungible tokens on the source chain. By creating application stacks for the same base application (one with fee middleware, one without), we can get backwards compatibility. ##### Correctness The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. The forward relayer is responsible for registering their source address before sending `recvPacket` messages, so that the destination fee middleware can embed this address in the acknowledgement. The fee middleware on source will then use the address in acknowledgement to pay the forward relayer on the source chain. The source chain will use a "best efforts" approach with regard to the forward relayer address. Since it is not verified directly by the counterparty and is instead just treated as a string to be passed back in the acknowledgement, the registered forward relayer source address may not be a valid source chain address. In this case, the invalid address is discarded, the receive fee is refunded, and the acknowledgement processing continues. It is incumbent on relayers to register their source addresses to the counterparty chain correctly. In the event that the counterparty chain itself incorrectly sends the forward relayer address, this will cause relayers to not collect fees on source chain for relaying packets. The incentivize-driven relayers will stop relaying for the chain until the acknowledgement logic is fixed, however the channel remains functional. We cannot return an error on an invalid source address as this would permanently prevent the source chain from processing the acknowledgment of a packet that was otherwise correctly received, processed and acknowledged on the counterparty chain. The IBC protocol requires that incorrect or malicious relayers may at best affect the liveness of a user's packets. Preventing successful acknowledgement in this case would leave the packet flow at a permanently incomplete state, which may be very consequential for certain IBC applications like ICS-20. Thus, the forward relayer reward is contingent on it providing the correct `payOnSender` address when it sends the `receive_packet` message. The packet flow will continue processing successfully even if the fee payment is unsuccessful. With the forward relayer correctly embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; the fee middleware will accurately escrow and distribute fee payments to the relevant relayers. #### Optional addenda ## Forwards Compatibility Not applicable. ## Example Implementations * Implementation of ICS 29 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go). ## History June 8 2021 - Switched to middleware solution from implementing callbacks in ICS-4 directly. June 1 2021 - Draft written July 6, 2022 - Update with latest changes from implementation ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # IBC Middleware Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-030-middleware/README ## Synopsis This standard documents specifies the interfaces and state machine logic that a module must implement in order to act as middleware between core IBC and an underlying application(s). IBC Middleware will enable arbitrary extensions to an application's functionality without requiring changes to the application or core IBC. ### Motivation IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC. The most prescient example of this, is the generalized fee payment protocol. Most applications will want to opt in to a protocol that incentivizes relayers to relay packets on their channel. However, some may not wish to enable this feature and yet others will want to implement their own custom fee handler. Without a middleware approach, developers must choose whether to place this extension in application logic inside each relevant application; or place the logic in core IBC. Placing it in each application is redundant and prone to error. Placing the logic in core IBC requires an opt-in from all applications and violates the abstraction barrier between core IBC (TAO) and the application. Either case is not scalable as the number of extensions increase, since this must either increase code bloat in applications or core IBC handlers. Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. ### Definitions `Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. `Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. `Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. `Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. ### Desired Properties * Middleware enables arbitrary extensions of application logic * Middleware can be arbitrarily nested to create a chain of app extensions * Core IBC does not need to change * Base Application logic does not need to change ## Technical Specification ### General Design In order to function as IBC Middleware, a module must implement the IBC application callbacks and pass along the pre-processed data to the nested application. It must also implement `WriteAcknowledgement` and `SendPacket`, which will be called by the end application, so that it may post-process the information before passing data along to core ibc. When nesting an application, the module must make sure that it is in the middle of communication between core IBC and the application in both directions. Developers should do this by registering the top-level module directly with the IBC router (not any nested applications). The nested applications in turn, must be given access only to the middleware's `WriteAcknowledgement` and `SendPacket` rather than to the core IBC handlers directly. Additionally, the middleware must take care to ensure that the application logic can execute its own version negotiation without interference from the nesting middleware. In order to do this, the middleware will format the version in a JSON-encoded string containing the middleware version and the application version (and potentially also other custom parameter fields). The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version string is as follows: ```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} { "": "", "app_version": "", // ... other custom parameter fields } ``` The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version` for ICS-29 fee middleware). In the application callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It must do its own version negotiation on `` and then hand over `` to the nested application's callback. This is only relevant if the middleware expects a compatible counterparty middleware at the same level on the counterparty stack. Middleware that only executes on a single side of the channel MUST NOT modify the channel version. Each application stack must reserve its own unique port with core IBC. Thus two stacks with the same base application must bind to separate ports. #### Interfaces ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // Middleware implements the ICS26 Module interface interface Middleware extends ICS26Module { // middleware has access to an underlying application which may be wrapped // by more middleware. app: ICS26Module // middleware has access to ICS4Wrapper which may be core IBC Channel Handler // or a higher-level middleware that wraps this middleware. ics4Wrapper: ICS4Wrapper } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // This is implemented by ICS4 and all middleware that are wrapping base application. // The base application will call `sendPacket` or `writeAcknowledgement` of the // middleware directly above them which will call the next middleware until it reaches // the core IBC handler. interface ICS4Wrapper { sendPacket( capability: CapabilityKey, sourcePort: Identifier, sourceChannel: Identifier, timeoutHeight: Height, timeoutTimestamp: uint64, data: bytes): uint64 writeAcknowledgement(packet: Packet, ack: Acknowledgement) } ``` #### Handshake Callbacks ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onChanOpenInit( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string): (version: string, err: Error) { if version != "" { // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. metadata, err = UnmarshalJSON(version) if err != nil { // call the underlying application's onChanOpenInit callback return app.onChanOpenInit( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, version, ) } } else { metadata = { // set middleware version to default value middlewareVersion: defaultMiddlewareVersion, // allow application to return its default version appVersion: "", } } doCustomLogic() // call the underlying application's OnChanOpenInit callback. // if the version string is empty, OnChanOpenInit is expected to return // a default version string representing the version(s) it supports appVersion, err = app.OnChanOpenInit( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, metadata.appVersion, // note we only pass app version here ) abortTransactionUnless(err != nil) // a new version string is constructed with the app version returned // by the underlying application, in case it is different than the // one passed by the caller metadata = { // note this should have a different field name specific to middleware middlewareVersion: metadata.middlewareVersion, appVersion: appVersion, } return MarshallJSON(metadata), nil } function onChanOpenTry( order: ChannelOrder, connectionHops: [Identifier], portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string): (version: string, err: Error) { // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. cpMetadata, err = UnmarshalJSON(counterpartyVersion) if err != nil { // call the underlying application's OnChanOpenTry callback return app.onChanOpenTry( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, counterpartyVersion, ) } // select mutually compatible middleware version if !isCompatible(cpMetadata.middlewareVersion) { return "", error } middlewareVersion = selectMiddlewareVersion(cpMetadata.middlewareVersion) doCustomLogic() // call the underlying application's OnChanOpenTry callback appVersion, err = app.OnChanOpenTry( order, connectionHops, portIdentifier, channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, cpMetadata.appVersion, // note we only pass counterparty app version here ) abortTransactionUnless(err != nil) // a new version string is constructed with the final middleware version // that is selected and the app version returned by the underlying // application (which may be different than the one passed by the caller) metadata = { // note this should have a different field name specific to middleware middlewareVersion: middlewareVersion, appVersion: appVersion, } return MarshalJSON(metadata), nil } function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, counterpartyVersion: string) { cpMetadata, err = UnmarshalJSON(counterpartyVersion) if err != nil { // call the underlying application's OnChanOpenAck callback return app.onChanOpenAck( portIdentifier, channelIdentifier, counterpartyChannelIdentifier, counterpartyVersion, ) } if !isSupported(cpMetadata.middlewareVersion) { return error } doCustomLogic() // call the underlying application's OnChanOpenAck callback return app.onChanOpenAck( portIdentifier, channelIdentifier, counterpartyChannelIdentifier, cpMetadata.appVersion, ) } function onChanOpenConfirm( portIdentifier: Identifier, channelIdentifier: Identifier) { doCustomLogic() app.OnChanOpenConfirm(portIdentifier, channelIdentifier) } ``` NOTE: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshaling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. #### Packet Callbacks ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function onRecvPacket(packet: Packet, relayer: string): bytes { doCustomLogic() app_acknowledgement = app.onRecvPacket(packet, relayer) // middleware may modify ack ack = doCustomLogic(app_acknowledgement) return marshal(ack) } function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { doCustomLogic() // middleware may modify ack app_ack = getAppAcknowledgement(acknowledgement) app.onAcknowledgePacket(packet, app_ack, relayer) doCustomLogic() } function onTimeoutPacket(packet: Packet, relayer: string) { doCustomLogic() app.onTimeoutPacket(packet, relayer) doCustomLogic() } function onTimeoutPacketClose(packet: Packet, relayer: string) { doCustomLogic() app.onTimeoutPacketClose(packet, relayer) doCustomLogic() } ``` NOTE: Middleware may do pre- and post-processing on underlying application data for all IBC Module callbacks defined in ICS-26. #### ICS-4 Wrappers ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function writeAcknowledgement( packet: Packet, acknowledgement: bytes) { // middleware may modify acknowledgement ack_bytes = doCustomLogic(acknowledgement) return ics4Wrapper.writeAcknowledgement(packet, ack_bytes) } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function sendPacket( capability: CapabilityKey, sourcePort: Identifier, sourceChannel: Identifier, timeoutHeight: Height, timeoutTimestamp: uint64, app_data: bytes): uint64 { // middleware may modify packet data = doCustomLogic(app_data) return ics4Wrapper.sendPacket( capability, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) } ``` ### User Interaction In the case where the middleware requires some user input in order to modify the outgoing packet messages from the underlying application, the middleware MUST get this information from the user before it receives the packet message from the underlying application. It must then do its own authentication of the user input, and ensure that the user input provided to the middleware is matched to the correct outgoing packet message. The middleware MAY accomplish this by requiring that the user input to middleware, and packet message to underlying application are sent atomically and ordered from outermost middleware to base application. ### Security Model As seen above, IBC middleware may arbitrarily modify any incoming or outgoing data from an underlying application. Thus, developers should not use any untrusted middleware in their application stacks. ## Backwards Compatibility The Middleware approach is a design pattern already enabled by current IBC. This ICS seeks to standardize a particular design pattern for IBC middleware. There are no changes required to core IBC or any existing application. ## Forwards Compatibility Not applicable. ## Example Implementations * Implementation of ICS 29 in Go following ICS 30 design pattern can be found in [ibc-go repository](https://github.com/cosmos/ibc-go). ## History June 22, 2021 - Draft submitted July 6, 2022 - Update with latest changes from implementation ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # Cross-chain Queries Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-031-crosschain-queries/README ## Synopsis This standard document specifies the data structures and state machine handling logic of the Cross-chain Queries module, which allows for cross-chain querying between IBC enabled chains. ## Overview and Basic Concepts ### Motivation We expect on-chain applications to depend on reads from other chains, e.g., a particular application on a chain may need to know the current price of the token of a second chain. While the IBC protocol enables on-chain applications to talk to other chains, using it for simply querying the state of chains would be too expensive: it would require to maintain an open channel between the querying chain and any other chain, and use the full IBC stack for every query request. Note that the latter implies exchanging packets between chains and therefore committing transactions at the queried chain, which may disrupt its operation if the load of query requests is high. Cross-chain queries solve this issue. It enables on-chain applications to query the state of other chains seamlessly: without involving the queried chain, and requiring very little from the querying chain. ### Definitions `Querying chain`: The chain that is interested in getting data from another chain (queried chain). The querying chain is the chain that implements the Cross-chain Queries module. `Queried chain`: The chain whose state is being queried. The queried chain gets queried via a relayer utilizing its RPC client which is then submitted back to the querying chain. `Cross-chain Queries Module`: The module that implements the cross-chain querying protocol. Only the querying chain integrates it. `Height` and client-related functions are as defined in ICS 2. `newCapability` and `authenticateCapability` are as defined in ICS 5. `CommitmentPath` and `CommitmentProof` are as defined in ICS 23. `Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in ICS 24. `Fee` is as defined in ICS 29. ## System Model and Properties ### Assumptions * **Safe chains:** Both the querying and queried chains are safe. This means that, for every chain, the underlying consensus engine satisfies safety (e.g., the chain does not fork) and the execution of the state machine follows the described protocol. * **Live chains:** Both the querying and queried chains MUST be live, i.e., new blocks are eventually added to the chain. * **Censorship-resistant querying chain:** The querying chain cannot selectively omit valid transactions. > For example, this means that if a relayer submits a valid transaction to the querying chain, the transaction is guaranteed to be eventually included in a committed block. Note that Tendermint does not currently guarantee this. * **Correct relayer:** There is at least one live relayer between the querying and queried chains where the relayer correctly follows the protocol. > In the context of this specification, this implies that for every query request coming from the querying chain, there is at least one relayer that (i) picks the query request up, (ii) executes the query at the queried chain, and (iii) submits the result in a transaction, together with a valid proof, to the querying chain. The above assumptions are enough to guarantee that the query protocol returns results to the application if the querying chain waits unboundly for query results. Nevertheless, this specification considers the case when the querying chain times out after a fixed period of time. Thus, to guarantee that the query protocol always returns query results to the application, the specification requires additional assumptions: both the querying chain and at least one correct relayer have to behave timely. * **Timely querying chain:** There exists an upper-bound in the time elapsed between the moment a transaction is submitted to the chain and when the chain commits a block including it. * **Timely relayer:** For correct and live relayers, there exists an upper-bound in the time elapsed between the moment a relayer picks a query request and when the relayer submits the query result. > Note then that to guarantee that the query protocol always returns results to the application, the timeout bound at the querying chain should be at least equal to the sum of the upper-bounds of assumptions **Timely querying chain** and **Timely relayer**. This would guarantee that the relayer submits and the querying chain process a query result transaction within the specified timeout bound. ### Desired Properties #### Permissionless The querying chain can query a chain without permission from the latter and implement cross-chain querying without any approval from a third party or chain governance. Note that since there is no prior negotiation between chains, the querying chain cannot assume that queried data will be in an expected format. #### Minimal queried chain Work Any chain that provides query support can act as a queried chain, requiring no implementation work or any extra module. This is possible by utilizing an RPC client on a relayer. #### Modular Supporting cross-chain queries should be as easy as implementing a module in your chain. #### Incentivization A bounty is paid to incentivize relayers for participating in cross-chain queries: fetching data from the queried chain and submitting it (together with proofs) to the querying chain. ## Technical Specification ### General Design The querying chain must implement the Cross-chain Queries module, which allows the querying chain to query state at the queried chain. Cross-chain queries rely on relayers operating between both chains. When a query request is received by the querying chain, the Cross-chain Queries module emits a `sendQuery` event. Relayers operating between the querying and queried chains must monitor the querying chain for `sendQuery` events. Eventually, a relayer will retrieve the query request and execute it, i.e., fetch the data and generate the corresponding proofs, at the queried chain. The relayer then submits the result in a transaction to the querying chain. The result is finally registered at the querying chain by the Cross-chain Queries module. A query request includes the height of the queried chain at which the query must be executed. The reason is that the keys being queried can have different values at different heights. Thus, a malicious relayer could choose to query a height that has a value that benefits it somehow. By letting the querying chain decide the height at which the query is executed, we can prevent relayers from affecting the result data. > Note that this mechanism does not prevent cross-chain MEV (maximal extractable value): this still creates an opportunity for altering the state on the queried chain if the height is in the future in order to change the results of the query. ### Data Structures The Cross-chain Queries module stores query requests when it processes them. A CrossChainQuery is a particular interface to represent query requests. A request is retrieved when its result is submitted. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface CrossChainQuery { id: Identifier path: CommitmentPath localTimeoutHeight: Height localTimeoutTimestamp: uint64 queryHeight: Height clientId: Identifier bounty: Fee } ``` * The `id` field uniquely identifies the query at the querying chain. * The `path` field is the path to be queried at the queried chain. * The `localTimeoutHeight` field specifies a height limit at the querying chain after which a query is considered to have failed and a timeout result should be returned to the original caller. * The `localTimeoutTimestamp` field specifies a timestamp limit at the querying chain after which a query is considered to have failed and a timeout result should be returned to the original caller. * The `queryHeight` field is the height at which the relayer must query the queried chain * The `clientId` field identifies the querying chain's client of the queried chain. * The `bounty` field is a bounty that is given to the relayer for participating in the query. The Cross-chain Queries module stores query results to allow query callers to asynchronously retrieve them. In this context, this standard defines the `QueryResult` type as follows: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} enum QueryResult { SUCCESS, FAILURE, TIMEOUT } ``` * A query that returns a value is marked as `SUCCESS`. This means that the query has been executed at the queried chain and there was a value associated to the queried path at the requested height. * A query that is executed but does not return a value is marked as `FAILURE`. This means that the query has been executed at the queried chain, but there was no value associated to the queried path at the requested height. * A query that timed out before a result is committed at the querying chain is marked as `TIMEOUT`. A `CrossChainQueryResult` is a particular interface used to represent query results. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} interface CrossChainQueryResult { id: Identifier result: QueryResult data: []byte } ``` * The `id` field uniquely identifies the query at the querying chain. * The `result` field indicates whether the query was correctly executed at the queried chain and if the queried path exists. * The `data` field is an opaque bytestring that contains the value associated with the queried path in case `result = SUCCESS`. ### Store paths #### Query path The query path is a private path that stores the state of ongoing cross-chain queries. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function queryPath(id: Identifier): Path { return "queries/{id}" } ``` #### Result query path The result query path is a private path that stores the result of completed queries. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function queryResultPath(id: Identifier): Path { return "result/queries/{id}" } ``` ### Helper functions The querying chain MUST implement a function `generateIdentifier`, which generates a unique query identifier: ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function generateIdentifier = () -> Identifier ``` ### Sub-protocols #### Query lifecycle 1. When the querying chain receives a query request, it calls `CrossChainQueryRequest` of the Cross-chain Queries module. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted as transactions to the querying chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. Typically, query requests will be issued by other IBC modules. 2. A correct relayer listening to `sendQuery` events from the querying chain will eventually pick the query request up and execute it at the queried chain. The result is then submitted in a transaction to the querying chain. 3. When the query result is committed at the querying chain, this calls the `CrossChainQueryResponse` function of the Cross-chain Queries module. 4. The `CrossChainQueryResponse` first retrieves the query from the `privateStore` using the query's unique identifier. It then proceeds to verify the result using its local client. If it passes the verification, the function removes the query from the `privateStore` and stores the result in the private store. > The querying chain may execute additional state machine logic when a query result is received. To account for this additional state machine logic and charge a fee to the query caller, an implementation of this specification could use the already existing `bounty` field of the `CrossChainQuery` interface or extend the interface with an additional field. 5. The query caller can then asynchronously retrieve the query result. The function `PruneCrossChainQueryResult` allows a query caller to prune the result from the store once it retrieves it. #### Normal path methods The `CrossChainQueryRequest` function is called when the Cross-chain Queries module at the querying chain receives a new query request. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function CrossChainQueryRequest( path: CommitmentPath, queryHeight: Height, localTimeoutHeight: Height, localTimeoutTimestamp: uint64, clientId: Identifier, bounty: Fee, ): [Identifier, CapabilityKey] { // Check that there exists a client of the queried chain. The client will be used to verify the query result. abortTransactionUnless(queryClientState(clientId) !== null) // Sanity-check that localTimeoutHeight is 0 or greater than the current height, otherwise the query will always time out. abortTransactionUnless(localTimeoutHeight === 0 || localTimeoutHeight > getCurrentHeight()) // Sanity-check that localTimeoutTimestamp is 0 or greater than the current timestamp, otherwise the query will always time out. abortTransactionUnless(localTimeoutTimestamp === 0 || localTimeoutTimestamp > currentTimestamp()) // Generate a unique query identifier. queryIdentifier = generateQueryIdentifier() // Create a query request record. query = CrossChainQuery{queryIdentifier, path, queryHeight, localTimeoutHeight, localTimeoutTimestamp, clientId, bounty} // Store the query in the local, private store. privateStore.set(queryPath(queryIdentifier), query) queryCapability = newCapability(queryIdentifier) // Log the query request. emitLogEntry("sendQuery", query) // Returns the query identifier. return [queryIdentifier, queryCapability] } ``` * **Precondition** * There exists a client with `clientId` identifier. * **Postcondition** * The query request is stored in the `privateStore`. * A `sendQuery` event is emitted. The `CrossChainQueryResponse` function is called when the Cross-chain Queries module at the querying chain receives a new query reply. We pass the address of the relayer that submitted the query result to the querying chain to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard). ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function CrossChainQueryResponse( queryId: Identifier, data: []byte proof: CommitmentProof, proofHeight: Height, delayPeriodTime: uint64, delayPeriodBlocks: uint64, relayer: string ) { // Retrieve query state from the local, private store using the query's identifier. query = privateStore.get(queryPath(queryIdentifier)) abortTransactionUnless(query !== null) // Retrieve client state of the queried chain. clientState = queryClientState(query.clientId) abortTransactionUnless(client !== null) // Check that the relier executed the query at the requested height at the queried chain. abortTransactionUnless(query.queryHeight !== proofHeight) // Check that localTimeoutHeight is 0 or greater than the current height. abortTransactionUnless(query.localTimeoutHeight === 0 || query.localTimeoutHeight > getCurrentHeight()) // Check that localTimeoutTimestamp is 0 or greater than the current timestamp. abortTransactionUnless(query.localTimeoutTimestamp === 0 || query.localTimeoutTimestamp > currentTimestamp()) // Verify query result using the local light client of the queried chain. // If the response carries data, then verify that the data is indeed the value associated with query.path at query.queryHeight at the queried chain. if (data !== null) { abortTransactionUnless(verifyMembership( clientState, proofHeight, delayPeriodTime, delayPeriodBlocks, proof, query.path, data )) result = SUCCESS // If the response does not carry any data, verify that query.path does not exist at query.queryHeight at the queried chain. } else { abortTransactionUnless(verifyNonMembership( clientState, proofHeight, delayPeriodTime, delayPeriodBlocks, proof, query.path, )) result = FAILURE } // Delete the query from the local, private store. privateStore.delete(queryPath(queryId)) // Create a query result record. resultRecord = CrossChainQuery{queryIdentifier, result, data} // Store the result in the local, private store. privateStore.set(queryResultPath(queryIdentifier), resultRecord) } ``` * **Precondition** * There exists a client with `clientId` identifier. * There is a query request stored in the `privateStore` identified by `queryId`. * **Postcondition** * The query request identified by `queryId` is deleted from the `privateStore`. * The query result is stored in the `privateStore`. The `PruneCrossChainQueryResult` function is called when the caller of a query has retrieved the result and wants to delete it. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function PruneCrossChainQueryResult( queryId: Identifier, queryCapability: CapabilityKey ) { // Retrieve the query result from the private store using the query's identifier. resultRecord = privateStore.get(queryResultPath(queryIdentifier)) abortTransactionUnless(resultRecord !== null) // Abort the transaction unless the caller has the right to clean the query result abortTransactionUnless(authenticateCapability(queryId, queryCapability)) // Delete the query result from the local, private store. privateStore.delete(queryResultPath(queryId)) } ``` * **Precondition** * There is a query result stored in the `privateStore` identified by `queryId`. * The caller has the right to clean the query result * **Postcondition** * The query result identified by `queryId` is deleted from the `privateStore`. #### Timeouts Query requests have associated a `localTimeoutHeight` and a `localTimeoutTimestamp` field that specifies the height and timestamp limit at the querying chain after which a query is considered to have failed. There are several alternatives on how to handle timeouts. For instance, the relayer could submit timeout notifications as transactions to the querying chain. Since the relayer is untrusted, for each of these notifications, the Cross-chain Queries module of the querying chain MUST call the `checkQueryTimeout` to check if the query has indeed timed out. An alternative could be to make the Cross-chain Queries module responsible for checking if any query has timed out by iterating over the ongoing queries at the beginning of a block and calling `checkQueryTimeout`. In this case, ongoing queries should be stored indexed by `localTimeoutTimestamp` and `localTimeoutHeight` to allow iterating over them more efficiently. These are implementation details that this specification does not cover. Assume that the relayer is in charge of submitting timeout notifications as transactions. The `checkQueryTimeout` function would look as follows. Note that we pass the relayer address just as in `CrossChainQueryResponse` to allow for possible incentivization here as well. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} function checkQueryTimeout( queryId: Identifier, relayer: string ){ // Retrieve the query state from the local, private store using the query's identifier. query = privateStore.get(queryPath(queryIdentifier)) abortTransactionUnless(query !== null) // Get the current height. currentHeight = getCurrentHeight() // Check that localTimeoutHeight or localTimeoutTimestamp has passed on the querying chain (locally) abortTransactionUnless( (query.localTimeoutHeight > 0 && query.localTimeoutHeight < getCurrentHeight()) || (query.localTimeoutTimestamp > 0 && query.localTimeoutTimestamp < currentTimestamp())) // Delete the query from the local, private store if it has timed out privateStore.delete(queryPath(queryId)) // Create a query result record. resultRecord = CrossChainQuery{queryIdentifier, TIMEOUT, query.caller null} // Store the result in the local, private store. privateStore.set(resultQueryPath(queryIdentifier), resultRecord) } ``` * **Precondition** * There is a query request stored in the `privateStore` identified by `queryId`. * **Postcondition** * If the query has indeed timed out, then * the query request identified by `queryId` is deleted from the `privateStore`; * the fact that the query has timed out is recorded in the `privateStore`. ## History January 6, 2022 - First draft May 11, 2022 - Major revision June 14, 2022 - Adds pruning, localTimeoutTimestamp and adds relayer address for incentivization July 28, 2022 - Revision of the assumptions ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). # Atomic Swap Source: https://docs.cosmos.network/ibc/latest/spec/app/ics-100-atomic-swap/README This specification has been reviewed by the Spec Committee, but it is not responsible for maintaining it. ## Synopsis This standard document specifies packet data structure, state machine handling logic, and encoding details for the atomic swap of fungible tokens over an IBC channel between two modules on separate chains. ### Motivation Users may wish to exchange tokens without transferring tokens away from their native chain. ICS-100 enabled chains can facilitate atomic swaps between users and their tokens located on the different chains. This is useful for exchanges between specific users at specific prices, and opens opportunities for new application designs. For example, a token exchange would require only one transaction from a user, compared to multiple transactions when using ICS-20. Additionally, users can minimize trade slippage compared to using a liquidity pool, given there is a willing counter-party. ### Definitions `Atomic Swap`: An exchange of tokens from separate chains without transferring tokens from one blockchain to another. The exchange either happens or it doesn't -- there is no other alternative. `Order`: An offer to exchange quantity X of token A for quantity Y of token B. Tokens offered are sent to an escrow account (owned by the module). `Maker`: A user that makes or initiates an order. `Taker`: The counterparty who takes or responds to an order. `Maker Chain`: The blockchain where a maker makes or initiates an order. `Taker Chain`: The blockchain where a taker takes or responds to an order. ### Desired Properties * `Permissionless`: no need to whitelist connections, modules, or denominations. * `Guarantee of exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. * `Escrow enabled`: an account owned by the module will hold tokens and facilitate exchange. * `Refundable`: tokens are refunded by escrow when a timeout occurs, or when an order is cancelled. * `Order cancellation`: orders without takers can be cancelled. * `Basic orderbook`: a store of orders functioning as an orderbook system. * `Atomicity`: an exchange of one token for another where it is either a total success or a total failure. ## Technical Specification ### General Design IBC swap diagram A maker offers token A in exchange for token B by making an order. The order specifies the quantity and price of exchange, and sends the offered token A to the maker chain's escrow account. Any taker on a different chain with token B can accept the offer by taking the order. The taker sends the desired amount of token B to the taker chain's escrow account. The escrow account on each respective chain transfers the corresponding token amounts to each user's receiving address, without requiring the usual IBC transfer. An order without takers can be cancelled. This enables users to rectify mistakes, such as inputting an incorrect price or taker address. Upon cancellation escrowed tokens will be refunded. When making or taking an order, a timeout window is specified in the relayed data packet. A timeout will result in escrowed tokens refunded back. This timeout window is customizable. ### Data Structures Only one packet data type is required: `AtomicSwapPacketData`, which specifies the swap message type, data (protobuf marshalled) and a memo field. ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} enum SwapMessageType { // Default zero value enumeration TYPE_UNSPECIFIED = 0, TYPE_MSG_MAKE_SWAP = 1, TYPE_MSG_TAKE_SWAP = 2, TYPE_MSG_CANCEL_SWAP = 3, } ``` ```typescript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}} // AtomicSwapPacketData is comprised of a swap message type, raw transaction and optional memo field. interface