Invariants

Pre-requisite Readings {hide}

Implementing Invariants

An Invariant is a function that checks for a particular invariant within a module. Module Invariants must follow the Invariants type:

Copypackage types import "fmt" // An Invariant is a function which tests a particular invariant. // The invariant returns a descriptive message about what happened // and a boolean indicating whether the invariant has been broken. // The simulator will then halt and print the logs. type Invariant func(ctx Context) (string, bool) // Invariants defines a group of invariants type Invariants []Invariant // expected interface for registering invariants type InvariantRegistry interface { RegisterRoute(moduleName, route string, invar Invariant) } // FormatInvariant returns a standardized invariant message. func FormatInvariant(module, name, msg string) string { return fmt.Sprintf("%s: %s invariant\n%s\n", module, name, msg) } Expandtypes / invariant.goView source

where the string return value is the invariant message, which can be used when printing logs, and the bool return value is the actual result of the invariant check.

In practice, each module implements Invariants in a ./internal/keeper/invariants.go file within the module's folder. The standard is to implement one Invariant function per logical grouping of invariants with the following model:

Copy// Example for an Invariant that checks balance-related invariants func BalanceInvariants(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { // Implement checks for balance-related invariants } } Expand

Additionally, module developers should generally implement an AllInvariants function that runs all the Invariants functions of the module:

Copy// AllInvariants runs all invariants of the module. // In this example, the module implements two Invariants: BalanceInvariants and DepositsInvariants func AllInvariants(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { res, stop := BalanceInvariants(k)(ctx) if stop { return res, stop } return DepositsInvariant(k)(ctx) } } Expand

Finally, module developers need to implement the RegisterInvariants method as part of the AppModule interface. Indeed, the RegisterInvariants method of the module, implemented in the module.go file, typically only defers the call to a RegisterInvariants method implemented in internal/keeper/invariants.go. The RegisterInvariants method registers a route for each Invariant function in the InvariantRegistry:

Copy// RegisterInvariants registers all staking invariants func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { ir.RegisterRoute(types.ModuleName, "module-accounts", BalanceInvariants(k)) ir.RegisterRoute(types.ModuleName, "nonnegative-power", DepositsInvariant(k)) } Expand

For more, see an example of Invariants implementation from the staking module.

Invariant Registry

The InvariantRegistry is a registry where the Invariants of all the modules of an application are registered. There is only one InvariantRegistry per application, meaning module developers need not implement their own InvariantRegistry when building a module. All module developers need to do is to register their modules' invariants in the InvariantRegistry, as explained in the section above. The rest of this section gives more information on the InvariantRegistry itself, and does not contain anything directly relevant to module developers.

At its core, the InvariantRegistry is defined in the SDK as an interface:

Copy// expected interface for registering invariants type InvariantRegistry interface { RegisterRoute(moduleName, route string, invar Invariant) }Expandtypes / invariant.goView source

Typically, this interface is implemented in the keeper of a specific module. The most used implementation of an InvariantRegistry can be found in the crisis module:

Copy// RegisterRoute register the routes for each of the invariants func (k *Keeper) RegisterRoute(moduleName, route string, invar sdk.Invariant) { invarRoute := types.NewInvarRoute(moduleName, route, invar) k.routes = append(k.routes, invarRoute) }Expandx / crisis / ... / keeper / keeper.goView source

The InvariantRegistry is therefore typically instantiated by instantiating the keeper of the crisis module in the application's constructor function.

Invariants can be checked manually via messages, but most often they are checked automatically at the end of each block. Here is an example from the crisis module:

Copy// check all registered invariants func EndBlocker(ctx sdk.Context, k Keeper) { if k.InvCheckPeriod() == 0 || ctx.BlockHeight()%int64(k.InvCheckPeriod()) != 0 { // skip running the invariant check return } k.AssertInvariants(ctx) }Expandx / crisis / abci.goView source

In both cases, if one of the Invariants returns false, the InvariantRegistry can trigger special logic (e.g. have the application panic and print the Invariants message in the log).

Next {hide}

Learn about genesis functionalities {hide}