Merge PR #5074: move docs/spec into x/module/spec
This commit is contained in:
committed by
Alexander Bezobchuk
parent
890030b5c5
commit
3aca119fd1
@@ -1,32 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
## Gas & Fees
|
||||
|
||||
Fees serve two purposes for an operator of the network.
|
||||
|
||||
Fees limit the growth of the state stored by every full node and allow for
|
||||
general purpose censorship of transactions of little economic value. Fees
|
||||
are best suited as an anti-spam mechanism where validators are disinterested in
|
||||
the use of the network and identities of users.
|
||||
|
||||
Fees are determined by the gas limits and gas prices transactions provide, where
|
||||
`fees = ceil(gasLimit * gasPrices)`. Txs incur gas costs for all state reads/writes,
|
||||
signature verification, as well as costs proportional to the tx size. Operators
|
||||
should set minimum gas prices when starting their nodes. They must set the unit
|
||||
costs of gas in each token denomination they wish to support:
|
||||
|
||||
`gaiad start ... --minimum-gas-prices=0.00001stake;0.05photinos`
|
||||
|
||||
When adding transactions to mempool or gossipping transactions, validators check
|
||||
if the transaction's gas prices, which are determined by the provided fees, meet
|
||||
any of the validator's minimum gas prices. In other words, a transaction must
|
||||
provide a fee of at least one denomination that matches a validator's minimum
|
||||
gas price.
|
||||
|
||||
Tendermint does not currently provide fee based mempool prioritization, and fee
|
||||
based mempool filtering is local to node and not part of consensus. But with
|
||||
minimum gas prices set, such a mechanism could be implemented by node operators.
|
||||
|
||||
Because the market value for tokens will fluctuate, validators are expected to
|
||||
dynamically adjust their minimum gas prices to a level that would encourage the
|
||||
use of the network.
|
||||
@@ -1,58 +0,0 @@
|
||||
# State
|
||||
|
||||
## Accounts
|
||||
|
||||
Accounts contain authentication information for a uniquely identified external user of an SDK blockchain,
|
||||
including public key, address, and account number / sequence number for replay protection. For efficiency,
|
||||
since account balances must also be fetched to pay fees, account structs also store the balance of a user
|
||||
as `sdk.Coins`.
|
||||
|
||||
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)`
|
||||
|
||||
### 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.
|
||||
|
||||
```go
|
||||
type Account interface {
|
||||
GetAddress() AccAddress
|
||||
SetAddress(AccAddress)
|
||||
|
||||
GetPubKey() PubKey
|
||||
SetPubKey(PubKey)
|
||||
|
||||
GetAccountNumber() uint64
|
||||
SetAccountNumber(uint64)
|
||||
|
||||
GetSequence() uint64
|
||||
SetSequence(uint64)
|
||||
|
||||
GetCoins() Coins
|
||||
SetCoins(Coins)
|
||||
}
|
||||
```
|
||||
|
||||
#### Base Account
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
### Vesting Account
|
||||
|
||||
See [Vesting](vesting.md).
|
||||
@@ -1,42 +0,0 @@
|
||||
# Messages
|
||||
|
||||
TODO make this file conform to typical messages spec
|
||||
|
||||
## 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
|
||||
to include in their proposed block transactions which fail `CheckTx`.
|
||||
|
||||
### Ante Handler
|
||||
|
||||
```go
|
||||
anteHandler(ak AccountKeeper, fck FeeCollectionKeeper, tx sdk.Tx)
|
||||
if !tx.(StdTx)
|
||||
fail with "not a StdTx"
|
||||
|
||||
if isCheckTx and tx.Fee < config.SubjectiveMinimumFee
|
||||
fail with "insufficient fee for mempool inclusion"
|
||||
|
||||
if tx.ValidateBasic() != nil
|
||||
fail with "tx failed ValidateBasic"
|
||||
|
||||
if tx.Fee > 0
|
||||
account = GetAccount(tx.GetSigners()[0])
|
||||
coins := acount.GetCoins()
|
||||
if coins < tx.Fee
|
||||
fail with "insufficient fee to pay for transaction"
|
||||
account.SetCoins(coins - tx.Fee)
|
||||
fck.AddCollectedFees(tx.Fee)
|
||||
|
||||
for index, signature in tx.GetSignatures()
|
||||
account = GetAccount(tx.GetSigners()[index])
|
||||
bytesToSign := StdSignBytes(chainID, acc.GetAccountNumber(),
|
||||
acc.GetSequence(), tx.Fee, tx.Msgs, tx.Memo)
|
||||
if !signature.Verify(bytesToSign)
|
||||
fail with "invalid signature"
|
||||
|
||||
return
|
||||
```
|
||||
@@ -1,65 +0,0 @@
|
||||
# Types
|
||||
|
||||
Besides accounts (specified in [State](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 StdSignDoc struct {
|
||||
AccountNumber uint64
|
||||
ChainID string
|
||||
Fee json.RawMessage
|
||||
Memo string
|
||||
Msgs []json.RawMessage
|
||||
Sequence uint64
|
||||
}
|
||||
```
|
||||
@@ -1,39 +0,0 @@
|
||||
# Keepers
|
||||
|
||||
The auth module only exposes one keeper, the account keeper, which can be used to read and write accounts.
|
||||
|
||||
## Account Keeper
|
||||
|
||||
Presently only one fully-permissioned account keeper is exposed, which has the ability to both read and write
|
||||
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
|
||||
|
||||
// Return a new account with the next account number. Does not save the new account to the store.
|
||||
NewAccount(Account) Account
|
||||
|
||||
// Retrieve an account from the store
|
||||
GetAccount(AccAddress) Account
|
||||
|
||||
// Set an account in the store
|
||||
SetAccount(Account)
|
||||
|
||||
// Remove an account from the store
|
||||
RemoveAccount(Account)
|
||||
|
||||
// Iterate over all accounts, calling the provided function. Stop iteraiton when it returns false.
|
||||
IterateAccounts(func(Account) (bool))
|
||||
|
||||
// Fetch the public key of an account at a specified address
|
||||
GetPubKey(AccAddress) PubKey
|
||||
|
||||
// Fetch the sequence of an account at a specified address
|
||||
GetSequence(AccAddress) uint64
|
||||
|
||||
// Fetch the next account number, and increment the internal counter
|
||||
GetNextAccountNumber() uint64
|
||||
}
|
||||
```
|
||||
@@ -1,437 +0,0 @@
|
||||
# Vesting
|
||||
|
||||
- [Vesting](#vesting)
|
||||
- [Intro and Requirements](#intro-and-requirements)
|
||||
- [Vesting Account Types](#vesting-account-types)
|
||||
- [Vesting Account Specification](#vesting-account-specification)
|
||||
- [Determining Vesting & Vested Amounts](#determining-vesting--vested-amounts)
|
||||
- [Continuously Vesting Accounts](#continuously-vesting-accounts)
|
||||
- [Delayed/Discrete Vesting Accounts](#delayeddiscrete-vesting-accounts)
|
||||
- [Transferring/Sending](#transferringsending)
|
||||
- [Keepers/Handlers](#keepershandlers)
|
||||
- [Delegating](#delegating)
|
||||
- [Keepers/Handlers](#keepershandlers-1)
|
||||
- [Undelegating](#undelegating)
|
||||
- [Keepers/Handlers](#keepershandlers-2)
|
||||
- [Keepers & Handlers](#keepers--handlers)
|
||||
- [Genesis Initialization](#genesis-initialization)
|
||||
- [Examples](#examples)
|
||||
- [Simple](#simple)
|
||||
- [Slashing](#slashing)
|
||||
- [Glossary](#glossary)
|
||||
|
||||
## Intro and Requirements
|
||||
|
||||
This specification describes the vesting account implementation for the Cosmos Hub.
|
||||
The requirements for this vesting account is that it should be initialized
|
||||
during genesis with a starting balance `X` and a vesting end time `T`.
|
||||
|
||||
The owner of this account should be able to delegate to and undelegate from
|
||||
validators, however they cannot send locked coins to other accounts until those
|
||||
coins have been fully vested.
|
||||
|
||||
In addition, a vesting account vests all of its coin denominations at the same
|
||||
rate. This may be subject to change.
|
||||
|
||||
**Note**: A vesting account could have some vesting and non-vesting coins. To
|
||||
support such a feature, the `GenesisAccount` type will need to be updated in
|
||||
order to make such a distinction.
|
||||
|
||||
## Vesting Account Types
|
||||
|
||||
```go
|
||||
// VestingAccount defines an interface that any vesting account type must
|
||||
// implement.
|
||||
type VestingAccount interface {
|
||||
Account
|
||||
|
||||
GetVestedCoins(Time) Coins
|
||||
GetVestingCoins(Time) Coins
|
||||
|
||||
// Delegation and undelegation accounting that returns the resulting base
|
||||
// coins amount.
|
||||
TrackDelegation(Time, Coins)
|
||||
TrackUndelegation(Coins)
|
||||
|
||||
GetStartTime() int64
|
||||
GetEndTime() int64
|
||||
}
|
||||
|
||||
// BaseVestingAccount implements the VestingAccount interface. It contains all
|
||||
// the necessary fields needed for any vesting account implementation.
|
||||
type BaseVestingAccount struct {
|
||||
BaseAccount
|
||||
|
||||
OriginalVesting Coins // coins in account upon initialization
|
||||
DelegatedFree Coins // coins that are vested and delegated
|
||||
DelegatedVesting Coins // coins that vesting and delegated
|
||||
|
||||
EndTime int64 // when the coins become unlocked
|
||||
}
|
||||
|
||||
// ContinuousVestingAccount implements the VestingAccount interface. It
|
||||
// continuously vests by unlocking coins linearly with respect to time.
|
||||
type ContinuousVestingAccount struct {
|
||||
BaseVestingAccount
|
||||
|
||||
StartTime int64 // when the coins start to vest
|
||||
}
|
||||
|
||||
// DelayedVestingAccount implements the VestingAccount interface. It vests all
|
||||
// coins after a specific time, but non prior. In other words, it keeps them
|
||||
// locked until a specified time.
|
||||
type DelayedVestingAccount struct {
|
||||
BaseVestingAccount
|
||||
}
|
||||
```
|
||||
|
||||
In order to facilitate less ad-hoc type checking and assertions and to support
|
||||
flexibility in account usage, the existing `Account` interface is updated to contain
|
||||
the following:
|
||||
|
||||
```go
|
||||
type Account interface {
|
||||
// ...
|
||||
|
||||
// Calculates the amount of coins that can be sent to other accounts given
|
||||
// the current time.
|
||||
SpendableCoins(Time) Coins
|
||||
}
|
||||
```
|
||||
|
||||
## Vesting Account Specification
|
||||
|
||||
Given a vesting account, we define the following in the proceeding operations:
|
||||
|
||||
- `OV`: The original vesting coin amount. It is a constant value.
|
||||
- `V`: The number of `OV` coins that are still _vesting_. It is derived by `OV`, `StartTime` and `EndTime`. This value is computed on demand and not on a per-block basis.
|
||||
- `V'`: The number of `OV` coins that are _vested_ (unlocked). This value is computed on demand and not a per-block basis.
|
||||
- `DV`: The number of delegated _vesting_ coins. It is a variable value. It is stored and modified directly in the vesting account.
|
||||
- `DF`: The number of delegated _vested_ (unlocked) coins. It is a variable value. It is stored and modified directly in the vesting account.
|
||||
- `BC`: The number of `OV` coins less any coins that are transferred (which can be negative or delegated). It is considered to be balance of the embedded base account. It is stored and modified directly in the vesting account.
|
||||
|
||||
### Determining Vesting & Vested Amounts
|
||||
|
||||
It is important to note that these values are computed on demand and not on a
|
||||
mandatory per-block basis (e.g. `BeginBlocker` or `EndBlocker`).
|
||||
|
||||
#### Continuously Vesting Accounts
|
||||
|
||||
To determine the amount of coins that are vested for a given block time `T`, the
|
||||
following is performed:
|
||||
|
||||
1. Compute `X := T - StartTime`
|
||||
2. Compute `Y := EndTime - StartTime`
|
||||
3. Compute `V' := OV * (X / Y)`
|
||||
4. Compute `V := OV - V'`
|
||||
|
||||
Thus, the total amount of _vested_ coins is `V'` and the remaining amount, `V`,
|
||||
is _vesting_.
|
||||
|
||||
```go
|
||||
func (cva ContinuousVestingAccount) GetVestedCoins(t Time) Coins {
|
||||
if t <= cva.StartTime {
|
||||
// We must handle the case where the start time for a vesting account has
|
||||
// been set into the future or when the start of the chain is not exactly
|
||||
// known.
|
||||
return ZeroCoins
|
||||
} else if t >= cva.EndTime {
|
||||
return cva.OriginalVesting
|
||||
}
|
||||
|
||||
x := t - cva.StartTime
|
||||
y := cva.EndTime - cva.StartTime
|
||||
|
||||
return cva.OriginalVesting * (x / y)
|
||||
}
|
||||
|
||||
func (cva ContinuousVestingAccount) GetVestingCoins(t Time) Coins {
|
||||
return cva.OriginalVesting - cva.GetVestedCoins(t)
|
||||
}
|
||||
```
|
||||
|
||||
#### Delayed/Discrete Vesting Accounts
|
||||
|
||||
Delayed vesting accounts are easier to reason about as they only have the full
|
||||
amount vesting up until a certain time, then all the coins become vested (unlocked).
|
||||
This does not include any unlocked coins the account may have initially.
|
||||
|
||||
```go
|
||||
func (dva DelayedVestingAccount) GetVestedCoins(t Time) Coins {
|
||||
if t >= dva.EndTime {
|
||||
return dva.OriginalVesting
|
||||
}
|
||||
|
||||
return ZeroCoins
|
||||
}
|
||||
|
||||
func (dva DelayedVestingAccount) GetVestingCoins(t Time) Coins {
|
||||
return dva.OriginalVesting - dva.GetVestedCoins(t)
|
||||
}
|
||||
```
|
||||
|
||||
### Transferring/Sending
|
||||
|
||||
At any given time, a vesting account may transfer: `min((BC + DV) - V, BC)`.
|
||||
|
||||
In other words, a vesting account may transfer the minimum of the base account
|
||||
balance and the base account balance plus the number of currently delegated
|
||||
vesting coins less the number of coins vested so far.
|
||||
|
||||
```go
|
||||
func (va VestingAccount) SpendableCoins(t Time) Coins {
|
||||
bc := va.GetCoins()
|
||||
return min((bc + va.DelegatedVesting) - va.GetVestingCoins(t), bc)
|
||||
}
|
||||
```
|
||||
|
||||
#### Keepers/Handlers
|
||||
|
||||
The corresponding `x/bank` keeper should appropriately handle sending coins
|
||||
based on if the account is a vesting account or not.
|
||||
|
||||
```go
|
||||
func SendCoins(t Time, from Account, to Account, amount Coins) {
|
||||
bc := from.GetCoins()
|
||||
|
||||
if isVesting(from) {
|
||||
sc := from.SpendableCoins(t)
|
||||
assert(amount <= sc)
|
||||
}
|
||||
|
||||
newCoins := bc - amount
|
||||
assert(newCoins >= 0)
|
||||
|
||||
from.SetCoins(bc - amount)
|
||||
to.SetCoins(amount)
|
||||
|
||||
// save accounts...
|
||||
}
|
||||
```
|
||||
|
||||
### Delegating
|
||||
|
||||
For a vesting account attempting to delegate `D` coins, the following is performed:
|
||||
|
||||
1. Verify `BC >= D > 0`
|
||||
2. Compute `X := min(max(V - DV, 0), D)` (portion of `D` that is vesting)
|
||||
3. Compute `Y := D - X` (portion of `D` that is free)
|
||||
4. Set `DV += X`
|
||||
5. Set `DF += Y`
|
||||
6. Set `BC -= D`
|
||||
|
||||
```go
|
||||
func (va VestingAccount) TrackDelegation(t Time, amount Coins) {
|
||||
x := min(max(va.GetVestingCoins(t) - va.DelegatedVesting, 0), amount)
|
||||
y := amount - x
|
||||
|
||||
va.DelegatedVesting += x
|
||||
va.DelegatedFree += y
|
||||
va.SetCoins(va.GetCoins() - amount)
|
||||
}
|
||||
```
|
||||
|
||||
#### Keepers/Handlers
|
||||
|
||||
```go
|
||||
func DelegateCoins(t Time, from Account, amount Coins) {
|
||||
bc := from.GetCoins()
|
||||
assert(amount <= bc)
|
||||
|
||||
if isVesting(from) {
|
||||
from.TrackDelegation(t, amount)
|
||||
} else {
|
||||
from.SetCoins(sc - amount)
|
||||
}
|
||||
|
||||
// save account...
|
||||
}
|
||||
```
|
||||
|
||||
### Undelegating
|
||||
|
||||
For a vesting account attempting to undelegate `D` coins, the following is performed:
|
||||
NOTE: `DV < D` and `(DV + DF) < D` may be possible due to quirks in the rounding of
|
||||
delegation/undelegation logic.
|
||||
|
||||
1. Verify `D > 0`
|
||||
2. Compute `X := min(DF, D)` (portion of `D` that should become free, prioritizing free coins)
|
||||
3. Compute `Y := min(DV, D - X)` (portion of `D` that should remain vesting)
|
||||
4. Set `DF -= X`
|
||||
5. Set `DV -= Y`
|
||||
6. Set `BC += D`
|
||||
|
||||
```go
|
||||
func (cva ContinuousVestingAccount) TrackUndelegation(amount Coins) {
|
||||
x := min(cva.DelegatedFree, amount)
|
||||
y := amount - x
|
||||
|
||||
cva.DelegatedFree -= x
|
||||
cva.DelegatedVesting -= y
|
||||
cva.SetCoins(cva.GetCoins() + amount)
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: If a delegation is slashed, the continuous vesting account will end up
|
||||
with an excess `DV` amount, even after all its coins have vested. This is because
|
||||
undelegating free coins are prioritized.
|
||||
|
||||
**Note**: The undelegation (bond refund) amount may exceed the delegated
|
||||
vesting (bond) amount due to the way undelegation truncates the bond refund,
|
||||
which can increase the validator's exchange rate (tokens/shares) slightly if the
|
||||
undelegated tokens are non-integral.
|
||||
|
||||
#### Keepers/Handlers
|
||||
|
||||
```go
|
||||
func UndelegateCoins(to Account, amount Coins) {
|
||||
if isVesting(to) {
|
||||
if to.DelegatedFree + to.DelegatedVesting >= amount {
|
||||
to.TrackUndelegation(amount)
|
||||
// save account ...
|
||||
}
|
||||
} else {
|
||||
AddCoins(to, amount)
|
||||
// save account...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Keepers & Handlers
|
||||
|
||||
The `VestingAccount` implementations reside in `x/auth`. However, any keeper in
|
||||
a module (e.g. staking in `x/staking`) wishing to potentially utilize any vesting
|
||||
coins, must call explicit methods on the `x/bank` keeper (e.g. `DelegateCoins`)
|
||||
opposed to `SendCoins` and `SubtractCoins`.
|
||||
|
||||
In addition, the vesting account should also be able to spend any coins it
|
||||
receives from other users. Thus, the bank module's `MsgSend` handler should
|
||||
error if a vesting account is trying to send an amount that exceeds their
|
||||
unlocked coin amount.
|
||||
|
||||
See the above specification for full implementation details.
|
||||
|
||||
## Genesis Initialization
|
||||
|
||||
To initialize both vesting and non-vesting accounts, the `GenesisAccount` struct will
|
||||
include new fields: `Vesting`, `StartTime`, and `EndTime`. Accounts meant to be
|
||||
of type `BaseAccount` or any non-vesting type will have `Vesting = false`. The
|
||||
genesis initialization logic (e.g. `initFromGenesisState`) will have to parse
|
||||
and return the correct accounts accordingly based off of these new fields.
|
||||
|
||||
```go
|
||||
type GenesisAccount struct {
|
||||
// ...
|
||||
|
||||
// vesting account fields
|
||||
OriginalVesting sdk.Coins `json:"original_vesting"`
|
||||
DelegatedFree sdk.Coins `json:"delegated_free"`
|
||||
DelegatedVesting sdk.Coins `json:"delegated_vesting"`
|
||||
StartTime int64 `json:"start_time"`
|
||||
EndTime int64 `json:"end_time"`
|
||||
}
|
||||
|
||||
func ToAccount(gacc GenesisAccount) Account {
|
||||
bacc := NewBaseAccount(gacc)
|
||||
|
||||
if gacc.OriginalVesting > 0 {
|
||||
if ga.StartTime != 0 && ga.EndTime != 0 {
|
||||
// return a continuous vesting account
|
||||
} else if ga.EndTime != 0 {
|
||||
// return a delayed vesting account
|
||||
} else {
|
||||
// invalid genesis vesting account provided
|
||||
panic()
|
||||
}
|
||||
}
|
||||
|
||||
return bacc
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple
|
||||
|
||||
Given a continuous vesting account with 10 vesting coins.
|
||||
|
||||
```
|
||||
OV = 10
|
||||
DF = 0
|
||||
DV = 0
|
||||
BC = 10
|
||||
V = 10
|
||||
V' = 0
|
||||
```
|
||||
|
||||
1. Immediately receives 1 coin
|
||||
```
|
||||
BC = 11
|
||||
```
|
||||
2. Time passes, 2 coins vest
|
||||
```
|
||||
V = 8
|
||||
V' = 2
|
||||
```
|
||||
3. Delegates 4 coins to validator A
|
||||
```
|
||||
DV = 4
|
||||
BC = 7
|
||||
```
|
||||
4. Sends 3 coins
|
||||
```
|
||||
BC = 4
|
||||
```
|
||||
5. More time passes, 2 more coins vest
|
||||
```
|
||||
V = 6
|
||||
V' = 4
|
||||
```
|
||||
6. Sends 2 coins. At this point the account cannot send anymore until further coins vest or it receives additional coins. It can still however, delegate.
|
||||
```
|
||||
BC = 2
|
||||
```
|
||||
|
||||
### Slashing
|
||||
|
||||
Same initial starting conditions as the simple example.
|
||||
|
||||
1. Time passes, 5 coins vest
|
||||
```
|
||||
V = 5
|
||||
V' = 5
|
||||
```
|
||||
2. Delegate 5 coins to validator A
|
||||
```
|
||||
DV = 5
|
||||
BC = 5
|
||||
```
|
||||
3. Delegate 5 coins to validator B
|
||||
```
|
||||
DF = 5
|
||||
BC = 0
|
||||
```
|
||||
4. Validator A gets slashed by 50%, making the delegation to A now worth 2.5 coins
|
||||
5. Undelegate from validator A (2.5 coins)
|
||||
```
|
||||
DF = 5 - 2.5 = 2.5
|
||||
BC = 0 + 2.5 = 2.5
|
||||
```
|
||||
6. Undelegate from validator B (5 coins). The account at this point can only send 2.5 coins unless it receives more coins or until more coins vest. It can still however, delegate.
|
||||
```
|
||||
DV = 5 - 2.5 = 2.5
|
||||
DF = 2.5 - 2.5 = 0
|
||||
BC = 2.5 + 5 = 7.5
|
||||
```
|
||||
|
||||
Notice how we have an excess amount of `DV`.
|
||||
|
||||
## Glossary
|
||||
|
||||
- OriginalVesting: The amount of coins (per denomination) that are initially part of a vesting account. These coins are set at genesis.
|
||||
- StartTime: The BFT time at which a vesting account starts to vest.
|
||||
- EndTime: The BFT time at which a vesting account is fully vested.
|
||||
- DelegatedFree: The tracked amount of coins (per denomination) that are delegated from a vesting account that have been fully vested at time of delegation.
|
||||
- DelegatedVesting: The tracked amount of coins (per denomination) that are delegated from a vesting account that were vesting at time of delegation.
|
||||
- ContinuousVestingAccount: A vesting account implementation that vests coins linearly over time.
|
||||
- DelayedVestingAccount: A vesting account implementation that only fully vests all coins at a given time.
|
||||
@@ -1,11 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The auth module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|------------------------|-----------------|---------|
|
||||
| MaxMemoCharacters | string (uint64) | "256" |
|
||||
| TxSigLimit | string (uint64) | "7" |
|
||||
| TxSizeCostPerByte | string (uint64) | "10" |
|
||||
| SigVerifyCostED25519 | string (uint64) | "590" |
|
||||
| SigVerifyCostSecp256k1 | string (uint64) | "1000" |
|
||||
@@ -1,37 +0,0 @@
|
||||
# Auth module specification
|
||||
|
||||
## Abstract
|
||||
|
||||
This document specifies the auth module of the Cosmos SDK.
|
||||
|
||||
The auth module is responsible for specifying the base transaction and account types
|
||||
for an application, since the SDK itself is agnostic to these particulars. It contains
|
||||
the ante handler, where all basic transaction validity checks (signatures, nonces, auxiliary fields)
|
||||
are performed, and exposes the account keeper, which allows other modules to read, write, and modify accounts.
|
||||
|
||||
This module will be used in the Cosmos Hub.
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
- [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)**
|
||||
@@ -1,5 +0,0 @@
|
||||
# State
|
||||
|
||||
Presently, the bank module has no inherent state — it simply reads and writes accounts using the `AccountKeeper` from the `auth` module.
|
||||
|
||||
This implementation choice is intended to minimize necessary state reads/writes, since we expect most transactions to involve coin amounts (for fees), so storing coin data in the account saves reading it separately.
|
||||
@@ -1,131 +0,0 @@
|
||||
# Keepers
|
||||
|
||||
The bank module provides three different exported keeper interfaces which can be passed to other modules which need to read or update account balances. Modules should use the least-permissive interface which provides the functionality they require.
|
||||
|
||||
Note that you should always review the `bank` module code to ensure that permissions are limited in the way that you expect.
|
||||
|
||||
## Common Types
|
||||
|
||||
### Input
|
||||
|
||||
An input of a multiparty transfer
|
||||
|
||||
```go
|
||||
type Input struct {
|
||||
Address AccAddress
|
||||
Coins Coins
|
||||
}
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
An output of a multiparty transfer.
|
||||
|
||||
```go
|
||||
type Output struct {
|
||||
Address AccAddress
|
||||
Coins Coins
|
||||
}
|
||||
```
|
||||
|
||||
## BaseKeeper
|
||||
|
||||
The base keeper provides full-permission access: the ability to arbitrary modify any account's balance and mint or burn coins.
|
||||
|
||||
```go
|
||||
type BaseKeeper interface {
|
||||
SetCoins(addr AccAddress, amt Coins)
|
||||
SubtractCoins(addr AccAddress, amt Coins)
|
||||
AddCoins(addr AccAddress, amt Coins)
|
||||
InputOutputCoins(inputs []Input, outputs []Output)
|
||||
}
|
||||
```
|
||||
|
||||
`setCoins` fetches an account by address, sets the coins on the account, and saves the account.
|
||||
|
||||
```
|
||||
setCoins(addr AccAddress, amt Coins)
|
||||
account = accountKeeper.getAccount(addr)
|
||||
if account == nil
|
||||
fail with "no account found"
|
||||
account.Coins = amt
|
||||
accountKeeper.setAccount(account)
|
||||
```
|
||||
|
||||
`subtractCoins` fetches the coins of an account, subtracts the provided amount, and saves the account. This decreases the total supply.
|
||||
|
||||
```
|
||||
subtractCoins(addr AccAddress, amt Coins)
|
||||
oldCoins = getCoins(addr)
|
||||
newCoins = oldCoins - amt
|
||||
if newCoins < 0
|
||||
fail with "cannot end up with negative coins"
|
||||
setCoins(addr, newCoins)
|
||||
```
|
||||
|
||||
`addCoins` fetches the coins of an account, adds the provided amount, and saves the account. This increases the total supply.
|
||||
|
||||
```
|
||||
addCoins(addr AccAddress, amt Coins)
|
||||
oldCoins = getCoins(addr)
|
||||
newCoins = oldCoins + amt
|
||||
setCoins(addr, newCoins)
|
||||
```
|
||||
|
||||
`inputOutputCoins` transfers coins from any number of input accounts to any number of output accounts.
|
||||
|
||||
```
|
||||
inputOutputCoins(inputs []Input, outputs []Output)
|
||||
for input in inputs
|
||||
subtractCoins(input.Address, input.Coins)
|
||||
for output in outputs
|
||||
addCoins(output.Address, output.Coins)
|
||||
```
|
||||
|
||||
## SendKeeper
|
||||
|
||||
The send keeper provides access to account balances and the ability to transfer coins between accounts, but not to alter the total supply (mint or burn coins).
|
||||
|
||||
```go
|
||||
type SendKeeper interface {
|
||||
SendCoins(from AccAddress, to AccAddress, amt Coins)
|
||||
}
|
||||
```
|
||||
|
||||
`sendCoins` transfers coins from one account to another.
|
||||
|
||||
```
|
||||
sendCoins(from AccAddress, to AccAddress, amt Coins)
|
||||
subtractCoins(from, amt)
|
||||
addCoins(to, amt)
|
||||
```
|
||||
|
||||
## ViewKeeper
|
||||
|
||||
The view keeper provides read-only access to account balances but no balance alteration functionality. All balance lookups are `O(1)`.
|
||||
|
||||
```go
|
||||
type ViewKeeper interface {
|
||||
GetCoins(addr AccAddress) Coins
|
||||
HasCoins(addr AccAddress, amt Coins) bool
|
||||
}
|
||||
```
|
||||
|
||||
`getCoins` returns the coins associated with an account.
|
||||
|
||||
```
|
||||
getCoins(addr AccAddress)
|
||||
account = accountKeeper.getAccount(addr)
|
||||
if account == nil
|
||||
return Coins{}
|
||||
return account.Coins
|
||||
```
|
||||
|
||||
`hasCoins` returns whether or not an account has at least the provided amount of coins.
|
||||
|
||||
```
|
||||
hasCoins(addr AccAddress, amt Coins)
|
||||
account = accountKeeper.getAccount(addr)
|
||||
coins = getCoins(addr)
|
||||
return coins >= amt
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
# Messages
|
||||
|
||||
## MsgSend
|
||||
|
||||
```go
|
||||
type MsgSend struct {
|
||||
Inputs []Input
|
||||
Outputs []Output
|
||||
}
|
||||
```
|
||||
|
||||
`handleMsgSend` just runs `inputOutputCoins`.
|
||||
|
||||
```
|
||||
handleMsgSend(msg MsgSend)
|
||||
inputSum = 0
|
||||
for input in inputs
|
||||
inputSum += input.Amount
|
||||
outputSum = 0
|
||||
for output in outputs
|
||||
outputSum += output.Amount
|
||||
if inputSum != outputSum:
|
||||
fail with "input/output amount mismatch"
|
||||
|
||||
return inputOutputCoins(msg.Inputs, msg.Outputs)
|
||||
```
|
||||
@@ -1,24 +0,0 @@
|
||||
# Events
|
||||
|
||||
The bank module emits the following events:
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgSend
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------|---------------|--------------------|
|
||||
| transfer | recipient | {recipientAddress} |
|
||||
| transfer | amount | {amount} |
|
||||
| message | module | bank |
|
||||
| message | action | send |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgMultiSend
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------|---------------|--------------------|
|
||||
| transfer | recipient | {recipientAddress} |
|
||||
| message | module | bank |
|
||||
| message | action | multisend |
|
||||
| message | sender | {senderAddress} |
|
||||
@@ -1,8 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The bank module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|-------------|------|---------|
|
||||
| sendenabled | bool | true |
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Bank module specification
|
||||
|
||||
## Abstract
|
||||
|
||||
This document specifies the bank module of the Cosmos SDK.
|
||||
|
||||
The bank module is responsible for handling multi-asset coin transfers between
|
||||
accounts and tracking special-case pseudo-transfers which must work differently
|
||||
with particular kinds of accounts (notably delegating/undelegating for vesting
|
||||
accounts). It exposes several interfaces with varying capabilities for secure
|
||||
interaction with other modules which must alter user balances.
|
||||
|
||||
This module will be used in the Cosmos Hub.
|
||||
|
||||
## 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)**
|
||||
@@ -1,14 +0,0 @@
|
||||
# State
|
||||
|
||||
## ConstantFee
|
||||
|
||||
Due to the anticipated large gas cost requirement to verify an invariant (and
|
||||
potential to exceed the maximum allowable block gas limit) a constant fee is
|
||||
used instead of the standard gas consumption method. The constant fee is
|
||||
intended to be larger than the anticipated gas cost of running the invariant
|
||||
with the standard gas consumption method.
|
||||
|
||||
The ConstantFee param is held in the global params store.
|
||||
|
||||
- Params: `mint/params -> amino(sdk.Coin)`
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Messages
|
||||
|
||||
In this section we describe the processing of the crisis messages and the
|
||||
corresponding updates to the state.
|
||||
|
||||
## MsgVerifyInvariant
|
||||
|
||||
Blockchain invariants can be checked using the `MsgVerifyInvariant` message.
|
||||
|
||||
```go
|
||||
type MsgVerifyInvariant struct {
|
||||
Sender sdk.AccAddress
|
||||
InvariantRoute string
|
||||
}
|
||||
```
|
||||
|
||||
This message is expected to fail if:
|
||||
- the sender does not have enough coins for the constant fee
|
||||
- the invariant route is not registered
|
||||
|
||||
This message checks the invariant provided, and if the invariant is broken it
|
||||
panics, halting the blockchain. If the invariant is broken, the constant fee is
|
||||
never deducted as the transaction is never committed to a block (equivalent to
|
||||
being refunded). However, if the invariant is not broken, the constant fee will
|
||||
not be refunded.
|
||||
@@ -1,14 +0,0 @@
|
||||
# Events
|
||||
|
||||
The crisis module emits the following events:
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgVerifyInvariance
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|-----------|---------------|------------------|
|
||||
| invariant | route | {invariantRoute} |
|
||||
| message | module | crisis |
|
||||
| message | action | verify_invariant |
|
||||
| message | sender | {senderAddress} |
|
||||
@@ -1,7 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The crisis module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|-------------|---------------|-----------------------------------|
|
||||
| ConstantFee | object (coin) | {"denom":"uatom","amount":"1000"} |
|
||||
@@ -1,17 +0,0 @@
|
||||
# Crisis
|
||||
|
||||
## Overview
|
||||
|
||||
The crisis module halts the blockchain under the circumstance that a blockchain
|
||||
invariant is broken. Invariants can be registered with the application during the
|
||||
application initialization process.
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[State](01_state.md)**
|
||||
- [ConstantFee](01_state.md#constantfee)
|
||||
2. **[Messages](02_messages.md)**
|
||||
- [MsgVerifyInvariant](02_messages.md#msgverifyinvariant)
|
||||
3. **[Events](03_events.md)**
|
||||
- [Handlers](03_events.md#handlers)
|
||||
4. **[Parameters](04_params.md)**
|
||||
@@ -1,20 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
## Reference Counting in F1 Fee Distribution
|
||||
|
||||
In F1 fee distribution, in order to calculate the rewards a delegator ought to receive when they
|
||||
withdraw their delegation, we must read the terms of the summation of rewards divided by tokens from
|
||||
the period which they ended when they delegated, and the final period (created when they withdraw).
|
||||
|
||||
Additionally, as slashes change the amount of tokens a delegation will have (but we calculate this lazily,
|
||||
only when a delegator un-delegates), we must calculate rewards in separate periods before / after any slashes
|
||||
which occurred in between when a delegator delegated and when they withdrew their rewards. Thus slashes, like
|
||||
delegations, reference the period which was ended by the slash event.
|
||||
|
||||
All stored historical rewards records for periods which are no longer referenced by any delegations
|
||||
or any slashes can thus be safely removed, as they will never be read (future delegations and future
|
||||
slashes will always reference future periods). This is implemented by tracking a `ReferenceCount`
|
||||
along with each historical reward storage entry. Each time a new object (delegation or slash)
|
||||
is created which might need to reference the historical record, the reference count is incremented.
|
||||
Each time one object which previously needed to reference the historical record is deleted, the reference
|
||||
count is decremented. If the reference count hits zero, the historical record is deleted.
|
||||
@@ -1,69 +0,0 @@
|
||||
# State
|
||||
|
||||
## FeePool
|
||||
|
||||
All globally tracked parameters for distribution are stored within
|
||||
`FeePool`. Rewards are collected and added to the reward pool and
|
||||
distributed to validators/delegators from here.
|
||||
|
||||
Note that the reward pool holds decimal coins (`DecCoins`) to allow
|
||||
for fractions of coins to be received from operations like inflation.
|
||||
When coins are distributed from the pool they are truncated back to
|
||||
`sdk.Coins` which are non-decimal.
|
||||
|
||||
- FeePool: `0x00 -> amino(FeePool)`
|
||||
|
||||
```go
|
||||
// coins with decimal
|
||||
type DecCoins []DecCoin
|
||||
|
||||
type DecCoin struct {
|
||||
Amount sdk.Dec
|
||||
Denom string
|
||||
}
|
||||
|
||||
type FeePool struct {
|
||||
TotalValAccumUpdateHeight int64 // last height which the total validator accum was updated
|
||||
TotalValAccum sdk.Dec // total valdator accum held by validators
|
||||
Pool DecCoins // funds for all validators which have yet to be withdrawn
|
||||
CommunityPool DecCoins // pool for community funds yet to be spent
|
||||
}
|
||||
```
|
||||
|
||||
## Validator Distribution
|
||||
|
||||
Validator distribution information for the relevant validator is updated each time:
|
||||
|
||||
1. delegation amount to a validator is updated,
|
||||
2. a validator successfully proposes a block and receives a reward,
|
||||
3. any delegator withdraws from a validator, or
|
||||
4. the validator withdraws it's commission.
|
||||
|
||||
- ValidatorDistInfo: `0x02 | ValOperatorAddr -> amino(validatorDistribution)`
|
||||
|
||||
```go
|
||||
type ValidatorDistInfo struct {
|
||||
FeePoolWithdrawalHeight int64 // last height this validator withdrew from the global fee pool
|
||||
Pool DecCoins // rewards owed to delegators, commission has already been charged (includes proposer reward)
|
||||
PoolCommission DecCoins // commission collected by this validator (pending withdrawal)
|
||||
|
||||
TotalDelAccumUpdateHeight int64 // last height which the total delegator accum was updated
|
||||
TotalDelAccum sdk.Dec // total proposer pool accumulation factor held by delegators
|
||||
}
|
||||
```
|
||||
|
||||
## Delegation Distribution
|
||||
|
||||
Each delegation distribution only needs to record the height at which it last
|
||||
withdrew fees. Because a delegation must withdraw fees each time it's
|
||||
properties change (aka bonded tokens etc.) its properties will remain constant
|
||||
and the delegator's _accumulation_ factor can be calculated passively knowing
|
||||
only the height of the last withdrawal and its current properties.
|
||||
|
||||
- DelegationDistInfo: `0x02 | DelegatorAddr | ValOperatorAddr -> amino(delegatorDist)`
|
||||
|
||||
```go
|
||||
type DelegationDistInfo struct {
|
||||
WithdrawalHeight int64 // last time this delegation withdrew rewards
|
||||
}
|
||||
```
|
||||
@@ -1,29 +0,0 @@
|
||||
# End Block
|
||||
|
||||
At each `EndBlock`, the fees received are transferred to the distribution `ModuleAccount`, as it's the account the one who keeps track of the flow of coins in (as in this case) and out the module. The fees are also allocated to the proposer, community fund and global pool. When the validator is the proposer of the round, that validator (and their delegators) receives between 1% and 5% of fee rewards, the reserve community tax is then charged, then the remainder is distributed proportionally by voting power to all bonded validators independent of whether they voted (social distribution). Note the social distribution is applied to proposer validator in addition to the proposer reward.
|
||||
|
||||
The amount of proposer reward is calculated from pre-commits Tendermint messages in order to incentivize validators to wait and include additional pre-commits in the block. All provision rewards are added to a provision reward pool which validator holds individually (`ValidatorDistribution.ProvisionsRewardPool`).
|
||||
|
||||
```go
|
||||
func AllocateTokens(feesCollected sdk.Coins, feePool FeePool, proposer ValidatorDistribution,
|
||||
sumPowerPrecommitValidators, totalBondedTokens, communityTax,
|
||||
proposerCommissionRate sdk.Dec)
|
||||
|
||||
SendCoins(FeeCollectorAddr, DistributionModuleAccAddr, feesCollected)
|
||||
feesCollectedDec = MakeDecCoins(feesCollected)
|
||||
proposerReward = feesCollectedDec * (0.01 + 0.04
|
||||
* sumPowerPrecommitValidators / totalBondedTokens)
|
||||
|
||||
commission = proposerReward * proposerCommissionRate
|
||||
proposer.PoolCommission += commission
|
||||
proposer.Pool += proposerReward - commission
|
||||
|
||||
communityFunding = feesCollectedDec * communityTax
|
||||
feePool.CommunityFund += communityFunding
|
||||
|
||||
poolReceived = feesCollectedDec - proposerReward - communityFunding
|
||||
feePool.Pool += poolReceived
|
||||
|
||||
SetValidatorDistribution(proposer)
|
||||
SetFeePool(feePool)
|
||||
```
|
||||
@@ -1,210 +0,0 @@
|
||||
# Messages
|
||||
|
||||
## MsgWithdrawDelegationRewardsAll
|
||||
|
||||
When a delegator wishes to withdraw their rewards it must send
|
||||
`MsgWithdrawDelegationRewardsAll`. Note that parts of this transaction logic are also
|
||||
triggered each with any change in individual delegations, such as an unbond,
|
||||
redelegation, or delegation of additional tokens to a specific validator.
|
||||
|
||||
```go
|
||||
type MsgWithdrawDelegationRewardsAll struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
}
|
||||
|
||||
func WithdrawDelegationRewardsAll(delegatorAddr, withdrawAddr sdk.AccAddress)
|
||||
height = GetHeight()
|
||||
withdraw = GetDelegatorRewardsAll(delegatorAddr, height)
|
||||
SendCoins(distributionModuleAcc, withdrawAddr, withdraw.TruncateDecimal())
|
||||
|
||||
func GetDelegatorRewardsAll(delegatorAddr sdk.AccAddress, height int64) DecCoins
|
||||
|
||||
// get all distribution scenarios
|
||||
delegations = GetDelegations(delegatorAddr)
|
||||
|
||||
// collect all entitled rewards
|
||||
withdraw = 0
|
||||
pool = staking.GetPool()
|
||||
feePool = GetFeePool()
|
||||
for delegation = range delegations
|
||||
delInfo = GetDelegationDistInfo(delegation.DelegatorAddr,
|
||||
delegation.ValidatorAddr)
|
||||
valInfo = GetValidatorDistInfo(delegation.ValidatorAddr)
|
||||
validator = GetValidator(delegation.ValidatorAddr)
|
||||
|
||||
feePool, diWithdraw = delInfo.WithdrawRewards(feePool, valInfo, height, pool.BondedTokens,
|
||||
validator.Tokens, validator.DelegatorShares, validator.Commission)
|
||||
withdraw += diWithdraw
|
||||
|
||||
SetFeePool(feePool)
|
||||
return withdraw
|
||||
```
|
||||
|
||||
## MsgWithdrawDelegationReward
|
||||
|
||||
under special circumstances a delegator may wish to withdraw rewards from only
|
||||
a single validator.
|
||||
|
||||
```go
|
||||
type MsgWithdrawDelegationReward struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorAddr sdk.ValAddress
|
||||
}
|
||||
|
||||
func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.AccAddress)
|
||||
height = GetHeight()
|
||||
|
||||
// get all distribution scenarios
|
||||
pool = staking.GetPool()
|
||||
feePool = GetFeePool()
|
||||
delInfo = GetDelegationDistInfo(delegatorAddr,
|
||||
validatorAddr)
|
||||
valInfo = GetValidatorDistInfo(validatorAddr)
|
||||
validator = GetValidator(validatorAddr)
|
||||
|
||||
feePool, withdraw = delInfo.WithdrawRewards(feePool, valInfo, height, pool.BondedTokens,
|
||||
validator.Tokens, validator.DelegatorShares, validator.Commission)
|
||||
|
||||
SetFeePool(feePool)
|
||||
SendCoins(distributionModuleAcc, withdrawAddr, withdraw.TruncateDecimal())
|
||||
```
|
||||
|
||||
|
||||
## MsgWithdrawValidatorRewardsAll
|
||||
|
||||
When a validator wishes to withdraw their rewards it must send
|
||||
`MsgWithdrawValidatorRewardsAll`. Note that parts of this transaction logic are also
|
||||
triggered each with any change in individual delegations, such as an unbond,
|
||||
redelegation, or delegation of additional tokens to a specific validator. This
|
||||
transaction withdraws the validators commission fee, as well as any rewards
|
||||
earning on their self-delegation.
|
||||
|
||||
```go
|
||||
type MsgWithdrawValidatorRewardsAll struct {
|
||||
OperatorAddr sdk.ValAddress // validator address to withdraw from
|
||||
}
|
||||
|
||||
func WithdrawValidatorRewardsAll(operatorAddr, withdrawAddr sdk.AccAddress)
|
||||
|
||||
height = GetHeight()
|
||||
feePool = GetFeePool()
|
||||
pool = GetPool()
|
||||
ValInfo = GetValidatorDistInfo(delegation.ValidatorAddr)
|
||||
validator = GetValidator(delegation.ValidatorAddr)
|
||||
|
||||
// withdraw self-delegation
|
||||
withdraw = GetDelegatorRewardsAll(validator.OperatorAddr, height)
|
||||
|
||||
// withdrawal validator commission rewards
|
||||
feePool, commission = valInfo.WithdrawCommission(feePool, valInfo, height, pool.BondedTokens,
|
||||
validator.Tokens, validator.Commission)
|
||||
withdraw += commission
|
||||
SetFeePool(feePool)
|
||||
|
||||
SendCoins(distributionModuleAcc, withdrawAddr, withdraw.TruncateDecimal())
|
||||
```
|
||||
|
||||
## Common calculations
|
||||
|
||||
### Update total validator accum
|
||||
|
||||
The total amount of validator accum must be calculated in order to determine
|
||||
the amount of pool tokens which a validator is entitled to at a particular
|
||||
block. The accum is always additive to the existing accum. This term is to be
|
||||
updated each time rewards are withdrawn from the system.
|
||||
|
||||
```go
|
||||
func (g FeePool) UpdateTotalValAccum(height int64, totalBondedTokens Dec) FeePool
|
||||
blocks = height - g.TotalValAccumUpdateHeight
|
||||
g.TotalValAccum += totalDelShares * blocks
|
||||
g.TotalValAccumUpdateHeight = height
|
||||
return g
|
||||
```
|
||||
|
||||
### Update validator's accums
|
||||
|
||||
The total amount of delegator accum must be updated in order to determine the
|
||||
amount of pool tokens which each delegator is entitled to, relative to the
|
||||
other delegators for that validator. The accum is always additive to
|
||||
the existing accum. This term is to be updated each time a
|
||||
withdrawal is made from a validator.
|
||||
|
||||
``` go
|
||||
func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares Dec) ValidatorDistInfo
|
||||
blocks = height - vi.TotalDelAccumUpdateHeight
|
||||
vi.TotalDelAccum += totalDelShares * blocks
|
||||
vi.TotalDelAccumUpdateHeight = height
|
||||
return vi
|
||||
```
|
||||
|
||||
### FeePool pool to validator pool
|
||||
|
||||
Every time a validator or delegator executes a withdrawal or the validator is
|
||||
the proposer and receives new tokens, the relevant validator must move tokens
|
||||
from the passive global pool to their own pool. It is at this point that the
|
||||
commission is withdrawn
|
||||
|
||||
```go
|
||||
func (vi ValidatorDistInfo) TakeFeePoolRewards(g FeePool, height int64, totalBonded, vdTokens, commissionRate Dec) (
|
||||
vi ValidatorDistInfo, g FeePool)
|
||||
|
||||
g.UpdateTotalValAccum(height, totalBondedShares)
|
||||
|
||||
// update the validators pool
|
||||
blocks = height - vi.FeePoolWithdrawalHeight
|
||||
vi.FeePoolWithdrawalHeight = height
|
||||
accum = blocks * vdTokens
|
||||
withdrawalTokens := g.Pool * accum / g.TotalValAccum
|
||||
commission := withdrawalTokens * commissionRate
|
||||
|
||||
g.TotalValAccum -= accumm
|
||||
vi.PoolCommission += commission
|
||||
vi.PoolCommissionFree += withdrawalTokens - commission
|
||||
g.Pool -= withdrawalTokens
|
||||
|
||||
return vi, g
|
||||
```
|
||||
|
||||
|
||||
### Delegation reward withdrawal
|
||||
|
||||
For delegations (including validator's self-delegation) all rewards from reward
|
||||
pool have already had the validator's commission taken away.
|
||||
|
||||
```go
|
||||
func (di DelegationDistInfo) WithdrawRewards(g FeePool, vi ValidatorDistInfo,
|
||||
height int64, totalBonded, vdTokens, totalDelShares, commissionRate Dec) (
|
||||
di DelegationDistInfo, g FeePool, withdrawn DecCoins)
|
||||
|
||||
vi.UpdateTotalDelAccum(height, totalDelShares)
|
||||
g = vi.TakeFeePoolRewards(g, height, totalBonded, vdTokens, commissionRate)
|
||||
|
||||
blocks = height - di.WithdrawalHeight
|
||||
di.WithdrawalHeight = height
|
||||
accum = delegatorShares * blocks
|
||||
|
||||
withdrawalTokens := vi.Pool * accum / vi.TotalDelAccum
|
||||
vi.TotalDelAccum -= accum
|
||||
|
||||
vi.Pool -= withdrawalTokens
|
||||
vi.TotalDelAccum -= accum
|
||||
return di, g, withdrawalTokens
|
||||
|
||||
```
|
||||
|
||||
### Validator commission withdrawal
|
||||
|
||||
Commission is calculated each time rewards enter into the validator.
|
||||
|
||||
```go
|
||||
func (vi ValidatorDistInfo) WithdrawCommission(g FeePool, height int64,
|
||||
totalBonded, vdTokens, commissionRate Dec) (
|
||||
vi ValidatorDistInfo, g FeePool, withdrawn DecCoins)
|
||||
|
||||
g = vi.TakeFeePoolRewards(g, height, totalBonded, vdTokens, commissionRate)
|
||||
|
||||
withdrawalTokens := vi.PoolCommission
|
||||
vi.PoolCommission = 0
|
||||
|
||||
return vi, g, withdrawalTokens
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
# Hooks
|
||||
|
||||
## Create or modify delegation distribution
|
||||
|
||||
- triggered-by: `staking.MsgDelegate`, `staking.MsgBeginRedelegate`, `staking.MsgUndelegate`
|
||||
|
||||
The pool of a new delegator bond will be 0 for the height at which the bond was
|
||||
added, or the withdrawal has taken place. This is achieved by setting
|
||||
`DelegationDistInfo.WithdrawalHeight` to the height of the triggering transaction.
|
||||
|
||||
## Commission rate change
|
||||
|
||||
- triggered-by: `staking.MsgEditValidator`
|
||||
|
||||
If a validator changes its commission rate, all commission on fees must be
|
||||
simultaneously withdrawn using the transaction `TxWithdrawValidator`.
|
||||
Additionally the change and associated height must be recorded in a
|
||||
`ValidatorUpdate` state record.
|
||||
|
||||
## Change in Validator State
|
||||
|
||||
- triggered-by: `staking.Slash`, `staking.UpdateValidator`
|
||||
|
||||
Whenever a validator is slashed or enters/leaves the validator group all of the
|
||||
validator entitled reward tokens must be simultaneously withdrawn from
|
||||
`Global.Pool` and added to `ValidatorDistInfo.Pool`.
|
||||
@@ -1,44 +0,0 @@
|
||||
# Events
|
||||
|
||||
The distribution module emits the following events:
|
||||
|
||||
## BeginBlocker
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|-----------------|---------------|--------------------|
|
||||
| proposer_reward | validator | {validatorAddress} |
|
||||
| proposer_reward | reward | {proposerReward} |
|
||||
| commission | amount | {commissionAmount} |
|
||||
| commission | validator | {validatorAddress} |
|
||||
| rewards | amount | {rewardAmount} |
|
||||
| rewards | validator | {validatorAddress} |
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgSetWithdrawAddress
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|------------------|----------------------|
|
||||
| set_withdraw_address | withdraw_address | {withdrawAddress} |
|
||||
| message | module | distribution |
|
||||
| message | action | set_withdraw_address |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgWithdrawDelegatorReward
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------|---------------|---------------------------|
|
||||
| withdraw_rewards | amount | {rewardAmount} |
|
||||
| withdraw_rewards | validator | {validatorAddress} |
|
||||
| message | module | distribution |
|
||||
| message | action | withdraw_delegator_reward |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgWithdrawValidatorCommission
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|------------|---------------|-------------------------------|
|
||||
| withdraw_commission | amount | {commissionAmount} |
|
||||
| message | module | distribution |
|
||||
| message | action | withdraw_validator_commission |
|
||||
| message | sender | {senderAddress} |
|
||||
@@ -1,10 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The distribution module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|---------------------|--------------|------------------------|
|
||||
| communitytax | string (dec) | "0.020000000000000000" |
|
||||
| baseproposerreward | string (dec) | "0.010000000000000000" |
|
||||
| bonusproposerreward | string (dec) | "0.040000000000000000" |
|
||||
| withdrawaddrenabled | bool | true |
|
||||
@@ -1,96 +0,0 @@
|
||||
# Distribution
|
||||
|
||||
## Overview
|
||||
|
||||
This _simple_ distribution mechanism describes a functional way to passively
|
||||
distribute rewards between validators and delegators. Note that this mechanism does
|
||||
not distribute funds in as precisely as active reward distribution mechanisms and
|
||||
will therefore be upgraded in the future.
|
||||
|
||||
The mechanism operates as follows. Collected rewards are pooled globally and
|
||||
divided out passively to validators and delegators. Each validator has the
|
||||
opportunity to charge commission to the delegators on the rewards collected on
|
||||
behalf of the delegators. Fees are collected directly into a global reward pool
|
||||
and validator proposer-reward pool. Due to the nature of passive accounting,
|
||||
whenever changes to parameters which affect the rate of reward distribution
|
||||
occurs, withdrawal of rewards must also occur.
|
||||
|
||||
- Whenever withdrawing, one must withdraw the maximum amount they are entitled
|
||||
to, leaving nothing in the pool.
|
||||
- Whenever bonding, unbonding, or re-delegating tokens to an existing account, a
|
||||
full withdrawal of the rewards must occur (as the rules for lazy accounting
|
||||
change).
|
||||
- Whenever a validator chooses to change the commission on rewards, all accumulated
|
||||
commission rewards must be simultaneously withdrawn.
|
||||
|
||||
The above scenarios are covered in `hooks.md`.
|
||||
|
||||
The distribution mechanism outlined herein is used to lazily distribute the
|
||||
following rewards between validators and associated delegators:
|
||||
|
||||
- multi-token fees to be socially distributed
|
||||
- proposer reward pool
|
||||
- inflated atom provisions
|
||||
- validator commission on all rewards earned by their delegators stake
|
||||
|
||||
Fees are pooled within a global pool, as well as validator specific
|
||||
proposer-reward pools. The mechanisms used allow for validators and delegators
|
||||
to independently and lazily withdraw their rewards.
|
||||
|
||||
## Shortcomings
|
||||
|
||||
As a part of the lazy computations, each delegator holds an accumulation term
|
||||
specific to each validator which is used to estimate what their approximate
|
||||
fair portion of tokens held in the global fee pool is owed to them.
|
||||
|
||||
```
|
||||
entitlement = delegator-accumulation / all-delegators-accumulation
|
||||
```
|
||||
|
||||
Under the circumstance that there was constant and equal flow of incoming
|
||||
reward tokens every block, this distribution mechanism would be equal to the
|
||||
active distribution (distribute individually to all delegators each block).
|
||||
However, this is unrealistic so deviations from the active distribution will
|
||||
occur based on fluctuations of incoming reward tokens as well as timing of
|
||||
reward withdrawal by other delegators.
|
||||
|
||||
If you happen to know that incoming rewards are about to significantly increase,
|
||||
you are incentivized to not withdraw until after this event, increasing the
|
||||
worth of your existing _accum_. See [#2764](https://github.com/cosmos/cosmos-sdk/issues/2764)
|
||||
for further details.
|
||||
|
||||
## Affect on Staking
|
||||
|
||||
Charging commission on Atom provisions while also allowing for Atom-provisions
|
||||
to be auto-bonded (distributed directly to the validators bonded stake) is
|
||||
problematic within BPoS. Fundamentally, these two mechanisms are mutually
|
||||
exclusive. If both commission and auto-bonding mechanisms are simultaneously
|
||||
applied to the staking-token then the distribution of staking-tokens between
|
||||
any validator and its delegators will change with each block. This then
|
||||
necessitates a calculation for each delegation records for each block -
|
||||
which is considered computationally expensive.
|
||||
|
||||
In conclusion, we can only have Atom commission and unbonded atoms
|
||||
provisions or bonded atom provisions with no Atom commission, and we elect to
|
||||
implement the former. Stakeholders wishing to rebond their provisions may elect
|
||||
to set up a script to periodically withdraw and rebond rewards.
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
- [Reference Counting in F1 Fee Distribution](01_concepts.md#reference-counting-in-f1-fee-distribution)
|
||||
2. **[State](02_state.md)**
|
||||
3. **[End Block](03_end_block.md)**
|
||||
4. **[Messages](04_messages.md)**
|
||||
- [MsgWithdrawDelegationRewardsAll](04_messages.md#msgwithdrawdelegationrewardsall)
|
||||
- [MsgWithdrawDelegationReward](04_messages.md#msgwithdrawdelegationreward)
|
||||
- [MsgWithdrawValidatorRewardsAll](04_messages.md#msgwithdrawvalidatorrewardsall)
|
||||
- [Common calculations ](04_messages.md#common-calculations-)
|
||||
5. **[Hooks](05_hooks.md)**
|
||||
- [Create or modify delegation distribution](05_hooks.md#create-or-modify-delegation-distribution)
|
||||
- [Commission rate change](05_hooks.md#commission-rate-change)
|
||||
- [Change in Validator State](05_hooks.md#change-in-validator-state)
|
||||
6. **[Events](06_events.md)**
|
||||
- [BeginBlocker](06_events.md#beginblocker)
|
||||
- [Handlers](06_events.md#handlers)
|
||||
7. **[Parameters](07_params.md)**
|
||||
@@ -1,174 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
*Disclaimer: This is work in progress. Mechanisms are susceptible to change.*
|
||||
|
||||
The governance process is divided in a few steps that are outlined below:
|
||||
|
||||
* **Proposal submission:** Proposal is submitted to the blockchain with a
|
||||
deposit.
|
||||
* **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is
|
||||
confirmed and vote opens. Bonded Atom holders can then send `TxGovVote`
|
||||
transactions to vote on the proposal.
|
||||
* If the proposal involves a software upgrade:
|
||||
* **Signal:** Validators start signaling that they are ready to switch to the
|
||||
new version.
|
||||
* **Switch:** Once more than 75% of validators have signaled that they are
|
||||
ready to switch, their software automatically flips to the new version.
|
||||
|
||||
## Proposal submission
|
||||
|
||||
### Right to submit a proposal
|
||||
|
||||
Any Atom holder, whether bonded or unbonded, can submit proposals by sending a
|
||||
`TxGovProposal` transaction. Once a proposal is submitted, it is identified by
|
||||
its unique `proposalID`.
|
||||
|
||||
### Proposal types
|
||||
|
||||
In the initial version of the governance module, there are two types of
|
||||
proposal:
|
||||
* `PlainTextProposal` All the proposals that do not involve a modification of
|
||||
the source code go under this type. For example, an opinion poll would use a
|
||||
proposal of type `PlainTextProposal`.
|
||||
* `SoftwareUpgradeProposal`. If accepted, validators are expected to update
|
||||
their software in accordance with the proposal. They must do so by following
|
||||
a 2-steps process described in the [Software Upgrade](#software-upgrade)
|
||||
section below. Software upgrade roadmap may be discussed and agreed on via
|
||||
`PlainTextProposals`, but actual software upgrades must be performed via
|
||||
`SoftwareUpgradeProposals`.
|
||||
|
||||
Other modules may expand upon the governance module by implementing their own
|
||||
proposal types and handlers. These types are registered and processed through the
|
||||
governance module (eg. `ParamChangeProposal`), which then execute the respective
|
||||
module's proposal handler when a proposal passes. This custom handler may perform
|
||||
arbitrary state changes.
|
||||
|
||||
## Deposit
|
||||
|
||||
To prevent spam, proposals must be submitted with a deposit in the coins defined in the `MinDeposit` param. The voting period will not start until the proposal's deposit equals `MinDeposit`.
|
||||
|
||||
When a proposal is submitted, it has to be accompanied by a deposit that must be strictly positive, but can be inferior to `MinDeposit`. The submitter doesn't need to pay for the entire deposit on their own. If a proposal's deposit is inferior to `MinDeposit`, other token holders can increase the proposal's deposit by sending a `Deposit` transaction. The deposit is kept in an escrow in the governance `ModuleAccount` until the proposal is finalized (passed or rejected).
|
||||
|
||||
Once the proposal's deposit reaches `MinDeposit`, it enters voting period. If proposal's deposit does not reach `MinDeposit` before `MaxDepositPeriod`, proposal closes and nobody can deposit on it anymore.
|
||||
|
||||
### Deposit refund and burn
|
||||
|
||||
When a the a proposal finalized, the coins from the deposit are either refunded or burned, according to the final tally of the proposal:
|
||||
|
||||
* If the proposal is approved or if it's rejected but _not_ vetoed, deposits will automatically be refunded to their respective depositor (transferred from the governance `ModuleAccount`).
|
||||
* When the proposal is vetoed with a supermajority, deposits be burned from the governance `ModuleAccount`.
|
||||
|
||||
## Vote
|
||||
|
||||
### Participants
|
||||
|
||||
*Participants* are users that have the right to vote on proposals. On the
|
||||
Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and
|
||||
other users do not get the right to participate in governance. However, they
|
||||
can submit and deposit on proposals.
|
||||
|
||||
Note that some *participants* can be forbidden to vote on a proposal under a
|
||||
certain validator if:
|
||||
* *participant* bonded or unbonded Atoms to said validator after proposal
|
||||
entered voting period.
|
||||
* *participant* became validator after proposal entered voting period.
|
||||
|
||||
This does not prevent *participant* to vote with Atoms bonded to other
|
||||
validators. For example, if a *participant* bonded some Atoms to validator A
|
||||
before a proposal entered voting period and other Atoms to validator B after
|
||||
proposal entered voting period, only the vote under validator B will be
|
||||
forbidden.
|
||||
|
||||
### Voting period
|
||||
|
||||
Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We
|
||||
define `Voting period` as the interval between the moment the vote opens and
|
||||
the moment the vote closes. `Voting period` should always be shorter than
|
||||
`Unbonding period` to prevent double voting. The initial value of
|
||||
`Voting period` is 2 weeks.
|
||||
|
||||
### Option set
|
||||
|
||||
The option set of a proposal refers to the set of choices a participant can
|
||||
choose from when casting its vote.
|
||||
|
||||
The initial option set includes the following options:
|
||||
- `Yes`
|
||||
- `No`
|
||||
- `NoWithVeto`
|
||||
- `Abstain`
|
||||
|
||||
`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option
|
||||
allows voters to signal that they do not intend to vote in favor or against the
|
||||
proposal but accept the result of the vote.
|
||||
|
||||
*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’
|
||||
option that casts a `NoWithVeto` vote.*
|
||||
|
||||
### Quorum
|
||||
|
||||
Quorum is defined as the minimum percentage of voting power that needs to be
|
||||
casted on a proposal for the result to be valid.
|
||||
|
||||
### Threshold
|
||||
|
||||
Threshold is defined as the minimum proportion of `Yes` votes (excluding
|
||||
`Abstain` votes) for the proposal to be accepted.
|
||||
|
||||
Initially, the threshold is set at 50% with a possibility to veto if more than
|
||||
1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means
|
||||
that proposals are accepted if the proportion of `Yes` votes (excluding
|
||||
`Abstain` votes) at the end of the voting period is superior to 50% and if the
|
||||
proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain`
|
||||
votes).
|
||||
|
||||
Proposals can be accepted before the end of the voting period if they meet a special condition. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower`exceeds 2:3, the proposal will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens.
|
||||
This condition exists so that the network can react quickly in case of urgency.
|
||||
|
||||
### Inheritance
|
||||
|
||||
If a delegator does not vote, it will inherit its validator vote.
|
||||
|
||||
* If the delegator votes before its validator, it will not inherit from the
|
||||
validator's vote.
|
||||
* If the delegator votes after its validator, it will override its validator
|
||||
vote with its own. If the proposal is urgent, it is possible
|
||||
that the vote will close before delegators have a chance to react and
|
||||
override their validator's vote. This is not a problem, as proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway.
|
||||
|
||||
### Validator’s punishment for non-voting
|
||||
|
||||
At present, validators are not punished for failing to vote.
|
||||
|
||||
### Governance address
|
||||
|
||||
Later, we may add permissioned keys that could only sign txs from certain modules. For the MVP, the `Governance address` will be the main validator address generated at account creation. This address corresponds to a different PrivKey than the Tendermint PrivKey which is responsible for signing consensus messages. Validators thus do not have to sign governance transactions with the sensitive Tendermint PrivKey.
|
||||
|
||||
## Software Upgrade
|
||||
|
||||
If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade
|
||||
their software to the new version that was voted. This process is divided in
|
||||
two steps.
|
||||
|
||||
### Signal
|
||||
|
||||
After a `SoftwareUpgradeProposal` is accepted, validators are expected to
|
||||
download and install the new version of the software while continuing to run
|
||||
the previous version. Once a validator has downloaded and installed the
|
||||
upgrade, it will start signaling to the network that it is ready to switch by
|
||||
including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation
|
||||
that we want it in the precommit?*)
|
||||
|
||||
Note: There is only one signal slot per *precommit*. If several
|
||||
`SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will
|
||||
form and they will be implemented one after the other in the order that they
|
||||
were accepted.
|
||||
|
||||
### Switch
|
||||
|
||||
Once a block contains more than 2/3rd *precommits* where a common
|
||||
`SoftwareUpgradeProposal` is signaled, all the nodes (including validator
|
||||
nodes, non-validating full nodes and light-nodes) are expected to switch to the
|
||||
new version of the software.
|
||||
|
||||
*Note: Not clear how the flip is handled programmatically*
|
||||
@@ -1,230 +0,0 @@
|
||||
# State
|
||||
|
||||
## Parameters and base types
|
||||
|
||||
`Parameters` define the rules according to which votes are run. There can only
|
||||
be one active parameter set at any given time. If governance wants to change a
|
||||
parameter set, either to modify a value or add/remove a parameter field, a new
|
||||
parameter set has to be created and the previous one rendered inactive.
|
||||
|
||||
```go
|
||||
type DepositParams struct {
|
||||
MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period.
|
||||
MaxDepositPeriod time.Time // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
type VotingParams struct {
|
||||
VotingPeriod time.Time // Length of the voting period. Initial value: 2 weeks
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
type TallyParams struct {
|
||||
Quorum sdk.Dec // Minimum percentage of stake that needs to vote for a proposal to be considered valid
|
||||
Threshold sdk.Dec // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5
|
||||
Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
|
||||
}
|
||||
```
|
||||
|
||||
Parameters are stored in a global `GlobalParams` KVStore.
|
||||
|
||||
Additionally, we introduce some basic types:
|
||||
|
||||
```go
|
||||
type Vote byte
|
||||
|
||||
const (
|
||||
VoteYes = 0x1
|
||||
VoteNo = 0x2
|
||||
VoteNoWithVeto = 0x3
|
||||
VoteAbstain = 0x4
|
||||
)
|
||||
|
||||
type ProposalType string
|
||||
|
||||
const (
|
||||
ProposalTypePlainText = "Text"
|
||||
ProposalTypeSoftwareUpgrade = "SoftwareUpgrade"
|
||||
)
|
||||
|
||||
type ProposalStatus byte
|
||||
|
||||
|
||||
const (
|
||||
StatusNil ProposalStatus = 0x00
|
||||
StatusDepositPeriod ProposalStatus = 0x01 // Proposal is submitted. Participants can deposit on it but not vote
|
||||
StatusVotingPeriod ProposalStatus = 0x02 // MinDeposit is reached, participants can vote
|
||||
StatusPassed ProposalStatus = 0x03 // Proposal passed and successfully executed
|
||||
StatusRejected ProposalStatus = 0x04 // Proposal has been rejected
|
||||
StatusFailed ProposalStatus = 0x05 // Proposal passed but failed execution
|
||||
)
|
||||
```
|
||||
|
||||
## Deposit
|
||||
|
||||
```go
|
||||
type Deposit struct {
|
||||
Amount sdk.Coins // Amount of coins deposited by depositor
|
||||
Depositor crypto.address // Address of depositor
|
||||
}
|
||||
```
|
||||
|
||||
## ValidatorGovInfo
|
||||
|
||||
This type is used in a temp map when tallying
|
||||
|
||||
```go
|
||||
type ValidatorGovInfo struct {
|
||||
Minus sdk.Dec
|
||||
Vote Vote
|
||||
}
|
||||
```
|
||||
|
||||
## Proposals
|
||||
|
||||
`Proposal` objects are used to account votes and generally track the proposal's state. They contain `Content` which denotes
|
||||
what this proposal is about, and other fields, which are the mutable state of
|
||||
the governance process.
|
||||
|
||||
```go
|
||||
type Proposal struct {
|
||||
Content // Proposal content interface
|
||||
|
||||
ProposalID uint64
|
||||
Status ProposalStatus // Status of the Proposal {Pending, Active, Passed, Rejected}
|
||||
FinalTallyResult TallyResult // Result of Tallies
|
||||
|
||||
SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included
|
||||
DepositEndTime time.Time // Time that the Proposal would expire if deposit amount isn't met
|
||||
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
|
||||
|
||||
VotingStartTime time.Time // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
|
||||
VotingEndTime time.Time // Time that the VotingPeriod for this proposal will end and votes will be tallied
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
type Content interface {
|
||||
GetTitle() string
|
||||
GetDescription() string
|
||||
ProposalRoute() string
|
||||
ProposalType() string
|
||||
ValidateBasic() sdk.Error
|
||||
String() string
|
||||
}
|
||||
```
|
||||
|
||||
The `Content` on a proposal is an interface which contains the information about
|
||||
the `Proposal` such as the tile, description, and any notable changes. Also, this
|
||||
`Content` type can by implemented by any module. The `Content`'s `ProposalRoute`
|
||||
returns a string which must be used to route the `Content`'s `Handler` in the
|
||||
governance keeper. This allows the governance keeper to execute proposal logic
|
||||
implemented by any module. If a proposal passes, the handler is executed. Only
|
||||
if the handler is successful does the state get persisted and the proposal finally
|
||||
passes. Otherwise, the proposal is rejected.
|
||||
|
||||
```go
|
||||
type Handler func(ctx sdk.Context, content Content) sdk.Error
|
||||
```
|
||||
|
||||
The `Handler` is responsible for actually executing the proposal and processing
|
||||
any state changes specified by the proposal. It is executed only if a proposal
|
||||
passes during `EndBlock`.
|
||||
|
||||
We also mention a method to update the tally for a given proposal:
|
||||
|
||||
```go
|
||||
func (proposal Proposal) updateTally(vote byte, amount sdk.Dec)
|
||||
```
|
||||
|
||||
## Stores
|
||||
|
||||
*Stores are KVStores in the multi-store. The key to find the store is the first
|
||||
parameter in the list*`
|
||||
|
||||
We will use one KVStore `Governance` to store two mappings:
|
||||
|
||||
* A mapping from `proposalID|'proposal'` to `Proposal`.
|
||||
* A mapping from `proposalID|'addresses'|address` to `Vote`. This mapping allows
|
||||
us to query all addresses that voted on the proposal along with their vote by
|
||||
doing a range query on `proposalID:addresses`.
|
||||
|
||||
|
||||
For pseudocode purposes, here are the two function we will use to read or write in stores:
|
||||
|
||||
* `load(StoreKey, Key)`: Retrieve item stored at key `Key` in store found at key `StoreKey` in the multistore
|
||||
* `store(StoreKey, Key, value)`: Write value `Value` at key `Key` in store found at key `StoreKey` in the multistore
|
||||
|
||||
## Proposal Processing Queue
|
||||
|
||||
**Store:**
|
||||
* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the
|
||||
`ProposalIDs` of proposals that reached `MinDeposit`. During each `EndBlock`,
|
||||
all the proposals that have reached the end of their voting period are processed.
|
||||
To process a finished proposal, the application tallies the votes, computes the
|
||||
votes of each validator and checks if every validator in the validator set has
|
||||
voted. If the proposal is accepted, deposits are refunded. Finally, the proposal
|
||||
content `Handler` is executed.
|
||||
|
||||
And the pseudocode for the `ProposalProcessingQueue`:
|
||||
|
||||
```go
|
||||
in EndBlock do
|
||||
|
||||
for finishedProposalID in GetAllFinishedProposalIDs(block.Time)
|
||||
proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key
|
||||
|
||||
validators = Keeper.getAllValidators()
|
||||
tmpValMap := map(sdk.AccAddress)ValidatorGovInfo
|
||||
|
||||
// Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes
|
||||
for each validator in validators
|
||||
tmpValMap(validator.OperatorAddr).Minus = 0
|
||||
|
||||
// Tally
|
||||
voterIterator = rangeQuery(Governance, <proposalID|'addresses'>) //return all the addresses that voted on the proposal
|
||||
for each (voterAddress, vote) in voterIterator
|
||||
delegations = stakingKeeper.getDelegations(voterAddress) // get all delegations for current voter
|
||||
|
||||
for each delegation in delegations
|
||||
// make sure delegation.Shares does NOT include shares being unbonded
|
||||
tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares
|
||||
proposal.updateTally(vote, delegation.Shares)
|
||||
|
||||
_, isVal = stakingKeeper.getValidator(voterAddress)
|
||||
if (isVal)
|
||||
tmpValMap(voterAddress).Vote = vote
|
||||
|
||||
tallyingParam = load(GlobalParams, 'TallyingParam')
|
||||
|
||||
// Update tally if validator voted they voted
|
||||
for each validator in validators
|
||||
if tmpValMap(validator).HasVoted
|
||||
proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus))
|
||||
|
||||
|
||||
|
||||
// Check if proposal is accepted or rejected
|
||||
totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
|
||||
if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingParam.Veto)
|
||||
// proposal was accepted at the end of the voting period
|
||||
// refund deposits (non-voters already punished)
|
||||
for each (amount, depositor) in proposal.Deposits
|
||||
depositor.AtomBalance += amount
|
||||
|
||||
stateWriter, err := proposal.Handler()
|
||||
if err != nil
|
||||
// proposal passed but failed during state execution
|
||||
proposal.CurrentStatus = ProposalStatusFailed
|
||||
else
|
||||
// proposal pass and state is persisted
|
||||
proposal.CurrentStatus = ProposalStatusAccepted
|
||||
stateWriter.save()
|
||||
else
|
||||
// proposal was rejected
|
||||
proposal.CurrentStatus = ProposalStatusRejected
|
||||
|
||||
store(Governance, <proposalID|'proposal'>, proposal)
|
||||
```
|
||||
@@ -1,190 +0,0 @@
|
||||
# Messages
|
||||
|
||||
## Proposal Submission
|
||||
|
||||
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal`
|
||||
transaction.
|
||||
|
||||
```go
|
||||
type TxGovSubmitProposal struct {
|
||||
Content Content
|
||||
InitialDeposit sdk.Coins
|
||||
Proposer sdk.AccAddress
|
||||
}
|
||||
```
|
||||
|
||||
The `Content` of a `TxGovSubmitProposal` message must have an appropriate router
|
||||
set in the governance module.
|
||||
|
||||
**State modifications:**
|
||||
* Generate new `proposalID`
|
||||
* Create new `Proposal`
|
||||
* Initialise `Proposals` attributes
|
||||
* Decrease balance of sender by `InitialDeposit`
|
||||
* If `MinDeposit` is reached:
|
||||
* Push `proposalID` in `ProposalProcessingQueue`
|
||||
* Transfer `InitialDeposit` from the `Proposer` to the governance `ModuleAccount`
|
||||
|
||||
A `TxGovSubmitProposal` transaction can be handled according to the following
|
||||
pseudocode.
|
||||
|
||||
```go
|
||||
// PSEUDOCODE //
|
||||
// Check if TxGovSubmitProposal is valid. If it is, create proposal //
|
||||
|
||||
upon receiving txGovSubmitProposal from sender do
|
||||
|
||||
if !correctlyFormatted(txGovSubmitProposal)
|
||||
// check if proposal is correctly formatted. Includes fee payment.
|
||||
throw
|
||||
|
||||
initialDeposit = txGovSubmitProposal.InitialDeposit
|
||||
if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms)
|
||||
// InitialDeposit is negative or null OR sender has insufficient funds
|
||||
throw
|
||||
|
||||
if (txGovSubmitProposal.Type != ProposalTypePlainText) OR (txGovSubmitProposal.Type != ProposalTypeSoftwareUpgrade)
|
||||
|
||||
sender.AtomBalance -= initialDeposit.Atoms
|
||||
|
||||
depositParam = load(GlobalParams, 'DepositParam')
|
||||
|
||||
proposalID = generate new proposalID
|
||||
proposal = NewProposal()
|
||||
|
||||
proposal.Title = txGovSubmitProposal.Title
|
||||
proposal.Description = txGovSubmitProposal.Description
|
||||
proposal.Type = txGovSubmitProposal.Type
|
||||
proposal.TotalDeposit = initialDeposit
|
||||
proposal.SubmitTime = <CurrentTime>
|
||||
proposal.DepositEndTime = <CurrentTime>.Add(depositParam.MaxDepositPeriod)
|
||||
proposal.Deposits.append({initialDeposit, sender})
|
||||
proposal.Submitter = sender
|
||||
proposal.YesVotes = 0
|
||||
proposal.NoVotes = 0
|
||||
proposal.NoWithVetoVotes = 0
|
||||
proposal.AbstainVotes = 0
|
||||
proposal.CurrentStatus = ProposalStatusOpen
|
||||
|
||||
store(Proposals, <proposalID|'proposal'>, proposal) // Store proposal in Proposals mapping
|
||||
return proposalID
|
||||
```
|
||||
|
||||
## Deposit
|
||||
|
||||
Once a proposal is submitted, if
|
||||
`Proposal.TotalDeposit < ActiveParam.MinDeposit`, Atom holders can send
|
||||
`TxGovDeposit` transactions to increase the proposal's deposit.
|
||||
|
||||
```go
|
||||
type TxGovDeposit struct {
|
||||
ProposalID int64 // ID of the proposal
|
||||
Deposit sdk.Coins // Number of Atoms to add to the proposal's deposit
|
||||
}
|
||||
```
|
||||
|
||||
**State modifications:**
|
||||
* Decrease balance of sender by `deposit`
|
||||
* Add `deposit` of sender in `proposal.Deposits`
|
||||
* Increase `proposal.TotalDeposit` by sender's `deposit`
|
||||
* If `MinDeposit` is reached:
|
||||
* Push `proposalID` in `ProposalProcessingQueueEnd`
|
||||
* Transfer `Deposit` from the `proposer` to the governance `ModuleAccount`
|
||||
|
||||
A `TxGovDeposit` transaction has to go through a number of checks to be valid.
|
||||
These checks are outlined in the following pseudocode.
|
||||
|
||||
```go
|
||||
// PSEUDOCODE //
|
||||
// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached
|
||||
|
||||
upon receiving txGovDeposit from sender do
|
||||
// check if proposal is correctly formatted. Includes fee payment.
|
||||
|
||||
if !correctlyFormatted(txGovDeposit)
|
||||
throw
|
||||
|
||||
proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>) // proposal is a const key, proposalID is variable
|
||||
|
||||
if (proposal == nil)
|
||||
// There is no proposal for this proposalID
|
||||
throw
|
||||
|
||||
if (txGovDeposit.Deposit.Atoms <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms) OR (proposal.CurrentStatus != ProposalStatusOpen)
|
||||
|
||||
// deposit is negative or null
|
||||
// OR sender has insufficient funds
|
||||
// OR proposal is not open for deposit anymore
|
||||
|
||||
throw
|
||||
|
||||
depositParam = load(GlobalParams, 'DepositParam')
|
||||
|
||||
if (CurrentBlock >= proposal.SubmitBlock + depositParam.MaxDepositPeriod)
|
||||
proposal.CurrentStatus = ProposalStatusClosed
|
||||
|
||||
else
|
||||
// sender can deposit
|
||||
sender.AtomBalance -= txGovDeposit.Deposit.Atoms
|
||||
|
||||
proposal.Deposits.append({txGovVote.Deposit, sender})
|
||||
proposal.TotalDeposit.Plus(txGovDeposit.Deposit)
|
||||
|
||||
if (proposal.TotalDeposit >= depositParam.MinDeposit)
|
||||
// MinDeposit is reached, vote opens
|
||||
|
||||
proposal.VotingStartBlock = CurrentBlock
|
||||
proposal.CurrentStatus = ProposalStatusActive
|
||||
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
|
||||
|
||||
store(Proposals, <txGovVote.ProposalID|'proposal'>, proposal)
|
||||
```
|
||||
|
||||
## Vote
|
||||
|
||||
Once `ActiveParam.MinDeposit` is reached, voting period starts. From there,
|
||||
bonded Atom holders are able to send `TxGovVote` transactions to cast their
|
||||
vote on the proposal.
|
||||
|
||||
```go
|
||||
type TxGovVote struct {
|
||||
ProposalID int64 // proposalID of the proposal
|
||||
Vote byte // option from OptionSet chosen by the voter
|
||||
}
|
||||
```
|
||||
|
||||
**State modifications:**
|
||||
* Record `Vote` of sender
|
||||
|
||||
*Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker*
|
||||
|
||||
|
||||
Next is a pseudocode proposal of the way `TxGovVote` transactions are
|
||||
handled:
|
||||
|
||||
```go
|
||||
// PSEUDOCODE //
|
||||
// Check if TxGovVote is valid. If it is, count vote//
|
||||
|
||||
upon receiving txGovVote from sender do
|
||||
// check if proposal is correctly formatted. Includes fee payment.
|
||||
|
||||
if !correctlyFormatted(txGovDeposit)
|
||||
throw
|
||||
|
||||
proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>)
|
||||
|
||||
if (proposal == nil)
|
||||
// There is no proposal for this proposalID
|
||||
throw
|
||||
|
||||
|
||||
if (proposal.CurrentStatus == ProposalStatusActive)
|
||||
|
||||
|
||||
// Sender can vote if
|
||||
// Proposal is active
|
||||
// Sender has some bonds
|
||||
|
||||
store(Governance, <txGovVote.ProposalID|'addresses'|sender>, txGovVote.Vote) // Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end.
|
||||
```
|
||||
@@ -1,51 +0,0 @@
|
||||
# Events
|
||||
|
||||
The governance module emits the following events:
|
||||
|
||||
## EndBlocker
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|-------------------|-----------------|------------------|
|
||||
| inactive_proposal | proposal_id | {proposalID} |
|
||||
| inactive_proposal | proposal_result | {proposalResult} |
|
||||
| active_proposal | proposal_id | {proposalID} |
|
||||
| active_proposal | proposal_result | {proposalResult} |
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgSubmitProposal
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------------------|---------------------|-----------------|
|
||||
| submit_proposal | proposal_id | {proposalID} |
|
||||
| submit_proposal [0] | voting_period_start | {proposalID} |
|
||||
| proposal_deposit | amount | {depositAmount} |
|
||||
| proposal_deposit | proposal_id | {proposalID} |
|
||||
| message | module | governance |
|
||||
| message | action | submit_proposal |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
* [0] Event only emitted if the voting period starts during the submission.
|
||||
|
||||
### MsgVote
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------------|---------------|-----------------|
|
||||
| proposal_vote | option | {voteOption} |
|
||||
| proposal_vote | proposal_id | {proposalID} |
|
||||
| message | module | governance |
|
||||
| message | action | vote |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgDeposit
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------------|-----------------|
|
||||
| proposal_deposit | amount | {depositAmount} |
|
||||
| proposal_deposit | proposal_id | {proposalID} |
|
||||
| proposal_deposit [0] | voting_period_start | {proposalID} |
|
||||
| message | module | governance |
|
||||
| message | action | deposit |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
* [0] Event only emitted if the voting period starts during the submission.
|
||||
@@ -1,26 +0,0 @@
|
||||
# Future Improvements
|
||||
|
||||
The current documentation only describes the minimum viable product for the
|
||||
governance module. Future improvements may include:
|
||||
|
||||
* **`BountyProposals`:** If accepted, a `BountyProposal` creates an open
|
||||
bounty. The `BountyProposal` specifies how many Atoms will be given upon
|
||||
completion. These Atoms will be taken from the `reserve pool`. After a
|
||||
`BountyProposal` is accepted by governance, anybody can submit a
|
||||
`SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a
|
||||
`BountyProposal` is accepted, the corresponding funds in the `reserve pool`
|
||||
are locked so that payment can always be honored. In order to link a
|
||||
`SoftwareUpgradeProposal` to an open bounty, the submitter of the
|
||||
`SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute.
|
||||
If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by
|
||||
governance, the funds that were reserved are automatically transferred to the
|
||||
submitter.
|
||||
* **Complex delegation:** Delegators could choose other representatives than
|
||||
their validators. Ultimately, the chain of representatives would always end
|
||||
up to a validator, but delegators could inherit the vote of their chosen
|
||||
representative before they inherit the vote of their validator. In other
|
||||
words, they would only inherit the vote of their validator if their other
|
||||
appointed representative did not vote.
|
||||
* **Better process for proposal review:** There would be two parts to
|
||||
`proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to
|
||||
reward third party auditors.
|
||||
@@ -1,24 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The governance module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|---------------|--------|----------------------------------------------------------------------------------------------------|
|
||||
| depositparams | object | {"min_deposit":[{"denom":"uatom","amount":"10000000"}],"max_deposit_period":"172800000000000"} |
|
||||
| votingparams | object | {"voting_period":"172800000000000"} |
|
||||
| tallyparams | object | {"quorum":"0.334000000000000000","threshold":"0.500000000000000000","veto":"0.334000000000000000"} |
|
||||
|
||||
## SubKeys
|
||||
|
||||
| Key | Type | Example |
|
||||
|--------------------|------------------|-----------------------------------------|
|
||||
| min_deposit | array (coins) | [{"denom":"uatom","amount":"10000000"}] |
|
||||
| max_deposit_period | string (time ns) | "172800000000000" |
|
||||
| voting_period | string (time ns) | "172800000000000" |
|
||||
| quorum | string (dec) | "0.334000000000000000" |
|
||||
| threshold | string (dec) | "0.500000000000000000" |
|
||||
| veto | string (dec) | "0.334000000000000000" |
|
||||
|
||||
__NOTE__: The governance module contains parameters that are objects unlike other
|
||||
modules. If only a subset of parameters are desired to be changed, only they need
|
||||
to be included and not the entire parameter object structure.
|
||||
@@ -1,50 +0,0 @@
|
||||
# Governance module specification
|
||||
|
||||
## Abstract
|
||||
|
||||
This paper specifies the Governance module of the Cosmos-SDK, which was first
|
||||
described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in
|
||||
June 2016.
|
||||
|
||||
The module enables Cosmos-SDK based blockchain to support an on-chain governance
|
||||
system. In this system, holders of the native staking token of the chain can vote
|
||||
on proposals on a 1 token 1 vote basis. Next is a list of features the module
|
||||
currently supports:
|
||||
|
||||
- **Proposal submission:** Users can submit proposals with a deposit. Once the
|
||||
minimum deposit is reached, proposal enters voting period
|
||||
- **Vote:** Participants can vote on proposals that reached MinDeposit
|
||||
- **Inheritance and penalties:** Delegators inherit their validator's vote if
|
||||
they don't vote themselves.
|
||||
- **Claiming deposit:** Users that deposited on proposals can recover their
|
||||
deposits if the proposal was accepted OR if the proposal never entered voting period.
|
||||
|
||||
This module will be used in the Cosmos Hub, the first Hub in the Cosmos network.
|
||||
Features that may be added in the future are described in [Future Improvements](05_future_improvements.md).
|
||||
|
||||
## Contents
|
||||
|
||||
The following specification uses *ATOM* as the native staking token. The module
|
||||
can be adapted to any Proof-Of-Stake blockchain by replacing *ATOM* with the native
|
||||
staking token of the chain.
|
||||
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
- [Proposal submission](01_concepts.md#proposal-submission)
|
||||
- [Vote](01_concepts.md#vote)
|
||||
- [Software Upgrade](01_concepts.md#software-upgrade)
|
||||
2. **[State](02_state.md)**
|
||||
- [Parameters and base types](02_state.md#parameters-and-base-types)
|
||||
- [Deposit](02_state.md#deposit)
|
||||
- [ValidatorGovInfo](02_state.md#validatorgovinfo)
|
||||
- [Proposals](02_state.md#proposals)
|
||||
- [Stores](02_state.md#stores)
|
||||
- [Proposal Processing Queue](02_state.md#proposal-processing-queue)
|
||||
3. **[Messages](03_messages.md)**
|
||||
- [Proposal Submission](03_messages.md#proposal-submission)
|
||||
- [Deposit](03_messages.md#deposit)
|
||||
- [Vote](03_messages.md#vote)
|
||||
4. **[Events](04_events.md)**
|
||||
- [EndBlocker](04_events.md#endblocker)
|
||||
- [Handlers](04_events.md#handlers)
|
||||
5. **[Future Improvements](05_future_improvements.md)**
|
||||
6. **[Parameters](06_params.md)**
|
||||
@@ -1,22 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
## The Minting Mechanism
|
||||
|
||||
The minting mechanism was designed to:
|
||||
- allow for a flexible inflation rate determined by market demand targeting a particular bonded-stake ratio
|
||||
- effect a balance between market liquidity and staked supply
|
||||
|
||||
In order to best determine the appropriate market rate for inflation rewards, a
|
||||
moving change rate is used. The moving change rate mechanism ensures that if
|
||||
the % bonded is either over or under the goal %-bonded, the inflation rate will
|
||||
adjust to further incentivize or disincentivize being bonded, respectively. Setting the goal
|
||||
%-bonded at less than 100% encourages the network to maintain some non-staked tokens
|
||||
which should help provide some liquidity.
|
||||
|
||||
It can be broken down in the following way:
|
||||
- If the inflation rate is below the goal %-bonded the inflation rate will
|
||||
increase until a maximum value is reached
|
||||
- If the goal % bonded (67% in Cosmos-Hub) is maintained, then the inflation
|
||||
rate will stay constant
|
||||
- If the inflation rate is above the goal %-bonded the inflation rate will
|
||||
decrease until a minimum value is reached
|
||||
@@ -1,31 +0,0 @@
|
||||
# State
|
||||
|
||||
## Minter
|
||||
|
||||
The minter is a space for holding current inflation information.
|
||||
|
||||
- Minter: `0x00 -> amino(minter)`
|
||||
|
||||
```go
|
||||
type Minter struct {
|
||||
Inflation sdk.Dec // current annual inflation rate
|
||||
AnnualProvisions sdk.Dec // current annual exptected provisions
|
||||
}
|
||||
```
|
||||
|
||||
## Params
|
||||
|
||||
Minting params are held in the global params store.
|
||||
|
||||
- Params: `mint/params -> amino(params)`
|
||||
|
||||
```go
|
||||
type Params struct {
|
||||
MintDenom string // type of coin to mint
|
||||
InflationRateChange sdk.Dec // maximum annual change in inflation rate
|
||||
InflationMax sdk.Dec // maximum inflation rate
|
||||
InflationMin sdk.Dec // minimum inflation rate
|
||||
GoalBonded sdk.Dec // goal of percent bonded atoms
|
||||
BlocksPerYear uint64 // expected blocks per year
|
||||
}
|
||||
```
|
||||
@@ -1,50 +0,0 @@
|
||||
# Begin-Block
|
||||
|
||||
Minting parameters are recalculated and inflation
|
||||
paid at the beginning of each block.
|
||||
|
||||
## NextInflationRate
|
||||
|
||||
The target annual inflation rate is recalculated each block.
|
||||
The inflation is also subject to a rate change (positive or negative)
|
||||
depending on the distance from the desired ratio (67%). The maximum rate change
|
||||
possible is defined to be 13% per year, however the annual inflation is capped
|
||||
as between 7% and 20%.
|
||||
|
||||
```
|
||||
NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) {
|
||||
inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange
|
||||
inflationRateChange = inflationRateChangePerYear/blocksPerYr
|
||||
|
||||
// increase the new annual inflation for this next cycle
|
||||
inflation += inflationRateChange
|
||||
if inflation > params.InflationMax {
|
||||
inflation = params.InflationMax
|
||||
}
|
||||
if inflation < params.InflationMin {
|
||||
inflation = params.InflationMin
|
||||
}
|
||||
|
||||
return inflation
|
||||
}
|
||||
```
|
||||
|
||||
## NextAnnualProvisions
|
||||
|
||||
Calculate the annual provisions based on current total supply and inflation
|
||||
rate. This parameter is calculated once per block.
|
||||
|
||||
```
|
||||
NextAnnualProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Dec) {
|
||||
return Inflation * totalSupply
|
||||
```
|
||||
|
||||
## BlockProvision
|
||||
|
||||
Calculate the provisions generated for each block based on current annual provisions. The provisions are then minted by the `mint` module's `ModuleMinterAccount` and then transferred to the `auth`'s `FeeCollector` `ModuleAccount`.
|
||||
|
||||
```
|
||||
BlockProvision(params Params) sdk.Coin {
|
||||
provisionAmt = AnnualProvisions/ params.BlocksPerYear
|
||||
return sdk.NewCoin(params.MintDenom, provisionAmt.Truncate())
|
||||
```
|
||||
@@ -1,12 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The minting module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|---------------------|-----------------|------------------------|
|
||||
| MintDenom | string | "uatom" |
|
||||
| InflationRateChange | string (dec) | "0.130000000000000000" |
|
||||
| InflationMax | string (dec) | "0.200000000000000000" |
|
||||
| InflationMin | string (dec) | "0.070000000000000000" |
|
||||
| GoalBonded | string (dec) | "0.670000000000000000" |
|
||||
| BlocksPerYear | string (uint64) | "6311520" |
|
||||
@@ -1,12 +0,0 @@
|
||||
# Events
|
||||
|
||||
The minting module emits the following events:
|
||||
|
||||
## BeginBlocker
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|------|-------------------|--------------------|
|
||||
| mint | bonded_ratio | {bondedRatio} |
|
||||
| mint | inflation | {inflation} |
|
||||
| mint | annual_provisions | {annualProvisions} |
|
||||
| mint | amount | {amount} |
|
||||
@@ -1,16 +0,0 @@
|
||||
# Mint Specification
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[Concept](01_concept.md)**
|
||||
2. **[State](02_state.md)**
|
||||
- [Minter](02_state.md#minter)
|
||||
- [Params](02_state.md#params)
|
||||
3. **[Begin-Block](03_begin_block.md)**
|
||||
- [NextInflationRate](03_begin_block.md#nextinflationrate)
|
||||
- [NextAnnualProvisions](03_begin_block.md#nextannualprovisions)
|
||||
- [BlockProvision](03_begin_block.md#blockprovision)
|
||||
4. **[Parameters](04_params.md)**
|
||||
5. **[Events](05_events.md)**
|
||||
- [BeginBlocker](05_events.md#beginblocker)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
## NFT
|
||||
|
||||
The `NFT` Interface inherits the BaseNFT struct and includes getter functions for the asset data. It also includes a Stringer function in order to print the struct. The interface may change if metadata is moved to it’s own module as it might no longer be necessary for the flexibility of an interface.
|
||||
|
||||
```go
|
||||
// NFT non fungible token interface
|
||||
type NFT interface {
|
||||
GetID() string // unique identifier of the NFT
|
||||
GetOwner() sdk.AccAddress // gets owner account of the NFT
|
||||
SetOwner(address sdk.AccAddress) // gets owner account of the NFT
|
||||
GetTokenURI() string // metadata field: URI to retrieve the of chain metadata of the NFT
|
||||
EditMetadata(tokenURI string) // edit metadata of the NFT
|
||||
String() string // string representation of the NFT object
|
||||
}
|
||||
```
|
||||
|
||||
## Collections
|
||||
|
||||
A Collection is used to organized sets of NFTs. It contains the denomination of the NFT instead of storing it within each NFT. This saves storage space by removing redundancy.
|
||||
|
||||
```go
|
||||
// Collection of non fungible tokens
|
||||
type Collection struct {
|
||||
Denom string `json:"denom,omitempty"` // name of the collection; not exported to clients
|
||||
NFTs []*NFT `json:"nfts"` // NFTs that belongs to a collection
|
||||
}
|
||||
```
|
||||
|
||||
## Owner
|
||||
|
||||
An Owner is a struct that includes information about all NFTs owned by a single account. It would be possible to retrieve this information by looping through all Collections but that process could become computationally prohibitive so a more efficient retrieval system is to store redundant information limited to the token ID by owner.
|
||||
|
||||
```go
|
||||
// Owner of non fungible tokens
|
||||
type Owner struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
IDCollections IDCollections `json:"IDCollections"`
|
||||
}
|
||||
```
|
||||
|
||||
An `IDCollection` is similar to a `Collection` except instead of containing NFTs it only contains an array of `NFT` IDs. This saves storage by avoiding redundancy.
|
||||
|
||||
```go
|
||||
// IDCollection of non fungible tokens
|
||||
type IDCollection struct {
|
||||
Denom string `json:"denom"`
|
||||
IDs []string `json:"IDs"`
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# State
|
||||
|
||||
## Collections
|
||||
|
||||
As all NFTs belong to a specific `Collection`, they are kept on store in an array
|
||||
within each `Collection`. Every time an NFT that belongs to a collection is updated,
|
||||
it needs to be updated on the corresponding NFT array on the corresponding `Collection`.
|
||||
`denomHash` is used as part of the key to limit the length of the `denomBytes` which is
|
||||
a hash of `denomBytes` made from the tendermint [tmhash library](https://github.com/tendermint/tendermint/tree/master/crypto/tmhash).
|
||||
|
||||
- Collections: `0x00 | denomHash -> amino(Collection)`
|
||||
- denomHash: `tmhash(denomBytes)`
|
||||
|
||||
## Owners
|
||||
|
||||
The ownership of an NFT is set initially when an NFT is minted and needs to be
|
||||
updated every time there's a transfer or when an NFT is burned.
|
||||
|
||||
- Owners: `0x01 | addressBytes | denomHash -> amino(Owner)`
|
||||
- denomHash: `tmhash(denomBytes)`
|
||||
@@ -1,86 +0,0 @@
|
||||
# Messages
|
||||
|
||||
## MsgTransferNFT
|
||||
|
||||
This is the most commonly expected MsgType to be supported across chains. While each application specific blockchain will have very different adoption of the `MsgMintNFT`, `MsgBurnNFT` and `MsgEditNFTMetadata` it should be expected that most chains support the ability to transfer ownership of the NFT asset. The exception to this would be non-transferable NFTs that might be attached to reputation or some asset which should not be transferable. It still makes sense for this to be represented as an NFT because there are common queriers which will remain relevant to the NFT type even if non-transferable. This Message will fail if the NFT does not exist. By default it will not fail if the transfer is executed by someone beside the owner. **It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.**
|
||||
|
||||
| **Field** | **Type** | **Description** |
|
||||
|:----------|:-----------------|:--------------------------------------------------------------------------------------------------------------|
|
||||
| Sender | `sdk.AccAddress` | The account address of the user sending the NFT. By default it is __not__ required that the sender is also the owner of the NFT. |
|
||||
| Recipient | `sdk.AccAddress` | The account address who will receive the NFT as a result of the transfer transaction. |
|
||||
| Denom | `string` | The denomination of the NFT, necessary as multiple denominations are able to be represented on each chain. |
|
||||
| ID | `string` | The unique ID of the NFT being transferred |
|
||||
|
||||
```go
|
||||
// MsgTransferNFT defines a TransferNFT message
|
||||
type MsgTransferNFT struct {
|
||||
Sender sdk.AccAddress
|
||||
Recipient sdk.AccAddress
|
||||
Denom string
|
||||
ID string
|
||||
}
|
||||
```
|
||||
|
||||
## MsgEditNFTMetadata
|
||||
|
||||
This message type allows the `TokenURI` to be updated. By default anyone can execute this Message type. **It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.**
|
||||
|
||||
| **Field** | **Type** | **Description** |
|
||||
|:------------|:-----------------|:-----------------------------------------------------------------------------------------------------------|
|
||||
| Sender | `sdk.AccAddress` | The creator of the message |
|
||||
| ID | `string` | The unique ID of the NFT being edited |
|
||||
| Denom | `string` | The denomination of the NFT, necessary as multiple denominations are able to be represented on each chain. |
|
||||
| TokenURI | `string` | The URI pointing to a JSON object that contains subsequent metadata information off-chain |
|
||||
|
||||
```go
|
||||
// MsgEditNFTMetadata edits an NFT's metadata
|
||||
type MsgEditNFTMetadata struct {
|
||||
Sender sdk.AccAddress
|
||||
ID string
|
||||
Denom string
|
||||
TokenURI string
|
||||
}
|
||||
```
|
||||
|
||||
## MsgMintNFT
|
||||
|
||||
This message type is used for minting new tokens. If a new `NFT` is minted under a new `Denom`, a new `Collection` will also be created, otherwise the `NFT` is added to the existing `Collection`. If a new `NFT` is minted by a new account, a new `Owner` is created, otherwise the `NFT` `ID` is added to the existing `Owner`'s `IDCollection`. By default anyone can execute this Message type. **It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.**
|
||||
|
||||
| **Field** | **Type** | **Description** |
|
||||
|:------------|:-----------------|:-----------------------------------------------------------------------------------------|
|
||||
| Sender | `sdk.AccAddress` | The sender of the Message |
|
||||
| Recipient | `sdk.AccAddress` | The recipiet of the new NFT |
|
||||
| ID | `string` | The unique ID of the NFT being minted |
|
||||
| Denom | `string` | The denomination of the NFT. |
|
||||
| TokenURI | `string` | The URI pointing to a JSON object that contains subsequent metadata information off-chain |
|
||||
|
||||
```go
|
||||
// MsgMintNFT defines a MintNFT message
|
||||
type MsgMintNFT struct {
|
||||
Sender sdk.AccAddress
|
||||
Recipient sdk.AccAddress
|
||||
ID string
|
||||
Denom string
|
||||
TokenURI string
|
||||
}
|
||||
```
|
||||
|
||||
### MsgBurnNFT
|
||||
|
||||
This message type is used for burning tokens which destroys and deletes them. By default anyone can execute this Message type. **It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.**
|
||||
|
||||
|
||||
| **Field** | **Type** | **Description** |
|
||||
|:----------|:-----------------|:---------------------------------------------------|
|
||||
| Sender | `sdk.AccAddress` | The account address of the user burning the token. |
|
||||
| ID | `string` | The ID of the Token. |
|
||||
| Denom | `string` | The Denom of the Token. |
|
||||
|
||||
```go
|
||||
// MsgBurnNFT defines a BurnNFT message
|
||||
type MsgBurnNFT struct {
|
||||
Sender sdk.AccAddress
|
||||
ID string
|
||||
Denom string
|
||||
}
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
# Events
|
||||
|
||||
The nft module emits the following events:
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgTransferNFT
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|--------------|---------------|--------------------|
|
||||
| transfer_nft | denom | {nftDenom} |
|
||||
| transfer_nft | nft-id | {nftID} |
|
||||
| transfer_nft | recipient | {recipientAddress} |
|
||||
| message | module | nft |
|
||||
| message | action | transfer_nft |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgEditNFTMetadata
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|-------------------|---------------|-------------------|
|
||||
| edit_nft_metadata | denom | {nftDenom} |
|
||||
| edit_nft_metadata | nft-id | {nftID} |
|
||||
| message | module | nft |
|
||||
| message | action | edit_nft_metadata |
|
||||
| message | sender | {senderAddress} |
|
||||
| message | token-uri | {tokenURI} |
|
||||
|
||||
### MsgMintNFT
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------|---------------|-----------------|
|
||||
| mint_nft | denom | {nftDenom} |
|
||||
| mint_nft | nft-id | {nftID} |
|
||||
| message | module | nft |
|
||||
| message | action | mint_nft |
|
||||
| message | sender | {senderAddress} |
|
||||
| message | token-uri | {tokenURI} |
|
||||
|
||||
### MsgBurnNFTs
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------|---------------|-----------------|
|
||||
| burn_nft | denom | {nftDenom} |
|
||||
| burn_nft | nft-id | {nftID} |
|
||||
| message | module | nft |
|
||||
| message | action | burn_nft |
|
||||
| message | sender | {senderAddress} |
|
||||
@@ -1,5 +0,0 @@
|
||||
# Future Improvements
|
||||
|
||||
There's interesting work that could be done about moving metadata into its own module. This could act as one of the `tokenURI` endpoints if a chain chooses to offer storage as a solution. Furthermore on-chain metadata can be trusted to a higher degree and might be used in secondary actions like price evaluation. Moving metadata to it's own module could be useful for the Bank Module as well. It would be able to describe attributes like decimal places and information regarding vesting schedules. It would be needed to have a level of introspection to describe the content without actually delivering the content for client libraries to interact with it. Using schema.org as a common location to settle metadata schema structure would be a good and impartial place to do so.
|
||||
|
||||
Inter-Blockchain Communication will need to develop its own Message types that allow NFTs to be transferred across chains. Making sure that spec is able to support the NFTs created by this module should be easy. What might be more complicated is a transfer that includes optional metadata so that a receiving chain has the option of parsing and storing it instead of making IBC queries when that data needs to be accessed (assuming that information stays up to date).
|
||||
@@ -1,7 +0,0 @@
|
||||
# Appendix
|
||||
|
||||
* Cosmos SDK: [PR #4209](https://github.com/cosmos/cosmos-sdk/pull/4209)
|
||||
* Cosmos SDK: [Issue #4046](https://github.com/cosmos/cosmos-sdk/issues/4046)
|
||||
* Interchain Standards: [ICS #17](https://github.com/cosmos/ics/issues/30)
|
||||
* Binance: [BEP #7](https://github.com/binance-chain/BEPs/pull/7)
|
||||
* Ethereum: [EIP #721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md)
|
||||
@@ -1,99 +0,0 @@
|
||||
# NFT Specification
|
||||
|
||||
## Overview
|
||||
|
||||
The NFT Module described here is meant to be used as a module across chains for managing non-fungible token that represent individual assets with unique features. This standard was first developed on Ethereum within the ERC-721 and the subsequent EIP of the same name. This standard utilized the features of the Ethereum blockchain as well as the restrictions. The subsequent ERC-1155 standard addressed some of the restrictions of Ethereum regarding storage costs and semi-fungible assets.
|
||||
|
||||
NFTs on application specific blockchains share some but not all features as their Ethereum brethren. Since application specific blockchains are more flexible in how their resources are utilized it makes sense that should have the option of exploiting those resources. This includes the aility to use strings as IDs and to optionally store metadata on chain. The user-flow of composability with smart contracts should also be rethought on application specific blockchains with regard to Inter-Blockchain Communication as it is a different design experience from communication between smart contracts.
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[Concepts](./01_concepts.md)**
|
||||
- [NFT](./01_concepts.md#nft)
|
||||
- [Collections](./01_concepts.md#collections)
|
||||
2. **[State](./02_state.md)**
|
||||
- [Collections](./02_state.md#collections)
|
||||
- [Owners](./02_state.md#owners)
|
||||
3. **[Messages](./03_messages.md)**
|
||||
- [Transfer NFT](./03_messages.md#transfer-nft)
|
||||
- [Edit Metadata](./03_messages.md#edit-metadata)
|
||||
- [Mint NFT](./03_messages.md#mint-nft)
|
||||
- [Burn NFT](./03_messages.md#burn-nft)
|
||||
4. **[Events](./04_events.md)**
|
||||
5. **[Future Improvements](./05_future_improvements.md)**
|
||||
|
||||
## A Note on Metadata & IBC
|
||||
|
||||
The BaseNFT includes `tokenURI` in order to be backwards compatible with Ethereum based NFTs. However the `NFT` type is an interface that allows arbitrary metadata to be stored on chain should it need be. Originally the module included `name`, `description` and `image` to demonstrate these capabilities. They were removed in order for the NFT to be more efficient for use cases that don't include a need for that information to be stored on chain. A demonstration of including them will be included in a sample app. It is also under discussion to move all metadata to a separate module that can handle arbitrary amounts of data on chain and can be used to describe assets beyond Non-Fungible Tokens, like normal Fungible Tokens `Coin` that could describe attributes like decimal places and vesting status.
|
||||
|
||||
A stand-alone metadata Module would allow for independent standards to evolve regarding arbitrary asset types with expanding precision. The standards supported by [http://schema.org](http://schema.org) and the process of adding nested information is being considered as a starting point for that standard. The Blockchain Gaming Alliance is working on a metadata standard to be used for specifically blockchain gaming assets.
|
||||
|
||||
With regards to Inter-Blockchain Communication the responsibility of the integrity of the metadata should be left to the origin chain. If a secondary chain was responsible for storing the source of truth of the metadata for an asset tracking that source of truth would become difficult if not impossible to track. Since origin chains are where the design and use of the NFT is determined, it should be up to that origin chain to decide who can update metadata and under what circumstances. Secondary chains can use IBC queriers to check needed metadata or keep redundant copies of the metadata locally when they receive the NFT originally. In that case it should be up to te secondary chain to keep the metadata in sync if need be, similar to how layer 2 solutions keep metadata in sync with a source of truth using events.
|
||||
|
||||
## Custom App-Specific Handlers
|
||||
|
||||
Each message type comes with a default handler that can be used by default but will most likely be too limited for each use case. In order to make them useful for as many situations as possible, there are very few limitations on who can execute the Messages and do things like mint, burn or edit metadata. We recommend that custom handlers are created to add in custom logic and restrictions over when the Message types can be executed. Below is an example implementation for initializing the module within the module manager so that a custom handler can be added. This can be seen in the example [NFT app](https://github.com/okwme/cosmos-nft).
|
||||
|
||||
```go
|
||||
// custom-handler.go
|
||||
|
||||
// OverrideNFTModule overrides the NFT module for custom handlers
|
||||
type OverrideNFTModule struct {
|
||||
nft.AppModule
|
||||
k nft.Keeper
|
||||
}
|
||||
|
||||
// NewHandler overwrites the legacy NewHandler in order to allow custom logic for handling the messages
|
||||
func (am OverrideNFTModule) NewHandler() sdk.Handler {
|
||||
return CustomNFTHandler(am.k)
|
||||
}
|
||||
|
||||
// NewOverrideNFTModule generates a new NFT Module
|
||||
func NewOverrideNFTModule(appModule nft.AppModule, keeper nft.Keeper) OverrideNFTModule {
|
||||
return OverrideNFTModule{
|
||||
AppModule: appModule,
|
||||
k: keeper,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can see here that `OverrideNFTModule` is the same as `nft.AppModule` except for the `NewHandler()` method. This method now returns a new Handler called `CustomNFTHandler`. This custom handler can be seen below:
|
||||
|
||||
```go
|
||||
// CustomNFTHandler routes the messages to the handlers
|
||||
func CustomNFTHandler(k keeper.Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case types.MsgTransferNFT:
|
||||
return nft.HandleMsgTransferNFT(ctx, msg, k)
|
||||
case types.MsgEditNFTMetadata:
|
||||
return nft.HandleMsgEditNFTMetadata(ctx, msg, k)
|
||||
case types.MsgMintNFT:
|
||||
return HandleMsgMintNFTCustom(ctx, msg, k) // <-- This one is custom, the others fall back onto the default
|
||||
case types.MsgBurnNFT:
|
||||
return nft.HandleMsgBurnNFT(ctx, msg, k)
|
||||
default:
|
||||
errMsg := fmt.Sprintf("unrecognized nft message type: %T", msg)
|
||||
return sdk.ErrUnknownRequest(errMsg).Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HandleMsgMintNFTCustom is a custom handler that handles MsgMintNFT
|
||||
func HandleMsgMintNFTCustom(ctx sdk.Context, msg types.MsgMintNFT, k keeper.Keeper,
|
||||
) sdk.Result {
|
||||
|
||||
isTwilight := checkTwilight(ctx)
|
||||
|
||||
if isTwilight {
|
||||
return nft.HandleMsgMintNFT(ctx, msg, k)
|
||||
}
|
||||
|
||||
errMsg := fmt.Sprintf("Can't mint astral bodies outside of twilight!")
|
||||
return sdk.ErrUnknownRequest(errMsg).Result()
|
||||
}
|
||||
```
|
||||
|
||||
The default handlers are imported here with the NFT module and used for `MsgTransferNFT`, `MsgEditNFTMetadata` and `MsgBurnNFT`. The `MsgMintNFT` however is handled with a custom function called `HandleMsgMintNFTCustom`. This custom function also utilizes the imported NFT module handler `HandleMsgMintNFT`, but only after certain conditions are checked. In this case it checks a function called `checkTwilight` which returns a boolean. Only if `isTwilight` is true will the Message succeed.
|
||||
|
||||
This pattern of inheriting and utilizing the module handlers wrapped in custom logic should allow each application specific blockchain to use the NFT while customizing it to their specific requirements.
|
||||
@@ -1,19 +0,0 @@
|
||||
# Keeper
|
||||
|
||||
In the app initialization stage, `Keeper.Subspace(Paramspace)` is passed to the user modules, and the subspaces are stored in `Keeper.spaces`. Later it can be retrieved with `Keeper.GetSubspace`, so the keepers holding `Keeper` can access to any subspace. For example, Gov module can take `Keeper` as its argument and modify parameter of any subspace when a `ParameterChangeProposal` is accepted.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
type MasterKeeper struct {
|
||||
pk params.Keeper
|
||||
}
|
||||
|
||||
func (k MasterKeeper) SetParam(ctx sdk.Context, space string, key string, param interface{}) {
|
||||
space, ok := k.ps.GetSubspace(space)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
space.Set(ctx, key, param)
|
||||
}
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
# Subspace
|
||||
|
||||
`Subspace` is a prefixed subspace of the parameter store. Each module who use the parameter store will take a `Subspace`, not the `Keeper`, to isolate permission to access.
|
||||
|
||||
## Key
|
||||
|
||||
Parameter keys are human readable alphanumeric strings. A parameter for the key `"ExampleParameter"` is stored under `[]byte("SubspaceName" + "/" + "ExampleParameter")`, where `"SubspaceName"` is the name of the subspace.
|
||||
|
||||
Subkeys are secondary parameter keys those are used along with a primary parameter key. Subkeys can be used for grouping or dynamic parameter key generation during runtime.
|
||||
|
||||
## KeyTable
|
||||
|
||||
All of the paramter keys that will be used should be registered at the compile time. `KeyTable` is essentially a `map[string]attribute`, where the `string` is a parameter key.
|
||||
|
||||
Currently, `attribute` only consists of `reflect.Type`, which indicates the parameter type. It is needed even if the state machine has no error, because the paraeter can be modified externally, for example via the governance.
|
||||
|
||||
Only primary keys have to be registered on the `KeyTable`. Subkeys inherit the attribute of the primary key.
|
||||
|
||||
## ParamSet
|
||||
|
||||
Modules often define a struct of parameters. Instead of calling methods with each of those parameters, when the struct implements `ParamSet`, it can be used with the following methods:
|
||||
|
||||
* `KeyTable.RegisterParamSet()`: registers all parameters in the struct
|
||||
* `Subspace.{Get, Set}ParamSet()`: Get to & Set from the struct
|
||||
|
||||
The implementor should be a pointer in order to use `GetParamSet()`
|
||||
@@ -1,23 +0,0 @@
|
||||
# Params module specification
|
||||
|
||||
## Abstract
|
||||
|
||||
Package params provides a globally available parameter store.
|
||||
|
||||
There are two main types, Keeper and Subspace. Subspace is an isolated namespace for a
|
||||
paramstore, where keys are prefixed by preconfigured spacename. Keeper has a
|
||||
permission to access all existing spaces.
|
||||
|
||||
Subspace can be used by the individual keepers, who needs a private parameter store
|
||||
that the other keeper cannot modify. Keeper can be used by the Governance keeper,
|
||||
who need to modify any parameter in case of the proposal passes.
|
||||
|
||||
The following contents explains how to use params module for master and user modules.
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[Keeper](01_keeper.md)**
|
||||
2. **[Subspace](02_subspace.md)**
|
||||
- [Key](02_subspace.md#key)
|
||||
- [KeyTable](02_subspace.md#keytable)
|
||||
- [ParamSet](02_subspace.md#paramset)
|
||||
@@ -1,55 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
## States
|
||||
|
||||
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
|
||||
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
|
||||
information partaining to validator's liveness and other infraction related
|
||||
attributes.
|
||||
|
||||
## Tombstone Caps
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
## Infraction Timelines
|
||||
|
||||
To illustrate how the `x/slashing` module handles submitted evidence through
|
||||
Tendermint consensus, consider the following examples:
|
||||
|
||||
__Definitions__:
|
||||
|
||||
*[* : timeline start
|
||||
*]* : timeline end
|
||||
*C<sub>n</sub>* : infraction `n` committed
|
||||
*D<sub>n</sub>* : infraction `n` discovered
|
||||
*V<sub>b</sub>* : validator bonded
|
||||
*V<sub>u</sub>* : validator unbonded
|
||||
|
||||
### Single Double Sign Infraction
|
||||
|
||||
<----------------->
|
||||
[----------C<sub>1</sub>----D<sub>1</sub>,V<sub>u</sub>-----]
|
||||
|
||||
A single infraction is committed then later discovered, at which point the
|
||||
validator is unbonded and slashed at the full amount for the infraction.
|
||||
|
||||
### Multiple Double Sign Infractions
|
||||
|
||||
<--------------------------->
|
||||
[----------C<sub>1</sub>--C<sub>2</sub>---C<sub>3</sub>---D<sub>1</sub>,D<sub>2</sub>,D<sub>3</sub>V<sub>u</sub>-----]
|
||||
|
||||
Multiple infractions are committed and then later discovered, at which point the
|
||||
validator is jailed and slashed for only one infraction. Because the validator
|
||||
is also tombstoned, they can not rejoin the validator set.
|
||||
@@ -1,62 +0,0 @@
|
||||
# State
|
||||
|
||||
## Signing Info (Liveness)
|
||||
|
||||
Every block includes a set of precommits by the validators for the previous block,
|
||||
known as the `LastCommitInfo` provided by Tendermint. A `LastCommitInfo` is valid so
|
||||
long as it contains precommits from +2/3 of total voting power.
|
||||
|
||||
Proposers are incentivized to include precommits from all validators in the `LastCommitInfo`
|
||||
by receiving additional fees proportional to the difference between the voting
|
||||
power included in the `LastCommitInfo` and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)).
|
||||
|
||||
Validators are penalized for failing to be included in the `LastCommitInfo` for some
|
||||
number of blocks by being automatically jailed, potentially slashed, and unbonded.
|
||||
|
||||
Information about validator's liveness activity is tracked through `ValidatorSigningInfo`.
|
||||
It is indexed in the store as follows:
|
||||
|
||||
- ValidatorSigningInfo: ` 0x01 | ConsAddress -> amino(valSigningInfo)`
|
||||
- MissedBlocksBitArray: ` 0x02 | ConsAddress | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)`
|
||||
|
||||
The first mapping allows us to easily lookup the recent signing info for a
|
||||
validator based on the validator's consensus address. The second mapping acts
|
||||
as a bit-array of size `SignedBlocksWindow` that tells us if the validator missed
|
||||
the block for a given index in the bit-array. The index in the bit-array is given
|
||||
as little endian uint64.
|
||||
|
||||
The result is a `varint` that takes on `0` or `1`, where `0` indicates the
|
||||
validator did not miss (did sign) the corresponding block, and `1` indicates
|
||||
they missed the block (did not sign).
|
||||
|
||||
Note that the `MissedBlocksBitArray` is not explicitly initialized up-front. Keys
|
||||
are added as we progress through the first `SignedBlocksWindow` blocks for a newly
|
||||
bonded validator. The `SignedBlocksWindow` parameter defines the size
|
||||
(number of blocks) of the sliding window used to track validator liveness.
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- __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
|
||||
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
|
||||
validator commits an equivocation or for any other configured misbehiavor.
|
||||
- __MissedBlocksCounter__: A counter kept to avoid unnecessary array reads. Note
|
||||
that `Sum(MissedBlocksBitArray)` equals `MissedBlocksCounter` always.
|
||||
@@ -1,38 +0,0 @@
|
||||
# Messages
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
handleMsgUnjail(tx TxUnjail)
|
||||
|
||||
validator = getValidator(tx.ValidatorAddr)
|
||||
if validator == nil
|
||||
fail with "No validator found"
|
||||
|
||||
if !validator.Jailed
|
||||
fail with "Validator not jailed, cannot unjail"
|
||||
|
||||
info = GetValidatorSigningInfo(operator)
|
||||
if info.Tombstoned
|
||||
fail with "Tombstoned validator cannot be unjailed"
|
||||
if block time < info.JailedUntil
|
||||
fail with "Validator still jailed, cannot unjail until period has expired"
|
||||
|
||||
validator.Jailed = false
|
||||
setValidator(validator)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
If the validator has enough stake to be in the top `n = MaximumBondedValidators`, they will be automatically rebonded,
|
||||
and all delegators still delegated to the validator will be rebonded and begin to again collect
|
||||
provisions and rewards.
|
||||
@@ -1,175 +0,0 @@
|
||||
# BeginBlock
|
||||
|
||||
## Evidence Handling
|
||||
|
||||
Tendermint blocks can include
|
||||
[Evidence](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator committed malicious
|
||||
behavior. The relevant information is forwarded to the application as ABCI Evidence
|
||||
in `abci.RequestBeginBlock` so that the validator an be accordingly punished.
|
||||
|
||||
For some `Evidence` 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 evidence is included in a block, the validator's stake is reduced by
|
||||
some penalty (`SlashFractionDoubleSign` for equivocation) 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.
|
||||
|
||||
We first need to loop through the unbondings and redelegations from the slashed
|
||||
validator and track how much stake has since moved:
|
||||
|
||||
```go
|
||||
slashAmountUnbondings := 0
|
||||
slashAmountRedelegations := 0
|
||||
|
||||
unbondings := getUnbondings(validator.Address)
|
||||
for unbond in unbondings {
|
||||
|
||||
if was not bonded before evidence.Height or started unbonding before unbonding period ago {
|
||||
continue
|
||||
}
|
||||
|
||||
burn := unbond.InitialTokens * SLASH_PROPORTION
|
||||
slashAmountUnbondings += burn
|
||||
|
||||
unbond.Tokens = max(0, unbond.Tokens - burn)
|
||||
}
|
||||
|
||||
// only care if source gets slashed because we're already bonded to destination
|
||||
// so if destination validator gets slashed our delegation just has same shares
|
||||
// of smaller pool.
|
||||
redels := getRedelegationsBySource(validator.Address)
|
||||
for redel in redels {
|
||||
|
||||
if was not bonded before evidence.Height or started redelegating before unbonding period ago {
|
||||
continue
|
||||
}
|
||||
|
||||
burn := redel.InitialTokens * SLASH_PROPORTION
|
||||
slashAmountRedelegations += burn
|
||||
|
||||
amount := unbondFromValidator(redel.Destination, burn)
|
||||
destroy(amount)
|
||||
}
|
||||
```
|
||||
|
||||
We then slash the validator and tombstone them:
|
||||
|
||||
```
|
||||
curVal := validator
|
||||
oldVal := loadValidator(evidence.Height, evidence.Address)
|
||||
|
||||
slashAmount := SLASH_PROPORTION * oldVal.Shares
|
||||
slashAmount -= slashAmountUnbondings
|
||||
slashAmount -= slashAmountRedelegations
|
||||
|
||||
curVal.Shares = max(0, curVal.Shares - slashAmount)
|
||||
|
||||
signInfo = SigningInfo.Get(val.Address)
|
||||
signInfo.JailedUntil = MAX_TIME
|
||||
signInfo.Tombstoned = true
|
||||
SigningInfo.Set(val.Address, signInfo)
|
||||
```
|
||||
|
||||
This ensures that offending validators are punished the same amount whether they
|
||||
act as a single validator with X stake or as N validators with collectively X
|
||||
stake. The amount slashed for all double signature infractions committed within a
|
||||
single slashing period is capped as described in [overview.md](overview.md) under Tombstone Caps.
|
||||
|
||||
## Liveness Tracking
|
||||
|
||||
At the beginning of each block, we update the `ValidatorSigningInfo` for each
|
||||
validator and check if they've crossed below the liveness threshold over a
|
||||
sliding window. This sliding window is defined by `SignedBlocksWindow` and the
|
||||
index in this window is determined by `IndexOffset` found in the validator's
|
||||
`ValidatorSigningInfo`. For each block processed, the `IndexOffset` is incrimented
|
||||
regardless if the validator signed or not. Once the index is determined, the
|
||||
`MissedBlocksBitArray` and `MissedBlocksCounter` are updated accordingly.
|
||||
|
||||
Finally, in order to determine if a validator crosses below the liveness threshold,
|
||||
we fetch the maximum number of blocks missed, `maxMissed`, which is
|
||||
`SignedBlocksWindow - (MinSignedPerWindow * SignedBlocksWindow)` and the minimum
|
||||
height at which we can determine liveness, `minHeight`. If the current block is
|
||||
greater than `minHeight` and the validator's `MissedBlocksCounter` is greater than
|
||||
`maxMissed`, they will be slashed by `SlashFractionDowntime`, will be jailed
|
||||
for `DowntimeJailDuration`, and have the following values reset:
|
||||
`MissedBlocksBitArray`, `MissedBlocksCounter`, and `IndexOffset`.
|
||||
|
||||
__Note__: Liveness slashes do **NOT** lead to a tombstombing.
|
||||
|
||||
```go
|
||||
height := block.Height
|
||||
|
||||
for vote in block.LastCommitInfo.Votes {
|
||||
signInfo := GetValidatorSigningInfo(vote.Validator.Address)
|
||||
|
||||
// This is a relative index, so we counts blocks the validator SHOULD have
|
||||
// signed. We use the 0-value default signing info if not present, except for
|
||||
// start height.
|
||||
index := signInfo.IndexOffset % SignedBlocksWindow()
|
||||
signInfo.IndexOffset++
|
||||
|
||||
// Update MissedBlocksBitArray and MissedBlocksCounter. The MissedBlocksCounter
|
||||
// just tracks the sum of MissedBlocksBitArray. That way we avoid needing to
|
||||
// read/write the whole array each time.
|
||||
missedPrevious := GetValidatorMissedBlockBitArray(vote.Validator.Address, index)
|
||||
missed := !signed
|
||||
|
||||
switch {
|
||||
case !missedPrevious && missed:
|
||||
// array index has changed from not missed to missed, increment counter
|
||||
SetValidatorMissedBlockBitArray(vote.Validator.Address, index, true)
|
||||
signInfo.MissedBlocksCounter++
|
||||
|
||||
case missedPrevious && !missed:
|
||||
// array index has changed from missed to not missed, decrement counter
|
||||
SetValidatorMissedBlockBitArray(vote.Validator.Address, index, false)
|
||||
signInfo.MissedBlocksCounter--
|
||||
|
||||
default:
|
||||
// array index at this index has not changed; no need to update counter
|
||||
}
|
||||
|
||||
if missed {
|
||||
// emit events...
|
||||
}
|
||||
|
||||
minHeight := signInfo.StartHeight + SignedBlocksWindow()
|
||||
maxMissed := SignedBlocksWindow() - MinSignedPerWindow()
|
||||
|
||||
// If we are past the minimum height and the validator has missed too many
|
||||
// jail and slash them.
|
||||
if height > minHeight && signInfo.MissedBlocksCounter > maxMissed {
|
||||
validator := ValidatorByConsAddr(vote.Validator.Address)
|
||||
|
||||
// emit events...
|
||||
|
||||
// We need to retrieve the stake distribution which signed the block, so we
|
||||
// subtract ValidatorUpdateDelay from the block height, and subtract an
|
||||
// additional 1 since this is the LastCommit.
|
||||
//
|
||||
// Note, that this CAN result in a negative "distributionHeight" up to
|
||||
// -ValidatorUpdateDelay-1, 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 := height - sdk.ValidatorUpdateDelay - 1
|
||||
|
||||
Slash(vote.Validator.Address, distributionHeight, vote.Validator.Power, SlashFractionDowntime())
|
||||
Jail(vote.Validator.Address)
|
||||
|
||||
signInfo.JailedUntil = block.Time.Add(DowntimeJailDuration())
|
||||
|
||||
// We need to reset the counter & array so that the validator won't be
|
||||
// immediately slashed for downtime upon rebonding.
|
||||
signInfo.MissedBlocksCounter = 0
|
||||
signInfo.IndexOffset = 0
|
||||
ClearValidatorMissedBlockBitArray(vote.Validator.Address)
|
||||
}
|
||||
|
||||
SetValidatorSigningInfo(vote.Validator.Address, signInfo)
|
||||
}
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
## Hooks
|
||||
|
||||
In this section we describe the "hooks" - slashing module code that runs when other events happen.
|
||||
|
||||
### Validator Bonded
|
||||
|
||||
Upon successful first-time bonding of a new validator, we create a new `ValidatorSigningInfo` structure for the
|
||||
now-bonded validator, which `StartHeight` of the current block.
|
||||
|
||||
```
|
||||
onValidatorBonded(address sdk.ValAddress)
|
||||
|
||||
signingInfo, found = GetValidatorSigningInfo(address)
|
||||
if !found {
|
||||
signingInfo = ValidatorSigningInfo {
|
||||
StartHeight : CurrentHeight,
|
||||
IndexOffset : 0,
|
||||
JailedUntil : time.Unix(0, 0),
|
||||
Tombstone : false,
|
||||
MissedBloskCounter : 0
|
||||
}
|
||||
setValidatorSigningInfo(signingInfo)
|
||||
}
|
||||
|
||||
return
|
||||
```
|
||||
@@ -1,30 +0,0 @@
|
||||
# Tags
|
||||
|
||||
The slashing module emits the following events/tags:
|
||||
|
||||
## BeginBlocker
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|-------|---------------|-----------------------------|
|
||||
| slash | address | {validatorConsensusAddress} |
|
||||
| slash | power | {validatorPower} |
|
||||
| slash | reason | {slashReason} |
|
||||
| slash | jailed [0] | {validatorConsensusAddress} |
|
||||
|
||||
- [0] Only included if the validator is jailed.
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------|---------------|-----------------------------|
|
||||
| liveness | address | {validatorConsensusAddress} |
|
||||
| liveness | missed_blocks | {missedBlocksCounter} |
|
||||
| liveness | height | {blockHeight} |
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgUnjail
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------|---------------|-----------------|
|
||||
| message | module | slashing |
|
||||
| message | action | unjail |
|
||||
| message | sender | {senderAddress} |
|
||||
@@ -1,121 +0,0 @@
|
||||
# Staking Tombstone
|
||||
|
||||
## Abstract
|
||||
|
||||
In the current implementation of the `slashing` module, when the consensus engine
|
||||
informs the state machine of a validator's consensus fault, the validator is
|
||||
partially slashed, and put into a "jail period", a period of time in which they
|
||||
are not allowed to rejoin the validator set. However, because of the nature of
|
||||
consensus faults and ABCI, there can be a delay between an infraction occurring,
|
||||
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).
|
||||
|
||||
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`
|
||||
themselves, and thus rejoin the validator set.
|
||||
|
||||
One of the "design desires" of the `slashing` module is that if multiple
|
||||
infractions occur before evidence is executed (and a validator is put in jail),
|
||||
they should only be punished for single worst infraction, but not cumulatively.
|
||||
For example, if the sequence of events is:
|
||||
|
||||
1. Validator A commits Infraction 1 (worth 30% slash)
|
||||
2. Validator A commits Infraction 2 (worth 40% slash)
|
||||
3. Validator A commits Infraction 3 (worth 35% slash)
|
||||
4. Evidence for Infraction 1 reaches state machine (and validator is put in jail)
|
||||
5. Evidence for Infraction 2 reaches state machine
|
||||
6. Evidence for Infraction 3 reaches state machine
|
||||
|
||||
Only Infraction 2 should have its slash take effect, as it is the highest. This
|
||||
is done, so that in the case of the compromise of a validator's consensus key,
|
||||
they will only be punished once, even if the hacker double-signs many blocks.
|
||||
Because, the unjailing has to be done with the validator's operator key, they
|
||||
have a chance to re-secure their consensus key, and then signal that they are
|
||||
ready using their operator key. We call this period during which we track only
|
||||
the max infraction, the "slashing period".
|
||||
|
||||
Once, a validator rejoins by unjailing themselves, we begin a new slashing period;
|
||||
if they commit a new infraction after unjailing, it gets slashed cumulatively on
|
||||
top of the worst infraction from the previous slashing period.
|
||||
|
||||
However, while infractions are grouped based off of the slashing periods, because
|
||||
evidence can be submitted up to an `unbondingPeriod` after the infraction, we
|
||||
still have to allow for evidence to be submitted for previous slashing periods.
|
||||
For example, if the sequence of events is:
|
||||
|
||||
1. Validator A commits Infraction 1 (worth 30% slash)
|
||||
2. Validator A commits Infraction 2 (worth 40% slash)
|
||||
3. Evidence for Infraction 1 reaches state machine (and Validator A is put in jail)
|
||||
4. Validator A unjails
|
||||
|
||||
We are now in a new slashing period, however we still have to keep the door open
|
||||
for the previous infraction, as the evidence for Infraction 2 may still come in.
|
||||
As the number of slashing periods increase, it creates more complexity as we have
|
||||
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.
|
||||
|
||||
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
|
||||
and 2 days, respectively. This means there could potentially be up to 11 slashing
|
||||
periods concurrently being tracked per validator. If we set the `JailPeriod >= UnbondingPeriod`,
|
||||
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
|
||||
(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).
|
||||
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
|
||||
state, we set `DoubleSignJailEndTime` to `time.Unix(253402300800)`, the maximum
|
||||
time supported by Amino.
|
||||
|
||||
Implementing the tombstone system and getting rid of the slashing period tracking
|
||||
will make the `slashing` module way simpler, especially because we can remove all
|
||||
of the hooks defined in the `slashing` module consumed by the `staking` module
|
||||
(the `slashing` module still consumes hooks defined in `staking`).
|
||||
|
||||
### Single slashing amount
|
||||
|
||||
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
|
||||
comparing potential future ones to find the max.
|
||||
|
||||
Currently the only Tendermint ABCI fault is:
|
||||
|
||||
- Unjustified precommits (double signs)
|
||||
|
||||
It is currently planned to include the following fault in the near future:
|
||||
|
||||
- Signing a precommit when you're in unbonding phase (needed to make light client bisection safe)
|
||||
|
||||
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).
|
||||
@@ -1,12 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The slashing module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|-------------------------|------------------|------------------------|
|
||||
| MaxEvidenceAge | string (time ns) | "120000000000" |
|
||||
| SignedBlocksWindow | string (int64) | "100" |
|
||||
| MinSignedPerWindow | string (dec) | "0.500000000000000000" |
|
||||
| DowntimeJailDuration | string (time ns) | "600000000000" |
|
||||
| SlashFractionDoubleSign | string (dec) | "0.050000000000000000" |
|
||||
| SlashFractionDowntime | string (dec) | "0.010000000000000000" |
|
||||
@@ -1,38 +0,0 @@
|
||||
# Slashing module specification
|
||||
|
||||
## Abstract
|
||||
|
||||
This section specifies the slashing module of the Cosmos SDK, which implements functionality
|
||||
first outlined in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016.
|
||||
|
||||
The slashing module enables Cosmos SDK-based blockchains to disincentivize any attributable action
|
||||
by a protocol-recognized actor with value at stake by penalizing them ("slashing").
|
||||
|
||||
Penalties may include, but are not limited to:
|
||||
- Burning some amount of their stake
|
||||
- Removing their ability to vote on future blocks for a period of time.
|
||||
|
||||
This module will be used by the Cosmos Hub, the first hub in the Cosmos ecosystem.
|
||||
|
||||
## 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)
|
||||
2. **[State](02_state.md)**
|
||||
- [Signing Info](02_state.md#signing-info)
|
||||
3. **[Messages](03_messages.md)**
|
||||
- [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)
|
||||
5. **[05_hooks.md](05_hooks.md)**
|
||||
- [Hooks](05_hooks.md#hooks)
|
||||
6. **[Events](06_events.md)**
|
||||
- [BeginBlocker](06_events.md#beginblocker)
|
||||
- [Handlers](06_events.md#handlers)
|
||||
7. **[Staking Tombstone](07_tombstone.md)**
|
||||
- [Abstract](07_tombstone.md#abstract)
|
||||
8. **[Parameters](08_params.md)**
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
# State
|
||||
|
||||
## LastTotalPower
|
||||
|
||||
LastTotalPower tracks the total amounts of bonded tokens recorded during the previous end block.
|
||||
|
||||
- LastTotalPower: `0x12 -> amino(sdk.Int)`
|
||||
|
||||
## Params
|
||||
|
||||
Params is a module-wide configuration structure that stores system parameters
|
||||
and defines overall functioning of the staking module.
|
||||
|
||||
- Params: `Paramsspace("staking") -> amino(params)`
|
||||
|
||||
```go
|
||||
type Params struct {
|
||||
UnbondingTime time.Duration // time duration of unbonding
|
||||
MaxValidators uint16 // maximum number of validators
|
||||
MaxEntries uint16 // max entries for either unbonding delegation or redelegation (per pair/trio)
|
||||
BondDenom string // bondable coin denomination
|
||||
}
|
||||
```
|
||||
|
||||
## Validator
|
||||
|
||||
Validators can have one of three statuses
|
||||
|
||||
- `Unbonded`: The validator is not in the active set. They cannot sign blocks and do not earn
|
||||
rewards. They can receive delegations.
|
||||
- `Bonded`": Once the validator receives sufficient bonded tokens they automtically join the
|
||||
active set during [`EndBlock`](./04_end_block.md#validator-set-changes) and their status is updated to `Bonded`.
|
||||
They are signing blocks and receiving rewards. They can receive further delegations.
|
||||
They can be slashed for misbehavior. Delegators to this validator who unbond their delegation
|
||||
must wait the duration of the UnbondingTime, a chain-specific param. during which time
|
||||
they are still slashable for offences of the source validator if those offences were committed
|
||||
during the period of time that the tokens were bonded.
|
||||
- `Unbonding`: When a validator leaves the active set, either by choice or due to slashing or
|
||||
tombstoning, an unbonding of all their delegations begins. All delegations must then wait the UnbondingTime
|
||||
before moving receiving their tokens to their accounts from the `BondedPool`.
|
||||
|
||||
Validators objects should be primarily stored and accessed by the
|
||||
`OperatorAddr`, an SDK validator address for the operator of the validator. Two
|
||||
additional indices are maintained per validator object in order to fulfill
|
||||
required lookups for slashing and validator-set updates. A third special index
|
||||
(`LastValidatorPower`) is also maintained which however remains constant
|
||||
throughout each block, unlike the first two indices which mirror the validator
|
||||
records within a block.
|
||||
|
||||
- Validators: `0x21 | OperatorAddr -> amino(validator)`
|
||||
- ValidatorsByConsAddr: `0x22 | ConsAddr -> OperatorAddr`
|
||||
- ValidatorsByPower: `0x23 | BigEndian(ConsensusPower) | OperatorAddr -> OperatorAddr`
|
||||
- LastValidatorsPower: `0x11 OperatorAddr -> amino(ConsensusPower)`
|
||||
|
||||
`Validators` is the primary index - it ensures that each operator can have only one
|
||||
associated validator, where the public key of that validator can change in the
|
||||
future. Delegators can refer to the immutable operator of the validator, without
|
||||
concern for the changing public key.
|
||||
|
||||
`ValidatorByConsAddr` is an additional index that enables lookups for slashing.
|
||||
When Tendermint reports evidence, it provides the validator address, so this
|
||||
map is needed to find the operator. Note that the `ConsAddr` corresponds to the
|
||||
address which can be derived from the validator's `ConsPubKey`.
|
||||
|
||||
`ValidatorsByPower` is an additional index that provides a sorted list o
|
||||
potential validators to quickly determine the current active set. Here
|
||||
ConsensusPower is validator.Tokens/10^6. Note that all validators where
|
||||
`Jailed` is true are not stored within this index.
|
||||
|
||||
`LastValidatorsPower` is a special index that provides a historical list of the
|
||||
last-block's bonded validators. This index remains constant during a block but
|
||||
is updated during the validator set update process which takes place in [`EndBlock`](./04_end_block.md).
|
||||
|
||||
Each validator's state is stored in a `Validator` struct:
|
||||
|
||||
```go
|
||||
type Validator struct {
|
||||
OperatorAddress sdk.ValAddress // address of the validator's operator; bech encoded in JSON
|
||||
ConsPubKey crypto.PubKey // the consensus public key of the validator; bech encoded in JSON
|
||||
Jailed bool // has the validator been jailed from bonded status?
|
||||
Status sdk.BondStatus // validator status (bonded/unbonding/unbonded)
|
||||
Tokens sdk.Int // delegated tokens (incl. self-delegation)
|
||||
DelegatorShares sdk.Dec // total shares issued to a validator's delegators
|
||||
Description Description // description terms for the validator
|
||||
UnbondingHeight int64 // if unbonding, height at which this validator has begun unbonding
|
||||
UnbondingCompletionTime time.Time // if unbonding, min time for the validator to complete unbonding
|
||||
Commission Commission // commission parameters
|
||||
MinSelfDelegation sdk.Int // validator's self declared minimum self delegation
|
||||
}
|
||||
|
||||
type Commission struct {
|
||||
CommissionRates
|
||||
UpdateTime time.Time // the last time the commission rate was changed
|
||||
}
|
||||
|
||||
CommissionRates struct {
|
||||
Rate sdk.Dec // the commission rate charged to delegators, as a fraction
|
||||
MaxRate sdk.Dec // maximum commission rate which validator can ever charge, as a fraction
|
||||
MaxChangeRate sdk.Dec // maximum daily increase of the validator commission, as a fraction
|
||||
}
|
||||
|
||||
type Description struct {
|
||||
Moniker string // name
|
||||
Identity string // optional identity signature (ex. UPort or Keybase)
|
||||
Website string // optional website link
|
||||
SecurityContact string // optional email for security contact
|
||||
Details string // optional details
|
||||
}
|
||||
```
|
||||
|
||||
## Delegation
|
||||
|
||||
Delegations are identified by combining `DelegatorAddr` (the address of the delegator)
|
||||
with the `ValidatorAddr` Delegators are indexed in the store as follows:
|
||||
|
||||
- Delegation: `0x31 | DelegatorAddr | ValidatorAddr -> amino(delegation)`
|
||||
|
||||
Stake holders may delegate coins to validators; under this circumstance their
|
||||
funds are held in a `Delegation` data structure. It is owned by one
|
||||
delegator, and is associated with the shares for one validator. The sender of
|
||||
the transaction is the owner of the bond.
|
||||
|
||||
```go
|
||||
type Delegation struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorAddr sdk.ValAddress
|
||||
Shares sdk.Dec // delegation shares received
|
||||
}
|
||||
```
|
||||
|
||||
### Delegator Shares
|
||||
|
||||
When one Delegates tokens to a Validator they are issued a number of delegator shares based on a
|
||||
dynamic exchange rate, calculated as follows from the total number of tokens delegated to the
|
||||
validator and the number of shares issued so far:
|
||||
|
||||
`Shares per Token = validator.TotalShares() / validator.Tokens()`
|
||||
|
||||
Only the number of shares received is stored on the DelegationEntry. When a delegator then
|
||||
Undelegates, the token amount they receive is calculated from the number of shares they currently
|
||||
hold and the inverse exchange rate:
|
||||
|
||||
`Tokens per Share = validator.Tokens() / validatorShares()`
|
||||
|
||||
These `Shares` are simply an accounting mechanism. They are not a fungible asset. The reason for
|
||||
this mechanism is to simplify the accounting around slashing. Rather than iteratively slashing the
|
||||
tokens of every delegation entry, instead the Validators total bonded tokens can be slashed,
|
||||
effectively reducing the value of each issued delegator share.
|
||||
|
||||
## UnbondingDelegation
|
||||
|
||||
Shares in a `Delegation` can be unbonded, but they must for some time exist as
|
||||
an `UnbondingDelegation`, where shares can be reduced if Byzantine behavior is
|
||||
detected.
|
||||
|
||||
`UnbondingDelegation` are indexed in the store as:
|
||||
|
||||
- UnbondingDelegation: `0x32 | DelegatorAddr | ValidatorAddr ->
|
||||
amino(unbondingDelegation)`
|
||||
- UnbondingDelegationsFromValidator: `0x33 | ValidatorAddr | DelegatorAddr ->
|
||||
nil`
|
||||
|
||||
The first map here is used in queries, to lookup all unbonding delegations for
|
||||
a given delegator, while the second map is used in slashing, to lookup all
|
||||
unbonding delegations associated with a given validator that need to be
|
||||
slashed.
|
||||
|
||||
A UnbondingDelegation object is created every time an unbonding is initiated.
|
||||
|
||||
```go
|
||||
type UnbondingDelegation struct {
|
||||
DelegatorAddr sdk.AccAddress // delegator
|
||||
ValidatorAddr sdk.ValAddress // validator unbonding from operator addr
|
||||
Entries []UnbondingDelegationEntry // unbonding delegation entries
|
||||
}
|
||||
|
||||
type UnbondingDelegationEntry struct {
|
||||
CreationHeight int64 // height which the unbonding took place
|
||||
CompletionTime time.Time // unix time for unbonding completion
|
||||
InitialBalance sdk.Coin // atoms initially scheduled to receive at completion
|
||||
Balance sdk.Coin // atoms to receive at completion
|
||||
}
|
||||
```
|
||||
|
||||
## Redelegation
|
||||
|
||||
The bonded tokens worth of a `Delegation` may be instantly redelegated from a
|
||||
source validator to a different validator (destination validator). However when
|
||||
this occurs they must be tracked in a `Redelegation` object, whereby their
|
||||
shares can be slashed if their tokens have contributed to a Byzantine fault
|
||||
committed by the source validator.
|
||||
|
||||
`Redelegation` are indexed in the store as:
|
||||
|
||||
- Redelegations: `0x34 | DelegatorAddr | ValidatorSrcAddr | ValidatorDstAddr -> amino(redelegation)`
|
||||
- RedelegationsBySrc: `0x35 | ValidatorSrcAddr | ValidatorDstAddr | DelegatorAddr -> nil`
|
||||
- RedelegationsByDst: `0x36 | ValidatorDstAddr | ValidatorSrcAddr | DelegatorAddr -> nil`
|
||||
|
||||
The first map here is used for queries, to lookup all redelegations for a given
|
||||
delegator. The second map is used for slashing based on the `ValidatorSrcAddr`,
|
||||
while the third map is for slashing based on the `ValidatorDstAddr`.
|
||||
|
||||
A redelegation object is created every time a redelegation occurs. To prevent
|
||||
"redelegation hopping" redelegations may not occur under the situation that:
|
||||
|
||||
- the (re)delegator already has another immature redelegation in progress
|
||||
with a destination to a validator (let's call it `Validator X`)
|
||||
- and, the (re)delegator is attempting to create a _new_ redelegation
|
||||
where the source validator for this new redelegation is `Validator-X`.
|
||||
|
||||
```go
|
||||
type Redelegation struct {
|
||||
DelegatorAddr sdk.AccAddress // delegator
|
||||
ValidatorSrcAddr sdk.ValAddress // validator redelegation source operator addr
|
||||
ValidatorDstAddr sdk.ValAddress // validator redelegation destination operator addr
|
||||
Entries []RedelegationEntry // redelegation entries
|
||||
}
|
||||
|
||||
type RedelegationEntry struct {
|
||||
CreationHeight int64 // height which the redelegation took place
|
||||
CompletionTime time.Time // unix time for redelegation completion
|
||||
InitialBalance sdk.Coin // initial balance when redelegation started
|
||||
Balance sdk.Coin // current balance (current value held in destination validator)
|
||||
SharesDst sdk.Dec // amount of destination-validator shares created by redelegation
|
||||
}
|
||||
```
|
||||
|
||||
## Queues
|
||||
|
||||
All queues objects are sorted by timestamp. The time used within any queue is
|
||||
first rounded to the nearest nanosecond then sorted. The sortable time format
|
||||
used is a slight modification of the RFC3339Nano and uses the the format string
|
||||
`"2006-01-02T15:04:05.000000000"`. Notably this format:
|
||||
|
||||
- right pads all zeros
|
||||
- drops the time zone info (uses UTC)
|
||||
|
||||
In all cases, the stored timestamp represents the maturation time of the queue
|
||||
element.
|
||||
|
||||
### UnbondingDelegationQueue
|
||||
|
||||
For the purpose of tracking progress of unbonding delegations the unbonding
|
||||
delegations queue is kept.
|
||||
|
||||
- UnbondingDelegation: `0x41 | format(time) -> []DVPair`
|
||||
|
||||
```go
|
||||
type DVPair struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorAddr sdk.ValAddress
|
||||
}
|
||||
```
|
||||
|
||||
### RedelegationQueue
|
||||
|
||||
For the purpose of tracking progress of redelegations the redelegation queue is
|
||||
kept.
|
||||
|
||||
- UnbondingDelegation: `0x42 | format(time) -> []DVVTriplet`
|
||||
|
||||
```go
|
||||
type DVVTriplet struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorSrcAddr sdk.ValAddress
|
||||
ValidatorDstAddr sdk.ValAddress
|
||||
}
|
||||
```
|
||||
|
||||
### ValidatorQueue
|
||||
|
||||
For the purpose of tracking progress of unbonding validators the validator
|
||||
queue is kept.
|
||||
|
||||
- ValidatorQueueTime: `0x43 | format(time) -> []sdk.ValAddress`
|
||||
|
||||
The stored object as each key is an array of validator operator addresses from
|
||||
which the validator object can be accessed. Typically it is expected that only
|
||||
a single validator record will be associated with a given timestamp however it is possible
|
||||
that multiple validators exist in the queue at the same location.
|
||||
@@ -1,136 +0,0 @@
|
||||
# State Transitions
|
||||
|
||||
This document describes the state transition operations pertaining to:
|
||||
|
||||
1. [Validators](./02_state_transitions.md#validators)
|
||||
2. [Delegations](./02_state_transitions.md#delegations)
|
||||
3. [Slashing](./02_state_transitions.md#slashing)
|
||||
|
||||
## Validators
|
||||
State transitions in validators are performed on every [`EndBlock`](./04_end_block.md#validator-set-changes)
|
||||
in order to check for changes in the active `ValidatorSet`.
|
||||
|
||||
### Unbonded to Bonded
|
||||
|
||||
The following transition occurs when a validator's ranking in the `ValidatorPowerIndex` surpasses
|
||||
that of the `LastValidator`.
|
||||
|
||||
- set `validator.Status` to `Bonded`
|
||||
- send the `validator.Tokens` from the `NotBondedTokens` to the `BondedPool` `ModuleAccount`
|
||||
- delete the existing record from `ValidatorByPowerIndex`
|
||||
- add a new updated record to the `ValidatorByPowerIndex`
|
||||
- update the `Validator` object for this validator
|
||||
- if it exists, delete any `ValidatorQueue` record for this validator
|
||||
|
||||
### Bonded to Unbonding
|
||||
|
||||
When a validator begins the unbonding process the following operations occur:
|
||||
|
||||
- send the `validator.Tokens` from the `BondedPool` to the `NotBondedTokens` `ModuleAccount`
|
||||
- set `validator.Status` to `Unbonding`
|
||||
- delete the existing record from `ValidatorByPowerIndex`
|
||||
- add a new updated record to the `ValidatorByPowerIndex`
|
||||
- update the `Validator` object for this validator
|
||||
- insert a new record into the `ValidatorQueue` for this validator
|
||||
|
||||
### Unbonding to Unbonded
|
||||
|
||||
A validator moves from unbonding to unbonded when the `ValidatorQueue` object
|
||||
moves from bonded to unbonded
|
||||
|
||||
- update the `Validator` object for this validator
|
||||
- set `validator.Status` to `Unbonded`
|
||||
|
||||
### Jail/Unjail
|
||||
|
||||
when a validator is jailed it is effectively removed from the Tendermint set.
|
||||
this process may be also be reversed. the following operations occur:
|
||||
|
||||
- set `Validator.Jailed` and update object
|
||||
- if jailed delete record from `ValidatorByPowerIndex`
|
||||
- if unjailed add record to `ValidatorByPowerIndex`
|
||||
|
||||
## Delegations
|
||||
|
||||
### Delegate
|
||||
|
||||
When a delegation occurs both the validator and the delegation objects are affected
|
||||
|
||||
- determine the delegators shares based on tokens delegated and the validator's exchange rate
|
||||
- remove tokens from the sending account
|
||||
- add shares the delegation object or add them to a created validator object
|
||||
- add new delegator shares and update the `Validator` object
|
||||
- transfer the `delegation.Amount` from the delegator's account to the `BondedPool` or the `NotBondedPool` `ModuleAccount` depending if the `validator.Status` is `Bonded` or not
|
||||
- delete the existing record from `ValidatorByPowerIndex`
|
||||
- add an new updated record to the `ValidatorByPowerIndex`
|
||||
|
||||
### Begin Unbonding
|
||||
|
||||
As a part of the Undelegate and Complete Unbonding state transitions Unbond
|
||||
Delegation may be called.
|
||||
|
||||
- subtract the unbonded shares from delegator
|
||||
- if the validator is `Unbonding` or `Bonded` add the tokens to an `UnbondingDelegation` Entry
|
||||
- if the validator is `Unbonded` send the tokens directly to the withdraw
|
||||
account
|
||||
- update the delegation or remove the delegation if there are no more shares
|
||||
- if the delegation is the operator of the validator and no more shares exist then trigger a jail validator
|
||||
- update the validator with removed the delegator shares and associated coins
|
||||
- if the validator state is `Bonded`, transfer the `Coins` worth of the unbonded
|
||||
shares from the `BondedPool` to the `NotBondedPool` `ModuleAccount`
|
||||
- remove the validator if it is unbonded and there are no more delegation shares.
|
||||
|
||||
### Complete Unbonding
|
||||
|
||||
For undelegations which do not complete immediately, the following operations
|
||||
occur when the unbonding delegation queue element matures:
|
||||
|
||||
- remove the entry from the `UnbondingDelegation` object
|
||||
- transfer the tokens from the `NotBondedPool` `ModuleAccount` to the delegator `Account`
|
||||
|
||||
### Begin Redelegation
|
||||
|
||||
Redelegations affect the delegation, source and destination validators.
|
||||
|
||||
- perform an `unbond` delegation from the source validator to retrieve the tokens worth of the unbonded shares
|
||||
- using the unbonded tokens, `Delegate` them to the destination validator
|
||||
- if the `sourceValidator.Status` is `Bonded`, and the `destinationValidator` is not,
|
||||
transfer the newly delegated tokens from the `BondedPool` to the `NotBondedPool` `ModuleAccount`
|
||||
- otherwise, if the `sourceValidator.Status` is not `Bonded`, and the `destinationValidator`
|
||||
is `Bonded`, transfer the newly delegated tokens from the `NotBondedPool` to the `BondedPool` `ModuleAccount`
|
||||
- record the token amount in an new entry in the relevant `Redelegation`
|
||||
|
||||
### Complete Redelegation
|
||||
|
||||
When a redelegations complete the following occurs:
|
||||
|
||||
- remove the entry from the `Redelegation` object
|
||||
|
||||
## Slashing
|
||||
|
||||
### Slash Validator
|
||||
|
||||
When a Validator is slashed, the following occurs:
|
||||
|
||||
- The total `slashAmount` is calculated as the `slashFactor` (a chain parameter) * `TokensFromConsensusPower`,
|
||||
the total number of tokens bonded to the validator at the time of the infraction.
|
||||
- Every unbonding delegation and redelegation from the validator are slashed by the `slashFactor`
|
||||
percentage of the initialBalance.
|
||||
- Each amount slashed from redelegations and unbonding delegations is subtracted from the
|
||||
total slash amount.
|
||||
- The `remaingSlashAmount` is then slashed from the validator's tokens in the `BondedPool` or
|
||||
`NonBondedPool` depending on the validator's status. This reduces the total supply of tokens.
|
||||
|
||||
### Slash Unbonding Delegation
|
||||
|
||||
When a validator is slashed, so are those unbonding delegations from the validator that began unbonding
|
||||
after the time of the infraction. Every entry in every unbonding delegation from the validator
|
||||
is slashed by `slashFactor`. The amount slashed is calculated from the `InitialBalance` of the
|
||||
delegation and is capped to prevent a resulting negative balance. Completed (or mature) unbondings are not slashed.
|
||||
|
||||
### Slash Redelegation
|
||||
|
||||
When a validator is slashed, so are all redelegations from the validator that began after the
|
||||
infraction. Redelegations are slashed by `slashFactor`.
|
||||
The amount slashed is calculated from the `InitialBalance` of the delegation and is capped to
|
||||
prevent a resulting negative balance. Mature redelegations are not slashed.
|
||||
@@ -1,149 +0,0 @@
|
||||
# Messages
|
||||
|
||||
In this section we describe the processing of the staking messages and the corresponding updates to the state. All created/modified state objects specified by each message are defined within the [state](./02_state.md) section.
|
||||
|
||||
## MsgCreateValidator
|
||||
|
||||
A validator is created using the `MsgCreateValidator` message.
|
||||
|
||||
```go
|
||||
type MsgCreateValidator struct {
|
||||
Description Description
|
||||
Commission Commission
|
||||
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorAddr sdk.ValAddress
|
||||
PubKey crypto.PubKey
|
||||
Delegation sdk.Coin
|
||||
}
|
||||
```
|
||||
|
||||
This message is expected to fail if:
|
||||
|
||||
- another validator with this operator address is already registered
|
||||
- another validator with this pubkey is already registered
|
||||
- the initial self-delegation tokens are of a denom not specified as the bonding denom
|
||||
- the commission parameters are faulty, namely:
|
||||
- `MaxRate` is either > 1 or < 0
|
||||
- the initial `Rate` is either negative or > `MaxRate`
|
||||
- the initial `MaxChangeRate` is either negative or > `MaxRate`
|
||||
- the description fields are too large
|
||||
|
||||
This message creates and stores the `Validator` object at appropriate indexes.
|
||||
Additionally a self-delegation is made with the initial tokens delegation
|
||||
tokens `Delegation`. The validator always starts as unbonded but may be bonded
|
||||
in the first end-block.
|
||||
|
||||
## MsgEditValidator
|
||||
|
||||
The `Description`, `CommissionRate` of a validator can be updated using the
|
||||
`MsgEditCandidacy`.
|
||||
|
||||
```go
|
||||
type MsgEditCandidacy struct {
|
||||
Description Description
|
||||
ValidatorAddr sdk.ValAddress
|
||||
CommissionRate sdk.Dec
|
||||
}
|
||||
```
|
||||
|
||||
This message is expected to fail if:
|
||||
|
||||
- the initial `CommissionRate` is either negative or > `MaxRate`
|
||||
- the `CommissionRate` has already been updated within the previous 24 hours
|
||||
- the `CommissionRate` is > `MaxChangeRate`
|
||||
- the description fields are too large
|
||||
|
||||
This message stores the updated `Validator` object.
|
||||
|
||||
## MsgDelegate
|
||||
|
||||
Within this message the delegator provides coins, and in return receives
|
||||
some amount of their validator's (newly created) delegator-shares that are
|
||||
assigned to `Delegation.Shares`.
|
||||
|
||||
```go
|
||||
type MsgDelegate struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorAddr sdk.ValAddress
|
||||
Amount sdk.Coin
|
||||
}
|
||||
```
|
||||
|
||||
This message is expected to fail if:
|
||||
|
||||
- the validator is does not exist
|
||||
- the validator is jailed
|
||||
- the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom`
|
||||
|
||||
If an existing `Delegation` object for provided addresses does not already
|
||||
exist than it is created as part of this message otherwise the existing
|
||||
`Delegation` is updated to include the newly received shares.
|
||||
|
||||
## MsgBeginUnbonding
|
||||
|
||||
The begin unbonding message allows delegators to undelegate their tokens from
|
||||
validator.
|
||||
|
||||
```go
|
||||
type MsgBeginUnbonding struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorAddr sdk.ValAddress
|
||||
Amount sdk.Coin
|
||||
}
|
||||
```
|
||||
|
||||
This message is expected to fail if:
|
||||
|
||||
- the delegation doesn't exist
|
||||
- the validator doesn't exist
|
||||
- the delegation has less shares than the ones worth of `Amount`
|
||||
- existing `UnbondingDelegation` has maximum entries as defined by `params.MaxEntries`
|
||||
- the `Amount` has a denomination different than one defined by `params.BondDenom`
|
||||
|
||||
When this message is processed the following actions occur:
|
||||
|
||||
- validator's `DelegatorShares` and the delegation's `Shares` are both reduced by the message `SharesAmount`
|
||||
- calculate the token worth of the shares remove that amount tokens held within the validator
|
||||
- with those removed tokens, if the validator is:
|
||||
- `Bonded` - add them to an entry in `UnbondingDelegation` (create `UnbondingDelegation` if it doesn't exist) with a completion time a full unbonding period from the current time. Update pool shares to reduce BondedTokens and increase NotBondedTokens by token worth of the shares.
|
||||
- `Unbonding` - add them to an entry in `UnbondingDelegation` (create `UnbondingDelegation` if it doesn't exist) with the same completion time as the validator (`UnbondingMinTime`).
|
||||
- `Unbonded` - then send the coins the message `DelegatorAddr`
|
||||
- if there are no more `Shares` in the delegation, then the delegation object is removed from the store
|
||||
- under this situation if the delegation is the validator's self-delegation then also jail the validator.
|
||||
|
||||
## MsgBeginRedelegate
|
||||
|
||||
The redelegation command allows delegators to instantly switch validators. Once
|
||||
the unbonding period has passed, the redelegation is automatically completed in
|
||||
the EndBlocker.
|
||||
|
||||
```go
|
||||
type MsgBeginRedelegate struct {
|
||||
DelegatorAddr sdk.AccAddress
|
||||
ValidatorSrcAddr sdk.ValAddress
|
||||
ValidatorDstAddr sdk.ValAddress
|
||||
Amount sdk.Coin
|
||||
}
|
||||
```
|
||||
|
||||
This message is expected to fail if:
|
||||
|
||||
- the delegation doesn't exist
|
||||
- the source or destination validators don't exist
|
||||
- the delegation has less shares than the ones worth of `Amount`
|
||||
- the source validator has a receiving redelegation which is not matured (aka. the redelegation may be transitive)
|
||||
- existing `Redelegation` has maximum entries as defined by `params.MaxEntries`
|
||||
- the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom`
|
||||
|
||||
When this message is processed the following actions occur:
|
||||
|
||||
- the source validator's `DelegatorShares` and the delegations `Shares` are both reduced by the message `SharesAmount`
|
||||
- calculate the token worth of the shares remove that amount tokens held within the source validator.
|
||||
- if the source validator is:
|
||||
- `Bonded` - add an entry to the `Redelegation` (create `Redelegation` if it doesn't exist) with a completion time a full unbonding period from the current time. Update pool shares to reduce BondedTokens and increase NotBondedTokens by token worth of the shares (this may be effectively reversed in the next step however).
|
||||
- `Unbonding` - add an entry to the `Redelegation` (create `Redelegation` if it doesn't exist) with the same completion time as the validator (`UnbondingMinTime`).
|
||||
- `Unbonded` - no action required in this step
|
||||
- Delegate the token worth to the destination validator, possibly moving tokens back to the bonded state.
|
||||
- if there are no more `Shares` in the source delegation, then the source delegation object is removed from the store
|
||||
- under this situation if the delegation is the validator's self-delegation then also jail the validator.
|
||||
@@ -1,67 +0,0 @@
|
||||
# End-Block
|
||||
|
||||
Each abci end block call, the operations to update queues and validator set
|
||||
changes are specified to execute.
|
||||
|
||||
## Validator Set Changes
|
||||
|
||||
The staking validator set is updated during this process by state transitions
|
||||
that run at the end of every block. As a part of this process any updated
|
||||
validators are also returned back to Tendermint for inclusion in the Tendermint
|
||||
validator set which is responsible for validating Tendermint messages at the
|
||||
consensus layer. Operations are as following:
|
||||
|
||||
- the new validator set is taken as the top `params.MaxValidators` number of
|
||||
validators retrieved from the ValidatorsByPower index
|
||||
- the previous validator set is compared with the new validator set:
|
||||
- missing validators begin unbonding and their `Tokens` are transferred from the
|
||||
`BondedPool` to the `NotBondedPool` `ModuleAccount`
|
||||
- new validators are instantly bonded and their `Tokens` are transferred from the
|
||||
`NotBondedPool` to the `BondedPool` `ModuleAccount`
|
||||
|
||||
In all cases, any validators leaving or entering the bonded validator set or
|
||||
changing balances and staying within the bonded validator set incur an update
|
||||
message which is passed back to Tendermint.
|
||||
|
||||
## Queues
|
||||
|
||||
Within staking, certain state-transitions are not instantaneous but take place
|
||||
over a duration of time (typically the unbonding period). When these
|
||||
transitions are mature certain operations must take place in order to complete
|
||||
the state operation. This is achieved through the use of queues which are
|
||||
checked/processed at the end of each block.
|
||||
|
||||
### Unbonding Validators
|
||||
|
||||
When a validator is kicked out of the bonded validator set (either through
|
||||
being jailed, or not having sufficient bonded tokens) it begins the unbonding
|
||||
process along with all its delegations begin unbonding (while still being
|
||||
delegated to this validator). At this point the validator is said to be an
|
||||
unbonding validator, whereby it will mature to become an "unbonded validator"
|
||||
after the unbonding period has passed.
|
||||
|
||||
Each block the validator queue is to be checked for mature unbonding validators
|
||||
(namely with a completion time <= current time). At this point any mature
|
||||
validators which do not have any delegations remaining are deleted from state.
|
||||
For all other mature unbonding validators that still have remaining
|
||||
delegations, the `validator.Status` is switched from `sdk.Unbonding` to
|
||||
`sdk.Unbonded`.
|
||||
|
||||
### Unbonding Delegations
|
||||
|
||||
Complete the unbonding of all mature `UnbondingDelegations.Entries` within the
|
||||
`UnbondingDelegations` queue with the following procedure:
|
||||
|
||||
- transfer the balance coins to the delegator's wallet address
|
||||
- remove the mature entry from `UnbondingDelegation.Entries`
|
||||
- remove the `UnbondingDelegation` object from the store if there are no
|
||||
remaining entries.
|
||||
|
||||
### Redelegations
|
||||
|
||||
Complete the unbonding of all mature `Redelegation.Entries` within the
|
||||
`Redelegations` queue with the following procedure:
|
||||
|
||||
- remove the mature entry from `Redelegation.Entries`
|
||||
- remove the `Redelegation` object from the store if there are no
|
||||
remaining entries.
|
||||
@@ -1,23 +0,0 @@
|
||||
# Hooks
|
||||
|
||||
Other modules may register operations to execute when a certain event has
|
||||
occurred within staking. These events can be registered to execute either
|
||||
right `Before` or `After` the staking event (as per the hook name). The
|
||||
following hooks can registered with staking:
|
||||
|
||||
- `AfterValidatorCreated(Context, ValAddress)`
|
||||
- called when a validator is created
|
||||
- `BeforeValidatorModified(Context, ValAddress)`
|
||||
- called when a validator's state is changed
|
||||
- `AfterValidatorRemoved(Context, ConsAddress, ValAddress)`
|
||||
- called when a validator is deleted
|
||||
- `AfterValidatorBonded(Context, ConsAddress, ValAddress)`
|
||||
- called when a validator is bonded
|
||||
- `AfterValidatorBeginUnbonding(Context, ConsAddress, ValAddress)`
|
||||
- called when a validator begins unbonding
|
||||
- `BeforeDelegationCreated(Context, AccAddress, ValAddress)`
|
||||
- called when a delegation is created
|
||||
- `BeforeDelegationSharesModified(Context, AccAddress, ValAddress)`
|
||||
- called when a delegation's shares are modified
|
||||
- `BeforeDelegationRemoved(Context, AccAddress, ValAddress)`
|
||||
- called when a delegation is removed
|
||||
@@ -1,72 +0,0 @@
|
||||
# Events
|
||||
|
||||
The staking module emits the following events:
|
||||
|
||||
## EndBlocker
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|-----------------------|-----------------------|-----------------------|
|
||||
| complete_unbonding | validator | {validatorAddress} |
|
||||
| complete_unbonding | delegator | {delegatorAddress} |
|
||||
| complete_redelegation | source_validator | {srcValidatorAddress} |
|
||||
| complete_redelegation | destination_validator | {dstValidatorAddress} |
|
||||
| complete_redelegation | delegator | {delegatorAddress} |
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgCreateValidator
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|------------------|---------------|--------------------|
|
||||
| create_validator | validator | {validatorAddress} |
|
||||
| create_validator | amount | {delegationAmount} |
|
||||
| message | module | staking |
|
||||
| message | action | create_validator |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgEditValidator
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------|---------------------|---------------------|
|
||||
| edit_validator | commission_rate | {commissionRate} |
|
||||
| edit_validator | min_self_delegation | {minSelfDelegation} |
|
||||
| message | module | staking |
|
||||
| message | action | edit_validator |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgDelegate
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------|---------------|--------------------|
|
||||
| delegate | validator | {validatorAddress} |
|
||||
| delegate | amount | {delegationAmount} |
|
||||
| message | module | staking |
|
||||
| message | action | delegate |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
### MsgUndelegate
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------|---------------------|--------------------|
|
||||
| unbond | validator | {validatorAddress} |
|
||||
| unbond | amount | {unbondAmount} |
|
||||
| unbond | completion_time [0] | {completionTime} |
|
||||
| message | module | staking |
|
||||
| message | action | begin_unbonding |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
* [0] Time is formatted in the RFC3339 standard
|
||||
|
||||
### MsgBeginRedelegate
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|------------|-----------------------|-----------------------|
|
||||
| redelegate | source_validator | {srcValidatorAddress} |
|
||||
| redelegate | destination_validator | {dstValidatorAddress} |
|
||||
| redelegate | amount | {unbondAmount} |
|
||||
| redelegate | completion_time [0] | {completionTime} |
|
||||
| message | module | staking |
|
||||
| message | action | begin_redelegate |
|
||||
| message | sender | {senderAddress} |
|
||||
|
||||
* [0] Time is formatted in the RFC3339 standard
|
||||
@@ -1,10 +0,0 @@
|
||||
# Parameters
|
||||
|
||||
The staking module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
|---------------|------------------|-------------------|
|
||||
| UnbondingTime | string (time ns) | "259200000000000" |
|
||||
| MaxValidators | uint16 | 100 |
|
||||
| KeyMaxEntries | uint16 | 7 |
|
||||
| BondDenom | string | "uatom" |
|
||||
@@ -1,45 +0,0 @@
|
||||
# Staking module specification
|
||||
|
||||
## Abstract
|
||||
|
||||
This paper specifies the Staking module of the Cosmos-SDK, which was first
|
||||
described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper)
|
||||
in June 2016.
|
||||
|
||||
The module enables Cosmos-SDK based blockchain to support an advanced
|
||||
Proof-of-Stake system. In this system, holders of the native staking token of
|
||||
the chain can become validators and can delegate tokens to validator
|
||||
validators, ultimately determining the effective validator set for the system.
|
||||
|
||||
This module will be used in the Cosmos Hub, the first Hub in the Cosmos
|
||||
network.
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[State](01_state.md)**
|
||||
- [Pool](01_state.md#pool)
|
||||
- [LastTotalPower](01_state.md#lasttotalpower)
|
||||
- [Params](01_state.md#params)
|
||||
- [Validator](01_state.md#validator)
|
||||
- [Delegation](01_state.md#delegation)
|
||||
- [UnbondingDelegation](01_state.md#unbondingdelegation)
|
||||
- [Redelegation](01_state.md#redelegation)
|
||||
- [Queues](01_state.md#queues)
|
||||
2. **[State Transitions](02_state_transitions.md)**
|
||||
- [Validators](02_state_transitions.md#validators)
|
||||
- [Delegations](02_state_transitions.md#delegations)
|
||||
- [Slashing](02_state_transitions.md#slashing)
|
||||
3. **[Messages](03_messages.md)**
|
||||
- [MsgCreateValidator](03_messages.md#msgcreatevalidator)
|
||||
- [MsgEditValidator](03_messages.md#msgeditvalidator)
|
||||
- [MsgDelegate](03_messages.md#msgdelegate)
|
||||
- [MsgBeginUnbonding](03_messages.md#msgbeginunbonding)
|
||||
- [MsgBeginRedelegate](03_messages.md#msgbeginredelegate)
|
||||
4. **[End-Block ](04_end_block.md)**
|
||||
- [Validator Set Changes](04_end_block.md#validator-set-changes)
|
||||
- [Queues ](04_end_block.md#queues-)
|
||||
5. **[Hooks](05_hooks.md)**
|
||||
6. **[Events](06_events.md)**
|
||||
- [EndBlocker](06_events.md#endblocker)
|
||||
- [Handlers](06_events.md#handlers)
|
||||
7. **[Parameters](07_params.md)**
|
||||
@@ -1,65 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
## Supply
|
||||
|
||||
The `supply` module:
|
||||
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
level these module accounts are capable of sending/receiving tokens to and from
|
||||
`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 `ModuleAccount` interface is defined as follows:
|
||||
|
||||
```go
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
> **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).
|
||||
|
||||
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
|
||||
to:
|
||||
|
||||
- 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 `ModuleAccount` (restricted to its permissions).
|
||||
|
||||
### Permissions
|
||||
|
||||
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
|
||||
`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:
|
||||
|
||||
- `Minter`: allows for a module to mint a specific amount of coins.
|
||||
- `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.
|
||||
@@ -1,13 +0,0 @@
|
||||
# State
|
||||
|
||||
## Supply
|
||||
|
||||
The `Supply` is a passive tracker of the supply of the chain:
|
||||
|
||||
- Supply: `0x0 -> amino(Supply)`
|
||||
|
||||
```go
|
||||
type Supply struct {
|
||||
Total sdk.Coins // total supply of tokens registered on the chain
|
||||
}
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
# Future improvements
|
||||
|
||||
The current supply module only keeps track of the total supply of coins held in the network.
|
||||
|
||||
Future improvements may also include other types of supply such as:
|
||||
|
||||
* **Register Supply:** Register a concrete supply type in order to track it passively on the chain.
|
||||
@@ -1,10 +0,0 @@
|
||||
# Supply Specification
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[Concept](./01_concepts.md)**
|
||||
- [Supply](./01_concepts.md#supply)
|
||||
- [Module Accounts](./01_concepts.md#module-accounts)
|
||||
2. **[State](./02_state.md)**
|
||||
- [Supply](./02_state.md#supply)
|
||||
3. **[Future Improvements](./03_future_improvements.md)**
|
||||
Reference in New Issue
Block a user