Merge branch 'master' into gamarin/update_gov_spec
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = python -msphinx
|
||||
SPHINXPROJ = Cosmos-SDK
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -0,0 +1,66 @@
|
||||
# Cosmos SDK Documentation
|
||||
|
||||
NOTE: This documentation is a work-in-progress!
|
||||
|
||||
- [Overview](overview)
|
||||
- [Overview](overview/overview.md) - An overview of the Cosmos-SDK
|
||||
- [The Object-Capability Model](overview/capabilities.md) - Security by
|
||||
least-privilege
|
||||
- [Application Architecture](overview/apps.md) - Layers in the application architecture
|
||||
- [Install](install.md) - Install the library and example applications
|
||||
- [Core](core)
|
||||
- [Introduction](core/intro.md) - Intro to the tutorial
|
||||
- [App1 - The Basics](core/app1.md)
|
||||
- [Messages](core/app1.md#messages) - Messages contain the content of a transaction
|
||||
- [Stores](core/app1.md#kvstore) - KVStore is a Merkle Key-Value store.
|
||||
- [Handlers](core/app1.md#handlers) - Handlers are the workhorse of the app!
|
||||
- [Tx](core/app1.md#tx) - Transactions are the ultimate input to the
|
||||
application
|
||||
- [BaseApp](core/app1.md#baseapp) - BaseApp is the base layer of the application
|
||||
- [App2 - Transactions](core/app2.md)
|
||||
- [Amino](core/app2.md#amino) - Amino is the primary serialization library used in the SDK
|
||||
- [Ante Handler](core/app2.md#antehandler) - The AnteHandler
|
||||
authenticates transactions
|
||||
- [App3 - Modules: Auth and Bank](core/app3.md)
|
||||
- [auth.Account](core/app3.md#accounts) - Accounts are the prototypical object kept in the store
|
||||
- [auth.AccountMapper](core/app3.md#account-mapper) - AccountMapper gets and sets Account on a KVStore
|
||||
- [auth.StdTx](core/app3.md#stdtx) - `StdTx` is the default implementation of `Tx`
|
||||
- [auth.StdSignBytes](core/app3.md#signing) - `StdTx` must be signed with certain
|
||||
information
|
||||
- [auth.AnteHandler](core/app3.md#antehandler) - The `AnteHandler`
|
||||
verifies `StdTx`, manages accounts, and deducts fees
|
||||
- [bank.CoinKeeper](core/app3.md#coinkeeper) - CoinKeeper allows for coin
|
||||
transfers on an underlying AccountMapper
|
||||
- [App4 - ABCI](core/app4.md)
|
||||
- [ABCI](core/app4.md#abci) - ABCI is the interface between Tendermint
|
||||
and the Cosmos-SDK
|
||||
- [InitChain](core/app4.md#initchain) - Initialize the application
|
||||
store
|
||||
- [BeginBlock](core/app4.md#beginblock) - BeginBlock runs at the
|
||||
beginning of every block and updates the app about validator behaviour
|
||||
- [EndBlock](core/app4.md#endblock) - EndBlock runs at the
|
||||
end of every block and lets the app change the validator set.
|
||||
- [Query](core/app4.md#query) - Query the application store
|
||||
- [CheckTx](core/app4.md#checktx) - CheckTx only runs the AnteHandler
|
||||
- [App5 - Basecoin](core/app5.md) -
|
||||
- [Directory Structure](core/app5.md#directory-structure) - Keep your
|
||||
application code organized
|
||||
- [Tendermint Node](core/app5.md#tendermint-node) - Run a full
|
||||
blockchain node with your app
|
||||
- [Clients](core/app5.md#clients) - Hook up your app to CLI and REST
|
||||
interfaces for clients to use!
|
||||
|
||||
- [Modules](modules)
|
||||
- [Bank](modules/README.md#bank)
|
||||
- [Staking](modules/README.md#stake)
|
||||
- [Slashing](modules/README.md#slashing)
|
||||
- [Provisions](modules/README.md#provisions)
|
||||
- [Governance](modules/README.md#governance)
|
||||
- [IBC](modules/README.md#ibc)
|
||||
|
||||
- [Clients](clients)
|
||||
- [Running a Node](clients/node.md) - Run a full node!
|
||||
- [Key Management](clients/keys.md) - Managing user keys
|
||||
- [CLI](clients/cli.md) - Queries and transactions via command line
|
||||
- [REST Light Client Daemon](clients/rest.md) - Queries and transactions via REST
|
||||
API
|
||||
@@ -197,13 +197,13 @@ We'll have ``alice`` send some ``mycoin`` to ``bob``, who has now joined the net
|
||||
|
||||
::
|
||||
|
||||
gaiacli send --amount=1000mycoin --sequence=0 --name=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU
|
||||
gaiacli send --amount=1000mycoin --sequence=0 --from=alice --to=5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6 --chain-id=test-chain-Uv1EVU
|
||||
|
||||
where the ``--sequence`` flag is to be incremented for each transaction, the ``--name`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like:
|
||||
where the ``--sequence`` flag is to be incremented for each transaction, the ``--from`` flag is the sender (alice), and the ``--to`` flag takes ``bob``'s address. You'll see something like:
|
||||
|
||||
::
|
||||
|
||||
Please enter passphrase for alice:
|
||||
Please enter passphrase for alice:
|
||||
{
|
||||
"check_tx": {
|
||||
"gas": 30
|
||||
@@ -250,7 +250,7 @@ First, we need the pub_key data:
|
||||
|
||||
::
|
||||
|
||||
cat $HOME/.gaia2/priv_validator.json
|
||||
cat $HOME/.gaia2/priv_validator.json
|
||||
|
||||
the first part will look like:
|
||||
|
||||
@@ -260,17 +260,17 @@ the first part will look like:
|
||||
|
||||
and you want the ``pub_key`` ``data`` that starts with ``96864CE``.
|
||||
|
||||
Now ``bob`` can declare candidacy to that pubkey:
|
||||
Now ``bob`` can create a validator with that pubkey.
|
||||
|
||||
::
|
||||
|
||||
gaiacli declare-candidacy --amount=10mycoin --name=bob --pubkey=<pub_key data> --moniker=bobby
|
||||
gaiacli stake create-validator --amount=10mycoin --from=bob --address-validator=<address> --pub-key=<pubkey> --moniker=bobby
|
||||
|
||||
with an output like:
|
||||
|
||||
::
|
||||
|
||||
Please enter passphrase for bob:
|
||||
Please enter passphrase for bob:
|
||||
{
|
||||
"check_tx": {
|
||||
"gas": 30
|
||||
@@ -285,13 +285,13 @@ We should see ``bob``'s account balance decrease by 10 mycoin:
|
||||
|
||||
::
|
||||
|
||||
gaiacli account 5D93A6059B6592833CBC8FA3DA90EE0382198985
|
||||
gaiacli account 5D93A6059B6592833CBC8FA3DA90EE0382198985
|
||||
|
||||
To confirm for certain the new validator is active, ask the tendermint node:
|
||||
|
||||
::
|
||||
|
||||
curl localhost:46657/validators
|
||||
curl localhost:26657/validators
|
||||
|
||||
If you now kill either node, blocks will stop streaming in, because
|
||||
there aren't enough validators online. Turn it back on and they will
|
||||
@@ -306,19 +306,19 @@ First let's have ``alice`` send some coins to ``charlie``:
|
||||
|
||||
::
|
||||
|
||||
gaiacli tx --amount=1000mycoin --sequence=2 --name=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
gaiacli send --amount=1000mycoin --sequence=2 --from=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
|
||||
Then ``charlie`` will delegate some mycoin to ``bob``:
|
||||
|
||||
::
|
||||
|
||||
gaiacli tx delegate --amount=10mycoin --name=charlie --pubkey=<pub_key data>
|
||||
gaiacli stake delegate --amount=10mycoin --address-delegator=<charlie's address> --address-validator=<bob's address> --from=charlie
|
||||
|
||||
You'll see output like:
|
||||
|
||||
::
|
||||
|
||||
Please enter passphrase for charlie:
|
||||
Please enter passphrase for charlie:
|
||||
{
|
||||
"check_tx": {
|
||||
"gas": 30
|
||||
@@ -334,7 +334,7 @@ To get more information about the candidate, try:
|
||||
|
||||
::
|
||||
|
||||
gaiacli query candidate --pubkey=<pub_key data>
|
||||
gaiacli stake validator <address>
|
||||
|
||||
and you'll see output similar to:
|
||||
|
||||
@@ -367,7 +367,7 @@ It's also possible the query the delegator's bond like so:
|
||||
|
||||
::
|
||||
|
||||
gaiacli query delegator-bond --delegator-address 48F74F48281C89E5E4BE9092F735EA519768E8EF --pubkey 52D6FCD8C92A97F7CCB01205ADF310A18411EA8FDCC10E65BF2FCDB05AD1689B
|
||||
gaiacli stake delegation --address-delegator=<address> --address-validator=<address>
|
||||
|
||||
with an output similar to:
|
||||
|
||||
@@ -383,9 +383,9 @@ with an output similar to:
|
||||
"Shares": 20
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
where the ``--delegator-address`` is ``charlie``'s address and the ``-pubkey`` is the same as we've been using.
|
||||
|
||||
where the ``--address-delegator`` is ``charlie``'s address and the ``--address-validator`` is ``bob``'s address.
|
||||
|
||||
|
||||
Unbonding
|
||||
@@ -396,7 +396,7 @@ your VotingPower reduce and your account balance increase.
|
||||
|
||||
::
|
||||
|
||||
gaiacli unbond --amount=5mycoin --name=charlie --pubkey=<pub_key data>
|
||||
gaiacli stake unbond --amount=5mycoin --from=charlie --address-delegator=<address> --address-validator=<address>
|
||||
gaiacli account 48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
|
||||
See the bond decrease with ``gaiacli query delegator-bond`` like above.
|
||||
See the bond decrease with ``gaiacli stake delegation`` like above.
|
||||
@@ -0,0 +1,216 @@
|
||||
//TODO update .rst
|
||||
|
||||
# Staking Module
|
||||
|
||||
## Overview
|
||||
|
||||
The Cosmos Hub is a Tendermint-based Delegated Proof of Stake (DPos) 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 is
|
||||
a Byzantine fault-tolerant distributed protocol for consensus among distrusting
|
||||
parties, in this case the group of validators which produce the blocks for the
|
||||
Cosmos Hub. To avoid the nothing-at-stake problem, a validator in Tendermint
|
||||
needs to lock up coins in a bond deposit. Each bond's atoms are illiquid, they
|
||||
cannot be transferred - in order to become liquid, they must be unbonded, a
|
||||
process which will take 3 weeks by default at Cosmos Hub launch. Tendermint
|
||||
protocol messages are signed by the validator's private key and are therefor
|
||||
attributable. Validators acting outside protocol specifications can be made
|
||||
accountable through punishing 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 incentivizes
|
||||
correct behavior of the validators and provides the economic security of the
|
||||
network.
|
||||
|
||||
The native token of the Cosmos Hub is called the 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 validators (Atom holders that
|
||||
want to become a validator). The other option for Atom holders is to delegate
|
||||
their atoms to validators, i.e., being a delegator. A delegator is an Atom
|
||||
holder that has put its Atoms at stake by delegating it to a validator. 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 Delegated Proof of Stake (DPos)
|
||||
blockchain system
|
||||
* Atom - native token of the Cosmsos Hub
|
||||
* Atom holder - an entity that holds some amount of Atoms
|
||||
* Pool - Global object within the Cosmos Hub which accounts global state
|
||||
including the total amount of bonded, unbonding, and unbonded atoms
|
||||
* Validator Share - Share which a validator holds to represent its portion of
|
||||
bonded, unbonding or unbonded atoms in the pool
|
||||
* Delegation Share - Shares which a delegation bond holds to represent its
|
||||
portion of bonded, unbonding or unbonded shares in a validator
|
||||
* Bond Atoms - a process of locking Atoms in a delegation share which holds them
|
||||
under protocol control.
|
||||
* Slash Atoms - the process of burning atoms in the pool and assoiated
|
||||
validator shares of a misbehaving validator, (not behaving according to the
|
||||
protocol specification). This process devalues the worth of delegation shares
|
||||
of the given validator
|
||||
* Unbond Shares - Process of retrieving atoms from shares. If the shares are
|
||||
bonded the shares must first remain in an inbetween unbonding state for the
|
||||
duration of the unbonding period
|
||||
* Redelegating Shares - Process of redelegating atoms from one validator to
|
||||
another. This process is instantaneous, but the redelegated atoms are
|
||||
retrospecively slashable if the old validator is found to misbehave for any
|
||||
blocks before the redelegation. These atoms are simultaniously slashable
|
||||
for any new blocks which the new validator misbehavess
|
||||
* Validator - entity with atoms which is either actively validating the Tendermint
|
||||
protocol (bonded validator) or vying to validate .
|
||||
* Bonded Validator - a validator whose atoms are currently bonded and liable to
|
||||
be slashed. These validators are to be able to sign protocol messages for
|
||||
Tendermint consensus. At Cosmos Hub genesis there is a maximum of 100
|
||||
bonded validator positions. Only Bonded Validators receive atom provisions
|
||||
and fee rewards.
|
||||
* Delegator - an Atom holder that has bonded Atoms to a validator
|
||||
* Unbonding period - time required in the unbonding state when unbonding
|
||||
shares. Time slashable to old validator after a redelegation. Time for which
|
||||
validators can be slashed after an infraction. To provide the requisite
|
||||
cryptoeconomic security guarantees, all of these must be equal.
|
||||
* Atom provisions - The process of increasing the Atom supply. Atoms are
|
||||
periodically created on the Cosmos Hub and issued to bonded Atom holders.
|
||||
The goal of inflation is to incentize most of the Atoms in existence to be
|
||||
bonded. Atoms are distributed unbonded and using the fee_distribution mechanism
|
||||
* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub
|
||||
transaction. The fees are collected by the current validator set and
|
||||
distributed among validators and delegators in proportion to their bonded
|
||||
Atom share
|
||||
* Commission fee - a fee taken from the transaction fees by a validator for
|
||||
their service
|
||||
|
||||
## The pool and the share
|
||||
|
||||
At the core of the Staking module is the concept of a pool which denotes a
|
||||
collection of Atoms contributed by different Atom holders. There are three
|
||||
pools in the Staking module: the bonded, unbonding, and unbonded pool. Bonded
|
||||
Atoms are part of the global bonded pool. If a validator or delegator wants to
|
||||
unbond its shares, these Shares are moved to the the unbonding pool for the
|
||||
duration of the unbonding period. From here normally Atoms will be moved
|
||||
directly into the delegators wallet, however under the situation thatn an
|
||||
entire validator gets unbonded, the Atoms of the delegations will remain with
|
||||
the validator and moved to the unbonded pool. For each pool, the total amount
|
||||
of bonded, unbonding, or unbonded Atoms are tracked as well as the current
|
||||
amount of issued pool-shares, the specific holdings of these shares by
|
||||
validators are tracked in protocol by the validator object.
|
||||
|
||||
A share is a unit of Atom distribution and the value of the share
|
||||
(share-to-atom exchange rate) can change during system execution. The
|
||||
share-to-atom exchange rate can be computed as:
|
||||
|
||||
`share-to-atom-exchange-rate = size of the pool / ammount of issued shares`
|
||||
|
||||
Then for each validator (in a per validator data structure) the protocol keeps
|
||||
track of the amount of shares the validator owns in a pool. At any point in
|
||||
time, the exact amount of Atoms a validator has in the pool can be computed as
|
||||
the number of shares it owns multiplied with the current share-to-atom exchange
|
||||
rate:
|
||||
|
||||
`validator-coins = validator.Shares * share-to-atom-exchange-rate`
|
||||
|
||||
The benefit of such accounting of the pool resources is the fact that a
|
||||
modification to the pool from bonding/unbonding/slashing of Atoms affects only
|
||||
global data (size of the pool and the number of shares) and not the related
|
||||
validator data structure, i.e., the data structure of other validators do not
|
||||
need to be modified. This has the advantage that modifying global data is much
|
||||
cheaper computationally than modifying data of every validator. Let's explain
|
||||
this further with several small examples:
|
||||
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXX TODO make way less verbose lets use bullet points to describe the example
|
||||
XXX Also need to update to not include bonded atom provisions all atoms are
|
||||
XXX redistributed with the fee pool now
|
||||
|
||||
We consider initially 4 validators p1, p2, p3 and p4, and that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 shares (note that the initial distribution of the shares,
|
||||
i.e., share-to-atom exchange rate can be set to any meaningful value), i.e.,
|
||||
share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we
|
||||
have, the size of the pool is 40 Atoms, and the amount of issued shares is
|
||||
equal to 40. And for each validator we store in their corresponding data
|
||||
structure that each has 10 shares of the bonded pool. Now lets assume that the
|
||||
validator p4 starts process of unbonding of 5 shares. Then the total size of
|
||||
the pool is decreased and now it will be 35 shares and the amount of Atoms is
|
||||
35 . Note that the only change in other data structures needed is reducing the
|
||||
number of shares for a validator p4 from 10 to 5.
|
||||
|
||||
Let's consider now the case where a validator p1 wants to bond 15 more atoms to
|
||||
the pool. Now the size of the pool is 50, and as the exchange rate hasn't
|
||||
changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we
|
||||
now have 50 shares in the pool in total. Validators p2, p3 and p4 still have
|
||||
(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we
|
||||
don't need to modify anything in their corresponding data structures. But p1
|
||||
now has 25 shares, so we update the amount of shares owned by p1 in its
|
||||
data structure. Note that apart from the size of the pool that is in Atoms, all
|
||||
other data structures refer only to shares.
|
||||
|
||||
Finally, let's consider what happens when new Atoms are created and added to
|
||||
the pool due to inflation. Let's assume that the inflation rate is 10 percent
|
||||
and that it is applied to the current state of the pool. This means that 5
|
||||
Atoms are created and added to the pool and that each validator now
|
||||
proportionally increase it's Atom count. Let's analyse how this change is
|
||||
reflected in the data structures. First, the size of the pool is increased and
|
||||
is now 55 atoms. As a share of each validator in the pool hasn't changed, this
|
||||
means that the total number of shares stay the same (50) and that the amount of
|
||||
shares of each validator stays the same (correspondingly 25, 10, 10, 5). But
|
||||
the exchange rate has changed and each share is now worth 55/50 Atoms per
|
||||
share, so each validator has effectively increased amount of Atoms it has. So
|
||||
validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms.
|
||||
|
||||
The concepts of the pool and its shares is at the core of the accounting in the
|
||||
Staking module. It is used for managing the global pools (such as bonding and
|
||||
unbonding pool), but also for distribution of Atoms between validator and its
|
||||
delegators (we will explain this in section X).
|
||||
|
||||
#### Delegator shares
|
||||
|
||||
A validator is, depending on its status, contributing Atoms to either the
|
||||
unbonding or unbonded pool - the validator in turn holds some amount of pool
|
||||
shares. Not all of a validator's Atoms (and respective shares) are necessarily
|
||||
owned by the validator, some may be owned by delegators to that validator. The
|
||||
mechanism for distribution of Atoms (and shares) between a validator and its
|
||||
delegators is based on a notion of delegator shares. More precisely, every
|
||||
validator is issuing (local) delegator shares
|
||||
(`Validator.IssuedDelegatorShares`) that represents some portion of global
|
||||
shares managed by the validator (`Validator.GlobalStakeShares`). The principle
|
||||
behind managing delegator shares is the same as described in [Section](#The
|
||||
pool and the share). We now illustrate it with an example.
|
||||
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXX TODO make way less verbose lets use bullet points to describe the example
|
||||
XXX Also need to update to not include bonded atom provisions all atoms are
|
||||
XXX redistributed with the fee pool now
|
||||
|
||||
Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 global shares, i.e., that
|
||||
`share-to-atom-exchange-rate = 1 atom per share`. So we will set
|
||||
`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the
|
||||
Validator data structure of each validator `Validator.GlobalStakeShares = 10`.
|
||||
Furthermore, each validator issued 10 delegator shares which are initially
|
||||
owned by itself, i.e., `Validator.IssuedDelegatorShares = 10`, where
|
||||
`delegator-share-to-global-share-ex-rate = 1 global share per delegator share`.
|
||||
Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and
|
||||
consider what are the updates we need to make to the data structures. First,
|
||||
`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for
|
||||
validator p1 we have `Validator.GlobalStakeShares = 15`, but we also need to
|
||||
issue also additional delegator shares, i.e.,
|
||||
`Validator.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator
|
||||
shares of validator p1, where each delegator share is worth 1 global shares,
|
||||
i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to
|
||||
inflation. In that case, we only need to update `GlobalState.BondedPool` which
|
||||
is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note
|
||||
that the amount of global and delegator shares stay the same but they are now
|
||||
worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share.
|
||||
Therefore, a delegator d1 now owns:
|
||||
|
||||
`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms`
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# Testnet Setup
|
||||
|
||||
**Note:** This document is incomplete and may not be up-to-date with the
|
||||
state of the code.
|
||||
|
||||
See the [installation guide](../sdk/install.html) for details on
|
||||
installation.
|
||||
|
||||
Here is a quick example to get you off your feet:
|
||||
|
||||
First, generate a couple of genesis transactions to be incorporated into
|
||||
the genesis file, this will create two keys with the password
|
||||
`1234567890`:
|
||||
|
||||
```
|
||||
gaiad init gen-tx --name=foo --home=$HOME/.gaiad1
|
||||
gaiad init gen-tx --name=bar --home=$HOME/.gaiad2
|
||||
gaiacli keys list
|
||||
```
|
||||
|
||||
**Note:** If you've already run these tests you may need to overwrite
|
||||
keys using the `--owk` flag When you list the keys you should see two
|
||||
addresses, we'll need these later so take note. Now let's actually
|
||||
create the genesis files for both nodes:
|
||||
|
||||
```
|
||||
cp -a ~/.gaiad2/config/gentx/. ~/.gaiad1/config/gentx/
|
||||
cp -a ~/.gaiad1/config/gentx/. ~/.gaiad2/config/gentx/
|
||||
gaiad init --gen-txs --home=$HOME/.gaiad1 --chain-id=test-chain
|
||||
gaiad init --gen-txs --home=$HOME/.gaiad2 --chain-id=test-chain
|
||||
```
|
||||
|
||||
**Note:** If you've already run these tests you may need to overwrite
|
||||
genesis using the `-o` flag. What we just did is copy the genesis
|
||||
transactions between each of the nodes so there is a common genesis
|
||||
transaction set; then we created both genesis files independently from
|
||||
each home directory. Importantly both nodes have independently created
|
||||
their `genesis.json` and `config.toml` files, which should be identical
|
||||
between nodes.
|
||||
|
||||
Great, now that we've initialized the chains, we can start both nodes in
|
||||
the background:
|
||||
|
||||
```
|
||||
gaiad start --home=$HOME/.gaiad1 &> gaia1.log &
|
||||
NODE1_PID=$!
|
||||
gaia start --home=$HOME/.gaiad2 &> gaia2.log &
|
||||
NODE2_PID=$!
|
||||
```
|
||||
|
||||
Note that we save the PID so we can later kill the processes. You can
|
||||
peak at your logs with `tail gaia1.log`, or follow them for a bit with
|
||||
`tail -f gaia1.log`.
|
||||
|
||||
Nice. We can also lookup the validator set:
|
||||
|
||||
```
|
||||
gaiacli validatorset
|
||||
```
|
||||
|
||||
Then, we try to transfer some `steak` to another account:
|
||||
|
||||
```
|
||||
gaiacli account <FOO-ADDR>
|
||||
gaiacli account <BAR-ADDR>
|
||||
gaiacli send --amount=10steak --to=<BAR-ADDR> --from=foo --chain-id=test-chain
|
||||
```
|
||||
|
||||
**Note:** We need to be careful with the `chain-id` and `sequence`
|
||||
|
||||
Check the balance & sequence with:
|
||||
|
||||
```
|
||||
gaiacli account <BAR-ADDR>
|
||||
```
|
||||
|
||||
To confirm for certain the new validator is active, check tendermint:
|
||||
|
||||
```
|
||||
curl localhost:46657/validators
|
||||
```
|
||||
|
||||
Finally, to relinquish all your power, unbond some coins. You should see
|
||||
your VotingPower reduce and your account balance increase.
|
||||
|
||||
```
|
||||
gaiacli unbond --chain-id=<chain-id> --from=test
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
||||
**Note:** TODO demonstrate edit-candidacy **Note:** TODO demonstrate
|
||||
delegation **Note:** TODO demonstrate unbond of delegation **Note:**
|
||||
TODO demonstrate unbond candidate
|
||||
@@ -0,0 +1,8 @@
|
||||
# CLI
|
||||
|
||||
See `gaiacli --help` for more details.
|
||||
|
||||
Also see the [testnet
|
||||
tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets).
|
||||
|
||||
TODO: cleanup the UX and document this properly.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Keys
|
||||
|
||||
See the [Tendermint specification](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md#public-key-cryptography) for how we work with keys.
|
||||
|
||||
See `gaiacli keys --help`.
|
||||
|
||||
Also see the [testnet
|
||||
tutorial](https://github.com/cosmos/cosmos-sdk/tree/develop/cmd/gaia/testnets).
|
||||
|
||||
TODO: cleanup the UX and document this properly
|
||||
@@ -2,9 +2,9 @@ swagger: '2.0'
|
||||
info:
|
||||
version: '1.1.0'
|
||||
title: Light client daemon to interface with Cosmos baseserver via REST
|
||||
description: Specification for the LCD provided by `gaiacli rest-server`
|
||||
description: Specification for the LCD provided by `gaiacli advanced rest-server`
|
||||
|
||||
|
||||
|
||||
securityDefinitions:
|
||||
kms:
|
||||
type: basic
|
||||
@@ -17,6 +17,13 @@ paths:
|
||||
responses:
|
||||
200:
|
||||
description: Plaintext version i.e. "v0.5.0"
|
||||
/node_version:
|
||||
get:
|
||||
summary: Version of the connected node
|
||||
description: Get the version of the SDK running on the connected node to compare against expected
|
||||
responses:
|
||||
200:
|
||||
description: Plaintext version i.e. "v0.5.0"
|
||||
/node_info:
|
||||
get:
|
||||
description: Only the node info. Block information can be queried via /block/latest
|
||||
@@ -41,7 +48,7 @@ paths:
|
||||
type: string
|
||||
listen_addr:
|
||||
type: string
|
||||
example: 192.168.56.1:46656
|
||||
example: 192.168.56.1:26656
|
||||
version:
|
||||
description: Tendermint version
|
||||
type: string
|
||||
@@ -58,7 +65,7 @@ paths:
|
||||
responses:
|
||||
200:
|
||||
description: '"true" or "false"'
|
||||
|
||||
|
||||
/keys:
|
||||
get:
|
||||
summary: List of accounts stored locally
|
||||
@@ -102,7 +109,7 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
200:
|
||||
description: 12 word Seed
|
||||
description: 16 word Seed
|
||||
schema:
|
||||
type: string
|
||||
/keys/{name}:
|
||||
@@ -199,12 +206,12 @@ paths:
|
||||
# description: Tx was send and will probably be added to the next block
|
||||
# 400:
|
||||
# description: The Tx was malformated
|
||||
|
||||
|
||||
/accounts/{address}:
|
||||
parameters:
|
||||
- in: path
|
||||
name: address
|
||||
description: Account address
|
||||
description: Account address in bech32 format
|
||||
required: true
|
||||
type: string
|
||||
get:
|
||||
@@ -222,7 +229,7 @@ paths:
|
||||
parameters:
|
||||
- in: path
|
||||
name: address
|
||||
description: Account address
|
||||
description: Account address in bech32 format
|
||||
required: true
|
||||
type: string
|
||||
post:
|
||||
@@ -255,18 +262,6 @@ paths:
|
||||
description: Tx was send and will probably be added to the next block
|
||||
400:
|
||||
description: The Tx was malformated
|
||||
/accounts/{address}/nonce:
|
||||
parameters:
|
||||
- in: path
|
||||
name: address
|
||||
description: Account address
|
||||
required: true
|
||||
type: string
|
||||
get:
|
||||
summary: Get the nonce for a certain account
|
||||
responses:
|
||||
200:
|
||||
description: Plaintext nonce i.e. "4" defaults to "0"
|
||||
/blocks/latest:
|
||||
get:
|
||||
summary: Get the latest block
|
||||
@@ -304,9 +299,14 @@ paths:
|
||||
200:
|
||||
description: The validator set at the latest block height
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/Delegate"
|
||||
type: object
|
||||
properties:
|
||||
block_height:
|
||||
type: number
|
||||
validators:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/Validator"
|
||||
/validatorsets/{height}:
|
||||
parameters:
|
||||
- in: path
|
||||
@@ -322,9 +322,14 @@ paths:
|
||||
200:
|
||||
description: The validator set at a specific block height
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/Delegate"
|
||||
type: object
|
||||
properties:
|
||||
block_height:
|
||||
type: number
|
||||
validators:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/Validator"
|
||||
404:
|
||||
description: Block at height not available
|
||||
# /txs:
|
||||
@@ -545,11 +550,24 @@ paths:
|
||||
# description: Tx was send and will probably be added to the next block
|
||||
# 400:
|
||||
# description: The Tx was malformated
|
||||
|
||||
|
||||
definitions:
|
||||
Address:
|
||||
type: string
|
||||
example: DF096FDE8D380FA5B2AD20DB2962C82DDEA1ED9B
|
||||
description: bech32 encoded addres
|
||||
example: cosmosaccaddr:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq
|
||||
ValidatorAddress:
|
||||
type: string
|
||||
description: bech32 encoded addres
|
||||
example: cosmosvaladdr:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq
|
||||
PubKey:
|
||||
type: string
|
||||
description: bech32 encoded public key
|
||||
example: cosmosaccpub:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq
|
||||
ValidatorPubKey:
|
||||
type: string
|
||||
description: bech32 encoded public key
|
||||
example: cosmosvalpub:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq
|
||||
Coins:
|
||||
type: object
|
||||
properties:
|
||||
@@ -630,7 +648,7 @@ definitions:
|
||||
Sig:
|
||||
type: string
|
||||
default: ''
|
||||
Pubkey:
|
||||
Pubkey:
|
||||
type: string
|
||||
default: ''
|
||||
TxSigned:
|
||||
@@ -652,16 +670,6 @@ definitions:
|
||||
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
|
||||
Pubkey:
|
||||
$ref: "#/definitions/PubKey"
|
||||
PubKey:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- ed25519
|
||||
data:
|
||||
type: string
|
||||
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
|
||||
Account:
|
||||
type: object
|
||||
properties:
|
||||
@@ -704,17 +712,17 @@ definitions:
|
||||
properties:
|
||||
header:
|
||||
type: object
|
||||
properties:
|
||||
properties:
|
||||
chain_id:
|
||||
type: string
|
||||
example: gaia-2
|
||||
height:
|
||||
height:
|
||||
type: number
|
||||
example: 1
|
||||
time:
|
||||
time:
|
||||
type: string
|
||||
example: '2017-12-30T05:53:09.287+01:00'
|
||||
num_txs:
|
||||
num_txs:
|
||||
type: number
|
||||
example: 0
|
||||
last_block_id:
|
||||
@@ -753,17 +761,19 @@ definitions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
Delegate:
|
||||
Validator:
|
||||
type: object
|
||||
properties:
|
||||
address:
|
||||
$ref: '#/definitions/ValidatorAddress'
|
||||
pub_key:
|
||||
$ref: "#/definitions/PubKey"
|
||||
$ref: "#/definitions/ValidatorPubKey"
|
||||
power:
|
||||
type: number
|
||||
example: 1000
|
||||
name:
|
||||
type: string
|
||||
example: "159.89.3.34"
|
||||
accum:
|
||||
type: number
|
||||
example: 1000
|
||||
# Added by API Auto Mocking Plugin
|
||||
host: virtserver.swaggerhub.com
|
||||
basePath: /faboweb1/Cosmos-LCD-2/1.0.0
|
||||
@@ -0,0 +1,29 @@
|
||||
# Ledger // Cosmos
|
||||
|
||||
### Ledger Support for account keys
|
||||
|
||||
`gaiacli` now supports derivation of account keys from a Ledger seed. To use this functionality you will need the following:
|
||||
|
||||
- A running `gaiad` instance connected to the network you wish to use.
|
||||
- A `gaiacli` instance configured to connect to your chosen `gaiad` instance.
|
||||
- A LedgerNano with the `ledger-cosmos` app installed
|
||||
* Install the Cosmos app onto your Ledger by following the instructions in the [`ledger-cosmos`](https://github.com/cosmos/ledger-cosmos/blob/master/docs/BUILD.md) repository.
|
||||
* A production-ready version of this app will soon be included in the [Ledger Apps Store](https://www.ledgerwallet.com/apps)
|
||||
|
||||
> **NOTE:** Cosmos keys are derived acording to the [BIP 44 Hierarchical Deterministic wallet spec](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). For more information on Cosmos derivation paths [see the hd package](https://github.com/cosmos/cosmos-sdk/blob/develop/crypto/keys/hd/hdpath.go#L30).
|
||||
|
||||
Once you have the Cosmos app installed on your Ledger, and the Ledger is accessible from the machine you are using `gaiacli` from you can create a new account key using the Ledger:
|
||||
|
||||
```bash
|
||||
$ gaiacli keys add {{ .Key.Name }} --ledger
|
||||
NAME: TYPE: ADDRESS: PUBKEY:
|
||||
{{ .Key.Name }} ledger cosmosaccaddr1aw64xxr80lwqqdk8u2xhlrkxqaxamkr3e2g943 cosmosaccpub1addwnpepqvhs678gh9aqrjc2tg2vezw86csnvgzqq530ujkunt5tkuc7lhjkz5mj629
|
||||
```
|
||||
|
||||
This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following:
|
||||
|
||||
```bash
|
||||
$ gaiacli send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000
|
||||
```
|
||||
|
||||
You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake!
|
||||
@@ -0,0 +1,10 @@
|
||||
# Running a Node
|
||||
|
||||
TODO: document `gaiad`
|
||||
|
||||
Options for running the `gaiad` binary are effectively the same as for `tendermint`.
|
||||
See `gaiad --help` and the
|
||||
[guide to using Tendermint](https://github.com/tendermint/tendermint/blob/master/docs/using-tendermint.md)
|
||||
for more details.
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# REST
|
||||
|
||||
See `gaiacli advanced rest-server --help` for more.
|
||||
|
||||
Also see the
|
||||
[work in progress API specification](https://github.com/cosmos/cosmos-sdk/pull/1314)
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cosmos-SDK documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Sep 1 21:37:02 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
import sphinx_rtd_theme
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Cosmos-SDK'
|
||||
copyright = u'2018, The Authors'
|
||||
author = u'The Authors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u''
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u''
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'old']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# This is required for the alabaster theme
|
||||
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
|
||||
html_sidebars = {
|
||||
'**': [
|
||||
'about.html',
|
||||
'navigation.html',
|
||||
'relations.html', # needs 'show_related': True theme option to display
|
||||
'searchbox.html',
|
||||
'donate.html',
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Cosmos-SDKdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'Cosmos-SDK.tex', u'Cosmos-SDK Documentation',
|
||||
u'The Authors', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'cosmos-sdk', u'Cosmos-SDK Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Cosmos-SDK', u'Cosmos-SDK Documentation',
|
||||
author, 'Cosmos-SDK', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
@@ -0,0 +1,495 @@
|
||||
# The Basics
|
||||
|
||||
Here we introduce the basic components of an SDK by building `App1`, a simple bank.
|
||||
Users have an account address and an account, and they can send coins around.
|
||||
It has no authentication, and just uses JSON for serialization.
|
||||
|
||||
The complete code can be found in [app1.go](examples/app1.go).
|
||||
|
||||
## Messages
|
||||
|
||||
Messages are the primary inputs to the application state machine.
|
||||
They define the content of transactions and can contain arbitrary information.
|
||||
Developers can create messages by implementing the `Msg` interface:
|
||||
|
||||
```go
|
||||
type Msg interface {
|
||||
// Return the message type.
|
||||
// Must be alphanumeric or empty.
|
||||
// Must correspond to name of message handler (XXX).
|
||||
Type() string
|
||||
|
||||
// ValidateBasic does a simple validation check that
|
||||
// doesn't require access to any other information.
|
||||
ValidateBasic() error
|
||||
|
||||
// Get the canonical byte representation of the Msg.
|
||||
// This is what is signed.
|
||||
GetSignBytes() []byte
|
||||
|
||||
// Signers returns the addrs of signers that must sign.
|
||||
// CONTRACT: All signatures must be present to be valid.
|
||||
// CONTRACT: Returns addrs in some deterministic order.
|
||||
GetSigners() []AccAddress
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
The `Msg` interface allows messages to define basic validity checks, as well as
|
||||
what needs to be signed and who needs to sign it.
|
||||
|
||||
For instance, take the simple token sending message type from app1.go:
|
||||
|
||||
```go
|
||||
// MsgSend to send coins from Input to Output
|
||||
type MsgSend struct {
|
||||
From sdk.AccAddress `json:"from"`
|
||||
To sdk.AccAddress `json:"to"`
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSend) Type() string { return "bank" }
|
||||
```
|
||||
|
||||
It specifies that the message should be JSON marshaled and signed by the sender:
|
||||
|
||||
```go
|
||||
// Implements Msg. JSON encode the message.
|
||||
func (msg MsgSend) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
|
||||
// Implements Msg. Return the signer.
|
||||
func (msg MsgSend) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.From}
|
||||
}
|
||||
```
|
||||
|
||||
Note Addresses in the SDK are arbitrary byte arrays that are
|
||||
[Bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) encoded
|
||||
when displayed as a string or rendered in JSON. Typically, addresses are the hash of
|
||||
a public key, so we can use them to uniquely identify the required signers for a
|
||||
transaction.
|
||||
|
||||
|
||||
The basic validity check ensures the From and To address are specified and the
|
||||
Amount is positive:
|
||||
|
||||
```go
|
||||
// Implements Msg. Ensure the addresses are good and the
|
||||
// amount is positive.
|
||||
func (msg MsgSend) ValidateBasic() sdk.Error {
|
||||
if len(msg.From) == 0 {
|
||||
return sdk.ErrInvalidAddress("From address is empty")
|
||||
}
|
||||
if len(msg.To) == 0 {
|
||||
return sdk.ErrInvalidAddress("To address is empty")
|
||||
}
|
||||
if !msg.Amount.IsPositive() {
|
||||
return sdk.ErrInvalidCoins("Amount is not positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Note the `ValidateBasic` method is called automatically by the SDK!
|
||||
|
||||
## KVStore
|
||||
|
||||
The basic persistence layer for an SDK application is the KVStore:
|
||||
|
||||
```go
|
||||
type KVStore interface {
|
||||
Store
|
||||
|
||||
// Get returns nil iff key doesn't exist. Panics on nil key.
|
||||
Get(key []byte) []byte
|
||||
|
||||
// Has checks if a key exists. Panics on nil key.
|
||||
Has(key []byte) bool
|
||||
|
||||
// Set sets the key. Panics on nil key.
|
||||
Set(key, value []byte)
|
||||
|
||||
// Delete deletes the key. Panics on nil key.
|
||||
Delete(key []byte)
|
||||
|
||||
// Iterator over a domain of keys in ascending order. End is exclusive.
|
||||
// Start must be less than end, or the Iterator is invalid.
|
||||
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
|
||||
Iterator(start, end []byte) Iterator
|
||||
|
||||
// Iterator over a domain of keys in descending order. End is exclusive.
|
||||
// Start must be greater than end, or the Iterator is invalid.
|
||||
// CONTRACT: No writes may happen within a domain while an iterator exists over it.
|
||||
ReverseIterator(start, end []byte) Iterator
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Note it is unforgiving - it panics on nil keys!
|
||||
|
||||
The primary implementation of the KVStore is currently the IAVL store. In the future, we plan to support other Merkle KVStores,
|
||||
like Ethereum's radix trie.
|
||||
|
||||
As we'll soon see, apps have many distinct KVStores, each with a different name and for a different concern.
|
||||
Access to a store is mediated by *object-capability keys*, which must be granted to a handler during application startup.
|
||||
|
||||
## Handlers
|
||||
|
||||
Now that we have a message type and a store interface, we can define our state transition function using a handler:
|
||||
|
||||
```go
|
||||
// Handler defines the core of the state transition function of an application.
|
||||
type Handler func(ctx Context, msg Msg) Result
|
||||
```
|
||||
|
||||
Along with the message, the Handler takes environmental information (a `Context`), and returns a `Result`.
|
||||
All information necessary for processing a message should be available in the context.
|
||||
|
||||
Where is the KVStore in all of this? Access to the KVStore in a message handler is restricted by the Context via object-capability keys.
|
||||
Only handlers which were given explict access to a store's key will be able to access that store during message processsing.
|
||||
|
||||
### Context
|
||||
|
||||
The SDK uses a `Context` to propogate common information across functions.
|
||||
Most importantly, the `Context` restricts access to KVStores based on object-capability keys.
|
||||
Only handlers which have been given explicit access to a key will be able to access the corresponding store.
|
||||
|
||||
For instance, the FooHandler can only load the store it's given the key for:
|
||||
|
||||
```go
|
||||
// newFooHandler returns a Handler that can access a single store.
|
||||
func newFooHandler(key sdk.StoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
store := ctx.KVStore(key)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Context` is modeled after the Golang
|
||||
[context.Context](https://golang.org/pkg/context/), which has
|
||||
become ubiquitous in networking middleware and routing applications as a means
|
||||
to easily propogate request context through handler functions.
|
||||
Many methods on SDK objects receive a context as the first argument.
|
||||
|
||||
The Context also contains the
|
||||
[block header](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md#header),
|
||||
which includes the latest timestamp from the blockchain and other information about the latest block.
|
||||
|
||||
See the [Context API
|
||||
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Context) for more details.
|
||||
|
||||
### Result
|
||||
|
||||
Handler takes a Context and Msg and returns a Result.
|
||||
Result is motivated by the corresponding [ABCI result](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto#L165).
|
||||
It contains return values, error information, logs, and meta data about the transaction:
|
||||
|
||||
```go
|
||||
// Result is the union of ResponseDeliverTx and ResponseCheckTx.
|
||||
type Result struct {
|
||||
|
||||
// Code is the response code, is stored back on the chain.
|
||||
Code ABCICodeType
|
||||
|
||||
// Data is any data returned from the app.
|
||||
Data []byte
|
||||
|
||||
// Log is just debug information. NOTE: nondeterministic.
|
||||
Log string
|
||||
|
||||
// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
GasWanted int64
|
||||
|
||||
// GasUsed is the amount of gas actually consumed. NOTE: unimplemented
|
||||
GasUsed int64
|
||||
|
||||
// Tx fee amount and denom.
|
||||
FeeAmount int64
|
||||
FeeDenom string
|
||||
|
||||
// Tags are used for transaction indexing and pubsub.
|
||||
Tags Tags
|
||||
}
|
||||
```
|
||||
|
||||
We'll talk more about these fields later in the tutorial. For now, note that a
|
||||
`0` value for the `Code` is considered a success, and everything else is a
|
||||
failure. The `Tags` can contain meta data about the transaction that will allow
|
||||
us to easily lookup transactions that pertain to particular accounts or actions.
|
||||
|
||||
### Handler
|
||||
|
||||
Let's define our handler for App1:
|
||||
|
||||
```go
|
||||
// Handle MsgSend.
|
||||
// NOTE: msg.From, msg.To, and msg.Amount were already validated
|
||||
// in ValidateBasic().
|
||||
func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result {
|
||||
// Load the store.
|
||||
store := ctx.KVStore(key)
|
||||
|
||||
// Debit from the sender.
|
||||
if res := handleFrom(store, msg.From, msg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Credit the receiver.
|
||||
if res := handleTo(store, msg.To, msg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Return a success (Code 0).
|
||||
// Add list of key-value pair descriptors ("tags").
|
||||
return sdk.Result{
|
||||
Tags: msg.Tags(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We have only a single message type, so just one message-specific function to define, `handleMsgSend`.
|
||||
|
||||
Note this handler has unrestricted access to the store specified by the capability key `keyAcc`,
|
||||
so it must define what to store and how to encode it. Later, we'll introduce
|
||||
higher-level abstractions so Handlers are restricted in what they can do.
|
||||
For this first example, we use a simple account that is JSON encoded:
|
||||
|
||||
```go
|
||||
type appAccount struct {
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
```
|
||||
|
||||
Coins is a useful type provided by the SDK for multi-asset accounts.
|
||||
We could just use an integer here for a single coin type, but
|
||||
it's worth [getting to know
|
||||
Coins](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Coins).
|
||||
|
||||
|
||||
Now we're ready to handle the two parts of the MsgSend:
|
||||
|
||||
```go
|
||||
func handleFrom(store sdk.KVStore, from sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Get sender account from the store.
|
||||
accBytes := store.Get(from)
|
||||
if accBytes == nil {
|
||||
// Account was not added to store. Return the result of the error.
|
||||
return sdk.NewError(2, 101, "Account not added to store").Result()
|
||||
}
|
||||
|
||||
// Unmarshal the JSON account bytes.
|
||||
var acc appAccount
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
// InternalError
|
||||
return sdk.ErrInternal("Error when deserializing account").Result()
|
||||
}
|
||||
|
||||
// Deduct msg amount from sender account.
|
||||
senderCoins := acc.Coins.Minus(amt)
|
||||
|
||||
// If any coin has negative amount, return insufficient coins error.
|
||||
if !senderCoins.IsNotNegative() {
|
||||
return sdk.ErrInsufficientCoins("Insufficient coins in account").Result()
|
||||
}
|
||||
|
||||
// Set acc coins to new amount.
|
||||
acc.Coins = senderCoins
|
||||
|
||||
// Encode sender account.
|
||||
accBytes, err = json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated sender account
|
||||
store.Set(from, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
func handleTo(store sdk.KVStore, to sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Add msg amount to receiver account
|
||||
accBytes := store.Get(to)
|
||||
var acc appAccount
|
||||
if accBytes == nil {
|
||||
// Receiver account does not already exist, create a new one.
|
||||
acc = appAccount{}
|
||||
} else {
|
||||
// Receiver account already exists. Retrieve and decode it.
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account decoding error").Result()
|
||||
}
|
||||
}
|
||||
|
||||
// Add amount to receiver's old coins
|
||||
receiverCoins := acc.Coins.Plus(amt)
|
||||
|
||||
// Update receiver account
|
||||
acc.Coins = receiverCoins
|
||||
|
||||
// Encode receiver account
|
||||
accBytes, err := json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated receiver account
|
||||
store.Set(to, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
```
|
||||
|
||||
The handler is straight forward. We first load the KVStore from the context using the granted capability key.
|
||||
Then we make two state transitions: one for the sender, one for the receiver.
|
||||
Each one involves JSON unmarshalling the account bytes from the store, mutating
|
||||
the `Coins`, and JSON marshalling back into the store.
|
||||
|
||||
And that's that!
|
||||
|
||||
## Tx
|
||||
|
||||
The final piece before putting it all together is the `Tx`.
|
||||
While `Msg` contains the content for particular functionality in the application, the actual input
|
||||
provided by the user is a serialized `Tx`. Applications may have many implementations of the `Msg` interface,
|
||||
but they should have only a single implementation of `Tx`:
|
||||
|
||||
|
||||
```go
|
||||
// Transactions wrap messages.
|
||||
type Tx interface {
|
||||
// Gets the Msgs.
|
||||
GetMsgs() []Msg
|
||||
}
|
||||
```
|
||||
|
||||
The `Tx` just wraps a `[]Msg`, and may include additional authentication data, like signatures and account nonces.
|
||||
Applications must specify how their `Tx` is decoded, as this is the ultimate input into the application.
|
||||
We'll talk more about `Tx` types later, specifically when we introduce the `StdTx`.
|
||||
|
||||
In this first application, we won't have any authentication at all. This might
|
||||
make sense in a private network where access is controlled by alternative means,
|
||||
like client-side TLS certificates, but in general, we'll want to bake the authentication
|
||||
right into our state machine. We'll use `Tx` to do that
|
||||
in the next app. For now, the `Tx` just embeds `MsgSend` and uses JSON:
|
||||
|
||||
|
||||
```go
|
||||
// Simple tx to wrap the Msg.
|
||||
type app1Tx struct {
|
||||
MsgSend
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app1Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.MsgSend}
|
||||
}
|
||||
|
||||
// JSON decode MsgSend.
|
||||
func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||
var tx app1Tx
|
||||
err := json.Unmarshal(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrTxDecode(err.Error())
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
```
|
||||
|
||||
## BaseApp
|
||||
|
||||
Finally, we stitch it all together using the `BaseApp`.
|
||||
|
||||
The BaseApp is an abstraction over the [Tendermint
|
||||
ABCI](https://github.com/tendermint/tendermint/tree/master/abci) that
|
||||
simplifies application development by handling common low-level concerns.
|
||||
It serves as the mediator between the two key components of an SDK app: the store
|
||||
and the message handlers. The BaseApp implements the
|
||||
[`abci.Application`](https://godoc.org/github.com/tendermint/tendermint/abci/types#Application) interface.
|
||||
See the [BaseApp API
|
||||
documentation](https://godoc.org/github.com/cosmos/cosmos-sdk/baseapp) for more details.
|
||||
|
||||
Here is the complete setup for App1:
|
||||
|
||||
```go
|
||||
func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
cdc := wire.NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app1Name, cdc, logger, db)
|
||||
|
||||
// Create a capability key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
|
||||
// Determine how transactions are decoded.
|
||||
app.SetTxDecoder(txDecoder)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler receives the keyAccount and thus
|
||||
// gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("bank", NewApp1Handler(keyAccount))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
```
|
||||
|
||||
Every app will have such a function that defines the setup of the app.
|
||||
It will typically be contained in an `app.go` file.
|
||||
We'll talk about how to connect this app object with the CLI, a REST API,
|
||||
the logger, and the filesystem later in the tutorial. For now, note that this is where we
|
||||
register handlers for messages and grant them access to stores.
|
||||
|
||||
Here, we have only a single Msg type, `bank`, a single store for accounts, and a single handler.
|
||||
The handler is granted access to the store by giving it the capability key.
|
||||
In future apps, we'll have multiple stores and handlers, and not every handler will get access to every store.
|
||||
|
||||
After setting the transaction decoder and the message handling routes, the final
|
||||
step is to mount the stores and load the latest version.
|
||||
Since we only have one store, we only mount one.
|
||||
|
||||
## Execution
|
||||
|
||||
We're now done the core logic of the app! From here, we can write tests in Go
|
||||
that initialize the store with accounts and execute transactions by calling
|
||||
the `app.DeliverTx` method.
|
||||
|
||||
In a real setup, the app would run as an ABCI application on top of the
|
||||
Tendermint consensus engine. It would be initialized by a Genesis file, and it
|
||||
would be driven by blocks of transactions committed by the underlying Tendermint
|
||||
consensus. We'll talk more about ABCI and how this all works a bit later, but
|
||||
feel free to check the
|
||||
[specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md).
|
||||
We'll also see how to connect our app to a complete suite of components
|
||||
for running and using a live blockchain application.
|
||||
|
||||
For now, we note the follow sequence of events occurs when a transaction is
|
||||
received (through `app.DeliverTx`):
|
||||
|
||||
- serialized transaction is received by `app.DeliverTx`
|
||||
- transaction is deserialized using `TxDecoder`
|
||||
- for each message in the transaction, run `msg.ValidateBasic()`
|
||||
- for each message in the transaction, load the appropriate handler and execute
|
||||
it with the message
|
||||
|
||||
## Conclusion
|
||||
|
||||
We now have a complete implementation of a simple app!
|
||||
|
||||
In the next section, we'll add another Msg type and another store. Once we have multiple message types
|
||||
we'll need a better way of decoding transactions, since we'll need to decode
|
||||
into the `Msg` interface. This is where we introduce Amino, a superior encoding scheme that lets us decode into interface types!
|
||||
@@ -0,0 +1,301 @@
|
||||
# Transactions
|
||||
|
||||
In the previous app we built a simple `bank` with one message type for sending
|
||||
coins and one store for storing accounts.
|
||||
Here we build `App2`, which expands on `App1` by introducing
|
||||
|
||||
- a new message type for issuing new coins
|
||||
- a new store for coin metadata (like who can issue coins)
|
||||
- a requirement that transactions include valid signatures
|
||||
|
||||
Along the way, we'll be introduced to Amino for encoding and decoding
|
||||
transactions and to the AnteHandler for processing them.
|
||||
|
||||
The complete code can be found in [app2.go](examples/app2.go).
|
||||
|
||||
|
||||
## Message
|
||||
|
||||
Let's introduce a new message type for issuing coins:
|
||||
|
||||
```go
|
||||
// MsgIssue to allow a registered issuer
|
||||
// to issue new coins.
|
||||
type MsgIssue struct {
|
||||
Issuer sdk.AccAddress
|
||||
Receiver sdk.AccAddress
|
||||
Coin sdk.Coin
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgIssue) Type() string { return "issue" }
|
||||
```
|
||||
|
||||
Note the `Type()` method returns `"issue"`, so this message is of a different
|
||||
type and will be executed by a different handler than `MsgSend`. The other
|
||||
methods for `MsgIssue` are similar to `MsgSend`.
|
||||
|
||||
## Handler
|
||||
|
||||
We'll need a new handler to support the new message type. It just checks if the
|
||||
sender of the `MsgIssue` is the correct issuer for the given coin type, as per the information
|
||||
in the issuer store:
|
||||
|
||||
```go
|
||||
// Handle MsgIssue
|
||||
func handleMsgIssue(keyIssue *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
issueMsg, ok := msg.(MsgIssue)
|
||||
if !ok {
|
||||
return sdk.NewError(2, 1, "MsgIssue is malformed").Result()
|
||||
}
|
||||
|
||||
// Retrieve stores
|
||||
issueStore := ctx.KVStore(keyIssue)
|
||||
accStore := ctx.KVStore(keyAcc)
|
||||
|
||||
// Handle updating coin info
|
||||
if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Issue coins to receiver using previously defined handleTo function
|
||||
if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
// Return result with Issue msg tags
|
||||
Tags: issueMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result {
|
||||
// the issuer address is stored directly under the coin denomination
|
||||
denom := []byte(coin.Denom)
|
||||
infoBytes := store.Get(denom)
|
||||
if infoBytes == nil {
|
||||
return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
var coinInfo coinInfo
|
||||
err := json.Unmarshal(infoBytes, &coinInfo)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Error when deserializing coinInfo").Result()
|
||||
}
|
||||
|
||||
// Msg Issuer is not authorized to issue these coins
|
||||
if !bytes.Equal(coinInfo.Issuer, issuer) {
|
||||
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// coinInfo stores meta data about a coin
|
||||
type coinInfo struct {
|
||||
Issuer sdk.AccAddress `json:"issuer"`
|
||||
}
|
||||
```
|
||||
|
||||
Note we've introduced the `coinInfo` type to store the issuer address for each coin.
|
||||
We JSON serialize this type and store it directly under the denomination in the
|
||||
issuer store. We could of course add more fields and logic around this,
|
||||
like including the current supply of coins in existence, and enforcing a maximum supply,
|
||||
but that's left as an excercise for the reader :).
|
||||
|
||||
## Amino
|
||||
|
||||
Now that we have two implementations of `Msg`, we won't know before hand
|
||||
which type is contained in a serialized `Tx`. Ideally, we would use the
|
||||
`Msg` interface inside our `Tx` implementation, but the JSON decoder can't
|
||||
decode into interface types. In fact, there's no standard way to unmarshal
|
||||
into interfaces in Go. This is one of the primary reasons we built
|
||||
[Amino](https://github.com/tendermint/go-amino) :).
|
||||
|
||||
While SDK developers can encode transactions and state objects however they
|
||||
like, Amino is the recommended format. The goal of Amino is to improve over the latest version of Protocol Buffers,
|
||||
`proto3`. To that end, Amino is compatible with the subset of `proto3` that
|
||||
excludes the `oneof` keyword.
|
||||
|
||||
While `oneof` provides union types, Amino aims to provide interfaces.
|
||||
The main difference being that with union types, you have to know all the types
|
||||
up front. But anyone can implement an interface type whenever and however
|
||||
they like.
|
||||
|
||||
To implement interface types, Amino allows any concrete implementation of an
|
||||
interface to register a globally unique name that is carried along whenever the
|
||||
type is serialized. This allows Amino to seamlessly deserialize into interface
|
||||
types!
|
||||
|
||||
The primary use for Amino in the SDK is for messages that implement the
|
||||
`Msg` interface. By registering each message with a distinct name, they are each
|
||||
given a distinct Amino prefix, allowing them to be easily distinguished in
|
||||
transactions.
|
||||
|
||||
Amino can also be used for persistent storage of interfaces.
|
||||
|
||||
To use Amino, simply create a codec, and then register types:
|
||||
|
||||
```
|
||||
func NewCodec() *wire.Codec {
|
||||
cdc := wire.NewCodec()
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil)
|
||||
cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil)
|
||||
return cdc
|
||||
}
|
||||
```
|
||||
|
||||
Amino supports encoding and decoding in both a binary and JSON format.
|
||||
See the [codec API docs](https://godoc.org/github.com/tendermint/go-amino#Codec) for more details.
|
||||
|
||||
## Tx
|
||||
|
||||
Now that we're using Amino, we can embed the `Msg` interface directly in our
|
||||
`Tx`. We can also add a public key and a signature for authentication.
|
||||
|
||||
```go
|
||||
// Simple tx to wrap the Msg.
|
||||
type app2Tx struct {
|
||||
sdk.Msg
|
||||
|
||||
PubKey crypto.PubKey
|
||||
Signature crypto.Signature
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app2Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.Msg}
|
||||
}
|
||||
```
|
||||
|
||||
We don't need a custom TxDecoder function anymore, since we're just using the
|
||||
Amino codec!
|
||||
|
||||
## AnteHandler
|
||||
|
||||
Now that we have an implementation of `Tx` that includes more than just the Msg,
|
||||
we need to specify how that extra information is validated and processed. This
|
||||
is the role of the `AnteHandler`. The word `ante` here denotes "before", as the
|
||||
`AnteHandler` is run before a `Handler`. While an app can have many Handlers,
|
||||
one for each set of messages, it can have only a single `AnteHandler` that
|
||||
corresponds to its single implementation of `Tx`.
|
||||
|
||||
|
||||
The AnteHandler resembles a Handler:
|
||||
|
||||
|
||||
```go
|
||||
type AnteHandler func(ctx Context, tx Tx) (newCtx Context, result Result, abort bool)
|
||||
```
|
||||
|
||||
Like Handler, AnteHandler takes a Context that restricts its access to stores
|
||||
according to whatever capability keys it was granted. Instead of a `Msg`,
|
||||
however, it takes a `Tx`.
|
||||
|
||||
Like Handler, AnteHandler returns a `Result` type, but it also returns a new
|
||||
`Context` and an `abort bool`.
|
||||
|
||||
For `App2`, we simply check if the PubKey matches the Address, and the Signature validates with the PubKey:
|
||||
|
||||
```go
|
||||
// Simple anteHandler that ensures msg signers have signed.
|
||||
// Provides no replay protection.
|
||||
func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) {
|
||||
appTx, ok := tx.(app2Tx)
|
||||
if !ok {
|
||||
// set abort boolean to true so that we don't continue to process failed tx
|
||||
return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true
|
||||
}
|
||||
|
||||
// expect only one msg in app2Tx
|
||||
msg := tx.GetMsgs()[0]
|
||||
|
||||
signerAddrs := msg.GetSigners()
|
||||
|
||||
if len(signerAddrs) != len(appTx.GetSignatures()) {
|
||||
return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true
|
||||
}
|
||||
|
||||
signBytes := msg.GetSignBytes()
|
||||
for i, addr := range signerAddrs {
|
||||
sig := appTx.GetSignatures()[i]
|
||||
|
||||
// check that submitted pubkey belongs to required address
|
||||
if !bytes.Equal(sig.PubKey.Address(), addr) {
|
||||
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
|
||||
}
|
||||
|
||||
// check that signature is over expected signBytes
|
||||
if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) {
|
||||
return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true
|
||||
}
|
||||
}
|
||||
|
||||
// authentication passed, app to continue processing by sending msg to handler
|
||||
return ctx, sdk.Result{}, false
|
||||
}
|
||||
```
|
||||
|
||||
## App2
|
||||
|
||||
Let's put it all together now to get App2:
|
||||
|
||||
```go
|
||||
func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app2Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
// Create a key for accessing the issue store.
|
||||
keyIssue := sdk.NewKVStoreKey("issue")
|
||||
|
||||
// set antehandler function
|
||||
app.SetAnteHandler(antehandler)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", handleMsgSend(keyAccount)).
|
||||
AddRoute("issue", handleMsgIssue(keyAccount, keyIssue))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyIssue)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
```
|
||||
|
||||
The main difference here, compared to `App1`, is that we use a second capability
|
||||
key for a second store that is *only* passed to a second handler, the
|
||||
`handleMsgIssue`. The first `handleMsgSend` has no access to this second store and cannot read or write to
|
||||
it, ensuring a strong separation of concerns.
|
||||
|
||||
Note also that we do not need to use `SetTxDecoder` here - now that we're using
|
||||
Amino, we simply create a codec, register our types on the codec, and pass the
|
||||
codec into `NewBaseApp`. The SDK takes care of the rest for us!
|
||||
|
||||
## Conclusion
|
||||
|
||||
We've expanded on our first app by adding a new message type for issuing coins,
|
||||
and by checking signatures. We learned how to use Amino for decoding into
|
||||
interface types, allowing us to support multiple Msg types, and we learned how
|
||||
to use the AnteHandler to validate transactions.
|
||||
|
||||
Unfortunately, our application is still insecure, because any valid transaction
|
||||
can be replayed multiple times to drain someones account! Besides, validating
|
||||
signatures and preventing replays aren't things developers should have to think
|
||||
about.
|
||||
|
||||
In the next section, we introduce the built-in SDK modules `auth` and `bank`,
|
||||
which respectively provide secure implementations for all our transaction authentication
|
||||
and coin transfering needs.
|
||||
@@ -0,0 +1,370 @@
|
||||
# Modules
|
||||
|
||||
In the previous app, we introduced a new `Msg` type and used Amino to encode
|
||||
transactions. We also introduced additional data to the `Tx`, and used a simple
|
||||
`AnteHandler` to validate it.
|
||||
|
||||
Here, in `App3`, we introduce two built-in SDK modules to
|
||||
replace the `Msg`, `Tx`, `Handler`, and `AnteHandler` implementations we've seen
|
||||
so far: `x/auth` and `x/bank`.
|
||||
|
||||
The `x/auth` module implements `Tx` and `AnteHandler` - it has everything we need to
|
||||
authenticate transactions. It also includes a new `Account` type that simplifies
|
||||
working with accounts in the store.
|
||||
|
||||
The `x/bank` module implements `Msg` and `Handler` - it has everything we need
|
||||
to transfer coins between accounts.
|
||||
|
||||
Here, we'll introduce the important types from `x/auth` and `x/bank`, and use
|
||||
them to build `App3`, our shortest app yet. The complete code can be found in
|
||||
[app3.go](examples/app3.go), and at the end of this section.
|
||||
|
||||
For more details, see the
|
||||
[x/auth](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth) and
|
||||
[x/bank](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) API documentation.
|
||||
|
||||
## Accounts
|
||||
|
||||
The `x/auth` module defines a model of accounts much like Ethereum.
|
||||
In this model, an account contains:
|
||||
|
||||
- Address for identification
|
||||
- PubKey for authentication
|
||||
- AccountNumber to prune empty accounts
|
||||
- Sequence to prevent transaction replays
|
||||
- Coins to carry a balance
|
||||
|
||||
Note that the `AccountNumber` is a unique number that is assigned when the account is
|
||||
created, and the `Sequence` is incremented by one every time a transaction is
|
||||
sent from the account.
|
||||
|
||||
### Account
|
||||
|
||||
The `Account` interface captures this account model with getters and setters:
|
||||
|
||||
```go
|
||||
// Account is a standard account using a sequence number for replay protection
|
||||
// and a pubkey for authentication.
|
||||
type Account interface {
|
||||
GetAddress() sdk.AccAddress
|
||||
SetAddress(sdk.AccAddress) error // errors if already set.
|
||||
|
||||
GetPubKey() crypto.PubKey // can return nil.
|
||||
SetPubKey(crypto.PubKey) error
|
||||
|
||||
GetAccountNumber() int64
|
||||
SetAccountNumber(int64) error
|
||||
|
||||
GetSequence() int64
|
||||
SetSequence(int64) error
|
||||
|
||||
GetCoins() sdk.Coins
|
||||
SetCoins(sdk.Coins) error
|
||||
}
|
||||
```
|
||||
|
||||
Note this is a low-level interface - it allows any of the fields to be over
|
||||
written. As we'll soon see, access can be restricted using the `Keeper`
|
||||
paradigm.
|
||||
|
||||
### BaseAccount
|
||||
|
||||
The default implementation of `Account` is the `BaseAccount`:
|
||||
|
||||
```go
|
||||
// BaseAccount - base account structure.
|
||||
// Extend this by embedding this in your AppAccount.
|
||||
// See the examples/basecoin/types/account.go for an example.
|
||||
type BaseAccount struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
PubKey crypto.PubKey `json:"public_key"`
|
||||
AccountNumber int64 `json:"account_number"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
}
|
||||
```
|
||||
|
||||
It simply contains a field for each of the methods.
|
||||
|
||||
### AccountMapper
|
||||
|
||||
In previous apps using our `appAccount`, we handled
|
||||
marshaling/unmarshaling the account from the store ourselves, by performing
|
||||
operations directly on the KVStore. But unrestricted access to a KVStore isn't really the interface we want
|
||||
to work with in our applications. In the SDK, we use the term `Mapper` to refer
|
||||
to an abstaction over a KVStore that handles marshalling and unmarshalling a
|
||||
particular data type to and from the underlying store.
|
||||
|
||||
The `x/auth` module provides an `AccountMapper` that allows us to get and
|
||||
set `Account` types to the store. Note the benefit of using the `Account`
|
||||
interface here - developers can implement their own account type that extends
|
||||
the `BaseAccount` to store additional data without requiring another lookup from
|
||||
the store.
|
||||
|
||||
Creating an AccountMapper is easy - we just need to specify a codec, a
|
||||
capability key, and a prototype of the object being encoded
|
||||
|
||||
```go
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
```
|
||||
|
||||
Then we can get, modify, and set accounts. For instance, we could double the
|
||||
amount of coins in an account:
|
||||
|
||||
```go
|
||||
acc := accountMapper.GetAccount(ctx, addr)
|
||||
acc.SetCoins(acc.Coins.Plus(acc.Coins))
|
||||
accountMapper.SetAccount(ctx, addr)
|
||||
```
|
||||
|
||||
Note that the `AccountMapper` takes a `Context` as the first argument, and will
|
||||
load the KVStore from there using the capability key it was granted on creation.
|
||||
|
||||
Also note that you must explicitly call `SetAccount` after mutating an account
|
||||
for the change to persist!
|
||||
|
||||
See the [AccountMapper API
|
||||
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#AccountMapper) for more information.
|
||||
|
||||
## StdTx
|
||||
|
||||
Now that we have a native model for accounts, it's time to introduce the native
|
||||
`Tx` type, the `auth.StdTx`:
|
||||
|
||||
```go
|
||||
// StdTx is a standard way to wrap a Msg with Fee and Signatures.
|
||||
// NOTE: the first signature is the FeePayer (Signatures must not be nil).
|
||||
type StdTx struct {
|
||||
Msgs []sdk.Msg `json:"msg"`
|
||||
Fee StdFee `json:"fee"`
|
||||
Signatures []StdSignature `json:"signatures"`
|
||||
Memo string `json:"memo"`
|
||||
}
|
||||
```
|
||||
|
||||
This is the standard form for a transaction in the SDK. Besides the Msgs, it
|
||||
includes:
|
||||
|
||||
- a fee to be paid by the first signer
|
||||
- replay protecting nonces in the signature
|
||||
- a memo of prunable additional data
|
||||
|
||||
Details on how these components are validated is provided under
|
||||
[auth.AnteHandler](#antehandler) below.
|
||||
|
||||
The standard form for signatures is `StdSignature`:
|
||||
|
||||
```go
|
||||
// StdSignature wraps the Signature and includes counters for replay protection.
|
||||
// It also includes an optional public key, which must be provided at least in
|
||||
// the first transaction made by the account.
|
||||
type StdSignature struct {
|
||||
crypto.PubKey `json:"pub_key"` // optional
|
||||
crypto.Signature `json:"signature"`
|
||||
AccountNumber int64 `json:"account_number"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
}
|
||||
```
|
||||
|
||||
The signature includes both an `AccountNumber` and a `Sequence`.
|
||||
The `Sequence` must match the one in the
|
||||
corresponding account when the transaction is processed, and will increment by
|
||||
one with every transaction. This prevents the same
|
||||
transaction from being replayed multiple times, resolving the insecurity that
|
||||
remains in App2.
|
||||
|
||||
The `AccountNumber` is also for replay protection - it allows accounts to be
|
||||
deleted from the store when they run out of accounts. If an account receives
|
||||
coins after it is deleted, the account will be re-created, with the Sequence
|
||||
reset to 0, but a new AccountNumber. If it weren't for the AccountNumber, the
|
||||
last sequence of transactions made by the account before it was deleted could be
|
||||
replayed!
|
||||
|
||||
Finally, the standard form for a transaction fee is `StdFee`:
|
||||
|
||||
```go
|
||||
// StdFee includes the amount of coins paid in fees and the maximum
|
||||
// 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 sdk.Coins `json:"amount"`
|
||||
Gas int64 `json:"gas"`
|
||||
}
|
||||
```
|
||||
|
||||
The fee must be paid by the first signer. This allows us to quickly check if the
|
||||
transaction fee can be paid, and reject the transaction if not.
|
||||
|
||||
## Signing
|
||||
|
||||
The `StdTx` supports multiple messages and multiple signers.
|
||||
To sign the transaction, each signer must collect the following information:
|
||||
|
||||
- the ChainID
|
||||
- the AccountNumber and Sequence for the given signer's account (from the
|
||||
blockchain)
|
||||
- the transaction fee
|
||||
- the list of transaction messages
|
||||
- an optional memo
|
||||
|
||||
Then they can compute the transaction bytes to sign using the
|
||||
`auth.StdSignBytes` function:
|
||||
|
||||
```go
|
||||
bytesToSign := StdSignBytes(chainID, accNum, accSequence, fee, msgs, memo)
|
||||
```
|
||||
|
||||
Note these bytes are unique for each signer, as they depend on the particular
|
||||
signers AccountNumber, Sequence, and optional memo. To facilitate easy
|
||||
inspection before signing, the bytes are actually just a JSON encoded form of
|
||||
all the relevant information.
|
||||
|
||||
## AnteHandler
|
||||
|
||||
As we saw in `App2`, we can use an `AnteHandler` to authenticate transactions
|
||||
before we handle any of their internal messages. While previously we implemented
|
||||
our own simple `AnteHandler`, the `x/auth` module provides a much more advanced
|
||||
one that uses `AccountMapper` and works with `StdTx`:
|
||||
|
||||
```go
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
```
|
||||
|
||||
The AnteHandler provided by `x/auth` enforces the following rules:
|
||||
|
||||
- the memo must not be too big
|
||||
- the right number of signatures must be provided (one for each unique signer
|
||||
returned by `msg.GetSigner` for each `msg`)
|
||||
- any account signing for the first-time must include a public key in the
|
||||
StdSignature
|
||||
- the signatures must be valid when authenticated in the same order as specified
|
||||
by the messages
|
||||
|
||||
Note that validating
|
||||
signatures requires checking that the correct account number and sequence was
|
||||
used by each signer, as this information is required in the `StdSignBytes`.
|
||||
|
||||
If any of the above are not satisfied, the AnteHandelr returns an error.
|
||||
|
||||
If all of the above verifications pass, the AnteHandler makes the following
|
||||
changes to the state:
|
||||
|
||||
- increment account sequence by one for all signers
|
||||
- set the pubkey in the account for any first-time signers
|
||||
- deduct the fee from the first signer's account
|
||||
|
||||
Recall that incrementing the `Sequence` prevents "replay attacks" where
|
||||
the same message could be executed over and over again.
|
||||
|
||||
The PubKey is required for signature verification, but it is only required in
|
||||
the StdSignature once. From that point on, it will be stored in the account.
|
||||
|
||||
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`,
|
||||
as provided by the `FeePayer(tx Tx) sdk.AccAddress` function.
|
||||
|
||||
## CoinKeeper
|
||||
|
||||
Now that we've seen the `auth.AccountMapper` and how its used to build a
|
||||
complete AnteHandler, it's time to look at how to build higher-level
|
||||
abstractions for taking action on accounts.
|
||||
|
||||
Earlier, we noted that `Mappers` are abstactions over KVStores that handle
|
||||
marshalling and unmarshalling data types to and from underlying stores.
|
||||
We can build another abstraction on top of `Mappers` that we call `Keepers`,
|
||||
which expose only limitted functionality on the underlying types stored by the `Mapper`.
|
||||
|
||||
For instance, the `x/bank` module defines the canonical versions of `MsgSend`
|
||||
and `MsgIssue` for the SDK, as well as a `Handler` for processing them. However,
|
||||
rather than passing a `KVStore` or even an `AccountMapper` directly to the handler,
|
||||
we introduce a `bank.Keeper`, which can only be used to transfer coins in and out of accounts.
|
||||
This allows us to determine up front that the only effect the bank module's
|
||||
`Handler` can have on the store is to change the amount of coins in an account -
|
||||
it can't increment sequence numbers, change PubKeys, or otherwise.
|
||||
|
||||
|
||||
A `bank.Keeper` is easily instantiated from an `AccountMapper`:
|
||||
|
||||
```go
|
||||
coinKeeper = bank.NewKeeper(accountMapper)
|
||||
```
|
||||
|
||||
We can then use it within a handler, instead of working directly with the
|
||||
`AccountMapper`. For instance, to add coins to an account:
|
||||
|
||||
```go
|
||||
// Finds account with addr in AccountMapper.
|
||||
// Adds coins to account's coin array.
|
||||
// Sets updated account in AccountMapper
|
||||
app.coinKeeper.AddCoins(ctx, addr, coins)
|
||||
```
|
||||
|
||||
See the [bank.Keeper API
|
||||
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#Keeper) for the full set of methods.
|
||||
|
||||
Note we can refine the `bank.Keeper` by restricting it's method set. For
|
||||
instance, the
|
||||
[bank.ViewKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#ViewKeeper)
|
||||
is a read-only version, while the
|
||||
[bank.SendKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#SendKeeper)
|
||||
only executes transfers of coins from input accounts to output
|
||||
accounts.
|
||||
|
||||
We use this `Keeper` paradigm extensively in the SDK as the way to define what
|
||||
kind of functionality each module gets access to. In particular, we try to
|
||||
follow the *principle of least authority*.
|
||||
Rather than providing full blown access to the `KVStore` or the `AccountMapper`,
|
||||
we restrict access to a small number of functions that do very specific things.
|
||||
|
||||
## App3
|
||||
|
||||
With the `auth.AccountMapper` and `bank.Keeper` in hand,
|
||||
we're now ready to build `App3`.
|
||||
The `x/auth` and `x/bank` modules do all the heavy lifting:
|
||||
|
||||
```go
|
||||
func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
// Create the codec with registered Msg types
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
keyFees := sdk.NewKVStoreKey("fee") // TODO
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to
|
||||
app.Router().
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
```
|
||||
|
||||
Note we use `bank.NewHandler`, which handles only `bank.MsgSend`,
|
||||
and receives only the `bank.Keeper`. See the
|
||||
[x/bank API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank)
|
||||
for more details.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Armed with native modules for authentication and coin transfer,
|
||||
emboldened by the paradigm of mappers and keepers,
|
||||
and ever invigorated by the desire to build secure state-machines,
|
||||
we find ourselves here with a full-blown, all-checks-in-place, multi-asset
|
||||
cryptocurrency - the beating heart of the Cosmos-SDK.
|
||||
@@ -0,0 +1,73 @@
|
||||
# ABCI
|
||||
|
||||
The Application BlockChain Interface, or ABCI, is a powerfully
|
||||
delineated boundary between the Cosmos-SDK and Tendermint.
|
||||
It separates the logical state transition machine of your application from
|
||||
its secure replication across many physical machines.
|
||||
|
||||
By providing a clear, language agnostic boundary between applications and consensus,
|
||||
ABCI provides tremendous developer flexibility and [support in many
|
||||
languages](https://tendermint.com/ecosystem). That said, it is still quite a low-level protocol, and
|
||||
requires frameworks to be built to abstract over that low-level componentry.
|
||||
The Cosmos-SDK is one such framework.
|
||||
|
||||
While we've already seen `DeliverTx`, the workhorse of any ABCI application,
|
||||
here we will introduce the other ABCI requests sent by Tendermint, and
|
||||
how we can use them to build more advanced applications. For a more complete
|
||||
depiction of the ABCI and how its used, see
|
||||
[the
|
||||
specification](https://github.com/tendermint/tendermint/blob/master/docs/abci-spec.md)
|
||||
|
||||
## InitChain
|
||||
|
||||
In our previous apps, we built out all the core logic, but we never specified
|
||||
how the store should be initialized. For that, we use the `app.InitChain` method,
|
||||
which is called once by Tendermint the very first time the application boots up.
|
||||
|
||||
The InitChain request contains a variety of Tendermint information, like the consensus
|
||||
parameters and an initial validator set, but it also contains an opaque blob of
|
||||
application specific bytes - typically JSON encoded.
|
||||
Apps can decide what to do with all of this information by calling the
|
||||
`app.SetInitChainer` method.
|
||||
|
||||
For instance, let's introduce a `GenesisAccount` struct that can be JSON encoded
|
||||
and part of a genesis file. Then we can populate the store with such accounts
|
||||
during InitChain:
|
||||
|
||||
```go
|
||||
TODO
|
||||
```
|
||||
|
||||
If we include a correctly formatted `GenesisAccount` in our Tendermint
|
||||
genesis.json file, the store will be initialized with those accounts and they'll
|
||||
be able to send transactions!
|
||||
|
||||
## BeginBlock
|
||||
|
||||
BeginBlock is called at the beginning of each block, before processing any
|
||||
transactions with DeliverTx.
|
||||
It contains information on what validators have signed.
|
||||
|
||||
## EndBlock
|
||||
|
||||
EndBlock is called at the end of each block, after processing all transactions
|
||||
with DeliverTx.
|
||||
It allows the application to return updates to the validator set.
|
||||
|
||||
## Commit
|
||||
|
||||
Commit is called after EndBlock. It persists the application state and returns
|
||||
the Merkle root hash to be included in the next Tendermint block. The root hash
|
||||
can be in Query for Merkle proofs of the state.
|
||||
|
||||
## Query
|
||||
|
||||
Query allows queries into the application store according to a path.
|
||||
|
||||
## CheckTx
|
||||
|
||||
CheckTx is used for the mempool. It only runs the AnteHandler. This is so
|
||||
potentially expensive message handling doesn't begin until the transaction has
|
||||
actually been committed in a block. The AnteHandler authenticates the sender and
|
||||
ensures they have enough to pay the fee for the transaction. If the transaction
|
||||
later fails, the sender still pays the fee.
|
||||
@@ -0,0 +1,72 @@
|
||||
# App5 - Basecoin
|
||||
|
||||
As we've seen, the SDK provides a flexible yet comprehensive framework for building state
|
||||
machines and defining their transitions, including authenticating transactions,
|
||||
executing messages, controlling access to stores, and updating the validator set.
|
||||
|
||||
Until now, we have focused on building only isolated ABCI applications to
|
||||
demonstrate and explain the various features and flexibilities of the SDK.
|
||||
Here, we'll connect our ABCI application to Tendermint so we can run a full
|
||||
blockchain node, and introduce command line and HTTP interfaces for interacting with it.
|
||||
|
||||
But first, let's talk about how source code should be laid out.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
TODO
|
||||
|
||||
## Tendermint Node
|
||||
|
||||
Since the Cosmos-SDK is written in Go, Cosmos-SDK applications can be compiled
|
||||
with Tendermint into a single binary. Of course, like any ABCI application, they
|
||||
can also run as separate processes that communicate with Tendermint via socket.
|
||||
|
||||
For more details on what's involved in starting a Tendermint full node, see the
|
||||
[NewNode](https://godoc.org/github.com/tendermint/tendermint/node#NewNode)
|
||||
function in `github.com/tendermint/tendermint/node`.
|
||||
|
||||
The `server` package in the Cosmos-SDK simplifies
|
||||
connecting an application with a Tendermint node.
|
||||
For instance, the following `main.go` file will give us a complete full node
|
||||
using the Basecoin application we built:
|
||||
|
||||
```go
|
||||
//TODO imports
|
||||
|
||||
func main() {
|
||||
cdc := app.MakeCodec()
|
||||
ctx := server.NewDefaultContext()
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "basecoind",
|
||||
Short: "Basecoin Daemon (server)",
|
||||
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
|
||||
}
|
||||
|
||||
server.AddCommands(ctx, cdc, rootCmd, server.DefaultAppInit,
|
||||
server.ConstructAppCreator(newApp, "basecoin"))
|
||||
|
||||
// prepare and add flags
|
||||
rootDir := os.ExpandEnv("$HOME/.basecoind")
|
||||
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir)
|
||||
executor.Execute()
|
||||
}
|
||||
|
||||
func newApp(logger log.Logger, db dbm.DB) abci.Application {
|
||||
return app.NewBasecoinApp(logger, db)
|
||||
}
|
||||
```
|
||||
|
||||
Note we utilize the popular [cobra library](https://github.com/spf13/cobra)
|
||||
for the CLI, in concert with the [viper library](https://github.com/spf13/library)
|
||||
for managing configuration. See our [cli library](https://github.com/tendermint/blob/master/tmlibs/cli/setup.go)
|
||||
for more details.
|
||||
|
||||
TODO: compile and run the binary
|
||||
|
||||
Options for running the `basecoind` binary are effectively the same as for `tendermint`.
|
||||
See [Using Tendermint](TODO) for more details.
|
||||
|
||||
## Clients
|
||||
|
||||
TODO
|
||||
@@ -0,0 +1,235 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bapp "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
const (
|
||||
app1Name = "App1"
|
||||
)
|
||||
|
||||
func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := wire.NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app1Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
|
||||
// Determine how transactions are decoded.
|
||||
app.SetTxDecoder(txDecoder)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", handleMsgSend(keyAccount))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Msg
|
||||
|
||||
// MsgSend implements sdk.Msg
|
||||
var _ sdk.Msg = MsgSend{}
|
||||
|
||||
// MsgSend to send coins from Input to Output
|
||||
type MsgSend struct {
|
||||
From sdk.AccAddress `json:"from"`
|
||||
To sdk.AccAddress `json:"to"`
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
}
|
||||
|
||||
// NewMsgSend
|
||||
func NewMsgSend(from, to sdk.AccAddress, amt sdk.Coins) MsgSend {
|
||||
return MsgSend{from, to, amt}
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSend) Type() string { return "send" }
|
||||
|
||||
// Implements Msg. Ensure the addresses are good and the
|
||||
// amount is positive.
|
||||
func (msg MsgSend) ValidateBasic() sdk.Error {
|
||||
if len(msg.From) == 0 {
|
||||
return sdk.ErrInvalidAddress("From address is empty")
|
||||
}
|
||||
if len(msg.To) == 0 {
|
||||
return sdk.ErrInvalidAddress("To address is empty")
|
||||
}
|
||||
if !msg.Amount.IsPositive() {
|
||||
return sdk.ErrInvalidCoins("Amount is not positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Msg. JSON encode the message.
|
||||
func (msg MsgSend) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// Implements Msg. Return the signer.
|
||||
func (msg MsgSend) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.From}
|
||||
}
|
||||
|
||||
// Returns the sdk.Tags for the message
|
||||
func (msg MsgSend) Tags() sdk.Tags {
|
||||
return sdk.NewTags("sender", []byte(msg.From.String())).
|
||||
AppendTag("receiver", []byte(msg.To.String()))
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Handler for the message
|
||||
|
||||
// Handle MsgSend.
|
||||
// NOTE: msg.From, msg.To, and msg.Amount were already validated
|
||||
// in ValidateBasic().
|
||||
func handleMsgSend(key *sdk.KVStoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
sendMsg, ok := msg.(MsgSend)
|
||||
if !ok {
|
||||
// Create custom error message and return result
|
||||
// Note: Using unreserved error codespace
|
||||
return sdk.NewError(2, 1, "MsgSend is malformed").Result()
|
||||
}
|
||||
|
||||
// Load the store.
|
||||
store := ctx.KVStore(key)
|
||||
|
||||
// Debit from the sender.
|
||||
if res := handleFrom(store, sendMsg.From, sendMsg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Credit the receiver.
|
||||
if res := handleTo(store, sendMsg.To, sendMsg.Amount); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Return a success (Code 0).
|
||||
// Add list of key-value pair descriptors ("tags").
|
||||
return sdk.Result{
|
||||
Tags: sendMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience Handlers
|
||||
func handleFrom(store sdk.KVStore, from sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Get sender account from the store.
|
||||
accBytes := store.Get(from)
|
||||
if accBytes == nil {
|
||||
// Account was not added to store. Return the result of the error.
|
||||
return sdk.NewError(2, 101, "Account not added to store").Result()
|
||||
}
|
||||
|
||||
// Unmarshal the JSON account bytes.
|
||||
var acc appAccount
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
// InternalError
|
||||
return sdk.ErrInternal("Error when deserializing account").Result()
|
||||
}
|
||||
|
||||
// Deduct msg amount from sender account.
|
||||
senderCoins := acc.Coins.Minus(amt)
|
||||
|
||||
// If any coin has negative amount, return insufficient coins error.
|
||||
if !senderCoins.IsNotNegative() {
|
||||
return sdk.ErrInsufficientCoins("Insufficient coins in account").Result()
|
||||
}
|
||||
|
||||
// Set acc coins to new amount.
|
||||
acc.Coins = senderCoins
|
||||
|
||||
// Encode sender account.
|
||||
accBytes, err = json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated sender account
|
||||
store.Set(from, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
func handleTo(store sdk.KVStore, to sdk.AccAddress, amt sdk.Coins) sdk.Result {
|
||||
// Add msg amount to receiver account
|
||||
accBytes := store.Get(to)
|
||||
var acc appAccount
|
||||
if accBytes == nil {
|
||||
// Receiver account does not already exist, create a new one.
|
||||
acc = appAccount{}
|
||||
} else {
|
||||
// Receiver account already exists. Retrieve and decode it.
|
||||
err := json.Unmarshal(accBytes, &acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account decoding error").Result()
|
||||
}
|
||||
}
|
||||
|
||||
// Add amount to receiver's old coins
|
||||
receiverCoins := acc.Coins.Plus(amt)
|
||||
|
||||
// Update receiver account
|
||||
acc.Coins = receiverCoins
|
||||
|
||||
// Encode receiver account
|
||||
accBytes, err := json.Marshal(acc)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Account encoding error").Result()
|
||||
}
|
||||
|
||||
// Update store with updated receiver account
|
||||
store.Set(to, accBytes)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// Simple account struct
|
||||
type appAccount struct {
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Tx
|
||||
|
||||
// Simple tx to wrap the Msg.
|
||||
type app1Tx struct {
|
||||
MsgSend
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app1Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.MsgSend}
|
||||
}
|
||||
|
||||
// JSON decode MsgSend.
|
||||
func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||
var tx app1Tx
|
||||
err := json.Unmarshal(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrTxDecode(err.Error())
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bapp "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
const (
|
||||
app2Name = "App2"
|
||||
)
|
||||
|
||||
var (
|
||||
issuer = crypto.GenPrivKeyEd25519().PubKey().Address()
|
||||
)
|
||||
|
||||
func NewCodec() *wire.Codec {
|
||||
cdc := wire.NewCodec()
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil)
|
||||
cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil)
|
||||
return cdc
|
||||
}
|
||||
|
||||
func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app2Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
// Create a key for accessing the issue store.
|
||||
keyIssue := sdk.NewKVStoreKey("issue")
|
||||
|
||||
// set antehandler function
|
||||
app.SetAnteHandler(antehandler)
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", handleMsgSend(keyAccount)).
|
||||
AddRoute("issue", handleMsgIssue(keyAccount, keyIssue))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyIssue)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Msgs
|
||||
|
||||
// MsgIssue to allow a registered issuer
|
||||
// to issue new coins.
|
||||
type MsgIssue struct {
|
||||
Issuer sdk.AccAddress
|
||||
Receiver sdk.AccAddress
|
||||
Coin sdk.Coin
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgIssue) Type() string { return "issue" }
|
||||
|
||||
// Implements Msg. Ensures addresses are valid and Coin is positive
|
||||
func (msg MsgIssue) ValidateBasic() sdk.Error {
|
||||
if len(msg.Issuer) == 0 {
|
||||
return sdk.ErrInvalidAddress("Issuer address cannot be empty")
|
||||
}
|
||||
|
||||
if len(msg.Receiver) == 0 {
|
||||
return sdk.ErrInvalidAddress("Receiver address cannot be empty")
|
||||
}
|
||||
|
||||
// Cannot issue zero or negative coins
|
||||
if !msg.Coin.IsPositive() {
|
||||
return sdk.ErrInvalidCoins("Cannot issue 0 or negative coin amounts")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Msg. Get canonical sign bytes for MsgIssue
|
||||
func (msg MsgIssue) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// Implements Msg. Return the signer.
|
||||
func (msg MsgIssue) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.Issuer}
|
||||
}
|
||||
|
||||
// Returns the sdk.Tags for the message
|
||||
func (msg MsgIssue) Tags() sdk.Tags {
|
||||
return sdk.NewTags("issuer", []byte(msg.Issuer.String())).
|
||||
AppendTag("receiver", []byte(msg.Receiver.String()))
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Handler for the message
|
||||
|
||||
// Handle MsgIssue.
|
||||
func handleMsgIssue(keyIssue *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
issueMsg, ok := msg.(MsgIssue)
|
||||
if !ok {
|
||||
return sdk.NewError(2, 1, "MsgIssue is malformed").Result()
|
||||
}
|
||||
|
||||
// Retrieve stores
|
||||
issueStore := ctx.KVStore(keyIssue)
|
||||
accStore := ctx.KVStore(keyAcc)
|
||||
|
||||
// Handle updating coin info
|
||||
if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Issue coins to receiver using previously defined handleTo function
|
||||
if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
// Return result with Issue msg tags
|
||||
Tags: issueMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result {
|
||||
// the issuer address is stored directly under the coin denomination
|
||||
denom := []byte(coin.Denom)
|
||||
infoBytes := store.Get(denom)
|
||||
if infoBytes == nil {
|
||||
return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
var coinInfo coinInfo
|
||||
err := json.Unmarshal(infoBytes, &coinInfo)
|
||||
if err != nil {
|
||||
return sdk.ErrInternal("Error when deserializing coinInfo").Result()
|
||||
}
|
||||
|
||||
// Msg Issuer is not authorized to issue these coins
|
||||
if !bytes.Equal(coinInfo.Issuer, issuer) {
|
||||
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// coinInfo stores meta data about a coin
|
||||
type coinInfo struct {
|
||||
Issuer sdk.AccAddress `json:"issuer"`
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Tx
|
||||
|
||||
// Simple tx to wrap the Msg.
|
||||
type app2Tx struct {
|
||||
sdk.Msg
|
||||
Signatures []auth.StdSignature
|
||||
}
|
||||
|
||||
// This tx only has one Msg.
|
||||
func (tx app2Tx) GetMsgs() []sdk.Msg {
|
||||
return []sdk.Msg{tx.Msg}
|
||||
}
|
||||
|
||||
func (tx app2Tx) GetSignatures() []auth.StdSignature {
|
||||
return tx.Signatures
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
|
||||
// Simple anteHandler that ensures msg signers have signed.
|
||||
// Provides no replay protection.
|
||||
func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) {
|
||||
appTx, ok := tx.(app2Tx)
|
||||
if !ok {
|
||||
// set abort boolean to true so that we don't continue to process failed tx
|
||||
return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true
|
||||
}
|
||||
|
||||
// expect only one msg in app2Tx
|
||||
msg := tx.GetMsgs()[0]
|
||||
|
||||
signerAddrs := msg.GetSigners()
|
||||
|
||||
if len(signerAddrs) != len(appTx.GetSignatures()) {
|
||||
return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true
|
||||
}
|
||||
|
||||
signBytes := msg.GetSignBytes()
|
||||
for i, addr := range signerAddrs {
|
||||
sig := appTx.GetSignatures()[i]
|
||||
|
||||
// check that submitted pubkey belongs to required address
|
||||
if !bytes.Equal(sig.PubKey.Address(), addr) {
|
||||
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
|
||||
}
|
||||
|
||||
// check that signature is over expected signBytes
|
||||
if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) {
|
||||
return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true
|
||||
}
|
||||
}
|
||||
|
||||
// authentication passed, app to continue processing by sending msg to handler
|
||||
return ctx, sdk.Result{}, false
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bapp "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
const (
|
||||
app3Name = "App3"
|
||||
)
|
||||
|
||||
func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
// Create the codec with registered Msg types
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
keyFees := sdk.NewKVStoreKey("fee") // TODO
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to
|
||||
app.Router().
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
bapp "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
app4Name = "App4"
|
||||
)
|
||||
|
||||
func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
|
||||
// TODO
|
||||
keyFees := sdk.NewKVStoreKey("fee")
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Set InitChainer
|
||||
app.SetInitChainer(NewInitChainer(cdc, accountMapper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
app.Router().
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
// Application state at Genesis has accounts with starting balances
|
||||
type GenesisState struct {
|
||||
Accounts []*GenesisAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
// GenesisAccount doesn't need pubkey or sequence
|
||||
type GenesisAccount struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
// Converts GenesisAccount to auth.BaseAccount for storage in account store
|
||||
func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) {
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: ga.Address,
|
||||
Coins: ga.Coins.Sort(),
|
||||
}
|
||||
return &baseAcc, nil
|
||||
}
|
||||
|
||||
// InitChainer will set initial balances for accounts as well as initial coin metadata
|
||||
// MsgIssue can no longer be used to create new coin
|
||||
func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
|
||||
genesisState := new(GenesisState)
|
||||
err := cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, gacc := range genesisState.Accounts {
|
||||
acc, err := gacc.ToAccount()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
acc.AccountNumber = accountMapper.GetNextAccountNumber(ctx)
|
||||
accountMapper.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Introduction
|
||||
|
||||
Welcome to the Cosmos-SDK Core Documentation.
|
||||
|
||||
Here you will learn how to use the Cosmos-SDK to build Basecoin, a
|
||||
complete proof-of-stake cryptocurrency system
|
||||
|
||||
We proceed through a series of increasingly advanced and complete implementations of
|
||||
the Basecoin application, with each implementation showcasing a new component of
|
||||
the SDK:
|
||||
|
||||
- App1 - The Basics - Messages, Stores, Handlers, BaseApp
|
||||
- App2 - Transactions - Amino and AnteHandler
|
||||
- App3 - Modules - `x/auth` and `x/bank`
|
||||
- App4 - Validator Set Changes - Change the Tendermint validator set
|
||||
- App5 - Basecoin - Bringing it all together
|
||||
@@ -0,0 +1,72 @@
|
||||
# MultiStore
|
||||
|
||||
TODO: reconcile this with everything ... would be nice to have this explanation
|
||||
somewhere but where does it belong ? So far we've already showed how to use it
|
||||
all by creating KVStore keys and calling app.MountStoresIAVL !
|
||||
|
||||
|
||||
The Cosmos-SDK provides a special Merkle database called a `MultiStore` to be used for all application
|
||||
storage. The MultiStore consists of multiple Stores that must be mounted to the
|
||||
MultiStore during application setup. Stores are mounted to the MultiStore using a capabilities key,
|
||||
ensuring that only parts of the program with access to the key can access the store.
|
||||
|
||||
The goals of the MultiStore are as follows:
|
||||
|
||||
- Enforce separation of concerns at the storage level
|
||||
- Restrict access to storage using capabilities
|
||||
- Support multiple Store implementations in a single MultiStore, for instance the Tendermint IAVL tree and
|
||||
the Ethereum Patricia Trie
|
||||
- Merkle proofs for various queries (existence, absence, range, etc.) on current and retained historical state
|
||||
- Allow for iteration within Stores
|
||||
- Provide caching for intermediate state during execution of blocks and transactions (including for iteration)
|
||||
|
||||
- Support historical state pruning and snapshotting
|
||||
|
||||
Currently, all Stores in the MultiStore must satisfy the `KVStore` interface,
|
||||
which defines a simple key-value store. In the future,
|
||||
we may support more kinds of stores, such as a HeapStore
|
||||
or a NDStore for multidimensional storage.
|
||||
|
||||
## Mounting Stores
|
||||
|
||||
Stores are mounted during application setup. To mount some stores, first create
|
||||
their capability-keys:
|
||||
|
||||
```
|
||||
fooKey := sdk.NewKVStoreKey("foo")
|
||||
barKey := sdk.NewKVStoreKey("bar")
|
||||
catKey := sdk.NewKVStoreKey("cat")
|
||||
```
|
||||
|
||||
Stores are mounted directly on the BaseApp.
|
||||
They can either specify their own database, or share the primary one already
|
||||
passed to the BaseApp.
|
||||
|
||||
In this example, `foo` and `bar` will share the primary database, while `cat` will
|
||||
specify its own:
|
||||
|
||||
```
|
||||
catDB := dbm.NewMemDB()
|
||||
app.MountStore(fooKey, sdk.StoreTypeIAVL)
|
||||
app.MountStore(barKey, sdk.StoreTypeIAVL)
|
||||
app.MountStoreWithDB(catKey, sdk.StoreTypeIAVL, catDB)
|
||||
```
|
||||
|
||||
## Accessing Stores
|
||||
|
||||
In the Cosmos-SDK, the only way to access a store is with a capability-key.
|
||||
Only modules given explicit access to the capability-key will
|
||||
be able to access the corresponding store. Access to the MultiStore is mediated
|
||||
through the `Context`.
|
||||
|
||||
## Notes
|
||||
|
||||
TODO: move this to the spec
|
||||
|
||||
In the example above, all IAVL nodes (inner and leaf) will be stored
|
||||
in mainDB with the prefix of "s/k:foo/" and "s/k:bar/" respectively,
|
||||
thus sharing the mainDB. All IAVL nodes (inner and leaf) for the
|
||||
cat KVStore are stored separately in catDB with the prefix of
|
||||
"s/\_/". The "s/k:KEY/" and "s/\_/" prefixes are there to
|
||||
disambiguate store items from other items of non-storage concern.
|
||||
|
||||
-320
@@ -1,320 +0,0 @@
|
||||
## Introduction
|
||||
|
||||
If you want to see some examples, take a look at the [examples/basecoin](/examples/basecoin) directory.
|
||||
|
||||
## Design Goals
|
||||
|
||||
The design of the Cosmos SDK is based on the principles of "capabilities systems".
|
||||
|
||||
## Capabilities systems
|
||||
|
||||
### Need for module isolation
|
||||
### Capability is implied permission
|
||||
### TODO Link to thesis
|
||||
|
||||
## Tx & Msg
|
||||
|
||||
The SDK distinguishes between transactions (Tx) and messages
|
||||
(Msg). A Tx is a Msg wrapped with authentication and fee data.
|
||||
|
||||
### Messages
|
||||
|
||||
Users can create messages containing arbitrary information by
|
||||
implementing the `Msg` interface:
|
||||
|
||||
```go
|
||||
type Msg interface {
|
||||
|
||||
// Return the message type.
|
||||
// Must be alphanumeric or empty.
|
||||
Type() string
|
||||
|
||||
// Get the canonical byte representation of the Msg.
|
||||
GetSignBytes() []byte
|
||||
|
||||
// ValidateBasic does a simple validation check that
|
||||
// doesn't require access to any other information.
|
||||
ValidateBasic() error
|
||||
|
||||
// Signers returns the addrs of signers that must sign.
|
||||
// CONTRACT: All signatures must be present to be valid.
|
||||
// CONTRACT: Returns addrs in some deterministic order.
|
||||
GetSigners() []Address
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Messages must specify their type via the `Type()` method. The type should
|
||||
correspond to the messages handler, so there can be many messages with the same
|
||||
type.
|
||||
|
||||
Messages must also specify how they are to be authenticated. The `GetSigners()`
|
||||
method return a list of addresses that must sign the message, while the
|
||||
`GetSignBytes()` method returns the bytes that must be signed for a signature
|
||||
to be valid.
|
||||
|
||||
Addresses in the SDK are arbitrary byte arrays that are hex-encoded when
|
||||
displayed as a string or rendered in JSON.
|
||||
|
||||
Messages can specify basic self-consistency checks using the `ValidateBasic()`
|
||||
method to enforce that message contents are well formed before any actual logic
|
||||
begins.
|
||||
|
||||
For instance, the `Basecoin` message types are defined in `x/bank/tx.go`:
|
||||
|
||||
```go
|
||||
type MsgSend struct {
|
||||
Inputs []Input `json:"inputs"`
|
||||
Outputs []Output `json:"outputs"`
|
||||
}
|
||||
|
||||
type MsgIssue struct {
|
||||
Banker sdk.Address `json:"banker"`
|
||||
Outputs []Output `json:"outputs"`
|
||||
}
|
||||
```
|
||||
|
||||
Each specifies the addresses that must sign the message:
|
||||
|
||||
```go
|
||||
func (msg MsgSend) GetSigners() []sdk.Address {
|
||||
addrs := make([]sdk.Address, len(msg.Inputs))
|
||||
for i, in := range msg.Inputs {
|
||||
addrs[i] = in.Address
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (msg MsgIssue) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Banker}
|
||||
}
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
A transaction is a message with additional information for authentication:
|
||||
|
||||
```go
|
||||
type Tx interface {
|
||||
|
||||
GetMsg() Msg
|
||||
|
||||
// Signatures returns the signature of signers who signed the Msg.
|
||||
// CONTRACT: Length returned is same as length of
|
||||
// pubkeys returned from MsgKeySigners, and the order
|
||||
// matches.
|
||||
// CONTRACT: If the signature is missing (ie the Msg is
|
||||
// invalid), then the corresponding signature is
|
||||
// .Empty().
|
||||
GetSignatures() []StdSignature
|
||||
}
|
||||
```
|
||||
|
||||
The `tx.GetSignatures()` method returns a list of signatures, which must match
|
||||
the list of addresses returned by `tx.Msg.GetSigners()`. The signatures come in
|
||||
a standard form:
|
||||
|
||||
```go
|
||||
type StdSignature struct {
|
||||
crypto.PubKey // optional
|
||||
crypto.Signature
|
||||
Sequence int64
|
||||
}
|
||||
```
|
||||
|
||||
It contains the signature itself, as well as the corresponding account's
|
||||
sequence number. The sequence number is expected to increment every time a
|
||||
message is signed by a given account. This prevents "replay attacks", where
|
||||
the same message could be executed over and over again.
|
||||
|
||||
The `StdSignature` can also optionally include the public key for verifying the
|
||||
signature. An application can store the public key for each address it knows
|
||||
about, making it optional to include the public key in the transaction. In the
|
||||
case of Basecoin, the public key only needs to be included in the first
|
||||
transaction send by a given account - after that, the public key is forever
|
||||
stored by the application and can be left out of transactions.
|
||||
|
||||
The address responsible for paying the transactions fee is the first address
|
||||
returned by msg.GetSigners(). The convenience function `FeePayer(tx Tx)` is provided
|
||||
to return this.
|
||||
|
||||
The standard way to create a transaction from a message is to use the `StdTx`:
|
||||
|
||||
```go
|
||||
type StdTx struct {
|
||||
Msg
|
||||
Signatures []StdSignature
|
||||
}
|
||||
```
|
||||
|
||||
### Encoding and Decoding Transactions
|
||||
|
||||
Messages and transactions are designed to be generic enough for developers to
|
||||
specify their own encoding schemes. This enables the SDK to be used as the
|
||||
framwork for constructing already specified cryptocurrency state machines, for
|
||||
instance Ethereum.
|
||||
|
||||
When initializing an application, a developer must specify a `TxDecoder`
|
||||
function which determines how an arbitrary byte array should be unmarshalled
|
||||
into a `Tx`:
|
||||
|
||||
```go
|
||||
type TxDecoder func(txBytes []byte) (Tx, error)
|
||||
```
|
||||
|
||||
In `Basecoin`, we use the Tendermint wire format and the `go-amino` library for
|
||||
encoding and decoding all message types. The `go-amino` library has the nice
|
||||
property that it can unmarshal into interface types, but it requires the
|
||||
relevant types to be registered ahead of type. Registration happens on a
|
||||
`Codec` object, so as not to taint the global name space.
|
||||
|
||||
For instance, in `Basecoin`, we wish to register the `MsgSend` and `MsgIssue`
|
||||
types:
|
||||
|
||||
```go
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(bank.MsgSend{}, "cosmos-sdk/MsgSend", nil)
|
||||
cdc.RegisterConcrete(bank.MsgIssue{}, "cosmos-sdk/MsgIssue", nil)
|
||||
```
|
||||
|
||||
Note how each concrete type is given a name - these name determine the type's
|
||||
unique "prefix bytes" during encoding. A registered type will always use the
|
||||
same prefix-bytes, regardless of what interface it is satisfying. For more
|
||||
details, see the [go-amino documentation](https://github.com/tendermint/go-amino/blob/develop).
|
||||
|
||||
|
||||
## Storage
|
||||
|
||||
### MultiStore
|
||||
|
||||
MultiStore is like a root filesystem of an operating system, except
|
||||
all the entries are fully Merkleized. You mount onto a MultiStore
|
||||
any number of Stores. Currently only KVStores are supported, but in
|
||||
the future we may support more kinds of stores, such as a HeapStore
|
||||
or a NDStore for multidimensional storage.
|
||||
|
||||
The MultiStore as well as all mounted stores provide caching (aka
|
||||
cache-wrapping) for intermediate state (aka software transactional
|
||||
memory) during the execution of transactions. In the case of the
|
||||
KVStore, this also works for iterators. For example, after running
|
||||
the app's AnteHandler, the MultiStore is cache-wrapped (and each
|
||||
store is also cache-wrapped) so that should processing of the
|
||||
transaction fail, at least the transaction fees are paid and
|
||||
sequence incremented.
|
||||
|
||||
The MultiStore as well as all stores support (or will support)
|
||||
historical state pruning and snapshotting and various kinds of
|
||||
queries with proofs.
|
||||
|
||||
### KVStore
|
||||
|
||||
Here we'll focus on the IAVLStore, which is a kind of KVStore.
|
||||
|
||||
IAVLStore is a fast balanced dynamic Merkle store that also supports
|
||||
iteration, and of course cache-wrapping, state pruning, and various
|
||||
queries with proofs, such as proofs of existence, absence, range,
|
||||
and so on.
|
||||
|
||||
Here's how you mount them to a MultiStore.
|
||||
|
||||
```go
|
||||
mainDB, catDB := dbm.NewMemDB(), dbm.NewMemDB()
|
||||
fooKey := sdk.NewKVStoreKey("foo")
|
||||
barKey := sdk.NewKVStoreKey("bar")
|
||||
catKey := sdk.NewKVStoreKey("cat")
|
||||
ms := NewCommitMultiStore(mainDB)
|
||||
ms.MountStoreWithDB(fooKey, sdk.StoreTypeIAVL, nil)
|
||||
ms.MountStoreWithDB(barKey, sdk.StoreTypeIAVL, nil)
|
||||
ms.MountStoreWithDB(catKey, sdk.StoreTypeIAVL, catDB)
|
||||
```
|
||||
|
||||
In the example above, all IAVL nodes (inner and leaf) will be stored
|
||||
in mainDB with the prefix of "s/k:foo/" and "s/k:bar/" respectively,
|
||||
thus sharing the mainDB. All IAVL nodes (inner and leaf) for the
|
||||
cat KVStore are stored separately in catDB with the prefix of
|
||||
"s/\_/". The "s/k:KEY/" and "s/\_/" prefixes are there to
|
||||
disambiguate store items from other items of non-storage concern.
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
The SDK uses a `Context` to propogate common information across functions. The
|
||||
`Context` is modeled after the Golang `context.Context` object, which has
|
||||
become ubiquitous in networking middleware and routing applications as a means
|
||||
to easily propogate request context through handler functions.
|
||||
|
||||
The main information stored in the `Context` includes the application
|
||||
MultiStore (see below), the last block header, and the transaction bytes.
|
||||
Effectively, the context contains all data that may be necessary for processing
|
||||
a transaction.
|
||||
|
||||
Many methods on SDK objects receive a context as the first argument.
|
||||
|
||||
## Handler
|
||||
|
||||
Transaction processing in the SDK is defined through `Handler` functions:
|
||||
|
||||
```go
|
||||
type Handler func(ctx Context, tx Tx) Result
|
||||
```
|
||||
|
||||
A handler takes a context and a transaction and returns a result. All
|
||||
information necessary for processing a transaction should be available in the
|
||||
context.
|
||||
|
||||
While the context holds the entire application state (all referenced from the
|
||||
root MultiStore), a particular handler only needs a particular kind of access
|
||||
to a particular store (or two or more). Access to stores is managed using
|
||||
capabilities keys and mappers. When a handler is initialized, it is passed a
|
||||
key or mapper that gives it access to the relevant stores.
|
||||
|
||||
```go
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_stores.go
|
||||
app.BaseApp.MountStore(app.capKeyMainStore, sdk.StoreTypeIAVL)
|
||||
app.accountMapper = auth.NewAccountMapper(
|
||||
app.capKeyMainStore, // target store
|
||||
&types.AppAccount{}, // prototype
|
||||
)
|
||||
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
|
||||
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
|
||||
|
||||
// File: cosmos-sdk/x/bank/handler.go
|
||||
// NOTE: Technically, NewHandler only needs a CoinMapper
|
||||
func NewHandler(am sdk.AccountMapper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
cm := CoinMapper{am}
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## AnteHandler
|
||||
|
||||
### Handling Fee payment
|
||||
### Handling Authentication
|
||||
|
||||
## Accounts and x/auth
|
||||
|
||||
### sdk.Account
|
||||
### auth.BaseAccount
|
||||
### auth.AccountMapper
|
||||
|
||||
## Wire codec
|
||||
|
||||
### Why another codec?
|
||||
### vs encoding/json
|
||||
### vs protobuf
|
||||
|
||||
## KVStore example
|
||||
|
||||
## Basecoin example
|
||||
|
||||
The quintessential SDK application is Basecoin - a simple
|
||||
multi-asset cryptocurrency. Basecoin consists of a set of
|
||||
accounts stored in a Merkle tree, where each account may have
|
||||
many coins. There are two message types: MsgSend and MsgIssue.
|
||||
MsgSend allows coins to be sent around, while MsgIssue allows a
|
||||
set of predefined users to issue new coins.
|
||||
|
||||
## Conclusion
|
||||
+2
-50
@@ -6,54 +6,6 @@
|
||||
Welcome to the Cosmos SDK!
|
||||
==========================
|
||||
|
||||
.. image:: graphics/cosmos-sdk-image.png
|
||||
:height: 250px
|
||||
:width: 500px
|
||||
:align: center
|
||||
This location for our documentation has been deprecated, please see:
|
||||
|
||||
SDK
|
||||
---
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
sdk/install.rst
|
||||
sdk/key-management.rst
|
||||
.. sdk/overview.rst # needs to be updated
|
||||
.. old/glossary.rst # not completely up to date but has good content
|
||||
|
||||
.. Basecoin
|
||||
.. --------
|
||||
|
||||
.. .. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
.. old/basecoin/basics.rst # has a decent getting-start tutorial that's relatively up to date, should be consolidated with the other getting started doc
|
||||
|
||||
.. Extensions
|
||||
.. ----------
|
||||
|
||||
.. old/basecoin/extensions.rst # probably not worth salvaging
|
||||
|
||||
.. Replay Protection
|
||||
.. ~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. old/replay-protection.rst # not sure if worth salvaging
|
||||
|
||||
|
||||
Staking
|
||||
~~~~~~~
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
staking/testnet.rst
|
||||
.. staking/intro.rst
|
||||
.. staking/key-management.rst
|
||||
.. staking/local-testnet.rst
|
||||
.. staking/public-testnet.rst
|
||||
|
||||
.. IBC
|
||||
.. ---
|
||||
|
||||
.. old/ibc.rst # needs to be updated
|
||||
- https://cosmos.network/docs/
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Install
|
||||
|
||||
The fastest and easiest way to install the Cosmos SDK binaries
|
||||
is to run [this script](https://github.com/cosmos/cosmos-sdk/blob/develop/scripts/install_sdk_ubuntu.sh) on a fresh Ubuntu instance. Similarly, you can run [this script](https://github.com/cosmos/cosmos-sdk/blob/develop/scripts/install_sdk_bsd.sh) on a fresh FreeBSD instance. Read the scripts before running them to ensure no untrusted connection is being made, for example we're making curl requests to download golang. Also read the comments / instructions carefully (i.e., reset your terminal after running the script).
|
||||
|
||||
Cosmos SDK can be installed to
|
||||
`$GOPATH/src/github.com/cosmos/cosmos-sdk` like a normal Go program:
|
||||
|
||||
```
|
||||
go get github.com/cosmos/cosmos-sdk
|
||||
```
|
||||
|
||||
If the dependencies have been updated with breaking changes, or if
|
||||
another branch is required, `dep` is used for dependency management.
|
||||
Thus, assuming you've already run `go get` or otherwise cloned the repo,
|
||||
the correct way to install is:
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
|
||||
make get_tools
|
||||
make get_vendor_deps
|
||||
make install
|
||||
make install_examples
|
||||
```
|
||||
|
||||
This will install `gaiad` and `gaiacli` and four example binaries:
|
||||
`basecoind`, `basecli`, `democoind`, and `democli`.
|
||||
|
||||
Verify that everything is OK by running:
|
||||
|
||||
```
|
||||
gaiad version
|
||||
```
|
||||
|
||||
you should see:
|
||||
|
||||
```
|
||||
0.17.3-a5a78eb
|
||||
```
|
||||
|
||||
then with:
|
||||
|
||||
```
|
||||
gaiacli version
|
||||
```
|
||||
you should see the same version (or a later one for both).
|
||||
|
||||
## Update
|
||||
|
||||
Get latest code (you can also `git fetch` only the version desired),
|
||||
ensure the dependencies are up to date, then recompile.
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
|
||||
git fetch -a origin
|
||||
git checkout VERSION
|
||||
make get_vendor_deps
|
||||
make install
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=python -msphinx
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=Cosmos-SDK
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
|
||||
echo.then set the SPHINXBUILD environment variable to point to the full
|
||||
echo.path of the 'sphinx-build' executable. Alternatively you may add the
|
||||
echo.Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
@@ -0,0 +1,51 @@
|
||||
# Bank
|
||||
|
||||
The `x/bank` module is for transferring coins between accounts.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank).
|
||||
|
||||
# Stake
|
||||
|
||||
The `x/stake` module is for Cosmos Delegated-Proof-of-Stake.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/stake).
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/staking)
|
||||
|
||||
# Slashing
|
||||
|
||||
The `x/slashing` module is for Cosmos Delegated-Proof-of-Stake.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/slashing)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/slashing)
|
||||
|
||||
# Provisions
|
||||
|
||||
The `x/provisions` module is for distributing fees and inflation across bonded
|
||||
stakeholders.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/provisions)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/provisions)
|
||||
|
||||
# Governance
|
||||
|
||||
The `x/gov` module is for bonded stakeholders to make proposals and vote on them.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/gov)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/governance)
|
||||
|
||||
# IBC
|
||||
|
||||
The `x/ibc` module is for InterBlockchain Communication.
|
||||
|
||||
See the [API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/ibc)
|
||||
|
||||
See the
|
||||
[specification](https://github.com/cosmos/cosmos-sdk/tree/develop/docs/spec/ibc)
|
||||
@@ -1,289 +0,0 @@
|
||||
Basecoin Basics
|
||||
===============
|
||||
|
||||
Here we explain how to get started with a basic Basecoin blockchain, how
|
||||
to send transactions between accounts using the ``basecoin`` tool, and
|
||||
what is happening under the hood.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
With go, it's one command:
|
||||
|
||||
::
|
||||
|
||||
go get -u github.com/cosmos/cosmos-sdk
|
||||
|
||||
If you have trouble, see the `installation guide <./install.html>`__.
|
||||
|
||||
TODO: update all the below
|
||||
|
||||
Generate some keys
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's generate two keys, one to receive an initial allocation of coins,
|
||||
and one to send some coins to later:
|
||||
|
||||
::
|
||||
|
||||
basecli keys new cool
|
||||
basecli keys new friend
|
||||
|
||||
You'll need to enter passwords. You can view your key names and
|
||||
addresses with ``basecli keys list``, or see a particular key's address
|
||||
with ``basecli keys get <NAME>``.
|
||||
|
||||
Initialize Basecoin
|
||||
-------------------
|
||||
|
||||
To initialize a new Basecoin blockchain, run:
|
||||
|
||||
::
|
||||
|
||||
basecoin init <ADDRESS>
|
||||
|
||||
If you prefer not to copy-paste, you can provide the address
|
||||
programatically:
|
||||
|
||||
::
|
||||
|
||||
basecoin init $(basecli keys get cool | awk '{print $2}')
|
||||
|
||||
This will create the necessary files for a Basecoin blockchain with one
|
||||
validator and one account (corresponding to your key) in
|
||||
``~/.basecoin``. For more options on setup, see the `guide to using the
|
||||
Basecoin tool </docs/guide/basecoin-tool.md>`__.
|
||||
|
||||
If you like, you can manually add some more accounts to the blockchain
|
||||
by generating keys and editing the ``~/.basecoin/genesis.json``.
|
||||
|
||||
Start Basecoin
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Now we can start Basecoin:
|
||||
|
||||
::
|
||||
|
||||
basecoin start
|
||||
|
||||
You should see blocks start streaming in!
|
||||
|
||||
Initialize Light-Client
|
||||
-----------------------
|
||||
|
||||
Now that Basecoin is running we can initialize ``basecli``, the
|
||||
light-client utility. Basecli is used for sending transactions and
|
||||
querying the state. Leave Basecoin running and open a new terminal
|
||||
window. Here run:
|
||||
|
||||
::
|
||||
|
||||
basecli init --node=tcp://localhost:46657 --genesis=$HOME/.basecoin/genesis.json
|
||||
|
||||
If you provide the genesis file to basecli, it can calculate the proper
|
||||
chainID and validator hash. Basecli needs to get this information from
|
||||
some trusted source, so all queries done with ``basecli`` can be
|
||||
cryptographically proven to be correct according to a known validator
|
||||
set.
|
||||
|
||||
Note: that ``--genesis`` only works if there have been no validator set
|
||||
changes since genesis. If there are validator set changes, you need to
|
||||
find the current set through some other method.
|
||||
|
||||
Send transactions
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now we are ready to send some transactions. First Let's check the
|
||||
balance of the two accounts we setup earlier:
|
||||
|
||||
::
|
||||
|
||||
ME=$(basecli keys get cool | awk '{print $2}')
|
||||
YOU=$(basecli keys get friend | awk '{print $2}')
|
||||
basecli query account $ME
|
||||
basecli query account $YOU
|
||||
|
||||
The first account is flush with cash, while the second account doesn't
|
||||
exist. Let's send funds from the first account to the second:
|
||||
|
||||
::
|
||||
|
||||
basecli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1
|
||||
|
||||
Now if we check the second account, it should have ``1000`` 'mycoin'
|
||||
coins!
|
||||
|
||||
::
|
||||
|
||||
basecli query account $YOU
|
||||
|
||||
We can send some of these coins back like so:
|
||||
|
||||
::
|
||||
|
||||
basecli tx send --name=friend --amount=500mycoin --to=$ME --sequence=1
|
||||
|
||||
Note how we use the ``--name`` flag to select a different account to
|
||||
send from.
|
||||
|
||||
If we try to send too much, we'll get an error:
|
||||
|
||||
::
|
||||
|
||||
basecli tx send --name=friend --amount=500000mycoin --to=$ME --sequence=2
|
||||
|
||||
Let's send another transaction:
|
||||
|
||||
::
|
||||
|
||||
basecli tx send --name=cool --amount=2345mycoin --to=$YOU --sequence=2
|
||||
|
||||
Note the ``hash`` value in the response - this is the hash of the
|
||||
transaction. We can query for the transaction by this hash:
|
||||
|
||||
::
|
||||
|
||||
basecli query tx <HASH>
|
||||
|
||||
See ``basecli tx send --help`` for additional details.
|
||||
|
||||
Proof
|
||||
-----
|
||||
|
||||
Even if you don't see it in the UI, the result of every query comes with
|
||||
a proof. This is a Merkle proof that the result of the query is actually
|
||||
contained in the state. And the state's Merkle root is contained in a
|
||||
recent block header. Behind the scenes, ``countercli`` will not only
|
||||
verify that this state matches the header, but also that the header is
|
||||
properly signed by the known validator set. It will even update the
|
||||
validator set as needed, so long as there have not been major changes
|
||||
and it is secure to do so. So, if you wonder why the query may take a
|
||||
second... there is a lot of work going on in the background to make sure
|
||||
even a lying full node can't trick your client.
|
||||
|
||||
Accounts and Transactions
|
||||
-------------------------
|
||||
|
||||
For a better understanding of how to further use the tools, it helps to
|
||||
understand the underlying data structures.
|
||||
|
||||
Accounts
|
||||
~~~~~~~~
|
||||
|
||||
The Basecoin state consists entirely of a set of accounts. Each account
|
||||
contains a public key, a balance in many different coin denominations,
|
||||
and a strictly increasing sequence number for replay protection. This
|
||||
type of account was directly inspired by accounts in Ethereum, and is
|
||||
unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). Note
|
||||
Basecoin is a multi-asset cryptocurrency, so each account can have many
|
||||
different kinds of tokens.
|
||||
|
||||
::
|
||||
|
||||
type Account struct {
|
||||
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
|
||||
Sequence int `json:"sequence"`
|
||||
Balance Coins `json:"coins"`
|
||||
}
|
||||
|
||||
type Coins []Coin
|
||||
|
||||
type Coin struct {
|
||||
Denom string `json:"denom"`
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
|
||||
If you want to add more coins to a blockchain, you can do so manually in
|
||||
the ``~/.basecoin/genesis.json`` before you start the blockchain for the
|
||||
first time.
|
||||
|
||||
Accounts are serialized and stored in a Merkle tree under the key
|
||||
``base/a/<address>``, where ``<address>`` is the address of the account.
|
||||
Typically, the address of the account is the 20-byte ``RIPEMD160`` hash
|
||||
of the public key, but other formats are acceptable as well, as defined
|
||||
in the `Tendermint crypto
|
||||
library <https://github.com/tendermint/go-crypto>`__. The Merkle tree
|
||||
used in Basecoin is a balanced, binary search tree, which we call an
|
||||
`IAVL tree <https://github.com/tendermint/iavl>`__.
|
||||
|
||||
Transactions
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Basecoin defines a transaction type, the ``SendTx``, which allows tokens
|
||||
to be sent to other accounts. The ``SendTx`` takes a list of inputs and
|
||||
a list of outputs, and transfers all the tokens listed in the inputs
|
||||
from their corresponding accounts to the accounts listed in the output.
|
||||
The ``SendTx`` is structured as follows:
|
||||
|
||||
::
|
||||
|
||||
type SendTx struct {
|
||||
Gas int64 `json:"gas"`
|
||||
Fee Coin `json:"fee"`
|
||||
Inputs []TxInput `json:"inputs"`
|
||||
Outputs []TxOutput `json:"outputs"`
|
||||
}
|
||||
|
||||
type TxInput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Coins Coins `json:"coins"` //
|
||||
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
|
||||
Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
|
||||
PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0
|
||||
}
|
||||
|
||||
type TxOutput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Coins Coins `json:"coins"` //
|
||||
}
|
||||
|
||||
Note the ``SendTx`` includes a field for ``Gas`` and ``Fee``. The
|
||||
``Gas`` limits the total amount of computation that can be done by the
|
||||
transaction, while the ``Fee`` refers to the total amount paid in fees.
|
||||
This is slightly different from Ethereum's concept of ``Gas`` and
|
||||
``GasPrice``, where ``Fee = Gas x GasPrice``. In Basecoin, the ``Gas``
|
||||
and ``Fee`` are independent, and the ``GasPrice`` is implicit.
|
||||
|
||||
In Basecoin, the ``Fee`` is meant to be used by the validators to inform
|
||||
the ordering of transactions, like in Bitcoin. And the ``Gas`` is meant
|
||||
to be used by the application plugin to control its execution. There is
|
||||
currently no means to pass ``Fee`` information to the Tendermint
|
||||
validators, but it will come soon...
|
||||
|
||||
Note also that the ``PubKey`` only needs to be sent for
|
||||
``Sequence == 0``. After that, it is stored under the account in the
|
||||
Merkle tree and subsequent transactions can exclude it, using only the
|
||||
``Address`` to refer to the sender. Ethereum does not require public
|
||||
keys to be sent in transactions as it uses a different elliptic curve
|
||||
scheme which enables the public key to be derived from the signature
|
||||
itself.
|
||||
|
||||
Finally, note that the use of multiple inputs and multiple outputs
|
||||
allows us to send many different types of tokens between many different
|
||||
accounts at once in an atomic transaction. Thus, the ``SendTx`` can
|
||||
serve as a basic unit of decentralized exchange. When using multiple
|
||||
inputs and outputs, you must make sure that the sum of coins of the
|
||||
inputs equals the sum of coins of the outputs (no creating money), and
|
||||
that all accounts that provide inputs have signed the transaction.
|
||||
|
||||
Clean Up
|
||||
--------
|
||||
|
||||
**WARNING:** Running these commands will wipe out any existing
|
||||
information in both the ``~/.basecli`` and ``~/.basecoin`` directories,
|
||||
including private keys.
|
||||
|
||||
To remove all the files created and refresh your environment (e.g., if
|
||||
starting this tutorial again or trying something new), the following
|
||||
commands are run:
|
||||
|
||||
::
|
||||
|
||||
basecli reset_all
|
||||
rm -rf ~/.basecoin
|
||||
|
||||
In this guide, we introduced the ``basecoin`` and ``basecli`` tools,
|
||||
demonstrated how to start a new basecoin blockchain and how to send
|
||||
tokens between accounts, and discussed the underlying data types for
|
||||
accounts and transactions, specifically the ``Account`` and the
|
||||
``SendTx``.
|
||||
@@ -1,215 +0,0 @@
|
||||
Basecoin Extensions
|
||||
===================
|
||||
|
||||
TODO: re-write for extensions
|
||||
|
||||
In the `previous guide <basecoin-basics.md>`__, we saw how to use the
|
||||
``basecoin`` tool to start a blockchain and the ``basecli`` tools to
|
||||
send transactions. We also learned about ``Account`` and ``SendTx``, the
|
||||
basic data types giving us a multi-asset cryptocurrency. Here, we will
|
||||
demonstrate how to extend the tools to use another transaction type, the
|
||||
``AppTx``, so we can send data to a custom plugin. In this example we
|
||||
explore a simple plugin named ``counter``.
|
||||
|
||||
Example Plugin
|
||||
--------------
|
||||
|
||||
The design of the ``basecoin`` tool makes it easy to extend for custom
|
||||
functionality. The Counter plugin is bundled with basecoin, so if you
|
||||
have already `installed basecoin <install.md>`__ and run
|
||||
``make install`` then you should be able to run a full node with
|
||||
``counter`` and the a light-client ``countercli`` from terminal. The
|
||||
Counter plugin is just like the ``basecoin`` tool. They both use the
|
||||
same library of commands, including one for signing and broadcasting
|
||||
``SendTx``.
|
||||
|
||||
Counter transactions take two custom inputs, a boolean argument named
|
||||
``valid``, and a coin amount named ``countfee``. The transaction is only
|
||||
accepted if both ``valid`` is set to true and the transaction input
|
||||
coins is greater than ``countfee`` that the user provides.
|
||||
|
||||
A new blockchain can be initialized and started just like in the
|
||||
`previous guide <basecoin-basics.md>`__:
|
||||
|
||||
::
|
||||
|
||||
# WARNING: this wipes out data - but counter is only for demos...
|
||||
rm -rf ~/.counter
|
||||
countercli reset_all
|
||||
|
||||
countercli keys new cool
|
||||
countercli keys new friend
|
||||
|
||||
counter init $(countercli keys get cool | awk '{print $2}')
|
||||
|
||||
counter start
|
||||
|
||||
The default files are stored in ``~/.counter``. In another window we can
|
||||
initialize the light-client and send a transaction:
|
||||
|
||||
::
|
||||
|
||||
countercli init --node=tcp://localhost:46657 --genesis=$HOME/.counter/genesis.json
|
||||
|
||||
YOU=$(countercli keys get friend | awk '{print $2}')
|
||||
countercli tx send --name=cool --amount=1000mycoin --to=$YOU --sequence=1
|
||||
|
||||
But the Counter has an additional command, ``countercli tx counter``,
|
||||
which crafts an ``AppTx`` specifically for this plugin:
|
||||
|
||||
::
|
||||
|
||||
countercli tx counter --name cool
|
||||
countercli tx counter --name cool --valid
|
||||
|
||||
The first transaction is rejected by the plugin because it was not
|
||||
marked as valid, while the second transaction passes. We can build
|
||||
plugins that take many arguments of different types, and easily extend
|
||||
the tool to accomodate them. Of course, we can also expose queries on
|
||||
our plugin:
|
||||
|
||||
::
|
||||
|
||||
countercli query counter
|
||||
|
||||
Tada! We can now see that our custom counter plugin transactions went
|
||||
through. You should see a Counter value of 1 representing the number of
|
||||
valid transactions. If we send another transaction, and then query
|
||||
again, we will see the value increment. Note that we need the sequence
|
||||
number here to send the coins (it didn't increment when we just pinged
|
||||
the counter)
|
||||
|
||||
::
|
||||
|
||||
countercli tx counter --name cool --countfee=2mycoin --sequence=2 --valid
|
||||
countercli query counter
|
||||
|
||||
The Counter value should be 2, because we sent a second valid
|
||||
transaction. And this time, since we sent a countfee (which must be less
|
||||
than or equal to the total amount sent with the tx), it stores the
|
||||
``TotalFees`` on the counter as well.
|
||||
|
||||
Keep it mind that, just like with ``basecli``, the ``countercli``
|
||||
verifies a proof that the query response is correct and up-to-date.
|
||||
|
||||
Now, before we implement our own plugin and tooling, it helps to
|
||||
understand the ``AppTx`` and the design of the plugin system.
|
||||
|
||||
AppTx
|
||||
-----
|
||||
|
||||
The ``AppTx`` is similar to the ``SendTx``, but instead of sending coins
|
||||
from inputs to outputs, it sends coins from one input to a plugin, and
|
||||
can also send some data.
|
||||
|
||||
::
|
||||
|
||||
type AppTx struct {
|
||||
Gas int64 `json:"gas"`
|
||||
Fee Coin `json:"fee"`
|
||||
Input TxInput `json:"input"`
|
||||
Name string `json:"type"` // Name of the plugin
|
||||
Data []byte `json:"data"` // Data for the plugin to process
|
||||
}
|
||||
|
||||
The ``AppTx`` enables Basecoin to be extended with arbitrary additional
|
||||
functionality through the use of plugins. The ``Name`` field in the
|
||||
``AppTx`` refers to the particular plugin which should process the
|
||||
transaction, and the ``Data`` field of the ``AppTx`` is the data to be
|
||||
forwarded to the plugin for processing.
|
||||
|
||||
Note the ``AppTx`` also has a ``Gas`` and ``Fee``, with the same meaning
|
||||
as for the ``SendTx``. It also includes a single ``TxInput``, which
|
||||
specifies the sender of the transaction, and some coins that can be
|
||||
forwarded to the plugin as well.
|
||||
|
||||
Plugins
|
||||
-------
|
||||
|
||||
A plugin is simply a Go package that implements the ``Plugin``
|
||||
interface:
|
||||
|
||||
::
|
||||
|
||||
type Plugin interface {
|
||||
|
||||
// Name of this plugin, should be short.
|
||||
Name() string
|
||||
|
||||
// Run a transaction from ABCI DeliverTx
|
||||
RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result)
|
||||
|
||||
// Other ABCI message handlers
|
||||
SetOption(store KVStore, key string, value string) (log string)
|
||||
InitChain(store KVStore, vals []*abci.Validator)
|
||||
BeginBlock(store KVStore, hash []byte, header *abci.Header)
|
||||
EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock)
|
||||
}
|
||||
|
||||
type CallContext struct {
|
||||
CallerAddress []byte // Caller's Address (hash of PubKey)
|
||||
CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted
|
||||
Coins Coins // The coins that the caller wishes to spend, excluding fees
|
||||
}
|
||||
|
||||
The workhorse of the plugin is ``RunTx``, which is called when an
|
||||
``AppTx`` is processed. The ``Data`` from the ``AppTx`` is passed in as
|
||||
the ``txBytes``, while the ``Input`` from the ``AppTx`` is used to
|
||||
populate the ``CallContext``.
|
||||
|
||||
Note that ``RunTx`` also takes a ``KVStore`` - this is an abstraction
|
||||
for the underlying Merkle tree which stores the account data. By passing
|
||||
this to the plugin, we enable plugins to update accounts in the Basecoin
|
||||
state directly, and also to store arbitrary other information in the
|
||||
state. In this way, the functionality and state of a Basecoin-derived
|
||||
cryptocurrency can be greatly extended. One could imagine going so far
|
||||
as to implement the Ethereum Virtual Machine as a plugin!
|
||||
|
||||
For details on how to initialize the state using ``SetOption``, see the
|
||||
`guide to using the basecoin tool <basecoin-tool.md#genesis>`__.
|
||||
|
||||
Implement your own
|
||||
------------------
|
||||
|
||||
To implement your own plugin and tooling, make a copy of
|
||||
``docs/guide/counter``, and modify the code accordingly. Here, we will
|
||||
briefly describe the design and the changes to be made, but see the code
|
||||
for more details.
|
||||
|
||||
First is the ``cmd/counter/main.go``, which drives the program. It can
|
||||
be left alone, but you should change any occurrences of ``counter`` to
|
||||
whatever your plugin tool is going to be called. You must also register
|
||||
your plugin(s) with the basecoin app with ``RegisterStartPlugin``.
|
||||
|
||||
The light-client is located in ``cmd/countercli/main.go`` and allows for
|
||||
transaction and query commands. This file can also be left mostly alone
|
||||
besides replacing the application name and adding references to new
|
||||
plugin commands.
|
||||
|
||||
Next is the custom commands in ``cmd/countercli/commands/``. These files
|
||||
are where we extend the tool with any new commands and flags we need to
|
||||
send transactions or queries to our plugin. You define custom ``tx`` and
|
||||
``query`` subcommands, which are registered in ``main.go`` (avoiding
|
||||
``init()`` auto-registration, for less magic and more control in the
|
||||
main executable).
|
||||
|
||||
Finally is ``plugins/counter/counter.go``, where we provide an
|
||||
implementation of the ``Plugin`` interface. The most important part of
|
||||
the implementation is the ``RunTx`` method, which determines the meaning
|
||||
of the data sent along in the ``AppTx``. In our example, we define a new
|
||||
transaction type, the ``CounterTx``, which we expect to be encoded in
|
||||
the ``AppTx.Data``, and thus to be decoded in the ``RunTx`` method, and
|
||||
used to update the plugin state.
|
||||
|
||||
For more examples and inspiration, see our `repository of example
|
||||
plugins <https://github.com/tendermint/basecoin-examples>`__.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
In this guide, we demonstrated how to create a new plugin and how to
|
||||
extend the ``basecoin`` tool to start a blockchain with the plugin
|
||||
enabled and send transactions to it. In the next guide, we introduce a
|
||||
`plugin for Inter Blockchain Communication <ibc.md>`__, which allows us
|
||||
to publish proofs of the state of one blockchain to another, and thus to
|
||||
transfer tokens and data between them.
|
||||
@@ -1,230 +0,0 @@
|
||||
Glossary
|
||||
========
|
||||
|
||||
This glossary defines many terms used throughout documentation of Quark.
|
||||
If there is every a concept that seems unclear, check here. This is
|
||||
mainly to provide a background and general understanding of the
|
||||
different words and concepts that are used. Other documents will explain
|
||||
in more detail how to combine these concepts to build a particular
|
||||
application.
|
||||
|
||||
Transaction
|
||||
-----------
|
||||
|
||||
A transaction is a packet of binary data that contains all information
|
||||
to validate and perform an action on the blockchain. The only other data
|
||||
that it interacts with is the current state of the chain (key-value
|
||||
store), and it must have a deterministic action. The transaction is the
|
||||
main piece of one request.
|
||||
|
||||
We currently make heavy use of
|
||||
`go-amino <https://github.com/tendermint/go-amino>`__ to
|
||||
provide binary and json encodings and decodings for ``struct`` or
|
||||
interface\ ``objects. Here, encoding and decoding operations are designed to operate with interfaces nested any amount times (like an onion!). There is one public``\ TxMapper\`
|
||||
in the basecoin root package, and all modules can register their own
|
||||
transaction types there. This allows us to deserialize the entire
|
||||
transaction in one location (even with types defined in other repos), to
|
||||
easily embed an arbitrary transaction inside another without specifying
|
||||
the type, and provide an automatic json representation allowing for
|
||||
users (or apps) to inspect the chain.
|
||||
|
||||
Note how we can wrap any other transaction, add a fee level, and not
|
||||
worry about the encoding in our module any more?
|
||||
|
||||
::
|
||||
|
||||
type Fee struct {
|
||||
Fee coin.Coin `json:"fee"`
|
||||
Payer basecoin.Actor `json:"payer"` // the address who pays the fee
|
||||
Tx basecoin.Tx `json:"tx"`
|
||||
}
|
||||
|
||||
Context (ctx)
|
||||
-------------
|
||||
|
||||
As a request passes through the system, it may pick up information such
|
||||
as the block height the request runs at. In order to carry this information
|
||||
between modules it is saved to the context. Further, all information
|
||||
must be deterministic from the context in which the request runs (based
|
||||
on the transaction and the block it was included in) and can be used to
|
||||
validate the transaction.
|
||||
|
||||
Data Store
|
||||
----------
|
||||
|
||||
In order to provide proofs to Tendermint, we keep all data in one
|
||||
key-value (kv) store which is indexed with a merkle tree. This allows
|
||||
for the easy generation of a root hash and proofs for queries without
|
||||
requiring complex logic inside each module. Standardization of this
|
||||
process also allows powerful light-client tooling as any store data may
|
||||
be verified on the fly.
|
||||
|
||||
The largest limitation of the current implemenation of the kv-store is
|
||||
that interface that the application must use can only ``Get`` and
|
||||
``Set`` single data points. That said, there are some data structures
|
||||
like queues and range queries that are available in ``state`` package.
|
||||
These provide higher-level functionality in a standard format, but have
|
||||
not yet been integrated into the kv-store interface.
|
||||
|
||||
Isolation
|
||||
---------
|
||||
|
||||
One of the main arguments for blockchain is security. So while we
|
||||
encourage the use of third-party modules, all developers must be
|
||||
vigilant against security holes. If you use the
|
||||
`stack <https://github.com/cosmos/cosmos-sdk/tree/master/stack>`__
|
||||
package, it will provide two different types of compartmentalization
|
||||
security.
|
||||
|
||||
The first is to limit the working kv-store space of each module. When
|
||||
``DeliverTx`` is called for a module, it is never given the entire data
|
||||
store, but rather only its own prefixed subset of the store. This is
|
||||
achieved by prefixing all keys transparently with
|
||||
``<module name> + 0x0``, using the null byte as a separator. Since the
|
||||
module name must be a string, no malicious naming scheme can ever lead
|
||||
to a collision. Inside a module, we can write using any key value we
|
||||
desire without the possibility that we have modified data belonging to
|
||||
separate module.
|
||||
|
||||
The second is to add permissions to the transaction context. The
|
||||
transaction context can specify that the tx has been signed by one or
|
||||
multiple specific actors.
|
||||
|
||||
A transactions will only be executed if the permission requirements have
|
||||
been fulfilled. For example the sender of funds must have signed, or 2
|
||||
out of 3 multi-signature actors must have signed a joint account. To
|
||||
prevent the forgery of account signatures from unintended modules each
|
||||
permission is associated with the module that granted it (in this case
|
||||
`auth <https://github.com/cosmos/cosmos-sdk/tree/master/x/auth>`__),
|
||||
and if a module tries to add a permission for another module, it will
|
||||
panic. There is also protection if a module creates a brand new fake
|
||||
context to trick the downstream modules. Each context enforces the rules
|
||||
on how to make child contexts, and the stack builder enforces
|
||||
that the context passed from one level to the next is a valid child of
|
||||
the original one.
|
||||
|
||||
These security measures ensure that modules can confidently write to
|
||||
their local section of the database and trust the permissions associated
|
||||
with the context, without concern of interference from other modules.
|
||||
(Okay, if you see a bunch of C-code in the module traversing through all
|
||||
the memory space of the application, then get worried....)
|
||||
|
||||
Handler
|
||||
-------
|
||||
|
||||
The ABCI interface is handled by ``app``, which translates these data
|
||||
structures into an internal format that is more convenient, but unable
|
||||
to travel over the wire. The basic interface for any code that modifies
|
||||
state is the ``Handler`` interface, which provides four methods:
|
||||
|
||||
::
|
||||
|
||||
Name() string
|
||||
CheckTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
|
||||
DeliverTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
|
||||
SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error)
|
||||
|
||||
Note the ``Context``, ``KVStore``, and ``Tx`` as principal carriers of
|
||||
information. And that Result is always success, and we have a second
|
||||
error return for errors (which is much more standard golang that
|
||||
``res.IsErr()``)
|
||||
|
||||
The ``Handler`` interface is designed to be the basis for all modules
|
||||
that execute transactions, and this can provide a large degree of code
|
||||
interoperability, much like ``http.Handler`` does in golang web
|
||||
development.
|
||||
|
||||
Modules
|
||||
-------
|
||||
|
||||
TODO: update (s/Modules/handlers+mappers+stores/g) & add Msg + Tx (a signed message)
|
||||
|
||||
A module is a set of functionality which should be typically designed as
|
||||
self-sufficient. Common elements of a module are:
|
||||
|
||||
- transaction types (either end transactions, or transaction wrappers)
|
||||
- custom error codes
|
||||
- data models (to persist in the kv-store)
|
||||
- handler (to handle any end transactions)
|
||||
|
||||
Dispatcher
|
||||
----------
|
||||
|
||||
We usually will want to have multiple modules working together, and need
|
||||
to make sure the correct transactions get to the correct module. So we
|
||||
have ``coin`` sending money, ``roles`` to create multi-sig accounts, and
|
||||
``ibc`` for following other chains all working together without
|
||||
interference.
|
||||
|
||||
We can then register a ``Dispatcher``, which
|
||||
also implements the ``Handler`` interface. We then register a list of
|
||||
modules with the dispatcher. Every module has a unique ``Name()``, which
|
||||
is used for isolating its state space. We use this same name for routing
|
||||
transactions. Each transaction implementation must be registed with
|
||||
go-amino via ``TxMapper``, so we just look at the registered name of this
|
||||
transaction, which should be of the form ``<module name>/xxx``. The
|
||||
dispatcher grabs the appropriate module name from the tx name and routes
|
||||
it if the module is present.
|
||||
|
||||
This all seems like a bit of magic, but really we're just making use of
|
||||
go-amino magic that we are already using, rather than add another layer.
|
||||
For all the transactions to be properly routed, the only thing you need
|
||||
to remember is to use the following pattern:
|
||||
|
||||
::
|
||||
|
||||
const (
|
||||
NameCoin = "coin"
|
||||
TypeSend = NameCoin + "/send"
|
||||
)
|
||||
|
||||
Permissions
|
||||
-----------
|
||||
|
||||
TODO: replaces perms with object capabilities/object capability keys
|
||||
- get rid of IPC
|
||||
|
||||
IPC requires a more complex permissioning system to allow the modules to
|
||||
have limited access to each other and also to allow more types of
|
||||
permissions than simple public key signatures. Rather than just use an
|
||||
address to identify who is performing an action, we can use a more
|
||||
complex structure:
|
||||
|
||||
::
|
||||
|
||||
type Actor struct {
|
||||
ChainID string `json:"chain"` // this is empty unless it comes from a different chain
|
||||
App string `json:"app"` // the app that the actor belongs to
|
||||
Address data.Bytes `json:"addr"` // arbitrary app-specific unique id
|
||||
}
|
||||
|
||||
Here, the ``Actor`` abstracts any address that can authorize actions,
|
||||
hold funds, or initiate any sort of transaction. It doesn't just have to
|
||||
be a pubkey on this chain, it could stem from another app (such as
|
||||
multi-sig account), or even another chain (via IBC)
|
||||
|
||||
``ChainID`` is for IBC, discussed below. Let's focus on ``App`` and
|
||||
``Address``. For a signature, the App is ``auth``, and any modules can
|
||||
check to see if a specific public key address signed like this
|
||||
``ctx.HasPermission(auth.SigPerm(addr))``. However, we can also
|
||||
authorize a tx with ``roles``, which handles multi-sig accounts, it
|
||||
checks if there were enough signatures by checking as above, then it can
|
||||
add the role permission like
|
||||
``ctx= ctx.WithPermissions(NewPerm(assume.Role))``
|
||||
|
||||
In addition to the permissions schema, the Actors are addresses just
|
||||
like public key addresses. So one can create a mulit-sig role, then send
|
||||
coin there, which can only be moved upon meeting the authorization
|
||||
requirements from that module. ``coin`` doesn't even know the existence
|
||||
of ``roles`` and one could build any other sort of module to provide
|
||||
permissions (like bind the outcome of an election to move coins or to
|
||||
modify the accounts on a role).
|
||||
|
||||
One idea - not yet implemented - is to provide scopes on the
|
||||
permissions. Currently, if I sign a transaction to one module, it can
|
||||
pass it on to any other module over IPC with the same permissions. It
|
||||
could move coins, vote in an election, or anything else. Ideally, when
|
||||
signing, one could also specify the scope(s) that this signature
|
||||
authorizes. The `oauth
|
||||
protocol <https://api.slack.com/docs/oauth-scopes>`__ also has to deal
|
||||
with a similar problem, and maybe could provide some inspiration.
|
||||
@@ -1,424 +0,0 @@
|
||||
IBC
|
||||
===
|
||||
|
||||
TODO: update in light of latest SDK (this document is currently out of date)
|
||||
|
||||
One of the most exciting elements of the Cosmos Network is the
|
||||
InterBlockchain Communication (IBC) protocol, which enables
|
||||
interoperability across different blockchains. We implemented IBC as a
|
||||
basecoin plugin, and we'll show you how to use it to send tokens across
|
||||
blockchains!
|
||||
|
||||
Please note: this tutorial assumes familiarity with the Cosmos SDK.
|
||||
|
||||
The IBC plugin defines a new set of transactions as subtypes of the
|
||||
``AppTx``. The plugin's functionality is accessed by setting the
|
||||
``AppTx.Name`` field to ``"IBC"``, and setting the ``Data`` field to the
|
||||
serialized IBC transaction type.
|
||||
|
||||
We'll demonstrate exactly how this works below.
|
||||
|
||||
Inter BlockChain Communication
|
||||
------------------------------
|
||||
|
||||
Let's review the IBC protocol. The purpose of IBC is to enable one
|
||||
blockchain to function as a light-client of another. Since we are using
|
||||
a classical Byzantine Fault Tolerant consensus algorithm, light-client
|
||||
verification is cheap and easy: all we have to do is check validator
|
||||
signatures on the latest block, and verify a Merkle proof of the state.
|
||||
|
||||
In Tendermint, validators agree on a block before processing it. This
|
||||
means that the signatures and state root for that block aren't included
|
||||
until the next block. Thus, each block contains a field called
|
||||
``LastCommit``, which contains the votes responsible for committing the
|
||||
previous block, and a field in the block header called ``AppHash``,
|
||||
which refers to the Merkle root hash of the application after processing
|
||||
the transactions from the previous block. So, if we want to verify the
|
||||
``AppHash`` from height H, we need the signatures from ``LastCommit`` at
|
||||
height H+1. (And remember that this ``AppHash`` only contains the
|
||||
results from all transactions up to and including block H-1)
|
||||
|
||||
Unlike Proof-of-Work, the light-client protocol does not need to
|
||||
download and check all the headers in the blockchain - the client can
|
||||
always jump straight to the latest header available, so long as the
|
||||
validator set has not changed much. If the validator set is changing,
|
||||
the client needs to track these changes, which requires downloading
|
||||
headers for each block in which there is a significant change. Here, we
|
||||
will assume the validator set is constant, and postpone handling
|
||||
validator set changes for another time.
|
||||
|
||||
Now we can describe exactly how IBC works. Suppose we have two
|
||||
blockchains, ``chain1`` and ``chain2``, and we want to send some data
|
||||
from ``chain1`` to ``chain2``. We need to do the following: 1. Register
|
||||
the details (ie. chain ID and genesis configuration) of ``chain1`` on
|
||||
``chain2`` 2. Within ``chain1``, broadcast a transaction that creates an
|
||||
outgoing IBC packet destined for ``chain2`` 3. Broadcast a transaction
|
||||
to ``chain2`` informing it of the latest state (ie. header and commit
|
||||
signatures) of ``chain1`` 4. Post the outgoing packet from ``chain1`` to
|
||||
``chain2``, including the proof that it was indeed committed on
|
||||
``chain1``. Note ``chain2`` can only verify this proof because it has a
|
||||
recent header and commit.
|
||||
|
||||
Each of these steps involves a separate IBC transaction type. Let's take
|
||||
them up in turn.
|
||||
|
||||
IBCRegisterChainTx
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``IBCRegisterChainTx`` is used to register one chain on another. It
|
||||
contains the chain ID and genesis configuration of the chain to
|
||||
register:
|
||||
|
||||
::
|
||||
|
||||
type IBCRegisterChainTx struct { BlockchainGenesis }
|
||||
|
||||
type BlockchainGenesis struct { ChainID string Genesis string }
|
||||
|
||||
This transaction should only be sent once for a given chain ID, and
|
||||
successive sends will return an error.
|
||||
|
||||
IBCUpdateChainTx
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``IBCUpdateChainTx`` is used to update the state of one chain on
|
||||
another. It contains the header and commit signatures for some block in
|
||||
the chain:
|
||||
|
||||
::
|
||||
|
||||
type IBCUpdateChainTx struct {
|
||||
Header tm.Header
|
||||
Commit tm.Commit
|
||||
}
|
||||
|
||||
In the future, it needs to be updated to include changes to the
|
||||
validator set as well. Anyone can relay an ``IBCUpdateChainTx``, and
|
||||
they only need to do so as frequently as packets are being sent or the
|
||||
validator set is changing.
|
||||
|
||||
IBCPacketCreateTx
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``IBCPacketCreateTx`` is used to create an outgoing packet on one
|
||||
chain. The packet itself contains the source and destination chain IDs,
|
||||
a sequence number (i.e. an integer that increments with every message
|
||||
sent between this pair of chains), a packet type (e.g. coin, data,
|
||||
etc.), and a payload.
|
||||
|
||||
::
|
||||
|
||||
type IBCPacketCreateTx struct {
|
||||
Packet
|
||||
}
|
||||
|
||||
type Packet struct {
|
||||
SrcChainID string
|
||||
DstChainID string
|
||||
Sequence uint64
|
||||
Type string
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
We have yet to define the format for the payload, so, for now, it's just
|
||||
arbitrary bytes.
|
||||
|
||||
One way to think about this is that ``chain2`` has an account on
|
||||
``chain1``. With a ``IBCPacketCreateTx`` on ``chain1``, we send funds to
|
||||
that account. Then we can prove to ``chain2`` that there are funds
|
||||
locked up for it in it's account on ``chain1``. Those funds can only be
|
||||
unlocked with corresponding IBC messages back from ``chain2`` to
|
||||
``chain1`` sending the locked funds to another account on ``chain1``.
|
||||
|
||||
IBCPacketPostTx
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The ``IBCPacketPostTx`` is used to post an outgoing packet from one
|
||||
chain to another. It contains the packet and a proof that the packet was
|
||||
committed into the state of the sending chain:
|
||||
|
||||
::
|
||||
|
||||
type IBCPacketPostTx struct {
|
||||
FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
|
||||
FromChainHeight uint64 // The block height in which Packet was committed, to check Proof Packet
|
||||
Proof *merkle.IAVLProof
|
||||
}
|
||||
|
||||
The proof is a Merkle proof in an IAVL tree, our implementation of a
|
||||
balanced, Merklized binary search tree. It contains a list of nodes in
|
||||
the tree, which can be hashed together to get the Merkle root hash. This
|
||||
hash must match the ``AppHash`` contained in the header at
|
||||
``FromChainHeight + 1``
|
||||
|
||||
- note the ``+ 1`` is necessary since ``FromChainHeight`` is the height
|
||||
in which the packet was committed, and the resulting state root is
|
||||
not included until the next block.
|
||||
|
||||
IBC State
|
||||
~~~~~~~~~
|
||||
|
||||
Now that we've seen all the transaction types, let's talk about the
|
||||
state. Each chain stores some IBC state in its Merkle tree. For each
|
||||
chain being tracked by our chain, we store:
|
||||
|
||||
- Genesis configuration
|
||||
- Latest state
|
||||
- Headers for recent heights
|
||||
|
||||
We also store all incoming (ingress) and outgoing (egress) packets.
|
||||
|
||||
The state of a chain is updated every time an ``IBCUpdateChainTx`` is
|
||||
committed. New packets are added to the egress state upon
|
||||
``IBCPacketCreateTx``. New packets are added to the ingress state upon
|
||||
``IBCPacketPostTx``, assuming the proof checks out.
|
||||
|
||||
Merkle Queries
|
||||
--------------
|
||||
|
||||
The Basecoin application uses a single Merkle tree that is shared across
|
||||
all its state, including the built-in accounts state and all plugin
|
||||
state. For this reason, it's important to use explicit key names and/or
|
||||
hashes to ensure there are no collisions.
|
||||
|
||||
We can query the Merkle tree using the ABCI Query method. If we pass in
|
||||
the correct key, it will return the corresponding value, as well as a
|
||||
proof that the key and value are contained in the Merkle tree.
|
||||
|
||||
The results of a query can thus be used as proof in an
|
||||
``IBCPacketPostTx``.
|
||||
|
||||
Relay
|
||||
-----
|
||||
|
||||
While we need all these packet types internally to keep track of all the
|
||||
proofs on both chains in a secure manner, for the normal work-flow, we
|
||||
can run a relay node that handles the cross-chain interaction.
|
||||
|
||||
In this case, there are only two steps. First ``basecoin relay init``,
|
||||
which must be run once to register each chain with the other one, and
|
||||
make sure they are ready to send and recieve. And then
|
||||
``basecoin relay start``, which is a long-running process polling the
|
||||
queue on each side, and relaying all new message to the other block.
|
||||
|
||||
This requires that the relay has access to accounts with some funds on
|
||||
both chains to pay for all the ibc packets it will be forwarding.
|
||||
|
||||
Try it out
|
||||
----------
|
||||
|
||||
Now that we have all the background knowledge, let's actually walk
|
||||
through the tutorial.
|
||||
|
||||
Make sure you have installed `basecoin and
|
||||
basecli </docs/guide/install.md>`__.
|
||||
|
||||
Basecoin is a framework for creating new cryptocurrency applications. It
|
||||
comes with an ``IBC`` plugin enabled by default.
|
||||
|
||||
You will also want to install the
|
||||
`jq <https://stedolan.github.io/jq/>`__ for handling JSON at the command
|
||||
line.
|
||||
|
||||
If you have any trouble with this, you can also look at the `test
|
||||
scripts </tests/cli/ibc.sh>`__ or just run ``make test_cli`` in basecoin
|
||||
repo. Otherwise, open up 5 (yes 5!) terminal tabs....
|
||||
|
||||
Preliminaries
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
# first, clean up any old garbage for a fresh slate...
|
||||
rm -rf ~/.ibcdemo/
|
||||
|
||||
Let's start by setting up some environment variables and aliases:
|
||||
|
||||
::
|
||||
|
||||
export BCHOME1_CLIENT=~/.ibcdemo/chain1/client
|
||||
export BCHOME1_SERVER=~/.ibcdemo/chain1/server
|
||||
export BCHOME2_CLIENT=~/.ibcdemo/chain2/client
|
||||
export BCHOME2_SERVER=~/.ibcdemo/chain2/server
|
||||
alias basecli1="basecli --home $BCHOME1_CLIENT"
|
||||
alias basecli2="basecli --home $BCHOME2_CLIENT"
|
||||
alias basecoin1="basecoin --home $BCHOME1_SERVER"
|
||||
alias basecoin2="basecoin --home $BCHOME2_SERVER"
|
||||
|
||||
This will give us some new commands to use instead of raw ``basecli``
|
||||
and ``basecoin`` to ensure we're using the right configuration for the
|
||||
chain we want to talk to.
|
||||
|
||||
We also want to set some chain IDs:
|
||||
|
||||
::
|
||||
|
||||
export CHAINID1="test-chain-1"
|
||||
export CHAINID2="test-chain-2"
|
||||
|
||||
And since we will run two different chains on one machine, we need to
|
||||
maintain different sets of ports:
|
||||
|
||||
::
|
||||
|
||||
export PORT_PREFIX1=1234
|
||||
export PORT_PREFIX2=2345
|
||||
export RPC_PORT1=${PORT_PREFIX1}7
|
||||
export RPC_PORT2=${PORT_PREFIX2}7
|
||||
|
||||
Setup Chain 1
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Now, let's create some keys that we can use for accounts on
|
||||
test-chain-1:
|
||||
|
||||
::
|
||||
|
||||
basecli1 keys new money
|
||||
basecli1 keys new gotnone
|
||||
export MONEY=$(basecli1 keys get money | awk '{print $2}')
|
||||
export GOTNONE=$(basecli1 keys get gotnone | awk '{print $2}')
|
||||
|
||||
and create an initial configuration giving lots of coins to the $MONEY
|
||||
key:
|
||||
|
||||
::
|
||||
|
||||
basecoin1 init --chain-id $CHAINID1 $MONEY
|
||||
|
||||
Now start basecoin:
|
||||
|
||||
::
|
||||
|
||||
sed -ie "s/4665/$PORT_PREFIX1/" $BCHOME1_SERVER/config.toml
|
||||
|
||||
basecoin1 start &> basecoin1.log &
|
||||
|
||||
Note the ``sed`` command to replace the ports in the config file. You
|
||||
can follow the logs with ``tail -f basecoin1.log``
|
||||
|
||||
Now we can attach the client to the chain and verify the state. The
|
||||
first account should have money, the second none:
|
||||
|
||||
::
|
||||
|
||||
basecli1 init --node=tcp://localhost:${RPC_PORT1} --genesis=${BCHOME1_SERVER}/genesis.json
|
||||
basecli1 query account $MONEY
|
||||
basecli1 query account $GOTNONE
|
||||
|
||||
Setup Chain 2
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This is the same as above, except with ``basecli2``, ``basecoin2``, and
|
||||
``$CHAINID2``. We will also need to change the ports, since we're
|
||||
running another chain on the same local machine.
|
||||
|
||||
Let's create new keys for test-chain-2:
|
||||
|
||||
::
|
||||
|
||||
basecli2 keys new moremoney
|
||||
basecli2 keys new broke
|
||||
MOREMONEY=$(basecli2 keys get moremoney | awk '{print $2}')
|
||||
BROKE=$(basecli2 keys get broke | awk '{print $2}')
|
||||
|
||||
And prepare the genesis block, and start the server:
|
||||
|
||||
::
|
||||
|
||||
basecoin2 init --chain-id $CHAINID2 $(basecli2 keys get moremoney | awk '{print $2}')
|
||||
|
||||
sed -ie "s/4665/$PORT_PREFIX2/" $BCHOME2_SERVER/config.toml
|
||||
|
||||
basecoin2 start &> basecoin2.log &
|
||||
|
||||
Now attach the client to the chain and verify the state. The first
|
||||
account should have money, the second none:
|
||||
|
||||
::
|
||||
|
||||
basecli2 init --node=tcp://localhost:${RPC_PORT2} --genesis=${BCHOME2_SERVER}/genesis.json
|
||||
basecli2 query account $MOREMONEY
|
||||
basecli2 query account $BROKE
|
||||
|
||||
Connect these chains
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OK! So we have two chains running on your local machine, with different
|
||||
keys on each. Let's hook them up together by starting a relay process to
|
||||
forward messages from one chain to the other.
|
||||
|
||||
The relay account needs some money in it to pay for the ibc messages, so
|
||||
for now, we have to transfer some cash from the rich accounts before we
|
||||
start the actual relay.
|
||||
|
||||
::
|
||||
|
||||
# note that this key.json file is a hardcoded demo for all chains, this will
|
||||
# be updated in a future release
|
||||
RELAY_KEY=$BCHOME1_SERVER/key.json
|
||||
RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \")
|
||||
|
||||
basecli1 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR--name=money
|
||||
basecli1 query account $RELAY_ADDR
|
||||
|
||||
basecli2 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR --name=moremoney
|
||||
basecli2 query account $RELAY_ADDR
|
||||
|
||||
Now we can start the relay process.
|
||||
|
||||
::
|
||||
|
||||
basecoin relay init --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \
|
||||
--chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \
|
||||
--genesis1=${BCHOME1_SERVER}/genesis.json --genesis2=${BCHOME2_SERVER}/genesis.json \
|
||||
--from=$RELAY_KEY
|
||||
|
||||
basecoin relay start --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \
|
||||
--chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \
|
||||
--from=$RELAY_KEY &> relay.log &
|
||||
|
||||
This should start up the relay, and assuming no error messages came out,
|
||||
the two chains are now fully connected over IBC. Let's use this to send
|
||||
our first tx accross the chains...
|
||||
|
||||
Sending cross-chain payments
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The hard part is over, we set up two blockchains, a few private keys,
|
||||
and a secure relay between them. Now we can enjoy the fruits of our
|
||||
labor...
|
||||
|
||||
::
|
||||
|
||||
# Here's an empty account on test-chain-2
|
||||
basecli2 query account $BROKE
|
||||
|
||||
::
|
||||
|
||||
# Let's send some funds from test-chain-1
|
||||
basecli1 tx send --amount=12345mycoin --sequence=2 --to=test-chain-2/$BROKE --name=money
|
||||
|
||||
::
|
||||
|
||||
# give it time to arrive...
|
||||
sleep 2
|
||||
# now you should see 12345 coins!
|
||||
basecli2 query account $BROKE
|
||||
|
||||
You're no longer broke! Cool, huh? Now have fun exploring and sending
|
||||
coins across the chains. And making more accounts as you want to.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
In this tutorial we explained how IBC works, and demonstrated how to use
|
||||
it to communicate between two chains. We did the simplest communciation
|
||||
possible: a one way transfer of data from chain1 to chain2. The most
|
||||
important part was that we updated chain2 with the latest state (i.e.
|
||||
header and commit) of chain1, and then were able to post a proof to
|
||||
chain2 that a packet was committed to the outgoing state of chain1.
|
||||
|
||||
In a future tutorial, we will demonstrate how to use IBC to actually
|
||||
transfer tokens between two blockchains, but we'll do it with real
|
||||
testnets deployed across multiple nodes on the network. Stay tuned!
|
||||
@@ -1,119 +0,0 @@
|
||||
# Keys CLI
|
||||
|
||||
**WARNING: out-of-date and parts are wrong.... please update**
|
||||
|
||||
This is as much an example how to expose cobra/viper, as for a cli itself
|
||||
(I think this code is overkill for what go-keys needs). But please look at
|
||||
the commands, and give feedback and changes.
|
||||
|
||||
`RootCmd` calls some initialization functions (`cobra.OnInitialize` and `RootCmd.PersistentPreRunE`) which serve to connect environmental variables and cobra flags, as well as load the config file. It also validates the flags registered on root and creates the cryptomanager, which will be used by all subcommands.
|
||||
|
||||
## Help info
|
||||
|
||||
```
|
||||
# keys help
|
||||
|
||||
Keys allows you to manage your local keystore for tendermint.
|
||||
|
||||
These keys may be in any format supported by go-crypto and can be
|
||||
used by light-clients, full nodes, or any other application that
|
||||
needs to sign with a private key.
|
||||
|
||||
Usage:
|
||||
keys [command]
|
||||
|
||||
Available Commands:
|
||||
get Get details of one key
|
||||
list List all keys
|
||||
new Create a new public/private key pair
|
||||
serve Run the key manager as an http server
|
||||
update Change the password for a private key
|
||||
|
||||
Flags:
|
||||
--keydir string Directory to store private keys (subdir of root) (default "keys")
|
||||
-o, --output string Output format (text|json) (default "text")
|
||||
-r, --root string root directory for config and data (default "/Users/ethan/.tlc")
|
||||
|
||||
Use "keys [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## Getting the config file
|
||||
|
||||
The first step is to load in root, by checking the following in order:
|
||||
|
||||
* -r, --root command line flag
|
||||
* TM_ROOT environmental variable
|
||||
* default ($HOME/.tlc evaluated at runtime)
|
||||
|
||||
Once the `rootDir` is established, the script looks for a config file named `keys.{json,toml,yaml,hcl}` in that directory and parses it. These values will provide defaults for flags of the same name.
|
||||
|
||||
There is an example config file for testing out locally, which writes keys to `./.mykeys`. You can
|
||||
|
||||
## Getting/Setting variables
|
||||
|
||||
When we want to get the value of a user-defined variable (eg. `output`), we can call `viper.GetString("output")`, which will do the following checks, until it finds a match:
|
||||
|
||||
* Is `--output` command line flag present?
|
||||
* Is `TM_OUTPUT` environmental variable set?
|
||||
* Was a config file found and does it have an `output` variable?
|
||||
* Is there a default set on the command line flag?
|
||||
|
||||
If no variable is set and there was no default, we get back "".
|
||||
|
||||
This setup allows us to have powerful command line flags, but use env variables or config files (local or 12-factor style) to avoid passing these arguments every time.
|
||||
|
||||
## Nesting structures
|
||||
|
||||
Sometimes we don't just need key-value pairs, but actually a multi-level config file, like
|
||||
|
||||
```
|
||||
[mail]
|
||||
from = "no-reply@example.com"
|
||||
server = "mail.example.com"
|
||||
port = 567
|
||||
password = "XXXXXX"
|
||||
```
|
||||
|
||||
This CLI is too simple to warant such a structure, but I think eg. tendermint could benefit from such an approach. Here are some pointers:
|
||||
|
||||
* [Accessing nested keys from config files](https://github.com/spf13/viper#accessing-nested-keys)
|
||||
* [Overriding nested values with envvars](https://www.netlify.com/blog/2016/09/06/creating-a-microservice-boilerplate-in-go/#nested-config-values) - the mentioned outstanding PR is already merged into master!
|
||||
* Overriding nested values with cli flags? (use `--log_config.level=info` ??)
|
||||
|
||||
I'd love to see an example of this fully worked out in a more complex CLI.
|
||||
|
||||
## Have your cake and eat it too
|
||||
|
||||
It's easy to render data different ways. Some better for viewing, some better for importing to other programs. You can just add some global (persistent) flags to control the output formatting, and everyone gets what they want.
|
||||
|
||||
```
|
||||
# keys list -e hex
|
||||
All keys:
|
||||
betty d0789984492b1674e276b590d56b7ae077f81adc
|
||||
john b77f4720b220d1411a649b6c7f1151eb6b1c226a
|
||||
|
||||
# keys list -e btc
|
||||
All keys:
|
||||
betty 3uTF4r29CbtnzsNHZoPSYsE4BDwH
|
||||
john 3ZGp2Md35iw4XVtRvZDUaAEkCUZP
|
||||
|
||||
# keys list -e b64 -o json
|
||||
[
|
||||
{
|
||||
"name": "betty",
|
||||
"address": "0HiZhEkrFnTidrWQ1Wt64Hf4Gtw=",
|
||||
"pubkey": {
|
||||
"type": "secp256k1",
|
||||
"data": "F83WvhT0KwttSoqQqd_0_r2ztUUaQix5EXdO8AZyREoV31Og780NW59HsqTAb2O4hZ-w-j0Z-4b2IjfdqqfhVQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "john",
|
||||
"address": "t39HILIg0UEaZJtsfxFR62scImo=",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "t1LFmbg_8UTwj-n1wkqmnTp6NfaOivokEhlYySlGYCY="
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
Replay Protection
|
||||
-----------------
|
||||
|
||||
In order to prevent `replay
|
||||
attacks <https://en.wikipedia.org/wiki/Replay_attack>`__ a multi account
|
||||
nonce system has been constructed as a module, which can be found in
|
||||
``modules/nonce``. By adding the nonce module to the stack, each
|
||||
transaction is verified for authenticity against replay attacks. This is
|
||||
achieved by requiring that a new signed copy of the sequence number
|
||||
which must be exactly 1 greater than the sequence number of the previous
|
||||
transaction. A distinct sequence number is assigned per chain-id,
|
||||
application, and group of signers. Each sequence number is tracked as a
|
||||
nonce-store entry where the key is the marshaled list of actors after
|
||||
having been sorted by chain, app, and address.
|
||||
|
||||
.. code:: golang
|
||||
|
||||
// Tx - Nonce transaction structure, contains list of signers and current sequence number
|
||||
type Tx struct {
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Signers []basecoin.Actor `json:"signers"`
|
||||
Tx basecoin.Tx `json:"tx"`
|
||||
}
|
||||
|
||||
By distinguishing sequence numbers across groups of Signers,
|
||||
multi-signature Actors need not lock up use of their Address while
|
||||
waiting for all the members of a multi-sig transaction to occur. Instead
|
||||
only the multi-sig account will be locked, while other accounts
|
||||
belonging to that signer can be used and signed with other sequence
|
||||
numbers.
|
||||
|
||||
By abstracting out the nonce module in the stack, entire series of
|
||||
transactions can occur without needing to verify the nonce for each
|
||||
member of the series. An common example is a stack which will send coins
|
||||
and charge a fee. Within the SDK this can be achieved using separate
|
||||
modules in a stack, one to send the coins and the other to charge the
|
||||
fee, however both modules do not need to check the nonce. This can occur
|
||||
as a separate module earlier in the stack.
|
||||
@@ -1,204 +0,0 @@
|
||||
Key Management
|
||||
==============
|
||||
|
||||
Here we explain a bit how to work with your keys, using the
|
||||
``gaia client keys`` subcommand.
|
||||
|
||||
**Note:** This keys tooling is not considered production ready and is
|
||||
for dev only.
|
||||
|
||||
We'll look at what you can do using the six sub-commands of
|
||||
``gaia client keys``:
|
||||
|
||||
::
|
||||
|
||||
new
|
||||
list
|
||||
get
|
||||
delete
|
||||
recover
|
||||
update
|
||||
|
||||
Create keys
|
||||
-----------
|
||||
|
||||
``gaia client keys new`` has two inputs (name, password) and two outputs
|
||||
(address, seed).
|
||||
|
||||
First, we name our key:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys new alice
|
||||
|
||||
This will prompt (10 character minimum) password entry which must be
|
||||
re-typed. You'll see:
|
||||
|
||||
::
|
||||
|
||||
Enter a passphrase:
|
||||
Repeat the passphrase:
|
||||
alice A159C96AE911F68913E715ED889D211C02EC7D70
|
||||
**Important** write this seed phrase in a safe place.
|
||||
It is the only way to recover your account if you ever forget your password.
|
||||
|
||||
pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset
|
||||
|
||||
which shows the address of your key named ``alice``, and its recovery
|
||||
seed. We'll use these shortly.
|
||||
|
||||
Adding the ``--output json`` flag to the above command would give this
|
||||
output:
|
||||
|
||||
::
|
||||
|
||||
Enter a passphrase:
|
||||
Repeat the passphrase:
|
||||
{
|
||||
"key": {
|
||||
"name": "alice",
|
||||
"address": "A159C96AE911F68913E715ED889D211C02EC7D70",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77"
|
||||
}
|
||||
},
|
||||
"seed": "pelican amateur empower assist awkward claim brave process cliff save album pigeon intact asset"
|
||||
}
|
||||
|
||||
To avoid the prompt, it's possible to pipe the password into the
|
||||
command, e.g.:
|
||||
|
||||
::
|
||||
|
||||
echo 1234567890 | gaia client keys new fred --output json
|
||||
|
||||
After trying each of the three ways to create a key, look at them, use:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys list
|
||||
|
||||
to list all the keys:
|
||||
|
||||
::
|
||||
|
||||
All keys:
|
||||
alice 6FEA9C99E2565B44FCC3C539A293A1378CDA7609
|
||||
bob A159C96AE911F68913E715ED889D211C02EC7D70
|
||||
charlie 784D623E0C15DE79043C126FA6449B68311339E5
|
||||
|
||||
Again, we can use the ``--output json`` flag:
|
||||
|
||||
::
|
||||
|
||||
[
|
||||
{
|
||||
"name": "alice",
|
||||
"address": "6FEA9C99E2565B44FCC3C539A293A1378CDA7609",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "878B297F1E863CC30CAD71E04A8B3C23DB71C18F449F39E35B954EDB2276D32D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bob",
|
||||
"address": "A159C96AE911F68913E715ED889D211C02EC7D70",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "2127CAAB96C08E3042C5B33C8B5A820079AAE8DD50642DCFCC1E8B74821B2BB9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "charlie",
|
||||
"address": "784D623E0C15DE79043C126FA6449B68311339E5",
|
||||
"pubkey": {
|
||||
"type": "ed25519",
|
||||
"data": "4BF22554B0F0BF2181187E5E5456E3BF3D96DB4C416A91F07F03A9C36F712B77"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
to get machine readable output.
|
||||
|
||||
If we want information about one specific key, then:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys get charlie --output json
|
||||
|
||||
will, for example, return the info for only the "charlie" key returned
|
||||
from the previous ``gaia client keys list`` command.
|
||||
|
||||
The keys tooling can support different types of keys with a flag:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys new bit --type secp256k1
|
||||
|
||||
and you'll see the difference in the ``"type": field from``\ gaia client
|
||||
keys get\`
|
||||
|
||||
Before moving on, let's set an enviroment variable to make
|
||||
``--output json`` the default.
|
||||
|
||||
Either run or put in your ``~/.bash_profile`` the following line:
|
||||
|
||||
::
|
||||
|
||||
export BC_OUTPUT=json
|
||||
|
||||
Recover a key
|
||||
-------------
|
||||
|
||||
Let's say, for whatever reason, you lose a key or forget the password.
|
||||
On creation, you were given a seed. We'll use it to recover a lost key.
|
||||
|
||||
First, let's simulate the loss by deleting a key:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys delete alice
|
||||
|
||||
which prompts for your current password, now rendered obsolete, and
|
||||
gives a warning message. The only way you can recover your key now is
|
||||
using the 12 word seed given on initial creation of the key. Let's try
|
||||
it:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys recover alice-again
|
||||
|
||||
which prompts for a new password then the seed:
|
||||
|
||||
::
|
||||
|
||||
Enter the new passphrase:
|
||||
Enter your recovery seed phrase:
|
||||
strike alien praise vendor term left market practice junior better deputy divert front calm
|
||||
alice-again CBF5D9CE6DDCC32806162979495D07B851C53451
|
||||
|
||||
and voila! You've recovered your key. Note that the seed can be typed
|
||||
out, pasted in, or piped into the command alongside the password.
|
||||
|
||||
To change the password of a key, we can:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys update alice-again
|
||||
|
||||
and follow the prompts.
|
||||
|
||||
That covers most features of the keys sub command.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<!-- use later in a test script, or more advance tutorial?
|
||||
SEED=$(echo 1234567890 | gaia client keys new fred -o json | jq .seed | tr -d \")
|
||||
echo $SEED
|
||||
(echo qwertyuiop; echo $SEED stamp) | gaia client keys recover oops
|
||||
(echo qwertyuiop; echo $SEED) | gaia client keys recover derf
|
||||
gaia client keys get fred -o json
|
||||
gaia client keys get derf -o json
|
||||
```
|
||||
-->
|
||||
@@ -1,83 +0,0 @@
|
||||
Local Testnet
|
||||
=============
|
||||
|
||||
This tutorial demonstrates the basics of setting up a gaia
|
||||
testnet locally.
|
||||
|
||||
If you haven't already made a key, make one now:
|
||||
|
||||
::
|
||||
|
||||
gaia client keys new alice
|
||||
|
||||
otherwise, use an existing key.
|
||||
|
||||
Initialize The Chain
|
||||
--------------------
|
||||
|
||||
Now initialize a gaia chain, using ``alice``'s address:
|
||||
|
||||
::
|
||||
|
||||
gaia node init 5D93A6059B6592833CBC8FA3DA90EE0382198985 --home=$HOME/.gaia1 --chain-id=gaia-test
|
||||
|
||||
This will create all the files necessary to run a single node chain in
|
||||
``$HOME/.gaia1``: a ``priv_validator.json`` file with the validators
|
||||
private key, and a ``genesis.json`` file with the list of validators and
|
||||
accounts.
|
||||
|
||||
We'll add a second node on our local machine by initiating a node in a
|
||||
new directory, with the same address, and copying in the genesis:
|
||||
|
||||
::
|
||||
|
||||
gaia node init 5D93A6059B6592833CBC8FA3DA90EE0382198985 --home=$HOME/.gaia2 --chain-id=gaia-test
|
||||
cp $HOME/.gaia1/genesis.json $HOME/.gaia2/genesis.json
|
||||
|
||||
We also need to modify ``$HOME/.gaia2/config.toml`` to set new seeds
|
||||
and ports. It should look like:
|
||||
|
||||
::
|
||||
|
||||
proxy_app = "tcp://127.0.0.1:46668"
|
||||
moniker = "anonymous"
|
||||
fast_sync = true
|
||||
db_backend = "leveldb"
|
||||
log_level = "state:info,*:error"
|
||||
|
||||
[rpc]
|
||||
laddr = "tcp://0.0.0.0:46667"
|
||||
|
||||
[p2p]
|
||||
laddr = "tcp://0.0.0.0:46666"
|
||||
seeds = "0.0.0.0:46656"
|
||||
|
||||
Start Nodes
|
||||
-----------
|
||||
|
||||
Now that we've initialized the chains, we can start both nodes:
|
||||
|
||||
NOTE: each command below must be started in seperate terminal windows. Alternatively, to run this testnet across multiple machines, you'd replace the ``seeds = "0.0.0.0"`` in ``~/.gaia2.config.toml`` with the IP of the first node, and could skip the modifications we made to the config file above because port conflicts would be avoided.
|
||||
|
||||
::
|
||||
|
||||
gaia node start --home=$HOME/.gaia1
|
||||
gaia node start --home=$HOME/.gaia2
|
||||
|
||||
Now we can initialize a client for the first node, and look up our
|
||||
account:
|
||||
|
||||
::
|
||||
|
||||
gaia client init --chain-id=gaia-test --node=tcp://localhost:46657
|
||||
gaia client query account 5D93A6059B6592833CBC8FA3DA90EE0382198985
|
||||
|
||||
To see what tendermint considers the validator set is, use:
|
||||
|
||||
::
|
||||
|
||||
curl localhost:46657/validators
|
||||
|
||||
and compare the information in this file: ``~/.gaia1/priv_validator.json``. The ``address`` and ``pub_key`` fields should match.
|
||||
|
||||
To add a second validator on your testnet, you'll need to bond some tokens be declaring candidacy.
|
||||
@@ -1,64 +0,0 @@
|
||||
Public Testnets
|
||||
===============
|
||||
|
||||
Here we'll cover the basics of joining a public testnet. These testnets
|
||||
come and go with various names are we release new versions of tendermint
|
||||
core. This tutorial covers joining the ``gaia-1`` testnet. To join
|
||||
other testnets, choose different initialization files, described below.
|
||||
|
||||
Get Tokens
|
||||
----------
|
||||
|
||||
If you haven't already `created a key <../key-management.html>`__,
|
||||
do so now. Copy your key's address and enter it into
|
||||
`this utility <http://www.cosmosvalidators.com/>`__ which will send you
|
||||
some ``steak`` testnet tokens.
|
||||
|
||||
Get Files
|
||||
---------
|
||||
|
||||
Now, to sync with the testnet, we need the genesis file and seeds. The
|
||||
easiest way to get them is to clone and navigate to the tendermint
|
||||
testnet repo:
|
||||
|
||||
::
|
||||
|
||||
git clone https://github.com/tendermint/testnets ~/testnets
|
||||
cd ~/testnets/gaia-1/gaia
|
||||
|
||||
NOTE: to join a different testnet, change the ``gaia-1/gaia`` filepath
|
||||
to another directory with testnet inititalization files *and* an
|
||||
active testnet.
|
||||
|
||||
Start Node
|
||||
----------
|
||||
|
||||
Now we can start a new node:it may take awhile to sync with the
|
||||
existing testnet.
|
||||
|
||||
::
|
||||
|
||||
gaia node start --home=$HOME/testnets/gaia-1/gaia
|
||||
|
||||
Once blocks slow down to about one per second, you're all caught up.
|
||||
|
||||
The ``gaia node start`` command will automaticaly generate a validator
|
||||
private key found in ``~/testnets/gaia-1/gaia/priv_validator.json``.
|
||||
|
||||
Finally, let's initialize the gaia client to interact with the testnet:
|
||||
|
||||
::
|
||||
|
||||
gaia client init --chain-id=gaia-1 --node=tcp://localhost:46657
|
||||
|
||||
and check our balance:
|
||||
|
||||
::
|
||||
|
||||
gaia client query account $MYADDR
|
||||
|
||||
Where ``$MYADDR`` is the address originally generated by ``gaia keys new bob``.
|
||||
|
||||
You are now ready to declare candidacy or delegate some steaks. See the
|
||||
`staking module overview <./staking-module.html>`__ for more information
|
||||
on using the ``gaia client``.
|
||||
@@ -0,0 +1,118 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
The SDK design optimizes flexibility and security. The framework is
|
||||
designed around a modular execution stack which allows applications to
|
||||
mix and match elements as desired. In addition, all modules are
|
||||
sandboxed for greater application security.
|
||||
|
||||
Framework Overview
|
||||
------------------
|
||||
|
||||
### Object-Capability Model
|
||||
|
||||
When thinking about security, it's good to start with a specific threat
|
||||
model. Our threat model is the following:
|
||||
|
||||
We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules.
|
||||
|
||||
The Cosmos-SDK is designed to address this threat by being the
|
||||
foundation of an object capability system.
|
||||
|
||||
The structural properties of object capability systems favor
|
||||
modularity in code design and ensure reliable encapsulation in
|
||||
code implementation.
|
||||
|
||||
These structural properties facilitate the analysis of some
|
||||
security properties of an object-capability program or operating
|
||||
system. Some of these — in particular, information flow properties
|
||||
— can be analyzed at the level of object references and
|
||||
connectivity, independent of any knowledge or analysis of the code
|
||||
that determines the behavior of the objects. As a consequence,
|
||||
these security properties can be established and maintained in the
|
||||
presence of new objects that contain unknown and possibly
|
||||
malicious code.
|
||||
|
||||
These structural properties stem from the two rules governing
|
||||
access to existing objects:
|
||||
|
||||
1) An object A can send a message to B only if object A holds a
|
||||
reference to B.
|
||||
|
||||
2) An object A can obtain a reference to C only
|
||||
if object A receives a message containing a reference to C. As a
|
||||
consequence of these two rules, an object can obtain a reference
|
||||
to another object only through a preexisting chain of references.
|
||||
In short, "Only connectivity begets connectivity."
|
||||
|
||||
See the [wikipedia
|
||||
article](https://en.wikipedia.org/wiki/Object-capability_model) for more
|
||||
information.
|
||||
|
||||
Strictly speaking, Golang does not implement object capabilities
|
||||
completely, because of several issues:
|
||||
|
||||
- pervasive ability to import primitive modules (e.g. "unsafe", "os")
|
||||
- pervasive ability to override module vars
|
||||
<https://github.com/golang/go/issues/23161>
|
||||
- data-race vulnerability where 2+ goroutines can create illegal
|
||||
interface values
|
||||
|
||||
The first is easy to catch by auditing imports and using a proper
|
||||
dependency version control system like Dep. The second and third are
|
||||
unfortunate but it can be audited with some cost.
|
||||
|
||||
Perhaps [Go2 will implement the object capability
|
||||
model](https://github.com/golang/go/issues/23157).
|
||||
|
||||
#### What does it look like?
|
||||
|
||||
Only reveal what is necessary to get the work done.
|
||||
|
||||
For example, the following code snippet violates the object capabilities
|
||||
principle:
|
||||
|
||||
type AppAccount struct {...}
|
||||
var account := &AppAccount{
|
||||
Address: pub.Address(),
|
||||
Coins: sdk.Coins{{"ATM", 100}},
|
||||
}
|
||||
var sumValue := externalModule.ComputeSumValue(account)
|
||||
|
||||
The method "ComputeSumValue" implies a pure function, yet the implied
|
||||
capability of accepting a pointer value is the capability to modify that
|
||||
value. The preferred method signature should take a copy instead.
|
||||
|
||||
var sumValue := externalModule.ComputeSumValue(*account)
|
||||
|
||||
In the Cosmos SDK, you can see the application of this principle in the
|
||||
basecoin examples folder.
|
||||
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/sketchy"
|
||||
)
|
||||
|
||||
func (app *BasecoinApp) initRouterHandlers() {
|
||||
|
||||
// All handlers must be added here.
|
||||
// The order matters.
|
||||
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
|
||||
app.router.AddRoute("sketchy", sketchy.NewHandler())
|
||||
}
|
||||
|
||||
In the Basecoin example, the sketchy handler isn't provided an account
|
||||
mapper, which does provide the bank handler with the capability (in
|
||||
conjunction with the context of a transaction run).
|
||||
|
||||
### Capabilities systems
|
||||
|
||||
TODO:
|
||||
|
||||
- Need for module isolation
|
||||
- Capability is implied permission
|
||||
- Link to thesis
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Overview
|
||||
|
||||
The Cosmos-SDK is a framework for building Tendermint ABCI applications in
|
||||
Golang. It is designed to allow developers to easily create custom interoperable
|
||||
blockchain applications within the Cosmos Network.
|
||||
|
||||
We envision the SDK as the `npm`-like framework to build secure blockchain applications on top of Tendermint.
|
||||
|
||||
To achieve its goals of flexibility and security, the SDK makes extensive use of
|
||||
the [object-capability
|
||||
model](https://en.wikipedia.org/wiki/Object-capability_model)
|
||||
and the [principle of least
|
||||
privelege](https://en.wikipedia.org/wiki/Principle_of_least_privilege).
|
||||
|
||||
For an introduction to object-capabilities, see this [article](http://habitatchronicles.com/2017/05/what-are-capabilities/).
|
||||
|
||||
## Languages
|
||||
|
||||
The Cosmos-SDK is currently writen in [Golang](https://golang.org/), though the
|
||||
framework could be implemented similarly in other languages.
|
||||
Contact us for information about funding an implementation in another language.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
The SDK is laid out in the following directories:
|
||||
|
||||
- `baseapp`: Defines the template for a basic [ABCI](https://cosmos.network/whitepaper#abci) application so that your Cosmos-SDK application can communicate with the underlying Tendermint node.
|
||||
- `client`: CLI and REST server tooling for interacting with SDK application.
|
||||
- `examples`: Examples of how to build working standalone applications.
|
||||
- `server`: The full node server for running an SDK application on top of
|
||||
Tendermint.
|
||||
- `store`: The database of the SDK - a Merkle multistore supporting multiple types of underling Merkle key-value stores.
|
||||
- `types`: Common types in SDK applications.
|
||||
- `x`: Extensions to the core, where all messages and handlers are defined.
|
||||
@@ -1,48 +0,0 @@
|
||||
Install
|
||||
=======
|
||||
|
||||
Cosmos SDK can be installed to
|
||||
``$GOPATH/src/github.com/cosmos/cosmos-sdk`` like a normal Go program:
|
||||
|
||||
::
|
||||
|
||||
go get github.com/cosmos/cosmos-sdk
|
||||
|
||||
If the dependencies have been updated with breaking changes, or if
|
||||
another branch is required, ``dep`` is used for dependency management.
|
||||
Thus, assuming you've already run ``go get`` or otherwise cloned the
|
||||
repo, the correct way to install is:
|
||||
|
||||
::
|
||||
|
||||
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
|
||||
make get_vendor_deps
|
||||
make install
|
||||
make install_examples
|
||||
|
||||
This will install ``gaiad`` and ``gaiacli`` and four example binaries:
|
||||
``basecoind``, ``basecli``, ``democoind``, and ``democli``.
|
||||
|
||||
Verify that everything is OK by running:
|
||||
|
||||
::
|
||||
|
||||
gaiad version
|
||||
|
||||
you should see:
|
||||
|
||||
::
|
||||
|
||||
0.15.0-rc1-9d90c6b
|
||||
|
||||
then with:
|
||||
|
||||
::
|
||||
|
||||
gaiacli version
|
||||
|
||||
you should see:
|
||||
|
||||
::
|
||||
|
||||
0.15.0-rc1-9d90c6b
|
||||
@@ -1,18 +0,0 @@
|
||||
Key Management
|
||||
==============
|
||||
|
||||
Here we cover many aspects of handling keys within the Cosmos SDK framework.
|
||||
|
||||
Pseudo Code
|
||||
-----------
|
||||
|
||||
Generating an address for an ed25519 public key (in pseudo code):
|
||||
|
||||
::
|
||||
|
||||
const TypeDistinguisher = HexToBytes("1624de6220")
|
||||
|
||||
// prepend the TypeDistinguisher as Bytes
|
||||
SerializedBytes = TypeDistinguisher ++ PubKey.asBytes()
|
||||
|
||||
Address = ripemd160(SerializedBytes)
|
||||
@@ -1,418 +0,0 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
The SDK design optimizes flexibility and security. The
|
||||
framework is designed around a modular execution stack which allows
|
||||
applications to mix and match elements as desired. In addition,
|
||||
all modules are sandboxed for greater application security.
|
||||
|
||||
Framework Overview
|
||||
------------------
|
||||
|
||||
Object-Capability Model
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When thinking about security, it's good to start with a specific threat model. Our threat model is the following:
|
||||
|
||||
::
|
||||
|
||||
We assume that a thriving ecosystem of Cosmos-SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules.
|
||||
|
||||
The Cosmos-SDK is designed to address this threat by being the foundation of an object capability system.
|
||||
|
||||
::
|
||||
|
||||
The structural properties of object capability systems favor
|
||||
modularity in code design and ensure reliable encapsulation in
|
||||
code implementation.
|
||||
|
||||
These structural properties facilitate the analysis of some
|
||||
security properties of an object-capability program or operating
|
||||
system. Some of these — in particular, information flow properties
|
||||
— can be analyzed at the level of object references and
|
||||
connectivity, independent of any knowledge or analysis of the code
|
||||
that determines the behavior of the objects. As a consequence,
|
||||
these security properties can be established and maintained in the
|
||||
presence of new objects that contain unknown and possibly
|
||||
malicious code.
|
||||
|
||||
These structural properties stem from the two rules governing
|
||||
access to existing objects:
|
||||
|
||||
1) An object A can send a message to B only if object A holds a
|
||||
reference to B.
|
||||
|
||||
2) An object A can obtain a reference to C only
|
||||
if object A receives a message containing a reference to C. As a
|
||||
consequence of these two rules, an object can obtain a reference
|
||||
to another object only through a preexisting chain of references.
|
||||
In short, "Only connectivity begets connectivity."
|
||||
|
||||
See the `wikipedia article <https://en.wikipedia.org/wiki/Object-capability_model>`__ for more information.
|
||||
|
||||
Strictly speaking, Golang does not implement object capabilities completely, because of several issues:
|
||||
|
||||
* pervasive ability to import primitive modules (e.g. "unsafe", "os")
|
||||
* pervasive ability to override module vars https://github.com/golang/go/issues/23161
|
||||
* data-race vulnerability where 2+ goroutines can create illegal interface values
|
||||
|
||||
The first is easy to catch by auditing imports and using a proper dependency version control system like Dep. The second and third are unfortunate but it can be audited with some cost.
|
||||
|
||||
Perhaps `Go2 will implement the object capability model <https://github.com/golang/go/issues/23157>`__.
|
||||
|
||||
What does it look like?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Only reveal what is necessary to get the work done.
|
||||
|
||||
For example, the following code snippet violates the object capabilities principle:
|
||||
|
||||
::
|
||||
|
||||
type AppAccount struct {...}
|
||||
var account := &AppAccount{
|
||||
Address: pub.Address(),
|
||||
Coins: sdk.Coins{{"ATM", 100}},
|
||||
}
|
||||
var sumValue := externalModule.ComputeSumValue(account)
|
||||
|
||||
The method "ComputeSumValue" implies a pure function, yet the implied capability of accepting a pointer value is the capability to modify that value. The preferred method signature should take a copy instead.
|
||||
|
||||
::
|
||||
|
||||
var sumValue := externalModule.ComputeSumValue(*account)
|
||||
|
||||
In the Cosmos SDK, you can see the application of this principle in the basecoin examples folder.
|
||||
|
||||
::
|
||||
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/sketchy"
|
||||
)
|
||||
|
||||
func (app *BasecoinApp) initRouterHandlers() {
|
||||
|
||||
// All handlers must be added here.
|
||||
// The order matters.
|
||||
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
|
||||
app.router.AddRoute("sketchy", sketchy.NewHandler())
|
||||
}
|
||||
|
||||
In the Basecoin example, the sketchy handler isn't provided an account mapper, which does provide the bank handler with the capability (in conjunction with the context of a transaction run).
|
||||
|
||||
Security Overview
|
||||
-----------------
|
||||
|
||||
For examples, see the `examples <https://github.com/cosmos/cosmos-sdk/tree/develop/examples>`__ directory.
|
||||
|
||||
Design Goals
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The design of the Cosmos SDK is based on the principles of "capabilities systems".
|
||||
|
||||
Capabilities systems
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
TODO:
|
||||
|
||||
* Need for module isolation
|
||||
* Capability is implied permission
|
||||
* Link to thesis
|
||||
|
||||
Tx & Msg
|
||||
~~~~~~~~
|
||||
|
||||
The SDK distinguishes between transactions (Tx) and messages
|
||||
(Msg). A Tx is a Msg wrapped with authentication and fee data.
|
||||
|
||||
Messages
|
||||
^^^^^^^^
|
||||
|
||||
Users can create messages containing arbitrary information by
|
||||
implementing the ``Msg`` interface:
|
||||
|
||||
::
|
||||
|
||||
type Msg interface {
|
||||
|
||||
// Return the message type.
|
||||
// Must be alphanumeric or empty.
|
||||
Type() string
|
||||
|
||||
// Get the canonical byte representation of the Msg.
|
||||
GetSignBytes() []byte
|
||||
|
||||
// ValidateBasic does a simple validation check that
|
||||
// doesn't require access to any other information.
|
||||
ValidateBasic() error
|
||||
|
||||
// Signers returns the addrs of signers that must sign.
|
||||
// CONTRACT: All signatures must be present to be valid.
|
||||
// CONTRACT: Returns addrs in some deterministic order.
|
||||
GetSigners() []Address
|
||||
}
|
||||
|
||||
Messages must specify their type via the ``Type()`` method. The type should
|
||||
correspond to the messages handler, so there can be many messages with the same
|
||||
type.
|
||||
|
||||
Messages must also specify how they are to be authenticated. The ``GetSigners()``
|
||||
method return a list of addresses that must sign the message, while the
|
||||
``GetSignBytes()`` method returns the bytes that must be signed for a signature
|
||||
to be valid.
|
||||
|
||||
Addresses in the SDK are arbitrary byte arrays that are hex-encoded when
|
||||
displayed as a string or rendered in JSON.
|
||||
|
||||
Messages can specify basic self-consistency checks using the ``ValidateBasic()``
|
||||
method to enforce that message contents are well formed before any actual logic
|
||||
begins.
|
||||
|
||||
For instance, the ``Basecoin`` message types are defined in ``x/bank/tx.go``:
|
||||
|
||||
::
|
||||
|
||||
type SendMsg struct {
|
||||
Inputs []Input `json:"inputs"`
|
||||
Outputs []Output `json:"outputs"`
|
||||
}
|
||||
|
||||
type IssueMsg struct {
|
||||
Banker sdk.Address `json:"banker"`
|
||||
Outputs []Output `json:"outputs"`
|
||||
}
|
||||
|
||||
Each specifies the addresses that must sign the message:
|
||||
|
||||
::
|
||||
|
||||
func (msg SendMsg) GetSigners() []sdk.Address {
|
||||
addrs := make([]sdk.Address, len(msg.Inputs))
|
||||
for i, in := range msg.Inputs {
|
||||
addrs[i] = in.Address
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (msg IssueMsg) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Banker}
|
||||
}
|
||||
|
||||
Transactions
|
||||
^^^^^^^^^^^^
|
||||
|
||||
A transaction is a message with additional information for authentication:
|
||||
|
||||
::
|
||||
|
||||
type Tx interface {
|
||||
|
||||
GetMsg() Msg
|
||||
|
||||
// Signatures returns the signature of signers who signed the Msg.
|
||||
// CONTRACT: Length returned is same as length of
|
||||
// pubkeys returned from MsgKeySigners, and the order
|
||||
// matches.
|
||||
// CONTRACT: If the signature is missing (ie the Msg is
|
||||
// invalid), then the corresponding signature is
|
||||
// .Empty().
|
||||
GetSignatures() []StdSignature
|
||||
}
|
||||
|
||||
The ``tx.GetSignatures()`` method returns a list of signatures, which must match
|
||||
the list of addresses returned by ``tx.Msg.GetSigners()``. The signatures come in
|
||||
a standard form:
|
||||
|
||||
::
|
||||
|
||||
type StdSignature struct {
|
||||
crypto.PubKey // optional
|
||||
crypto.Signature
|
||||
Sequence int64
|
||||
}
|
||||
|
||||
It contains the signature itself, as well as the corresponding account's
|
||||
sequence number. The sequence number is expected to increment every time a
|
||||
message is signed by a given account. This prevents "replay attacks", where
|
||||
the same message could be executed over and over again.
|
||||
|
||||
The ``StdSignature`` can also optionally include the public key for verifying the
|
||||
signature. An application can store the public key for each address it knows
|
||||
about, making it optional to include the public key in the transaction. In the
|
||||
case of Basecoin, the public key only needs to be included in the first
|
||||
transaction send by a given account - after that, the public key is forever
|
||||
stored by the application and can be left out of transactions.
|
||||
|
||||
The standard way to create a transaction from a message is to use the ``StdTx``:
|
||||
|
||||
::
|
||||
|
||||
type StdTx struct {
|
||||
Msg
|
||||
Signatures []StdSignature
|
||||
}
|
||||
|
||||
Encoding and Decoding Transactions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Messages and transactions are designed to be generic enough for developers to
|
||||
specify their own encoding schemes. This enables the SDK to be used as the
|
||||
framwork for constructing already specified cryptocurrency state machines, for
|
||||
instance Ethereum.
|
||||
|
||||
When initializing an application, a developer must specify a ``TxDecoder``
|
||||
function which determines how an arbitrary byte array should be unmarshalled
|
||||
into a ``Tx``:
|
||||
|
||||
::
|
||||
|
||||
type TxDecoder func(txBytes []byte) (Tx, error)
|
||||
|
||||
In ``Basecoin``, we use the Tendermint wire format and the ``go-amino`` library for
|
||||
encoding and decoding all message types. The ``go-amino`` library has the nice
|
||||
property that it can unmarshal into interface types, but it requires the
|
||||
relevant types to be registered ahead of type. Registration happens on a
|
||||
``Codec`` object, so as not to taint the global name space.
|
||||
|
||||
For instance, in ``Basecoin``, we wish to register the ``SendMsg`` and ``IssueMsg``
|
||||
types:
|
||||
|
||||
::
|
||||
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(bank.SendMsg{}, "cosmos-sdk/SendMsg", nil)
|
||||
cdc.RegisterConcrete(bank.IssueMsg{}, "cosmos-sdk/IssueMsg", nil)
|
||||
|
||||
Note how each concrete type is given a name - these name determine the type's
|
||||
unique "prefix bytes" during encoding. A registered type will always use the
|
||||
same prefix-bytes, regardless of what interface it is satisfying. For more
|
||||
details, see the `go-amino documentation <https://github.com/tendermint/go-amino/tree/develop>`__.
|
||||
|
||||
|
||||
MultiStore
|
||||
~~~~~~~~~~
|
||||
|
||||
MultiStore is like a filesystem
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Mounting an IAVLStore
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO:
|
||||
|
||||
* IAVLStore: Fast balanced dynamic Merkle store.
|
||||
|
||||
* supports iteration.
|
||||
|
||||
* MultiStore: multiple Merkle tree backends in a single store
|
||||
|
||||
* allows using Ethereum Patricia Trie and Tendermint IAVL in same app
|
||||
|
||||
* Provide caching for intermediate state during execution of blocks and transactions (including for iteration)
|
||||
* Historical state pruning and snapshotting.
|
||||
* Query proofs (existence, absence, range, etc.) on current and retained historical state.
|
||||
|
||||
Context
|
||||
-------
|
||||
|
||||
The SDK uses a ``Context`` to propogate common information across functions. The
|
||||
``Context`` is modelled after the Golang ``context.Context`` object, which has
|
||||
become ubiquitous in networking middleware and routing applications as a means
|
||||
to easily propogate request context through handler functions.
|
||||
|
||||
The main information stored in the ``Context`` includes the application
|
||||
MultiStore (see below), the last block header, and the transaction bytes.
|
||||
Effectively, the context contains all data that may be necessary for processing
|
||||
a transaction.
|
||||
|
||||
Many methods on SDK objects receive a context as the first argument.
|
||||
|
||||
Handler
|
||||
-------
|
||||
|
||||
Transaction processing in the SDK is defined through ``Handler`` functions:
|
||||
|
||||
::
|
||||
|
||||
type Handler func(ctx Context, tx Tx) Result
|
||||
|
||||
A handler takes a context and a transaction and returns a result. All
|
||||
information necessary for processing a transaction should be available in the
|
||||
context.
|
||||
|
||||
While the context holds the entire application state (all referenced from the
|
||||
root MultiStore), a particular handler only needs a particular kind of access
|
||||
to a particular store (or two or more). Access to stores is managed using
|
||||
capabilities keys and mappers. When a handler is initialized, it is passed a
|
||||
key or mapper that gives it access to the relevant stores.
|
||||
|
||||
::
|
||||
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_stores.go
|
||||
app.BaseApp.MountStore(app.capKeyMainStore, sdk.StoreTypeIAVL)
|
||||
app.accountMapper = auth.NewAccountMapper(
|
||||
app.capKeyMainStore, // target store
|
||||
&types.AppAccount{}, // prototype
|
||||
)
|
||||
|
||||
// File: cosmos-sdk/examples/basecoin/app/init_handlers.go
|
||||
app.router.AddRoute("bank", bank.NewHandler(app.accountMapper))
|
||||
|
||||
// File: cosmos-sdk/x/bank/handler.go
|
||||
// NOTE: Technically, NewHandler only needs a CoinMapper
|
||||
func NewHandler(am sdk.AccountMapper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
cm := CoinMapper{am}
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
AnteHandler
|
||||
-----------
|
||||
|
||||
Handling Fee payment
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Handling Authentication
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Accounts and x/auth
|
||||
-------------------
|
||||
|
||||
sdk.Account
|
||||
~~~~~~~~~~~
|
||||
|
||||
auth.BaseAccount
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
auth.AccountMapper
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Wire codec
|
||||
----------
|
||||
|
||||
Why another codec?
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
vs encoding/json
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
vs protobuf
|
||||
~~~~~~~~~~~
|
||||
|
||||
KVStore example
|
||||
---------------
|
||||
|
||||
Basecoin example
|
||||
----------------
|
||||
|
||||
The quintessential SDK application is Basecoin - a simple
|
||||
multi-asset cryptocurrency. Basecoin consists of a set of
|
||||
accounts stored in a Merkle tree, where each account may have
|
||||
many coins. There are two message types: SendMsg and IssueMsg.
|
||||
SendMsg allows coins to be sent around, while IssueMsg allows a
|
||||
set of predefined users to issue new coins.
|
||||
+20
-12
@@ -1,19 +1,27 @@
|
||||
# Cosmos Hub Spec
|
||||
|
||||
This directory contains specifications for the application level components of
|
||||
the Cosmos Hub.
|
||||
This directory contains specifications for the state transition machine of the
|
||||
Cosmos Hub.
|
||||
|
||||
NOTE: the specifications are not yet complete and very much a work in progress.
|
||||
The Cosmos Hub holds all of its state in a Merkle store. Updates to
|
||||
the store may be made during transactions and at the beginning and end of every
|
||||
block.
|
||||
|
||||
- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for
|
||||
sending tokens.
|
||||
- [Staking](staking) - Proof of Stake related specifications including bonding
|
||||
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.
|
||||
While the first implementation of the Cosmos Hub is built using the Cosmos-SDK,
|
||||
these specifications aim to be independent of any implementation details. That
|
||||
said, they provide a detailed resource for understanding the Cosmos-SDK.
|
||||
|
||||
- [Store](store) - The core Merkle store that holds the state.
|
||||
- [Auth](auth) - The structure and authentication of accounts and transactions.
|
||||
- [Bank](bank) - Sending tokens.
|
||||
- [Governance](governance) - Proposals and voting.
|
||||
- [Staking](staking) - Proof-of-stake bonding, delegation, etc.
|
||||
- [Slashing](slashing) - Validator punishment mechanisms.
|
||||
- [Provisioning](provisioning) - Fee distribution, and atom provision distribution
|
||||
- [IBC](ibc) - Inter-Blockchain Communication (IBC) protocol.
|
||||
- [Other](other) - Other components of the Cosmos Hub, including the reserve
|
||||
pool, All in Bits vesting, etc.
|
||||
|
||||
The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec),
|
||||
i.e. the underlying blockchain, can be found elsewhere.
|
||||
For details on the underlying blockchain and p2p protocols, see
|
||||
the [Tendermint specification](https://github.com/tendermint/tendermint/tree/develop/docs/spec).
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ const (
|
||||
|
||||
type ProposalStatus byte
|
||||
|
||||
|
||||
const (
|
||||
ProposalStatusOpen = 0x1 // Proposal is submitted. Participants can deposit on it but not vote
|
||||
ProposalStatusActive = 0x2 // MinDeposit is reachhed, participants can vote
|
||||
|
||||
@@ -166,6 +166,7 @@ vote on the proposal.
|
||||
|
||||
*Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker*
|
||||
|
||||
|
||||
Next is a pseudocode proposal of the way `TxGovVote` transactions are
|
||||
handled:
|
||||
|
||||
@@ -188,11 +189,10 @@ handled:
|
||||
|
||||
if (proposal.CurrentStatus == ProposalStatusActive)
|
||||
|
||||
|
||||
// Sender can vote if
|
||||
// Proposal is active
|
||||
// Sender has some bonds
|
||||
|
||||
store(Governance, <txGovVote.ProposalID|'addresses'|sender>, txGovVote.Vote) // Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end.
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Bech32 on Cosmos
|
||||
|
||||
The Cosmos network prefers to use the Bech32 address format whereever users must handle binary data. Bech32 encoding provides robust integrity checks on data and the human readable part(HRP) provides contextual hints that can assist UI developers with providing informative error messages.
|
||||
|
||||
In the Cosmos network, keys and addresses may refer to a number of different roles in the network like accounts, validators etc.
|
||||
|
||||
|
||||
## HRP table
|
||||
|
||||
| HRP | Definition |
|
||||
| ------------- |:-------------:|
|
||||
| `cosmosaccaddr` | Cosmos Account Address |
|
||||
| `cosmosaccpub` | Cosmos Account Public Key |
|
||||
| `cosmosvaladdr` | Cosmos Consensus Address |
|
||||
| `cosmosvalpub` | Cosmos Consensus Public Key|
|
||||
|
||||
## Encoding
|
||||
|
||||
While all user facing interfaces to Cosmos software should exposed bech32 interfaces, many internal interfaces encode binary value in hex or base64 encoded form.
|
||||
|
||||
To covert between other binary reprsentation of addresses and keys, it is important to first apply the Amino enocoding process before bech32 encoding.
|
||||
|
||||
A complete implementation of the Amino serialization format is unncessary in most cases. Simply prepending bytes from this [table](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md#public-key-cryptography) to the bytestring payload before bech32 encoding will sufficient for compatible representation.
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
# Fee Distribution
|
||||
|
||||
## Overview
|
||||
|
||||
Fees are pooled separately and withdrawn lazily, at any time. They are not
|
||||
bonded, and can be paid in multiple tokens. An adjustment factor is maintained
|
||||
for each validator and delegator to determine the true proportion of fees in
|
||||
the pool they are entitled too. Adjustment factors are updated every time a
|
||||
validator or delegator's voting power changes. Validators and delegators must
|
||||
withdraw all fees they are entitled too before they can bond or unbond Atoms.
|
||||
|
||||
## Affect on Staking
|
||||
|
||||
Because fees are optimized to note
|
||||
|
||||
Commission on Atom Provisions and having atoms autobonded are mutually
|
||||
exclusive (we can’t have both). The reason for this is that if there are atoms
|
||||
commissions and autobonding, the portion of atoms the fee distribution
|
||||
calculation would become very large as the atom portion for each delegator
|
||||
would change each block making a withdrawal of fees for a delegator require a
|
||||
calculation for every single block since the last withdrawal. Conclusion we can
|
||||
only have atom commission and unbonded atoms provisions, or bonded atom
|
||||
provisions and no atom commission
|
||||
|
||||
## Fee Calculations
|
||||
|
||||
Collected fees are pooled globally and divided out passively to validators and
|
||||
delegators. Each validator has the opportunity to charge commission to the
|
||||
delegators on the fees collected on behalf of the delegators by the validators.
|
||||
Fees are paid directly into a global fee pool. Due to the nature of of passive
|
||||
accounting whenever changes to parameters which affect the rate of fee
|
||||
distribution occurs, withdrawal of fees must also occur.
|
||||
|
||||
- when withdrawing one must withdrawal the maximum amount they are entitled
|
||||
too, leaving nothing in the pool,
|
||||
- when bonding, unbonding, or re-delegating tokens to an existing account a
|
||||
full withdrawal of the fees must occur (as the rules for lazy accounting
|
||||
change),
|
||||
- when a validator chooses to change the commission on fees, all accumulated
|
||||
commission fees must be simultaneously withdrawn.
|
||||
|
||||
When the validator is the proposer of the round, that validator (and their
|
||||
delegators) receives between 1% and 5% of fee rewards, the reserve tax is then
|
||||
charged, then the remainder is distributed socially by voting power to all
|
||||
validators including the proposer validator. The amount of proposer reward is
|
||||
calculated from pre-commits Tendermint messages. All provision rewards are
|
||||
added to a provision reward pool which validator holds individually. Here note
|
||||
that `BondedShares` represents the sum of all voting power saved in the
|
||||
`GlobalState` (denoted `gs`).
|
||||
|
||||
```
|
||||
proposerReward = feesCollected * (0.01 + 0.04
|
||||
* sumOfVotingPowerOfPrecommitValidators / gs.BondedShares)
|
||||
validator.ProposerRewardPool += proposerReward
|
||||
|
||||
reserveTaxed = feesCollected * params.ReserveTax
|
||||
gs.ReservePool += reserveTaxed
|
||||
|
||||
distributedReward = feesCollected - proposerReward - reserveTaxed
|
||||
gs.FeePool += distributedReward
|
||||
gs.SumFeesReceived += distributedReward
|
||||
gs.RecentFee = distributedReward
|
||||
```
|
||||
|
||||
The entitlement to the fee pool held by the each validator can be accounted for
|
||||
lazily. First we must account for a validator's `count` and `adjustment`. The
|
||||
`count` represents a lazy accounting of what that validators entitlement to the
|
||||
fee pool would be if there `VotingPower` was to never change and they were to
|
||||
never withdraw fees.
|
||||
|
||||
```
|
||||
validator.count = validator.VotingPower * BlockHeight
|
||||
```
|
||||
|
||||
Similarly the GlobalState count can be passively calculated whenever needed,
|
||||
where `BondedShares` is the updated sum of voting powers from all validators.
|
||||
|
||||
```
|
||||
gs.count = gs.BondedShares * BlockHeight
|
||||
```
|
||||
|
||||
The `adjustment` term accounts for changes in voting power and withdrawals of
|
||||
fees. The adjustment factor must be persisted with the validator and modified
|
||||
whenever fees are withdrawn from the validator or the voting power of the
|
||||
validator changes. When the voting power of the validator changes the
|
||||
`Adjustment` factor is increased/decreased by the cumulative difference in the
|
||||
voting power if the voting power has been the new voting power as opposed to
|
||||
the old voting power for the entire duration of the blockchain up the previous
|
||||
block. Each time there is an adjustment change the GlobalState (denoted `gs`)
|
||||
`Adjustment` must also be updated.
|
||||
|
||||
```
|
||||
simplePool = validator.count / gs.count * gs.SumFeesReceived
|
||||
projectedPool = validator.PrevPower * (height-1)
|
||||
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
|
||||
+ validator.Power / gs.Power * gs.RecentFee
|
||||
|
||||
AdjustmentChange = simplePool - projectedPool
|
||||
validator.AdjustmentRewardPool += AdjustmentChange
|
||||
gs.Adjustment += AdjustmentChange
|
||||
```
|
||||
|
||||
Every instance that the voting power changes, information about the state of
|
||||
the validator set during the change must be recorded as a `powerChange` for
|
||||
other validators to run through. Before any validator modifies its voting power
|
||||
it must first run through the above calculation to determine the change in
|
||||
their `caandidate.AdjustmentRewardPool` for all historical changes in the set
|
||||
of `powerChange` which they have not yet synced to. The set of all
|
||||
`powerChange` may be trimmed from its oldest members once all validators have
|
||||
synced past the height of the oldest `powerChange`. This trim procedure will
|
||||
occur on an epoch basis.
|
||||
|
||||
```golang
|
||||
type powerChange struct {
|
||||
height int64 // block height at change
|
||||
power rational.Rat // total power at change
|
||||
prevpower rational.Rat // total power at previous height-1
|
||||
feesin coins.Coin // fees in at block height
|
||||
prevFeePool coins.Coin // total fees in at previous block height
|
||||
}
|
||||
```
|
||||
|
||||
Note that the adjustment factor may result as negative if the voting power of a
|
||||
different validator has decreased.
|
||||
|
||||
```
|
||||
validator.AdjustmentRewardPool += withdrawn
|
||||
gs.Adjustment += withdrawn
|
||||
```
|
||||
|
||||
Now the entitled fee pool of each validator can be lazily accounted for at
|
||||
any given block:
|
||||
|
||||
```
|
||||
validator.feePool = validator.simplePool - validator.Adjustment
|
||||
```
|
||||
|
||||
So far we have covered two sources fees which can be withdrawn from: Fees from
|
||||
proposer rewards (`validator.ProposerRewardPool`), and fees from the fee pool
|
||||
(`validator.feePool`). However we should note that all fees from fee pool are
|
||||
subject to commission rate from the owner of the validator. These next
|
||||
calculations outline the math behind withdrawing fee rewards as either a
|
||||
delegator to a validator providing commission, or as the owner of a validator
|
||||
who is receiving commission.
|
||||
|
||||
### Calculations For Delegators and Validators
|
||||
|
||||
The same mechanism described to calculate the fees which an entire validator is
|
||||
entitled to is be applied to delegator level to determine the entitled fees for
|
||||
each delegator and the validators entitled commission from `gs.FeesPool` and
|
||||
`validator.ProposerRewardPool`.
|
||||
|
||||
The calculations are identical with a few modifications to the parameters:
|
||||
- Delegator's entitlement to `gs.FeePool`:
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
after commission is retrieved,
|
||||
`bond.Shares/validator.TotalDelegatorShares * validator.VotingPower * (1 - validator.Commission)`
|
||||
- Delegator's entitlement to `validator.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`validator.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the effective shares after
|
||||
commission is retrieved, `bond.Shares * (1 - validator.Commission)`
|
||||
- Validator's commission entitlement to `gs.FeePool`
|
||||
- entitled party voting power should be taken as the effective voting power
|
||||
of commission portion of total voting power,
|
||||
`validator.VotingPower * validator.Commission`
|
||||
- Validator's commission entitlement to `validator.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`validator.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the of commission portion
|
||||
of total delegators shares,
|
||||
`validator.TotalDelegatorShares * validator.Commission`
|
||||
|
||||
For more implementation ideas see spreadsheet `spec/AbsoluteFeeDistrModel.xlsx`
|
||||
|
||||
As mentioned earlier, every time the voting power of a delegator bond is
|
||||
changing either by unbonding or further bonding, all fees must be
|
||||
simultaneously withdrawn. Similarly if the validator changes the commission
|
||||
rate, all commission on fees must be simultaneously withdrawn.
|
||||
|
||||
### Other general notes on fees accounting
|
||||
|
||||
- When a delegator chooses to re-delegate shares, fees continue to accumulate
|
||||
until the re-delegation queue reaches maturity. At the block which the queue
|
||||
reaches maturity and shares are re-delegated all available fees are
|
||||
simultaneously withdrawn.
|
||||
- Whenever a totally new validator is added to the validator set, the `accum`
|
||||
of the entire validator must be 0, meaning that the initial value for
|
||||
`validator.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the validator is added on the validator set.
|
||||
- The feePool of a new delegator bond will be 0 for the height at which the bond
|
||||
was added. This is achieved by setting `DelegatorBond.FeeWithdrawalHeight` to
|
||||
the height which the bond was added.
|
||||
|
||||
### Atom provisions
|
||||
|
||||
Validator provisions are minted on an hourly basis (the first block of a new
|
||||
hour). The annual target of between 7% and 20%. The long-term target ratio of
|
||||
bonded tokens to unbonded tokens is 67%.
|
||||
|
||||
The target annual inflation rate is recalculated for each provisions cycle. The
|
||||
inflation is also subject to a rate change (positive or negative) depending on
|
||||
the distance from the desired ratio (67%). The maximum rate change possible is
|
||||
defined to be 13% per year, however the annual inflation is capped as between
|
||||
7% and 20%.
|
||||
|
||||
```go
|
||||
inflationRateChange(0) = 0
|
||||
Inflation(0) = 0.07
|
||||
|
||||
bondedRatio = Pool.BondedTokens / Pool.TotalSupplyTokens
|
||||
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
|
||||
|
||||
annualInflation += AnnualInflationRateChange
|
||||
|
||||
if annualInflation > 0.20 then Inflation = 0.20
|
||||
if annualInflation < 0.07 then Inflation = 0.07
|
||||
|
||||
provisionTokensHourly = Pool.TotalSupplyTokens * Inflation / (365.25*24)
|
||||
```
|
||||
|
||||
Because the validators hold a relative bonded share (`GlobalStakeShares`), when
|
||||
more bonded tokens are added proportionally to all validators, the only term
|
||||
which needs to be updated is the `GlobalState.BondedPool`. So for each
|
||||
provisions cycle:
|
||||
|
||||
```go
|
||||
Pool.BondedPool += provisionTokensHourly
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
Validator
|
||||
|
||||
* Adjustment factor used to passively calculate each validators entitled fees
|
||||
from `GlobalState.FeePool`
|
||||
|
||||
Delegation Shares
|
||||
|
||||
* 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 `Validator.ProposerRewardPool`
|
||||
@@ -0,0 +1,115 @@
|
||||
# End-Block
|
||||
|
||||
## Slashing
|
||||
|
||||
Tendermint blocks can include
|
||||
[Evidence](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/blockchain.md#evidence), which indicates that a validator
|
||||
committed malicious behaviour. The relevant information is forwarded to the
|
||||
application as [ABCI
|
||||
Evidence](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto#L259), so the validator an be accordingly 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 validator's stake is reduced by `SLASH_PROPORTION` of
|
||||
what their stake was when the infraction occurred (rather than when the evidence was discovered).
|
||||
We want to "follow the stake": the stake which contributed to the infraction should be
|
||||
slashed, even if it has since been redelegated or started unbonding.
|
||||
|
||||
We first need to loop through the unbondings and redelegations from the slashed validator
|
||||
and track how much stake has since moved:
|
||||
|
||||
```
|
||||
slashAmountUnbondings := 0
|
||||
slashAmountRedelegations := 0
|
||||
|
||||
unbondings := getUnbondings(validator.Address)
|
||||
for unbond in unbondings {
|
||||
|
||||
if was not bonded before evidence.Height or started unbonding before unbonding period ago {
|
||||
continue
|
||||
}
|
||||
|
||||
burn := unbond.InitialTokens * SLASH_PROPORTION
|
||||
slashAmountUnbondings += burn
|
||||
|
||||
unbond.Tokens = max(0, unbond.Tokens - burn)
|
||||
}
|
||||
|
||||
// only care if source gets slashed because we're already bonded to destination
|
||||
// so if destination validator gets slashed our delegation just has same shares
|
||||
// of smaller pool.
|
||||
redels := getRedelegationsBySource(validator.Address)
|
||||
for redel in redels {
|
||||
|
||||
if was not bonded before evidence.Height or started redelegating before unbonding period ago {
|
||||
continue
|
||||
}
|
||||
|
||||
burn := redel.InitialTokens * SLASH_PROPORTION
|
||||
slashAmountRedelegations += burn
|
||||
|
||||
amount := unbondFromValidator(redel.Destination, burn)
|
||||
destroy(amount)
|
||||
}
|
||||
```
|
||||
|
||||
We then slash the validator:
|
||||
|
||||
```
|
||||
curVal := validator
|
||||
oldVal := loadValidator(evidence.Height, evidence.Address)
|
||||
|
||||
slashAmount := SLASH_PROPORTION * oldVal.Shares
|
||||
slashAmount -= slashAmountUnbondings
|
||||
slashAmount -= slashAmountRedelegations
|
||||
|
||||
curVal.Shares = max(0, curVal.Shares - slashAmount)
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
At the beginning of each block, we update the signing info for each validator and check if they should be automatically unbonded:
|
||||
|
||||
```
|
||||
height := block.Height
|
||||
|
||||
for val in block.Validators:
|
||||
signInfo = SigningInfo.Get(val.Address)
|
||||
if signInfo == nil{
|
||||
signInfo.StartHeight = height
|
||||
}
|
||||
|
||||
index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW
|
||||
signInfo.IndexOffset++
|
||||
previous = SigningBitArray.Get(val.Address, index)
|
||||
|
||||
// update counter if array has changed
|
||||
if previous and val in block.AbsentValidators:
|
||||
SigningBitArray.Set(val.Address, index, false)
|
||||
signInfo.SignedBlocksCounter--
|
||||
else if !previous and val not in block.AbsentValidators:
|
||||
SigningBitArray.Set(val.Address, index, true)
|
||||
signInfo.SignedBlocksCounter++
|
||||
// else previous == val not in block.AbsentValidators, no change
|
||||
|
||||
// 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
|
||||
if height > minHeight AND signInfo.SignedBlocksCounter < minSigned:
|
||||
signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION
|
||||
|
||||
slash & unbond the validator
|
||||
|
||||
SigningInfo.Set(val.Address, signInfo)
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
## State
|
||||
|
||||
### Signing Info
|
||||
|
||||
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.
|
||||
|
||||
Information about validator activity is tracked in a `ValidatorSigningInfo`.
|
||||
It is indexed in the store as follows:
|
||||
|
||||
- SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)`
|
||||
- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)`
|
||||
|
||||
The first map allows us to easily lookup the recent signing info for a
|
||||
validator, according to the Tendermint validator address. The second map acts as
|
||||
a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array.
|
||||
|
||||
The index in the bit-array is given as little endian uint64.
|
||||
|
||||
The result is a `varint` that takes on `0` or `1`, where `0` indicates the
|
||||
validator did not sign the corresponding block, and `1` indicates they did.
|
||||
|
||||
Note that the SigningBitArray is not explicitly initialized up-front. Keys are
|
||||
added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly
|
||||
bonded validator.
|
||||
|
||||
The information stored for tracking validator liveness is as follows:
|
||||
|
||||
```go
|
||||
type ValidatorSigningInfo struct {
|
||||
StartHeight int64
|
||||
IndexOffset int64
|
||||
JailedUntil int64
|
||||
SignedBlocksCounter int64
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Where:
|
||||
* `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power).
|
||||
* `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not).
|
||||
* `JailedUntil` is set whenever the candidate is revoked due to downtime
|
||||
* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always.
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
### 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
|
||||
```
|
||||
+22
-14
@@ -2,30 +2,38 @@
|
||||
|
||||
## 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.
|
||||
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.
|
||||
The module enables Cosmos-SDK based blockchain to support an advanced
|
||||
Proof-of-Stake system. In this system, holders of the native staking token of
|
||||
the chain can become validators and can delegate tokens to validator
|
||||
validators, ultimately determining the effective validator set for the system.
|
||||
|
||||
This module will be used in the Cosmos Hub, the first Hub in the Cosmos network.
|
||||
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.
|
||||
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
|
||||
1. Params
|
||||
1. Pool
|
||||
2. Validators
|
||||
3. Delegations
|
||||
2. **[Transactions](transactions.md)**
|
||||
1. Declare Candidacy
|
||||
2. Edit Candidacy
|
||||
3. Delegate
|
||||
4. Unbond
|
||||
5. Redelegate
|
||||
6. ProveLive
|
||||
1. Create-Validator
|
||||
2. Edit-Validator
|
||||
3. Repeal-Revocation
|
||||
4. Delegate
|
||||
5. Unbond
|
||||
6. Redelegate
|
||||
3. **[Validator Set Changes](valset-changes.md)**
|
||||
1. Validator set updates
|
||||
2. Slashing
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# End-Block
|
||||
|
||||
Two staking activities are intended to be processed in the application end-block.
|
||||
- inform Tendermint of validator set changes
|
||||
- process and set atom inflation
|
||||
|
||||
# Validator Set Changes
|
||||
|
||||
The Tendermint validator set may be updated by state transitions that run at
|
||||
the end of every block. The Tendermint validator set may be changed by
|
||||
validators either being revoked due to inactivity/unexpected behaviour (covered
|
||||
in slashing) or changed in validator power. Determining which validator set
|
||||
changes must be made occurs during staking transactions (and slashing
|
||||
transactions) - during end-block the already accounted changes are applied and
|
||||
the changes cleared
|
||||
|
||||
```golang
|
||||
EndBlock() ValidatorSetChanges
|
||||
vsc = GetTendermintUpdates()
|
||||
ClearTendermintUpdates()
|
||||
return vsc
|
||||
```
|
||||
|
||||
# Inflation
|
||||
|
||||
The atom inflation rate is changed once per hour based on the current and
|
||||
historic bond ratio
|
||||
|
||||
```golang
|
||||
processProvisions():
|
||||
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
|
||||
|
||||
time = BFTTime()
|
||||
if time > pool.InflationLastTime + ProvisionTimeout
|
||||
pool.InflationLastTime = time
|
||||
pool.Inflation = nextInflation(hrsPerYr).Round(1000000000)
|
||||
|
||||
provisions = pool.Inflation * (pool.TotalSupply / hrsPerYr)
|
||||
|
||||
pool.LooseTokens += provisions
|
||||
feePool += LooseTokens
|
||||
|
||||
setPool(pool)
|
||||
|
||||
nextInflation(hrsPerYr rational.Rat):
|
||||
if pool.TotalSupply > 0
|
||||
bondedRatio = pool.BondedPool / pool.TotalSupply
|
||||
else
|
||||
bondedRation = 0
|
||||
|
||||
inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange
|
||||
inflationRateChange = inflationRateChangePerYear / hrsPerYr
|
||||
|
||||
inflation = pool.Inflation + inflationRateChange
|
||||
if inflation > params.InflationMax then inflation = params.InflationMax
|
||||
|
||||
if inflation < params.InflationMin then inflation = params.InflationMin
|
||||
|
||||
return inflation
|
||||
```
|
||||
|
||||
@@ -1,214 +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.
|
||||
|
||||
## Basic Terms and Definitions
|
||||
|
||||
* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system
|
||||
* Atom - native token of the Cosmsos Hub
|
||||
* Atom holder - an entity that holds some amount of Atoms
|
||||
* Candidate - an Atom holder that is actively involved in the Tendermint
|
||||
blockchain protocol (running Tendermint Full Node (TODO: add link to Full
|
||||
Node definition) and is competing with other candidates to be elected as a
|
||||
validator (TODO: add link to Validator definition))
|
||||
* Validator - a candidate that is currently selected among a set of candidates
|
||||
to be able to sign protocol messages in the Tendermint consensus protocol
|
||||
* Delegator - an Atom holder that has bonded some of its Atoms by delegating
|
||||
them to a validator (or a candidate)
|
||||
* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms
|
||||
under protocol control). Atoms are always bonded through a validator (or
|
||||
candidate) process. Bonded atoms can be slashed (burned) in case a validator
|
||||
process misbehaves (does not behave according to the protocol specification).
|
||||
Atom holders can regain access to their bonded Atoms if they have not been
|
||||
slashed by waiting an Unbonding period.
|
||||
* Unbonding period - a period of time after which Atom holder gains access to
|
||||
its bonded Atoms (they can be withdrawn to a user account) or they can be
|
||||
re-delegated.
|
||||
* Inflationary provisions - inflation is the process of increasing the Atom supply.
|
||||
Atoms are periodically created on the Cosmos Hub and issued to bonded Atom holders.
|
||||
The goal of inflation is to incentize most of the Atoms in existence to be bonded.
|
||||
* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub
|
||||
transaction. The fees are collected by the current validator set and
|
||||
distributed among validators and delegators in proportion to their bonded
|
||||
Atom share.
|
||||
* Commission fee - a fee taken from the transaction fees by a validator for
|
||||
their service
|
||||
|
||||
## The pool and the share
|
||||
|
||||
At the core of the Staking module is the concept of a pool which denotes a
|
||||
collection of Atoms contributed by different Atom holders. There are two global
|
||||
pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms
|
||||
are part of the global bonded pool. If a candidate or delegator wants to unbond
|
||||
its Atoms, those Atoms are moved to the the unbonding pool for the duration of
|
||||
the unbonding period. In the Staking module, a pool is a logical concept, i.e.,
|
||||
there is no pool data structure that would be responsible for managing pool
|
||||
resources. Instead, it is managed in a distributed way. More precisely, at the
|
||||
global level, for each pool, we track only the total amount of bonded or unbonded
|
||||
Atoms and the current amount of issued shares. A share is a unit of Atom distribution
|
||||
and the value of the share (share-to-atom exchange rate) changes during
|
||||
system execution. The share-to-atom exchange rate can be computed as:
|
||||
|
||||
`share-to-atom-exchange-rate = size of the pool / ammount of issued shares`
|
||||
|
||||
Then for each validator candidate (in a per candidate data structure) we keep track of
|
||||
the amount of shares the candidate owns in a pool. At any point in time,
|
||||
the exact amount of Atoms a candidate has in the pool can be computed as the
|
||||
number of shares it owns multiplied with the current share-to-atom exchange rate:
|
||||
|
||||
`candidate-coins = candidate.Shares * share-to-atom-exchange-rate`
|
||||
|
||||
The benefit of such accounting of the pool resources is the fact that a
|
||||
modification to the pool from bonding/unbonding/slashing/provisioning of
|
||||
Atoms affects only global data (size of the pool and the number of shares) and
|
||||
not the related validator/candidate data structure, i.e., the data structure of
|
||||
other validators do not need to be modified. This has the advantage that
|
||||
modifying global data is much cheaper computationally than modifying data of
|
||||
every validator. Let's explain this further with several small examples:
|
||||
|
||||
We consider initially 4 validators p1, p2, p3 and p4, and that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 shares (note that the initial distribution of the shares,
|
||||
i.e., share-to-atom exchange rate can be set to any meaningful value), i.e.,
|
||||
share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we
|
||||
have, the size of the pool is 40 Atoms, and the amount of issued shares is
|
||||
equal to 40. And for each validator we store in their corresponding data
|
||||
structure that each has 10 shares of the bonded pool. Now lets assume that the
|
||||
validator p4 starts process of unbonding of 5 shares. Then the total size of
|
||||
the pool is decreased and now it will be 35 shares and the amount of Atoms is
|
||||
35 . Note that the only change in other data structures needed is reducing the
|
||||
number of shares for a validator p4 from 10 to 5.
|
||||
|
||||
Let's consider now the case where a validator p1 wants to bond 15 more atoms to
|
||||
the pool. Now the size of the pool is 50, and as the exchange rate hasn't
|
||||
changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we
|
||||
now have 50 shares in the pool in total. Validators p2, p3 and p4 still have
|
||||
(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we
|
||||
don't need to modify anything in their corresponding data structures. But p1
|
||||
now has 25 shares, so we update the amount of shares owned by p1 in its
|
||||
data structure. Note that apart from the size of the pool that is in Atoms, all
|
||||
other data structures refer only to shares.
|
||||
|
||||
Finally, let's consider what happens when new Atoms are created and added to
|
||||
the pool due to inflation. Let's assume that the inflation rate is 10 percent
|
||||
and that it is applied to the current state of the pool. This means that 5
|
||||
Atoms are created and added to the pool and that each validator now
|
||||
proportionally increase it's Atom count. Let's analyse how this change is
|
||||
reflected in the data structures. First, the size of the pool is increased and
|
||||
is now 55 atoms. As a share of each validator in the pool hasn't changed, this
|
||||
means that the total number of shares stay the same (50) and that the amount of
|
||||
shares of each validator stays the same (correspondingly 25, 10, 10, 5). But
|
||||
the exchange rate has changed and each share is now worth 55/50 Atoms per
|
||||
share, so each validator has effectively increased amount of Atoms it has. So
|
||||
validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms.
|
||||
|
||||
The concepts of the pool and its shares is at the core of the accounting in the
|
||||
Staking module. It is used for managing the global pools (such as bonding and
|
||||
unbonding pool), but also for distribution of Atoms between validator and its
|
||||
delegators (we will explain this in section X).
|
||||
|
||||
#### Delegator shares
|
||||
|
||||
A candidate is, depending on it's status, contributing Atoms to either the
|
||||
bonded or unbonding pool, and in return gets some amount of (global) pool
|
||||
shares. Note that not all those Atoms (and respective shares) are owned by the
|
||||
candidate as some Atoms could be delegated to a candidate. The mechanism for
|
||||
distribution of Atoms (and shares) between a candidate and it's delegators is
|
||||
based on a notion of delegator shares. More precisely, every candidate is
|
||||
issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that
|
||||
represents some portion of global shares managed by the candidate
|
||||
(`Candidate.GlobalStakeShares`). The principle behind managing delegator shares
|
||||
is the same as described in [Section](#The pool and the share). We now
|
||||
illustrate it with an example.
|
||||
|
||||
Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator
|
||||
has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have
|
||||
issued initially 40 global shares, i.e., that
|
||||
`share-to-atom-exchange-rate = 1 atom per share`. So we will set
|
||||
`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the
|
||||
Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`.
|
||||
Furthermore, each validator issued 10 delegator shares which are initially
|
||||
owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where
|
||||
`delegator-share-to-global-share-ex-rate = 1 global share per delegator share`.
|
||||
Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and
|
||||
consider what are the updates we need to make to the data structures. First,
|
||||
`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for
|
||||
validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to
|
||||
issue also additional delegator shares, i.e.,
|
||||
`Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator
|
||||
shares of validator p1, where each delegator share is worth 1 global shares,
|
||||
i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to
|
||||
inflation. In that case, we only need to update `GlobalState.BondedPool` which
|
||||
is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note
|
||||
that the amount of global and delegator shares stay the same but they are now
|
||||
worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share.
|
||||
Therefore, a delegator d1 now owns:
|
||||
|
||||
`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms`
|
||||
|
||||
### Inflation provisions
|
||||
|
||||
Validator provisions are minted on an hourly basis (the first block of a new
|
||||
hour). The annual target of between 7% and 20%. The long-term target ratio of
|
||||
bonded tokens to unbonded tokens is 67%.
|
||||
|
||||
The target annual inflation rate is recalculated for each provisions cycle. The
|
||||
inflation is also subject to a rate change (positive or negative) depending on
|
||||
the distance from the desired ratio (67%). The maximum rate change possible is
|
||||
defined to be 13% per year, however the annual inflation is capped as between
|
||||
7% and 20%.
|
||||
|
||||
```go
|
||||
inflationRateChange(0) = 0
|
||||
GlobalState.Inflation(0) = 0.07
|
||||
|
||||
bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply
|
||||
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
|
||||
|
||||
annualInflation += AnnualInflationRateChange
|
||||
|
||||
if annualInflation > 0.20 then GlobalState.Inflation = 0.20
|
||||
if annualInflation < 0.07 then GlobalState.Inflation = 0.07
|
||||
|
||||
provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24)
|
||||
```
|
||||
|
||||
Because the validators hold a relative bonded share (`GlobalStakeShares`), when
|
||||
more bonded tokens are added proportionally to all validators, the only term
|
||||
which needs to be updated is the `GlobalState.BondedPool`. So for each
|
||||
provisions cycle:
|
||||
|
||||
```go
|
||||
GlobalState.BondedPool += provisionTokensHourly
|
||||
```
|
||||
+161
-164
@@ -1,204 +1,201 @@
|
||||
|
||||
## 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
|
||||
### Pool
|
||||
|
||||
### Global State
|
||||
- key: `01`
|
||||
- value: `amino(pool)`
|
||||
|
||||
The GlobalState contains information about the total amount of Atoms, the
|
||||
global bonded/unbonded position, the Atom inflation rate, and the fees.
|
||||
The pool is a space for all dynamic global state of the Cosmos Hub. It tracks
|
||||
information about the total amounts of Atoms in all states, representative
|
||||
validator shares for stake in the global pools, moving Atom inflation
|
||||
information, etc.
|
||||
|
||||
`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
|
||||
```golang
|
||||
type Pool struct {
|
||||
LooseTokens int64 // tokens not associated with any validator
|
||||
UnbondedTokens int64 // reserve of unbonded tokens held with validators
|
||||
UnbondingTokens int64 // tokens moving from bonded to unbonded pool
|
||||
BondedTokens int64 // reserve of bonded tokens
|
||||
UnbondedShares sdk.Rat // sum of all shares distributed for the Unbonded Pool
|
||||
UnbondingShares sdk.Rat // shares moving from Bonded to Unbonded Pool
|
||||
BondedShares sdk.Rat // sum of all shares distributed for the Bonded Pool
|
||||
InflationLastTime int64 // block which the last inflation was processed // TODO make time
|
||||
Inflation sdk.Rat // current annual inflation rate
|
||||
|
||||
DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily)
|
||||
}
|
||||
|
||||
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
|
||||
type PoolShares struct {
|
||||
Status sdk.BondStatus // either: unbonded, unbonding, or bonded
|
||||
Amount sdk.Rat // total shares of type ShareKind
|
||||
}
|
||||
```
|
||||
|
||||
### Candidate
|
||||
### Params
|
||||
- key: `00`
|
||||
- value: `amino(params)`
|
||||
|
||||
The `Candidate` holds the current state and some historical
|
||||
actions of validators or candidate-validators.
|
||||
Params is global data structure that stores system parameters and defines
|
||||
overall functioning of the stake module.
|
||||
|
||||
``` go
|
||||
type CandidateStatus byte
|
||||
```golang
|
||||
type Params struct {
|
||||
InflationRateChange sdk.Rat // maximum annual change in inflation rate
|
||||
InflationMax sdk.Rat // maximum inflation rate
|
||||
InflationMin sdk.Rat // minimum inflation rate
|
||||
GoalBonded sdk.Rat // Goal of percent bonded atoms
|
||||
|
||||
const (
|
||||
Bonded CandidateStatus = 0x01
|
||||
Unbonded CandidateStatus = 0x02
|
||||
Revoked CandidateStatus = 0x03
|
||||
)
|
||||
MaxValidators uint16 // maximum number of validators
|
||||
BondDenom string // bondable coin denomination
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
### Validator
|
||||
|
||||
Validators are identified according to the `ValOwnerAddr`,
|
||||
an SDK account address for the owner of the validator.
|
||||
|
||||
Validators also have a `ValTendermintAddr`, the address
|
||||
of the public key of the validator.
|
||||
|
||||
Validators are indexed in the store using the following maps:
|
||||
|
||||
- Validators: `0x02 | ValOwnerAddr -> amino(validator)`
|
||||
- ValidatorsByPubKey: `0x03 | ValTendermintAddr -> ValOwnerAddr`
|
||||
- ValidatorsByPower: `0x05 | power | blockHeight | blockTx -> ValOwnerAddr`
|
||||
|
||||
`Validators` is the primary index - it ensures that each owner can have only one
|
||||
associated validator, where the public key of that validator can change in the
|
||||
future. Delegators can refer to the immutable owner of the validator, without
|
||||
concern for the changing public key.
|
||||
|
||||
`ValidatorsByPubKey` is a secondary index that enables lookups for slashing.
|
||||
When Tendermint reports evidence, it provides the validator address, so this
|
||||
map is needed to find the owner.
|
||||
|
||||
`ValidatorsByPower` is a secondary index that provides a sorted list of
|
||||
potential validators to quickly determine the current active set. For instance,
|
||||
the first 100 validators in this list can be returned with every EndBlock.
|
||||
|
||||
The `Validator` holds the current state and some historical actions of the
|
||||
validator.
|
||||
|
||||
```golang
|
||||
type Validator struct {
|
||||
ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator
|
||||
Revoked bool // has the validator been revoked?
|
||||
|
||||
PoolShares PoolShares // total shares for tokens held in the pool
|
||||
DelegatorShares sdk.Rat // total shares issued to a validator's delegators
|
||||
SlashRatio sdk.Rat // increases each time the validator is slashed
|
||||
|
||||
Description Description // description terms for the validator
|
||||
|
||||
// Needed for ordering vals in the bypower key
|
||||
BondHeight int64 // earliest height as a bonded validator
|
||||
BondIntraTxCounter int16 // block-local tx index of validator change
|
||||
|
||||
CommissionInfo CommissionInfo // info about the validator's commission
|
||||
|
||||
ProposerRewardPool sdk.Coins // reward pool collected from being the proposer
|
||||
|
||||
// TODO: maybe this belongs in distribution module ?
|
||||
PrevPoolShares PoolShares // total shares of a global hold pools
|
||||
}
|
||||
|
||||
type CommissionInfo struct {
|
||||
Rate sdk.Rat // the commission rate of fees charged to any delegators
|
||||
Max sdk.Rat // maximum commission rate which this validator can ever charge
|
||||
ChangeRate sdk.Rat // maximum daily increase of the validator commission
|
||||
ChangeToday sdk.Rat // commission rate change today, reset each day (UTC time)
|
||||
LastChange int64 // unix timestamp of last commission change
|
||||
}
|
||||
|
||||
type Description struct {
|
||||
Name string
|
||||
DateBonded string
|
||||
Identity string
|
||||
Website string
|
||||
Details string
|
||||
Moniker string // name
|
||||
Identity string // optional identity signature (ex. UPort or Keybase)
|
||||
Website string // optional website link
|
||||
Details string // optional details
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
### Delegation
|
||||
|
||||
### DelegatorBond
|
||||
Delegations are identified by combining `DelegatorAddr` (the address of the delegator) with the ValOwnerAddr
|
||||
Delegators are indexed in the store as follows:
|
||||
|
||||
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
|
||||
- Delegation: ` 0x0A | DelegatorAddr | ValOwnerAddr -> amino(delegation)`
|
||||
|
||||
Atom holders may delegate coins to validators; under this circumstance their
|
||||
funds are held in a `Delegation` data structure. It is owned by one
|
||||
delegator, and is associated with the shares for one validator. The sender of
|
||||
the transaction is the owner of the bond.
|
||||
|
||||
``` go
|
||||
type 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
|
||||
type Delegation struct {
|
||||
Shares sdk.Rat // delegation shares recieved
|
||||
Height int64 // last height bond updated
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
### UnbondingDelegation
|
||||
|
||||
### QueueElemUnbondDelegation
|
||||
Shares in a `Delegation` can be unbonded, but they must for some time exist as an `UnbondingDelegation`,
|
||||
where shares can be reduced if Byzantine behaviour is detected.
|
||||
|
||||
QueueElemUnbondDelegation structure is used in the unbonding queue.
|
||||
`UnbondingDelegation` are indexed in the store as:
|
||||
|
||||
- UnbondingDelegationByDelegator: ` 0x0B | DelegatorAddr | ValOwnerAddr ->
|
||||
amino(unbondingDelegation)`
|
||||
- UnbondingDelegationByValOwner: ` 0x0C | ValOwnerAddr | DelegatorAddr | ValOwnerAddr ->
|
||||
nil`
|
||||
|
||||
The first map here is used in queries, to lookup all unbonding delegations for
|
||||
a given delegator, while the second map is used in slashing, to lookup all
|
||||
unbonding delegations associated with a given validator that need to be
|
||||
slashed.
|
||||
|
||||
A UnbondingDelegation object is created every time an unbonding is initiated.
|
||||
The unbond must be completed with a second transaction provided by the
|
||||
delegation owner after the unbonding period has passed.
|
||||
|
||||
```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
|
||||
type UnbondingDelegation struct {
|
||||
Tokens sdk.Coins // the value in Atoms of the amount of shares which are unbonding
|
||||
CompleteTime int64 // unix time to complete redelegation
|
||||
}
|
||||
```
|
||||
|
||||
### QueueElemReDelegate
|
||||
### Redelegation
|
||||
|
||||
QueueElemReDelegate structure is used in the re-delegation queue.
|
||||
Shares in a `Delegation` can be rebonded to a different validator, but they must for some time exist as a `Redelegation`,
|
||||
where shares can be reduced if Byzantine behaviour is detected. This is tracked
|
||||
as moving a delegation from a `FromValOwnerAddr` to a `ToValOwnerAddr`.
|
||||
|
||||
`Redelegation` are indexed in the store as:
|
||||
|
||||
- Redelegations: `0x0D | DelegatorAddr | FromValOwnerAddr | ToValOwnerAddr ->
|
||||
amino(redelegation)`
|
||||
- RedelegationsBySrc: `0x0E | FromValOwnerAddr | ToValOwnerAddr |
|
||||
DelegatorAddr -> nil`
|
||||
- RedelegationsByDst: `0x0F | ToValOwnerAddr | FromValOwnerAddr | DelegatorAddr
|
||||
-> nil`
|
||||
|
||||
|
||||
The first map here is used for queries, to lookup all redelegations for a given
|
||||
delegator. The second map is used for slashing based on the FromValOwnerAddr,
|
||||
while the third map is for slashing based on the ToValOwnerAddr.
|
||||
|
||||
A redelegation object is created every time a redelegation occurs. The
|
||||
redelegation must be completed with a second transaction provided by the
|
||||
delegation owner after the unbonding period has passed. The destination
|
||||
delegation of a redelegation may not itself undergo a new redelegation until
|
||||
the original redelegation has been completed.
|
||||
|
||||
```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
|
||||
type Redelegation struct {
|
||||
SourceShares sdk.Rat // amount of source shares redelegating
|
||||
DestinationShares sdk.Rat // amount of destination shares created at redelegation
|
||||
CompleteTime int64 // unix time to complete redelegation
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+251
-203
@@ -1,67 +1,61 @@
|
||||
|
||||
### Transaction Overview
|
||||
|
||||
Available Transactions:
|
||||
* TxDeclareCandidacy
|
||||
* TxEditCandidacy
|
||||
* TxDelegate
|
||||
* TxUnbond
|
||||
* TxRedelegate
|
||||
* TxProveLive
|
||||
In this section we describe the processing of the transactions and the
|
||||
corresponding updates to the state. Transactions:
|
||||
- TxCreateValidator
|
||||
- TxEditValidator
|
||||
- TxDelegation
|
||||
- TxStartUnbonding
|
||||
- TxCompleteUnbonding
|
||||
- TxRedelegate
|
||||
- TxCompleteRedelegation
|
||||
|
||||
## Transaction processing
|
||||
Other important state changes:
|
||||
- Update Validators
|
||||
|
||||
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.
|
||||
Other notes:
|
||||
- `tx` denotes a reference to the transaction being processed
|
||||
- `sender` denotes the address of the sender of the transaction
|
||||
- `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and
|
||||
modify objects from the store
|
||||
- `sdk.Rat` refers to a rational numeric type specified by the SDK.
|
||||
|
||||
### TxDeclareCandidacy
|
||||
### TxCreateValidator
|
||||
|
||||
A validator candidacy is declared using the `TxDeclareCandidacy` transaction.
|
||||
A validator is created using the `TxCreateValidator` transaction.
|
||||
|
||||
```golang
|
||||
type TxDeclareCandidacy struct {
|
||||
type TxCreateValidator struct {
|
||||
OwnerAddr sdk.Address
|
||||
ConsensusPubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission rational.Rat
|
||||
CommissionMax int64
|
||||
CommissionMaxChange int64
|
||||
SelfDelegation coin.Coin
|
||||
|
||||
Description Description
|
||||
Commission sdk.Rat
|
||||
CommissionMax sdk.Rat
|
||||
CommissionMaxChange sdk.Rat
|
||||
}
|
||||
|
||||
|
||||
declareCandidacy(tx TxDeclareCandidacy):
|
||||
candidate = loadCandidate(store, tx.PubKey)
|
||||
if candidate != nil return // candidate with that public key already exists
|
||||
createValidator(tx TxCreateValidator):
|
||||
validator = getValidator(tx.OwnerAddr)
|
||||
if validator != nil return // only one validator per address
|
||||
|
||||
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
|
||||
validator = NewValidator(OwnerAddr, ConsensusPubKey, GovernancePubKey, Description)
|
||||
init validator poolShares, delegatorShares set to 0
|
||||
init validator commision fields from tx
|
||||
validator.PoolShares = 0
|
||||
|
||||
saveCandidate(store, candidate)
|
||||
setValidator(validator)
|
||||
|
||||
txDelegate = TxDelegate(tx.PubKey, tx.Amount)
|
||||
return delegateWithCandidate(txDelegate, candidate)
|
||||
|
||||
// see delegateWithCandidate function in [TxDelegate](TxDelegate)
|
||||
txDelegate = TxDelegate(tx.OwnerAddr, tx.OwnerAddr, tx.SelfDelegation)
|
||||
delegate(txDelegate, validator) // see delegate function in [TxDelegate](TxDelegate)
|
||||
return
|
||||
```
|
||||
|
||||
### TxEditCandidacy
|
||||
### TxEditValidator
|
||||
|
||||
If either the `Description` (excluding `DateBonded` which is constant),
|
||||
`Commission`, or the `GovernancePubKey` need to be updated, the
|
||||
@@ -70,214 +64,268 @@ If either the `Description` (excluding `DateBonded` which is constant),
|
||||
```golang
|
||||
type TxEditCandidacy struct {
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission int64
|
||||
Commission sdk.Rat
|
||||
Description Description
|
||||
}
|
||||
|
||||
editCandidacy(tx TxEditCandidacy):
|
||||
candidate = loadCandidate(store, tx.PubKey)
|
||||
if candidate == nil or candidate.Status == Revoked return
|
||||
validator = getValidator(tx.ValidatorAddr)
|
||||
|
||||
if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey
|
||||
if tx.Commission >= 0 candidate.Commission = tx.Commission
|
||||
if tx.Description != nil candidate.Description = tx.Description
|
||||
if tx.Commission > CommissionMax || tx.Commission < 0 then fail
|
||||
if rateChange(tx.Commission) > CommissionMaxChange then fail
|
||||
validator.Commission = tx.Commission
|
||||
|
||||
if tx.GovernancePubKey != nil validator.GovernancePubKey = tx.GovernancePubKey
|
||||
if tx.Description != nil validator.Description = tx.Description
|
||||
|
||||
saveCandidate(store, candidate)
|
||||
setValidator(store, validator)
|
||||
return
|
||||
```
|
||||
|
||||
### TxDelegate
|
||||
### TxDelegation
|
||||
|
||||
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`.
|
||||
Within this transaction the delegator provides coins, and in return receives
|
||||
some amount of their validator's delegator-shares that are assigned to
|
||||
`Delegation.Shares`.
|
||||
|
||||
```golang
|
||||
type TxDelegate struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
Amount sdk.Coin
|
||||
}
|
||||
|
||||
delegate(tx TxDelegate):
|
||||
candidate = loadCandidate(store, tx.PubKey)
|
||||
if candidate == nil return
|
||||
return delegateWithCandidate(tx, candidate)
|
||||
pool = getPool()
|
||||
if validator.Status == Revoked return
|
||||
|
||||
delegateWithCandidate(tx TxDelegate, candidate Candidate):
|
||||
if candidate.Status == Revoked return
|
||||
|
||||
if candidate.Status == Bonded
|
||||
poolAccount = params.HoldBonded
|
||||
else
|
||||
poolAccount = params.HoldUnbonded
|
||||
delegation = getDelegatorBond(DelegatorAddr, ValidatorAddr)
|
||||
if delegation == nil then delegation = NewDelegation(DelegatorAddr, ValidatorAddr)
|
||||
|
||||
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
|
||||
validator, pool, issuedDelegatorShares = validator.addTokensFromDel(tx.Amount, pool)
|
||||
delegation.Shares += issuedDelegatorShares
|
||||
|
||||
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
|
||||
|
||||
setDelegation(delegation)
|
||||
updateValidator(validator)
|
||||
setPool(pool)
|
||||
return
|
||||
```
|
||||
|
||||
### TxUnbond
|
||||
### TxStartUnbonding
|
||||
|
||||
Delegator unbonding is defined with the following transaction:
|
||||
|
||||
```golang
|
||||
type TxUnbond struct {
|
||||
PubKey crypto.PubKey
|
||||
Shares rational.Rat
|
||||
type TxStartUnbonding struct {
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
Shares string
|
||||
}
|
||||
|
||||
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)
|
||||
startUnbonding(tx TxStartUnbonding):
|
||||
delegation, found = getDelegatorBond(store, sender, tx.PubKey)
|
||||
if !found == nil return
|
||||
|
||||
if revokeCandidacy
|
||||
if candidate.Status == Bonded then bondedToUnbondedPool(candidate)
|
||||
candidate.Status = Revoked
|
||||
if bond.Shares < tx.Shares
|
||||
return ErrNotEnoughBondShares
|
||||
|
||||
if candidate.IssuedDelegatorShares.IsZero()
|
||||
removeCandidate(store, tx.PubKey)
|
||||
else
|
||||
saveCandidate(store, candidate)
|
||||
validator, found = GetValidator(tx.ValidatorAddr)
|
||||
if !found {
|
||||
return err
|
||||
|
||||
saveGlobalState(store, gs)
|
||||
return
|
||||
bond.Shares -= tx.Shares
|
||||
|
||||
removeShares(candidate Candidate, shares rational.Rat):
|
||||
globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares
|
||||
revokeCandidacy = false
|
||||
if bond.Shares.IsZero() {
|
||||
|
||||
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
|
||||
if bond.DelegatorAddr == validator.Owner && validator.Revoked == false
|
||||
revokeCandidacy = true
|
||||
|
||||
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
|
||||
removeDelegation( bond)
|
||||
else
|
||||
bond.Height = currentBlockHeight
|
||||
setDelegation(bond)
|
||||
|
||||
return transfer(address of the bonded pool, address of the unbonded pool, removedTokens)
|
||||
pool = GetPool()
|
||||
validator, pool, returnAmount = validator.removeDelShares(pool, tx.Shares)
|
||||
setPool( pool)
|
||||
|
||||
unbondingDelegation = NewUnbondingDelegation(sender, returnAmount, currentHeight/Time, startSlashRatio)
|
||||
setUnbondingDelegation(unbondingDelegation)
|
||||
|
||||
if revokeCandidacy
|
||||
validator.Revoked = true
|
||||
|
||||
validator = updateValidator(validator)
|
||||
|
||||
if validator.DelegatorShares == 0 {
|
||||
removeValidator(validator.Owner)
|
||||
|
||||
return
|
||||
```
|
||||
|
||||
### TxRedelegate
|
||||
### TxCompleteUnbonding
|
||||
|
||||
The re-delegation command allows delegators to switch validators while still
|
||||
receiving equal reward to as if they had never unbonded.
|
||||
Complete the unbonding and transfer the coins to the delegate. Perform any
|
||||
slashing that occurred during the unbonding period.
|
||||
|
||||
```golang
|
||||
type TxRedelegate struct {
|
||||
PubKeyFrom crypto.PubKey
|
||||
PubKeyTo crypto.PubKey
|
||||
Shares rational.Rat
|
||||
type TxUnbondingComplete struct {
|
||||
DelegatorAddr sdk.Address
|
||||
ValidatorAddr sdk.Address
|
||||
}
|
||||
|
||||
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)
|
||||
redelegationComplete(tx TxRedelegate):
|
||||
unbonding = getUnbondingDelegation(tx.DelegatorAddr, tx.Validator)
|
||||
if unbonding.CompleteTime >= CurrentBlockTime && unbonding.CompleteHeight >= CurrentBlockHeight
|
||||
validator = GetValidator(tx.ValidatorAddr)
|
||||
returnTokens = ExpectedTokens * tx.startSlashRatio/validator.SlashRatio
|
||||
AddCoins(unbonding.DelegatorAddr, returnTokens)
|
||||
removeUnbondingDelegation(unbonding)
|
||||
return
|
||||
```
|
||||
|
||||
### TxProveLive
|
||||
### TxRedelegation
|
||||
|
||||
If a validator was automatically unbonded due to liveness issues and wishes to
|
||||
assert it is still online, it can send `TxProveLive`:
|
||||
The redelegation command allows delegators to instantly switch validators. Once
|
||||
the unbonding period has passed, the redelegation must be completed with
|
||||
txRedelegationComplete.
|
||||
|
||||
```golang
|
||||
type TxProveLive struct {
|
||||
PubKey crypto.PubKey
|
||||
type TxRedelegate struct {
|
||||
DelegatorAddr Address
|
||||
ValidatorFrom Validator
|
||||
ValidatorTo Validator
|
||||
Shares sdk.Rat
|
||||
CompletedTime int64
|
||||
}
|
||||
|
||||
redelegate(tx TxRedelegate):
|
||||
|
||||
pool = getPool()
|
||||
delegation = getDelegatorBond(tx.DelegatorAddr, tx.ValidatorFrom.Owner)
|
||||
if delegation == nil
|
||||
return
|
||||
|
||||
if delegation.Shares < tx.Shares
|
||||
return
|
||||
delegation.shares -= Tx.Shares
|
||||
validator, pool, createdCoins = validator.RemoveShares(pool, tx.Shares)
|
||||
setPool(pool)
|
||||
|
||||
redelegation = newRedelegation(tx.DelegatorAddr, tx.validatorFrom,
|
||||
tx.validatorTo, tx.Shares, createdCoins, tx.CompletedTime)
|
||||
setRedelegation(redelegation)
|
||||
return
|
||||
```
|
||||
|
||||
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.
|
||||
### TxCompleteRedelegation
|
||||
|
||||
Note that unlike TxCompleteUnbonding slashing of redelegating shares does not
|
||||
take place during completion. Slashing on redelegated shares takes place
|
||||
actively as a slashing occurs.
|
||||
|
||||
```golang
|
||||
type TxRedelegationComplete struct {
|
||||
DelegatorAddr Address
|
||||
ValidatorFrom Validator
|
||||
ValidatorTo Validator
|
||||
}
|
||||
|
||||
redelegationComplete(tx TxRedelegate):
|
||||
redelegation = getRedelegation(tx.DelegatorAddr, tx.validatorFrom, tx.validatorTo)
|
||||
if redelegation.CompleteTime >= CurrentBlockTime && redelegation.CompleteHeight >= CurrentBlockHeight
|
||||
removeRedelegation(redelegation)
|
||||
return
|
||||
```
|
||||
TODO: pseudo-code
|
||||
|
||||
### Update Validators
|
||||
|
||||
Within many transactions the validator set must be updated based on changes in
|
||||
power to a single validator. This process also updates the Tendermint-Updates
|
||||
store for use in end-block when validators are either added or kicked from the
|
||||
Tendermint.
|
||||
|
||||
```golang
|
||||
updateBondedValidators(newValidator Validator) (updatedVal Validator)
|
||||
|
||||
kickCliffValidator = false
|
||||
oldCliffValidatorAddr = getCliffValidator(ctx)
|
||||
|
||||
// add the actual validator power sorted store
|
||||
maxValidators = GetParams(ctx).MaxValidators
|
||||
iterator = ReverseSubspaceIterator(ValidatorsByPowerKey) // largest to smallest
|
||||
bondedValidatorsCount = 0
|
||||
var validator Validator
|
||||
for {
|
||||
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
|
||||
|
||||
if bondedValidatorsCount == int(maxValidators) { // is cliff validator
|
||||
setCliffValidator(ctx, validator, GetPool(ctx))
|
||||
iterator.Close()
|
||||
break
|
||||
|
||||
// either retrieve the original validator from the store,
|
||||
// or under the situation that this is the "new validator" just
|
||||
// use the validator provided because it has not yet been updated
|
||||
// in the main validator store
|
||||
|
||||
ownerAddr = iterator.Value()
|
||||
if bytes.Equal(ownerAddr, newValidator.Owner) {
|
||||
validator = newValidator
|
||||
else
|
||||
validator = getValidator(ownerAddr)
|
||||
|
||||
// if not previously a validator (and unrevoked),
|
||||
// kick the cliff validator / bond this new validator
|
||||
if validator.Status() != Bonded && !validator.Revoked {
|
||||
kickCliffValidator = true
|
||||
|
||||
validator = bondValidator(ctx, store, validator)
|
||||
if bytes.Equal(ownerAddr, newValidator.Owner) {
|
||||
updatedVal = validator
|
||||
|
||||
bondedValidatorsCount++
|
||||
iterator.Next()
|
||||
|
||||
// perform the actual kicks
|
||||
if oldCliffValidatorAddr != nil && kickCliffValidator {
|
||||
validator = getValidator(store, oldCliffValidatorAddr)
|
||||
unbondValidator(ctx, store, validator)
|
||||
return
|
||||
|
||||
// perform all the store operations for when a validator status becomes unbonded
|
||||
unbondValidator(ctx Context, store KVStore, validator Validator)
|
||||
pool = GetPool(ctx)
|
||||
|
||||
// set the status
|
||||
validator, pool = validator.UpdateStatus(pool, Unbonded)
|
||||
setPool(ctx, pool)
|
||||
|
||||
// save the now unbonded validator record
|
||||
setValidator(validator)
|
||||
|
||||
// add to accumulated changes for tendermint
|
||||
setTendermintUpdates(validator.abciValidatorZero)
|
||||
|
||||
// also remove from the bonded validators index
|
||||
removeValidatorsBonded(validator)
|
||||
}
|
||||
|
||||
// perform all the store operations for when a validator status becomes bonded
|
||||
bondValidator(ctx Context, store KVStore, validator Validator) Validator
|
||||
pool = GetPool(ctx)
|
||||
|
||||
// set the status
|
||||
validator, pool = validator.UpdateStatus(pool, Bonded)
|
||||
setPool(ctx, pool)
|
||||
|
||||
// save the now bonded validator record to the three referenced stores
|
||||
setValidator(validator)
|
||||
setValidatorsBonded(validator)
|
||||
|
||||
// add to accumulated changes for tendermint
|
||||
setTendermintUpdates(validator.abciValidator)
|
||||
|
||||
return validator
|
||||
```
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
@@ -1,82 +0,0 @@
|
||||
Testnet Setup
|
||||
=============
|
||||
|
||||
**Note:** This document is incomplete and may not be up-to-date with the state of the code.
|
||||
|
||||
See the `installation guide <../sdk/install.html>`__ for details on installation.
|
||||
|
||||
Here is a quick example to get you off your feet:
|
||||
|
||||
First, generate a couple of genesis transactions to be incorparated into the genesis file, this will create two keys with the password ``1234567890``
|
||||
|
||||
::
|
||||
|
||||
gaiad init gen-tx --name=foo --home=$HOME/.gaiad1
|
||||
gaiad init gen-tx --name=bar --home=$HOME/.gaiad2
|
||||
gaiacli keys list
|
||||
|
||||
**Note:** If you've already run these tests you may need to overwrite keys using the ``--OWK`` flag
|
||||
When you list the keys you should see two addresses, we'll need these later so take note.
|
||||
Now let's actually create the genesis files for both nodes:
|
||||
|
||||
::
|
||||
|
||||
cp -a ~/.gaiad2/config/gentx/. ~/.gaiad1/config/gentx/
|
||||
cp -a ~/.gaiad1/config/gentx/. ~/.gaiad2/config/gentx/
|
||||
gaiad init --gen-txs --home=$HOME/.gaiad1 --chain-id=test-chain
|
||||
gaiad init --gen-txs --home=$HOME/.gaiad2 --chain-id=test-chain
|
||||
|
||||
**Note:** If you've already run these tests you may need to overwrite genesis using the ``-o`` flag
|
||||
What we just did is copy the genesis transactions between each of the nodes so there is a common genesis transaction set; then we created both genesis files independently from each home directory. Importantly both nodes have independently created their ``genesis.json`` and ``config.toml`` files, which should be identical between nodes.
|
||||
|
||||
Great, now that we've initialized the chains, we can start both nodes in the background:
|
||||
|
||||
::
|
||||
|
||||
gaiad start --home=$HOME/.gaiad1 &> gaia1.log &
|
||||
NODE1_PID=$!
|
||||
gaia start --home=$HOME/.gaiad2 &> gaia2.log &
|
||||
NODE2_PID=$!
|
||||
|
||||
Note that we save the PID so we can later kill the processes. You can peak at your logs with ``tail gaia1.log``, or follow them for a bit with ``tail -f gaia1.log``.
|
||||
|
||||
Nice. We can also lookup the validator set:
|
||||
|
||||
::
|
||||
|
||||
gaiacli validatorset
|
||||
|
||||
Then, we try to transfer some ``steak`` to another account:
|
||||
|
||||
::
|
||||
|
||||
gaiacli account <FOO-ADDR>
|
||||
gaiacli account <BAR-ADDR>
|
||||
gaiacli send --amount=10steak --to=<BAR-ADDR> --name=foo --chain-id=test-chain
|
||||
|
||||
**Note:** We need to be careful with the ``chain-id`` and ``sequence``
|
||||
|
||||
Check the balance & sequence with:
|
||||
|
||||
::
|
||||
|
||||
gaiacli account <BAR-ADDR>
|
||||
|
||||
To confirm for certain the new validator is active, check tendermint:
|
||||
|
||||
::
|
||||
|
||||
curl localhost:46657/validators
|
||||
|
||||
Finally, to relinquish all your power, unbond some coins. You should see your VotingPower reduce and your account balance increase.
|
||||
|
||||
::
|
||||
|
||||
gaiacli unbond --chain-id=<chain-id> --name=test
|
||||
|
||||
That's it!
|
||||
|
||||
**Note:** TODO demonstrate edit-candidacy
|
||||
**Note:** TODO demonstrate delegation
|
||||
**Note:** TODO demonstrate unbond of delegation
|
||||
**Note:** TODO demonstrate unbond candidate
|
||||
Reference in New Issue
Block a user