cleanup _attic
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
Using The Staking Module
|
||||
========================
|
||||
|
||||
This project is a demonstration of the Cosmos Hub staking functionality; it is
|
||||
designed to get validator acquianted with staking concepts and procedures.
|
||||
|
||||
Potential validators will be declaring their candidacy, after which users can
|
||||
delegate and, if they so wish, unbond. This can be practiced using a local or
|
||||
public testnet.
|
||||
|
||||
This example covers initial setup of a two-node testnet between a server in the cloud and a local machine. Begin this tutorial from a cloud machine that you've ``ssh``'d into.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
The ``gaiad`` and ``gaiacli`` binaries:
|
||||
|
||||
::
|
||||
|
||||
go get github.com/cosmos/cosmos-sdk
|
||||
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
|
||||
make get_vendor_deps
|
||||
make install
|
||||
|
||||
Let's jump right into it. First, we initialize some default files:
|
||||
|
||||
::
|
||||
|
||||
gaiad init
|
||||
|
||||
which will output:
|
||||
|
||||
::
|
||||
|
||||
I[03-30|11:20:13.365] Found private validator module=main path=/root/.gaiad/config/priv_validator.json
|
||||
I[03-30|11:20:13.365] Found genesis file module=main path=/root/.gaiad/config/genesis.json
|
||||
Secret phrase to access coins:
|
||||
citizen hungry tennis noise park hire glory exercise link glow dolphin labor design grit apple abandon
|
||||
|
||||
This tell us we have a ``priv_validator.json`` and ``genesis.json`` in the ``~/.gaiad/config`` directory. A ``config.toml`` was also created in the same directory. It is a good idea to get familiar with those files. Write down the seed.
|
||||
|
||||
The next thing we'll need to is add the key from ``priv_validator.json`` to the ``gaiacli`` key manager. For this we need a seed and a password:
|
||||
|
||||
::
|
||||
|
||||
gaiacli keys add alice --recover
|
||||
|
||||
which will give you three prompts:
|
||||
|
||||
::
|
||||
|
||||
Enter a passphrase for your key:
|
||||
Repeat the passphrase:
|
||||
Enter your recovery seed phrase:
|
||||
|
||||
create a password and copy in your seed phrase. The name and address of the key will be output:
|
||||
|
||||
::
|
||||
NAME: ADDRESS: PUBKEY:
|
||||
alice 67997DD03D527EB439B7193F2B813B05B219CC02 1624DE6220BB89786C1D597050438C728202436552C6226AB67453CDB2A4D2703402FB52B6
|
||||
|
||||
You can see all available keys with:
|
||||
|
||||
::
|
||||
|
||||
gaiacli keys list
|
||||
|
||||
Setup Testnet
|
||||
-------------
|
||||
|
||||
Next, we start the daemon (do this in another window):
|
||||
|
||||
::
|
||||
|
||||
gaiad start
|
||||
|
||||
and you'll see blocks start streaming through.
|
||||
|
||||
For this example, we're doing the above on a cloud machine. The next steps should be done on your local machine or another server in the cloud, which will join the running testnet then bond/unbond.
|
||||
|
||||
Accounts
|
||||
--------
|
||||
|
||||
We have:
|
||||
|
||||
- ``alice`` the initial validator (in the cloud)
|
||||
- ``bob`` receives tokens from ``alice`` then declares candidacy (from local machine)
|
||||
- ``charlie`` will bond and unbond to ``bob`` (from local machine)
|
||||
|
||||
Remember that ``alice`` was already created. On your second machine, install the binaries and create two new keys:
|
||||
|
||||
::
|
||||
|
||||
gaiacli keys add bob
|
||||
gaiacli keys add charlie
|
||||
|
||||
both of which will prompt you for a password. Now we need to copy the ``genesis.json`` and ``config.toml`` from the first machine (with ``alice``) to the second machine. This is a good time to look at both these files.
|
||||
|
||||
The ``genesis.json`` should look something like:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"app_state": {
|
||||
"accounts": [
|
||||
{
|
||||
"address": "1D9B2356CAADF46D3EE3488E3CCE3028B4283DEE",
|
||||
"coins": [
|
||||
{
|
||||
"denom": "steak",
|
||||
"amount": 100000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"stake": {
|
||||
"pool": {
|
||||
"total_supply": 0,
|
||||
"bonded_shares": {
|
||||
"num": 0,
|
||||
"denom": 1
|
||||
},
|
||||
"unbonded_shares": {
|
||||
"num": 0,
|
||||
"denom": 1
|
||||
},
|
||||
"bonded_pool": 0,
|
||||
"unbonded_pool": 0,
|
||||
"inflation_last_time": 0,
|
||||
"inflation": {
|
||||
"num": 7,
|
||||
"denom": 100
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"inflation_rate_change": {
|
||||
"num": 13,
|
||||
"denom": 100
|
||||
},
|
||||
"inflation_max": {
|
||||
"num": 20,
|
||||
"denom": 100
|
||||
},
|
||||
"inflation_min": {
|
||||
"num": 7,
|
||||
"denom": 100
|
||||
},
|
||||
"goal_bonded": {
|
||||
"num": 67,
|
||||
"denom": 100
|
||||
},
|
||||
"max_validators": 100,
|
||||
"bond_denom": "steak"
|
||||
}
|
||||
}
|
||||
},
|
||||
"validators": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "AC26791624DE60",
|
||||
"value": "rgpc/ctVld6RpSfwN5yxGBF17R1PwMTdhQ9gKVUZp5g="
|
||||
},
|
||||
"power": 10,
|
||||
"name": ""
|
||||
}
|
||||
],
|
||||
"app_hash": "",
|
||||
"genesis_time": "0001-01-01T00:00:00Z",
|
||||
"chain_id": "test-chain-Uv1EVU"
|
||||
}
|
||||
|
||||
|
||||
To notice is that the ``accounts`` field has a an address and a whole bunch of "mycoin". This is ``alice``'s address (todo: dbl check). Under ``validators`` we see the ``pub_key.data`` field, which will match the same field in the ``priv_validator.json`` file.
|
||||
|
||||
The ``config.toml`` is long so let's focus on one field:
|
||||
|
||||
::
|
||||
|
||||
# Comma separated list of seed nodes to connect to
|
||||
seeds = ""
|
||||
|
||||
On the ``alice`` cloud machine, we don't need to do anything here. Instead, we need its IP address. After copying this file (and the ``genesis.json`` to your local machine, you'll want to put the IP in the ``seeds = "138.197.161.74"`` field, in this case, we have a made-up IP. For joining testnets with many nodes, you can add more comma-seperated IPs to the list.
|
||||
|
||||
|
||||
Now that your files are all setup, it's time to join the network. On your local machine, run:
|
||||
|
||||
::
|
||||
|
||||
gaiad start
|
||||
|
||||
and your new node will connect to the running validator (``alice``).
|
||||
|
||||
Sending Tokens
|
||||
--------------
|
||||
|
||||
We'll have ``alice`` send some ``mycoin`` to ``bob``, who has now joined the network:
|
||||
|
||||
::
|
||||
|
||||
gaiacli send --amount=1000mycoin --sequence=0 --name=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:
|
||||
|
||||
::
|
||||
|
||||
Please enter passphrase for alice:
|
||||
{
|
||||
"check_tx": {
|
||||
"gas": 30
|
||||
},
|
||||
"deliver_tx": {
|
||||
"tags": [
|
||||
{
|
||||
"key": "height",
|
||||
"value_type": 1,
|
||||
"value_int": 2963
|
||||
},
|
||||
{
|
||||
"key": "coin.sender",
|
||||
"value_string": "5D93A6059B6592833CBC8FA3DA90EE0382198985"
|
||||
},
|
||||
{
|
||||
"key": "coin.receiver",
|
||||
"value_string": "5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hash": "423BD7EA3C4B36AF8AFCCA381C0771F8A698BA77",
|
||||
"height": 2963
|
||||
}
|
||||
|
||||
TODO: check the above with current actual output.
|
||||
|
||||
Check out ``bob``'s account, which should now have 1000 mycoin:
|
||||
|
||||
::
|
||||
|
||||
gaiacli account 5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6
|
||||
|
||||
Adding a Second Validator
|
||||
-------------------------
|
||||
|
||||
**This section is wrong/needs to be updated**
|
||||
|
||||
Next, let's add the second node as a validator.
|
||||
|
||||
First, we need the pub_key data:
|
||||
|
||||
** need to make bob a priv_Val above?
|
||||
|
||||
::
|
||||
|
||||
cat $HOME/.gaia2/priv_validator.json
|
||||
|
||||
the first part will look like:
|
||||
|
||||
::
|
||||
|
||||
{"address":"7B78527942C831E16907F10C3263D5ED933F7E99","pub_key":{"type":"ed25519","data":"96864CE7085B2E342B0F96F2E92B54B18C6CC700186238810D5AA7DFDAFDD3B2"},
|
||||
|
||||
and you want the ``pub_key`` ``data`` that starts with ``96864CE``.
|
||||
|
||||
Now ``bob`` can create a validator with that pubkey.
|
||||
|
||||
::
|
||||
|
||||
gaiacli stake create-validator --amount=10mycoin --name=bob --address-validator=<address> --pub-key=<pubkey> --moniker=bobby
|
||||
|
||||
with an output like:
|
||||
|
||||
::
|
||||
|
||||
Please enter passphrase for bob:
|
||||
{
|
||||
"check_tx": {
|
||||
"gas": 30
|
||||
},
|
||||
"deliver_tx": {},
|
||||
"hash": "2A2A61FFBA1D7A59138E0068C82CC830E5103799",
|
||||
"height": 4075
|
||||
}
|
||||
|
||||
|
||||
We should see ``bob``'s account balance decrease by 10 mycoin:
|
||||
|
||||
::
|
||||
|
||||
gaiacli account 5D93A6059B6592833CBC8FA3DA90EE0382198985
|
||||
|
||||
To confirm for certain the new validator is active, ask the tendermint node:
|
||||
|
||||
::
|
||||
|
||||
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
|
||||
start streaming again.
|
||||
|
||||
Now that ``bob`` has declared candidacy, which essentially bonded 10 mycoin and made him a validator, we're going to get ``charlie`` to delegate some coins to ``bob``.
|
||||
|
||||
Delegating
|
||||
----------
|
||||
|
||||
First let's have ``alice`` send some coins to ``charlie``:
|
||||
|
||||
::
|
||||
|
||||
gaiacli send --amount=1000mycoin --sequence=2 --name=alice --to=48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
|
||||
Then ``charlie`` will delegate some mycoin to ``bob``:
|
||||
|
||||
::
|
||||
|
||||
gaiacli stake delegate --amount=10mycoin --address-delegator=<charlie's address> --address-validator=<bob's address> --name=charlie
|
||||
|
||||
You'll see output like:
|
||||
|
||||
::
|
||||
|
||||
Please enter passphrase for charlie:
|
||||
{
|
||||
"check_tx": {
|
||||
"gas": 30
|
||||
},
|
||||
"deliver_tx": {},
|
||||
"hash": "C3443BA30FCCC1F6E3A3D6AAAEE885244F8554F0",
|
||||
"height": 51585
|
||||
}
|
||||
|
||||
And that's it. You can query ``charlie``'s account to see the decrease in mycoin.
|
||||
|
||||
To get more information about the candidate, try:
|
||||
|
||||
::
|
||||
|
||||
gaiacli stake validator <address>
|
||||
|
||||
and you'll see output similar to:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"height": 51899,
|
||||
"data": {
|
||||
"pub_key": {
|
||||
"type": "ed25519",
|
||||
"data": "52D6FCD8C92A97F7CCB01205ADF310A18411EA8FDCC10E65BF2FCDB05AD1689B"
|
||||
},
|
||||
"owner": {
|
||||
"chain": "",
|
||||
"app": "sigs",
|
||||
"addr": "5A35E4CC7B7DC0A5CB49CEA91763213A9AE92AD6"
|
||||
},
|
||||
"shares": 20,
|
||||
"voting_power": 20,
|
||||
"description": {
|
||||
"moniker": "bobby",
|
||||
"identity": "",
|
||||
"website": "",
|
||||
"details": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It's also possible the query the delegator's bond like so:
|
||||
|
||||
::
|
||||
|
||||
gaiacli stake delegation --address-delegator=<address> --address-validator=<address>
|
||||
|
||||
with an output similar to:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"height": 325782,
|
||||
"data": {
|
||||
"PubKey": {
|
||||
"type": "ed25519",
|
||||
"data": "52D6FCD8C92A97F7CCB01205ADF310A18411EA8FDCC10E65BF2FCDB05AD1689B"
|
||||
},
|
||||
"Shares": 20
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
where the ``--address-delegator`` is ``charlie``'s address and the ``--address-validator`` is ``bob``'s address.
|
||||
|
||||
|
||||
Unbonding
|
||||
---------
|
||||
|
||||
Finally, to relinquish your voting power, unbond some coins. You should see
|
||||
your VotingPower reduce and your account balance increase.
|
||||
|
||||
::
|
||||
|
||||
gaiacli stake unbond --amount=5mycoin --name=charlie --address-delegator=<address> --address-validator=<address>
|
||||
gaiacli account 48F74F48281C89E5E4BE9092F735EA519768E8EF
|
||||
|
||||
See the bond decrease with ``gaiacli stake delegation`` like above.
|
||||
@@ -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:26668"
|
||||
moniker = "anonymous"
|
||||
fast_sync = true
|
||||
db_backend = "leveldb"
|
||||
log_level = "state:info,*:error"
|
||||
|
||||
[rpc]
|
||||
laddr = "tcp://0.0.0.0:26667"
|
||||
|
||||
[p2p]
|
||||
laddr = "tcp://0.0.0.0:26666"
|
||||
seeds = "0.0.0.0:26656"
|
||||
|
||||
Start Nodes
|
||||
-----------
|
||||
|
||||
Now that we've initialized the chains, we can start both nodes:
|
||||
|
||||
NOTE: each command below must be started in separate 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:26657
|
||||
gaia client query account 5D93A6059B6592833CBC8FA3DA90EE0382198985
|
||||
|
||||
To see what tendermint considers the validator set is, use:
|
||||
|
||||
::
|
||||
|
||||
curl localhost:26657/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.
|
||||
@@ -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`
|
||||
|
||||
@@ -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:26657
|
||||
|
||||
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,675 @@
|
||||
# Stake Module
|
||||
|
||||
## Overview
|
||||
|
||||
The stake module is tasked with various core staking functionality. Through the
|
||||
stake module atoms may be bonded, delegated, and provisions/rewards are
|
||||
distributed. Atom provisions are distributed to validators and their delegators
|
||||
through share distribution of a collective pool of all staked atoms. As atoms
|
||||
are created they are added to the common pool and each share become
|
||||
proportionally worth more atoms. Fees are distributed through a similar pooling
|
||||
mechanism but where each validator and delegator maintains an adjustment factor
|
||||
to determine the true proportion of fees they are entitled too. This adjustment
|
||||
factor is updated for each delegator and validator for each block where changes
|
||||
to the voting power occurs in the network. Broken down, the stake module at a
|
||||
high level is responsible for:
|
||||
- Declaration of candidacy for becoming a validator
|
||||
- Updating Tendermint validating power to reflect slashable stake
|
||||
- Delegation and unbonding transactions
|
||||
- Implementing unbonding period
|
||||
- Provisioning Atoms
|
||||
- Managing and distributing transaction fees
|
||||
- Providing the framework for validator commission on delegators
|
||||
|
||||
### Transaction Overview
|
||||
|
||||
Available Transactions:
|
||||
- TxDeclareCandidacy
|
||||
- TxEditCandidacy
|
||||
- TxLivelinessCheck
|
||||
- TxProveLive
|
||||
- TxDelegate
|
||||
- TxUnbond
|
||||
- TxRedelegate
|
||||
|
||||
## Global State
|
||||
|
||||
`Params` and `GlobalState` represent the global persistent state of Gaia.
|
||||
`Params` is intended to remain static whereas `GlobalState` is anticipated to
|
||||
change each block.
|
||||
|
||||
``` golang
|
||||
type Params struct {
|
||||
HoldBonded Address // account where all bonded coins are held
|
||||
HoldUnbonded Address // account where all delegated but unbonded 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
|
||||
}
|
||||
```
|
||||
|
||||
``` golang
|
||||
type GlobalState struct {
|
||||
TotalSupply int64 // total supply of atom tokens
|
||||
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
|
||||
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
|
||||
BondedPool int64 // reserve of bonded tokens
|
||||
UnbondedPool int64 // reserve of unbonded tokens held with candidates
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
### The Queue
|
||||
|
||||
The queue is ordered so the next 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 `InitHeight` commence with final settlement of the unbonding and pop the
|
||||
queue. All queue elements used for unbonding share a common struct:
|
||||
|
||||
``` golang
|
||||
type QueueElem struct {
|
||||
Candidate crypto.PubKey
|
||||
InitHeight int64 // when the queue was initiated
|
||||
}
|
||||
```
|
||||
|
||||
Each `QueueElem` is persisted in the store until it is popped from the queue.
|
||||
|
||||
## Validator-Candidate
|
||||
|
||||
The `Candidate` struct holds the current state and some historical actions of
|
||||
validators or candidate-validators.
|
||||
|
||||
``` golang
|
||||
type Candidate struct {
|
||||
Status CandidateStatus
|
||||
PubKey crypto.PubKey
|
||||
GovernancePubKey crypto.PubKey
|
||||
Owner Address
|
||||
GlobalStakeShares rational.Rat
|
||||
IssuedDelegatorShares rational.Rat
|
||||
RedelegatingShares rational.Rat
|
||||
VotingPower rational.Rat
|
||||
Commission rational.Rat
|
||||
CommissionMax rational.Rat
|
||||
CommissionChangeRate rational.Rat
|
||||
CommissionChangeToday rational.Rat
|
||||
ProposerRewardPool coin.Coins
|
||||
Adjustment rational.Rat
|
||||
Description Description
|
||||
}
|
||||
|
||||
type CandidateStatus byte
|
||||
const (
|
||||
VyingUnbonded CandidateStatus = 0x00
|
||||
VyingUnbonding CandidateStatus = 0x01
|
||||
Bonded CandidateStatus = 0x02
|
||||
KickUnbonding CandidateStatus = 0x03
|
||||
KickUnbonded CandidateStatus = 0x04
|
||||
)
|
||||
|
||||
type Description struct {
|
||||
Name string
|
||||
DateBonded string
|
||||
Identity string
|
||||
Website string
|
||||
Details string
|
||||
}
|
||||
```
|
||||
|
||||
Candidate parameters are described:
|
||||
- Status: signal that the candidate is either vying for validator status
|
||||
either unbonded or unbonding, an active validator, or a kicked validator
|
||||
either unbonding or unbonded.
|
||||
- PubKey: separated key from the owner of the candidate as is used strictly
|
||||
for participating in consensus.
|
||||
- Owner: Address where coins are bonded from and unbonded to
|
||||
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
|
||||
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` if
|
||||
`Candidate.Status` is otherwise
|
||||
- IssuedDelegatorShares: Sum of all shares issued to delegators (which
|
||||
includes the candidate's self-bond) which represent each of 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 the validator is within the top 100 validators.
|
||||
- Commission: The commission rate of fees charged to any delegators
|
||||
- CommissionMax: The maximum commission rate which 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
|
||||
|
||||
validator candidacy can be declared using the `TxDeclareCandidacy` transaction.
|
||||
During this transaction a self-delegation transaction is executed to bond
|
||||
tokens which are sent in with the transaction.
|
||||
|
||||
``` golang
|
||||
type TxDeclareCandidacy struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission rational.Rat
|
||||
CommissionMax int64
|
||||
CommissionMaxChange int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
For all subsequent self-bonding, whether self-bonding or delegation the
|
||||
`TxDelegate` function should be used. In this context `TxUnbond` is used to
|
||||
unbond either delegation bonds or validator self-bonds.
|
||||
|
||||
If either the `Description` (excluding `DateBonded` which is constant),
|
||||
`Commission`, or the `GovernancePubKey` need to be updated, the
|
||||
`TxEditCandidacy` transaction should be sent from the owner account:
|
||||
|
||||
``` golang
|
||||
type TxEditCandidacy struct {
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
### Persistent State
|
||||
|
||||
Within the store, each `Candidate` is stored by validator-pubkey.
|
||||
|
||||
- key: validator-pubkey
|
||||
- value: `Candidate` object
|
||||
|
||||
A second key-value pair is also persisted in order to quickly sort though the
|
||||
group of all candidates, this second index is however not persisted through the
|
||||
merkle store.
|
||||
|
||||
- key: `Candidate.GlobalStakeShares`
|
||||
- value: `Candidate.PubKey`
|
||||
|
||||
When the set of all validators needs to be determined from the group of all
|
||||
candidates, the top candidates, sorted by GlobalStakeShares can be retrieved
|
||||
from this sorting without the need to retrieve the entire group of candidates.
|
||||
When validators are kicked from the validator set they are removed from this
|
||||
list.
|
||||
|
||||
### New Validators
|
||||
|
||||
The validator set is updated in the first block of every hour. Validators are
|
||||
taken as the first `GlobalState.MaxValidators` number of candidates with the
|
||||
greatest amount of staked atoms who have not been kicked from the validator
|
||||
set.
|
||||
|
||||
### Kicked Validators
|
||||
|
||||
Unbonding of an entire validator-candidate to a temporary liquid account occurs
|
||||
under the scenarios:
|
||||
- not enough stake to be within the validator set
|
||||
- the owner unbonds all of their staked tokens
|
||||
- validator liveliness issues
|
||||
- crosses a self-imposed safety threshold
|
||||
- minimum number of tokens staked by owner
|
||||
- minimum ratio of tokens staked by owner to delegator tokens
|
||||
|
||||
When this occurs delegator's tokens do not unbond to their personal wallets but
|
||||
begin the unbonding process to a pool where they must then transact in order to
|
||||
withdraw to their respective wallets. The following unbonding will use the
|
||||
following queue element
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondCandidate struct {
|
||||
QueueElem
|
||||
}
|
||||
```
|
||||
|
||||
If a delegator chooses to initiate an unbond or re-delegation of their shares
|
||||
while a candidate-unbond is commencing, then that unbond/re-delegation is
|
||||
subject to a reduced unbonding period based on how much time those funds have
|
||||
already spent in the unbonding queue.
|
||||
|
||||
#### Liveliness issues
|
||||
|
||||
Liveliness issues are calculated by keeping track of the block precommits in
|
||||
the block header. A queue is persisted which contains the block headers from
|
||||
all recent blocks for the duration of the unbonding period. A validator is
|
||||
defined as having livliness issues if they have not been included in more than
|
||||
33% of the blocks over:
|
||||
- The most recent 24 Hours if they have >= 20% of global stake
|
||||
- The most recent week if they have = 0% of global stake
|
||||
- Linear interpolation of the above two scenarios
|
||||
|
||||
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
|
||||
submitted.
|
||||
|
||||
``` golang
|
||||
type TxLivelinessCheck struct {
|
||||
PubKey crypto.PubKey
|
||||
RewardAccount Addresss
|
||||
}
|
||||
```
|
||||
|
||||
If the `TxLivelinessCheck is successful in kicking a validator, 5% of the
|
||||
liveliness punishment is provided as a reward to `RewardAccount`.
|
||||
|
||||
#### Validator Liveliness Proof
|
||||
|
||||
If the validator was kicked for liveliness issues and is able to regain
|
||||
liveliness then all delegators in the temporary unbonding pool which have not
|
||||
transacted to move will be bonded back to the now-live validator and begin to
|
||||
once again collect provisions and rewards. Regaining livliness is demonstrated
|
||||
by sending in a `TxProveLive` transaction:
|
||||
|
||||
``` golang
|
||||
type TxProveLive struct {
|
||||
PubKey crypto.PubKey
|
||||
}
|
||||
```
|
||||
|
||||
## Delegator bond
|
||||
|
||||
Atom holders may delegate coins to validators, under this circumstance their
|
||||
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
|
||||
associated with the shares for one validator. The sender of the transaction is
|
||||
considered to be the owner of the bond,
|
||||
|
||||
``` golang
|
||||
type DelegatorBond struct {
|
||||
Candidate crypto.PubKey
|
||||
Shares rational.Rat
|
||||
AdjustmentFeePool coin.Coins
|
||||
AdjustmentRewardPool coin.Coins
|
||||
}
|
||||
```
|
||||
|
||||
Description:
|
||||
- Candidate: pubkey of the validator candidate: bonding too
|
||||
- Shares: the number of 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``
|
||||
|
||||
Each `DelegatorBond` is individually indexed within the store by delegator
|
||||
address and candidate pubkey.
|
||||
|
||||
- key: Delegator and Candidate-Pubkey
|
||||
- value: DelegatorBond
|
||||
|
||||
|
||||
### Delegating
|
||||
|
||||
Delegator bonds are created using the TxDelegate transaction. Within this
|
||||
transaction the validator candidate queried with an amount of coins, whereby
|
||||
given the current exchange rate of candidate's delegator-shares-to-atoms the
|
||||
candidate will return shares which are assigned in `DelegatorBond.Shares`.
|
||||
|
||||
``` golang
|
||||
type TxDelegate struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
}
|
||||
```
|
||||
|
||||
### Unbonding
|
||||
|
||||
Delegator unbonding is defined by the following transaction type:
|
||||
|
||||
``` golang
|
||||
type TxUnbond struct {
|
||||
PubKey crypto.PubKey
|
||||
Shares rational.Rat
|
||||
}
|
||||
```
|
||||
|
||||
When unbonding is initiated, delegator shares are immediately removed from the
|
||||
candidate and added to a queue object.
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondDelegation struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Shares rational.Rat // amount of shares which are unbonding
|
||||
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation
|
||||
}
|
||||
```
|
||||
|
||||
In the unbonding queue - the fraction of all historical slashings on
|
||||
that validator are recorded (`StartSlashRatio`). When this queue reaches maturity
|
||||
if that total slashing applied is greater on the validator then the
|
||||
difference (amount that should have been slashed from the first validator) is
|
||||
assigned to the amount being paid out.
|
||||
|
||||
|
||||
### Re-Delegation
|
||||
|
||||
The re-delegation command allows delegators to switch validators while still
|
||||
receiving equal reward to as if you had never unbonded.
|
||||
|
||||
``` golang
|
||||
type TxRedelegate struct {
|
||||
PubKeyFrom crypto.PubKey
|
||||
PubKeyTo crypto.PubKey
|
||||
Shares rational.Rat
|
||||
}
|
||||
```
|
||||
|
||||
When re-delegation is initiated, delegator shares remain accounted for within
|
||||
the `Candidate.Shares`, the term `RedelegatingShares` is incremented and a
|
||||
queue element is created.
|
||||
|
||||
``` 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
|
||||
}
|
||||
```
|
||||
|
||||
During the unbonding period all unbonding shares do not count towards the
|
||||
voting power of a validator. Once the `QueueElemReDelegation` has reached
|
||||
maturity, the appropriate unbonding shares are removed from the `Shares` and
|
||||
`RedelegatingShares` term.
|
||||
|
||||
Note that with the current menchanism a delegator cannot redelegate funds which
|
||||
are currently redelegating.
|
||||
|
||||
### Cancel Unbonding
|
||||
|
||||
A delegator who is in the process of unbonding from a validator may use the
|
||||
re-delegate transaction to bond back to the original validator they're
|
||||
currently unbonding from (and only that validator). If initiated, the delegator
|
||||
will immediately begin to one again collect rewards from their validator.
|
||||
|
||||
|
||||
## Provision Calculations
|
||||
|
||||
Every hour atom provisions are assigned proportionally to the each slashable
|
||||
bonded token which includes re-delegating atoms but not unbonding tokens.
|
||||
|
||||
Validation provisions are payed directly to a global hold account
|
||||
(`BondedTokenPool`) and proportions of that hold account owned by each
|
||||
validator is defined as the `GlobalStakeBonded`. The tokens are payed as bonded
|
||||
tokens.
|
||||
|
||||
Here, the bonded tokens that a candidate has can be calculated as:
|
||||
|
||||
```
|
||||
globalStakeExRate = params.BondedTokenPool / params.IssuedGlobalStakeShares
|
||||
candidateCoins = candidate.GlobalStakeShares * globalStakeExRate
|
||||
```
|
||||
|
||||
If a delegator chooses to add more tokens to a validator then the amount of
|
||||
validator shares distributed is calculated on exchange rate (aka every
|
||||
delegators shares do not change value at that moment. The validator's
|
||||
accounting of distributed shares to delegators must also increased at every
|
||||
deposit.
|
||||
|
||||
```
|
||||
delegatorExRate = validatorCoins / candidate.IssuedDelegatorShares
|
||||
createShares = coinsDeposited / delegatorExRate
|
||||
candidate.IssuedDelegatorShares += createShares
|
||||
```
|
||||
|
||||
Whenever a validator has new tokens added to it, the `BondedTokenPool` is
|
||||
increased and must be reflected in the global parameter as well as the
|
||||
validators `GlobalStakeShares`. This calculation ensures that the worth of the
|
||||
`GlobalStakeShares` of other validators remains worth a constant absolute
|
||||
amount of the `BondedTokenPool`
|
||||
|
||||
```
|
||||
createdGlobalStakeShares = coinsDeposited / globalStakeExRate
|
||||
validator.GlobalStakeShares += createdGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares += createdGlobalStakeShares
|
||||
|
||||
params.BondedTokenPool += coinsDeposited
|
||||
```
|
||||
|
||||
Similarly, if a delegator wanted to unbond coins:
|
||||
|
||||
```
|
||||
coinsWithdrawn = withdrawlShares * delegatorExRate
|
||||
|
||||
destroyedGlobalStakeShares = coinsWithdrawn / globalStakeExRate
|
||||
validator.GlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.BondedTokenPool -= coinsWithdrawn
|
||||
```
|
||||
|
||||
Note that when an re-delegation occurs the shares to move are placed in an
|
||||
re-delegation queue where they continue to collect validator provisions until
|
||||
queue element matures. Although provisions are collected during re-delegation,
|
||||
re-delegation tokens do not contribute to the voting power of a validator.
|
||||
|
||||
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 previsions cycle. The
|
||||
inflation is also subject to a rate change (positive of negative) depending or
|
||||
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%.
|
||||
|
||||
```
|
||||
inflationRateChange(0) = 0
|
||||
annualInflation(0) = 0.07
|
||||
|
||||
bondedRatio = bondedTokenPool / totalTokenSupply
|
||||
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
|
||||
|
||||
annualInflation += AnnualInflationRateChange
|
||||
|
||||
if annualInflation > 0.20 then annualInflation = 0.20
|
||||
if annualInflation < 0.07 then annualInflation = 0.07
|
||||
|
||||
provisionTokensHourly = totalTokenSupply * annualInflation / (365.25*24)
|
||||
```
|
||||
|
||||
Because the validators hold a relative bonded share (`GlobalStakeShare`), when
|
||||
more bonded tokens are added proportionally to all validators the only term
|
||||
which needs to be updated is the `BondedTokenPool`. So for each previsions
|
||||
cycle:
|
||||
|
||||
```
|
||||
params.BondedTokenPool += provisionTokensHourly
|
||||
```
|
||||
|
||||
## 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 candidate 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)
|
||||
candidate.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 candidate's `count` and `adjustment`. The
|
||||
`count` represents a lazy accounting of what that candidates entitlement to the
|
||||
fee pool would be if there `VotingPower` was to never change and they were to
|
||||
never withdraw fees.
|
||||
|
||||
```
|
||||
candidate.count = candidate.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 candidate and modified
|
||||
whenever fees are withdrawn from the candidate or the voting power of the
|
||||
candidate changes. When the voting power of the candidate 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 = candidate.count / gs.count * gs.SumFeesReceived
|
||||
projectedPool = candidate.PrevPower * (height-1)
|
||||
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
|
||||
+ candidate.Power / gs.Power * gs.RecentFee
|
||||
|
||||
AdjustmentChange = simplePool - projectedPool
|
||||
candidate.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 candidate has decreased.
|
||||
|
||||
```
|
||||
candidate.AdjustmentRewardPool += withdrawn
|
||||
gs.Adjustment += withdrawn
|
||||
```
|
||||
|
||||
Now the entitled fee pool of each candidate can be lazily accounted for at
|
||||
any given block:
|
||||
|
||||
```
|
||||
candidate.feePool = candidate.simplePool - candidate.Adjustment
|
||||
```
|
||||
|
||||
So far we have covered two sources fees which can be withdrawn from: Fees from
|
||||
proposer rewards (`candidate.ProposerRewardPool`), and fees from the fee pool
|
||||
(`candidate.feePool`). However we should note that all fees from fee pool are
|
||||
subject to commission rate from the owner of the candidate. These next
|
||||
calculations outline the math behind withdrawing fee rewards as either a
|
||||
delegator to a candidate providing commission, or as the owner of a candidate
|
||||
who is receiving commission.
|
||||
|
||||
### Calculations For Delegators and Candidates
|
||||
|
||||
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 candidates entitled commission from `gs.FeesPool` and
|
||||
`candidate.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/candidate.TotalDelegatorShares * candidate.VotingPower * (1 - candidate.Commission)`
|
||||
- Delegator's entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the effective shares after
|
||||
commission is retrieved, `bond.Shares * (1 - candidate.Commission)`
|
||||
- Candidate'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,
|
||||
`candidate.VotingPower * candidate.Commission`
|
||||
- Candidate's commission entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the of commission portion
|
||||
of total delegators shares,
|
||||
`candidate.TotalDelegatorShares * candidate.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 candidate must be 0, meaning that the initial value for
|
||||
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the candidate 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.
|
||||
@@ -0,0 +1,698 @@
|
||||
# Stake Module
|
||||
|
||||
## Overview
|
||||
|
||||
The stake module is tasked with various core staking functionality,
|
||||
including validator set rotation, unbonding periods, and the
|
||||
distribution of inflationary provisions and transaction fees.
|
||||
It is designed to efficiently facilitate small numbers of
|
||||
validators (hundreds), and large numbers of delegators (tens of thousands).
|
||||
|
||||
Bonded Atoms are pooled globally and for each validator.
|
||||
Validators have shares in the global pool, and delegators
|
||||
have shares in the pool of every validator they delegate to.
|
||||
Atom provisions simply accumulate in the global pool, making
|
||||
each share worth proportionally more.
|
||||
|
||||
Validator shares can be redeemed for Atoms, but the Atoms will be locked in a queue
|
||||
for an unbonding period before they can be withdrawn to an account.
|
||||
Delegators can exchange one validator's shares for another immediately
|
||||
(ie. they can re-delegate to another validator), but must then wait the
|
||||
unbonding period before they can do it again.
|
||||
|
||||
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.
|
||||
|
||||
## State
|
||||
|
||||
The staking module persists the following to the store:
|
||||
- `GlobalState`, describing the global pools
|
||||
- a `Candidate` for each candidate validator, indexed by public key
|
||||
- a `Candidate` for each candidate validator, indexed by shares in the global pool (ie. ordered)
|
||||
- a `DelegatorBond` for each delegation to a candidate by a delegator, indexed by delegator and candidate
|
||||
public keys
|
||||
- a `Queue` of unbonding delegations (TODO)
|
||||
|
||||
### Global State
|
||||
|
||||
``` golang
|
||||
type GlobalState struct {
|
||||
TotalSupply int64 // total supply of atom tokens
|
||||
BondedShares rational.Rat // sum of all shares distributed for the BondedPool
|
||||
UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool
|
||||
BondedPool int64 // reserve of bonded tokens
|
||||
UnbondedPool int64 // reserve of unbonded tokens held with candidates
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
### Candidate
|
||||
|
||||
The `Candidate` struct holds the current state and some historical actions of
|
||||
validators or candidate-validators.
|
||||
|
||||
``` golang
|
||||
type Candidate struct {
|
||||
Status CandidateStatus
|
||||
PubKey crypto.PubKey
|
||||
GovernancePubKey crypto.PubKey
|
||||
Owner Address
|
||||
GlobalStakeShares rational.Rat
|
||||
IssuedDelegatorShares rational.Rat
|
||||
RedelegatingShares rational.Rat
|
||||
VotingPower rational.Rat
|
||||
Commission rational.Rat
|
||||
CommissionMax rational.Rat
|
||||
CommissionChangeRate rational.Rat
|
||||
CommissionChangeToday rational.Rat
|
||||
ProposerRewardPool coin.Coins
|
||||
Adjustment rational.Rat
|
||||
Description Description
|
||||
}
|
||||
|
||||
type CandidateStatus byte
|
||||
const (
|
||||
VyingUnbonded CandidateStatus = 0x00
|
||||
VyingUnbonding CandidateStatus = 0x01
|
||||
Bonded CandidateStatus = 0x02
|
||||
KickUnbonding CandidateStatus = 0x03
|
||||
KickUnbonded CandidateStatus = 0x04
|
||||
)
|
||||
|
||||
type Description struct {
|
||||
Name string
|
||||
DateBonded string
|
||||
Identity string
|
||||
Website string
|
||||
Details string
|
||||
}
|
||||
```
|
||||
|
||||
Candidate parameters are described:
|
||||
- Status: signal that the candidate is either vying for validator status
|
||||
either unbonded or unbonding, an active validator, or a kicked validator
|
||||
either unbonding or unbonded.
|
||||
- PubKey: separated key from the owner of the candidate as is used strictly
|
||||
for participating in consensus.
|
||||
- Owner: Address where coins are bonded from and unbonded to
|
||||
- GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if
|
||||
`Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` if
|
||||
`Candidate.Status` is otherwise
|
||||
- IssuedDelegatorShares: Sum of all shares issued to delegators (which
|
||||
includes the candidate's self-bond) which represent each of 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 the validator is within the top 100 validators.
|
||||
- Commission: The commission rate of fees charged to any delegators
|
||||
- CommissionMax: The maximum commission rate which 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
|
||||
|
||||
|
||||
Candidates are indexed by their `Candidate.PubKey`.
|
||||
Additionally, we index empty values by the candidates global stake shares concatenated with the public key.
|
||||
|
||||
TODO: be more precise.
|
||||
|
||||
When the set of all validators needs to be determined from the group of all
|
||||
candidates, the top candidates, sorted by GlobalStakeShares can be retrieved
|
||||
from this sorting without the need to retrieve the entire group of candidates.
|
||||
When validators are kicked from the validator set they are removed from this
|
||||
list.
|
||||
|
||||
|
||||
### DelegatorBond
|
||||
|
||||
Atom holders may delegate coins to validators, under this circumstance their
|
||||
funds are held in a `DelegatorBond`. It is owned by one delegator, and is
|
||||
associated with the shares for one validator. The sender of the transaction is
|
||||
considered to be the owner of the bond,
|
||||
|
||||
``` golang
|
||||
type DelegatorBond struct {
|
||||
Candidate crypto.PubKey
|
||||
Shares rational.Rat
|
||||
AdjustmentFeePool coin.Coins
|
||||
AdjustmentRewardPool coin.Coins
|
||||
}
|
||||
```
|
||||
|
||||
Description:
|
||||
- Candidate: pubkey of the validator candidate: bonding too
|
||||
- Shares: the number of 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``
|
||||
|
||||
Each `DelegatorBond` is individually indexed within the store by delegator
|
||||
address and candidate pubkey.
|
||||
|
||||
- key: Delegator and Candidate-Pubkey
|
||||
- value: DelegatorBond
|
||||
|
||||
|
||||
### Unbonding Queue
|
||||
|
||||
|
||||
- main unbonding queue contains both UnbondElem and RedelegateElem
|
||||
- "queue" + <i>
|
||||
- new unbonding queue every time a val leaves the validator set
|
||||
- "queue"+ <candidate.pubkey > + <i>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
The queue is ordered so the next 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 `InitHeight` commence with final settlement of the unbonding and pop the
|
||||
queue. All queue elements used for unbonding share a common struct:
|
||||
|
||||
``` golang
|
||||
type QueueElem struct {
|
||||
Candidate crypto.PubKey
|
||||
InitHeight int64 // when the queue was initiated
|
||||
}
|
||||
```
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondCandidate struct {
|
||||
QueueElem
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
``` golang
|
||||
type QueueElemUnbondDelegation struct {
|
||||
QueueElem
|
||||
Payout Address // account to pay out to
|
||||
Shares rational.Rat // amount of shares which are unbonding
|
||||
StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
``` 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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Each `QueueElem` is persisted in the store until it is popped from the queue.
|
||||
|
||||
## Transactions
|
||||
|
||||
### TxDeclareCandidacy
|
||||
|
||||
Validator candidacy can be declared using the `TxDeclareCandidacy` transaction.
|
||||
During this transaction a self-delegation transaction is executed to bond
|
||||
tokens which are sent in with the transaction.
|
||||
|
||||
``` golang
|
||||
type TxDeclareCandidacy struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission rational.Rat
|
||||
CommissionMax int64
|
||||
CommissionMaxChange int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
### TxEditCandidacy
|
||||
|
||||
If either the `Description` (excluding `DateBonded` which is constant),
|
||||
`Commission`, or the `GovernancePubKey` need to be updated, the
|
||||
`TxEditCandidacy` transaction should be sent from the owner account:
|
||||
|
||||
``` golang
|
||||
type TxEditCandidacy struct {
|
||||
GovernancePubKey crypto.PubKey
|
||||
Commission int64
|
||||
Description Description
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### TxLivelinessCheck
|
||||
|
||||
Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is
|
||||
submitted.
|
||||
|
||||
``` golang
|
||||
type TxLivelinessCheck struct {
|
||||
PubKey crypto.PubKey
|
||||
RewardAccount Addresss
|
||||
}
|
||||
```
|
||||
|
||||
If the `TxLivelinessCheck is successful in kicking a validator, 5% of the
|
||||
liveliness punishment is provided as a reward to `RewardAccount`.
|
||||
|
||||
|
||||
### TxProveLive
|
||||
|
||||
If the validator was kicked for liveliness issues and is able to regain
|
||||
liveliness then all delegators in the temporary unbonding pool which have not
|
||||
transacted to move will be bonded back to the now-live validator and begin to
|
||||
once again collect provisions and rewards. Regaining livliness is demonstrated
|
||||
by sending in a `TxProveLive` transaction:
|
||||
|
||||
``` golang
|
||||
type TxProveLive struct {
|
||||
PubKey crypto.PubKey
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### TxDelegate
|
||||
|
||||
All bonding, whether self-bonding or delegation, is done via
|
||||
`TxDelegate`.
|
||||
|
||||
Delegator bonds are created using the TxDelegate transaction. Within this
|
||||
transaction the validator candidate queried with an amount of coins, whereby
|
||||
given the current exchange rate of candidate's delegator-shares-to-atoms the
|
||||
candidate will return shares which are assigned in `DelegatorBond.Shares`.
|
||||
|
||||
``` golang
|
||||
type TxDelegate struct {
|
||||
PubKey crypto.PubKey
|
||||
Amount coin.Coin
|
||||
}
|
||||
```
|
||||
|
||||
### TxUnbond
|
||||
|
||||
|
||||
In this context `TxUnbond` is used to
|
||||
unbond either delegation bonds or validator self-bonds.
|
||||
|
||||
Delegator unbonding is defined by the following transaction type:
|
||||
|
||||
``` golang
|
||||
type TxUnbond struct {
|
||||
PubKey crypto.PubKey
|
||||
Shares rational.Rat
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### TxRedelegate
|
||||
|
||||
The re-delegation command allows delegators to switch validators while still
|
||||
receiving equal reward to as if you had never unbonded.
|
||||
|
||||
``` golang
|
||||
type TxRedelegate struct {
|
||||
PubKeyFrom crypto.PubKey
|
||||
PubKeyTo crypto.PubKey
|
||||
Shares rational.Rat
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
A delegator who is in the process of unbonding from a validator may use the
|
||||
re-delegate transaction to bond back to the original validator they're
|
||||
currently unbonding from (and only that validator). If initiated, the delegator
|
||||
will immediately begin to one again collect rewards from their validator.
|
||||
|
||||
### TxWithdraw
|
||||
|
||||
....
|
||||
|
||||
|
||||
## EndBlock
|
||||
|
||||
### Update Validators
|
||||
|
||||
The validator set is updated in the first block of every hour. Validators are
|
||||
taken as the first `GlobalState.MaxValidators` number of candidates with the
|
||||
greatest amount of staked atoms who have not been kicked from the validator
|
||||
set.
|
||||
|
||||
Unbonding of an entire validator-candidate to a temporary liquid account occurs
|
||||
under the scenarios:
|
||||
- not enough stake to be within the validator set
|
||||
- the owner unbonds all of their staked tokens
|
||||
- validator liveliness issues
|
||||
- crosses a self-imposed safety threshold
|
||||
- minimum number of tokens staked by owner
|
||||
- minimum ratio of tokens staked by owner to delegator tokens
|
||||
|
||||
When this occurs delegator's tokens do not unbond to their personal wallets but
|
||||
begin the unbonding process to a pool where they must then transact in order to
|
||||
withdraw to their respective wallets.
|
||||
|
||||
### Unbonding
|
||||
|
||||
When unbonding is initiated, delegator shares are immediately removed from the
|
||||
candidate and added to a queue object.
|
||||
|
||||
In the unbonding queue - the fraction of all historical slashings on
|
||||
that validator are recorded (`StartSlashRatio`). When this queue reaches maturity
|
||||
if that total slashing applied is greater on the validator then the
|
||||
difference (amount that should have been slashed from the first validator) is
|
||||
assigned to the amount being paid out.
|
||||
|
||||
|
||||
#### Liveliness issues
|
||||
|
||||
Liveliness issues are calculated by keeping track of the block precommits in
|
||||
the block header. A queue is persisted which contains the block headers from
|
||||
all recent blocks for the duration of the unbonding period.
|
||||
|
||||
A validator is defined as having livliness issues if they have not been included in more than
|
||||
33% of the blocks over:
|
||||
- The most recent 24 Hours if they have >= 20% of global stake
|
||||
- The most recent week if they have = 0% of global stake
|
||||
- Linear interpolation of the above two scenarios
|
||||
|
||||
|
||||
## Invariants
|
||||
|
||||
-----------------------------
|
||||
|
||||
------------
|
||||
|
||||
|
||||
|
||||
|
||||
If a delegator chooses to initiate an unbond or re-delegation of their shares
|
||||
while a candidate-unbond is commencing, then that unbond/re-delegation is
|
||||
subject to a reduced unbonding period based on how much time those funds have
|
||||
already spent in the unbonding queue.
|
||||
|
||||
### Re-Delegation
|
||||
|
||||
When re-delegation is initiated, delegator shares remain accounted for within
|
||||
the `Candidate.Shares`, the term `RedelegatingShares` is incremented and a
|
||||
queue element is created.
|
||||
|
||||
During the unbonding period all unbonding shares do not count towards the
|
||||
voting power of a validator. Once the `QueueElemReDelegation` has reached
|
||||
maturity, the appropriate unbonding shares are removed from the `Shares` and
|
||||
`RedelegatingShares` term.
|
||||
|
||||
Note that with the current menchanism a delegator cannot redelegate funds which
|
||||
are currently redelegating.
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
## Provision Calculations
|
||||
|
||||
Every hour atom provisions are assigned proportionally to the each slashable
|
||||
bonded token which includes re-delegating atoms but not unbonding tokens.
|
||||
|
||||
Validation provisions are payed directly to a global hold account
|
||||
(`BondedTokenPool`) and proportions of that hold account owned by each
|
||||
validator is defined as the `GlobalStakeBonded`. The tokens are payed as bonded
|
||||
tokens.
|
||||
|
||||
Here, the bonded tokens that a candidate has can be calculated as:
|
||||
|
||||
```
|
||||
globalStakeExRate = params.BondedTokenPool / params.IssuedGlobalStakeShares
|
||||
candidateCoins = candidate.GlobalStakeShares * globalStakeExRate
|
||||
```
|
||||
|
||||
If a delegator chooses to add more tokens to a validator then the amount of
|
||||
validator shares distributed is calculated on exchange rate (aka every
|
||||
delegators shares do not change value at that moment. The validator's
|
||||
accounting of distributed shares to delegators must also increased at every
|
||||
deposit.
|
||||
|
||||
```
|
||||
delegatorExRate = validatorCoins / candidate.IssuedDelegatorShares
|
||||
createShares = coinsDeposited / delegatorExRate
|
||||
candidate.IssuedDelegatorShares += createShares
|
||||
```
|
||||
|
||||
Whenever a validator has new tokens added to it, the `BondedTokenPool` is
|
||||
increased and must be reflected in the global parameter as well as the
|
||||
validators `GlobalStakeShares`. This calculation ensures that the worth of the
|
||||
`GlobalStakeShares` of other validators remains worth a constant absolute
|
||||
amount of the `BondedTokenPool`
|
||||
|
||||
```
|
||||
createdGlobalStakeShares = coinsDeposited / globalStakeExRate
|
||||
validator.GlobalStakeShares += createdGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares += createdGlobalStakeShares
|
||||
|
||||
params.BondedTokenPool += coinsDeposited
|
||||
```
|
||||
|
||||
Similarly, if a delegator wanted to unbond coins:
|
||||
|
||||
```
|
||||
coinsWithdrawn = withdrawlShares * delegatorExRate
|
||||
|
||||
destroyedGlobalStakeShares = coinsWithdrawn / globalStakeExRate
|
||||
validator.GlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.IssuedGlobalStakeShares -= destroyedGlobalStakeShares
|
||||
params.BondedTokenPool -= coinsWithdrawn
|
||||
```
|
||||
|
||||
Note that when an re-delegation occurs the shares to move are placed in an
|
||||
re-delegation queue where they continue to collect validator provisions until
|
||||
queue element matures. Although provisions are collected during re-delegation,
|
||||
re-delegation tokens do not contribute to the voting power of a validator.
|
||||
|
||||
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 previsions cycle. The
|
||||
inflation is also subject to a rate change (positive of negative) depending or
|
||||
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%.
|
||||
|
||||
```
|
||||
inflationRateChange(0) = 0
|
||||
annualInflation(0) = 0.07
|
||||
|
||||
bondedRatio = bondedTokenPool / totalTokenSupply
|
||||
AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13
|
||||
|
||||
annualInflation += AnnualInflationRateChange
|
||||
|
||||
if annualInflation > 0.20 then annualInflation = 0.20
|
||||
if annualInflation < 0.07 then annualInflation = 0.07
|
||||
|
||||
provisionTokensHourly = totalTokenSupply * annualInflation / (365.25*24)
|
||||
```
|
||||
|
||||
Because the validators hold a relative bonded share (`GlobalStakeShare`), when
|
||||
more bonded tokens are added proportionally to all validators the only term
|
||||
which needs to be updated is the `BondedTokenPool`. So for each previsions
|
||||
cycle:
|
||||
|
||||
```
|
||||
params.BondedTokenPool += provisionTokensHourly
|
||||
```
|
||||
|
||||
## 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 candidate 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)
|
||||
candidate.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 candidate's `count` and `adjustment`. The
|
||||
`count` represents a lazy accounting of what that candidates entitlement to the
|
||||
fee pool would be if there `VotingPower` was to never change and they were to
|
||||
never withdraw fees.
|
||||
|
||||
```
|
||||
candidate.count = candidate.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 candidate and modified
|
||||
whenever fees are withdrawn from the candidate or the voting power of the
|
||||
candidate changes. When the voting power of the candidate 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 = candidate.count / gs.count * gs.SumFeesReceived
|
||||
projectedPool = candidate.PrevPower * (height-1)
|
||||
/ (gs.PrevPower * (height-1)) * gs.PrevFeesReceived
|
||||
+ candidate.Power / gs.Power * gs.RecentFee
|
||||
|
||||
AdjustmentChange = simplePool - projectedPool
|
||||
candidate.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 candidate has decreased.
|
||||
|
||||
```
|
||||
candidate.AdjustmentRewardPool += withdrawn
|
||||
gs.Adjustment += withdrawn
|
||||
```
|
||||
|
||||
Now the entitled fee pool of each candidate can be lazily accounted for at
|
||||
any given block:
|
||||
|
||||
```
|
||||
candidate.feePool = candidate.simplePool - candidate.Adjustment
|
||||
```
|
||||
|
||||
So far we have covered two sources fees which can be withdrawn from: Fees from
|
||||
proposer rewards (`candidate.ProposerRewardPool`), and fees from the fee pool
|
||||
(`candidate.feePool`). However we should note that all fees from fee pool are
|
||||
subject to commission rate from the owner of the candidate. These next
|
||||
calculations outline the math behind withdrawing fee rewards as either a
|
||||
delegator to a candidate providing commission, or as the owner of a candidate
|
||||
who is receiving commission.
|
||||
|
||||
### Calculations For Delegators and Candidates
|
||||
|
||||
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 candidates entitled commission from `gs.FeesPool` and
|
||||
`candidate.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/candidate.TotalDelegatorShares * candidate.VotingPower * (1 - candidate.Commission)`
|
||||
- Delegator's entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the effective shares after
|
||||
commission is retrieved, `bond.Shares * (1 - candidate.Commission)`
|
||||
- Candidate'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,
|
||||
`candidate.VotingPower * candidate.Commission`
|
||||
- Candidate's commission entitlement to `candidate.ProposerFeePool`
|
||||
- global power in this context is actually shares
|
||||
`candidate.TotalDelegatorShares`
|
||||
- entitled party voting power should be taken as the of commission portion
|
||||
of total delegators shares,
|
||||
`candidate.TotalDelegatorShares * candidate.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 candidate must be 0, meaning that the initial value for
|
||||
`candidate.Adjustment` must be set to the value of `canidate.Count` for the
|
||||
height which the candidate 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.
|
||||
@@ -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> --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