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
This commit is contained in:
Amaury Martiny 2020-09-28 18:51:14 +02:00 committed by GitHub
parent be59020f29
commit e17dd4fd50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 541 additions and 556 deletions

View File

@ -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:
```

View File

@ -1,11 +0,0 @@
<!--
order: 1
-->
# 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)`

View File

@ -1,135 +0,0 @@
<!--
order: 2
-->
# 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
```

View File

@ -1,30 +0,0 @@
<!--
order: 3
-->
# 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)
```

View File

@ -1,29 +0,0 @@
<!--
order: 4
-->
# 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} |

View File

@ -1,25 +0,0 @@
<!--
order: 5
-->
# 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.

View File

@ -1,13 +1,26 @@
<!--
order: 0
title: Bank Overview
parent:
title: "bank"
-->
# `x/bank`
## Abstract
## Table of Contents
<!-- TOC -->
- **[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.

View File

@ -1,62 +0,0 @@
<!--
order: 1
-->
# 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
```

View File

@ -1,16 +0,0 @@
<!--
order: 2
-->
# 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`).

View File

@ -1,46 +0,0 @@
<!--
order: 3
-->
# 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.

View File

@ -1,18 +0,0 @@
<!--
order: 4
-->
# 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 |

View File

@ -1,11 +0,0 @@
<!--
order: 5
-->
# Parameters
The evidence module contains the following parameters:
| Key | Type | Example |
| -------------- | ---------------- | -------------- |
| MaxEvidenceAge | string (time ns) | "120000000000" |

View File

@ -1,100 +0,0 @@
<!--
order: 6
-->
# 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)

View File

@ -1,23 +1,18 @@
<!--
order: 0
title: Evidence Overview
parent:
title: "evidence"
-->
# `evidence`
# `x/evidence`
## Table of Contents
<!-- TOC -->
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)