diff --git a/app.go b/app.go index 867fa78..fc0bdf4 100644 --- a/app.go +++ b/app.go @@ -64,6 +64,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/tx/signing" "github.com/cosmos/gogoproto/proto" + // counter tutorial app wiring 1: add counter imports here counter "github.com/cosmos/example/x/counter" counterkeeper "github.com/cosmos/example/x/counter/keeper" countertypes "github.com/cosmos/example/x/counter/types" @@ -115,6 +116,7 @@ type ExampleApp struct { StakingKeeper *stakingkeeper.Keeper SlashingKeeper slashingkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper + // counter tutorial app wiring 2: add the counter keeper field here CounterKeeper *counterkeeper.Keeper // the module manager @@ -172,6 +174,7 @@ func NewExampleApp( slashingtypes.StoreKey, govtypes.StoreKey, consensusparamtypes.StoreKey, + // counter tutorial app wiring 3: add the counter store key here countertypes.StoreKey, ) @@ -280,6 +283,7 @@ func NewExampleApp( ), ) + // counter tutorial app wiring 4: create the counter keeper here app.CounterKeeper = counterkeeper.NewKeeper(runtime.NewKVStoreService(keys[countertypes.StoreKey]), appCodec, app.BankKeeper) app.ModuleManager = module.NewManager( @@ -295,6 +299,7 @@ func NewExampleApp( distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, nil), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, nil), vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + // counter tutorial app wiring 5: register the counter module here counter.NewAppModule(appCodec, app.CounterKeeper), ) @@ -329,6 +334,7 @@ func NewExampleApp( distrtypes.ModuleName, slashingtypes.ModuleName, stakingtypes.ModuleName, + // counter tutorial app wiring 6: add the counter module to genesis order here countertypes.ModuleName, genutiltypes.ModuleName, ) @@ -336,6 +342,7 @@ func NewExampleApp( banktypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, + // counter tutorial app wiring 7: add the counter module to export order here countertypes.ModuleName, genutiltypes.ModuleName, ) diff --git a/docs/tutorial-00-prerequisites.md b/docs/tutorial-00-prerequisites.md index 08a954e..91ea840 100644 --- a/docs/tutorial-00-prerequisites.md +++ b/docs/tutorial-00-prerequisites.md @@ -74,12 +74,12 @@ example/ └── Makefile # Build, test, and dev commands ``` -**Where to make changes:** +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: -- Adding or modifying a module → `x//` and `proto/` -- Wiring a module into the chain → `app.go` -- Changing the binary or CLI → `exampled/` -- Running the chain or tests → `Makefile` targets +- 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 --- diff --git a/docs/tutorial-01-quickstart.md b/docs/tutorial-01-quickstart.md index 9119f5a..1f2f552 100644 --- a/docs/tutorial-01-quickstart.md +++ b/docs/tutorial-01-quickstart.md @@ -1,29 +1,37 @@ # Quickstart -This page gets the example chain running in a few minutes so you can see the counter module working before building it yourself. +`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. + +This quickstart gets you running `exampled`, submitting a transaction, and querying the result as quickly as possible. It also includes the `x/counter` module, which stores a single counter value, lets you query the current count, and lets you submit `Add` transactions to increment it. In the next tutorials, you'll build a simple version of this module yourself and then walk through the full implementation and its additional features. ## Install the binary +Run the following to compile the `exampled` binary and place it on your `$PATH`. + ```bash make install ``` -This compiles the `exampled` binary and places it on your `$PATH`. - -Verify: +Verify the install by running: ```bash exampled version ``` +You can also run the following to see all available node CLI commands: + +```bash +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 make start ``` -This starts 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. - ## Query the counter Open a second terminal and query the current count: @@ -32,16 +40,21 @@ Open a second terminal and query the current count: exampled query counter count ``` -``` -count: "0" +You should see the following output, which means the counter is starting at `0`: + + +```text +{} ``` -Query the module parameters: +You can also query the module parameters: ```bash 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 params: add_cost: @@ -52,7 +65,7 @@ params: ## Submit an add transaction -The `alice` account is funded at chain start. Send 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 exampled tx counter add 5 --from alice --chain-id demo --yes @@ -60,24 +73,27 @@ 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 exampled query counter count ``` +You should see the following: + ``` count: "5" ``` -The counter incremented by 5. +Congratulations! You just ran a blockchain, submitted a transaction, and queried module state. -## What you just ran +## Next steps -The chain is running the `x/counter` module — the production counter module in this repository. In the following tutorials you will: +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 production `x/counter` to see what it adds +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 →](./tutorial-02-build-a-module.md) diff --git a/docs/tutorial-02-build-a-module.md b/docs/tutorial-02-build-a-module.md index 832a68f..7562706 100644 --- a/docs/tutorial-02-build-a-module.md +++ b/docs/tutorial-02-build-a-module.md @@ -1,58 +1,41 @@ # Build a Module from Scratch -This tutorial walks through building a minimal counter module from scratch so you can learn the core Cosmos SDK module pattern as quickly as possible. It uses the same overall structure as `x/counter`, the fuller example module in this repository. +In [quickstart](./tutorial-01-quickstart.md), 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. -By the end, you'll have a working counter module wired into a running chain. +> **Install prerequisites:** Before continuing, follow the [Prerequisites guide](./tutorial-00-prerequisites.md) to make sure everything is installed. This tutorial will not work without them. -## Before You Begin +## Making modules - - -This tutorial uses the `tutorial/start` branch, which has the counter module stripped out and `app.go` wiring removed. The directory structure is in place — you fill it in. - -```bash -git clone https://github.com/cosmos/example -cd example -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/`. - -> **Branch model:** On the `main` branch, `x/counter` is the full production module. On `tutorial/start`, that module and its app.go wiring are removed so you can rebuild a minimal version from scratch under the same path (`x/counter`). Without this context, it may look like you are editing the production module — you are not. - - -## The Module Loop - -Every Cosmos SDK module follows the same pattern: +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 proto files → code generation → keeper → msg server → query server → module.go → app wiring ``` -- **Proto files** define the module's messages, queries, and state types -- **Code generation** (`make proto-gen`) produces Go interfaces and types from those definitions -- **Keeper** owns and manages the module's state -- **MsgServer** handles incoming transactions and delegates to the keeper -- **QueryServer** handles read-only queries against the keeper -- **module.go** wires everything together and registers it with the application - -Here is how each proto definition maps to generated code and then to your implementation: +First, you'll define what the module does: -| Proto | Generated (types/) | Your implementation | -| --- | --- | --- | -| `service Msg { rpc Add }` | `MsgServer` interface | `keeper/msg_server.go` | -| `service Query { rpc Count }` | `QueryServer` interface | `keeper/query_server.go` | -| `message MsgAddRequest` | `MsgAddRequest` struct | used as input in `msg_server.go` | -| `message GenesisState` | `GenesisState` struct | used in `InitGenesis` / `ExportGenesis` | +- 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` -Steps 3–8 implement the right-hand column. Steps 1–2 produce the middle column. +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` -## Directory Structure +You'll build the following module structure: ```text +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 @@ -64,16 +47,33 @@ x/counter/ │ └── *.pb.go # Generated from proto — do not edit ├── module.go # AppModule wiring └── autocli.go # CLI command definitions +``` -proto/example/counter/v1/ -├── tx.proto -├── query.proto -└── genesis.proto +## 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 +git clone https://github.com/cosmos/example +cd example ``` -## Step 1: Proto files +2. Check out the `tutorial/start` branch and make the new module directories: + +```bash +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. -Proto files are the source of truth for the module's public API. You define messages and services here. 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. After, `make proto-gen` produces the Go interfaces you then implement. +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: @@ -87,7 +87,7 @@ 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. +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. Add the following code to `tx.proto`. ```proto syntax = "proto3"; @@ -123,7 +123,7 @@ message MsgAddResponse { ### query.proto -This file defines the read-only gRPC query service and the response type for fetching the current count. +This file defines the read-only gRPC query service and the response type for fetching the current count. Add the following code to `query.proto`. ```proto syntax = "proto3"; @@ -155,7 +155,7 @@ message QueryCountResponse { ### genesis.proto -This file defines the data the module stores in genesis so the counter can be initialized when the chain starts. +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 syntax = "proto3"; @@ -172,16 +172,20 @@ message GenesisState { } ``` -## Step 2: Generate Code +## Step 3: Generate Code -Make sure Docker is running. The first time you run proto-gen you need to build the builder image: +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 make proto-image-build make proto-gen ``` -This compiles the proto files using [buf](https://buf.build) inside Docker. The generated files will appear in `x/counter/types/`: +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 x/counter/types/ @@ -196,11 +200,11 @@ x/counter/types/ 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 3: Types +## Step 4: Types -In this step, you define the small supporting files in `x/counter/types` that the rest of the module depends on before you write any keeper logic. They live here because they describe shared module types and identifiers, not state-management code. +Next, you'll define the module types and identifiers in `x/counter/types` that the rest of the module depends on. -First, create the two files for this section: +Create the two files for this section: ```bash touch x/counter/types/keys.go \ @@ -254,7 +258,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { `_Msg_serviceDesc` is generated by `make proto-gen` — it describes the `Msg` gRPC service defined in `tx.proto`. -## Step 4: Keeper +## 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. @@ -342,7 +346,7 @@ func (k *Keeper) ExportGenesis(ctx context.Context) (*types.GenesisState, error) > - `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 5: MsgServer +## 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`. @@ -388,7 +392,7 @@ func (m msgServer) Add(ctx context.Context, req *types.MsgAddRequest) (*types.Ms `msgServer` embeds `*Keeper` and delegates directly to `AddCount`. The handler itself contains no business logic. -## Step 6: QueryServer +## 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. @@ -431,7 +435,7 @@ func (q queryServer) Count(ctx context.Context, _ *types.QueryCountRequest) (*ty ``` -## Step 7: module.go +## 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. @@ -546,7 +550,7 @@ The `var _ interface = Struct{}` block at the top is a Go compile-time check — `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 8: AutoCLI +## 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. @@ -558,7 +562,7 @@ 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. +This file tells `AutoCLI` how to expose the `Count` query and `Add` transaction as simple command-line commands. ```go // x/counter/autocli.go @@ -580,7 +584,7 @@ func (a AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { Tx: &autocliv1.ServiceCommandDescriptor{ Service: "example.counter.Msg", RpcCommandOptions: []*autocliv1.RpcCommandOptions{ - // exampled tx counter add 5 --from alice + // exampled tx counter add 4 --from alice {RpcMethod: "Add", Use: "add [amount]", Short: "Add to the counter", PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "add"}}}, }, @@ -589,81 +593,85 @@ func (a AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { } ``` -`PositionalArgs` maps the first CLI argument to the `add` field in `MsgAddRequest`, so `add 5` works instead of `add --add 5`. +`PositionalArgs` maps the first CLI argument to the `add` field in `MsgAddRequest`, so `add 4` works instead of `add --add 4`. -## Step 9: Wire into app.go +## 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. -Open `app.go`. For each change below, find the matching block and add the highlighted lines. +Open `app.go`. Each code block below starts with the exact marker comment to look for in the file. Paste your code directly below each marker. -### Imports +### 1. Imports -Find the import block near the bottom of the Cosmos SDK imports and add these three lines: +Add the counter module, keeper, and shared types imports to `app.go`. ```go -// tutorial-02: add counter imports here -// After: "github.com/cosmos/cosmos-sdk/x/tx/signing" +// counter tutorial app wiring 1: add counter imports here counter "github.com/cosmos/example/x/counter" counterkeeper "github.com/cosmos/example/x/counter/keeper" countertypes "github.com/cosmos/example/x/counter/types" ``` -### Keeper Field +### 2. Keeper Field -Find the keeper fields on `type ExampleApp struct` and add `CounterKeeper`: +Store the counter keeper on `ExampleApp` so the rest of the app can reference it. ```go -// tutorial-02: add counter keeper field here -// After: ConsensusParamsKeeper consensusparamkeeper.Keeper +// counter tutorial app wiring 2: add the counter keeper field here CounterKeeper *counterkeeper.Keeper ``` -### Store Key +### 3. Store Key -Find `keys := storetypes.NewKVStoreKeys(` and add the counter store key: +Give the counter module its own KV store namespace. ```go -// tutorial-02: add counter store key here -// After: consensusparamtypes.StoreKey, +// counter tutorial app wiring 3: add the counter store key here countertypes.StoreKey, ``` -### Keeper Instantiation +### 4. Keeper Instantiation -Find the code just after `app.GovKeeper = ...` and add the counter keeper construction: +Construct the counter keeper using the module store and app codec. ```go -// tutorial-02: create the counter keeper here -// After: app.GovKeeper = *govKeeper.SetHooks(...) +// counter tutorial app wiring 4: create the counter keeper here app.CounterKeeper = counterkeeper.NewKeeper( runtime.NewKVStoreService(keys[countertypes.StoreKey]), appCodec, ) ``` -### Module Manager +### 5. Module Manager -Find `app.ModuleManager = module.NewManager(` and add the counter module to the list: +Register the counter module with the app's module manager. ```go -// tutorial-02: register the counter module here -// After: vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), +// counter tutorial app wiring 5: register the counter module here counter.NewAppModule(appCodec, app.CounterKeeper), ``` -### Genesis and Export Order +### 6. Genesis Order -For the minimal module, the important ordering is genesis import and export. Add `countertypes.ModuleName` to both `genesisModuleOrder` and `exportModuleOrder`. This module has no special ordering requirements, so placing it near the end of each list is fine. The production module adds module-account wiring and begin/end block hooks later in Tutorial 03. +Include the counter module when the app initializes state from genesis. ```go -// tutorial-02: add this in genesisModuleOrder and exportModuleOrder +// counter tutorial app wiring 6: add the counter module to genesis order here countertypes.ModuleName, ``` +### 7. Export Order + +Include the counter module when the app exports state back out to genesis. -## Step 10: Build +```go +// counter tutorial app wiring 7: add the counter module to export order here +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. @@ -674,7 +682,7 @@ go build ./... Fix any compilation errors before continuing. -## Step 11: Smoke Test +## 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. @@ -688,10 +696,9 @@ make start ``` This builds and installs `exampled` and then runs [`scripts/local_node.sh`](../../scripts/local_node.sh), which: -- resets the local home directory +- resets the local chain data - initializes genesis -- creates the `alice` and `bob` test accounts -- funds those accounts +- creates and funds the `alice` and `bob` test accounts - creates a validator transaction - starts the chain @@ -713,7 +720,7 @@ code: 0 ### Query the chain -Query the counter to confirm the stored value changed. This uses the `exampled query counter count` command that AutoCLI generated from the `Query` service you defined earlier: +Query the counter to confirm the stored value changed using the query command that `AutoCLI` generated earlier: ```bash exampled query counter count @@ -725,24 +732,10 @@ You should see the following output: count: "4" ``` -Congratulations, You've just created a Cosmos module from scratch and wired it into a real chain! - - -## Summary - -| File | Role | -| --- | --- | -| `tx.proto` / `query.proto` / `genesis.proto` | Declare the module's public API | -| `types/keys.go` | Module name and store key | -| `types/codec.go` | Interface registration | -| `keeper/keeper.go` | State ownership and access | -| `keeper/msg_server.go` | Transaction handling | -| `keeper/query_server.go` | Query handling | -| `module.go` | Framework registration | -| `autocli.go` | CLI command definitions | -| `app.go` | Chain integration | +Congratulations, you've just created a Cosmos module from scratch and wired it into a real chain! -The production `x/counter` on the `main` branch follows this exact same structure. In the next section you'll see what it adds on top. +## 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: [Production Counter Walkthrough →](./tutorial-03-counter-walkthrough.md) +Next: [Full Counter Module Walkthrough →](./tutorial-03-counter-walkthrough.md) diff --git a/docs/tutorial-03-counter-walkthrough.md b/docs/tutorial-03-counter-walkthrough.md index 6f1c78a..6cc037e 100644 --- a/docs/tutorial-03-counter-walkthrough.md +++ b/docs/tutorial-03-counter-walkthrough.md @@ -1,16 +1,20 @@ -# Production Counter Walkthrough +# Full Counter Module Walkthrough -The minimal counter you built in the previous tutorial captures the core SDK module pattern. The `x/counter` module in this repository follows the same pattern — it just adds several production features on top. +If you came here from the module building tutorial, switch back to the `main` branch first: -This walkthrough explains what was added, where it lives, and why. +```bash +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. -The wiring code — `msg_server.go`, `query_server.go`, `module.go`, `types/` — is structurally identical between the two. Almost all of the new logic lives in a single method: `keeper.AddCount`. +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 -## Minimal vs production +The full counter in the `main` branch adds quite a bit of functionality to the minimal tutorial counter. -| Feature | Minimal | x/counter | +| Feature | minimal x/counter | full x/counter | |---|---|---| | State | `count` | `count` + `params` | | Messages | `Add` | `Add` + `UpdateParams` | @@ -25,44 +29,79 @@ The wiring code — `msg_server.go`, `query_server.go`, `module.go`, `types/` | Block hooks | None | `BeginBlock` + `EndBlock` stubs | | 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 -The most significant addition is a `Params` type that lets the chain governance configure the module's behavior at runtime. +A module param 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 +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 -A new proto file defines the `Params` type: +The relevant addition in [`state.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/state.proto) is: ```proto -// proto/example/counter/v1/state.proto message Params { uint64 max_add_value = 1; - repeated cosmos.base.v1beta1.Coin add_cost = 2; + 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 +### tx.proto - UpdateParams -A second message is added to `tx.proto`: +The relevant addition in [`tx.proto`](https://github.com/cosmos/example/blob/main/proto/example/counter/v1/tx.proto) is: ```proto rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); message MsgUpdateParams { - string authority = 1; - Params params = 2; + 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. +`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 - Params -A second query is added to expose the current 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 rpc Params(QueryParamsRequest) returns (QueryParamsResponse); @@ -73,9 +112,10 @@ rpc Params(QueryParamsRequest) returns (QueryParamsResponse); The keeper stores the authority address and checks it on every `UpdateParams` call: ```go -// keeper.go type Keeper struct { // ... + // authority is the address capable of executing a MsgUpdateParams message. + // Typically, this should be the x/gov module account. authority string } ``` @@ -97,20 +137,68 @@ The authority defaults to the governance module account at keeper construction: authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), ``` -This pattern — storing authority in the keeper, checking it in `MsgServer` — is the standard Cosmos SDK approach to governance-gated configuration. +This pattern, storing authority in the keeper and checking it in `MsgServer`, is the standard Cosmos SDK approach to governance-gated configuration. + + +## Expected keepers and fee collection + +This section shows the standard Cosmos SDK pattern for module-to-module interaction. `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 +maccPerms = map[string][]string{ + // ... + countertypes.ModuleName: nil, +} +``` + +```go +app.CounterKeeper = counterkeeper.NewKeeper( + runtime.NewKVStoreService(keys[countertypes.StoreKey]), + appCodec, + app.BankKeeper, +) +``` + +### Try it + +Submit an add transaction and the configured `AddCost` fee will be charged from the sender: ---- +```bash +exampled tx counter add 5 --from alice --chain-id demo --yes +``` -## Fee collection — BankKeeper +### Add this to your module -`x/counter` charges a fee for each add operation when `AddCost` is set. This requires calling into the bank 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 -// types/expected_keepers.go +// x/counter/types/expected_keepers.go type BankKeeper interface { SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } @@ -125,7 +213,7 @@ type Keeper struct { Schema collections.Schema counter collections.Item[uint64] params collections.Item[types.Params] - bankKeeper types.BankKeeper // injected at construction + bankKeeper types.BankKeeper authority string } ``` @@ -134,42 +222,54 @@ type Keeper struct { ```go func (k *Keeper) AddCount(ctx context.Context, sender string, amount uint64) (uint64, error) { - // overflow check if amount >= math.MaxUint64 { return 0, ErrNumTooLarge } params, err := k.GetParams(ctx) + if err != nil { + return 0, err + } - // enforce MaxAddValue if params.MaxAddValue > 0 && amount > params.MaxAddValue { return 0, ErrExceedsMaxAdd } - // charge fee if configured if !params.AddCost.IsZero() { - senderAddr, _ := sdk.AccAddressFromBech32(sender) + 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()) } } - // update state - count, _ := k.GetCount(ctx) + count, err := k.GetCount(ctx) + if err != nil { + return 0, err + } + newCount := count + amount - k.counter.Set(ctx, newCount) + if err := k.counter.Set(ctx, newCount); err != nil { + return 0, err + } - // emit event - sdkCtx.EventManager().EmitEvent(sdk.NewEvent("count_increased", - sdk.NewAttribute("count", fmt.Sprintf("%v", newCount)), - )) + 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 } ``` -All the business logic — validation, fee charging, state mutation, events, and telemetry — lives in `AddCount`. The `MsgServer` stays thin: +All the business logic, validation, fee charging, state mutation, events, and telemetry, lives in `AddCount`. The `MsgServer` stays thin: ```go func (m msgServer) Add(ctx context.Context, req *types.MsgAddRequest) (*types.MsgAddResponse, error) { @@ -181,11 +281,13 @@ func (m msgServer) Add(ctx context.Context, req *types.MsgAddRequest) (*types.Ms } ``` -Because `AddCount` is a named keeper method, it can also be called from `BeginBlock`, governance hooks, or other modules — not just from the `MsgServer`. +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 -### Module account +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 the counter module receives fees from users, it needs a module account in the bank module's permission map. This is registered in `app.go`: +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 maccPerms = map[string][]string{ @@ -194,13 +296,16 @@ maccPerms = map[string][]string{ } ``` -`nil` means the module account can receive funds but not mint or burn them. - ---- +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 errors with registered codes: +Rather than returning generic errors, `x/counter` defines named 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 // keeper/errors.go @@ -213,44 +318,70 @@ var ( Registered errors produce structured error responses on-chain that clients can match against by code, not just by string. ---- - ## Telemetry +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 -// keeper/telemetry.go +// x/counter/keeper/telemetry.go var ( - meter = otel.Meter("github.com/cosmos/example/x/counter") + 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 -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 the recommended default for production modules. +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. -The `autocli.go` in `x/counter`: +```bash +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 // autocli.go func (a AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { return &autocliv1.ModuleOptions{ Query: &autocliv1.ServiceCommandDescriptor{ - Service: "example.counter.Query", + 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", + Service: "example.counter.Msg", + EnhanceCustomCommand: true, RpcCommandOptions: []*autocliv1.RpcCommandOptions{ {RpcMethod: "Add", Use: "add [amount]", Short: "Add to the counter", PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "add"}}}, @@ -260,22 +391,40 @@ func (a AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { } ``` ---- ## Simulation +Simulation 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 -// simulation/msg_factory.go +// 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) - addAmount := uint64(testData.Rand().Intn(100) + 1) - return []simsx.SimAccount{sender}, &types.MsgAddRequest{ + 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 } } ``` @@ -288,10 +437,27 @@ func (a AppModule) WeightedOperationsX(weights simsx.WeightSource, reg simsx.Reg } ``` ---- ## BeginBlock and EndBlock +These 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 @@ -306,13 +472,45 @@ func (a AppModule) EndBlock(ctx context.Context) error { } ``` -`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. +In [`app.go`](https://github.com/cosmos/example/blob/main/app.go), the module is added to the blocker order lists like this: ---- +```go +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 -`x/counter` ships a full test suite in `x/counter/keeper/`: +The full module example includes a real test suite 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 +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 | |---|---| @@ -320,7 +518,7 @@ func (a AppModule) EndBlock(ctx context.Context) error { | `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`, which sets up an isolated in-memory store, a mock bank keeper, and a real keeper instance: +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 type KeeperTestSuite struct { @@ -350,60 +548,7 @@ s.bankKeeper.SendCoinsFromAccountToModuleFn = func(...) error { } ``` ---- - -## Additional app.go Wiring - -Tutorial 02 already had you add the basic app wiring for the minimal module: imports, the keeper field, the store key, keeper construction, module registration, and genesis/export ordering. The production module adds a few more `app.go` changes on top of that. - -### Module account permissions - -Because the production module can collect `AddCost` fees, it needs a module account entry in `maccPerms`: - -```go -maccPerms = map[string][]string{ - // ... - countertypes.ModuleName: nil, -} -``` - -This lives in the top-level `maccPerms` map in `app.go`. `nil` means the account can receive funds but does not get mint or burn permissions. - -### Keeper construction with BankKeeper - -The production keeper takes one extra dependency: `app.BankKeeper`. This is added where `app.CounterKeeper` is constructed: - -```go -app.CounterKeeper = counterkeeper.NewKeeper( - runtime.NewKVStoreService(keys[countertypes.StoreKey]), - appCodec, - app.BankKeeper, -) -``` - -`app.BankKeeper` satisfies the `types.BankKeeper` interface defined in `expected_keepers.go`, so the counter module can charge fees without importing the full bank keeper type directly. - -### Begin and end block ordering - -The production `AppModule` implements `BeginBlock` and `EndBlock` in `x/counter/module.go`, even though both methods currently return `nil`. Because the module advertises those hooks, `app.go` also adds `countertypes.ModuleName` to `SetOrderBeginBlockers` and `SetOrderEndBlockers`: - -```go -app.ModuleManager.SetOrderBeginBlockers( - // ... - countertypes.ModuleName, -) - -app.ModuleManager.SetOrderEndBlockers( - // ... - countertypes.ModuleName, -) -``` - -That tells the module manager where the counter module belongs in the per-block execution order. The production branch keeps the same `genesisModuleOrder` and `exportModuleOrder` wiring from Tutorial 02 as well. - ---- - Next: [Running and Testing →](./tutorial-04-run-and-test.md) - \ No newline at end of file + diff --git a/docs/tutorial-04-run-and-test.md b/docs/tutorial-04-run-and-test.md index 8080f00..ff52674 100644 --- a/docs/tutorial-04-run-and-test.md +++ b/docs/tutorial-04-run-and-test.md @@ -1,11 +1,13 @@ # Running and Testing -This page covers how to run the chain locally, use the CLI, and execute the full test suite. +Now that you've [built a module from scratch](./tutorial-02-build-a-module.md) and walked through the [full counter module](./tutorial-03-counter-walkthrough.md), 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 @@ -35,6 +37,8 @@ Re-running `make start` resets state automatically. There is no separate reset c ## Localnet (multi-validator) +Use localnet when you want a setup that is closer to a real network. It runs multiple validators in Docker so you can test multi-node behavior locally. + For a multi-validator setup using Docker: ```bash @@ -54,12 +58,14 @@ make localnet-stop make localnet-clean ``` ---- - ## CLI reference +Once the chain is running, these are the core CLI commands you'll use to inspect state and submit transactions. + ### Query commands +Use query commands to read module state without changing anything on-chain. + ```bash # Query the current counter value exampled query counter count @@ -73,6 +79,8 @@ exampled query counter count --node tcp://localhost:26657 ### Transaction commands +Use transaction commands to submit state-changing messages to the chain. + ```bash # Add to the counter exampled tx counter add 10 --from alice --chain-id demo --yes @@ -86,6 +94,8 @@ exampled tx counter update-params --from alice --chain-id demo --yes ### 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 | @@ -99,6 +109,10 @@ exampled tx counter update-params --from alice --chain-id demo --yes ## Unit tests +Start here when you want fast feedback on module logic without running a chain. These tests isolate the keeper 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 @@ -129,6 +143,10 @@ The test suite is structured around three files: ## E2E tests +Run E2E 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 @@ -141,6 +159,14 @@ E2E tests take longer than unit tests because they spin up a real node. Run them ## Simulation tests +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 @@ -160,6 +186,10 @@ Simulation requires the `sims` build tag, which the Makefile targets handle auto ## 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 make lint ``` @@ -174,6 +204,8 @@ 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 | |---|---| | `go test ./x/counter/...` | Keeper, MsgServer, QueryServer in isolation |