From e17dd4fd50085e49ee3ffab796af686549b9756e Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Mon, 28 Sep 2020 18:51:14 +0200 Subject: [PATCH] Update SPEC-SPEC & documentation for x/{bank,evidence} (#7404) * Update documentation for x/bank * Update ModuleAccounts * Put contets on top * Update comments * Do x/evidence * Add ValidatorEvidence * Update SPEC-SPEC * Update anchor --- docs/spec/SPEC-SPEC.md | 41 ++-- x/bank/spec/01_state.md | 11 -- x/bank/spec/02_keepers.md | 135 -------------- x/bank/spec/03_messages.md | 30 --- x/bank/spec/04_events.md | 29 --- x/bank/spec/05_params.md | 25 --- x/bank/spec/README.md | 298 ++++++++++++++++++++++++++---- x/evidence/spec/01_concepts.md | 62 ------- x/evidence/spec/02_state.md | 16 -- x/evidence/spec/03_messages.md | 46 ----- x/evidence/spec/04_events.md | 18 -- x/evidence/spec/05_params.md | 11 -- x/evidence/spec/06_begin_block.md | 100 ---------- x/evidence/spec/README.md | 275 +++++++++++++++++++++++++-- 14 files changed, 541 insertions(+), 556 deletions(-) delete mode 100644 x/bank/spec/01_state.md delete mode 100644 x/bank/spec/02_keepers.md delete mode 100644 x/bank/spec/03_messages.md delete mode 100644 x/bank/spec/04_events.md delete mode 100644 x/bank/spec/05_params.md delete mode 100644 x/evidence/spec/01_concepts.md delete mode 100644 x/evidence/spec/02_state.md delete mode 100644 x/evidence/spec/03_messages.md delete mode 100644 x/evidence/spec/04_events.md delete mode 100644 x/evidence/spec/05_params.md delete mode 100644 x/evidence/spec/06_begin_block.md diff --git a/docs/spec/SPEC-SPEC.md b/docs/spec/SPEC-SPEC.md index fb95c3ab96..bdbf4d53b7 100644 --- a/docs/spec/SPEC-SPEC.md +++ b/docs/spec/SPEC-SPEC.md @@ -19,28 +19,33 @@ element as a part of a larger description. ## Common Layout -The following generalized file structure should be used to breakdown -specifications for modules. With the exception of README.md, `XX` at the -beginning of the file name should be replaced with a number to indicate -document flow (ex. read `01_state.md` before `02_state_transitions.md`). The -following list is nonbinding and all files are optional. +The specifications should be contained in a single `README.md` file inside the +`spec/` folder of a given module. -- `README.md` - overview of the module -- `XX_concepts.md` - describe specialized concepts and definitions used throughout the spec -- `XX_state.md` - specify and describe structures expected to marshalled into the store, and their keys -- `XX_state_transitions.md` - standard state transition operations triggered by hooks, messages, etc. -- `XX_messages.md` - specify message structure(s) and expected state machine behaviour(s) -- `XX_begin_block.md` - specify any begin-block operations -- `XX_end_block.md` - specify any end-block operations -- `XX_hooks.md` - describe available hooks to be called by/from this module -- `XX_events.md` - list and describe event tags used -- `XX_params.md` - list all module parameters, their types (in JSON) and examples -- `XX_future_improvements.md` - describe future improvements of this module -- `XX_appendix.md` - supplementary details referenced elsewhere within the spec +The following generalized document structure should be used to breakdown +specifications for modules. Each bullet item corresponds to a new section in +the document, and should begin with a secondary heading (`## {HEADING}` in +Markdown). The `XX` at the beginning of the section name should be replaced +with a number to indicate document flow (ex. read `01. Concepts` before +`02. State Transitions`). The following list is nonbinding and all sections are +optional. + +- `XX. Abstract` - overview of the module +- `XX. Concepts` - describe specialized concepts and definitions used throughout the spec +- `XX. State` - specify and describe structures expected to marshalled into the store, and their keys +- `XX. State Transitions` - standard state transition operations triggered by hooks, messages, etc. +- `XX. Messages` - specify message structure(s) and expected state machine behaviour(s) +- `XX. BeginBlock` - specify any begin-block operations +- `XX. EndBlock` - specify any end-block operations +- `XX. Hooks` - describe available hooks to be called by/from this module +- `XX. Events` - list and describe event tags used +- `XX. Params` - list all module parameters, their types (in JSON) and examples +- `XX. Future Improvements` - describe future improvements of this module +- `XX. Appendix` - supplementary details referenced elsewhere within the spec ### Notation for key-value mapping -Within `state.md` the following notation `->` should be used to describe key to +Within the `State` section, the following notation `->` should be used to describe key to value mapping: ``` diff --git a/x/bank/spec/01_state.md b/x/bank/spec/01_state.md deleted file mode 100644 index f744e2e779..0000000000 --- a/x/bank/spec/01_state.md +++ /dev/null @@ -1,11 +0,0 @@ - - -# State - -The `x/bank` module keeps state of two primary objects, account balances and the -total supply of all balances. - -- Balances: `[]byte("balances") | []byte(address) / []byte(balance.Denom) -> ProtocolBuffer(balance)` -- Supply: `0x0 -> ProtocolBuffer(Supply)` diff --git a/x/bank/spec/02_keepers.md b/x/bank/spec/02_keepers.md deleted file mode 100644 index d20b42fe3d..0000000000 --- a/x/bank/spec/02_keepers.md +++ /dev/null @@ -1,135 +0,0 @@ - - -# Keepers - -The bank module provides three different exported keeper interfaces which can be passed to other modules which need to read or update account balances. Modules should use the least-permissive interface which provides the functionality they require. - -Note that you should always review the `bank` module code to ensure that permissions are limited in the way that you expect. - -## Common Types - -### Input - -An input of a multiparty transfer - -```go -type Input struct { - Address AccAddress - Coins Coins -} -``` - -### Output - -An output of a multiparty transfer. - -```go -type Output struct { - Address AccAddress - Coins Coins -} -``` - -## BaseKeeper - -The base keeper provides full-permission access: the ability to arbitrary modify any account's balance and mint or burn coins. - -```go -type BaseKeeper interface { - SetCoins(addr AccAddress, amt Coins) - SubtractCoins(addr AccAddress, amt Coins) - AddCoins(addr AccAddress, amt Coins) - InputOutputCoins(inputs []Input, outputs []Output) -} -``` - -`setCoins` fetches an account by address, sets the coins on the account, and saves the account. - -``` -setCoins(addr AccAddress, amt Coins) - account = accountKeeper.getAccount(addr) - if account == nil - fail with "no account found" - account.Coins = amt - accountKeeper.setAccount(account) -``` - -`subtractCoins` fetches the coins of an account, subtracts the provided amount, and saves the account. This decreases the total supply. - -``` -subtractCoins(addr AccAddress, amt Coins) - oldCoins = getCoins(addr) - newCoins = oldCoins - amt - if newCoins < 0 - fail with "cannot end up with negative coins" - setCoins(addr, newCoins) -``` - -`addCoins` fetches the coins of an account, adds the provided amount, and saves the account. This increases the total supply. - -``` -addCoins(addr AccAddress, amt Coins) - oldCoins = getCoins(addr) - newCoins = oldCoins + amt - setCoins(addr, newCoins) -``` - -`inputOutputCoins` transfers coins from any number of input accounts to any number of output accounts. - -``` -inputOutputCoins(inputs []Input, outputs []Output) - for input in inputs - subtractCoins(input.Address, input.Coins) - for output in outputs - addCoins(output.Address, output.Coins) -``` - -## SendKeeper - -The send keeper provides access to account balances and the ability to transfer coins between accounts, but not to alter the total supply (mint or burn coins). - -```go -type SendKeeper interface { - SendCoins(from AccAddress, to AccAddress, amt Coins) -} -``` - -`sendCoins` transfers coins from one account to another. - -``` -sendCoins(from AccAddress, to AccAddress, amt Coins) - subtractCoins(from, amt) - addCoins(to, amt) -``` - -## ViewKeeper - -The view keeper provides read-only access to account balances but no balance alteration functionality. All balance lookups are `O(1)`. - -```go -type ViewKeeper interface { - GetCoins(addr AccAddress) Coins - HasCoins(addr AccAddress, amt Coins) bool -} -``` - -`getCoins` returns the coins associated with an account. - -``` -getCoins(addr AccAddress) - account = accountKeeper.getAccount(addr) - if account == nil - return Coins{} - return account.Coins -``` - -`hasCoins` returns whether or not an account has at least the provided amount of coins. - -``` -hasCoins(addr AccAddress, amt Coins) - account = accountKeeper.getAccount(addr) - coins = getCoins(addr) - return coins >= amt -``` diff --git a/x/bank/spec/03_messages.md b/x/bank/spec/03_messages.md deleted file mode 100644 index f9cde52283..0000000000 --- a/x/bank/spec/03_messages.md +++ /dev/null @@ -1,30 +0,0 @@ - - -# Messages - -## MsgSend - -```go -type MsgSend struct { - Inputs []Input - Outputs []Output -} -``` - -`handleMsgSend` just runs `inputOutputCoins`. - -``` -handleMsgSend(msg MsgSend) - inputSum = 0 - for input in inputs - inputSum += input.Amount - outputSum = 0 - for output in outputs - outputSum += output.Amount - if inputSum != outputSum: - fail with "input/output amount mismatch" - - return inputOutputCoins(msg.Inputs, msg.Outputs) -``` diff --git a/x/bank/spec/04_events.md b/x/bank/spec/04_events.md deleted file mode 100644 index 1f97e7ab9b..0000000000 --- a/x/bank/spec/04_events.md +++ /dev/null @@ -1,29 +0,0 @@ - - -# Events - -The bank module emits the following events: - -## Handlers - -### 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} | diff --git a/x/bank/spec/05_params.md b/x/bank/spec/05_params.md deleted file mode 100644 index c4e89a4200..0000000000 --- a/x/bank/spec/05_params.md +++ /dev/null @@ -1,25 +0,0 @@ - - -# Parameters - -The bank module contains the following parameters: - -| Key | Type | Example | -|--------------------|---------------|------------------------------------| -| SendEnabled | []SendEnabled | [{denom: "stake", enabled: true }] | -| DefaultSendEnabled | bool | true | - - -## SendEnabled - -The send enabled parameter is an array of SendEnabled entries mapping coin -denominations to their send_enabled status. Entries in this list take -precedence over the `DefaultSendEnabled` setting. - -## DefaultSendEnabled - -The default send enabled value controls send transfer capability for all -coin denominations unless specifically included in the array of `SendEnabled` -parameters. \ No newline at end of file diff --git a/x/bank/spec/README.md b/x/bank/spec/README.md index 271581cf68..254488945c 100644 --- a/x/bank/spec/README.md +++ b/x/bank/spec/README.md @@ -1,13 +1,26 @@ - - # `x/bank` -## Abstract +## Table of Contents + + + +- **[01. Abstract](#01-abstract)** +- **[02. Concepts](#02-concepts)** + - [Supply](#supply) + - [Module Accounts](#module-accounts) +- **[03. State](#03-state)** +- **[04. Keepers](#04-keepers)** + - [Common Types](#common-types) + - [BaseKeeper](#basekeeper) + - [SendKeeper](#sendkeeper) + - [ViewKeeper](#viewkeeper) +- **[05. Messages](#05-messages)** + - [MsgSend](#msgsend) +- **[06. Events](#06-events)** + - [Handlers](#handlers) +- **[07. Parameters](#07-parameters)** + +## 01. Abstract This document specifies the bank module of the Cosmos SDK. @@ -22,7 +35,9 @@ supply of all assets used in the application. This module will be used in the Cosmos Hub. -## Supply +## 02. Concepts + +### Supply The `supply` module: @@ -30,55 +45,57 @@ The `supply` module: - provides a pattern for modules to hold/interact with `Coins`, and - introduces the invariant check to verify a chain's total supply. -### 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 +### Module Accounts -The supply module 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 +The supply module introduces a new type of `auth.AccountI` interface, called `ModuleAccountI`, 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 +`auth.AccountI` interfaces 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: +The `ModuleAccountI` interface is defined as follows: ```go -type ModuleAccount interface { - auth.Account // same methods as the Account interface +// ModuleAccountI defines an account interface for modules that hold tokens in +// an escrow. +type ModuleAccountI interface { + AccountI // 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 + 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). +> 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 +and the bank `Keeper` that are related to `ModuleAccountI`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 +- Get and set `ModuleAccountI`s by providing the `Name`. +- Send coins from and to other `ModuleAccountI`s or standard `Account`s (`BaseAccount` or `VestingAccount`) by passing only the `Name`. -- `Mint` or `Burn` coins for a `ModuleAccount` (restricted to its permissions). +- `Mint` or `Burn` coins for a `ModuleAccountI` (restricted to its permissions). -### Permissions +#### Permissions -Each `ModuleAccount` has a different set of permissions that provide different +Each `ModuleAccountI` 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 +`ModuleAccountI` calls the allowed functions, the `Keeper` can lookup the permissions to that specific account and perform or not the action. The available permissions are: @@ -87,16 +104,217 @@ The available permissions are: - `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. -## Contents +## 03. State -1. **[State](01_state.md)** -2. **[Keepers](02_keepers.md)** - - [Common Types](02_keepers.md#common-types) - - [BaseKeeper](02_keepers.md#basekeeper) - - [SendKeeper](02_keepers.md#sendkeeper) - - [ViewKeeper](02_keepers.md#viewkeeper) -3. **[Messages](03_messages.md)** - - [MsgSend](03_messages.md#msgsend) -4. **[Events](04_events.md)** - - [Handlers](04_events.md#handlers) -5. **[Parameters](05_params.md)** +The `x/bank` module keeps state of two primary objects, account balances and the +total supply of all balances. + +- Balances: `[]byte("balances") | []byte(address) / []byte(balance.Denom) -> ProtocolBuffer(balance)` +- Supply: `0x0 -> ProtocolBuffer(Supply)` + +## 04. Keepers + +The bank module provides three different exported keeper interfaces which can be passed to other modules which need to read or update account balances. Modules should use the least-permissive interface which provides the functionality they require. + +Note that you should always review the `bank` module code to ensure that permissions are limited in the way that you expect. + +### Common Types + +#### Input + +An input of a multiparty transfer + +```protobuf +// Input models transaction input. +message Input { + string address = 1; + repeated cosmos.base.v1beta1.Coin coins = 2; +} +``` + +#### Output + +An output of a multiparty transfer. + +```protobuf +// 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. The `BaseKeeper` struct implements the following `Keeper` interface. + +```go +// Keeper defines a module interface that facilitates the transfer of coins +// between accounts. +type Keeper interface { + SendKeeper + + InitGenesis(sdk.Context, types.GenesisState) + ExportGenesis(sdk.Context) *types.GenesisState + + GetSupply(ctx sdk.Context) exported.SupplyI + SetSupply(ctx sdk.Context, supply exported.SupplyI) + + GetDenomMetaData(ctx sdk.Context, denom string) types.Metadata + SetDenomMetaData(ctx sdk.Context, denomMetaData types.Metadata) + IterateAllDenomMetaData(ctx sdk.Context, cb func(types.Metadata) bool) + + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + + DelegateCoins(ctx sdk.Context, delegatorAddr, moduleAccAddr sdk.AccAddress, amt sdk.Coins) error + UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error + MarshalSupply(supplyI exported.SupplyI) ([]byte, error) + UnmarshalSupply(bz []byte) (exported.SupplyI, error) + + types.QueryServer +} +``` + +### SendKeeper + +The send keeper provides access to account balances and the ability to transfer coins between accounts, but not to alter the total supply (mint or burn coins). + +```go +// SendKeeper defines a module interface that facilitates the transfer of coins +// between accounts without the possibility of creating coins. +type SendKeeper interface { + ViewKeeper + + InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + + SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error + AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error + + SetBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error + SetBalances(ctx sdk.Context, addr sdk.AccAddress, balances sdk.Coins) error + + GetParams(ctx sdk.Context) types.Params + SetParams(ctx sdk.Context, params types.Params) + + SendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool + SendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error + + BlockedAddr(addr sdk.AccAddress) bool +} +``` + +### ViewKeeper + +The view keeper provides read-only access to account balances but no balance alteration functionality. All balance lookups are `O(1)`. + +```go +// ViewKeeper defines a module interface that facilitates read only access to +// account balances. +type ViewKeeper interface { + ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) error + HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool + + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetAccountsBalances(ctx sdk.Context) []types.Balance + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + + IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool)) + IterateAllBalances(ctx sdk.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool)) +} +``` + +## 05. Messages + +### MsgSend + +```protobuf +// MsgSend represents a message to send coins from one account to another. +message MsgSend { + string from_address = 1; + string to_address = 2; + repeated cosmos.base.v1beta1.Coin amount = 3; +} +``` + +`handleMsgSend` just runs `inputOutputCoins`. + +``` +handleMsgSend(msg MsgSend) + inputSum = 0 + for input in inputs + inputSum += input.Amount + outputSum = 0 + for output in outputs + outputSum += output.Amount + if inputSum != outputSum: + fail with "input/output amount mismatch" + + return inputOutputCoins(msg.Inputs, msg.Outputs) +``` + +## 06. Events + +The bank module emits the following events: + +### Handlers + +#### 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} | + +## 07. Parameters + +The bank module contains the following parameters: + +| Key | Type | Example | +| ------------------ | ------------- | ---------------------------------- | +| SendEnabled | []SendEnabled | [{denom: "stake", enabled: true }] | +| DefaultSendEnabled | bool | true | + +The corresponding Protobuf message is: + +```protobuf +// Params defines the parameters for the bank module. +message Params { + option = false; + repeated SendEnabled send_enabled = 1; + bool default_send_enabled = 2; +} +``` + +### SendEnabled + +The send enabled parameter is an array of SendEnabled entries mapping coin +denominations to their send_enabled status. Entries in this list take +precedence over the `DefaultSendEnabled` setting. + +### DefaultSendEnabled + +The default send enabled value controls send transfer capability for all +coin denominations unless specifically included in the array of `SendEnabled` +parameters. diff --git a/x/evidence/spec/01_concepts.md b/x/evidence/spec/01_concepts.md deleted file mode 100644 index 3b50ccecfb..0000000000 --- a/x/evidence/spec/01_concepts.md +++ /dev/null @@ -1,62 +0,0 @@ - - -# 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. - -```go -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 -} -``` - -## 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 -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. - -```go -type Handler func(Context, Evidence) error -``` diff --git a/x/evidence/spec/02_state.md b/x/evidence/spec/02_state.md deleted file mode 100644 index f65876837a..0000000000 --- a/x/evidence/spec/02_state.md +++ /dev/null @@ -1,16 +0,0 @@ - - -# 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`. - -```go -type GenesisState struct { - Evidence []Evidence `json:"evidence" yaml:"evidence"` -} -``` - -All `Evidence` is retrieved and stored via a prefix `KVStore` using prefix `0x00` (`KeyPrefixEvidence`). diff --git a/x/evidence/spec/03_messages.md b/x/evidence/spec/03_messages.md deleted file mode 100644 index a39052dab9..0000000000 --- a/x/evidence/spec/03_messages.md +++ /dev/null @@ -1,46 +0,0 @@ - - -# Messages - -## MsgSubmitEvidence - -Evidence is submitted through a `MsgSubmitEvidence` message: - -```go -type MsgSubmitEvidence struct { - Evidence Evidence - Submitter AccAddress -} -``` - -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 -func SubmitEvidence(ctx Context, evidence Evidence) error { - if _, ok := GetEvidence(ctx, evidence.Hash()); ok { - return ErrEvidenceExists(codespace, evidence.Hash().String()) - } - if !router.HasRoute(evidence.Route()) { - return ErrNoEvidenceHandlerExists(codespace, evidence.Route()) - } - - handler := router.GetRoute(evidence.Route()) - if err := handler(ctx, evidence); err != nil { - return ErrInvalidEvidence(codespace, err.Error()) - } - - 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`, it is persisted to state. diff --git a/x/evidence/spec/04_events.md b/x/evidence/spec/04_events.md deleted file mode 100644 index 35fd77b3f5..0000000000 --- a/x/evidence/spec/04_events.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# 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 | diff --git a/x/evidence/spec/05_params.md b/x/evidence/spec/05_params.md deleted file mode 100644 index 15a444ecde..0000000000 --- a/x/evidence/spec/05_params.md +++ /dev/null @@ -1,11 +0,0 @@ - - -# Parameters - -The evidence module contains the following parameters: - -| Key | Type | Example | -| -------------- | ---------------- | -------------- | -| MaxEvidenceAge | string (time ns) | "120000000000" | diff --git a/x/evidence/spec/06_begin_block.md b/x/evidence/spec/06_begin_block.md deleted file mode 100644 index e90543862e..0000000000 --- a/x/evidence/spec/06_begin_block.md +++ /dev/null @@ -1,100 +0,0 @@ - - -# BeginBlock - -## Evidence Handling - -Tendermint blocks can include -[Evidence](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#evidence), -which indicates that a validator committed malicious behavior. The relevant information is -forwarded to the application as ABCI Evidence in `abci.RequestBeginBlock` so that -the validator an be accordingly punished. - -### Equivocation - -Currently, the evidence module only handles evidence of type `Equivocation` which is derived from -Tendermint's `ABCIEvidenceTypeDuplicateVote` during `BeginBlock`. - -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` and -`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`, which is 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 which 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 making it impossible for that -validator to ever re-enter the validator set. - -The `Equivocation` evidence is handled as follows: - -```go -func (k Keeper) HandleDoubleSign(ctx Context, evidence Equivocation) { - consAddr := evidence.GetConsensusAddress() - infractionHeight := evidence.GetHeight() - - // calculate the age of the evidence - blockTime := ctx.BlockHeader().Time - age := blockTime.Sub(evidence.GetTime()) - - // reject evidence we cannot handle - if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil { - return - } - - // reject evidence if it is too old - if age > k.MaxEvidenceAge(ctx) { - return - } - - // reject evidence if the validator is already unbonded - validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr) - if validator == nil || validator.IsUnbonded() { - return - } - - // verify the validator has signing info in order to be slashed and tombstoned - if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok { - panic(...) - } - - // reject evidence if the validator is already tombstoned - if k.slashingKeeper.IsTombstoned(ctx, consAddr) { - return - } - - // We need to retrieve the stake distribution which signed the block, so we - // subtract ValidatorUpdateDelay from the evidence height. - // Note, that this *can* result in a negative "distributionHeight", up to - // -ValidatorUpdateDelay, 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 := infractionHeight - sdk.ValidatorUpdateDelay - - // Slash validator. The `power` is the int64 power of the validator as provided - // to/by Tendermint. This value is validator.Tokens as sent to Tendermint via - // ABCI, and now received as evidence. The fraction is passed in to separately - // to slash unbonding and rebonding delegations. - k.slashingKeeper.Slash(ctx, consAddr, evidence.GetValidatorPower(), distributionHeight) - - // Jail the validator if not already jailed. This will begin unbonding the - // validator if not already unbonding (tombstoned). - if !validator.IsJailed() { - k.slashingKeeper.Jail(ctx, consAddr) - } - - k.slashingKeeper.JailUntil(ctx, consAddr, types.DoubleSignJailEndTime) - k.slashingKeeper.Tombstone(ctx, consAddr) -} -``` - -Note, the slashing, jailing, and tombstoning calls are delegated through the `x/slashing` module -which emit informative events and finally delegate calls to the `x/staking` module. Documentation -on slashing and jailing can be found in the [x/staking spec](/.././cosmos-sdk/x/staking/spec/02_state_transitions.md) diff --git a/x/evidence/spec/README.md b/x/evidence/spec/README.md index ff0754133c..b94a73ffd1 100644 --- a/x/evidence/spec/README.md +++ b/x/evidence/spec/README.md @@ -1,23 +1,18 @@ - - -# `evidence` +# `x/evidence` ## Table of Contents -1. **[Concepts](01_concepts.md)** -2. **[State](02_state.md)** -3. **[Messages](03_messages.md)** -4. **[Events](04_events.md)** -5. **[Params](05_params.md)** -6. **[BeginBlock](06_begin_block.md)** -## Abstract +- **[01. Abstract](#01-abstract)** +- **[02. Concepts](#02-concepts)** +- **[03. State](#03-state)** +- **[04. Messages](#04-messages)** +- **[05. Events](#05-events)** +- **[06. Parameters](#06-parameters)** +- **[07. BeginBlock](#07-beginblock)** + +## 01. Abstract `x/evidence` is an implementation of a Cosmos SDK module, per [ADR 009](./../../../docs/architecture/adr-009-evidence-module.md), that allows for the submission and handling of arbitrary evidence of misbehavior such @@ -37,3 +32,253 @@ 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. + +## 02. 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 +// Evidence defines the contract which concrete evidence types of misbehavior +// must implement. +type Evidence interface { + Route() string + Type() string + String() string + Hash() tmbytes.HexBytes + 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 +// Router defines a contract for which any Evidence handling module must +// implement in order to route Evidence to registered Handlers. +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. + +```go +// 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, Evidence) error +``` + +## 03. 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 +// 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`). + +## 04. Messages + +### MsgSubmitEvidence + +Evidence is submitted through a `MsgSubmitEvidence` message: + +```protobuf +// 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 +func SubmitEvidence(ctx Context, evidence Evidence) error { + if _, ok := GetEvidence(ctx, evidence.Hash()); ok { + return ErrEvidenceExists(codespace, evidence.Hash().String()) + } + if !router.HasRoute(evidence.Route()) { + return ErrNoEvidenceHandlerExists(codespace, evidence.Route()) + } + + handler := router.GetRoute(evidence.Route()) + if err := handler(ctx, evidence); err != nil { + return ErrInvalidEvidence(codespace, err.Error()) + } + + 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`, it is persisted to state. + +## 05. 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 | + +## 06. Parameters + +The evidence module does not have any parameters. + +## 07. BeginBlock + +### Evidence Handling + +Tendermint blocks can include +[Evidence](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#evidence), +which indicates that a validator committed malicious behavior. The relevant information is +forwarded to the application as ABCI Evidence in `abci.RequestBeginBlock` so that +the validator an be accordingly punished. + +#### Equivocation + +Currently, the evidence module only handles evidence of type `Equivocation` which is derived from +Tendermint's `ABCIEvidenceTypeDuplicateVote` during `BeginBlock`. + +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` and +`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`, which is 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 which 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 making it impossible for that +validator to ever re-enter the validator set. + +The `Equivocation` evidence is handled as follows: + +```go +func (k Keeper) HandleDoubleSign(ctx Context, evidence Equivocation) { + consAddr := evidence.GetConsensusAddress() + infractionHeight := evidence.GetHeight() + + // calculate the age of the evidence + blockTime := ctx.BlockHeader().Time + age := blockTime.Sub(evidence.GetTime()) + + // reject evidence we cannot handle + if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil { + return + } + + // reject evidence if it is too old + if age > k.MaxEvidenceAge(ctx) { + return + } + + // reject evidence if the validator is already unbonded + validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr) + if validator == nil || validator.IsUnbonded() { + return + } + + // verify the validator has signing info in order to be slashed and tombstoned + if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok { + panic(...) + } + + // reject evidence if the validator is already tombstoned + if k.slashingKeeper.IsTombstoned(ctx, consAddr) { + return + } + + // We need to retrieve the stake distribution which signed the block, so we + // subtract ValidatorUpdateDelay from the evidence height. + // Note, that this *can* result in a negative "distributionHeight", up to + // -ValidatorUpdateDelay, 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 := infractionHeight - sdk.ValidatorUpdateDelay + + // Slash validator. The `power` is the int64 power of the validator as provided + // to/by Tendermint. This value is validator.Tokens as sent to Tendermint via + // ABCI, and now received as evidence. The fraction is passed in to separately + // to slash unbonding and rebonding delegations. + k.slashingKeeper.Slash(ctx, consAddr, evidence.GetValidatorPower(), distributionHeight) + + // Jail the validator if not already jailed. This will begin unbonding the + // validator if not already unbonding (tombstoned). + if !validator.IsJailed() { + k.slashingKeeper.Jail(ctx, consAddr) + } + + k.slashingKeeper.JailUntil(ctx, consAddr, types.DoubleSignJailEndTime) + k.slashingKeeper.Tombstone(ctx, consAddr) +} +``` + +Note, the slashing, jailing, and tombstoning calls are delegated through the `x/slashing` module +which emit informative events and finally delegate calls to the `x/staking` module. Documentation +on slashing and jailing can be found in the [x/staking spec](/.././cosmos-sdk/x/staking/spec/02_state_transitions.md)