diff --git a/docs/spec/SPEC-SPEC.md b/docs/spec/SPEC-SPEC.md
index bdbf4d53b718..fb95c3ab96ef 100644
--- a/docs/spec/SPEC-SPEC.md
+++ b/docs/spec/SPEC-SPEC.md
@@ -19,33 +19,28 @@ element as a part of a larger description.
## Common Layout
-The specifications should be contained in a single `README.md` file inside the
-`spec/` folder of a given module.
-
-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
+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.
+
+- `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
### Notation for key-value mapping
-Within the `State` section, the following notation `->` should be used to describe key to
+Within `state.md` the following notation `->` should be used to describe key to
value mapping:
```
diff --git a/x/auth/keeper/account.go b/x/auth/keeper/account.go
index e39c98ac52e7..9921bb96f945 100644
--- a/x/auth/keeper/account.go
+++ b/x/auth/keeper/account.go
@@ -5,7 +5,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/types"
)
-// NewAccountWithAddress implements sdk.AccountKeeper.
+// NewAccountWithAddress implements AccountKeeperI.
func (ak AccountKeeper) NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) types.AccountI {
acc := ak.proto()
err := acc.SetAddress(addr)
@@ -25,7 +25,7 @@ func (ak AccountKeeper) NewAccount(ctx sdk.Context, acc types.AccountI) types.Ac
return acc
}
-// GetAccount implements sdk.AccountKeeper.
+// GetAccount implements AccountKeeperI.
func (ak AccountKeeper) GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI {
store := ctx.KVStore(ak.key)
bz := store.Get(types.AddressStoreKey(addr))
@@ -46,7 +46,7 @@ func (ak AccountKeeper) GetAllAccounts(ctx sdk.Context) (accounts []types.Accoun
return accounts
}
-// SetAccount implements sdk.AccountKeeper.
+// SetAccount implements AccountKeeperI.
func (ak AccountKeeper) SetAccount(ctx sdk.Context, acc types.AccountI) {
addr := acc.GetAddress()
store := ctx.KVStore(ak.key)
diff --git a/x/auth/keeper/keeper.go b/x/auth/keeper/keeper.go
index eaefead6c2ea..86189b62a24d 100644
--- a/x/auth/keeper/keeper.go
+++ b/x/auth/keeper/keeper.go
@@ -14,6 +14,36 @@ import (
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
)
+// AccountKeeperI is the interface contract that x/auth's keeper implements.
+type AccountKeeperI interface {
+ // Return a new account with the next account number and the specified address. Does not save the new account to the store.
+ NewAccountWithAddress(sdk.Context, sdk.AccAddress) types.AccountI
+
+ // Return a new account with the next account number. Does not save the new account to the store.
+ NewAccount(sdk.Context, types.AccountI) types.AccountI
+
+ // Retrieve an account from the store.
+ GetAccount(sdk.Context, sdk.AccAddress) types.AccountI
+
+ // Set an account in the store.
+ SetAccount(sdk.Context, types.AccountI)
+
+ // Remove an account from the store.
+ RemoveAccount(sdk.Context, types.AccountI)
+
+ // Iterate over all accounts, calling the provided function. Stop iteraiton when it returns false.
+ IterateAccounts(sdk.Context, func(types.AccountI) bool)
+
+ // Fetch the public key of an account at a specified address
+ GetPubKey(sdk.Context, sdk.AccAddress) (crypto.PubKey, error)
+
+ // Fetch the sequence of an account at a specified address.
+ GetSequence(sdk.Context, sdk.AccAddress) (uint64, error)
+
+ // Fetch the next account number, and increment the internal counter.
+ GetNextAccountNumber(sdk.Context) uint64
+}
+
// AccountKeeper encodes/decodes accounts using the go-amino (binary)
// encoding/decoding library.
type AccountKeeper struct {
@@ -26,7 +56,9 @@ type AccountKeeper struct {
proto func() types.AccountI
}
-// NewAccountKeeper returns a new sdk.AccountKeeper that uses go-amino to
+var _ AccountKeeperI = &AccountKeeper{}
+
+// NewAccountKeeper returns a new AccountKeeperI that uses go-amino to
// (binary) encode and decode concrete sdk.Accounts.
func NewAccountKeeper(
cdc codec.BinaryMarshaler, key sdk.StoreKey, paramstore paramtypes.Subspace, proto func() types.AccountI,
diff --git a/x/auth/spec/02_state.md b/x/auth/spec/02_state.md
index 37afc5e03d0c..b9008603d158 100644
--- a/x/auth/spec/02_state.md
+++ b/x/auth/spec/02_state.md
@@ -15,30 +15,39 @@ Accounts are exposed externally as an interface, and stored internally as
either a base account or vesting account. Module clients wishing to add more
account types may do so.
-- `0x01 | Address -> amino(account)`
+- `0x01 | Address -> ProtocolBuffer(account)`
### Account Interface
The account interface exposes methods to read and write standard account information.
-Note that all of these methods operate on an account struct confirming to the interface
-- in order to write the account to the store, the account keeper will need to be used.
+Note that all of these methods operate on an account struct confirming to the
+interface - in order to write the account to the store, the account keeper will
+need to be used.
```go
-type Account interface {
- GetAddress() AccAddress
- SetAddress(AccAddress)
+// AccountI is an interface used to store coins at a given address within state.
+// It presumes a notion of sequence numbers for replay protection,
+// a notion of account numbers for replay protection for previously pruned accounts,
+// and a pubkey for authentication purposes.
+//
+// Many complex conditions can be used in the concrete struct which implements AccountI.
+type AccountI interface {
+ proto.Message
- GetPubKey() PubKey
- SetPubKey(PubKey)
+ GetAddress() sdk.AccAddress
+ SetAddress(sdk.AccAddress) error // errors if already set.
- GetAccountNumber() uint64
- SetAccountNumber(uint64)
+ GetPubKey() crypto.PubKey // can return nil.
+ SetPubKey(crypto.PubKey) error
- GetSequence() uint64
- SetSequence(uint64)
+ GetAccountNumber() uint64
+ SetAccountNumber(uint64) error
- GetCoins() Coins
- SetCoins(Coins)
+ GetSequence() uint64
+ SetSequence(uint64) error
+
+ // Ensure that account implements stringer
+ String() string
}
```
@@ -47,13 +56,15 @@ type Account interface {
A base account is the simplest and most common account type, which just stores all requisite
fields directly in a struct.
-```go
-type BaseAccount struct {
- Address AccAddress
- Coins Coins
- PubKey PubKey
- AccountNumber uint64
- Sequence uint64
+```protobuf
+// BaseAccount defines a base account type. It contains all the necessary fields
+// for basic account functionality. Any custom account type should extend this
+// type for additional functionality (e.g. vesting).
+message BaseAccount {
+ string address = 1;
+ google.protobuf.Any pub_key = 2;
+ uint64 account_number = 3;
+ uint64 sequence = 4;
}
```
diff --git a/x/auth/spec/03_messages.md b/x/auth/spec/03_antehandlers.md
similarity index 90%
rename from x/auth/spec/03_messages.md
rename to x/auth/spec/03_antehandlers.md
index 37834d8d1627..709cae3789f4 100644
--- a/x/auth/spec/03_messages.md
+++ b/x/auth/spec/03_antehandlers.md
@@ -2,16 +2,14 @@
order: 3
-->
-# Messages
-
-TODO make this file conform to typical messages spec
+# AnthHandlers
## Handlers
The auth module presently has no transaction handlers of its own, but does expose
the special `AnteHandler`, used for performing basic validity checks on a transaction,
such that it could be thrown out of the mempool. Note that the ante handler is called on
-`CheckTx`, but *also* on `DeliverTx`, as Tendermint proposers presently have the ability
+`CheckTx`, but _also_ on `DeliverTx`, as Tendermint proposers presently have the ability
to include in their proposed block transactions which fail `CheckTx`.
### Ante Handler
diff --git a/x/auth/spec/03_types.md b/x/auth/spec/03_types.md
deleted file mode 100644
index 5e768fc3b828..000000000000
--- a/x/auth/spec/03_types.md
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-# Types
-
-Besides accounts (specified in [State](02_state.md)), the types exposed by the auth module
-are `StdFee`, the combination of an amount and gas limit, `StdSignature`, the combination
-of an optional public key and a cryptographic signature as a byte array, `StdTx`,
-a struct which implements the `sdk.Tx` interface using `StdFee` and `StdSignature`, and
-`StdSignDoc`, a replay-prevention structure for `StdTx` which transaction senders must sign over.
-
-## StdFee
-
-A `StdFee` is simply the combination of a fee amount, in any number of denominations,
-and a gas limit (where dividing the amount by the gas limit gives a "gas price").
-
-```go
-type StdFee struct {
- Amount Coins
- Gas uint64
-}
-```
-
-## StdSignature
-
-A `StdSignature` is the combination of an optional public key and a cryptographic signature
-as a byte array. The SDK is agnostic to particular key or signature formats and supports any
-supported by the `PubKey` interface.
-
-```go
-type StdSignature struct {
- PubKey PubKey
- Signature []byte
-}
-```
-
-## StdTx
-
-A `StdTx` is a struct which implements the `sdk.Tx` interface, and is likely to be generic
-enough to serve the purposes of many Cosmos SDK blockchains.
-
-```go
-type StdTx struct {
- Msgs []sdk.Msg
- Fee StdFee
- Signatures []StdSignature
- Memo string
-}
-```
-
-## StdSignDoc
-
-A `StdSignDoc` is a replay-prevention structure to be signed over, which ensures that
-any submitted transaction (which is simply a signature over a particular bytestring)
-will only be executable once on a particular blockchain.
-
-`json.RawMessage` is preferred over using the SDK types for future compatibility.
-
-```go
-type StdSignMsg struct {
- ChainID string
- AccountNumber uint64
- Sequence uint64
- TimeoutHeight uint64
- Fee StdFee
- Msgs []sdk.Msg
- Memo string
-}
-```
diff --git a/x/auth/spec/04_keepers.md b/x/auth/spec/04_keepers.md
index e154e5383042..f57988b4fa7c 100644
--- a/x/auth/spec/04_keepers.md
+++ b/x/auth/spec/04_keepers.md
@@ -12,32 +12,33 @@ Presently only one fully-permissioned account keeper is exposed, which has the a
all fields of all accounts, and to iterate over all stored accounts.
```go
-type AccountKeeper interface {
- // Return a new account with the next account number and the specified address. Does not save the new account to the store.
- NewAccountWithAddress(AccAddress) Account
+// AccountKeeperI is the interface contract that x/auth's keeper implements.
+type AccountKeeperI interface {
+ // Return a new account with the next account number and the specified address. Does not save the new account to the store.
+ NewAccountWithAddress(sdk.Context, sdk.AccAddress) types.AccountI
- // Return a new account with the next account number. Does not save the new account to the store.
- NewAccount(Account) Account
+ // Return a new account with the next account number. Does not save the new account to the store.
+ NewAccount(sdk.Context, types.AccountI) types.AccountI
- // Retrieve an account from the store
- GetAccount(AccAddress) Account
+ // Retrieve an account from the store.
+ GetAccount(sdk.Context, sdk.AccAddress) types.AccountI
- // Set an account in the store
- SetAccount(Account)
+ // Set an account in the store.
+ SetAccount(sdk.Context, types.AccountI)
- // Remove an account from the store
- RemoveAccount(Account)
+ // Remove an account from the store.
+ RemoveAccount(sdk.Context, types.AccountI)
- // Iterate over all accounts, calling the provided function. Stop iteraiton when it returns false.
- IterateAccounts(func(Account) (bool))
+ // Iterate over all accounts, calling the provided function. Stop iteraiton when it returns false.
+ IterateAccounts(sdk.Context, func(types.AccountI) bool)
- // Fetch the public key of an account at a specified address
- GetPubKey(AccAddress) PubKey
+ // Fetch the public key of an account at a specified address
+ GetPubKey(sdk.Context, sdk.AccAddress) (crypto.PubKey, error)
- // Fetch the sequence of an account at a specified address
- GetSequence(AccAddress) uint64
+ // Fetch the sequence of an account at a specified address.
+ GetSequence(sdk.Context, sdk.AccAddress) (uint64, error)
- // Fetch the next account number, and increment the internal counter
- GetNextAccountNumber() uint64
+ // Fetch the next account number, and increment the internal counter.
+ GetNextAccountNumber(sdk.Context) uint64
}
```
diff --git a/x/auth/spec/07_params.md b/x/auth/spec/07_params.md
index 6ef63ed1215b..93ddc89c2219 100644
--- a/x/auth/spec/07_params.md
+++ b/x/auth/spec/07_params.md
@@ -7,7 +7,7 @@ order: 7
The auth module contains the following parameters:
| Key | Type | Example |
-|------------------------|-----------------|---------|
+| ---------------------- | --------------- | ------- |
| MaxMemoCharacters | string (uint64) | "256" |
| TxSigLimit | string (uint64) | "7" |
| TxSizeCostPerByte | string (uint64) | "10" |
diff --git a/x/auth/spec/README.md b/x/auth/spec/README.md
index ea49a9736efe..f666e0923b2e 100644
--- a/x/auth/spec/README.md
+++ b/x/auth/spec/README.md
@@ -21,24 +21,19 @@ This module will be used in the Cosmos Hub.
## Contents
1. **[Concepts](01_concepts.md)**
- - [Gas & Fees](01_concepts.md#gas-&-fees)
+ - [Gas & Fees](01_concepts.md#gas-&-fees)
2. **[State](02_state.md)**
- - [Accounts](02_state.md#accounts)
-3. **[Messages](03_messages.md)**
- - [Handlers](03_messages.md#handlers)
-4. **[Types](03_types.md)**
- - [StdFee](03_types.md#stdfee)
- - [StdSignature](03_types.md#stdsignature)
- - [StdTx](03_types.md#stdtx)
- - [StdSignDoc](03_types.md#stdsigndoc)
-5. **[Keepers](04_keepers.md)**
- - [Account Keeper](04_keepers.md#account-keeper)
-6. **[Vesting](05_vesting.md)**
- - [Intro and Requirements](05_vesting.md#intro-and-requirements)
- - [Vesting Account Types](05_vesting.md#vesting-account-types)
- - [Vesting Account Specification](05_vesting.md#vesting-account-specification)
- - [Keepers & Handlers](05_vesting.md#keepers-&-handlers)
- - [Genesis Initialization](05_vesting.md#genesis-initialization)
- - [Examples](05_vesting.md#examples)
- - [Glossary](05_vesting.md#glossary)
-7. **[Parameters](07_params.md)**
+ - [Accounts](02_state.md#accounts)
+3. **[AnteHandlers](03_antehandlers.md)**
+ - [Handlers](03_antehandlers.md#handlers)
+4. **[Keepers](04_keepers.md)**
+ - [Account Keeper](04_keepers.md#account-keeper)
+5. **[Vesting](05_vesting.md)**
+ - [Intro and Requirements](05_vesting.md#intro-and-requirements)
+ - [Vesting Account Types](05_vesting.md#vesting-account-types)
+ - [Vesting Account Specification](05_vesting.md#vesting-account-specification)
+ - [Keepers & Handlers](05_vesting.md#keepers-&-handlers)
+ - [Genesis Initialization](05_vesting.md#genesis-initialization)
+ - [Examples](05_vesting.md#examples)
+ - [Glossary](05_vesting.md#glossary)
+6. **[Parameters](07_params.md)**
diff --git a/x/bank/spec/01_state.md b/x/bank/spec/01_state.md
new file mode 100644
index 000000000000..f744e2e779a1
--- /dev/null
+++ b/x/bank/spec/01_state.md
@@ -0,0 +1,11 @@
+
+
+# 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
new file mode 100644
index 000000000000..ec74358fbb70
--- /dev/null
+++ b/x/bank/spec/02_keepers.md
@@ -0,0 +1,123 @@
+
+
+# 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.
+
+```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))
+}
+```
diff --git a/x/bank/spec/03_messages.md b/x/bank/spec/03_messages.md
new file mode 100644
index 000000000000..ecb9dc695fae
--- /dev/null
+++ b/x/bank/spec/03_messages.md
@@ -0,0 +1,32 @@
+
+
+# Messages
+
+## MsgSend
+
+```go
+// 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)
+```
diff --git a/x/bank/spec/04_events.md b/x/bank/spec/04_events.md
new file mode 100644
index 000000000000..55c93b805ed1
--- /dev/null
+++ b/x/bank/spec/04_events.md
@@ -0,0 +1,29 @@
+
+
+# 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
new file mode 100644
index 000000000000..8d254243c4bc
--- /dev/null
+++ b/x/bank/spec/05_params.md
@@ -0,0 +1,24 @@
+
+
+# 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.
diff --git a/x/bank/spec/README.md b/x/bank/spec/README.md
index 254488945c69..9a1a0afb6edc 100644
--- a/x/bank/spec/README.md
+++ b/x/bank/spec/README.md
@@ -1,26 +1,13 @@
-# `x/bank`
-
-## 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)**
+# `x/bank`
-## 01. Abstract
+## Abstract
This document specifies the bank module of the Cosmos SDK.
@@ -35,46 +22,42 @@ supply of all assets used in the application.
This module will be used in the Cosmos Hub.
-## 02. Concepts
-
-### Supply
+## Supply
-The `supply` module:
+The `supply` functionality:
- passively tracks the total supply of coins within a chain,
- provides a pattern for modules to hold/interact with `Coins`, and
- introduces the invariant check to verify a chain's total supply.
-#### Total Supply
+### 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.AccountI` interface, called `ModuleAccountI`, which can be used by
+The supply functionality introduces a new type of `auth.Account` which can be used by
modules to allocate tokens and in special cases mint or burn tokens. At a base
level these module accounts are capable of sending/receiving tokens to and from
-`auth.AccountI` interfaces and other module accounts. This design replaces previous
+`auth.Account`s and other module accounts. This design replaces previous
alternative designs where, to hold tokens, modules would burn the incoming
tokens from the sender account, and then track those tokens internally. Later,
in order to send tokens, the module would need to effectively mint tokens
within a destination account. The new design removes duplicate logic between
modules to perform this accounting.
-The `ModuleAccountI` interface is defined as follows:
+The `ModuleAccount` interface is defined as follows:
```go
-// ModuleAccountI defines an account interface for modules that hold tokens in
-// an escrow.
-type ModuleAccountI interface {
- AccountI // same methods as the Account interface
+type ModuleAccount interface {
+ auth.Account // same methods as the Account interface
- GetName() string // name of the module; used to obtain the address
- GetPermissions() []string // permissions of module account
- HasPermission(string) bool
+ GetName() string // name of the module; used to obtain the address
+ GetPermissions() []string // permissions of module account
+ HasPermission(string) bool
}
```
@@ -82,20 +65,20 @@ type ModuleAccountI interface {
> 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 `ModuleAccountI`s in order to be able
+and the bank `Keeper` that are related to `ModuleAccount`s in order to be able
to:
-- Get and set `ModuleAccountI`s by providing the `Name`.
-- Send coins from and to other `ModuleAccountI`s or standard `Account`s
+- Get and set `ModuleAccount`s by providing the `Name`.
+- Send coins from and to other `ModuleAccount`s or standard `Account`s
(`BaseAccount` or `VestingAccount`) by passing only the `Name`.
-- `Mint` or `Burn` coins for a `ModuleAccountI` (restricted to its permissions).
+- `Mint` or `Burn` coins for a `ModuleAccount` (restricted to its permissions).
-#### Permissions
+### Permissions
-Each `ModuleAccountI` has a different set of permissions that provide different
+Each `ModuleAccount` has a different set of permissions that provide different
object capabilities to perform certain actions. Permissions need to be
registered upon the creation of the supply `Keeper` so that every time a
-`ModuleAccountI` calls the allowed functions, the `Keeper` can lookup the
+`ModuleAccount` calls the allowed functions, the `Keeper` can lookup the
permissions to that specific account and perform or not the action.
The available permissions are:
@@ -104,217 +87,16 @@ 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.
-## 03. 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)`
-
-## 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.
+## Contents
+
+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)**
diff --git a/x/evidence/spec/01_concepts.md b/x/evidence/spec/01_concepts.md
new file mode 100644
index 000000000000..736e75c9a68e
--- /dev/null
+++ b/x/evidence/spec/01_concepts.md
@@ -0,0 +1,75 @@
+
+
+# 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
+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
+```
diff --git a/x/evidence/spec/02_state.md b/x/evidence/spec/02_state.md
new file mode 100644
index 000000000000..00d8d05bedff
--- /dev/null
+++ b/x/evidence/spec/02_state.md
@@ -0,0 +1,19 @@
+
+
+# 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`).
diff --git a/x/evidence/spec/03_messages.md b/x/evidence/spec/03_messages.md
new file mode 100644
index 000000000000..85a8228905e7
--- /dev/null
+++ b/x/evidence/spec/03_messages.md
@@ -0,0 +1,48 @@
+
+
+# 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.
diff --git a/x/evidence/spec/04_events.md b/x/evidence/spec/04_events.md
new file mode 100644
index 000000000000..35fd77b3f5ac
--- /dev/null
+++ b/x/evidence/spec/04_events.md
@@ -0,0 +1,18 @@
+
+
+# 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
new file mode 100644
index 000000000000..4c48b540afdd
--- /dev/null
+++ b/x/evidence/spec/05_params.md
@@ -0,0 +1,7 @@
+
+
+# Parameters
+
+The evidence module does not contain any parameters.
diff --git a/x/evidence/spec/06_begin_block.md b/x/evidence/spec/06_begin_block.md
new file mode 100644
index 000000000000..317b5523ee93
--- /dev/null
+++ b/x/evidence/spec/06_begin_block.md
@@ -0,0 +1,155 @@
+
+
+# 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 SDK handles two types of evidence inside ABCI's `BeginBlock`:
+
+- `DuplicateVoteEvidence`,
+- `LightClientAttackEvidence`.
+
+These two evidence types are handled the same way by the evidence module. First, the SDK converts the Tendermint concrete evidence type to a SDK `Evidence` interface using `Equivocation` as the concrete type.
+
+```proto
+// Equivocation implements the Evidence interface.
+message Equivocation {
+ int64 height = 1;
+ google.protobuf.Timestamp time = 2;
+ int64 power = 3;
+ string consensus_address = 4;
+}
+```
+
+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) HandleEquivocationEvidence(ctx sdk.Context, evidence *types.Equivocation) {
+ logger := k.Logger(ctx)
+ consAddr := evidence.GetConsensusAddress()
+
+ if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); err != nil {
+ // Ignore evidence that cannot be handled.
+ //
+ // NOTE: We used to panic with:
+ // `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`,
+ // but this couples the expectations of the app to both Tendermint and
+ // the simulator. Both are expected to provide the full range of
+ // allowable but none of the disallowed evidence types. Instead of
+ // getting this coordination right, it is easier to relax the
+ // constraints and ignore evidence that cannot be handled.
+ return
+ }
+
+ // calculate the age of the evidence
+ infractionHeight := evidence.GetHeight()
+ infractionTime := evidence.GetTime()
+ ageDuration := ctx.BlockHeader().Time.Sub(infractionTime)
+ ageBlocks := ctx.BlockHeader().Height - infractionHeight
+
+ // Reject evidence if the double-sign is too old. Evidence is considered stale
+ // if the difference in time and number of blocks is greater than the allowed
+ // parameters defined.
+ cp := ctx.ConsensusParams()
+ if cp != nil && cp.Evidence != nil {
+ if ageDuration > cp.Evidence.MaxAgeDuration && ageBlocks > cp.Evidence.MaxAgeNumBlocks {
+ logger.Info(
+ "ignored equivocation; evidence too old",
+ "validator", consAddr,
+ "infraction_height", infractionHeight,
+ "max_age_num_blocks", cp.Evidence.MaxAgeNumBlocks,
+ "infraction_time", infractionTime,
+ "max_age_duration", cp.Evidence.MaxAgeDuration,
+ )
+ return
+ }
+ }
+
+ validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr)
+ if validator == nil || validator.IsUnbonded() {
+ // Defensive: Simulation doesn't take unbonding periods into account, and
+ // Tendermint might break this assumption at some point.
+ return
+ }
+
+ if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok {
+ panic(fmt.Sprintf("expected signing info for validator %s but not found", consAddr))
+ }
+
+ // ignore if the validator is already tombstoned
+ if k.slashingKeeper.IsTombstoned(ctx, consAddr) {
+ logger.Info(
+ "ignored equivocation; validator already tombstoned",
+ "validator", consAddr,
+ "infraction_height", infractionHeight,
+ "infraction_time", infractionTime,
+ )
+ return
+ }
+
+ logger.Info(
+ "confirmed equivocation",
+ "validator", consAddr,
+ "infraction_height", infractionHeight,
+ "infraction_time", infractionTime,
+ )
+
+ // 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,
+ k.slashingKeeper.SlashFractionDoubleSign(ctx),
+ 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 b94a73ffd14b..dd72780164e7 100644
--- a/x/evidence/spec/README.md
+++ b/x/evidence/spec/README.md
@@ -1,18 +1,24 @@
+
+
# `x/evidence`
## Table of Contents
-- **[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)**
+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)**
-## 01. Abstract
+## 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
@@ -32,253 +38,3 @@ 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)
diff --git a/x/slashing/spec/01_concepts.md b/x/slashing/spec/01_concepts.md
index b38a6edbed8c..9505706f90d3 100644
--- a/x/slashing/spec/01_concepts.md
+++ b/x/slashing/spec/01_concepts.md
@@ -8,8 +8,8 @@ order: 1
At any given time, there are any number of validators registered in the state
machine. Each block, the top `MaxValidators` (defined by `x/staking`) validators
-who are not jailed become *bonded*, meaning that they may propose and vote on
-blocks. Validators who are *bonded* are *at stake*, meaning that part or all of
+who are not jailed become _bonded_, meaning that they may propose and vote on
+blocks. Validators who are _bonded_ are _at stake_, meaning that part or all of
their stake and their delegators' stake is at risk if they commit a protocol fault.
For each of these validators we keep a `ValidatorSigningInfo` record that contains
@@ -20,26 +20,26 @@ attributes.
In order to mitigate the impact of initially likely categories of non-malicious
protocol faults, the Cosmos Hub implements for each validator
-a *tombstone* cap, which only allows a validator to be slashed once for a double
+a _tombstone_ cap, which only allows a validator to be slashed once for a double
sign fault. For example, if you misconfigure your HSM and double-sign a bunch of
old blocks, you'll only be punished for the first double-sign (and then immediately tombstombed). This will still be quite expensive and desirable to avoid, but tombstone caps
somewhat blunt the economic impact of unintentional misconfiguration.
-Liveness faults do not have caps, as they can't stack upon each other. Liveness bugs are "detected" as soon as the infraction occurs, and the validators are immediately put in jail, so it is not possible for them to commit multiple liveness faults without unjailing in between.
+Liveness faults do not have caps, as they can't stack upon each other. Liveness bugs are "detected" as soon as the infraction occurs, and the validators are immediately put in jail, so it is not possible for them to commit multiple liveness faults without unjailing in between.
## Infraction Timelines
To illustrate how the `x/slashing` module handles submitted evidence through
Tendermint consensus, consider the following examples:
-__Definitions__:
+**Definitions**:
-*[* : timeline start
-*]* : timeline end
-*Cn* : infraction `n` committed
-*Dn* : infraction `n` discovered
-*Vb* : validator bonded
-*Vu* : validator unbonded
+_[_ : timeline start
+_]_ : timeline end
+_Cn_ : infraction `n` committed
+_Dn_ : infraction `n` discovered
+_Vb_ : validator bonded
+_Vu_ : validator unbonded
### Single Double Sign Infraction
diff --git a/x/slashing/spec/02_state.md b/x/slashing/spec/02_state.md
index 867fa7998374..17931ce866a0 100644
--- a/x/slashing/spec/02_state.md
+++ b/x/slashing/spec/02_state.md
@@ -40,27 +40,35 @@ bonded validator. The `SignedBlocksWindow` parameter defines the size
The information stored for tracking validator liveness is as follows:
-```go
-type ValidatorSigningInfo struct {
- Address sdk.ConsAddress
- StartHeight int64
- IndexOffset int64
- JailedUntil time.Time
- Tombstoned bool
- MissedBlocksCounter int64
+```protobuf
+// ValidatorSigningInfo defines a validator's signing info for monitoring their
+// liveness activity.
+message ValidatorSigningInfo {
+ string address = 1;
+ // height at which validator was first a candidate OR was unjailed
+ int64 start_height = 2;
+ // index offset into signed block bit array
+ int64 index_offset = 3;
+ // timestamp validator cannot be unjailed until
+ google.protobuf.Timestamp jailed_until = 4;
+ // whether or not a validator has been tombstoned (killed out of validator
+ // set)
+ bool tombstoned = 5;
+ // missed blocks counter (to avoid scanning the array every time)
+ int64 missed_blocks_counter = 6;
}
```
Where:
-- __Address__: The validator's consensus address.
-- __StartHeight__: The height that the candidate became an active validator
+- **Address**: The validator's consensus address.
+- **StartHeight**: The height that the candidate became an active validator
(with non-zero voting power).
-- __IndexOffset__: Index which is incremented each time the validator was a bonded
+- **IndexOffset**: Index which is incremented each time the validator was a bonded
in a block and may have signed a precommit or not. This in conjunction with the
`SignedBlocksWindow` param determines the index in the `MissedBlocksBitArray`.
-- __JailedUntil__: Time for which the validator is jailed until due to liveness downtime.
-- __Tombstoned__: Desribes if the validator is tombstoned or not. It is set once the
+- **JailedUntil**: Time for which the validator is jailed until due to liveness downtime.
+- **Tombstoned**: Desribes if the validator is tombstoned or not. It is set once the
validator commits an equivocation or for any other configured misbehiavor.
-- __MissedBlocksCounter__: A counter kept to avoid unnecessary array reads. Note
+- **MissedBlocksCounter**: A counter kept to avoid unnecessary array reads. Note
that `Sum(MissedBlocksBitArray)` equals `MissedBlocksCounter` always.
diff --git a/x/slashing/spec/03_messages.md b/x/slashing/spec/03_messages.md
index d7825bfd0b8f..6e7d168ffdb5 100644
--- a/x/slashing/spec/03_messages.md
+++ b/x/slashing/spec/03_messages.md
@@ -9,15 +9,21 @@ In this section we describe the processing of messages for the `slashing` module
## Unjail
If a validator was automatically unbonded due to downtime and wishes to come back online &
-possibly rejoin the bonded set, it must send `TxUnjail`:
-
-```
-type TxUnjail struct {
- ValidatorAddr sdk.AccAddress
+possibly rejoin the bonded set, it must send `MsgUnjail`:
+
+```protobuf
+// MsgUnjail is an sdk.Msg used for unjailing a jailed validator, thus returning
+// them into the bonded validator set, so they can begin receiving provisions
+// and rewards again.
+message MsgUnjail {
+ string validator_addr = 1;
}
+```
-handleMsgUnjail(tx TxUnjail)
+And below is its corresponding handler:
+```
+handleMsgUnjail(tx MsgUnjail)
validator = getValidator(tx.ValidatorAddr)
if validator == nil
fail with "No validator found"
diff --git a/x/slashing/spec/04_begin_block.md b/x/slashing/spec/04_begin_block.md
index 96d1217f1945..99572c419f20 100644
--- a/x/slashing/spec/04_begin_block.md
+++ b/x/slashing/spec/04_begin_block.md
@@ -23,7 +23,7 @@ greater than `minHeight` and the validator's `MissedBlocksCounter` is greater th
for `DowntimeJailDuration`, and have the following values reset:
`MissedBlocksBitArray`, `MissedBlocksCounter`, and `IndexOffset`.
-__Note__: Liveness slashes do **NOT** lead to a tombstombing.
+**Note**: Liveness slashes do **NOT** lead to a tombstombing.
```go
height := block.Height
diff --git a/x/slashing/spec/05_hooks.md b/x/slashing/spec/05_hooks.md
index adb8da39b01f..8f78cdff01b0 100644
--- a/x/slashing/spec/05_hooks.md
+++ b/x/slashing/spec/05_hooks.md
@@ -25,6 +25,6 @@ onValidatorBonded(address sdk.ValAddress)
}
setValidatorSigningInfo(signingInfo)
}
-
+
return
```
diff --git a/x/slashing/spec/07_tombstone.md b/x/slashing/spec/07_tombstone.md
index a062278cec9d..4759a89b7190 100644
--- a/x/slashing/spec/07_tombstone.md
+++ b/x/slashing/spec/07_tombstone.md
@@ -15,18 +15,18 @@ and evidence of the infraction reaching the state machine (this is one of the
primary reasons for the existence of the unbonding period).
> Note: The tombstone concept, only applies to faults that have a delay between
-the infraction occurring and evidence reaching the state machine. For example,
-evidence of a validator double signing may take a while to reach the state machine
-due to unpredictable evidence gossip layer delays and the ability of validators to
-selectively reveal double-signatures (e.g. to infrequently-online light clients).
-Liveness slashing, on the other hand, is detected immediately as soon as the
-infraction occurs, and therefore no slashing period is needed. A validator is
-immediately put into jail period, and they cannot commit another liveness fault
-until they unjail. In the future, there may be other types of byzantine faults
-that have delays (for example, submitting evidence of an invalid proposal as a transaction).
-When implemented, it will have to be decided whether these future types of
-byzantine faults will result in a tombstoning (and if not, the slash amounts
-will not be capped by a slashing period).
+> the infraction occurring and evidence reaching the state machine. For example,
+> evidence of a validator double signing may take a while to reach the state machine
+> due to unpredictable evidence gossip layer delays and the ability of validators to
+> selectively reveal double-signatures (e.g. to infrequently-online light clients).
+> Liveness slashing, on the other hand, is detected immediately as soon as the
+> infraction occurs, and therefore no slashing period is needed. A validator is
+> immediately put into jail period, and they cannot commit another liveness fault
+> until they unjail. In the future, there may be other types of byzantine faults
+> that have delays (for example, submitting evidence of an invalid proposal as a transaction).
+> When implemented, it will have to be decided whether these future types of
+> byzantine faults will result in a tombstoning (and if not, the slash amounts
+> will not be capped by a slashing period).
In the current system design, once a validator is put in the jail for a consensus
fault, after the `JailPeriod` they are allowed to send a transaction to `unjail`
@@ -72,10 +72,10 @@ As the number of slashing periods increase, it creates more complexity as we hav
to keep track of the highest infraction amount for every single slashing period.
> Note: Currently, according to the `slashing` module spec, a new slashing period
-is created every time a validator is unbonded then rebonded. This should probably
-be changed to jailed/unjailed. See issue [#3205](https://github.com/cosmos/cosmos-sdk/issues/3205)
-for further details. For the remainder of this, I will assume that we only start
-a new slashing period when a validator gets unjailed.
+> is created every time a validator is unbonded then rebonded. This should probably
+> be changed to jailed/unjailed. See issue [#3205](https://github.com/cosmos/cosmos-sdk/issues/3205)
+> for further details. For the remainder of this, I will assume that we only start
+> a new slashing period when a validator gets unjailed.
The maximum number of slashing periods is the `len(UnbondingPeriod) / len(JailPeriod)`.
The current defaults in Gaia for the `UnbondingPeriod` and `JailPeriod` are 3 weeks
@@ -85,7 +85,7 @@ we only have to track 1 slashing period (i.e not have to track slashing periods)
Currently, in the jail period implementation, once a validator unjails, all of
their delegators who are delegated to them (haven't unbonded / redelegated away),
-stay with them. Given that consensus safety faults are so egregious
+stay with them. Given that consensus safety faults are so egregious
(way more so than liveness faults), it is probably prudent to have delegators not
"auto-rebond" to the validator. Thus, we propose setting the "jail time" for a
validator who commits a consensus safety fault, to `infinite` (i.e. a tombstone state).
@@ -93,7 +93,7 @@ This essentially kicks the validator out of the validator set and does not allow
them to re-enter the validator set. All of their delegators (including the operator themselves)
have to either unbond or redelegate away. The validator operator can create a new
validator if they would like, with a new operator key and consensus key, but they
-have to "re-earn" their delegations back. To put the validator in the tombstone
+have to "re-earn" their delegations back. To put the validator in the tombstone
state, we set `DoubleSignJailEndTime` to `time.Unix(253402300800)`, the maximum
time supported by Amino.
@@ -106,7 +106,7 @@ of the hooks defined in the `slashing` module consumed by the `staking` module
Another optimization that can be made is that if we assume that all ABCI faults
for Tendermint consensus are slashed at the same level, we don't have to keep
-track of "max slash". Once an ABCI fault happens, we don't have to worry about
+track of "max slash". Once an ABCI fault happens, we don't have to worry about
comparing potential future ones to find the max.
Currently the only Tendermint ABCI fault is:
@@ -121,5 +121,5 @@ Given that these faults are both attributable byzantine faults, we will likely
want to slash them equally, and thus we can enact the above change.
> Note: This change may make sense for current Tendermint consensus, but maybe
-not for a different consensus algorithm or future versions of Tendermint that
-may want to punish at different levels (for example, partial slashing).
+> not for a different consensus algorithm or future versions of Tendermint that
+> may want to punish at different levels (for example, partial slashing).
diff --git a/x/slashing/spec/README.md b/x/slashing/spec/README.md
index 7f694d03d36f..226306562333 100644
--- a/x/slashing/spec/README.md
+++ b/x/slashing/spec/README.md
@@ -5,7 +5,7 @@ parent:
title: "slashing"
-->
-# `slashing`
+# `x/slashing`
## Abstract
@@ -25,21 +25,21 @@ This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosyste
## Contents
1. **[Concepts](01_concepts.md)**
- - [States](01_concepts.md#states)
- - [Tombstone Caps](01_concepts.md#tombstone-caps)
- - [ASCII timelines](01_concepts.md#ascii-timelines)
+ - [States](01_concepts.md#states)
+ - [Tombstone Caps](01_concepts.md#tombstone-caps)
+ - [ASCII timelines](01_concepts.md#ascii-timelines)
2. **[State](02_state.md)**
- - [Signing Info](02_state.md#signing-info)
+ - [Signing Info](02_state.md#signing-info)
3. **[Messages](03_messages.md)**
- - [Unjail](03_messages.md#unjail)
+ - [Unjail](03_messages.md#unjail)
4. **[Begin-Block](04_begin_block.md)**
- - [Evidence handling](04_begin_block.md#evidence-handling)
- - [Uptime tracking](04_begin_block.md#uptime-tracking)
+ - [Evidence handling](04_begin_block.md#evidence-handling)
+ - [Uptime tracking](04_begin_block.md#uptime-tracking)
5. **[05_hooks.md](05_hooks.md)**
- - [Hooks](05_hooks.md#hooks)
+ - [Hooks](05_hooks.md#hooks)
6. **[Events](06_events.md)**
- - [BeginBlocker](06_events.md#beginblocker)
- - [Handlers](06_events.md#handlers)
+ - [BeginBlocker](06_events.md#beginblocker)
+ - [Handlers](06_events.md#handlers)
7. **[Staking Tombstone](07_tombstone.md)**
- - [Abstract](07_tombstone.md#abstract)
+ - [Abstract](07_tombstone.md#abstract)
8. **[Parameters](08_params.md)**