Merge pull request #999 from cosmos/release/v0.17.0

Release/v0.17.0
This commit is contained in:
Ethan Buchman 2018-05-15 16:16:34 -04:00 committed by GitHub
commit 35d0e34b26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 2300 additions and 1779 deletions

View File

@ -68,6 +68,22 @@ jobs:
export PATH="$GOBIN:$PATH"
gometalinter --disable-all --enable='golint' --vendor ./...
test_unit:
<<: *defaults
parallelism: 4
steps:
- attach_workspace:
at: /tmp/workspace
- restore_cache:
key: v1-pkg-cache
- restore_cache:
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
- run:
name: Test unit
command: |
export PATH="$GOBIN:$PATH"
make test_unit
test_cover:
<<: *defaults
parallelism: 4
@ -83,7 +99,7 @@ jobs:
command: |
export PATH="$GOBIN:$PATH"
make install
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
for pkg in $(go list github.com/cosmos/cosmos-sdk/... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test | circleci tests split --split-by=timings); do
id=$(basename "$pkg")
go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg"
@ -121,6 +137,9 @@ workflows:
- lint:
requires:
- setup_dependencies
- test_unit:
requires:
- setup_dependencies
- test_cover:
requires:
- setup_dependencies

View File

@ -1,5 +1,32 @@
# Changelog
## 0.17.0 (May 15, 2018)
BREAKING CHANGES
* [stake] MarshalJSON -> MarshalBinary
FEATURES
* [gaiacli] Support queries for candidates, delegator-bonds
* [gaiad] Added `gaiad export` command to export current state to JSON
* [x/bank] Tx tags with sender/recipient for indexing & later retrieval
* [x/stake] Tx tags with delegator/candidate for delegation & unbonding, and candidate info for declare candidate / edit candidacy
IMPROVEMENTS
* [gaiad] Update for Tendermint v0.19.3 (improve `/dump_consensus_state` and add
`/consensus_state`)
* [spec/ibc] Added spec!
* [spec/stake] Cleanup structure, include details about slashing and
auto-unbonding
* [spec/governance] Fixup some names and pseudocode
* NOTE: specs are still a work-in-progress ...
BUG FIXES
* Auto-sequencing now works correctly
## 0.16.0 (May 14th, 2018)
BREAKING CHANGES
@ -28,6 +55,7 @@ FEATURES:
* Initialize with genesis txs using `--gen-txs` flag
* Context now has access to the application-configured logger
BUG FIXES
* Gaia now uses stake, ported from github.com/cosmos/gaia

View File

@ -26,7 +26,7 @@ ADD . $REPO_PATH
# Install minimum necessary dependencies, build Cosmos SDK, remove packages
RUN apk add --no-cache $PACKAGES && \
cd $REPO_PATH && make get_tools && make get_vendor_deps && make all && make install && \
cd $REPO_PATH && make get_tools && make get_vendor_deps && make build && make install && \
apk del $PACKAGES
# Set entrypoint

39
Gopkg.lock generated
View File

@ -87,14 +87,14 @@
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
version = "v1.6.1"
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
name = "github.com/gorilla/websocket"
@ -159,7 +159,7 @@
branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b"
[[projects]]
name = "github.com/pelletier/go-toml"
@ -183,7 +183,7 @@
branch = "master"
name = "github.com/rcrowley/go-metrics"
packages = ["."]
revision = "d932a24a8ccb8fcadc993e5c6c58f93dac168294"
revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
[[projects]]
name = "github.com/spf13/afero"
@ -250,7 +250,7 @@
"leveldb/table",
"leveldb/util"
]
revision = "714f901b98fdb3aa954b4193d8cbd64a28d80cad"
revision = "9637fa0b2f0db13c99d899b91007edb7df4610b7"
[[projects]]
name = "github.com/tendermint/abci"
@ -277,8 +277,8 @@
[[projects]]
name = "github.com/tendermint/go-amino"
packages = ["."]
revision = "3668c02a8feace009f80754a5e5a8541e5d7b996"
version = "0.9.8"
revision = "ed62928576cfcaf887209dc96142cd79cdfff389"
version = "0.9.9"
[[projects]]
name = "github.com/tendermint/go-crypto"
@ -323,7 +323,6 @@
"p2p",
"p2p/conn",
"p2p/pex",
"p2p/trust",
"p2p/upnp",
"proxy",
"rpc/client",
@ -342,8 +341,8 @@
"types/priv_validator",
"version"
]
revision = "26f633ed48441f72895b710f0e87b7b6c6791066"
version = "v0.19.1"
revision = "03f6a29a64fc4d26322a90839b892014d2cb90a5"
version = "v0.19.3-rc0"
[[projects]]
name = "github.com/tendermint/tmlibs"
@ -360,8 +359,8 @@
"pubsub",
"pubsub/query"
]
revision = "d94e312673e16a11ea55d742cefb3e331228f898"
version = "v0.8.2"
revision = "cc5f287c4798ffe88c04d02df219ecb6932080fd"
version = "v0.8.3-rc0"
[[projects]]
branch = "master"
@ -377,7 +376,7 @@
"ripemd160",
"salsa20/salsa"
]
revision = "b49d69b5da943f7ef3c9cf91c8777c1f78a0cc3c"
revision = "2fc4c88bf43f0ea5ea305eae2b7af24b2cc93287"
[[projects]]
branch = "master"
@ -389,16 +388,15 @@
"http2/hpack",
"idna",
"internal/timeseries",
"lex/httplex",
"trace"
]
revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23"
revision = "2491c5de3490fced2f6cff376127c667efeed857"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "cbbc999da32df943dac6cd71eb3ee39e1d7838b9"
revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b"
[[projects]]
name = "golang.org/x/text"
@ -422,10 +420,9 @@
version = "v0.3.0"
[[projects]]
branch = "master"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2"
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
[[projects]]
name = "google.golang.org/grpc"
@ -460,6 +457,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "48f831a7ccc2c0fd3b790c16ca99ec864b89253d087ff4190d821c01c13e635a"
inputs-digest = "30cd9c49b9e87f62f40a02fd0ba35a2d67a519ef781e06316246b307e6be638d"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -62,7 +62,7 @@
[[constraint]]
name = "github.com/tendermint/go-amino"
version = "~0.9.8"
version = "~0.9.9"
[[constraint]]
name = "github.com/tendermint/iavl"
@ -70,11 +70,16 @@
[[constraint]]
name = "github.com/tendermint/tendermint"
version = "0.19.1"
version = "0.19.3-rc0"
[[override]]
name = "github.com/tendermint/tmlibs"
version = "~0.8.2-rc1"
version = "~0.8.3-rc0"
# this got updated and broke, so locked to an old working commit ...
[[override]]
name = "google.golang.org/genproto"
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
[prune]
go-tests = true

View File

@ -1,13 +1,14 @@
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test)
COMMIT_HASH := $(shell git rev-parse --short HEAD)
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}"
all: check_tools get_vendor_deps build build_examples install install_examples test
all: check_tools get_vendor_deps install install_examples test_lint test
########################################
### CI
ci: get_tools get_vendor_deps install test_cover
ci: get_tools get_vendor_deps install test_cover test_lint test
########################################
### Build
@ -83,18 +84,13 @@ godocs:
########################################
### Testing
test: test_unit # test_cli
test: test_unit
test_nocli:
go test `go list ./... | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test`
# Must be run in each package seperately for the visualization
# Added here for easy reference
# coverage:
# go test -coverprofile=c.out && go tool cover -html=c.out
test_cli:
@go test -count 1 -p 1 `go list github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test`
test_unit:
@go test $(PACKAGES)
@go test $(PACKAGES_NOCLITEST)
test_cover:
@bash tests/test_cover.sh
@ -103,7 +99,7 @@ test_lint:
gometalinter --disable-all --enable='golint' --vendor ./...
benchmark:
@go test -bench=. $(PACKAGES)
@go test -bench=. $(PACKAGES_NOCLITEST)
########################################
@ -133,4 +129,4 @@ devdoc_update:
# To avoid unintended conflicts with file names, always add to .PHONY
# unless there is a reason not to.
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_nocli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update
.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update

View File

@ -43,8 +43,23 @@ func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit,
// Query from Tendermint with the provided key and storename
func (ctx CoreContext) Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
return ctx.query(key, storeName, "key")
}
path := fmt.Sprintf("/%s/key", storeName)
// Query from Tendermint with the provided storename and subspace
func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KVPair, err error) {
resRaw, err := ctx.query(subspace, storeName, "subspace")
if err != nil {
return res, err
}
cdc.MustUnmarshalBinary(resRaw, &res)
return
}
// Query from Tendermint with the provided storename and path
func (ctx CoreContext) query(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) {
path := fmt.Sprintf("/%s/%s", storeName, endPath)
node, err := ctx.GetNode()
if err != nil {
return res, err
@ -158,6 +173,11 @@ func (ctx CoreContext) NextSequence(address []byte) (int64, error) {
return 0, err
}
if len(res) == 0 {
fmt.Printf("No account found, defaulting to sequence 0\n")
return 0, err
}
account, err := ctx.Decoder(res)
if err != nil {
panic(err)

View File

@ -36,7 +36,7 @@ func NewCoreContextFromViper() CoreContext {
Sequence: viper.GetInt64(client.FlagSequence),
Client: rpc,
Decoder: nil,
AccountStore: "main",
AccountStore: "acc",
}
}
@ -55,7 +55,8 @@ func defaultChainID() (string, error) {
// EnsureSequence - automatically set sequence number if none provided
func EnsureSequence(ctx CoreContext) (CoreContext, error) {
if viper.IsSet(client.FlagSequence) {
// Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331
if viper.GetInt64(client.FlagSequence) != 0 {
return ctx, nil
}
from, err := ctx.GetFromAddress()

View File

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/ibc"
@ -107,7 +108,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
StakeData: stake.GetDefaultGenesisState(),
}
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
stateBytes, err := wire.MarshalJSONIndent(gapp.cdc, genesisState)
if err != nil {
return err
}

View File

@ -66,8 +66,9 @@ func GaiaAppInit() server.AppInit {
fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError)
fsAppGenTx.String(flagName, "", "validator moniker, if left blank, do not add validator")
fsAppGenTx.String(flagClientHome, DefaultCLIHome, "home directory for the client, used for key generation")
fsAppGenTx.Bool(flagOWK, false, "overwrite the for the accounts created")
fsAppGenTx.String(flagClientHome, DefaultCLIHome,
"home directory for the client, used for key generation")
fsAppGenTx.Bool(flagOWK, false, "overwrite the accounts created")
return server.AppInit{
FlagsAppGenState: fsAppGenState,

View File

@ -48,6 +48,15 @@ func TestGaiaCLISend(t *testing.T) {
assert.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak"))
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooAddr, flags))
assert.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak"))
// test autosequencing
executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%v --name=foo", flags, barAddr), pass)
time.Sleep(time.Second * 3) // waiting for some blocks to pass
barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barAddr, flags))
assert.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak"))
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooAddr, flags))
assert.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak"))
}
func TestGaiaCLIDeclareCandidacy(t *testing.T) {

View File

@ -46,7 +46,7 @@ func main() {
client.GetCommands(
authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)),
stakecmd.GetCmdQueryCandidate("stake", cdc),
//stakecmd.GetCmdQueryCandidates("stake", cdc),
stakecmd.GetCmdQueryCandidates("stake", cdc),
stakecmd.GetCmdQueryDelegatorBond("stake", cdc),
//stakecmd.GetCmdQueryDelegatorBonds("stake", cdc),
)...)

View File

@ -11,6 +11,7 @@ NOTE: the specifications are not yet complete and very much a work in progress.
and delegation transactions, inflation, fees, etc.
- [Governance](governance) - Governance related specifications including
proposals and voting.
- [IBC](ibc) - Specification of the Cosmos inter-blockchain communication (IBC) protocol.
- [Other](other) - Other components of the Cosmos Hub, including the reserve
pool, All in Bits vesting, etc.

View File

@ -1,659 +0,0 @@
# Governance documentation
*Disclaimer: This is work in progress. Mechanisms are susceptible to change.*
This document describes the high-level architecture of the governance module. The governance module allows bonded Atom holders to vote on proposals on a 1 bonded Atom 1 vote basis.
## Design overview
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 filter (minimum deposit)
To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit `MinDeposit`.
When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `MinDeposit`, it enters voting period.
### Deposit refund
There are two instances where Atom holders that deposited can claim back their deposit:
- If the proposal is accepted
- If the proposal's deposit does not reach `MinDeposit` for a period longer than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore.
In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` transaction to retrieve their share of the deposit.
### 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`.
### Proposal categories
There are two categories of proposal:
- `Regular`
- `Urgent`
These two categories are strictly identical except that `Urgent` proposals can be accepted faster if a certain condition is met. For more information, see [Threshold](#threshold) section.
## 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.
In the initial version of the governance module, there will be no quorum enforced by the protocol. Participation is ensured via the combination of inheritance and validator's punishment for non-voting.
### 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).
`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, `UrgentProposal` 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.
### 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 a `Urgent` proposal, 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 `Urgent` 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.
### Validators punishment for non-voting
Validators are required to vote on all proposals to ensure that results have legitimacy. Voting is part of validators' directives and failure to do it will result in a penalty.
If a validators address is not in the list of addresses that voted on a proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then the validator will automatically be partially slashed of `GovernancePenalty`.
*Note: Need to define values for `GovernancePenalty`*
**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it.
### Governance key and governance address
Validators can make use of a slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign votes using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment.
## 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 programatically*
## Implementation
*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic and implementation might widely differ*
### State
#### Procedures
`Procedures` define the rule according to which votes are run. There can only be one active procedure at any given time. If governance wants to change a procedure, either to modify a value or add/remove a parameter, a new procedure has to be created and the previous one rendered inactive.
```Go
type Procedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain}
ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal}
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
GovernancePenalty int64 // Penalty if validator does not vote
IsActive bool // If true, procedure is active. Only one procedure can have isActive true.
}
```
**Store**:
- `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber`
- `ActiveProcedureNumber`: returns current procedure number
#### Proposals
`Proposals` are item to be voted on.
```Go
type Proposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Category bool // false=regular, true=urgent
Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0)
InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1)
Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain)
}
```
We also introduce a type `ValidatorGovInfo`
```Go
type ValidatorGovInfo struct {
InitVotingPower int64 // Voting power of validator when proposal enters voting period
Minus int64 // Minus of validator, used to compute validator's voting power
}
```
**Store:**
- `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID`
- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `<proposalID>:<depositorPubKey>` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the proposal)
- `Options`: A mapping `map[[]byte]string` of options indexed by `<proposalID>:<voterPubKey>:<validatorPubKey>` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator)
- `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `<proposalID>:<validatorGovPubKey>`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period.
#### Proposal Processing Queue
**Store:**
- `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`.
And the pseudocode for the `ProposalProcessingQueue`:
```
in BeginBlock do
checkProposal() // First call of the recursive function
// Recursive function. First call in BeginBlock
func checkProposal()
if (ProposalProcessingQueue.Peek() == nil)
return
else
proposalID = ProposalProcessingQueue.Peek()
proposal = load(store, Proposals, proposalID)
initProcedure = load(store, Procedures, proposal.InitProcedureNumber)
if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3)
// proposal was urgent and accepted under the special condition
// no punishment
ProposalProcessingQueue.pop()
checkProposal()
else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod)
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
for each validator in CurrentBondedValidators
validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey)
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
validatorOption = load(store, Options, validator.GovPubKey)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
ProposalProcessingQueue.pop()
checkProposal()
```
### Transactions
#### Proposal Submission
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction.
```Go
type TxGovSubmitProposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Category bool // false=regular, true=urgent
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
}
```
**State modifications:**
- Generate new `proposalID`
- Create new `Proposal`
- Initialise `Proposals` attributes
- Store sender's deposit in `Deposits`
- Decrease balance of sender by `InitialDeposit`
- If `MinDeposit` is reached:
- Push `proposalID` in `ProposalProcessingQueueEnd`
- Store each validator's voting power in `ValidatorGovInfos`
A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode
```
// PSEUDOCODE //
// Check if TxGovSubmitProposal is valid. If it is, create proposal //
upon receiving txGovSubmitProposal from sender do
if !correctlyFormatted(txGovSubmitProposal) then
// check if proposal is correctly formatted. Includes fee payment.
throw
else
if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then
// InitialDeposit is negative or null OR sender has insufficient funds
throw
else
sender.AtomBalance -= txGovSubmitProposal.InitialDeposit
proposalID = generate new proposalID
proposal = NewProposal()
proposal.Title = txGovSubmitProposal.Title
proposal.Description = txGovSubmitProposal.Description
proposal.Type = txGovSubmitProposal.Type
proposal.Category = txGovSubmitProposal.Category
proposal.Deposit = txGovSubmitProposal.InitialDeposit
proposal.SubmitBlock = CurrentBlock
store(Deposits, <proposalID>:<sender>, txGovSubmitProposal.InitialDeposit)
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then
// MinDeposit is not reached
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
proposal.InitProcedureNumber = -1
else
// MinDeposit is reached
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedureNumber = ActiveProcedureNumber
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.GovPubKey>, validatorGovInfo)
ProposalProcessingQueue.push(proposalID)
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
return proposalID
```
#### Deposit
Once a proposal is submitted, if `Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit.
```Go
type TxGovDeposit struct {
ProposalID int64 // ID of the proposal
Deposit int64 // Number of Atoms to add to the proposal's deposit
}
```
**State modifications:**
- Decrease balance of sender by `deposit`
- Initialize or increase `deposit` of sender in `Deposits`
- Increase `proposal.Deposit` by sender's `deposit`
- If `MinDeposit` is reached:
- Push `proposalID` in `ProposalProcessingQueueEnd`
- Store each validator's voting power in `ValidatorGovInfos`
A `TxGovDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode.
```
// 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) then
throw
else
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
throw
else
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (proposal.Deposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
throw
else
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
throw
else
// sender can deposit
sender.AtomBalance -= txGovDeposit.Deposit
deposit = load(store, Deposits, <txGovDeposit.ProposalID>:<sender>)
if (deposit == nil)
// sender has never deposited on this proposal
store(Deposits, <txGovDeposit.ProposalID>:<sender>, deposit)
else
// sender has already deposited on this proposal
newDeposit = deposit + txGovDeposit.Deposit
store(Deposits, <txGovDeposit.ProposalID>:<sender>, newDeposit)
proposal.Deposit += txGovDeposit.Deposit
if (proposal.Deposit >= activeProcedure.MinDeposit) then
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedureNumber = ActiveProcedureNumber
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.GovPubKey>, validatorGovInfo)
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
```
#### Claiming deposit
Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send `TxGovClaimDeposit` transaction to claim their deposits.
```Go
type TxGovClaimDeposit struct {
ProposalID int64
}
```
**State modifications:**
If conditions are met, reimburse the deposit, i.e.
- Increase `AtomBalance` of sender by `deposit`
- Set `deposit` of sender in `DepositorsList` to 0
And the associated pseudocode
```
// PSEUDOCODE //
/* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */
upon receiving txGovClaimDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovClaimDeposit) then
throw
else
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
deposit = load(store, Deposits, <txGovClaimDeposit.ProposalID>:<sender>)
if (deposit == nil)
// sender has not deposited on this proposal
throw
else
if (deposit <= 0)
// deposit has already been claimed
throw
else
if (proposal.VotingStartBlock <= 0)
// Vote never started
activeProcedure = load(store, Procedures, ActiveProcedureNumber)
if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod)
// MaxDepositPeriod is not reached
throw
else
// MaxDepositPeriod is reached
// Set sender's deposit to 0 and refund
store(Deposits, <txGovClaimDeposit.ProposalID>:<sender>, 0)
sender.AtomBalance += deposit
else
// Vote started
initProcedure = load(store, Procedures, proposal.InitProcedureNumber)
if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR
((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then
// Proposal was accepted either because
// Proposal was urgent and special condition was met
// Voting period ended and vote satisfies threshold
store(Deposits, <txGovClaimDeposit.ProposalID>:<sender>, 0)
sender.AtomBalance += deposit
```
#### Vote
Once `ActiveProcedure.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
Option string // option from OptionSet chosen by the voter
ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to
}
```
**State modifications:**
- If sender is not a validator and validator has not voted, initialize or increase minus of validator by sender's `voting power`
- If sender is not a validator and validator has voted, decrease `proposal.Votes['validatorOption']` by sender's `voting power`
- If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` by sender's `voting power`
- If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by validator's `InitialVotingPower - minus` (`minus` can be equal to 0)
Votes need to be tied to a validator in order to compute validator's voting power. If a delegator is bonded to multiple validators, it will have to send one transaction per validator (the UI should facilitate this so that multiple transactions can be sent in one "vote flow").
If the sender is the validator itself, then it will input its own GovernancePubKey as `ValidatorPubKey`
Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled:
```
// 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) then
throw
else
proposal = load(store, Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
else
initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened
validator = load(store, Validators, txGovVote.ValidatorPubKey)
if !initProcedure.OptionSet.includes(txGovVote.Option) OR
(validator == nil) then
// Throws if
// Option is not in Option Set of procedure that was active when vote opened OR if
// ValidatorPubKey is not the GovPubKey of a current validator
throw
else
option = load(store, Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey>)
if (option != nil)
// sender has already voted with the Atoms bonded to ValidatorPubKey
throw
else
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR
(proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
// Vote had ended OR if
// sender bonded Atoms to ValidatorPubKey after start of vote OR if
// sender unbonded Atoms from ValidatorPubKey after start of vote OR if
// proposal is urgent and special condition is met, i.e. proposal is accepted and closed
throw
else
validatorGovInfo = load(store, ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorGovPubKey>)
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
throw
else
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey>, txGovVote.Option)
if (sender != validator.GovPubKey)
// Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey
if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then
// check in Staking module
throw
else
validatorOption = load(store, Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorPubKey)
if (validatorOption == nil)
// Validator has not voted already
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorPubKey)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorGovPubKey>, validatorGovInfo)
else
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey)
// increase votes of option chosen by sender by bonded Amount
proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey)
else
// sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey
// i.e. sender == validator
proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
```
## Future improvements (not in scope for MVP)
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.
- **`ParameterProposals` and `WhitelistProposals`:** These proposals would automatically change pre-defined parameters and whitelists. Upon acceptance, these proposals would not require validators to do the signal and switch process.
- **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.

View File

@ -10,11 +10,29 @@ procedure, either to modify a value or add/remove a parameter, a new procedure
has to be created and the previous one rendered inactive.
```go
type VoteType byte
const (
VoteTypeYes = 0x1
VoteTypeNo = 0x2
VoteTypeNoWithVeto = 0x3
VoteTypeAbstain = 0x4
)
type ProposalType byte
const (
ProposalTypePlainText = 0x1
ProposalTypeSoftwareUpgrade = 0x2
)
type Procedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain}
ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal}
VoteTypes []VoteType // Vote types available to voters.
ProposalTypes []ProposalType // Proposal types available to submitters.
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
@ -30,7 +48,7 @@ The current active procedure is stored in a global `params` KVStore.
```go
type Deposit struct {
Amount sdk.Coins // sAmount of coins deposited by depositer
Amount sdk.Coins // Amount of coins deposited by depositer
Depositer crypto.address // Address of depositer
}
```
@ -39,27 +57,27 @@ The current active procedure is stored in a global `params` KVStore.
```go
type Votes struct {
YesVotes int64
NoVote int64
NoWithVetoVotes int64
AbstainVotes int64
Yes int64
No int64
NoWithVeto int64
Abstain int64
}
```
### Proposals
`Proposals` are item to be voted on.
`Proposals` are an item to be voted on.
```go
type Proposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Type ProposalType // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
Deposits []Deposit // List of deposits on the proposal
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
Submitter crypto.address // Address of the submitter
Submitter crypto.Address // Address of the submitter
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0)
@ -83,14 +101,12 @@ type ValidatorGovInfo struct {
*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*
* `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their
* `Proposals: int64 => Proposal` maps `proposalID` to the `Proposal`
`proposalID`
* `Options`: A mapping `map[[]byte]string` of options indexed by
`<proposalID>:<voterAddress>:<validatorAddress>` as `[]byte`. Given a
`proposalID`, an `address` and a validator's `address`, returns option chosen by this `address` for this validator (`nil` if `address` has not voted under this validator)
* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's
governance infos indexed by `<proposalID>:<validatorAddress>`. Returns
`nil` if proposal has not entered voting period or if `address` was not the
* `Options: <proposalID | voterAddress | validatorAddress> => VoteType`: maps to the vote of the `voterAddress` for `proposalID` re its delegation to `validatorAddress`.
Returns 0x0 If `voterAddress` has not voted under this validator.
* `ValidatorGovInfos: <proposalID | validatorAddress> => ValidatorGovInfo`: maps to the gov info for the `validatorAddress` and `proposalID`.
Returns `nil` if proposal has not entered voting period or if `address` was not the
address of a validator when proposal entered voting period.
For pseudocode purposes, here are the two function we will use to read or write in stores:
@ -121,62 +137,62 @@ And the pseudocode for the `ProposalProcessingQueue`:
// Recursive function. First call in BeginBlock
func checkProposal()
if (ProposalProcessingQueue.Peek() == nil)
proposalID = ProposalProcessingQueue.Peek()
if (proposalID == nil)
return
else
proposalID = ProposalProcessingQueue.Peek()
proposal = load(Proposals, proposalID)
proposal = load(Proposals, proposalID)
if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3)
if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower > 2/3)
// proposal was urgent and accepted under the special condition
// no punishment
// refund deposits
// proposal accepted early by super-majority
// no punishments; refund deposits
ProposalProcessingQueue.pop()
ProposalProcessingQueue.pop()
newDeposits = new []Deposits
var newDeposits []Deposits
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
// XXX: why do we need to reset deposits? cant we just clear it ?
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
proposal.Deposits = newDeposits
store(Proposals, <proposalID>, proposal)
proposal.Deposits = newDeposits
store(Proposals, proposalID, proposal)
checkProposal()
checkProposal()
else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod)
else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod)
ProposalProcessingQueue.pop()
activeProcedure = load(params, 'ActiveProcedure')
ProposalProcessingQueue.pop()
activeProcedure = load(params, 'ActiveProcedure')
for each validator in CurrentBondedValidators
validatorGovInfo = load(ValidatorGovInfos, <proposalID>:<validator.address>)
for each validator in CurrentBondedValidators
validatorGovInfo = load(ValidatorGovInfos, <proposalID | validator.Address>)
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
validatorOption = load(Options, <proposalID>:<validator.address><validator.address>)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
validatorOption = load(Options, <proposalID | validator.Address>)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
if((proposal.Votes.YesVotes/(proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes)) > 0.5 AND (proposal.Votes.NoWithVetoVotes/(proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes) < 1/3))
totalNonAbstain = proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes
if( proposal.Votes.YesVotes/totalNonAbstain > 0.5 AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < 1/3)
// proposal was accepted at the end of the voting period
// refund deposits
// refund deposits (non-voters already punished)
newDeposits = new []Deposits
var newDeposits []Deposits
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
proposal.Deposits = newDeposits
store(Proposals, <proposalID>, proposal)
store(Proposals, proposalID, proposal)
checkProposal()
```

View File

@ -11,7 +11,7 @@ transaction.
type TxGovSubmitProposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Type ProposalType // Type of proposal
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
}
```
@ -36,61 +36,58 @@ upon receiving txGovSubmitProposal from sender do
if !correctlyFormatted(txGovSubmitProposal) then
// check if proposal is correctly formatted. Includes fee payment.
throw
initialDeposit = txGovSubmitProposal.InitialDeposit
if (initialDeposit <= 0) OR (sender.AtomBalance < initialDeposit) then
// InitialDeposit is negative or null OR sender has insufficient funds
throw
sender.AtomBalance -= initialDeposit
proposalID = generate new proposalID
proposal = NewProposal()
proposal.Title = txGovSubmitProposal.Title
proposal.Description = txGovSubmitProposal.Description
proposal.Type = txGovSubmitProposal.Type
proposal.TotalDeposit = initialDeposit
proposal.SubmitBlock = CurrentBlock
proposal.Deposits.append({initialDeposit, sender})
proposal.Submitter = sender
proposal.Votes.Yes = 0
proposal.Votes.No = 0
proposal.Votes.NoWithVeto = 0
proposal.Votes.Abstain = 0
activeProcedure = load(params, 'ActiveProcedure')
if (initialDeposit < activeProcedure.MinDeposit) then
// MinDeposit is not reached
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
else
if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then
// InitialDeposit is negative or null OR sender has insufficient funds
// MinDeposit is reached
throw
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
else
sender.AtomBalance -= txGovSubmitProposal.InitialDeposit
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
proposalID = generate new proposalID
proposal = NewProposal()
validatorGovInfo = new ValidatorGovInfo
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
proposal.Title = txGovSubmitProposal.Title
proposal.Description = txGovSubmitProposal.Description
proposal.Type = txGovSubmitProposal.Type
proposal.TotalDeposit = txGovSubmitProposal.InitialDeposit
proposal.SubmitBlock = CurrentBlock
proposal.Deposits.append({InitialDeposit, sender})
proposal.Submitter = sender
proposal.Votes.YesVotes = 0
proposal.Votes.NoVotes = 0
proposal.Votes.NoWithVetoVotes = 0
proposal.Votes.AbstainVotes = 0
store(ValidatorGovInfos, <proposalID | validator.Address>, validatorGovInfo)
activeProcedure = load(params, 'ActiveProcedure')
ProposalProcessingQueue.push(proposalID)
if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then
// MinDeposit is not reached
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
else
// MinDeposit is reached
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = new ValidatorGovInfo
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.Address>, validatorGovInfo)
ProposalProcessingQueue.push(proposalID)
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
return proposalID
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
return proposalID
```
### Deposit
@ -127,61 +124,54 @@ upon receiving txGovDeposit from sender do
if !correctlyFormatted(txGovDeposit) then
throw
proposal = load(Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
throw
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
throw
activeProcedure = load(params, 'ActiveProcedure')
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
// TODO: shouldnt we do something here ?
throw
else
proposal = load(Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
throw
else
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
// sender can deposit
throw
sender.AtomBalance -= txGovDeposit.Deposit
else
activeProcedure = load(params, 'ActiveProcedure')
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit += txGovDeposit.Deposit
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit is reached, vote opens
throw
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
else
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
throw
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
else
// sender can deposit
store(ValidatorGovInfos, <proposalID | validator.Address>, validatorGovInfo)
sender.AtomBalance -= txGovDeposit.Deposit
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit += txGovDeposit.Deposit
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID>:<validator.Address>, validatorGovInfo)
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
store(Proposals, txGovVote.ProposalID, proposal)
store(Proposals, txGovVote.ProposalID, proposal)
```
### Vote
@ -227,101 +217,88 @@ handled:
if !correctlyFormatted(txGovDeposit) then
throw
else
proposal = load(Proposals, txGovDeposit.ProposalID)
proposal = load(Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
// There is no proposal for this proposalID
if (proposal == nil) then
// There is no proposal for this proposalID
throw
validator = load(CurrentValidators, txGovVote.ValidatorAddress)
if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR
(validator == nil) then
// Throws if
// Option is not in Option Set of procedure that was active when vote opened OR if
// ValidatorAddress is not the address of a current validator
throw
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (option != nil)
// sender has already voted with the Atoms bonded to ValidatorAddress
throw
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR
(proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
// Vote had ended OR if
// sender bonded Atoms to ValidatorAddress after start of vote OR if
// sender unbonded Atoms from ValidatorAddress after start of vote OR if
// special condition is met, i.e. proposal is accepted and closed
throw
validatorGovInfo = load(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>)
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
throw
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>, txGovVote.Option)
if (sender != validator.address)
// Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress
if sender does not have bonded Atoms to txGovVote.ValidatorAddress then
// check in Staking module
throw
validatorOption = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (validatorOption == nil)
// Validator has not voted already
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>, validatorGovInfo)
else
validator = load(CurrentValidators, txGovVote.ValidatorAddress)
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR
(validator == nil) then
proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
// Throws if
// Option is not in Option Set of procedure that was active when vote opened OR if
// ValidatorAddress is not the address of a current validator
// increase votes of option chosen by sender by bonded Amount
throw
senderOption = txGovVote.Option
propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
else
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (option != nil)
// sender has already voted with the Atoms bonded to ValidatorAddress
throw
else
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR
(proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
// Vote had ended OR if
// sender bonded Atoms to ValidatorAddress after start of vote OR if
// sender unbonded Atoms from ValidatorAddress after start of vote OR if
// special condition is met, i.e. proposal is accepted and closed
throw
else
validatorGovInfo = load(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>)
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
throw
else
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>, txGovVote.Option)
if (sender != validator.address)
// Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress
if sender does not have bonded Atoms to txGovVote.ValidatorAddress then
// check in Staking module
throw
else
validatorOption = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (validatorOption == nil)
// Validator has not voted already
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>, validatorGovInfo)
else
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
// increase votes of option chosen by sender by bonded Amount
senderOption = txGovVote.Option
propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
store(Proposals, txGovVote.ProposalID, proposal)
store(Proposals, txGovVote.ProposalID, proposal)
else
// sender is the address of the validator whose main Address is txGovVote.ValidatorAddress
// i.e. sender == validator
proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
store(Proposals, txGovVote.ProposalID, proposal)
else
// sender is the address of the validator whose main Address is txGovVote.ValidatorAddress
// i.e. sender == validator
proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
store(Proposals, txGovVote.ProposalID, proposal)
```

47
docs/spec/ibc/README.md Normal file
View File

@ -0,0 +1,47 @@
# Cosmos Inter-Blockchain Communication (IBC) Protocol
## Abstract
This paper specifies the Cosmos Inter-Blockchain Communication (IBC) protocol. The IBC protocol defines a set of semantics for authenticated, strictly-ordered message passing between two blockchains with independent consensus algorithms.
The core IBC protocol is payload-agnostic. On top of IBC, developers can implement the semantics of a particular application, enabling users to transfer valuable assets between different blockchains while preserving the contractual guarantees of the asset in question - such as scarcity and fungibility for a currency or global uniqueness for a digital kitty-cat.
IBC requires two blockchains with cheaply verifiable rapid finality and Merkle tree substate proofs. The protocol makes no assumptions of block confirmation times or maximum network latency of packet transmissions, and the two consensus algorithms remain completely independent. Each chain maintains a local partial order and inter-chain message sequencing ensures cross-chain linearity. Once the two chains have registered a trust relationship, cryptographically verifiable packets can be sent between them.
IBC was first outlined in the [Cosmos Whitepaper](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc), and later described in more detail by the [IBC specification paper](https://github.com/cosmos/ibc/raw/master/CosmosIBCSpecification.pdf). This document supersedes both. It explains the requirements and structure of the protocol and provides sufficient detail for both analysis and implementation.
## Contents
1. **[Overview](overview.md)**
1. [Summary](overview.md#11-summary)
1. [Definitions](overview.md#12-definitions)
1. [Threat Models](overview.md#13-threat-models)
1. **[Connections](connections.md)**
1. [Definitions](connections.md#21-definitions)
1. [Requirements](connections.md#22-requirements)
1. [Connection lifecycle](connections.md#23-connection-lifecycle)
1. [Opening a connection](connections.md#231-opening-a-connection)
1. [Following block headers](connections.md#232-following-block-headers)
1. [Closing a connection](connections.md#233-closing-a-connection)
1. **[Channels & Packets](channels-and-packets.md)**
1. [Background](channels-and-packets.md#31-background)
1. [Definitions](channels-and-packets.md#32-definitions)
1. [Packet](channels-and-packets.md#321-packet)
1. [Receipt](channels-and-packets.md#322-receipt)
1. [Queue](channels-and-packets.md#323-queue)
1. [Channel](channels-and-packets.md#324-channel)
1. [Requirements](channels-and-packets.md#33-requirements)
1. [Sending a packet](channels-and-packets.md#34-sending-a-packet)
1. [Receiving a packet](channels-and-packets.md#35-receiving-a-packet)
1. [Packet relayer](channels-and-packets.md#36-packet-relayer)
1. **[Optimizations](optimizations.md)**
1. [Timeouts](optimizations.md#41-timeouts)
1. [Cleanup](optimizations.md#42-cleanup)
1. **[Conclusion](conclusion.md)**
1. **[References](references.md)**
1. **[Appendices](appendices.md)**
1. [Appendix A: Encoding Libraries](appendices.md#appendix-a-encoding-libraries)
1. [Appendix B: IBC Queue Format](appendices.md#appendix-b-ibc-queue-format)
1. [Appendix C: Merkle Proof Format](appendices.md#appendix-c-merkle-proof-formats)
1. [Appendix D: Byzantine Recovery Strategies](appendices.md#appendix-d-byzantine-recovery-strategies)
1. [Appendix E: Tendermint Header Proofs](appendices.md#appendix-e-tendermint-header-proofs)

104
docs/spec/ibc/appendices.md Normal file
View File

@ -0,0 +1,104 @@
# Appendices
([Back to table of contents](README.md#contents))
## Appendix A: Encoding Libraries
The specification has focused on semantics and functionality of the IBC protocol. However in order to facilitate the communication between multiple implementations of the protocol, we seek to define a standard syntax, or binary encoding, of the data structures defined above. Many structures are universal and for these, we provide one standard syntax. Other structures, such as _H<sub>h </sub>, U<sub>h </sub>, _and _X<sub>h</sub>_ are tied to the consensus engine and we can define the standard encoding for tendermint, but support for additional consensus engines must be added separately. Finally, there are some aspects of the messaging, such as the envelope to post this data (fees, nonce, signatures, etc.), which is different for every chain, and must be known to the relay, but are not important to the IBC algorithm itself and left undefined.
In defining a standard binary encoding for all the "universal" components, we wish to make use of a standardized library, with efficient serialization and support in multiple languages. We considered two main formats: Ethereum's RLP[[6](./references.md#6)] and Google's Protobuf[[7](./references.md#7)]. We decided for protobuf, as it is more widely supported, is more expressive for different data types, and supports code generation for very efficient (de)serialization codecs. It does have a learning curve and more setup to generate the code from the type specifications, but the ibc data types should not change often and this code generation setup only needs to happen once per language (and can be exposed in a common repo), so this is not a strong counter-argument. Efficiency, expressiveness, and wider support rule in its favor. It is also widely used in gRPC and in many microservice architectures.
The tendermint-specific data structures are encoded with go-wire[[8](./references.md#8)], the native binary encoding used inside of tendermint. Most blockchains define their own formats, and until some universal format for headers and signatures among blockchains emerge, it seems very premature to enforce any encoding here. These are defined as arbitrary byte slices in the protocol, to be parsed in an consensus engine-dependent manner.
For the following appendixes, the data structure specifications will be in proto3[[9](./references.md#9)] format.
## Appendix B: IBC Queue Format
The foundational data structure of the IBC protocol are the packet queues stored inside each chain. We start with a well-defined binary representation of the keys and values used in these queues. The encodings mirror the semantics defined above:
_key = _(_remote id, [send|receipt], [head|tail|index])_
_V<sub>send</sub> = (maxHeight, maxTime, type, data)_
_V<sub>receipt</sub> = (result, [success|error code])_
Keys and values are binary encoded and stored as bytes in the Merkle tree in order to generate the root hash stored in the block header, which validates all proofs. They are treated as arrays of bytes by the Merkle proofs for deterministically generating the sequence of hashes and passed as such in all interchain messages. Once the validity of a key value pair has been determined from the Merkle proof and header, the payload bytes can be deserialized and interpreted by the protocol.
See [binary format as protobuf specification](./protobuf/queue.proto)
## Appendix C: Merkle Proof Formats
A Merkle tree (or a trie) generates a single hash that can be used to prove any element of the tree. In order to generate this hash, we first hash the leaf nodes, then hash multiple leaf nodes together to get the hash of an inner node (two or more, based on degree k of the k-ary tree), and repeat for each level of the tree until we end up with one root hash.
With a known root hash (which is included in the block headers), the existence of a particular key/value in the tree can be proven by tracing the path to the value and revealing the (k-1) hashes for the paths not taken on each level ([[10](./references.md#10)]).
There are a number of different implementations of this basic idea, using different hash functions, as well as prefixes to prevent second preimage attacks (differentiating leaf nodes from inner nodes). Rather than force all chains that wish to participate in IBC to use the same data store, we provide a data structure that can represent Merkle proofs from a variety of data stores, and provide for chaining proofs to allow for subtrees. While searching for a solution, we did find the chainpoint proof format[[11](./references.md#11)], which inspired this design significantly, but didn't (yet) offer the flexibility we needed.
We generalize the left/right idiom to the concatenation a (possibly empty) fixed prefix, the (just calculated) last hash, and a (possibly empty) fixed suffix. We must only define two fields on each level and can represent any type, even a 16-ary Patricia tree, with this structure. One must only translate from the store's native proof to this format, and it can be verified by any chain, providing compatibility with arbitrary data stores.
The proof format also allows for chaining of trees, combining multiple Merkle stores into a "multi-store". Many applications (such as the EVM) define a data store with a large proof size for internal use. Rather than force them to change the store (impossible), or live with huge proofs (inefficient), we provide the possibility to express Merkle proofs connecting multiple subtrees. Thus, one could have one subtree for data, and a second for IBC. Each tree produces its own Merkle root, and these are then hashed together to produce the root hash that is stored in the block header.
A valid Merkle proof for IBC must either consist of a proof of one tree, and prepend `ibc` to all key names as defined above, or use a subtree named `ibc` in the first section, and store the key names as above in the second tree.
In order to minimize the size of their Merkle proofs, we recommend using Tendermint's IAVL+ tree implementation[[12](./references.md#12)], which is designed for optimal proof size and released under a permissive license. It uses an AVL tree (a type of binary tree) with ripemd160 as the hashing algorithm at each stage. This produces optimally compact proofs, ideal for posting in blockchain transactions. For a data store of _n_ values, there will be _log<sub>2</sub>(n)_ levels, each requiring one 20-byte hash for proving the branch not taken (plus possible metadata for the level). We can express a proof in a tree of 1 million elements in something around 400 bytes. If we further store all IBC messages in a separate subtree, we should expect the count of nodes in this tree to be a few thousand, and require less than 400 bytes, even for blockchains with a large state.
See [binary format as protobuf specification](./protobuf/merkle.proto)
## Appendix D: Byzantine Recovery Strategies
IBC guarantees reliable, ordered packet delivery in the face of malicious nodes or relays, on top of which application invariants can be ensured. However, all guarantees break down when the blockchain on the other end of the connection exhibits Byzantine behavior. This can take two forms: a failure of the consensus mechanism (reverting previously finalized blocks), or a failure at the application level (not correctly performing the application-level functions on the packet).
The IBC protocol can detect a limited class of Byzantine faults at the consensus level by identifying duplicate headers -- if an IBC module ever sees two different headers for the same height (or any evidence that headers belong to different forks), then it can freeze the connection immediately. State reconciliation (e.g. restoring token balances to owners of vouchers on the other chain) must be handled by blockchain governance.
If there is a big divide in the remote chain and the validation set splits (e.g. 60-40 weighted) as to the direction of the chain, then the light-client header update protocol will refuses to follow either fork. If both sides declare a hard fork and continue with new validator sets that are not compatible with the consensus engine (they don't have ⅔ support from the previous block), then the connection(s) will need to be reopened manually (by governance on the local chain) and set to the new header set(s). The IBC protocol doesn't have the option to follow both chains as the queue and associated state must map to exactly one remote chain. In a fork, the chain can continue the connection with one fork, and optionally make a fresh connection with the other fork.
Another kind of Byzantine action is at the application level. Let us assume packets represent transfer of value. If chain `A` sends a message with `x` tokens to chain `B`, then it promises to remove `x` tokens from the local supply. And if chain `B` handles this message successfully, it promises to credit `x` token vouchers to the account indicated in the packet. If chain `A` does not remove tokens from supply, or chain `B` does not generate vouchers, the application invariants (conservation of supply & fungibility) break down.
The IBC protocol does not handle these kinds of errors. They must be handled individually by each application. Applications could use Plasma-like fraud proofs to allow state recovery on one chain if fraud can be proved on the other chain. Although complex to implement, a correct implementation would allow applications to guarantee their invariants as long as *either* blockchain's consensus algorithm behaves correctly (and this could be extended to `n` chains). Economic incentives can additionally be used to disincentivize any kind of provable fraud.
## Appendix E: Tendermint Header Proofs
{ Ensure this is correct. }
**TODO: clean this all up**
This is a mess now, we need to figure out what formats we use, define go-wire, etc. or just point to the source???? Will do more later, need help here from the tendermint core team.
In order to prove a merkle root, we must fully define the headers, signatures, and validator information returned from the Tendermint consensus engine, as well as the rules by which to verify a header. We also define here the messages used for creating and removing connections to other blockchains as well as how to handle forks.
Building Blocks: Header, PubKey, Signature, Commit, ValidatorSet
→ needs input/support from Tendermint Core team (and go-crypto)
Registering Chain
Updating Header
Validator Changes
**ROOT of trust**
As mentioned in the definitions, all proofs are based on an original assumption. The root of trust here is either the genesis block (if it is newer than the unbonding period) or any signed header of the other chain.
When governance on a pair of chain, the respective chains must agree to a root of trust on the counterparty chain. This can be the genesis block on a chain that launches with an IBC channel or a later block header.
From this signed header, one can check the validator set against the validator hash stored in the header, and then verify the signatures match. This provides internal consistency and accountability, but if 5 nodes provide you different headers (eg. of forks), you must make a subjective decision which one to trust. This should be performed by on-chain governance to avoid an exploitable position of trust.
**VERIFYING HEADERS**
Once we have a trusted header with a known validator set, we can quickly validate any new header with the same validator set. To validate a new header, simply verifying that the validator hash has not changed, and that over 2/3 of the voting power in that set has properly signed a commit for that header. We can skip all intervening headers, as we have complete finality (no forks) and accountability (to punish a double-sign).
This is safe as long as we have a valid signed header by the trusted validator set that is within the unbonding period for staking. In that case, if we were given a false (forked) header, we could use this as proof to slash the stake of all the double-signing validators. This demonstrates the importance of attribution and is the same security guarantee of any non-validating full node. Even in the presence of some ultra-powerful malicious actors, this makes the cost of creating a fake proof for a header equal to at least one third of all staked tokens, which should be significantly higher than any gain of a false message.
**UPDATING VALIDATORS SET**
If the validator hash is different than the trusted one, we must simultaneously both verify that if the change is valid while, as well as use using the new set to validate the header. Since the entire validator set is not provided by default when we give a header and commit votes, this must be provided as extra data to the certifier.
A validator change in Tendermint can be securely verified with the following checks:
* First, that the new header, validators, and signatures are internally consistent
* We have a new set of validators that matches the hash on the new header
* At least 2/3 of the voting power of the new set validates the new header
* Second, that the new header is also valid in the eyes of our trust set
* Verify at least 2/3 of the voting power of our trusted set, which are also in the new set, properly signed a commit to the new header
In that case, we can update to this header, and update the trusted validator set, with the same guarantees as above (the ability to slash at least one third of all staked tokens on any false proof).

View File

@ -0,0 +1,248 @@
## 3 Channels & Packets
([Back to table of contents](README.md#contents))
### 3.1 Background
IBC uses a cross-chain message passing model that makes no assumptions about network synchrony. IBC *data packets* (hereafter just *packets*) are relayed from one blockchain to the other by external infrastructure. Chain `A` and chain `B` confirm new blocks independently, and packets from one chain to the other may be delayed or censored arbitrarily. The speed of packet transmission and confirmation is limited only by the speed of the underlying chains.
The IBC protocol as defined here is payload-agnostic. The packet receiver on chain `B` decides how to act upon the incoming message, and may add its own application logic to determine which state transactions to apply according to what data the packet contains. Both chains must only agree that the packet has been received.
To facilitate useful application logic, we introduce an IBC *channel*: a set of reliable messaging queues that allows us to guarantee a cross-chain causal ordering[[5](./references.md#5)] of IBC packets. Causal ordering means that if packet *x* is processed before packet *y* on chain `A`, packet *x* must also be processed before packet *y* on chain `B`.
IBC channels implement a vector clock[[2](references.md#2)] for the restricted case of two processes (in our case, blockchains). Given *x**y* means *x* is causally before *y*, chains `A` and `B`, and *a**b* means *a* implies *b*:
*A:send(msg<sub>i </sub>)* → *B:receive(msg<sub>i </sub>)*
*B:receive(msg<sub>i </sub>)* → *A:receipt(msg<sub>i </sub>)*
*A:send(msg<sub>i </sub>)* → *A:send(msg<sub>i+1 </sub>)*
*x* → *A:send(msg<sub>i </sub>)*
*x* → *B:receive(msg<sub>i </sub>)*
*y* → *B:receive(msg<sub>i </sub>)*
*y* → *A:receipt(msg<sub>i </sub>)*
Every transaction on the same chain already has a well-defined causality relation (order in history). IBC provides an ordering guarantee across two chains which can be used to reason about the combined state of both chains as a whole.
For example, an application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain `B` can later be redeemed back to chain `A`.
This section provides definitions for packets and channels, a high-level specification of the queue interface, and a list of the necessary proofs. To implement wire-compatible IBC, chain `A` and chain `B` must also use a common encoding format. An example binary encoding format can be found in [Appendix C](appendices.md#appendix-c-merkle-proof-formats).
### 3.2 Definitions
#### 3.2.1 Packet
We define an IBC *packet* `P` as the five-tuple `(type, sequence, source, destination, data)`, where:
`type` is an opaque routing field
`sequence` is an unsigned, arbitrary-precision integer
`source` is a string uniquely identifying the chain, connection, and channel from which this packet was sent
`destination` is a string uniquely identifying the chain, connection, and channel which should receive this packet
`data` is an opaque application payload
#### 3.2.2 Receipt
We define an IBC *receipt* `R` as the four-tuple `(sequence, source, destination, result)`, where
`sequence` is an unsigned, arbitrary-precision integer
`source` is a string uniquely identifying the chain, connection, and channel from which this packet was sent
`destination` is a string uniquely identifying the chain, connection, and channel which should receive this packet
`result` is an opaque application result payload
#### 3.2.3 Queue
To implement strict message ordering, we introduce an ordered *queue*. A queue can be conceptualized as a slice of an infinite array. Two numerical indices - `q_head` and `q_tail` - bound the slice, such that for every `index` where `q_head <= index < q_tail`, there is a queue element `q[index]`. Elements can be appended to the tail (end) and removed from the head (beginning). We introduce one further method, `advance`, to facilitate efficient queue cleanup.
Each IBC-supporting blockchain must provide a queue abstraction with the following functionality:
`init`
```
set q_head = 0
set q_tail = 0
```
`peek ⇒ e`
```
match q_head == q_tail with
true ⇒ return nil
false ⇒
return q[q_head]
```
`pop ⇒ e`
```
match q_head == q_tail with
true ⇒ return nil
false ⇒
set q_head = q_head + 1
return q_head - 1
```
`retrieve(i) ⇒ e`
```
match q_head <= i < q_tail with
true ⇒ return q[i]
false ⇒ return nil
```
`push(e)`
```
set q[q_tail] = e
set q_tail = q_tail + 1
```
`advance(i)`
```
set q_head = i
set q_tail = max(q_tail, i)
```
`head ⇒ i`
```
return q_head
```
`tail ⇒ i`
```
return q_tail
```
#### 3.2.4 Channel
We introduce the abstraction of an IBC *channel*: a set of the required packet queues to facilitate ordered bidirectional communication between two blockchains `A` and `B`. An IBC connection, as defined earlier, can have any number of associated channels. IBC connections handle header initialization & updates. All IBC channels use the same connection, but implement independent queues and thus independent ordering guarantees.
An IBC channel consists of four distinct queues, two on each chain:
`outgoing_A`: Outgoing IBC packets from chain `A` to chain `B`, stored on chain `A`
`incoming_A`: IBC receipts for incoming IBC packets from chain `B`, stored on chain `A`
`outgoing_B`: Outgoing IBC packets from chain `B` to chain `A`, stored on chain `B`
`incoming_B`: IBC receipts for incoming IBC packets from chain `A`, stored on chain `B`
### 3.3 Requirements
In order to provide the ordering guarantees specified above, each blockchain utilizing the IBC protocol must provide proofs that particular IBC packets have been stored at particular indices in the outgoing packet queue, and particular IBC packet execution results have been stored at particular indices in the incoming packet queue.
We use the previously-defined Merkle proof `M_kvh` to provide the requisite proofs. In order to do so, we must define a unique, deterministic key in the Merkle store for each message in the queue:
`key = (queue name, head | tail | index)`
The index is stored as a fixed-length unsigned integer in big endian format, so that the lexicographical order of the byte representation of the key is consistent with their sequence number. This allows us to quickly iterate over the queue, as well as prove the content of a packet (or lack of packet) at a given sequence. `head` and `tail` are two special constants that store an integer index, and are chosen such that their serializated representation cannot collide with that of any possible index.
Once written to the queue, a packet must be immutable (except for deletion when popped from the queue). That is, if a value `v` is written to a queue, then every valid proof `M_kvh` must refer to the same `v`. In practice, this means that an IBC implementation must ensure that only the IBC module can write to the IBC subspace of the blockchain's Merkle store.
Each incoming & outgoing queue for each connection must be provably associated with another uniquely identified chain, connection, and channel so that an observer can prove that a message was intended for that chain and only that chain. This can easily be done by prefixing the queue keys in the Merkle store with strings unique to the chain (such as chain identifier), connection, and channel.
### 3.4 Sending a packet
To send an IBC packet, an application module on the source chain must call the send method of the IBC module, providing a packet as defined above. The IBC module must ensure that the destination chain was already properly registered and that the calling module has permission to write this packet. If all is in order, the IBC module simply pushes the packet to the tail of `outgoing_a`, which enables all the proofs described above.
The packet must provide routing information in the `type` field, so that different modules can write different kinds of packets and maintain any application-level invariants related to this area. For example, a "coin" module can ensure a fixed supply, or a "NFT" module can ensure token uniqueness. The IBC module on the destination chain must associate every supported packet type with a particular handler (`f_type`).
To send an IBC packet from blockchain `A` to blockchain `B`:
`send(P{type, sequence, source, destination, data}) ⇒ success | failure`
```
case
source /= (A, connection, channel) ⇒ fail with "wrong sender"
sequence /= tail(outgoing_A) ⇒ fail with "wrong sequence"
otherwise ⇒
push(outgoing_A, P)
success
```
Note that the `sequence`, `source`, and `destination` can all be encoded in the Merkle tree key for the channel and do not need to be stored individually in each packet.
### 3.5 Receiving a packet
Upon packet receipt, chain `B` must check that the packet is valid, that it was intended for the destination, and that all previous packets have been processed. `receive` must write the receipt queue upon accepting a valid packet regardless of the result of handler execution so that future packets can be processed.
To receive an IBC packet on blockchain `B` from a source chain `A`, with a Merkle proof `M_kvh` and the current set of trusted headers for that chain `T_A`:
`receive(P{type, sequence, source, destination, data}, M_kvh) ⇒ success | failure`
```
case
incoming_B == nil ⇒ fail with "unregistered sender"
destination /= (B, connection, channel) ⇒ fail with "wrong destination"
sequence /= head(Incoming_B) ⇒ fail with "out of order"
H_h not in T_A ⇒ fail with "must submit header for height h"
valid(H_h, M_kvh) == false ⇒ fail with "invalid Merkle proof"
otherwise ⇒
set result = f_type(data)
push(incoming_B, R{tail(incoming_B), (B, connection, channel), (A, connection, channel), result})
success
```
### 3.6 Handling a receipt
When we wish to create a transaction that atomically commits or rolls back across two chains, we must look at the execution result returned in the IBC receipt. For example, if I want to send tokens from Alice on chain `A` to Bob on chain `B`, chain `A` must decrement Alice's account *if and only if* Bob's account was incremented on chain `B`. We can achieve that by storing a protected intermediate state on chain `A` (escrowing the assets in question), which is then committed or rolled back based on the result of executing the transaction on chain `B`.
To do this requires that we not only provably send a packet from chain `A` to chain `B`, but provably return the result of executing that packet (the receipt `data`) from chain `B` to chain `A`. If a valid IBC packet was sent from `A` to `B`, then the result of executing it is stored in `incoming_B`. Since the receipts are stored in a queue with the same key construction as the sending queue, we can generate the same set of proofs for them, and perform a similar sequence of steps to handle a receipt coming back to `A` for a message previously sent to `B`. Receipts, like packets, are processed in order.
To handle an IBC receipt on blockchain `A` received from blockchain `B`, with a Merkle proof `M_kvh` and the current set of trusted headers for that chain `T_B`:
`handle_receipt(R{sequence, source, destination, data}, M_kvh)`
```
case
outgoing_A == nil ⇒ fail with "unregistered sender"
destination /= (A, connection, channel) ⇒ fail with "wrong destination"
sequence /= head(incoming_A) ⇒ fail with "out of order"
H_h not in T_B ⇒ fail with "must submit header for height h"
valid(H_h, M_kvh) == false ⇒ fail with "invalid Merkle proof"
otherwise ⇒
set P{type, _, _, _, _} = pop(outgoing_A)
f_type(result)
success
```
This allows applications to reason about ordering and enforce application-level guarantees by committing or reverting state changes on chain `A` based on the result of packet execution on chain `B`:
![Successful Transaction](images/Receipts.png)
![Rejected Transaction](images/ReceiptError.png)
### 3.7 Packet relayer
The blockchain itself only records the *intention* to send the given message to the recipient chain. Physical network packet relay must be performed by off-chain infrastructure. We define the concept of a *relay* process that connects two chains by querying one for all outgoing packets & proofs, then committing those packets & proofs to the recipient chain.
The relay process must have access to accounts on both chains with sufficient balance to pay for transaction fees but needs no other permissions. Relayers may employ application-level methods to recoup these fees. Any number of *relay* processes may be safely run in parallel. However, they will consume unnecessary fees if they submit the same proof multiple times, so some minimal coordination is ideal.
As an example, here is a naive algorithm for relaying outgoing packets from `A` to `B` and incoming receipts from `B` back to `A`. All reads of variables belonging to a chain imply queries and all function calls imply submitting a transaction to the blockchain.
```
while true
set pending = tail(outgoing_A)
set received = tail(incoming_B)
if pending > received
set U_h = A.latestHeader
if U_h /= B.knownHeaderA
B.updateHeader(U_h)
for i from received to pending
set P = outgoing_A[i]
set M_kvh = A.prove(U_h, P)
B.receive(P, M_kvh)
```
Note that updating a header is a costly transaction compared to posting a Merkle proof for a known header. Thus, a process could wait until many messages are pending, then submit one header along with multiple Merkle proofs, rather than a separate header for each message. This decreases total computation cost (and fees) at the price of additional latency and is a trade-off each relay can dynamically adjust.

View File

@ -0,0 +1,9 @@
## 5 Conclusion
([Back to table of contents](README.md#contents))
We have demonstrated a secure, performant, and flexible protocol for cross-blockchain messaging, and provided sufficient detail to reason about the correctness and efficiency of the protocol.
This document defines solely a message queue protocol - not the application-level semantics which must sit on top of it to enable asset transfer between two chains. We will shortly release a separate paper on Cosmos IBC that defines the application logic used for direct value transfer as well as routing over the Cosmos hub. That paper builds upon the IBC protocol defined here and provides a first example of how to reason about application logic and global invariants in the context of IBC.
There is a reference implementation of the Cosmos IBC protocol as part of the Cosmos SDK, written in Golang and released under the Apache license. To facilitate implementations in other langauages which are wire-compatible with the Cosmos implementation, the following appendices define exact message and binary encoding formats.

View File

@ -0,0 +1,97 @@
## 2 Connections
([Back to table of contents](README.md#contents))
The basis of IBC is the ability to verify in the on-chain consensus ruleset of chain `B` that a data packet received on chain `B` was correctly generated on chain `A`. This establishes a cross-chain linearity guarantee: upon validation of that packet on chain `B` we know that the packet has been executed on chain `A` and any associated logic resolved (such as assets being escrowed), and we can safely perform application logic on chain `B` (such as generating vouchers on chain `B` for the chain `A` assets which can later be redeemed with a packet in the opposite direction).
This section outlines the abstraction of an IBC _connection_: the state and consensus ruleset necessary to perform IBC packet verification.
### 2.1 Definitions
- Chain `A` is the source blockchain from which the IBC packet is sent
- Chain `B` is the destination blockchain on which the IBC packet is received
- `H_h` is the signed header of chain `A` at height `h`
- `C_h` is a subset of the consensus ruleset of chain `A` at height `h`
- `V_kh` is the value stored on chain `A` under key `k` at height `h`
- `P` is the unbonding period of chain `P`, in units of time
- `dt(a, b)` is the time difference between events `a` and `b`
Note that of all these, only `H_h` defines a signature and is thus attributable.
### 2.2 Requirements
To facilitate an IBC connection, the two blockchains must provide the following proofs:
1. Given a trusted `H_h` and `C_h` and an attributable update message `U_h`,
it is possible to prove `H_h'` where `C_h' == C_h` and `dt(now, H_h) < P`
2. Given a trusted `H_h` and `C_h` and an attributable change message `X_h`,
it is possible to prove `H_h'` where `C_h' /= C_h` and `dt(now, H_h) < P`
3. Given a trusted `H_h` and a Merkle proof `M_kvh` it is possible to prove `V_kh`
It is possible to make use of the structure of BFT consensus to construct extremely lightweight and provable messages `U_h'` and `X_h'`. The implementation of these requirements with Tendermint consensus is defined in [Appendix E](appendices.md#appendix-e-tendermint-header-proofs). Another algorithm able to provide equally strong guarantees (such as Casper) is also compatible with IBC but must define its own set of update and change messages.
The Merkle proof `M_kvh` is a well-defined concept in the blockchain space, and provides a compact proof that the key value pair `(k, v)` is consistent with a Merkle root stored in `H_h`. Handling the case where `k` is not in the store requires a separate proof of non-existence, which is not supported by all Merkle stores. Thus, we define the proof only as a proof of existence. There is no valid proof for missing keys, and we design the algorithm to work without it.
Blockchains supporting IBC must implement Merkle proof verification:
`valid(H_h, M_kvh) ⇒ true | false`
### 2.3 Connection Lifecycle
#### 2.3.1 Opening a connection
All proofs require an initial `H_h` and `C_h` for some `h`, where `dt(now, H_h) < P`.
Establishing a bidirectional initial root-of-trust between the two blockchains (`A` to `B` and `B` to `A`) — `H_ah` and `C_ah` stored on chain `B`, and `H_bh` and `C_bh` stored on chain `A` — is necessary before any IBC packets can be sent.
Any header may be from a malicious chain (e.g. shadowing a real chain state with a fake validator set), so a subjective decision is required before establishing a connection. This can be performed permissionlessly, in which case users later utilizing the IBC channel must check the root-of-trust themselves, or authorized by on-chain governance for additional assurance.
#### 2.3.2 Following block headers
We define two messages `U_h` and `X_h`, which together allow us to securely advance our trust from some known `H_n` to some future `H_h` where `h > n`. Some implementations may require that `h == n + 1` (all headers must be processed in order). IBC implemented on top of Tendermint or similar BFT algorithms requires only that `delta-vals(C_n, C_h) < ⅓` (each step must have a change of less than one-third of the validator set)[[4](./references.md#4)].
Either requirement is compatible with IBC. However, by supporting proofs where `h - n > 1`, we can follow the block headers much more efficiently in situations where the majority of blocks do not include an IBC packet between chains `A` and `B`, and enable low-bandwidth connections to be implemented at very low cost. If there are packets to relay every block, these two requirements collapse to the same case (every header must be relayed).
Since these messages `U_h` and `X_h` provide all knowledge of the remote blockchain, we require that they not just be provable, but also attributable. As such, any attempt to violate the finality guarantees in headers posted to chain `B` can be submitted back to chain `A` for punishment, in the same manner that chain `A` would independently punish (slash) identified Byzantine actors.
More formally, given existing set of trust `T` = `{(H_i, C_i), (H_j, C_j), …}`, we must provide:
`valid(T, X_h | U_h) ⇒ true | false | unknown`
`valid` must fulfill the following properties:
```
if H_h-1 ∈ T then
valid(T, X_h | U_h) ⇒ true | false
∃ (U_h | X_h) ⇒ valid(T, X_h | U_h)
```
```
if C_h ∉ T then
valid(T, U_h) ⇒ false
```
We can then process update transactions as follows:
`update(T, X_h | U_h) ⇒ success | failure`
```
update(T, X_h | U_h) = match valid(T, X_h | U_h) with
false ⇒ fail with "invalid proof"
unknown ⇒ fail with "need a proof between current and h"
true ⇒
set T = T (H_h, C_h)
```
Define `max(T)` as `max(h, where H_h ∈ T)`. For any `T` with `max(T) == h-1`, there must exist some `X_h | U_h` so that `max(update(T, X_h | U_h)) == h`.
By induction, there must exist a set of proofs, such that `max(update…(T,...)) == h + n` for any `n`.
Bisection can be used to discover this set of proofs. That is, given `max(T) == n` and `valid(T, X_h | U_h) == unknown`, we then try `update(T, X_b | U_b)`, where _`b == (h + n) / 2`. The base case is where `valid(T, X_h | U_h) == true` and is guaranteed to exist if `h == max(T) + 1`.
#### 2.3.3 Closing a connection
IBC implementations may optionally include the ability to close an IBC connection and prevent further header updates, simply causing `update(T, X_h | U_h)` as defined above to always return `false`.
Closing a connection may break application invariants (such as fungiblity - token vouchers on chain `B` will no longer be redeemable for tokens on chain `A`) and should only be undertaken in extreme circumstances such as Byzantine behavior of the connected chain.
Closure may be permissioned to an on-chain governance system, an identifiable party on the other chain (such as a signer quorum, although this will not work in some Byzantine cases), or any user who submits an application-specific fraud proof. When a connection is closed, application-specific measures may be undertaken to recover assets held on a Byzantine chain. We defer further discussion to [Appendix D](appendices.md#appendix-d-byzantine-recovery-strategies).

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,95 @@
## 4 Optimizations
([Back to table of contents](README.md#contents))
The above sections describe a secure messaging protocol that can handle all normal situations between two blockchains. All messages are processed exactly once and in order, and applications can guarantee invariants over their combined state on both chains. IBC can be further extended and optimized to provide additional guarantees and minimize costs on the underlying blockchains. We detail two extensions: packet timeouts and packet cleanup.
### 4.1 Timeouts
Application semantics may require some timeout: an upper limit to how long the chain will wait for a transaction to be processed before considering it an error. Since the two chains have different local clocks, this is an obvious attack vector for a double spend - an attacker may delay the relay of the receipt or wait to send the packet until right after the timeout - so applications cannot safely implement naive timeout logic themselves.
One solution is to include a timeout in the IBC packet itself. When sending a packet, one can specify a block height or timestamp on chain `B` after which the packet is no longer valid. If the packet is posted before the cutoff, it will be processed normally. If it is posted after the cutoff, it will be a guaranteed error. In order to provide the necessary guarantees, the timeout must be specified relative to a condition on the receiving chain, and the sending chain must have proof of this condition after the cutoff.
For a sending chain `A` and a receiving chain `B`, with an IBC packet `P={_, i, _, _, _}` and some height `h` on chain `B`, the base IBC protocol provides the following guarantees:
`A:M_kvh == ∅` if message `i` was not sent before height `h`
`A:M_kvh == ∅` if message `i` was sent and the corresponding receipt received before height `h` (and the receipts for all messages j < i were also handled)
`A:M_kvh /= ∅` otherwise, if message `i` was sent but the receipt has not yet been processed
`B:M_kvh == ∅` if message `i` was not received before height `h`
`B:M_kvh /= ∅` if message `i` was received before height `h`
We can make a few modifications of the above protocol to allow us to prove timeouts, by adding some fields to the messages in the send queue and defining an expired function that returns true iff `h > maxHeight` or `timestamp(H_h) > maxTime`.
`P = (type, sequence, source, destination, data, maxHeight, maxTime)`
`expired(H_h, P) ⇒ true | false`
We then update message handling in `receive`, so that chain `B` doesn't even call the handler function if the timeout was reached but instead directly writes an error in the receipt queue:
`receive`
```
case
...
expired(latestHeader, v) ⇒ push(incoming_b, R{..., TimeoutError})
otherwise ⇒
set result = f_type(data)
push(incoming_B, R{tail(incoming_B), (B, connection, channel), (A, connection, channel), result})
```
The `receipt_handler` function on chain `A` can now verify timeouts and pass valid timeout receipts to the application handler (which can revert state changes such as escrowing assets):
`receipt_handler`
```
case
...
result == TimeoutError ⇒ case
not expired(H_h, P) ⇒ fail with "message timeout not yet reached"
otherwise ⇒ f_type(R, TimeoutError)
...
```
This adds one more guarantee:
`A:M_kvh == ∅` if message i was sent and timeout proven before height h (and the receipts for all messages j < i were also handled).
Now chain `A` can rollback all transactions that were blocked by this flood of unrelayed packets - since they can never confirm - without waiting for chain `B` to process them and return a receipt. Adding reasonable timeouts to all packets allows us to gracefully handle any errors with the IBC relay processes or a flood of unrelayed "spam" IBC packets. If a blockchain requires a timeout on all messages and imposes some reasonable upper limit, we can guarantee that if a packet is not processed by the upper limit of the timeout period, then all previous packets must also have either been processed or reached the timeout period.
Note that in order to avoid any possible "double-spend" attacks, the timeout algorithm requires that the destination chain is running and reachable. One can prove nothing in a complete network partition, and must wait to connect; the timeout must be proven on the recipient chain, not simply the absence of a response on the sending chain.
Additionally, if timestamp-based timeouts are used instead of height-based timeouts, the destination chain's consensus ruleset must enforce always-increasing timestamps (or the sending chain must use a more complex `expired` function).
### 4.2 Cleanup
While we clean up the _send queue_ upon getting a receipt, if left to run indefinitely, the _receipt queues_ could grow without limit and create a major storage cost for the chains. However, we must not delete receipts until they have been proven to be processed by the sending chain, or we lose important information and sacrifice reliability.
Additionally, with the above timeout implementation, when we perform the timeout on the sending chain, we do not update the _receipt queue_ on the receiving chain, and now it is blocked waiting for a packet `i`, which no longer exists on the sending chain. We can update the guarantees of the receipt queue as follows to allow us to handle both:
`B:M_kvh == ∅` if packet `i` was not received before height `h`
`B:M_kvh == ∅` if packet i was provably resolved on the sending chain before height `h`
`B:M_kvh /= ∅` otherwise (if packet `i` was processed before height `h` but chain `A` has not handled the receipt)
Consider a connection where many messages have been sent, and their receipts processed on the sending chain, either explicitly or through a timeout. We wish to quickly advance over all the processed messages, either for a normal cleanup, or to prepare the queue for normal use again after timeouts.
Through the definition of the send queue, we know that all packets `i < head` have been fully processed and all packets `head <= i < tail` are awaiting processing. By proving a much advanced `head` of `outgoing_B`, we can demonstrate that the sending chain already handled all messages. Thus, we can safely advance `incoming_A` to the new head of `outgoing_B`.
```
cleanup(A, M_kvh, head) = case
incoming_A == ∅ => fail with "unknown sender"
H_h ∉ T_B => fail with "must submit header for height h"
not valid(H_h, M_kvh, head) => fail with "invalid Merkle proof of outgoing_B queue height"
head >= head(incoming_A) => fail with "cleanup must go forward"
otherwise =>
advance(incoming_A, head)
```
This allows us to invoke the `cleanup` function to resolve all outstanding messages up to and including `index` with one Merkle proof. Note that if this handles both recovering from a blocked queue after timeouts, as well as a routine cleanup method to recover space. In the cleanup scenario, we assume that there may also be a number of packets that have been processed by the receiving chain, but not yet posted to the sending chain, `tail(incoming_B) > head(outgoing_A)`. As such, `advance` must not modify any packets between the head and the tail.
![Cleaning up Packets](images/CleanUp.png)

33
docs/spec/ibc/overview.md Normal file
View File

@ -0,0 +1,33 @@
## 1 Overview
([Back to table of contents](README.md#contents))
### 1.1 Summary
The IBC protocol creates a mechanism by which two replicated fault-tolerant state machines may pass messages to each other. These messages provide a base layer for the creation of communicating blockchain architecture that overcomes challenges in the scalability and extensibility of computing blockchain environments.
The IBC protocol assumes that multiple applications are running on their own blockchain with their own state and own logic. Communication is achieved over an ordered message queue primitive, allowing the creation of complex inter-chain processes without trusted third parties.
The message packets are not signed by one psuedonymous account, or even multiple, as in multi-signature sidechain implementations. Rather, IBC assigns authorization of the packets to the source blockchain's consensus algorithm, performing light-client style verification on the destination chain. The Byzantine-fault-tolerant properties of the underlying blockchains are preserved: a user transferring assets between two chains using IBC must trust only the consensus algorithms of both chains.
In this paper, we define a process of posting block headers and Merkle tree proofs to enable secure verification of individual packets. We then describe how to combine these packets into a messaging queue to guarantee ordered delivery. We then explain how to handle packet receipts (response/error) on the source chain, which enables the creation of asynchronous RPC-like protocols on top of IBC. Finally, we detail some optimizations and how to handle Byzantine blockchains.
### 1.2 Definitions
*Blockchain* - A replicated fault-tolerant state machine with a distributed consensus algorithm. The smallest unit produced through consensus is a block, which may contain many transactions, each applying some arbitrary mutation to the state.
*Module* - We assume that the state machine of each blockchain is comprised of multiple components that have limited rights to execute some particular set of state transfers (these are modules in the Cosmos SDK or smart contracts in Ethereum).
*Finality* - The guarantee that a given block will not be reverted within some predefined conditions of a consensus algorithm. All proof-of-work systems offer probabilistic finality, which means that the difficulty of reverting a block increases as the block is embedded more deeply in the chain. Many proof-of-stake systems offer much weaker guarantees, based only on the honesty of the block producers. BFT algorithms such as Tendermint guarantee complete finality upon production of a block (unless over two thirds of the validators collude to break consensus, in which case the offenders can be identified and punished - further discussion of that scenario is outside the scope of this document).
*Attributable* - Knowledge of the pseudonymous identity which made a statement, whom we can punish with some deduction of value (slashing) if the statement is false. Synonymous with accountability.
*Unbonding period* - Proof-of-stake algorithms need to lock the stake (prevent transfers) for some time to provide a lower bound for the length of a long-range attack [[3](./references.md#3)]. Complete finality is associated with a subset of the proof-of-stake class of consensus algorithms. We assume the proof-of-stake algorithms utilized by the two blockchains have some unbonding period P.
### 1.3 Threat Models
*False statements* - Any information we receive may be false.
*Network partitions and delays* - We assume an asynchronous, adversarial network with unbounded latency. Network messages may be modified, reordered, duplicated, or selectively dropped. Actors may be arbitrarily partitioned by a powerful adversary. The IBC protocol favors correctness over liveness where applicable.
*Byzantine actors* - An entire blockchain may not act according to protocol. This must be detectable and provable, allowing the communicating blockchain to revoke trust and take necessary action. Application-level protocols designed on top of IBC should consider and mitigate this risk in a manner suitable to their application.

1
docs/spec/ibc/protobuf/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pb.go

View File

@ -0,0 +1,7 @@
.PHONEY: proto test
proto:
protoc --gogo_out=. *.proto
test: proto
go install .

View File

@ -0,0 +1,79 @@
syntax = "proto3";
package protobuf;
// HashOp is the hashing algorithm we use at each level
enum HashOp {
RIPEMD160 = 0;
SHA224 = 1;
SHA256 = 2;
SHA384 = 3;
SHA512 = 4;
SHA3_224 = 5;
SHA3_256 = 6;
SHA3_384 = 7;
SHA3_512 = 8;
SHA256_X2 = 9;
};
// Op represents one hash in a chain of hashes.
// An operation takes the output of the last level and returns
// a hash for the next level:
// Op(last) => Operation(prefix + last + sufix)
//
// A simple left/right hash would simply set prefix=left or
// suffix=right and leave the other blank. However, one could
// also represent the a Patricia trie proof by setting
// prefix to the rlp encoding of all nodes before the branch
// we select, and suffix to all those after the one we select.
message Op {
bytes prefix = 1;
bytes suffix = 2;
HashOp op = 3;
}
// Data is the end value stored,
// used to generate the initial hash store
message Data {
// optional prefix allows second preimage resistance
bytes prefix = 1;
bytes key = 2;
bytes value = 3;
HashOp op = 4;
// If it is KeyValue, this is the data we want
// If it is SubTree, key is name of the tree,
// value is root hash
enum DataType {
KeyValue = 0;
SubTree = 1;
}
DataType dataType = 5;
}
// Branch will hash data and then pass it through operations
// from first to last in order to calculate the root node.
//
// Visualize Branch as representing the data closest to
// root as the first item, and the leaf as the last item.
message Branch {
// if either are non-empty, enforce this prefix on all
// leaf/inner nodes to provide second preimage resistence
bytes prefixLeaf = 1;
bytes prefixInner = 2;
// this is the data to get the original hash,
// and a set of operations to calculate the root hash
Data data = 3;
repeated Op operations = 4;
}
message MerkleProof {
// TODO: root, height, chain_id, etc...
// branches start from the value, and then may
// include multiple subtree branches to embed it
//
// The first branch must have dataType KeyValue
// Following branches must have dataType SubTree
repeated Branch branches = 1;
}

View File

@ -0,0 +1,29 @@
syntax = "proto3";
package protobuf;
import "merkle.proto";
// IBCPacket sends a proven key/value pair from an IBCQueue.
// Depending on the type of message, we require a certain type
// of key (MessageKey at a given height, or StateKey).
//
// Includes src_chain and src_height to look up the proper
// header to verify the merkle proof.
message IBCPacket {
// chain id it is coming from
string src_chain = 1;
// height for the header the proof belongs to
uint64 src_height = 2;
// the message type, which determines what key/value mean
enum MsgType {
RECEIVE = 0;
RECEIPT = 1;
TIMEOUT = 2;
CLEANUP = 3;
}
MsgType msgType = 3;
// the proof of the message, includes key and value
MerkleProof proof = 6;
}

View File

@ -0,0 +1,57 @@
syntax = "proto3";
package protobuf;
import "google/protobuf/timestamp.proto";
message QueueName {
// chain_id is which chain this queue is
// associated with
string chain_id = 1;
enum Purpose {
SEND = 0;
RECEIPT = 1;
}
Purpose purpose = 2;
}
// StateKey is a key for the head/tail of a given queue
message StateKey {
QueueName queue = 1;
// both encode into one byte with varint encoding
// never clash with 8 byte message indexes
enum State {
HEAD = 0;
TAIL = 0x7f;
}
State state = 2;
}
// StateValue is the type stored under a StateKey
message StateValue {
fixed64 index = 1;
}
// MessageKey is the key for message *index* in a given queue
message MessageKey {
QueueName queue = 1;
fixed64 index = 2;
}
// SendValue is stored under a MessageKey in the SEND queue
message SendValue {
uint64 maxHeight = 1;
google.protobuf.Timestamp maxTime = 2;
// use kind instead of type to avoid keyword conflict
bytes kind = 3;
bytes data = 4;
}
// ReceiptValue is stored under a MessageKey in the RECEIPT queue
message ReceiptValue {
// 0 is success, others are application-defined errors
int32 errorCode = 1;
// contains result on success, optional info on error
bytes data = 2;
}

View File

@ -0,0 +1,39 @@
## References
([Back to table of contents](README.md#contents))
##### 1:
[https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc](https://github.com/cosmos/cosmos/blob/master/WHITEPAPER.md#inter-blockchain-communication-ibc)
##### 2:
[https://en.wikipedia.org/wiki/Vector_clock](https://en.wikipedia.org/wiki/Vector_clock)
##### 3:
[https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d](https://blog.cosmos.network/consensus-compare-casper-vs-tendermint-6df154ad56ae#215d)
##### 4:
[https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104](https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104)
##### 5:
[http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/](http://scattered-thoughts.net/blog/2012/08/16/causal-ordering/)
##### 6:
[https://github.com/ethereum/wiki/wiki/RLP](https://github.com/ethereum/wiki/wiki/RLP)
##### 7:
[https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/)
##### 8:
[https://github.com/tendermint/go-wire](https://github.com/tendermint/go-wire)
##### 9:
[https://developers.google.com/protocol-buffers/docs/proto3](https://developers.google.com/protocol-buffers/docs/proto3)
##### 10:
[https://en.wikipedia.org/wiki/Merkle_tree](https://en.wikipedia.org/wiki/Merkle_tree)
##### 11:
[https://chainpoint.org/](https://chainpoint.org/)
##### 12:
[https://github.com/tendermint/iavl](https://github.com/tendermint/iavl)

View File

@ -0,0 +1,33 @@
# 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 candidate validators and can delegate tokens to candidate 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
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. **[Design overview](overview.md)**
2. **Implementation**
1. **[State](state.md)**
1. Global State
2. Validator Candidates
3. Delegator Bonds
4. Unbond and Rebond Queue
2. **[Transactions](transactions.md)**
1. Declare Candidacy
2. Edit Candidacy
3. Delegate
4. Unbond
5. Redelegate
6. ProveLive
3. **[Validator Set Changes](valset-changes.md)**
1. Validator set updates
2. Slashing
3. Automatic Unbonding
3. **[Future improvements](future_improvements.md)**

View File

@ -1,5 +1,38 @@
# Staking Module
## Overview
The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that
serves as a backbone of the Cosmos ecosystem. It is operated and secured by an
open and globally decentralized set of validators. Tendermint consensus is a
Byzantine fault-tolerant distributed protocol that involves all validators in
the process of exchanging protocol messages in the production of each block. To
avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up
coins in a bond deposit. Tendermint protocol messages are signed by the
validator's private key, and this is a basis for Tendermint strict
accountability that allows punishing misbehaving validators by slashing
(burning) their bonded Atoms. On the other hand, validators are rewarded for
their service of securing blockchain network by the inflationary provisions and
transactions fees. This incentives correct behavior of the validators and
provides the economic security of the network.
The native token of the Cosmos Hub is called Atom; becoming a validator of the
Cosmos Hub requires holding Atoms. However, not all Atom holders are validators
of the Cosmos Hub. More precisely, there is a selection process that determines
the validator set as a subset of all validator candidates (Atom holders that
wants to become a validator). The other option for Atom holder is to delegate
their atoms to validators, i.e., being a delegator. A delegator is an Atom
holder that has bonded its Atoms by delegating it to a validator (or validator
candidate). By bonding Atoms to secure the network (and taking a risk of being
slashed in case of misbehaviour), a user is rewarded with inflationary
provisions and transaction fees proportional to the amount of its bonded Atoms.
The Cosmos Hub is designed to efficiently facilitate a small numbers of
validators (hundreds), and large numbers of delegators (tens of thousands).
More precisely, it is the role of the Staking module of the Cosmos Hub to
support various staking functionality including validator set selection,
delegating, bonding and withdrawing Atoms, and the distribution of inflationary
provisions and transaction fees.
## Basic Terms and Definitions
* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system
@ -179,7 +212,3 @@ provisions cycle:
```go
GlobalState.BondedPool += provisionTokensHourly
```

View File

@ -1,624 +0,0 @@
# Staking Module
## Overview
The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that
serves as a backbone of the Cosmos ecosystem. It is operated and secured by an
open and globally decentralized set of validators. Tendermint consensus is a
Byzantine fault-tolerant distributed protocol that involves all validators in
the process of exchanging protocol messages in the production of each block. To
avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up
coins in a bond deposit. Tendermint protocol messages are signed by the
validator's private key, and this is a basis for Tendermint strict
accountability that allows punishing misbehaving validators by slashing
(burning) their bonded Atoms. On the other hand, validators are rewarded for
their service of securing blockchain network by the inflationary provisions and
transactions fees. This incentives correct behavior of the validators and
provides the economic security of the network.
The native token of the Cosmos Hub is called Atom; becoming a validator of the
Cosmos Hub requires holding Atoms. However, not all Atom holders are validators
of the Cosmos Hub. More precisely, there is a selection process that determines
the validator set as a subset of all validator candidates (Atom holders that
wants to become a validator). The other option for Atom holder is to delegate
their atoms to validators, i.e., being a delegator. A delegator is an Atom
holder that has bonded its Atoms by delegating it to a validator (or validator
candidate). By bonding Atoms to secure the network (and taking a risk of being
slashed in case of misbehaviour), a user is rewarded with inflationary
provisions and transaction fees proportional to the amount of its bonded Atoms.
The Cosmos Hub is designed to efficiently facilitate a small numbers of
validators (hundreds), and large numbers of delegators (tens of thousands).
More precisely, it is the role of the Staking module of the Cosmos Hub to
support various staking functionality including validator set selection,
delegating, bonding and withdrawing Atoms, and the distribution of inflationary
provisions and transaction fees.
## State
The staking module persists the following information to the store:
* `GlobalState`, describing the global pools and the inflation related fields
* validator candidates (including current validators), indexed by public key and shares in the global pool
(bonded or unbonded depending on candidate status)
* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate
public key
* the queue of unbonding delegations
* the queue of re-delegations
### Global State
The GlobalState data structure contains total Atom supply, amount of Atoms in
the bonded pool, sum of all shares distributed for the bonded pool, amount of
Atoms in the unbonded pool, sum of all shares distributed for the unbonded
pool, a timestamp of the last processing of inflation, the current annual
inflation rate, a timestamp for the last comission accounting reset, the global
fee pool, a pool of reserve taxes collected for the governance use and an
adjustment factor for calculating global fee accum. `Params` is global data
structure that stores system parameters and defines overall functioning of the
module.
``` go
type GlobalState struct {
TotalSupply int64 // total supply of Atoms
BondedPool int64 // reserve of bonded tokens
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
UnbondedPool int64 // reserve of unbonding tokens held with candidates
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
InflationLastTime int64 // timestamp of last processing of inflation
Inflation rational.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset
FeePool coin.Coins // fee pool for all the fee shares which have already been distributed
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
}
type Params struct {
HoldBonded Address // account where all bonded coins are held
HoldUnbonding Address // account where all delegated but unbonding coins are held
InflationRateChange rational.Rational // maximum annual change in inflation rate
InflationMax rational.Rational // maximum inflation rate
InflationMin rational.Rational // minimum inflation rate
GoalBonded rational.Rational // Goal of percent bonded atoms
ReserveTax rational.Rational // Tax collected on all fees
MaxVals uint16 // maximum number of validators
AllowedBondDenom string // bondable coin denomination
// gas costs for txs
GasDeclareCandidacy int64
GasEditCandidacy int64
GasDelegate int64
GasRedelegate int64
GasUnbond int64
}
```
### Candidate
The `Candidate` data structure holds the current state and some historical
actions of validators or candidate-validators.
``` go
type Candidate struct {
Status CandidateStatus
ConsensusPubKey crypto.PubKey
GovernancePubKey crypto.PubKey
Owner crypto.Address
GlobalStakeShares rational.Rat
IssuedDelegatorShares rational.Rat
RedelegatingShares rational.Rat
VotingPower rational.Rat
Commission rational.Rat
CommissionMax rational.Rat
CommissionChangeRate rational.Rat
CommissionChangeToday rational.Rat
ProposerRewardPool coin.Coins
Adjustment rational.Rat
Description Description
}
type Description struct {
Name string
DateBonded string
Identity string
Website string
Details string
}
```
Candidate parameters are described:
* Status: it can be Bonded (active validator), Unbonding (validator candidate)
or Revoked
* ConsensusPubKey: candidate public key that is used strictly for participating in
consensus
* GovernancePubKey: public key used by the validator for governance voting
* Owner: Address that is allowed to unbond coins.
* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
`Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool`
otherwise
* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators
(which includes the candidate's self-bond); a delegator share represents
their stake in the Candidate's `GlobalStakeShares`
* RedelegatingShares: The portion of `IssuedDelegatorShares` which are
currently re-delegating to a new validator
* VotingPower: Proportional to the amount of bonded tokens which the validator
has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0`
* Commission: The commission rate of fees charged to any delegators
* CommissionMax: The maximum commission rate this candidate can charge each
day from the date `GlobalState.DateLastCommissionReset`
* CommissionChangeRate: The maximum daily increase of the candidate commission
* CommissionChangeToday: Counter for the amount of change to commission rate
which has occurred today, reset on the first block of each day (UTC time)
* ProposerRewardPool: reward pool for extra fees collected when this candidate
is the proposer of a block
* Adjustment factor used to passively calculate each validators entitled fees
from `GlobalState.FeePool`
* Description
* Name: moniker
* DateBonded: date determined which the validator was bonded
* Identity: optional field to provide a signature which verifies the
validators identity (ex. UPort or Keybase)
* Website: optional website link
* Details: optional details
### DelegatorBond
Atom holders may delegate coins to candidates; under this circumstance their
funds are held in a `DelegatorBond` data structure. It is owned by one
delegator, and is associated with the shares for one candidate. The sender of
the transaction is the owner of the bond.
``` go
type DelegatorBond struct {
Candidate crypto.PubKey
Shares rational.Rat
AdjustmentFeePool coin.Coins
AdjustmentRewardPool coin.Coins
}
```
Description:
* Candidate: the public key of the validator candidate: bonding too
* Shares: the number of delegator shares received from the validator candidate
* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
entitled fees from `GlobalState.FeePool`
* AdjustmentRewardPool: Adjustment factor used to passively calculate each
bonds entitled fees from `Candidate.ProposerRewardPool`
### QueueElem
The Unbonding and re-delegation process is implemented using the ordered queue
data structure. All queue elements share a common structure:
```golang
type QueueElem struct {
Candidate crypto.PubKey
InitTime int64 // when the element was added to the queue
}
```
The queue is ordered so the next element to unbond/re-delegate is at the head.
Every tick the head of the queue is checked and if the unbonding period has
passed since `InitTime`, the final settlement of the unbonding is started or
re-delegation is executed, and the element is popped from the queue. Each
`QueueElem` is persisted in the store until it is popped from the queue.
### QueueElemUnbondDelegation
QueueElemUnbondDelegation structure is used in the unbonding queue.
```golang
type QueueElemUnbondDelegation struct {
QueueElem
Payout Address // account to pay out to
Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding
StartSlashRatio rational.Rat // candidate slash ratio
}
```
### QueueElemReDelegate
QueueElemReDelegate structure is used in the re-delegation queue.
```golang
type QueueElemReDelegate struct {
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding
NewCandidate crypto.PubKey // validator to bond to after unbond
}
```
### Transaction Overview
Available Transactions:
* TxDeclareCandidacy
* TxEditCandidacy
* TxDelegate
* TxUnbond
* TxRedelegate
* TxLivelinessCheck
* TxProveLive
## Transaction processing
In this section we describe the processing of the transactions and the
corresponding updates to the global state. In the following text we will use
`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a
reference to the queue of unbond delegations, `reDelegationQueue` is the
reference for the queue of redelegations. We use `tx` to denote a
reference to a transaction that is being processed, and `sender` to denote the
address of the sender of the transaction. We use function
`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store,
and `saveCandidate(store, candidate)` to save it. Similarly, we use
`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the
key (sender and PubKey) from the store, and
`saveDelegatorBond(store, sender, bond)` to save it.
`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the
store.
### TxDeclareCandidacy
A validator candidacy is declared using the `TxDeclareCandidacy` transaction.
```golang
type TxDeclareCandidacy struct {
ConsensusPubKey crypto.PubKey
Amount coin.Coin
GovernancePubKey crypto.PubKey
Commission rational.Rat
CommissionMax int64
CommissionMaxChange int64
Description Description
}
declareCandidacy(tx TxDeclareCandidacy):
candidate = loadCandidate(store, tx.PubKey)
if candidate != nil return // candidate with that public key already exists
candidate = NewCandidate(tx.PubKey)
candidate.Status = Unbonded
candidate.Owner = sender
init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero
init commision related fields based on the values from tx
candidate.ProposerRewardPool = Coin(0)
candidate.Description = tx.Description
saveCandidate(store, candidate)
txDelegate = TxDelegate(tx.PubKey, tx.Amount)
return delegateWithCandidate(txDelegate, candidate)
// see delegateWithCandidate function in [TxDelegate](TxDelegate)
```
### TxEditCandidacy
If either the `Description` (excluding `DateBonded` which is constant),
`Commission`, or the `GovernancePubKey` need to be updated, the
`TxEditCandidacy` transaction should be sent from the owner account:
```golang
type TxEditCandidacy struct {
GovernancePubKey crypto.PubKey
Commission int64
Description Description
}
editCandidacy(tx TxEditCandidacy):
candidate = loadCandidate(store, tx.PubKey)
if candidate == nil or candidate.Status == Revoked return
if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey
if tx.Commission >= 0 candidate.Commission = tx.Commission
if tx.Description != nil candidate.Description = tx.Description
saveCandidate(store, candidate)
return
```
### TxDelegate
Delegator bonds are created using the `TxDelegate` transaction. Within this
transaction the delegator provides an amount of coins, and in return receives
some amount of candidate's delegator shares that are assigned to
`DelegatorBond.Shares`.
```golang
type TxDelegate struct {
PubKey crypto.PubKey
Amount coin.Coin
}
delegate(tx TxDelegate):
candidate = loadCandidate(store, tx.PubKey)
if candidate == nil return
return delegateWithCandidate(tx, candidate)
delegateWithCandidate(tx TxDelegate, candidate Candidate):
if candidate.Status == Revoked return
if candidate.Status == Bonded
poolAccount = params.HoldBonded
else
poolAccount = params.HoldUnbonded
err = transfer(sender, poolAccount, tx.Amount)
if err != nil return
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0))
issuedDelegatorShares = addTokens(tx.Amount, candidate)
bond.Shares += issuedDelegatorShares
saveCandidate(store, candidate)
saveDelegatorBond(store, sender, bond)
saveGlobalState(store, gs)
return
addTokens(amount coin.Coin, candidate Candidate):
if candidate.Status == Bonded
gs.BondedPool += amount
issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool)
gs.BondedShares += issuedShares
else
gs.UnbondedPool += amount
issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
gs.UnbondedShares += issuedShares
candidate.GlobalStakeShares += issuedShares
if candidate.IssuedDelegatorShares.IsZero()
exRate = rational.One
else
exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
issuedDelegatorShares = issuedShares / exRate
candidate.IssuedDelegatorShares += issuedDelegatorShares
return issuedDelegatorShares
exchangeRate(shares rational.Rat, tokenAmount int64):
if shares.IsZero() then return rational.One
return tokenAmount / shares
```
### TxUnbond
Delegator unbonding is defined with the following transaction:
```golang
type TxUnbond struct {
PubKey crypto.PubKey
Shares rational.Rat
}
unbond(tx TxUnbond):
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil return
if bond.Shares < tx.Shares return
bond.Shares -= tx.Shares
candidate = loadCandidate(store, tx.PubKey)
revokeCandidacy = false
if bond.Shares.IsZero()
if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond)
else
saveDelegatorBond(store, sender, bond)
if candidate.Status == Bonded
poolAccount = params.HoldBonded
else
poolAccount = params.HoldUnbonded
returnedCoins = removeShares(candidate, shares)
unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio)
unbondDelegationQueue.add(unbondDelegationElem)
transfer(poolAccount, unbondingPoolAddress, returnCoins)
if revokeCandidacy
if candidate.Status == Bonded then bondedToUnbondedPool(candidate)
candidate.Status = Revoked
if candidate.IssuedDelegatorShares.IsZero()
removeCandidate(store, tx.PubKey)
else
saveCandidate(store, candidate)
saveGlobalState(store, gs)
return
removeShares(candidate Candidate, shares rational.Rat):
globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares
if candidate.Status == Bonded
gs.BondedShares -= globalPoolSharesToRemove
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove
gs.BondedPool -= removedTokens
else
gs.UnbondedShares -= globalPoolSharesToRemove
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove
gs.UnbondedPool -= removedTokens
candidate.GlobalStakeShares -= removedTokens
candidate.IssuedDelegatorShares -= shares
return returnedCoins
delegatorShareExRate(candidate Candidate):
if candidate.IssuedDelegatorShares.IsZero() then return rational.One
return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
bondedToUnbondedPool(candidate Candidate):
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares
gs.BondedShares -= candidate.GlobalStakeShares
gs.BondedPool -= removedTokens
gs.UnbondedPool += removedTokens
issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
gs.UnbondedShares += issuedShares
candidate.GlobalStakeShares = issuedShares
candidate.Status = Unbonded
return transfer(address of the bonded pool, address of the unbonded pool, removedTokens)
```
### TxRedelegate
The re-delegation command allows delegators to switch validators while still
receiving equal reward to as if they had never unbonded.
```golang
type TxRedelegate struct {
PubKeyFrom crypto.PubKey
PubKeyTo crypto.PubKey
Shares rational.Rat
}
redelegate(tx TxRedelegate):
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then return
if bond.Shares < tx.Shares return
candidate = loadCandidate(store, tx.PubKeyFrom)
if candidate == nil return
candidate.RedelegatingShares += tx.Shares
reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo)
redelegationQueue.add(reDelegationElem)
return
```
### TxLivelinessCheck
Liveliness issues are calculated by keeping track of the block precommits in
the block header. A queue is persisted which contains the block headers from
all recent blocks for the duration of the unbonding period. A validator is
defined as having livliness issues if they have not been included in more than
33% of the blocks over:
* The most recent 24 Hours if they have >= 20% of global stake
* The most recent week if they have = 0% of global stake
* Linear interpolation of the above two scenarios
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
submitted.
```golang
type TxLivelinessCheck struct {
PubKey crypto.PubKey
RewardAccount Addresss
}
```
If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the
liveliness punishment is provided as a reward to `RewardAccount`.
### TxProveLive
If the validator was kicked for liveliness issues and is able to regain
liveliness then all delegators in the temporary unbonding pool which have not
transacted to move will be bonded back to the now-live validator and begin to
once again collect provisions and rewards. Regaining liveliness is demonstrated
by sending in a `TxProveLive` transaction:
```golang
type TxProveLive struct {
PubKey crypto.PubKey
}
```
### End of block handling
```golang
tick(ctx Context):
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
time = ctx.Time()
if time > gs.InflationLastTime + ProvisionTimeout
gs.InflationLastTime = time
gs.Inflation = nextInflation(hrsPerYr).Round(1000000000)
provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr)
gs.BondedPool += provisions
gs.TotalSupply += provisions
saveGlobalState(store, gs)
if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
transfer(unbondingQueueAddress, elem.Payout, elem.Tokens)
unbondDelegationQueue.remove(elem)
if time > reDelegationQueue.head().InitTime + UnbondingPeriod
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
candidate = getCandidate(store, elem.PubKey)
returnedCoins = removeShares(candidate, elem.Shares)
candidate.RedelegatingShares -= elem.Shares
delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate)
reDelegationQueue.remove(elem)
return UpdateValidatorSet()
nextInflation(hrsPerYr rational.Rat):
if gs.TotalSupply > 0
bondedRatio = gs.BondedPool / gs.TotalSupply
else
bondedRation = 0
inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange
inflationRateChange = inflationRateChangePerYear / hrsPerYr
inflation = gs.Inflation + inflationRateChange
if inflation > params.InflationMax then inflation = params.InflationMax
if inflation < params.InflationMin then inflation = params.InflationMin
return inflation
UpdateValidatorSet():
candidates = loadCandidates(store)
v1 = candidates.Validators()
v2 = updateVotingPower(candidates).Validators()
change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets
return change
updateVotingPower(candidates Candidates):
foreach candidate in candidates do
candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate)
candidates.Sort()
foreach candidate in candidates do
if candidate is not in the first params.MaxVals
candidate.VotingPower = rational.Zero
if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate)
else if candidate.Status == UnBonded then unbondedToBondedPool(candidate)
saveCandidate(store, c)
return candidates
unbondedToBondedPool(candidate Candidate):
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares
gs.UnbondedShares -= candidate.GlobalStakeShares
gs.UnbondedPool -= removedTokens
gs.BondedPool += removedTokens
issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool)
gs.BondedShares += issuedShares
candidate.GlobalStakeShares = issuedShares
candidate.Status = Bonded
return transfer(address of the unbonded pool, address of the bonded pool, removedTokens)
```

204
docs/spec/staking/state.md Normal file
View File

@ -0,0 +1,204 @@
## State
The staking module persists the following information to the store:
* `GlobalState`, a struct describing the global pools, inflation, and
fees
* `ValidatorCandidates: <pubkey | shares> => <candidate>`, a map of all candidates (including current validators) in the store,
indexed by their public key and shares in the global pool.
* `DelegatorBonds: < delegator-address | candidate-pubkey > => <delegator-bond>`. a map of all delegations by a delegator to a candidate,
indexed by delegator address and candidate pubkey.
public key
* `UnbondQueue`, the queue of unbonding delegations
* `RedelegateQueue`, the queue of re-delegations
### Global State
The GlobalState contains information about the total amount of Atoms, the
global bonded/unbonded position, the Atom inflation rate, and the fees.
`Params` is global data structure that stores system parameters and defines overall functioning of the
module.
``` go
type GlobalState struct {
TotalSupply int64 // total supply of Atoms
BondedPool int64 // reserve of bonded tokens
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
UnbondedPool int64 // reserve of unbonding tokens held with candidates
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
InflationLastTime int64 // timestamp of last processing of inflation
Inflation rational.Rat // current annual inflation rate
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset
FeePool coin.Coins // fee pool for all the fee shares which have already been distributed
ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use
Adjustment rational.Rat // Adjustment factor for calculating global fee accum
}
type Params struct {
HoldBonded Address // account where all bonded coins are held
HoldUnbonding Address // account where all delegated but unbonding coins are held
InflationRateChange rational.Rational // maximum annual change in inflation rate
InflationMax rational.Rational // maximum inflation rate
InflationMin rational.Rational // minimum inflation rate
GoalBonded rational.Rational // Goal of percent bonded atoms
ReserveTax rational.Rational // Tax collected on all fees
MaxVals uint16 // maximum number of validators
AllowedBondDenom string // bondable coin denomination
// gas costs for txs
GasDeclareCandidacy int64
GasEditCandidacy int64
GasDelegate int64
GasRedelegate int64
GasUnbond int64
}
```
### Candidate
The `Candidate` holds the current state and some historical
actions of validators or candidate-validators.
``` go
type CandidateStatus byte
const (
Bonded CandidateStatus = 0x01
Unbonded CandidateStatus = 0x02
Revoked CandidateStatus = 0x03
)
type Candidate struct {
Status CandidateStatus
ConsensusPubKey crypto.PubKey
GovernancePubKey crypto.PubKey
Owner crypto.Address
GlobalStakeShares rational.Rat
IssuedDelegatorShares rational.Rat
RedelegatingShares rational.Rat
VotingPower rational.Rat
Commission rational.Rat
CommissionMax rational.Rat
CommissionChangeRate rational.Rat
CommissionChangeToday rational.Rat
ProposerRewardPool coin.Coins
Adjustment rational.Rat
Description Description
}
type Description struct {
Name string
DateBonded string
Identity string
Website string
Details string
}
```
Candidate parameters are described:
* Status: it can be Bonded (active validator), Unbonding (validator candidate)
or Revoked
* ConsensusPubKey: candidate public key that is used strictly for participating in
consensus
* GovernancePubKey: public key used by the validator for governance voting
* Owner: Address that is allowed to unbond coins.
* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
`Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool`
otherwise
* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators
(which includes the candidate's self-bond); a delegator share represents
their stake in the Candidate's `GlobalStakeShares`
* RedelegatingShares: The portion of `IssuedDelegatorShares` which are
currently re-delegating to a new validator
* VotingPower: Proportional to the amount of bonded tokens which the validator
has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0`
* Commission: The commission rate of fees charged to any delegators
* CommissionMax: The maximum commission rate this candidate can charge each
day from the date `GlobalState.DateLastCommissionReset`
* CommissionChangeRate: The maximum daily increase of the candidate commission
* CommissionChangeToday: Counter for the amount of change to commission rate
which has occurred today, reset on the first block of each day (UTC time)
* ProposerRewardPool: reward pool for extra fees collected when this candidate
is the proposer of a block
* Adjustment factor used to passively calculate each validators entitled fees
from `GlobalState.FeePool`
* Description
* Name: moniker
* DateBonded: date determined which the validator was bonded
* Identity: optional field to provide a signature which verifies the
validators identity (ex. UPort or Keybase)
* Website: optional website link
* Details: optional details
### DelegatorBond
Atom holders may delegate coins to candidates; under this circumstance their
funds are held in a `DelegatorBond` data structure. It is owned by one
delegator, and is associated with the shares for one candidate. The sender of
the transaction is the owner of the bond.
``` go
type DelegatorBond struct {
Candidate crypto.PubKey
Shares rational.Rat
AdjustmentFeePool coin.Coins
AdjustmentRewardPool coin.Coins
}
```
Description:
* Candidate: the public key of the validator candidate: bonding too
* Shares: the number of delegator shares received from the validator candidate
* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds
entitled fees from `GlobalState.FeePool`
* AdjustmentRewardPool: Adjustment factor used to passively calculate each
bonds entitled fees from `Candidate.ProposerRewardPool`
### QueueElem
The Unbonding and re-delegation process is implemented using the ordered queue
data structure. All queue elements share a common structure:
```golang
type QueueElem struct {
Candidate crypto.PubKey
InitTime int64 // when the element was added to the queue
}
```
The queue is ordered so the next element to unbond/re-delegate is at the head.
Every tick the head of the queue is checked and if the unbonding period has
passed since `InitTime`, the final settlement of the unbonding is started or
re-delegation is executed, and the element is popped from the queue. Each
`QueueElem` is persisted in the store until it is popped from the queue.
### QueueElemUnbondDelegation
QueueElemUnbondDelegation structure is used in the unbonding queue.
```golang
type QueueElemUnbondDelegation struct {
QueueElem
Payout Address // account to pay out to
Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding
StartSlashRatio rational.Rat // candidate slash ratio
}
```
### QueueElemReDelegate
QueueElemReDelegate structure is used in the re-delegation queue.
```golang
type QueueElemReDelegate struct {
QueueElem
Payout Address // account to pay out to
Shares rational.Rat // amount of shares which are unbonding
NewCandidate crypto.PubKey // validator to bond to after unbond
}
```

View File

@ -0,0 +1,283 @@
### Transaction Overview
Available Transactions:
* TxDeclareCandidacy
* TxEditCandidacy
* TxDelegate
* TxUnbond
* TxRedelegate
* TxProveLive
## Transaction processing
In this section we describe the processing of the transactions and the
corresponding updates to the global state. In the following text we will use
`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a
reference to the queue of unbond delegations, `reDelegationQueue` is the
reference for the queue of redelegations. We use `tx` to denote a
reference to a transaction that is being processed, and `sender` to denote the
address of the sender of the transaction. We use function
`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store,
and `saveCandidate(store, candidate)` to save it. Similarly, we use
`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the
key (sender and PubKey) from the store, and
`saveDelegatorBond(store, sender, bond)` to save it.
`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the
store.
### TxDeclareCandidacy
A validator candidacy is declared using the `TxDeclareCandidacy` transaction.
```golang
type TxDeclareCandidacy struct {
ConsensusPubKey crypto.PubKey
Amount coin.Coin
GovernancePubKey crypto.PubKey
Commission rational.Rat
CommissionMax int64
CommissionMaxChange int64
Description Description
}
declareCandidacy(tx TxDeclareCandidacy):
candidate = loadCandidate(store, tx.PubKey)
if candidate != nil return // candidate with that public key already exists
candidate = NewCandidate(tx.PubKey)
candidate.Status = Unbonded
candidate.Owner = sender
init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero
init commision related fields based on the values from tx
candidate.ProposerRewardPool = Coin(0)
candidate.Description = tx.Description
saveCandidate(store, candidate)
txDelegate = TxDelegate(tx.PubKey, tx.Amount)
return delegateWithCandidate(txDelegate, candidate)
// see delegateWithCandidate function in [TxDelegate](TxDelegate)
```
### TxEditCandidacy
If either the `Description` (excluding `DateBonded` which is constant),
`Commission`, or the `GovernancePubKey` need to be updated, the
`TxEditCandidacy` transaction should be sent from the owner account:
```golang
type TxEditCandidacy struct {
GovernancePubKey crypto.PubKey
Commission int64
Description Description
}
editCandidacy(tx TxEditCandidacy):
candidate = loadCandidate(store, tx.PubKey)
if candidate == nil or candidate.Status == Revoked return
if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey
if tx.Commission >= 0 candidate.Commission = tx.Commission
if tx.Description != nil candidate.Description = tx.Description
saveCandidate(store, candidate)
return
```
### TxDelegate
Delegator bonds are created using the `TxDelegate` transaction. Within this
transaction the delegator provides an amount of coins, and in return receives
some amount of candidate's delegator shares that are assigned to
`DelegatorBond.Shares`.
```golang
type TxDelegate struct {
PubKey crypto.PubKey
Amount coin.Coin
}
delegate(tx TxDelegate):
candidate = loadCandidate(store, tx.PubKey)
if candidate == nil return
return delegateWithCandidate(tx, candidate)
delegateWithCandidate(tx TxDelegate, candidate Candidate):
if candidate.Status == Revoked return
if candidate.Status == Bonded
poolAccount = params.HoldBonded
else
poolAccount = params.HoldUnbonded
err = transfer(sender, poolAccount, tx.Amount)
if err != nil return
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0))
issuedDelegatorShares = addTokens(tx.Amount, candidate)
bond.Shares += issuedDelegatorShares
saveCandidate(store, candidate)
saveDelegatorBond(store, sender, bond)
saveGlobalState(store, gs)
return
addTokens(amount coin.Coin, candidate Candidate):
if candidate.Status == Bonded
gs.BondedPool += amount
issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool)
gs.BondedShares += issuedShares
else
gs.UnbondedPool += amount
issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
gs.UnbondedShares += issuedShares
candidate.GlobalStakeShares += issuedShares
if candidate.IssuedDelegatorShares.IsZero()
exRate = rational.One
else
exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
issuedDelegatorShares = issuedShares / exRate
candidate.IssuedDelegatorShares += issuedDelegatorShares
return issuedDelegatorShares
exchangeRate(shares rational.Rat, tokenAmount int64):
if shares.IsZero() then return rational.One
return tokenAmount / shares
```
### TxUnbond
Delegator unbonding is defined with the following transaction:
```golang
type TxUnbond struct {
PubKey crypto.PubKey
Shares rational.Rat
}
unbond(tx TxUnbond):
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil return
if bond.Shares < tx.Shares return
bond.Shares -= tx.Shares
candidate = loadCandidate(store, tx.PubKey)
revokeCandidacy = false
if bond.Shares.IsZero()
if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond)
else
saveDelegatorBond(store, sender, bond)
if candidate.Status == Bonded
poolAccount = params.HoldBonded
else
poolAccount = params.HoldUnbonded
returnedCoins = removeShares(candidate, shares)
unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio)
unbondDelegationQueue.add(unbondDelegationElem)
transfer(poolAccount, unbondingPoolAddress, returnCoins)
if revokeCandidacy
if candidate.Status == Bonded then bondedToUnbondedPool(candidate)
candidate.Status = Revoked
if candidate.IssuedDelegatorShares.IsZero()
removeCandidate(store, tx.PubKey)
else
saveCandidate(store, candidate)
saveGlobalState(store, gs)
return
removeShares(candidate Candidate, shares rational.Rat):
globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares
if candidate.Status == Bonded
gs.BondedShares -= globalPoolSharesToRemove
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove
gs.BondedPool -= removedTokens
else
gs.UnbondedShares -= globalPoolSharesToRemove
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove
gs.UnbondedPool -= removedTokens
candidate.GlobalStakeShares -= removedTokens
candidate.IssuedDelegatorShares -= shares
return returnedCoins
delegatorShareExRate(candidate Candidate):
if candidate.IssuedDelegatorShares.IsZero() then return rational.One
return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares
bondedToUnbondedPool(candidate Candidate):
removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares
gs.BondedShares -= candidate.GlobalStakeShares
gs.BondedPool -= removedTokens
gs.UnbondedPool += removedTokens
issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool)
gs.UnbondedShares += issuedShares
candidate.GlobalStakeShares = issuedShares
candidate.Status = Unbonded
return transfer(address of the bonded pool, address of the unbonded pool, removedTokens)
```
### TxRedelegate
The re-delegation command allows delegators to switch validators while still
receiving equal reward to as if they had never unbonded.
```golang
type TxRedelegate struct {
PubKeyFrom crypto.PubKey
PubKeyTo crypto.PubKey
Shares rational.Rat
}
redelegate(tx TxRedelegate):
bond = loadDelegatorBond(store, sender, tx.PubKey)
if bond == nil then return
if bond.Shares < tx.Shares return
candidate = loadCandidate(store, tx.PubKeyFrom)
if candidate == nil return
candidate.RedelegatingShares += tx.Shares
reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo)
redelegationQueue.add(reDelegationElem)
return
```
### TxProveLive
If a validator was automatically unbonded due to liveness issues and wishes to
assert it is still online, it can send `TxProveLive`:
```golang
type TxProveLive struct {
PubKey crypto.PubKey
}
```
All delegators in the temporary unbonding pool which have not
transacted to move will be bonded back to the now-live validator and begin to
once again collect provisions and rewards.
```
TODO: pseudo-code
```

View File

@ -0,0 +1,190 @@
# Validator Set Changes
The validator set may be updated by state transitions that run at the beginning and
end of every block. This can happen one of three ways:
- voting power of a validator changes due to bonding and unbonding
- voting power of validator is "slashed" due to conflicting signed messages
- validator is automatically unbonded due to inactivity
## Voting Power Changes
At the end of every block, we run the following:
(TODO remove inflation from here)
```golang
tick(ctx Context):
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
time = ctx.Time()
if time > gs.InflationLastTime + ProvisionTimeout
gs.InflationLastTime = time
gs.Inflation = nextInflation(hrsPerYr).Round(1000000000)
provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr)
gs.BondedPool += provisions
gs.TotalSupply += provisions
saveGlobalState(store, gs)
if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
transfer(unbondingQueueAddress, elem.Payout, elem.Tokens)
unbondDelegationQueue.remove(elem)
if time > reDelegationQueue.head().InitTime + UnbondingPeriod
for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do
candidate = getCandidate(store, elem.PubKey)
returnedCoins = removeShares(candidate, elem.Shares)
candidate.RedelegatingShares -= elem.Shares
delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate)
reDelegationQueue.remove(elem)
return UpdateValidatorSet()
nextInflation(hrsPerYr rational.Rat):
if gs.TotalSupply > 0
bondedRatio = gs.BondedPool / gs.TotalSupply
else
bondedRation = 0
inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange
inflationRateChange = inflationRateChangePerYear / hrsPerYr
inflation = gs.Inflation + inflationRateChange
if inflation > params.InflationMax then inflation = params.InflationMax
if inflation < params.InflationMin then inflation = params.InflationMin
return inflation
UpdateValidatorSet():
candidates = loadCandidates(store)
v1 = candidates.Validators()
v2 = updateVotingPower(candidates).Validators()
change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets
return change
updateVotingPower(candidates Candidates):
foreach candidate in candidates do
candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate)
candidates.Sort()
foreach candidate in candidates do
if candidate is not in the first params.MaxVals
candidate.VotingPower = rational.Zero
if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate)
else if candidate.Status == UnBonded then unbondedToBondedPool(candidate)
saveCandidate(store, c)
return candidates
unbondedToBondedPool(candidate Candidate):
removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares
gs.UnbondedShares -= candidate.GlobalStakeShares
gs.UnbondedPool -= removedTokens
gs.BondedPool += removedTokens
issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool)
gs.BondedShares += issuedShares
candidate.GlobalStakeShares = issuedShares
candidate.Status = Bonded
return transfer(address of the unbonded pool, address of the bonded pool, removedTokens)
```
## Slashing
Messges which may compromise the safety of the underlying consensus protocol ("equivocations")
result in some amount of the offending validator's shares being removed ("slashed").
Currently, such messages include only the following:
- prevotes by the same validator for more than one BlockID at the same
Height and Round
- precommits by the same validator for more than one BlockID at the same
Height and Round
We call any such pair of conflicting votes `Evidence`. Full nodes in the network prioritize the
detection and gossipping of `Evidence` so that it may be rapidly included in blocks and the offending
validators punished.
For some `evidence` to be valid, it must satisfy:
`evidence.Timestamp >= block.Timestamp - MAX_EVIDENCE_AGE`
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 offending validator loses
a constant `SLASH_PROPORTION` of their current stake at the beginning of the block:
```
oldShares = validator.shares
validator.shares = oldShares * (1 - SLASH_PROPORTION)
```
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.
## Automatic Unbonding
Every block includes a set of precommits by the validators for the previous block,
known as the LastCommit. A LastCommit is valid so long as it contains precommits from +2/3 of voting power.
Proposers are incentivized to include precommits from all
validators in the LastCommit by receiving additional fees
proportional to the difference between the voting power included in the
LastCommit and +2/3 (see [TODO](https://github.com/cosmos/cosmos-sdk/issues/967)).
Validators are penalized for failing to be included in the LastCommit for some
number of blocks by being automatically unbonded.
The following information is stored with each validator candidate, and is only non-zero if the candidate becomes an active validator:
```go
type ValidatorSigningInfo struct {
StartHeight int64
SignedBlocksBitArray BitArray
}
```
Where:
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
* `SignedBlocksBitArray` is a bit-array of size `SIGNED_BLOCKS_WINDOW` that records, for each of the last `SIGNED_BLOCKS_WINDOW` blocks,
whether or not this validator was included in the LastCommit. It uses a `0` if the validator was included, and a `1` if it was not.
Note it is initialized with all 0s.
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
```
h = block.Height
index = h % SIGNED_BLOCKS_WINDOW
for val in block.Validators:
signInfo = val.SignInfo
if val in block.LastCommit:
signInfo.SignedBlocksBitArray.Set(index, 0)
else
signInfo.SignedBlocksBitArray.Set(index, 1)
// validator must be active for at least SIGNED_BLOCKS_WINDOW
// before they can be automatically unbonded for failing to be
// included in 50% of the recent LastCommits
minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW
minSigned = SIGNED_BLOCKS_WINDOW / 2
blocksSigned = signInfo.SignedBlocksBitArray.Sum()
if h > minHeight AND blocksSigned < minSigned:
unbond the validator
```

View File

@ -53,7 +53,7 @@ func handleMsgQuiz(ctx sdk.Context, k Keeper, msg MsgQuiz) sdk.Result {
bonusCoins := sdk.Coins{{msg.CoolAnswer, 69}}
_, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins)
_, _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins)
if err != nil {
return err.Result()
}

View File

@ -125,7 +125,7 @@ func (k Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (ui
// Add some coins for a POW well done
func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.Address, newDifficulty uint64, newCount uint64) sdk.Error {
_, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{k.config.Denomination, k.config.Reward}})
_, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.Coin{k.config.Denomination, k.config.Reward}})
if ckErr != nil {
return ckErr
}

View File

@ -66,7 +66,7 @@ func (k Keeper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, st
return 0, ErrIncorrectStakingToken(k.codespace)
}
_, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake})
_, _, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake})
if err != nil {
return 0, err
}
@ -95,7 +95,7 @@ func (k Keeper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64,
returnedBond := sdk.Coin{stakingToken, bi.Power}
_, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond})
_, _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond})
if err != nil {
return bi.PubKey, bi.Power, err
}

View File

@ -8,6 +8,7 @@ import (
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/wire"
tmtypes "github.com/tendermint/tendermint/types"
)
// ExportCmd dumps app state to JSON
@ -21,7 +22,16 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co
if err != nil {
return errors.Errorf("Error exporting state: %v\n", err)
}
fmt.Println(string(appState))
doc, err := tmtypes.GenesisDocFromFile(ctx.Config.GenesisFile())
if err != nil {
return err
}
doc.AppStateJSON = appState
encoded, err := wire.MarshalJSONIndent(cdc, doc)
if err != nil {
return err
}
fmt.Println(string(encoded))
return nil
},
}

View File

@ -7,6 +7,7 @@ import (
"os"
"path"
"path/filepath"
"sort"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -210,16 +211,18 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) (
validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) {
// XXX sort the files by contents just incase people renamed their files
var fos []os.FileInfo
fos, err = ioutil.ReadDir(genTxsDir)
if err != nil {
return
}
genTxs := make(map[string]GenesisTx)
var nodeIDs []string
for _, fo := range fos {
filename := path.Join(genTxsDir, fo.Name())
if !fo.IsDir() && (path.Ext(filename) != ".json") {
return
continue
}
// get the genTx
@ -234,6 +237,15 @@ func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) (
return
}
genTxs[genTx.NodeID] = genTx
nodeIDs = append(nodeIDs, genTx.NodeID)
}
sort.Strings(nodeIDs)
for _, nodeID := range nodeIDs {
genTx := genTxs[nodeID]
// combine some stuff
validators = append(validators, genTx.Validator)
appGenTxs = append(appGenTxs, genTx.AppGenTx)

View File

@ -176,7 +176,16 @@ func (st *iavlStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
} else {
_, res.Value = tree.GetVersioned(key, height)
}
case "/subspace":
subspace := req.Data
res.Key = subspace
var KVs []KVPair
iterator := st.SubspaceIterator(subspace)
for ; iterator.Valid(); iterator.Next() {
KVs = append(KVs, KVPair{iterator.Key(), iterator.Value()})
}
iterator.Close()
res.Value = cdc.MustMarshalBinary(KVs)
default:
msg := fmt.Sprintf("Unexpected Query path: %v", req.Path)
return sdk.ErrUnknownRequest(msg).QueryResult()

View File

@ -263,19 +263,40 @@ func TestIAVLStoreQuery(t *testing.T) {
tree := iavl.NewVersionedTree(db, cacheSize)
iavlStore := newIAVLStore(tree, numHistory)
k, v := []byte("wind"), []byte("blows")
k2, v2 := []byte("water"), []byte("flows")
v3 := []byte("is cold")
// k3, v3 := []byte("earth"), []byte("soes")
// k4, v4 := []byte("fire"), []byte("woes")
k1, v1 := []byte("key1"), []byte("val1")
k2, v2 := []byte("key2"), []byte("val2")
v3 := []byte("val3")
ksub := []byte("key")
KVs0 := []KVPair{}
KVs1 := []KVPair{
{k1, v1},
{k2, v2},
}
KVs2 := []KVPair{
{k1, v3},
{k2, v2},
}
valExpSubEmpty := cdc.MustMarshalBinary(KVs0)
valExpSub1 := cdc.MustMarshalBinary(KVs1)
valExpSub2 := cdc.MustMarshalBinary(KVs2)
cid := iavlStore.Commit()
ver := cid.Version
query := abci.RequestQuery{Path: "/key", Data: k, Height: ver}
query := abci.RequestQuery{Path: "/key", Data: k1, Height: ver}
querySub := abci.RequestQuery{Path: "/subspace", Data: ksub, Height: ver}
// query subspace before anything set
qres := iavlStore.Query(querySub)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, valExpSubEmpty, qres.Value)
// set data
iavlStore.Set(k1, v1)
iavlStore.Set(k2, v2)
// set data without commit, doesn't show up
iavlStore.Set(k, v)
qres := iavlStore.Query(query)
qres = iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Nil(t, qres.Value)
@ -289,17 +310,21 @@ func TestIAVLStoreQuery(t *testing.T) {
query.Height = cid.Version
qres = iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v, qres.Value)
assert.Equal(t, v1, qres.Value)
// and for the subspace
qres = iavlStore.Query(querySub)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, valExpSub1, qres.Value)
// modify
iavlStore.Set(k2, v2)
iavlStore.Set(k, v3)
iavlStore.Set(k1, v3)
cid = iavlStore.Commit()
// query will return old values, as height is fixed
qres = iavlStore.Query(query)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v, qres.Value)
assert.Equal(t, v1, qres.Value)
// update to latest in the query and we are happy
query.Height = cid.Version
@ -310,10 +335,14 @@ func TestIAVLStoreQuery(t *testing.T) {
qres = iavlStore.Query(query2)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v2, qres.Value)
// and for the subspace
qres = iavlStore.Query(querySub)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, valExpSub2, qres.Value)
// default (height 0) will show latest -1
query0 := abci.RequestQuery{Path: "/store", Data: k}
query0 := abci.RequestQuery{Path: "/store", Data: k1}
qres = iavlStore.Query(query0)
assert.Equal(t, uint32(sdk.CodeOK), qres.Code)
assert.Equal(t, v, qres.Value)
assert.Equal(t, v1, qres.Value)
}

View File

@ -13,6 +13,7 @@ type MultiStore = types.MultiStore
type CacheMultiStore = types.CacheMultiStore
type CommitMultiStore = types.CommitMultiStore
type KVStore = types.KVStore
type KVPair = types.KVPair
type Iterator = types.Iterator
type CacheKVStore = types.CacheKVStore
type CommitKVStore = types.CommitKVStore

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
PKGS=$(go list ./... | grep -v /vendor/)
PKGS=$(go list ./... | grep -v /vendor/ | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test)
set -e
echo "mode: atomic" > coverage.txt

View File

@ -1,7 +1,6 @@
package types
import (
"bytes"
"fmt"
"math/big"
"strconv"
@ -153,26 +152,16 @@ func (r Rat) ToLeftPadded(totalDigits int8) string {
//___________________________________________________________________________________
//Wraps r.MarshalText() in quotes to make it a valid JSON string.
func (r Rat) MarshalJSON() ([]byte, error) {
//Wraps r.MarshalText().
func (r Rat) MarshalAmino() (string, error) {
bz, err := (&(r.Rat)).MarshalText()
if err != nil {
return nil, err
}
return []byte(fmt.Sprintf("\"%s\"", bz)), nil
return string(bz), err
}
// Requires a valid JSON string - strings quotes and calls UnmarshalText
func (r *Rat) UnmarshalJSON(data []byte) (err error) {
quote := []byte(`"`)
if len(data) < 2 ||
!bytes.HasPrefix(data, quote) ||
!bytes.HasSuffix(data, quote) {
return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string, have %v", string(data))
}
data = bytes.Trim(data, `"`)
func (r *Rat) UnmarshalAmino(text string) (err error) {
tempRat := big.NewRat(0, 1)
err = tempRat.UnmarshalText(data)
err = tempRat.UnmarshalText([]byte(text))
if err != nil {
return err
}

View File

@ -229,23 +229,34 @@ func TestSerializationText(t *testing.T) {
bz, err := r.MarshalText()
require.NoError(t, err)
r2 := NewRat(0, 1)
var r2 Rat
err = r2.UnmarshalText(bz)
require.NoError(t, err)
assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
}
func TestSerializationGoWire(t *testing.T) {
func TestSerializationGoWireJSON(t *testing.T) {
r := NewRat(1, 3)
bz, err := cdc.MarshalJSON(r)
require.NoError(t, err)
r2 := NewRat(0, 1)
var r2 Rat
err = cdc.UnmarshalJSON(bz, &r2)
require.NoError(t, err)
assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
}
func TestSerializationGoWireBinary(t *testing.T) {
r := NewRat(1, 3)
bz, err := cdc.MarshalBinary(r)
require.NoError(t, err)
var r2 Rat
err = cdc.UnmarshalBinary(bz, &r2)
require.NoError(t, err)
assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
}
type testEmbedStruct struct {
Field1 string `json:"f1"`
Field2 int `json:"f2"`

View File

@ -2,7 +2,6 @@ package types
import (
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
)
// Result is the union of ResponseDeliverTx and ResponseCheckTx.
@ -31,7 +30,7 @@ type Result struct {
ValidatorUpdates []abci.Validator
// Tags are used for transaction indexing and pubsub.
Tags []cmn.KVPair
Tags Tags
}
// TODO: In the future, more codes may be OK.

View File

@ -4,6 +4,7 @@ import (
"fmt"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
)
@ -256,3 +257,8 @@ func PrefixEndBytes(prefix []byte) []byte {
}
return end
}
//----------------------------------------
// key-value result for iterator queries
type KVPair cmn.KVPair

48
types/tags.go Normal file
View File

@ -0,0 +1,48 @@
package types
import (
cmn "github.com/tendermint/tmlibs/common"
)
// Type synonym for convenience
type Tag = cmn.KVPair
// Type synonym for convenience
type Tags cmn.KVPairs
// New empty tags
func EmptyTags() Tags {
return make(Tags, 0)
}
// Append a single tag
func (t Tags) AppendTag(k string, v []byte) Tags {
return append(t, MakeTag(k, v))
}
// Append two lists of tags
func (t Tags) AppendTags(a Tags) Tags {
return append(t, a...)
}
// New variadic tags, must be k string, v []byte repeating
func NewTags(tags ...interface{}) Tags {
var ret Tags
if len(tags)%2 != 0 {
panic("must specify key-value pairs as varargs")
}
i := 0
for {
if i == len(tags) {
break
}
ret = append(ret, Tag{Key: []byte(tags[i].(string)), Value: tags[i+1].([]byte)})
i += 2
}
return ret
}
// Make a tag from a key and a value
func MakeTag(k string, v []byte) Tag {
return Tag{Key: []byte(k), Value: v}
}

24
types/tags_test.go Normal file
View File

@ -0,0 +1,24 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAppendTags(t *testing.T) {
a := NewTags("a", []byte("1"))
b := NewTags("b", []byte("2"))
c := a.AppendTags(b)
require.Equal(t, c, Tags{MakeTag("a", []byte("1")), MakeTag("b", []byte("2"))})
}
func TestEmptyTags(t *testing.T) {
a := EmptyTags()
require.Equal(t, a, Tags{})
}
func TestNewTags(t *testing.T) {
b := NewTags("a", []byte("1"))
require.Equal(t, b, Tags{MakeTag("a", []byte("1"))})
}

View File

@ -77,8 +77,8 @@ func FeePayer(tx Tx) Address {
// gas to be used by the transaction. The ratio yields an effective "gasprice",
// which must be above some miminum to be accepted into the mempool.
type StdFee struct {
Amount Coins `json"amount"`
Gas int64 `json"gas"`
Amount Coins `json:"amount"`
Gas int64 `json:"gas"`
}
func NewStdFee(gas int64, amount ...Coin) StdFee {

View File

@ -6,10 +6,10 @@ package version
// TODO improve
const Maj = "0"
const Min = "16"
const Min = "17"
const Fix = "0"
const Version = "0.16.0"
const Version = "0.17.0"
// GitCommit set by build flags
var GitCommit = ""

View File

@ -25,13 +25,14 @@ func NewHandler(k Keeper) sdk.Handler {
func handleMsgSend(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result {
// NOTE: totalIn == totalOut should already have been checked
err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs)
tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs)
if err != nil {
return err.Result()
}
// TODO: add some tags so we can search it!
return sdk.Result{} // TODO
return sdk.Result{
Tags: tags,
}
}
// Handle MsgIssue.

View File

@ -32,22 +32,22 @@ func (keeper Keeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins)
}
// SubtractCoins subtracts amt from the coins at the addr.
func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) {
func (keeper Keeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
return subtractCoins(ctx, keeper.am, addr, amt)
}
// AddCoins adds amt to the coins at the addr.
func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) {
func (keeper Keeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
return addCoins(ctx, keeper.am, addr, amt)
}
// SendCoins moves coins from one account to another
func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error {
func (keeper Keeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) {
return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt)
}
// InputOutputCoins handles a list of inputs and outputs
func (keeper Keeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) sdk.Error {
func (keeper Keeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) {
return inputOutputCoins(ctx, keeper.am, inputs, outputs)
}
@ -74,12 +74,12 @@ func (keeper SendKeeper) HasCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coi
}
// SendCoins moves coins from one account to another
func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error {
func (keeper SendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) {
return sendCoins(ctx, keeper.am, fromAddr, toAddr, amt)
}
// InputOutputCoins handles a list of inputs and outputs
func (keeper SendKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) sdk.Error {
func (keeper SendKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) {
return inputOutputCoins(ctx, keeper.am, inputs, outputs)
}
@ -131,59 +131,65 @@ func hasCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.C
}
// SubtractCoins subtracts amt from the coins at the addr.
func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) {
func subtractCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
oldCoins := getCoins(ctx, am, addr)
newCoins := oldCoins.Minus(amt)
if !newCoins.IsNotNegative() {
return amt, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
}
err := setCoins(ctx, am, addr, newCoins)
return newCoins, err
tags := sdk.NewTags("sender", addr.Bytes())
return newCoins, tags, err
}
// AddCoins adds amt to the coins at the addr.
func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) {
func addCoins(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
oldCoins := getCoins(ctx, am, addr)
newCoins := oldCoins.Plus(amt)
if !newCoins.IsNotNegative() {
return amt, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt))
}
err := setCoins(ctx, am, addr, newCoins)
return newCoins, err
tags := sdk.NewTags("recipient", addr.Bytes())
return newCoins, tags, err
}
// SendCoins moves coins from one account to another
// NOTE: Make sure to revert state changes from tx on error
func sendCoins(ctx sdk.Context, am sdk.AccountMapper, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error {
_, err := subtractCoins(ctx, am, fromAddr, amt)
func sendCoins(ctx sdk.Context, am sdk.AccountMapper, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) (sdk.Tags, sdk.Error) {
_, subTags, err := subtractCoins(ctx, am, fromAddr, amt)
if err != nil {
return err
return nil, err
}
_, err = addCoins(ctx, am, toAddr, amt)
_, addTags, err := addCoins(ctx, am, toAddr, amt)
if err != nil {
return err
return nil, err
}
return nil
return subTags.AppendTags(addTags), nil
}
// InputOutputCoins handles a list of inputs and outputs
// NOTE: Make sure to revert state changes from tx on error
func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, outputs []Output) sdk.Error {
func inputOutputCoins(ctx sdk.Context, am sdk.AccountMapper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) {
allTags := sdk.EmptyTags()
for _, in := range inputs {
_, err := subtractCoins(ctx, am, in.Address, in.Coins)
_, tags, err := subtractCoins(ctx, am, in.Address, in.Coins)
if err != nil {
return err
return nil, err
}
allTags = allTags.AppendTags(tags)
}
for _, out := range outputs {
_, err := addCoins(ctx, am, out.Address, out.Coins)
_, tags, err := addCoins(ctx, am, out.Address, out.Coins)
if err != nil {
return err
return nil, err
}
allTags = allTags.AppendTags(tags)
}
return nil
return allTags, nil
}

View File

@ -65,7 +65,7 @@ func TestKeeper(t *testing.T) {
coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 5}})
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
_, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}})
_, _, err := coinKeeper.SubtractCoins(ctx, addr, sdk.Coins{{"barcoin", 11}})
assert.Implements(t, (*sdk.Error)(nil), err)
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"barcoin", 10}, {"foocoin", 15}}))
@ -78,7 +78,7 @@ func TestKeeper(t *testing.T) {
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}}))
assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}}))
err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}})
_, err2 := coinKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}})
assert.Implements(t, (*sdk.Error)(nil), err2)
assert.True(t, coinKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}}))
assert.True(t, coinKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}}))
@ -147,7 +147,7 @@ func TestSendKeeper(t *testing.T) {
assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}}))
assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}}))
err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}})
_, err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{{"foocoin", 50}})
assert.Implements(t, (*sdk.Error)(nil), err2)
assert.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{{"foocoin", 10}}))
assert.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{{"foocoin", 5}}))

View File

@ -119,7 +119,7 @@ func (msg MsgIssue) GetSigners() []sdk.Address {
//----------------------------------------
// Input
// Transaction Output
// Transaction Input
type Input struct {
Address sdk.Address `json:"address"`
Coins sdk.Coins `json:"coins"`

View File

@ -25,7 +25,7 @@ func NewHandler(ibcm Mapper, ck bank.Keeper) sdk.Handler {
func handleIBCTransferMsg(ctx sdk.Context, ibcm Mapper, ck bank.Keeper, msg IBCTransferMsg) sdk.Result {
packet := msg.IBCPacket
_, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins)
_, _, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins)
if err != nil {
return err.Result()
}
@ -47,7 +47,7 @@ func handleIBCReceiveMsg(ctx sdk.Context, ibcm Mapper, ck bank.Keeper, msg IBCRe
return ErrInvalidSequence(ibcm.codespace).Result()
}
_, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins)
_, _, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins)
if err != nil {
return err.Result()
}

View File

@ -34,7 +34,8 @@ func newAddress() crypto.Address {
func getCoins(ck bank.Keeper, ctx sdk.Context, addr crypto.Address) (sdk.Coins, sdk.Error) {
zero := sdk.Coins(nil)
return ck.AddCoins(ctx, addr, zero)
coins, _, err := ck.AddCoins(ctx, addr, zero)
return coins, err
}
func makeCodec() *wire.Codec {
@ -70,7 +71,7 @@ func TestIBC(t *testing.T) {
zero := sdk.Coins(nil)
mycoins := sdk.Coins{sdk.Coin{"mycoin", 10}}
coins, err := ck.AddCoins(ctx, src, mycoins)
coins, _, err := ck.AddCoins(ctx, src, mycoins)
assert.Nil(t, err)
assert.Equal(t, mycoins, coins)

View File

@ -15,42 +15,6 @@ import (
"github.com/cosmos/cosmos-sdk/x/stake"
)
//// create command to query for all candidates
//func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command {
//cmd := &cobra.Command{
//Use: "candidates",
//Short: "Query for the set of validator-candidates pubkeys",
//RunE: func(cmd *cobra.Command, args []string) error {
//key := stake.CandidatesKey
//ctx := context.NewCoreContextFromViper()
//res, err := ctx.Query(key, storeName)
//if err != nil {
//return err
//}
//// parse out the candidates
//candidates := new(stake.Candidates)
//err = cdc.UnmarshalBinary(res, candidates)
//if err != nil {
//return err
//}
//output, err := wire.MarshalJSONIndent(cdc, candidates)
//if err != nil {
//return err
//}
//fmt.Println(string(output))
//return nil
//// TODO output with proofs / machine parseable etc.
//},
//}
//cmd.Flags().AddFlagSet(fsDelegator)
//return cmd
//}
// get the command to query a candidate
func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
@ -64,9 +28,7 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command {
}
key := stake.GetCandidateKey(addr)
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(key, storeName)
if err != nil {
return err
@ -74,10 +36,7 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command {
// parse out the candidate
candidate := new(stake.Candidate)
err = cdc.UnmarshalJSON(res, candidate)
if err != nil {
return err
}
cdc.MustUnmarshalBinary(res, candidate)
output, err := wire.MarshalJSONIndent(cdc, candidate)
if err != nil {
return err
@ -93,6 +52,41 @@ func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command {
return cmd
}
// get the command to query a candidate
func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "candidates",
Short: "Query for all validator-candidate accounts",
RunE: func(cmd *cobra.Command, args []string) error {
key := stake.CandidatesKey
ctx := context.NewCoreContextFromViper()
resKVs, err := ctx.QuerySubspace(cdc, key, storeName)
if err != nil {
return err
}
// parse out the candidates
var candidates []stake.Candidate
for _, KV := range resKVs {
var candidate stake.Candidate
cdc.MustUnmarshalBinary(KV.Value, &candidate)
candidates = append(candidates, candidate)
}
output, err := wire.MarshalJSONIndent(cdc, candidates)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
// TODO output with proofs / machine parseable etc.
},
}
return cmd
}
// get the command to query a single delegator bond
func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
@ -112,9 +106,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command
delegator := crypto.Address(bz)
key := stake.GetDelegatorBondKey(delegator, addr, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.Query(key, storeName)
if err != nil {
return err
@ -122,10 +114,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command
// parse out the bond
bond := new(stake.DelegatorBond)
err = cdc.UnmarshalJSON(res, bond)
if err != nil {
return err
}
cdc.MustUnmarshalBinary(res, bond)
output, err := wire.MarshalJSONIndent(cdc, bond)
if err != nil {
return err
@ -142,44 +131,42 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command
return cmd
}
//// get the command to query all the candidates bonded to a delegator
//func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command {
//cmd := &cobra.Command{
//Use: "delegator-candidates",
//Short: "Query all delegators bond's candidate-addresses based on delegator-address",
//RunE: func(cmd *cobra.Command, args []string) error {
// get the command to query all the candidates bonded to a delegator
func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "delegator-candidates",
Short: "Query all delegators bonds based on delegator-address",
RunE: func(cmd *cobra.Command, args []string) error {
//bz, err := hex.DecodeString(viper.GetString(FlagAddressDelegator))
//if err != nil {
//return err
//}
//delegator := crypto.Address(bz)
delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator))
if err != nil {
return err
}
key := stake.GetDelegatorBondsKey(delegatorAddr, cdc)
ctx := context.NewCoreContextFromViper()
resKVs, err := ctx.QuerySubspace(cdc, key, storeName)
if err != nil {
return err
}
//key := stake.GetDelegatorBondsKey(delegator, cdc)
// parse out the candidates
var delegators []stake.DelegatorBond
for _, KV := range resKVs {
var delegator stake.DelegatorBond
cdc.MustUnmarshalBinary(KV.Value, &delegator)
delegators = append(delegators, delegator)
}
//ctx := context.NewCoreContextFromViper()
output, err := wire.MarshalJSONIndent(cdc, delegators)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
//res, err := ctx.Query(key, storeName)
//if err != nil {
//return err
//}
//// parse out the candidates list
//var candidates []crypto.PubKey
//err = cdc.UnmarshalBinary(res, candidates)
//if err != nil {
//return err
//}
//output, err := wire.MarshalJSONIndent(cdc, candidates)
//if err != nil {
//return err
//}
//fmt.Println(string(output))
//return nil
//// TODO output with proofs / machine parseable etc.
//},
//}
//cmd.Flags().AddFlagSet(fsDelegator)
//return cmd
//}
// TODO output with proofs / machine parseable etc.
},
}
cmd.Flags().AddFlagSet(fsDelegator)
return cmd
}

View File

@ -22,6 +22,8 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command {
Use: "declare-candidacy",
Short: "create new validator-candidate account and delegate some coins to it",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
amount, err := sdk.ParseCoin(viper.GetString(FlagAmount))
if err != nil {
return err
@ -56,8 +58,6 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command {
msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description)
// build and sign the transaction, then broadcast to Tendermint
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc)
if err != nil {
return err

View File

@ -59,7 +59,7 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase,
}
var bond stake.DelegatorBond
err = cdc.UnmarshalJSON(res, &bond)
err = cdc.UnmarshalBinary(res, &bond)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode bond. Error: %s", err.Error())))

View File

@ -97,14 +97,18 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe
candidate := NewCandidate(msg.CandidateAddr, msg.PubKey, msg.Description)
k.setCandidate(ctx, candidate)
tags := sdk.NewTags("action", []byte("declareCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity))
// move coins from the msg.Address account to a (self-bond) delegator account
// the candidate account and global shares are updated within here
err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate)
delegateTags, err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate)
if err != nil {
return err.Result()
}
return sdk.Result{}
tags = tags.AppendTags(delegateTags)
return sdk.Result{
Tags: tags,
}
}
func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result {
@ -128,7 +132,10 @@ func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk
candidate.Description.Details = msg.Description.Details
k.setCandidate(ctx, candidate)
return sdk.Result{}
tags := sdk.NewTags("action", []byte("editCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity))
return sdk.Result{
Tags: tags,
}
}
func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result {
@ -148,16 +155,18 @@ func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result {
GasUsed: GasDelegate,
}
}
err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate)
tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate)
if err != nil {
return err.Result()
}
return sdk.Result{}
return sdk.Result{
Tags: tags,
}
}
// common functionality between handlers
func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
bondAmt sdk.Coin, candidate Candidate) sdk.Error {
bondAmt sdk.Coin, candidate Candidate) (sdk.Tags, sdk.Error) {
// Get or create the delegator bond
bond, found := k.GetDelegatorBond(ctx, delegatorAddr, candidate.Address)
@ -171,9 +180,9 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
// Account new shares, save
pool := k.GetPool(ctx)
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt})
_, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt})
if err != nil {
return err
return nil, err
}
pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount)
bond.Shares = bond.Shares.Add(newShares)
@ -184,7 +193,8 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
k.setDelegatorBond(ctx, bond)
k.setCandidate(ctx, candidate)
k.setPool(ctx, pool)
return nil
tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "candidate", candidate.Address.Bytes())
return tags, nil
}
func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
@ -281,5 +291,8 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
k.setCandidate(ctx, candidate)
}
k.setPool(ctx, p)
return sdk.Result{}
tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "candidate", msg.CandidateAddr.Bytes())
return sdk.Result{
Tags: tags,
}
}

View File

@ -41,7 +41,7 @@ func (k Keeper) getCounter(ctx sdk.Context) int16 {
return 0
}
var counter int16
err := k.cdc.UnmarshalJSON(b, &counter)
err := k.cdc.UnmarshalBinary(b, &counter)
if err != nil {
panic(err)
}
@ -51,7 +51,7 @@ func (k Keeper) getCounter(ctx sdk.Context) int16 {
// set the current in-block validator operation counter
func (k Keeper) setCounter(ctx sdk.Context, counter int16) {
store := ctx.KVStore(k.storeKey)
bz, err := k.cdc.MarshalJSON(counter)
bz, err := k.cdc.MarshalBinary(counter)
if err != nil {
panic(err)
}
@ -67,7 +67,7 @@ func (k Keeper) GetCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candi
if b == nil {
return candidate, false
}
err := k.cdc.UnmarshalJSON(b, &candidate)
err := k.cdc.UnmarshalBinary(b, &candidate)
if err != nil {
panic(err)
}
@ -88,7 +88,7 @@ func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Ca
}
bz := iterator.Value()
var candidate Candidate
err := k.cdc.UnmarshalJSON(bz, &candidate)
err := k.cdc.UnmarshalBinary(bz, &candidate)
if err != nil {
panic(err)
}
@ -112,7 +112,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) {
}
// marshal the candidate record and add to the state
bz, err := k.cdc.MarshalJSON(candidate)
bz, err := k.cdc.MarshalBinary(candidate)
if err != nil {
panic(err)
}
@ -145,7 +145,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) {
}
// update the candidate record
bz, err = k.cdc.MarshalJSON(candidate)
bz, err = k.cdc.MarshalBinary(candidate)
if err != nil {
panic(err)
}
@ -153,7 +153,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) {
// marshal the new validator record
validator := candidate.validator()
bz, err = k.cdc.MarshalJSON(validator)
bz, err = k.cdc.MarshalBinary(validator)
if err != nil {
panic(err)
}
@ -171,7 +171,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) {
setAcc = true
}
if setAcc {
bz, err = k.cdc.MarshalJSON(validator.abciValidator(k.cdc))
bz, err = k.cdc.MarshalBinary(validator.abciValidator(k.cdc))
if err != nil {
panic(err)
}
@ -200,7 +200,7 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) {
if store.Get(GetRecentValidatorKey(address)) == nil {
return
}
bz, err := k.cdc.MarshalJSON(candidate.validator().abciValidatorZero(k.cdc))
bz, err := k.cdc.MarshalBinary(candidate.validator().abciValidatorZero(k.cdc))
if err != nil {
panic(err)
}
@ -242,7 +242,7 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) {
}
bz := iterator.Value()
var validator Validator
err := k.cdc.UnmarshalJSON(bz, &validator)
err := k.cdc.UnmarshalBinary(bz, &validator)
if err != nil {
panic(err)
}
@ -266,11 +266,11 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) {
// get the zero abci validator from the ToKickOut iterator value
bz := iterator.Value()
var validator Validator
err := k.cdc.UnmarshalJSON(bz, &validator)
err := k.cdc.UnmarshalBinary(bz, &validator)
if err != nil {
panic(err)
}
bz, err = k.cdc.MarshalJSON(validator.abciValidatorZero(k.cdc))
bz, err = k.cdc.MarshalBinary(validator.abciValidatorZero(k.cdc))
if err != nil {
panic(err)
}
@ -297,7 +297,7 @@ func (k Keeper) isNewValidator(ctx sdk.Context, store sdk.KVStore, address sdk.A
}
bz := iterator.Value()
var val Validator
err := k.cdc.UnmarshalJSON(bz, &val)
err := k.cdc.UnmarshalBinary(bz, &val)
if err != nil {
panic(err)
}
@ -330,7 +330,7 @@ func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []abci.Validato
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val abci.Validator
err := k.cdc.UnmarshalJSON(valBytes, &val)
err := k.cdc.UnmarshalBinary(valBytes, &val)
if err != nil {
panic(err)
}
@ -364,7 +364,7 @@ func (k Keeper) GetDelegatorBond(ctx sdk.Context,
return bond, false
}
err := k.cdc.UnmarshalJSON(delegatorBytes, &bond)
err := k.cdc.UnmarshalBinary(delegatorBytes, &bond)
if err != nil {
panic(err)
}
@ -385,7 +385,7 @@ func (k Keeper) getBonds(ctx sdk.Context, maxRetrieve int16) (bonds []DelegatorB
}
bondBytes := iterator.Value()
var bond DelegatorBond
err := k.cdc.UnmarshalJSON(bondBytes, &bond)
err := k.cdc.UnmarshalBinary(bondBytes, &bond)
if err != nil {
panic(err)
}
@ -410,7 +410,7 @@ func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet
}
bondBytes := iterator.Value()
var bond DelegatorBond
err := k.cdc.UnmarshalJSON(bondBytes, &bond)
err := k.cdc.UnmarshalBinary(bondBytes, &bond)
if err != nil {
panic(err)
}
@ -422,7 +422,7 @@ func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet
func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalJSON(bond)
b, err := k.cdc.MarshalBinary(bond)
if err != nil {
panic(err)
}
@ -448,7 +448,7 @@ func (k Keeper) GetParams(ctx sdk.Context) (params Params) {
panic("Stored params should not have been nil")
}
err := k.cdc.UnmarshalJSON(b, &params)
err := k.cdc.UnmarshalBinary(b, &params)
if err != nil {
panic(err)
}
@ -456,7 +456,7 @@ func (k Keeper) GetParams(ctx sdk.Context) (params Params) {
}
func (k Keeper) setParams(ctx sdk.Context, params Params) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalJSON(params)
b, err := k.cdc.MarshalBinary(params)
if err != nil {
panic(err)
}
@ -477,7 +477,7 @@ func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) {
if b == nil {
panic("Stored pool should not have been nil")
}
err := k.cdc.UnmarshalJSON(b, &pool)
err := k.cdc.UnmarshalBinary(b, &pool)
if err != nil {
panic(err) // This error should never occur big problem if does
}
@ -486,7 +486,7 @@ func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) {
func (k Keeper) setPool(ctx sdk.Context, p Pool) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalJSON(p)
b, err := k.cdc.MarshalBinary(p)
if err != nil {
panic(err)
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
crypto "github.com/tendermint/go-crypto"
)
@ -19,6 +20,12 @@ const StakingToken = "steak"
//Verify interface at compile time
var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{}
var msgCdc = wire.NewCodec()
func init() {
wire.RegisterCrypto(msgCdc)
}
//______________________________________________________________________
// MsgDeclareCandidacy - struct for unbonding transactions
@ -45,11 +52,7 @@ func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address
// get the bytes for the message signer to sign on
func (msg MsgDeclareCandidacy) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
return msgCdc.MustMarshalBinary(msg)
}
// quick validity check