Release v0.16.0pull/1248/head v0.16.0
@ -0,0 +1,6 @@ | |||
<!-- Thanks for filing a PR! Before hitting the button, please check the following items.--> | |||
* [ ] Updated all relevant documentation in docs | |||
* [ ] Updated all code comments where relevant | |||
* [ ] Wrote tests | |||
* [ ] Updated CHANGELOG.md |
@ -0,0 +1 @@ | |||
2.7.14 |
@ -1,122 +1,15 @@ | |||
Tendermint Ecosystem | |||
==================== | |||
Below are the many applications built using various pieces of the Tendermint stack. We thank the community for their contributions thus far and welcome the addition of new projects. Feel free to submit a pull request to add your project! | |||
The growing list of applications built using various pieces of the Tendermint stack can be found at: | |||
ABCI Applications | |||
----------------- | |||
* https://tendermint.com/ecosystem | |||
Burrow | |||
^^^^^^ | |||
We thank the community for their contributions thus far and welcome the addition of new projects. A pull request can be submitted to `this file <https://github.com/tendermint/tendermint/blob/master/docs/ecosystem.rst>`__ to include your project. | |||
Ethereum Virtual Machine augmented with native permissioning scheme and global key-value store, written in Go, authored by Monax Industries, and incubated `by Hyperledger <https://github.com/hyperledger/burrow>`__. | |||
cb-ledger | |||
^^^^^^^^^ | |||
Custodian Bank Ledger, integrating central banking with the blockchains of tomorrow, written in C++, and `authored by Block Finance <https://github.com/block-finance/cpp-abci>`__. | |||
Clearchain | |||
^^^^^^^^^^ | |||
Application to manage a distributed ledger for money transfers that support multi-currency accounts, written in Go, and `authored by Allession Treglia <https://github.com/tendermint/clearchain>`__. | |||
Comit | |||
^^^^^ | |||
Public service reporting and tracking, written in Go, and `authored by Zach Balder <https://github.com/zbo14/comit>`__. | |||
Cosmos SDK | |||
^^^^^^^^^^ | |||
A prototypical account based crypto currency state machine supporting plugins, written in Go, and `authored by Cosmos <https://github.com/cosmos/cosmos-sdk>`__. | |||
Ethermint | |||
^^^^^^^^^ | |||
The go-ethereum state machine run as a ABCI app, written in Go, `authored by Tendermint <https://github.com/tendermint/ethermint>`__. | |||
IAVL | |||
^^^^ | |||
Immutable AVL+ tree with Merkle proofs, Written in Go, `authored by Tendermint <https://github.com/tendermint/iavl>`__. | |||
Lotion | |||
^^^^^^ | |||
A Javascript microframework for building blockchain applications with Tendermint, written in Javascript, `authored by Judd Keppel of Tendermint <https://github.com/keppel/lotion>`__. See also `lotion-chat <https://github.com/keppel/lotion-chat>`__ and `lotion-coin <https://github.com/keppel/lotion-coin>`__ apps written using Lotion. | |||
MerkleTree | |||
^^^^^^^^^^ | |||
Immutable AVL+ tree with Merkle proofs, Written in Java, `authored by jTendermint <https://github.com/jTendermint/MerkleTree>`__. | |||
Passchain | |||
^^^^^^^^^ | |||
Passchain is a tool to securely store and share passwords, tokens and other short secrets, `authored by trusch <https://github.com/trusch/passchain>`__. | |||
Passwerk | |||
^^^^^^^^ | |||
Encrypted storage web-utility backed by Tendermint, written in Go, `authored by Rigel Rozanski <https://github.com/rigelrozanski/passwerk>`__. | |||
Py-Tendermint | |||
^^^^^^^^^^^^^ | |||
A Python microframework for building blockchain applications with Tendermint, written in Python, `authored by Dave Bryson <https://github.com/davebryson/py-tendermint>`__. | |||
Stratumn | |||
^^^^^^^^ | |||
SDK for "Proof-of-Process" networks, written in Go, `authored by the Stratumn team <https://github.com/stratumn/sdk>`__. | |||
TMChat | |||
^^^^^^ | |||
P2P chat using Tendermint, written in Java, `authored by wolfposd <https://github.com/wolfposd/TMChat>`__. | |||
ABCI Servers | |||
------------ | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| **Name** | **Author** | **Language** | | |||
| | | | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `abci <https://github.com/tendermint/abci>`__ | Tendermint | Go | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `js abci <https://github.com/tendermint/js-abci>`__ | Tendermint | Javascript | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `cpp-tmsp <https://github.com/block-finance/cpp-abci>`__ | Martin Dyring | C++ | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `c-abci <https://github.com/chainx-org/c-abci>`__ | ChainX | C | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `jabci <https://github.com/jTendermint/jabci>`__ | jTendermint | Java | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `ocaml-tmsp <https://github.com/zbo14/ocaml-tmsp>`__ | Zach Balder | Ocaml | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `abci_server <https://github.com/KrzysiekJ/abci_server>`__ | Krzysztof Jurewicz | Erlang | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `rust-tsp <https://github.com/tendermint/rust-tsp>`__ | Adrian Brink | Rust | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `hs-abci <https://github.com/albertov/hs-abci>`__ | Alberto Gonzalez | Haskell | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `haskell-abci <https://github.com/cwgoes/haskell-abci>`__ | Christoper Goes | Haskell | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `Spearmint <https://github.com/dennismckinnon/spearmint>`__ | Dennis Mckinnon | Javascript | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
| `py-abci <https://github.com/davebryson/py-abci>`__ | Dave Bryson | Python | | |||
+------------------------------------------------------------------+--------------------+--------------+ | |||
Deployment Tools | |||
---------------- | |||
Other Tools | |||
----------- | |||
See `deploy testnets <./deploy-testnets.html>`__ for information about all the tools built by Tendermint. We have Kubernetes, Ansible, and Terraform integrations. | |||
Cloudsoft built `brooklyn-tendermint <https://github.com/cloudsoft/brooklyn-tendermint>`__ for deploying a tendermint testnet in docker continers. It uses Clocker for Apache Brooklyn. | |||
Dev Tools | |||
--------- | |||
For upgrading from older to newer versions of tendermint and to migrate your chain data, see `tm-migrator <https://github.com/hxzqlh/tm-tools>`__ written by @hxzqlh. |
@ -0,0 +1,142 @@ | |||
# Tendermint | |||
## Overview | |||
This is a quick start guide. If you have a vague idea about how Tendermint works | |||
and want to get started right away, continue. Otherwise, [review the documentation](http://tendermint.readthedocs.io/en/master/) | |||
## Install | |||
### Quick Install | |||
On a fresh Ubuntu 16.04 machine can be done with [this script](https://git.io/vNLfY), like so: | |||
``` | |||
curl -L https://git.io/vNLfY | bash | |||
source ~/.profile | |||
``` | |||
WARNING: do not run the above on your local machine. | |||
The script is also used to facilitate cluster deployment below. | |||
### Manual Install | |||
Requires: | |||
- `go` minimum version 1.9 | |||
- `$GOPATH` environment variable must be set | |||
- `$GOPATH/bin` must be on your `$PATH` (see https://github.com/tendermint/tendermint/wiki/Setting-GOPATH) | |||
To install Tendermint, run: | |||
``` | |||
go get github.com/tendermint/tendermint | |||
cd $GOPATH/src/github.com/tendermint/tendermint | |||
make get_tools && make get_vendor_deps | |||
make install | |||
``` | |||
Note that `go get` may return an error but it can be ignored. | |||
Confirm installation: | |||
``` | |||
$ tendermint version | |||
0.15.0-381fe19 | |||
``` | |||
## Initialization | |||
Running: | |||
``` | |||
tendermint init | |||
``` | |||
will create the required files for a single, local node. | |||
These files are found in `$HOME/.tendermint`: | |||
``` | |||
$ ls $HOME/.tendermint | |||
config.toml data genesis.json priv_validator.json | |||
``` | |||
For a single, local node, no further configuration is required. | |||
Configuring a cluster is covered further below. | |||
## Local Node | |||
Start tendermint with a simple in-process application: | |||
``` | |||
tendermint node --proxy_app=dummy | |||
``` | |||
and blocks will start to stream in: | |||
``` | |||
I[01-06|01:45:15.592] Executed block module=state height=1 validTxs=0 invalidTxs=0 | |||
I[01-06|01:45:15.624] Committed state module=state height=1 txs=0 appHash= | |||
``` | |||
Check the status with: | |||
``` | |||
curl -s localhost:46657/status | |||
``` | |||
### Sending Transactions | |||
With the dummy app running, we can send transactions: | |||
``` | |||
curl -s 'localhost:46657/broadcast_tx_commit?tx="abcd"' | |||
``` | |||
and check that it worked with: | |||
``` | |||
curl -s 'localhost:46657/abci_query?data="abcd"' | |||
``` | |||
We can send transactions with a key and value too: | |||
``` | |||
curl -s 'localhost:46657/broadcast_tx_commit?tx="name=satoshi"' | |||
``` | |||
and query the key: | |||
``` | |||
curl -s 'localhost:46657/abci_query?data="name"' | |||
``` | |||
where the value is returned in hex. | |||
## Cluster of Nodes | |||
First create four Ubuntu cloud machines. The following was tested on Digital Ocean Ubuntu 16.04 x64 (3GB/1CPU, 20GB SSD). We'll refer to their respective IP addresses below as IP1, IP2, IP3, IP4. | |||
Then, `ssh` into each machine, and execute [this script](https://git.io/vNLfY): | |||
``` | |||
curl -L https://git.io/vNLfY | bash | |||
source ~/.profile | |||
``` | |||
This will install `go` and other dependencies, get the Tendermint source code, then compile the `tendermint` binary. | |||
Next, `cd` into `docs/examples`. Each command below should be run from each node, in sequence: | |||
``` | |||
tendermint node --home ./node1 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 | |||
tendermint node --home ./node2 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 | |||
tendermint node --home ./node3 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 | |||
tendermint node --home ./node4 --proxy_app=dummy --p2p.seeds IP1:46656,IP2:46656,IP3:46656,IP4:46656 | |||
``` | |||
Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json`) have come online. Seeds can also be specified in the `config.toml`. See [this PR](https://github.com/tendermint/tendermint/pull/792) for more information about configuration options. | |||
Transactions can then be sent as covered in the single, local node example above. |
@ -0,0 +1,32 @@ | |||
#!/usr/bin/env bash | |||
# XXX: this script is meant to be used only on a fresh Ubuntu 16.04 instance | |||
# and has only been tested on Digital Ocean | |||
# get and unpack golang | |||
curl -O https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz | |||
tar -xvf go1.9.2.linux-amd64.tar.gz | |||
apt install make | |||
## move go and add binary to path | |||
mv go /usr/local | |||
echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile | |||
## create the GOPATH directory, set GOPATH and put on PATH | |||
mkdir goApps | |||
echo "export GOPATH=/root/goApps" >> ~/.profile | |||
echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile | |||
source ~/.profile | |||
## get the code and move into it | |||
REPO=github.com/tendermint/tendermint | |||
go get $REPO | |||
cd $GOPATH/src/$REPO | |||
## build | |||
git checkout v0.15.0 | |||
make get_tools | |||
make get_vendor_deps | |||
make install |
@ -0,0 +1,15 @@ | |||
# This is a TOML config file. | |||
# For more information, see https://github.com/toml-lang/toml | |||
proxy_app = "tcp://127.0.0.1:46658" | |||
moniker = "penguin" | |||
fast_sync = true | |||
db_backend = "leveldb" | |||
log_level = "state:info,*:error" | |||
[rpc] | |||
laddr = "tcp://0.0.0.0:46657" | |||
[p2p] | |||
laddr = "tcp://0.0.0.0:46656" | |||
seeds = "" |
@ -0,0 +1,42 @@ | |||
{ | |||
"genesis_time":"0001-01-01T00:00:00Z", | |||
"chain_id":"test-chain-wt7apy", | |||
"validators":[ | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" | |||
}, | |||
"power":10, | |||
"name":"node1" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" | |||
}, | |||
"power":10, | |||
"name":"node2" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" | |||
}, | |||
"power":10, | |||
"name":"node3" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" | |||
}, | |||
"power":10, | |||
"name":"node4" | |||
} | |||
], | |||
"app_hash":"" | |||
} |
@ -0,0 +1,15 @@ | |||
{ | |||
"address":"4DC2756029CE0D8F8C6C3E4C3CE6EE8C30AF352F", | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" | |||
}, | |||
"last_height":0, | |||
"last_round":0, | |||
"last_step":0, | |||
"last_signature":null, | |||
"priv_key":{ | |||
"type":"ed25519", | |||
"data":"4D3648E1D93C8703E436BFF814728B6BD270CFDFD686DF5385E8ACBEB7BE2D7DF08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" | |||
} | |||
} |
@ -0,0 +1,15 @@ | |||
# This is a TOML config file. | |||
# For more information, see https://github.com/toml-lang/toml | |||
proxy_app = "tcp://127.0.0.1:46658" | |||
moniker = "penguin" | |||
fast_sync = true | |||
db_backend = "leveldb" | |||
log_level = "state:info,*:error" | |||
[rpc] | |||
laddr = "tcp://0.0.0.0:46657" | |||
[p2p] | |||
laddr = "tcp://0.0.0.0:46656" | |||
seeds = "" |
@ -0,0 +1,42 @@ | |||
{ | |||
"genesis_time":"0001-01-01T00:00:00Z", | |||
"chain_id":"test-chain-wt7apy", | |||
"validators":[ | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" | |||
}, | |||
"power":10, | |||
"name":"node1" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" | |||
}, | |||
"power":10, | |||
"name":"node2" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" | |||
}, | |||
"power":10, | |||
"name":"node3" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" | |||
}, | |||
"power":10, | |||
"name":"node4" | |||
} | |||
], | |||
"app_hash":"" | |||
} |
@ -0,0 +1,15 @@ | |||
{ | |||
"address": "DD6C63A762608A9DDD4A845657743777F63121D6", | |||
"pub_key": { | |||
"type": "ed25519", | |||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" | |||
}, | |||
"last_height": 0, | |||
"last_round": 0, | |||
"last_step": 0, | |||
"last_signature": null, | |||
"priv_key": { | |||
"type": "ed25519", | |||
"data": "7B0DE666FF5E9B437D284BCE767F612381890C018B93B0A105D2E829A568DA6FA8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" | |||
} | |||
} |
@ -0,0 +1,15 @@ | |||
# This is a TOML config file. | |||
# For more information, see https://github.com/toml-lang/toml | |||
proxy_app = "tcp://127.0.0.1:46658" | |||
moniker = "penguin" | |||
fast_sync = true | |||
db_backend = "leveldb" | |||
log_level = "state:info,*:error" | |||
[rpc] | |||
laddr = "tcp://0.0.0.0:46657" | |||
[p2p] | |||
laddr = "tcp://0.0.0.0:46656" | |||
seeds = "" |
@ -0,0 +1,42 @@ | |||
{ | |||
"genesis_time":"0001-01-01T00:00:00Z", | |||
"chain_id":"test-chain-wt7apy", | |||
"validators":[ | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" | |||
}, | |||
"power":10, | |||
"name":"node1" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" | |||
}, | |||
"power":10, | |||
"name":"node2" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" | |||
}, | |||
"power":10, | |||
"name":"node3" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" | |||
}, | |||
"power":10, | |||
"name":"node4" | |||
} | |||
], | |||
"app_hash":"" | |||
} |
@ -0,0 +1,15 @@ | |||
{ | |||
"address": "6D6A1E313B407B5474106CA8759C976B777AB659", | |||
"pub_key": { | |||
"type": "ed25519", | |||
"data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" | |||
}, | |||
"last_height": 0, | |||
"last_round": 0, | |||
"last_step": 0, | |||
"last_signature": null, | |||
"priv_key": { | |||
"type": "ed25519", | |||
"data": "622432A370111A5C25CFE121E163FE709C9D5C95F551EDBD7A2C69A8545C9B76E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" | |||
} | |||
} |
@ -0,0 +1,15 @@ | |||
# This is a TOML config file. | |||
# For more information, see https://github.com/toml-lang/toml | |||
proxy_app = "tcp://127.0.0.1:46658" | |||
moniker = "penguin" | |||
fast_sync = true | |||
db_backend = "leveldb" | |||
log_level = "state:info,*:error" | |||
[rpc] | |||
laddr = "tcp://0.0.0.0:46657" | |||
[p2p] | |||
laddr = "tcp://0.0.0.0:46656" | |||
seeds = "" |
@ -0,0 +1,42 @@ | |||
{ | |||
"genesis_time":"0001-01-01T00:00:00Z", | |||
"chain_id":"test-chain-wt7apy", | |||
"validators":[ | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data":"F08446C80A33E10D620E21450821B58D053778528F2B583D423B3E46EC647D30" | |||
}, | |||
"power":10, | |||
"name":"node1" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "A8423F70A9E512643B4B00F7C3701ECAD1F31B0A1FAA45852C41046353B9A07F" | |||
}, | |||
"power":10, | |||
"name":"node2" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "E52EFFAEDFE1D618ECDA71DE3B23592B3612CAABA0C10826E4C3120B2198C29A" | |||
}, | |||
"power":10, | |||
"name":"node3" | |||
} | |||
, | |||
{ | |||
"pub_key":{ | |||
"type":"ed25519", | |||
"data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" | |||
}, | |||
"power":10, | |||
"name":"node4" | |||
} | |||
], | |||
"app_hash":"" | |||
} |
@ -0,0 +1,15 @@ | |||
{ | |||
"address": "829A9663611D3DD88A3D84EA0249679D650A0755", | |||
"pub_key": { | |||
"type": "ed25519", | |||
"data": "2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" | |||
}, | |||
"last_height": 0, | |||
"last_round": 0, | |||
"last_step": 0, | |||
"last_signature": null, | |||
"priv_key": { | |||
"type": "ed25519", | |||
"data": "0A604D1C9AE94A50150BF39E603239092F9392E4773F4D8F4AC1D86E6438E89E2B8FC09C07955A02998DFE5AF1AAD1C44115ECA7635FF51A867CF4265D347C07" | |||
} | |||
} |
@ -1,58 +1,182 @@ | |||
Configuration | |||
============= | |||
TendermintCore can be configured via a TOML file in | |||
``$TMHOME/config.toml``. Some of these parameters can be overridden by | |||
command-line flags. | |||
Config parameters | |||
~~~~~~~~~~~~~~~~~ | |||
The main config parameters are defined | |||
`here <https://github.com/tendermint/tendermint/blob/master/config/config.go>`__. | |||
- ``abci``: ABCI transport (socket \| grpc). *Default*: ``socket`` | |||
- ``db_backend``: Database backend for the blockchain and | |||
TendermintCore state. ``leveldb`` or ``memdb``. *Default*: | |||
``"leveldb"`` | |||
- ``db_dir``: Database dir. *Default*: ``"$TMHOME/data"`` | |||
- ``fast_sync``: Whether to sync faster from the block pool. *Default*: | |||
``true`` | |||
- ``genesis_file``: The location of the genesis file. *Default*: | |||
``"$TMHOME/genesis.json"`` | |||
- ``log_level``: *Default*: ``"state:info,*:error"`` | |||
- ``moniker``: Name of this node. *Default*: the host name or ``"anonymous"`` | |||
if runtime fails to get the host name | |||
- ``priv_validator_file``: Validator private key file. *Default*: | |||
``"$TMHOME/priv_validator.json"`` | |||
- ``prof_laddr``: Profile listen address. *Default*: ``""`` | |||
- ``proxy_app``: The ABCI app endpoint. *Default*: | |||
``"tcp://127.0.0.1:46658"`` | |||
- ``consensus.max_block_size_txs``: Maximum number of block txs. | |||
*Default*: ``10000`` | |||
- ``consensus.create_empty_blocks``: Create empty blocks w/o txs. | |||
*Default*: ``true`` | |||
- ``consensus.create_empty_blocks_interval``: Block creation interval, even if empty. | |||
- ``consensus.timeout_*``: Various consensus timeout parameters | |||
- ``consensus.wal_file``: Consensus state WAL. *Default*: | |||
``"$TMHOME/data/cs.wal/wal"`` | |||
- ``consensus.wal_light``: Whether to use light-mode for Consensus | |||
state WAL. *Default*: ``false`` | |||
- ``mempool.*``: Various mempool parameters | |||
- ``p2p.addr_book_file``: Peer address book. *Default*: | |||
``"$TMHOME/addrbook.json"``. **NOT USED** | |||
- ``p2p.laddr``: Node listen address. (0.0.0.0:0 means any interface, | |||
any port). *Default*: ``"0.0.0.0:46656"`` | |||
- ``p2p.pex``: Enable Peer-Exchange (dev feature). *Default*: ``false`` | |||
- ``p2p.seeds``: Comma delimited host:port seed nodes. *Default*: | |||
``""`` | |||
- ``p2p.skip_upnp``: Skip UPNP detection. *Default*: ``false`` | |||
- ``rpc.grpc_laddr``: GRPC listen address (BroadcastTx only). Port | |||
required. *Default*: ``""`` | |||
- ``rpc.laddr``: RPC listen address. Port required. *Default*: | |||
``"0.0.0.0:46657"`` | |||
- ``rpc.unsafe``: Enabled unsafe rpc methods. *Default*: ``true`` | |||
Tendermint Core can be configured via a TOML file in | |||
``$TMHOME/config/config.toml``. Some of these parameters can be overridden by | |||
command-line flags. For most users, the options in the ``##### main | |||
base configuration options #####`` are intended to be modified while | |||
config options further below are intended for advance power users. | |||
Config options | |||
~~~~~~~~~~~~~~ | |||
The default configuration file create by ``tendermint init`` has all | |||
the parameters set with their default values. It will look something | |||
like the file below, however, double check by inspecting the | |||
``config.toml`` created with your version of ``tendermint`` installed: | |||
:: | |||
# This is a TOML config file. | |||
# For more information, see https://github.com/toml-lang/toml | |||
##### main base config options ##### | |||
# TCP or UNIX socket address of the ABCI application, | |||
# or the name of an ABCI application compiled in with the Tendermint binary | |||
proxy_app = "tcp://127.0.0.1:46658" | |||
# A custom human readable name for this node | |||
moniker = "anonymous" | |||
# If this node is many blocks behind the tip of the chain, FastSync | |||
# allows them to catchup quickly by downloading blocks in parallel | |||
# and verifying their commits | |||
fast_sync = true | |||
# Database backend: leveldb | memdb | |||
db_backend = "leveldb" | |||
# Database directory | |||
db_path = "data" | |||
# Output level for logging | |||
log_level = "state:info,*:error" | |||
##### additional base config options ##### | |||
# The ID of the chain to join (should be signed with every transaction and vote) | |||
chain_id = "" | |||
# Path to the JSON file containing the initial validator set and other meta data | |||
genesis_file = "genesis.json" | |||
# Path to the JSON file containing the private key to use as a validator in the consensus protocol | |||
priv_validator_file = "priv_validator.json" | |||
# Mechanism to connect to the ABCI application: socket | grpc | |||
abci = "socket" | |||
# TCP or UNIX socket address for the profiling server to listen on | |||
prof_laddr = "" | |||
# If true, query the ABCI app on connecting to a new peer | |||
# so the app can decide if we should keep the connection or not | |||
filter_peers = false | |||
##### advanced configuration options ##### | |||
##### rpc server configuration options ##### | |||
[rpc] | |||
# TCP or UNIX socket address for the RPC server to listen on | |||
laddr = "tcp://0.0.0.0:46657" | |||
# TCP or UNIX socket address for the gRPC server to listen on | |||
# NOTE: This server only supports /broadcast_tx_commit | |||
grpc_laddr = "" | |||
# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool | |||
unsafe = false | |||
##### peer to peer configuration options ##### | |||
[p2p] | |||
# Address to listen for incoming connections | |||
laddr = "tcp://0.0.0.0:46656" | |||
# Comma separated list of seed nodes to connect to | |||
seeds = "" | |||
# Comma separated list of nodes to keep persistent connections to | |||
persistent_peers = "" | |||
# Path to address book | |||
addr_book_file = "addrbook.json" | |||
# Set true for strict address routability rules | |||
addr_book_strict = true | |||
# Time to wait before flushing messages out on the connection, in ms | |||
flush_throttle_timeout = 100 | |||
# Maximum number of peers to connect to | |||
max_num_peers = 50 | |||
# Maximum size of a message packet payload, in bytes | |||
max_msg_packet_payload_size = 1024 | |||
# Rate at which packets can be sent, in bytes/second | |||
send_rate = 512000 | |||
# Rate at which packets can be received, in bytes/second | |||
recv_rate = 512000 | |||
# Set true to enable the peer-exchange reactor | |||
pex = true | |||
# Seed mode, in which node constantly crawls the network and looks for | |||
# peers. If another node asks it for addresses, it responds and disconnects. | |||
# | |||
# Does not work if the peer-exchange reactor is disabled. | |||
seed_mode = false | |||
##### mempool configuration options ##### | |||
[mempool] | |||
recheck = true | |||
recheck_empty = true | |||
broadcast = true | |||
wal_dir = "data/mempool.wal" | |||
##### consensus configuration options ##### | |||
[consensus] | |||
wal_file = "data/cs.wal/wal" | |||
wal_light = false | |||
# All timeouts are in milliseconds | |||
timeout_propose = 3000 | |||
timeout_propose_delta = 500 | |||
timeout_prevote = 1000 | |||
timeout_prevote_delta = 500 | |||
timeout_precommit = 1000 | |||
timeout_precommit_delta = 500 | |||
timeout_commit = 1000 | |||
# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) | |||
skip_timeout_commit = false | |||
# BlockSize | |||
max_block_size_txs = 10000 | |||
max_block_size_bytes = 1 | |||
# EmptyBlocks mode and possible interval between empty blocks in seconds | |||
create_empty_blocks = true | |||
create_empty_blocks_interval = 0 | |||
# Reactor sleep duration parameters are in milliseconds | |||
peer_gossip_sleep_duration = 100 | |||
peer_query_maj23_sleep_duration = 2000 | |||
##### transactions indexer configuration options ##### | |||
[tx_index] | |||
# What indexer to use for transactions | |||
# | |||
# Options: | |||
# 1) "null" (default) | |||
# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). | |||
indexer = "{{ .TxIndex.Indexer }}" | |||
# Comma-separated list of tags to index (by default the only tag is tx hash) | |||
# | |||
# It's recommended to index only a subset of tags due to possible memory | |||
# bloat. This is, of course, depends on the indexer's DB and the volume of | |||
# transactions. | |||
index_tags = "{{ .TxIndex.IndexTags }}" | |||
# When set to true, tells indexer to index all tags. Note this may be not | |||
# desirable (see the comment above). IndexTags has a precedence over | |||
# IndexAllTags (i.e. when given both, IndexTags will be indexed). | |||
index_all_tags = {{ .TxIndex.IndexAllTags }} |
@ -0,0 +1,42 @@ | |||
# BFT time in Tendermint | |||
Tendermint provides a deterministic, Byzantine fault-tolerant, source of time. | |||
Time in Tendermint is defined with the Time field of the block header. | |||
It satisfies the following properties: | |||
- Time Monotonicity: Time is monotonically increasing, i.e., given | |||
a header H1 for height h1 and a header H2 for height `h2 = h1 + 1`, `H1.Time < H2.Time`. | |||
- Time Validity: Given a set of Commit votes that forms the `block.LastCommit` field, a range of | |||
valid values for the Time field of the block header is defined only by | |||
Precommit messages (from the LastCommit field) sent by correct processes, i.e., | |||
a faulty process cannot arbitrarily increase the Time value. | |||
In the context of Tendermint, time is of type int64 and denotes UNIX time in milliseconds, i.e., | |||
corresponds to the number of milliseconds since January 1, 1970. Before defining rules that need to be enforced by the | |||
Tendermint consensus protocol, so the properties above holds, we introduce the following definition: | |||
- median of a set of `Vote` messages is equal to the median of `Vote.Time` fields of the corresponding `Vote` messages | |||
We ensure Time Monotonicity and Time Validity properties by the following rules: | |||
- let rs denotes `RoundState` (consensus internal state) of some process. Then | |||
`rs.ProposalBlock.Header.Time == median(rs.LastCommit) && | |||
rs.Proposal.Timestamp == rs.ProposalBlock.Header.Time`. | |||
- Furthermore, when creating the `vote` message, the following rules for determining `vote.Time` field should hold: | |||
- if `rs.Proposal` is defined then | |||
`vote.Time = max(rs.Proposal.Timestamp + 1, time.Now())`, where `time.Now()` | |||
denotes local Unix time in milliseconds. | |||
- if `rs.Proposal` is not defined and `rs.Votes` contains +2/3 of the corresponding vote messages (votes for the | |||
current height and round, and with the corresponding type (`Prevote` or `Precommit`)), then | |||
`vote.Time = max(median(getVotes(rs.Votes, vote.Height, vote.Round, vote.Type)), time.Now())`, | |||
where `getVotes` function returns the votes for particular `Height`, `Round` and `Type`. | |||
The second rule is relevant for the case when a process jumps to a higher round upon receiving +2/3 votes for a higher | |||
round, but the corresponding `Proposal` message for the higher round hasn't been received yet. | |||
@ -0,0 +1,38 @@ | |||
# P2P Config | |||
Here we describe configuration options around the Peer Exchange. | |||
These can be set using flags or via the `$TMHOME/config/config.toml` file. | |||
## Seed Mode | |||
`--p2p.seed_mode` | |||
The node operates in seed mode. In seed mode, a node continuously crawls the network for peers, | |||
and upon incoming connection shares some peers and disconnects. | |||
## Seeds | |||
`--p2p.seeds “1.2.3.4:466656,2.3.4.5:4444”` | |||
Dials these seeds when we need more peers. They should return a list of peers and then disconnect. | |||
If we already have enough peers in the address book, we may never need to dial them. | |||
## Persistent Peers | |||
`--p2p.persistent_peers “1.2.3.4:46656,2.3.4.5:466656”` | |||
Dial these peers and auto-redial them if the connection fails. | |||
These are intended to be trusted persistent peers that can help | |||
anchor us in the p2p network. The auto-redial uses exponential | |||
backoff and will give up after a day of trying to connect. | |||
**Note:** If `seeds` and `persistent_peers` intersect, | |||
the user will be warned that seeds may auto-close connections | |||
and that the node may not be able to keep the connection persistent. | |||
## Private Persistent Peers | |||
`--p2p.private_persistent_peers “1.2.3.4:46656,2.3.4.5:466656”` | |||
These are persistent peers that we do not add to the address book or | |||
gossip to other peers. They stay private to us. |
@ -0,0 +1,110 @@ | |||
# P2P Multiplex Connection | |||
## MConnection | |||
`MConnection` is a multiplex connection that supports multiple independent streams | |||
with distinct quality of service guarantees atop a single TCP connection. | |||
Each stream is known as a `Channel` and each `Channel` has a globally unique *byte id*. | |||
Each `Channel` also has a relative priority that determines the quality of service | |||
of the `Channel` compared to other `Channel`s. | |||
The *byte id* and the relative priorities of each `Channel` are configured upon | |||
initialization of the connection. | |||
The `MConnection` supports three packet types: | |||
- Ping | |||
- Pong | |||
- Msg | |||
### Ping and Pong | |||
The ping and pong messages consist of writing a single byte to the connection; 0x1 and 0x2, respectively. | |||
When we haven't received any messages on an `MConnection` in time `pingTimeout`, we send a ping message. | |||
When a ping is received on the `MConnection`, a pong is sent in response only if there are no other messages | |||
to send and the peer has not sent us too many pings (TODO). | |||
If a pong or message is not received in sufficient time after a ping, the peer is disconnected from. | |||
### Msg | |||
Messages in channels are chopped into smaller `msgPacket`s for multiplexing. | |||
``` | |||
type msgPacket struct { | |||
ChannelID byte | |||
EOF byte // 1 means message ends here. | |||
Bytes []byte | |||
} | |||
``` | |||
The `msgPacket` is serialized using [go-wire](https://github.com/tendermint/go-wire) and prefixed with 0x3. | |||
The received `Bytes` of a sequential set of packets are appended together | |||
until a packet with `EOF=1` is received, then the complete serialized message | |||
is returned for processing by the `onReceive` function of the corresponding channel. | |||
### Multiplexing | |||
Messages are sent from a single `sendRoutine`, which loops over a select statement and results in the sending | |||
of a ping, a pong, or a batch of data messages. The batch of data messages may include messages from multiple channels. | |||
Message bytes are queued for sending in their respective channel, with each channel holding one unsent message at a time. | |||
Messages are chosen for a batch one at a time from the channel with the lowest ratio of recently sent bytes to channel priority. | |||
## Sending Messages | |||
There are two methods for sending messages: | |||
```go | |||
func (m MConnection) Send(chID byte, msg interface{}) bool {} | |||
func (m MConnection) TrySend(chID byte, msg interface{}) bool {} | |||
``` | |||
`Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued | |||
for the channel with the given id byte `chID`. The message `msg` is serialized | |||
using the `tendermint/wire` submodule's `WriteBinary()` reflection routine. | |||
`TrySend(chID, msg)` is a nonblocking call that queues the message msg in the channel | |||
with the given id byte chID if the queue is not full; otherwise it returns false immediately. | |||
`Send()` and `TrySend()` are also exposed for each `Peer`. | |||
## Peer | |||
Each peer has one `MConnection` instance, and includes other information such as whether the connection | |||
was outbound, whether the connection should be recreated if it closes, various identity information about the node, | |||
and other higher level thread-safe data used by the reactors. | |||
## Switch/Reactor | |||
The `Switch` handles peer connections and exposes an API to receive incoming messages | |||
on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one | |||
or more `Channels`. So while sending outgoing messages is typically performed on the peer, | |||
incoming messages are received on the reactor. | |||
```go | |||
// Declare a MyReactor reactor that handles messages on MyChannelID. | |||
type MyReactor struct{} | |||
func (reactor MyReactor) GetChannels() []*ChannelDescriptor { | |||
return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}} | |||
} | |||
func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) { | |||
r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error) | |||
msgString := ReadString(r, n, err) | |||
fmt.Println(msgString) | |||
} | |||
// Other Reactor methods omitted for brevity | |||
... | |||
switch := NewSwitch([]Reactor{MyReactor{}}) | |||
... | |||
// Send a random message to all outbound connections | |||
for _, peer := range switch.Peers().List() { | |||
if peer.IsOutbound() { | |||
peer.Send(MyChannelID, "Here's a random message") | |||
} | |||
} | |||
``` |
@ -0,0 +1,65 @@ | |||
# Tendermint Peer Discovery | |||
A Tendermint P2P network has different kinds of nodes with different requirements for connectivity to one another. | |||
This document describes what kind of nodes Tendermint should enable and how they should work. | |||
## Seeds | |||
Seeds are the first point of contact for a new node. | |||
They return a list of known active peers and then disconnect. | |||
Seeds should operate full nodes with the PEX reactor in a "crawler" mode | |||
that continuously explores to validate the availability of peers. | |||
Seeds should only respond with some top percentile of the best peers it knows about. | |||
See [reputation](TODO) for details on peer quality. | |||
## New Full Node | |||
A new node needs a few things to connect to the network: | |||
- a list of seeds, which can be provided to Tendermint via config file or flags, | |||
or hardcoded into the software by in-process apps | |||
- a `ChainID`, also called `Network` at the p2p layer | |||
- a recent block height, H, and hash, HASH for the blockchain. | |||
The values `H` and `HASH` must be received and corroborated by means external to Tendermint, and specific to the user - ie. via the user's trusted social consensus. | |||
This requirement to validate `H` and `HASH` out-of-band and via social consensus | |||
is the essential difference in security models between Proof-of-Work and Proof-of-Stake blockchains. | |||
With the above, the node then queries some seeds for peers for its chain, | |||
dials those peers, and runs the Tendermint protocols with those it successfully connects to. | |||
When the peer catches up to height H, it ensures the block hash matches HASH. | |||
If not, Tendermint will exit, and the user must try again - either they are connected | |||
to bad peers or their social consensus is invalid. | |||
## Restarted Full Node | |||
A node checks its address book on startup and attempts to connect to peers from there. | |||
If it can't connect to any peers after some time, it falls back to the seeds to find more. | |||
Restarted full nodes can run the `blockchain` or `consensus` reactor protocols to sync up | |||
to the latest state of the blockchain from wherever they were last. | |||
In a Proof-of-Stake context, if they are sufficiently far behind (greater than the length | |||
of the unbonding period), they will need to validate a recent `H` and `HASH` out-of-band again | |||
so they know they have synced the correct chain. | |||
## Validator Node | |||
A validator node is a node that interfaces with a validator signing key. | |||
These nodes require the highest security, and should not accept incoming connections. | |||
They should maintain outgoing connections to a controlled set of "Sentry Nodes" that serve | |||
as their proxy shield to the rest of the network. | |||
Validators that know and trust each other can accept incoming connections from one another and maintain direct private connectivity via VPN. | |||
## Sentry Node | |||
Sentry nodes are guardians of a validator node and provide it access to the rest of the network. | |||
They should be well connected to other full nodes on the network. | |||
Sentry nodes may be dynamic, but should maintain persistent connections to some evolving random subset of each other. | |||
They should always expect to have direct incoming connections from the validator node and its backup(s). | |||
They do not report the validator node's address in the PEX and | |||
they may be more strict about the quality of peers they keep. | |||
Sentry nodes belonging to validators that trust each other may wish to maintain persistent connections via VPN with one another, but only report each other sparingly in the PEX. |
@ -0,0 +1,114 @@ | |||
# Tendermint Peers | |||
This document explains how Tendermint Peers are identified and how they connect to one another. | |||
For details on peer discovery, see the [peer exchange (PEX) reactor doc](pex.md). | |||
## Peer Identity | |||
Tendermint peers are expected to maintain long-term persistent identities in the form of a public key. | |||
Each peer has an ID defined as `peer.ID == peer.PubKey.Address()`, where `Address` uses the scheme defined in go-crypto. | |||
A single peer ID can have multiple IP addresses associated with it. | |||
TODO: define how to deal with this. | |||
When attempting to connect to a peer, we use the PeerURL: `<ID>@<IP>:<PORT>`. | |||
We will attempt to connect to the peer at IP:PORT, and verify, | |||
via authenticated encryption, that it is in possession of the private key | |||
corresponding to `<ID>`. This prevents man-in-the-middle attacks on the peer layer. | |||
Peers can also be connected to without specifying an ID, ie. just `<IP>:<PORT>`. | |||
In this case, the peer must be authenticated out-of-band of Tendermint, | |||
for instance via VPN. | |||
## Connections | |||
All p2p connections use TCP. | |||
Upon establishing a successful TCP connection with a peer, | |||
two handhsakes are performed: one for authenticated encryption, and one for Tendermint versioning. | |||
Both handshakes have configurable timeouts (they should complete quickly). | |||
### Authenticated Encryption Handshake | |||
Tendermint implements the Station-to-Station protocol | |||
using ED25519 keys for Diffie-Helman key-exchange and NACL SecretBox for encryption. | |||
It goes as follows: | |||
- generate an emphemeral ED25519 keypair | |||
- send the ephemeral public key to the peer | |||
- wait to receive the peer's ephemeral public key | |||
- compute the Diffie-Hellman shared secret using the peers ephemeral public key and our ephemeral private key | |||
- generate two nonces to use for encryption (sending and receiving) as follows: | |||
- sort the ephemeral public keys in ascending order and concatenate them | |||
- RIPEMD160 the result | |||
- append 4 empty bytes (extending the hash to 24-bytes) | |||
- the result is nonce1 | |||
- flip the last bit of nonce1 to get nonce2 | |||
- if we had the smaller ephemeral pubkey, use nonce1 for receiving, nonce2 for sending; | |||
else the opposite | |||
- all communications from now on are encrypted using the shared secret and the nonces, where each nonce | |||
increments by 2 every time it is used | |||
- we now have an encrypted channel, but still need to authenticate | |||
- generate a common challenge to sign: | |||
- SHA256 of the sorted (lowest first) and concatenated ephemeral pub keys | |||
- sign the common challenge with our persistent private key | |||
- send the go-wire encoded persistent pubkey and signature to the peer | |||
- wait to receive the persistent public key and signature from the peer | |||
- verify the signature on the challenge using the peer's persistent public key | |||
If this is an outgoing connection (we dialed the peer) and we used a peer ID, | |||
then finally verify that the peer's persistent public key corresponds to the peer ID we dialed, | |||
ie. `peer.PubKey.Address() == <ID>`. | |||
The connection has now been authenticated. All traffic is encrypted. | |||
Note: only the dialer can authenticate the identity of the peer, | |||
but this is what we care about since when we join the network we wish to | |||
ensure we have reached the intended peer (and are not being MITMd). | |||
### Peer Filter | |||
Before continuing, we check if the new peer has the same ID as ourselves or | |||
an existing peer. If so, we disconnect. | |||
We also check the peer's address and public key against | |||
an optional whitelist which can be managed through the ABCI app - | |||
if the whitelist is enabled and the peer does not qualify, the connection is | |||
terminated. | |||
### Tendermint Version Handshake | |||
The Tendermint Version Handshake allows the peers to exchange their NodeInfo: | |||
```golang | |||
type NodeInfo struct { | |||
PubKey crypto.PubKey | |||
Moniker string | |||
Network string | |||
RemoteAddr string | |||
ListenAddr string | |||
Version string | |||
Channels []int8 | |||
Other []string | |||
} | |||
``` | |||
The connection is disconnected if: | |||
- `peer.NodeInfo.PubKey != peer.PubKey` | |||
- `peer.NodeInfo.Version` is not formatted as `X.X.X` where X are integers known as Major, Minor, and Revision | |||
- `peer.NodeInfo.Version` Major is not the same as ours | |||
- `peer.NodeInfo.Version` Minor is not the same as ours | |||
- `peer.NodeInfo.Network` is not the same as ours | |||
- `peer.Channels` does not intersect with our known Channels. | |||
At this point, if we have not disconnected, the peer is valid. | |||
It is added to the switch and hence all reactors via the `AddPeer` method. | |||
Note that each reactor may handle multiple channels. | |||
## Connection Activity | |||
Once a peer is added, incoming messages for a given reactor are handled through | |||
that reactor's `Receive` method, and output messages are sent directly by the Reactors | |||
on each peer. A typical reactor maintains per-peer go-routine(s) that handle this. |
@ -0,0 +1,46 @@ | |||
## Blockchain Reactor | |||
* coordinates the pool for syncing | |||
* coordinates the store for persistence | |||
* coordinates the playing of blocks towards the app using a sm.BlockExecutor | |||
* handles switching between fastsync and consensus | |||
* it is a p2p.BaseReactor | |||
* starts the pool.Start() and its poolRoutine() | |||
* registers all the concrete types and interfaces for serialisation | |||
### poolRoutine | |||
* listens to these channels: | |||
* pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends | |||
a &bcBlockRequestMessage for a specific height | |||
* pool signals timeout of a specific peer by posting to timeoutsCh | |||
* switchToConsensusTicker to periodically try and switch to consensus | |||
* trySyncTicker to periodically check if we have fallen behind and then catch-up sync | |||
* if there aren't any new blocks available on the pool it skips syncing | |||
* tries to sync the app by taking downloaded blocks from the pool, gives them to the app and stores | |||
them on disk | |||
* implements Receive which is called by the switch/peer | |||
* calls AddBlock on the pool when it receives a new block from a peer | |||
## Block Pool | |||
* responsible for downloading blocks from peers | |||
* makeRequestersRoutine() | |||
* removes timeout peers | |||
* starts new requesters by calling makeNextRequester() | |||
* requestRoutine(): | |||
* picks a peer and sends the request, then blocks until: | |||
* pool is stopped by listening to pool.Quit | |||
* requester is stopped by listening to Quit | |||
* request is redone | |||
* we receive a block | |||
* gotBlockCh is strange | |||
## Block Store | |||
* persists blocks to disk | |||
# TODO | |||
* How does the switch from bcR to conR happen? Does conR persist blocks to disk too? | |||
* What is the interaction between the consensus and blockchain reactors? |
@ -0,0 +1,49 @@ | |||
# Blockchain Reactor | |||
The Blockchain Reactor's high level responsibility is to enable peers who are | |||
far behind the current state of the consensus to quickly catch up by downloading | |||
many blocks in parallel, verifying their commits, and executing them against the | |||
ABCI application. | |||
Tendermint full nodes run the Blockchain Reactor as a service to provide blocks | |||
to new nodes. New nodes run the Blockchain Reactor in "fast_sync" mode, | |||
where they actively make requests for more blocks until they sync up. | |||
Once caught up, "fast_sync" mode is disabled and the node switches to | |||
using (and turns on) the Consensus Reactor. | |||
## Message Types | |||
```go | |||
const ( | |||
msgTypeBlockRequest = byte(0x10) | |||
msgTypeBlockResponse = byte(0x11) | |||
msgTypeNoBlockResponse = byte(0x12) | |||
msgTypeStatusResponse = byte(0x20) | |||
msgTypeStatusRequest = byte(0x21) | |||
) | |||
``` | |||
```go | |||
type bcBlockRequestMessage struct { | |||
Height int64 | |||
} | |||
type bcNoBlockResponseMessage struct { | |||
Height int64 | |||
} | |||
type bcBlockResponseMessage struct { | |||
Block Block | |||
} | |||
type bcStatusRequestMessage struct { | |||
Height int64 | |||
type bcStatusResponseMessage struct { | |||
Height int64 | |||
} | |||
``` | |||
## Protocol | |||
TODO |
@ -0,0 +1,344 @@ | |||
# Consensus Reactor | |||
Consensus Reactor defines a reactor for the consensus service. It contains the ConsensusState service that | |||
manages the state of the Tendermint consensus internal state machine. | |||
When Consensus Reactor is started, it starts Broadcast Routine which starts ConsensusState service. | |||
Furthermore, for each peer that is added to the Consensus Reactor, it creates (and manages) the known peer state | |||
(that is used extensively in gossip routines) and starts the following three routines for the peer p: | |||
Gossip Data Routine, Gossip Votes Routine and QueryMaj23Routine. Finally, Consensus Reactor is responsible | |||
for decoding messages received from a peer and for adequate processing of the message depending on its type and content. | |||
The processing normally consists of updating the known peer state and for some messages | |||
(`ProposalMessage`, `BlockPartMessage` and `VoteMessage`) also forwarding message to ConsensusState module | |||
for further processing. In the following text we specify the core functionality of those separate unit of executions | |||
that are part of the Consensus Reactor. | |||
## ConsensusState service | |||
Consensus State handles execution of the Tendermint BFT consensus algorithm. It processes votes and proposals, | |||
and upon reaching agreement, commits blocks to the chain and executes them against the application. | |||
The internal state machine receives input from peers, the internal validator and from a timer. | |||
Inside Consensus State we have the following units of execution: Timeout Ticker and Receive Routine. | |||
Timeout Ticker is a timer that schedules timeouts conditional on the height/round/step that are processed | |||
by the Receive Routine. | |||
### Receive Routine of the ConsensusState service | |||
Receive Routine of the ConsensusState handles messages which may cause internal consensus state transitions. | |||
It is the only routine that updates RoundState that contains internal consensus state. | |||
Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities. | |||
It receives messages from peers, internal validators and from Timeout Ticker | |||
and invokes the corresponding handlers, potentially updating the RoundState. | |||
The details of the protocol (together with formal proofs of correctness) implemented by the Receive Routine are | |||
discussed in separate document (see [spec](https://github.com/tendermint/spec)). For understanding of this document | |||
it is sufficient to understand that the Receive Routine manages and updates RoundState data structure that is | |||
then extensively used by the gossip routines to determine what information should be sent to peer processes. | |||
## Round State | |||
RoundState defines the internal consensus state. It contains height, round, round step, a current validator set, | |||
a proposal and proposal block for the current round, locked round and block (if some block is being locked), set of | |||
received votes and last commit and last validators set. | |||
```golang | |||
type RoundState struct { | |||
Height int64 | |||
Round int | |||
Step RoundStepType | |||
Validators ValidatorSet | |||
Proposal Proposal | |||
ProposalBlock Block | |||
ProposalBlockParts PartSet | |||
LockedRound int | |||
LockedBlock Block | |||
LockedBlockParts PartSet | |||
Votes HeightVoteSet | |||
LastCommit VoteSet | |||
LastValidators ValidatorSet | |||
} | |||
``` | |||
Internally, consensus will run as a state machine with the following states: | |||
- RoundStepNewHeight | |||
- RoundStepNewRound | |||
- RoundStepPropose | |||
- RoundStepProposeWait | |||
- RoundStepPrevote | |||
- RoundStepPrevoteWait | |||
- RoundStepPrecommit | |||
- RoundStepPrecommitWait | |||
- RoundStepCommit | |||
## Peer Round State | |||
Peer round state contains the known state of a peer. It is being updated by the Receive routine of | |||
Consensus Reactor and by the gossip routines upon sending a message to the peer. | |||
```golang | |||
type PeerRoundState struct { | |||
Height int64 // Height peer is at | |||
Round int // Round peer is at, -1 if unknown. | |||
Step RoundStepType // Step peer is at | |||
Proposal bool // True if peer has proposal for this round | |||
ProposalBlockPartsHeader PartSetHeader | |||
ProposalBlockParts BitArray | |||
ProposalPOLRound int // Proposal's POL round. -1 if none. | |||
ProposalPOL BitArray // nil until ProposalPOLMessage received. | |||
Prevotes BitArray // All votes peer has for this round | |||
Precommits BitArray // All precommits peer has for this round | |||
LastCommitRound int // Round of commit for last height. -1 if none. | |||
LastCommit BitArray // All commit precommits of commit for last height. | |||
CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none. | |||
CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound | |||
} | |||
``` | |||
## Receive method of Consensus reactor | |||
The entry point of the Consensus reactor is a receive method. When a message is received from a peer p, | |||
normally the peer round state is updated correspondingly, and some messages | |||
are passed for further processing, for example to ConsensusState service. We now specify the processing of messages | |||
in the receive method of Consensus reactor for each message type. In the following message handler, `rs` and `prs` denote | |||
`RoundState` and `PeerRoundState`, respectively. | |||
### NewRoundStepMessage handler | |||
``` | |||
handleMessage(msg): | |||
if msg is from smaller height/round/step then return | |||
// Just remember these values. | |||
prsHeight = prs.Height | |||
prsRound = prs.Round | |||
prsCatchupCommitRound = prs.CatchupCommitRound | |||
prsCatchupCommit = prs.CatchupCommit | |||
Update prs with values from msg | |||
if prs.Height or prs.Round has been updated then | |||
reset Proposal related fields of the peer state | |||
if prs.Round has been updated and msg.Round == prsCatchupCommitRound then | |||
prs.Precommits = psCatchupCommit | |||
if prs.Height has been updated then | |||
if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then | |||
prs.LastCommitRound = msg.LastCommitRound | |||
prs.LastCommit = prs.Precommits | |||
} else { | |||
prs.LastCommitRound = msg.LastCommitRound | |||
prs.LastCommit = nil | |||
} | |||
Reset prs.CatchupCommitRound and prs.CatchupCommit | |||
``` | |||
### CommitStepMessage handler | |||
``` | |||
handleMessage(msg): | |||
if prs.Height == msg.Height then | |||
prs.ProposalBlockPartsHeader = msg.BlockPartsHeader | |||
prs.ProposalBlockParts = msg.BlockParts | |||
``` | |||
### HasVoteMessage handler | |||
``` | |||
handleMessage(msg): | |||
if prs.Height == msg.Height then | |||
prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index) | |||
``` | |||
### VoteSetMaj23Message handler | |||
``` | |||
handleMessage(msg): | |||
if prs.Height == msg.Height then | |||
Record in rs that a peer claim to have ⅔ majority for msg.BlockID | |||
Send VoteSetBitsMessage showing votes node has for that BlockId | |||
``` | |||
### ProposalMessage handler | |||
``` | |||
handleMessage(msg): | |||
if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return | |||
prs.Proposal = true | |||
prs.ProposalBlockPartsHeader = msg.BlockPartsHeader | |||
prs.ProposalBlockParts = empty set | |||
prs.ProposalPOLRound = msg.POLRound | |||
prs.ProposalPOL = nil | |||
Send msg through internal peerMsgQueue to ConsensusState service | |||
``` | |||
### ProposalPOLMessage handler | |||
``` | |||
handleMessage(msg): | |||
if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return | |||
prs.ProposalPOL = msg.ProposalPOL | |||
``` | |||
### BlockPartMessage handler | |||
``` | |||
handleMessage(msg): | |||
if prs.Height != msg.Height || prs.Round != msg.Round then return | |||
Record in prs that peer has block part msg.Part.Index | |||
Send msg trough internal peerMsgQueue to ConsensusState service | |||
``` | |||
### VoteMessage handler | |||
``` | |||
handleMessage(msg): | |||
Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round | |||
Send msg trough internal peerMsgQueue to ConsensusState service | |||
``` | |||
### VoteSetBitsMessage handler | |||
``` | |||
handleMessage(msg): | |||
Update prs for the bit-array of votes peer claims to have for the msg.BlockID | |||
``` | |||
## Gossip Data Routine | |||
It is used to send the following messages to the peer: `BlockPartMessage`, `ProposalMessage` and | |||
`ProposalPOLMessage` on the DataChannel. The gossip data routine is based on the local RoundState (`rs`) | |||
and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: | |||
``` | |||
1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then | |||
Part = pick a random proposal block part the peer does not have | |||
Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel | |||
if send returns true, record that the peer knows the corresponding block Part | |||
Continue | |||
1b) if (0 < prs.Height) and (prs.Height < rs.Height) then | |||
help peer catch up using gossipDataForCatchup function | |||
Continue | |||
1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) then | |||
Sleep PeerGossipSleepDuration | |||
Continue | |||
// at this point rs.Height == prs.Height and rs.Round == prs.Round | |||
1d) if (rs.Proposal != nil and !prs.Proposal) then | |||
Send ProposalMessage(rs.Proposal) to the peer | |||
if send returns true, record that the peer knows Proposal | |||
if 0 <= rs.Proposal.POLRound then | |||
polRound = rs.Proposal.POLRound | |||
prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray() | |||
Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray) | |||
Continue | |||
2) Sleep PeerGossipSleepDuration | |||
``` | |||
### Gossip Data For Catchup | |||
This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height). | |||
The function executes the following logic: | |||
if peer does not have all block parts for prs.ProposalBlockPart then | |||
blockMeta = Load Block Metadata for height prs.Height from blockStore | |||
if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then | |||
Sleep PeerGossipSleepDuration | |||
return | |||
Part = pick a random proposal block part the peer does not have | |||
Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel | |||
if send returns true, record that the peer knows the corresponding block Part | |||
return | |||
else Sleep PeerGossipSleepDuration | |||
## Gossip Votes Routine | |||
It is used to send the following message: `VoteMessage` on the VoteChannel. | |||
The gossip votes routine is based on the local RoundState (`rs`) | |||
and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below: | |||
``` | |||
1a) if rs.Height == prs.Height then | |||
if prs.Step == RoundStepNewHeight then | |||
vote = random vote from rs.LastCommit the peer does not have | |||
Send VoteMessage(vote) to the peer | |||
if send returns true, continue | |||
if prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round then | |||
Prevotes = rs.Votes.Prevotes(prs.Round) | |||
vote = random vote from Prevotes the peer does not have | |||
Send VoteMessage(vote) to the peer | |||
if send returns true, continue | |||
if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then | |||
Precommits = rs.Votes.Precommits(prs.Round) | |||
vote = random vote from Precommits the peer does not have | |||
Send VoteMessage(vote) to the peer | |||
if send returns true, continue | |||
if prs.ProposalPOLRound != -1 then | |||
PolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) | |||
vote = random vote from PolPrevotes the peer does not have | |||
Send VoteMessage(vote) to the peer | |||
if send returns true, continue | |||
1b) if prs.Height != 0 and rs.Height == prs.Height+1 then | |||
vote = random vote from rs.LastCommit peer does not have | |||
Send VoteMessage(vote) to the peer | |||
if send returns true, continue | |||
1c) if prs.Height != 0 and rs.Height >= prs.Height+2 then | |||
Commit = get commit from BlockStore for prs.Height | |||
vote = random vote from Commit the peer does not have | |||
Send VoteMessage(vote) to the peer | |||
if send returns true, continue | |||
2) Sleep PeerGossipSleepDuration | |||
``` | |||
## QueryMaj23Routine | |||
It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Message` is sent to indicate that a given | |||
BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs`) and the known PeerRoundState | |||
(`prs`). The routine repeats forever the logic shown below. | |||
``` | |||
1a) if rs.Height == prs.Height then | |||
Prevotes = rs.Votes.Prevotes(prs.Round) | |||
if there is a ⅔ majority for some blockId in Prevotes then | |||
m = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId) | |||
Send m to peer | |||
Sleep PeerQueryMaj23SleepDuration | |||
1b) if rs.Height == prs.Height then | |||
Precommits = rs.Votes.Precommits(prs.Round) | |||
if there is a ⅔ majority for some blockId in Precommits then | |||
m = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId) | |||
Send m to peer | |||
Sleep PeerQueryMaj23SleepDuration | |||
1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 then | |||
Prevotes = rs.Votes.Prevotes(prs.ProposalPOLRound) | |||
if there is a ⅔ majority for some blockId in Prevotes then | |||
m = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId) | |||
Send m to peer | |||
Sleep PeerQueryMaj23SleepDuration | |||
1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and | |||
prs.Height <= blockStore.Height() then | |||
Commit = LoadCommit(prs.Height) | |||
m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.blockId) | |||
Send m to peer | |||
Sleep PeerQueryMaj23SleepDuration | |||
2) Sleep PeerQueryMaj23SleepDuration | |||
``` | |||
## Broadcast routine | |||
The Broadcast routine subscribes to an internal event bus to receive new round steps, votes messages and proposal | |||
heartbeat messages, and broadcasts messages to peers upon receiving those events. | |||
It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that | |||
broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel. | |||
Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel. | |||
`ProposalHeartbeatMessage` is sent the same way on the StateChannel. |
@ -0,0 +1,212 @@ | |||
# Tendermint Consensus Reactor | |||
Tendermint Consensus is a distributed protocol executed by validator processes to agree on | |||
the next block to be added to the Tendermint blockchain. The protocol proceeds in rounds, where | |||
each round is a try to reach agreement on the next block. A round starts by having a dedicated | |||
process (called proposer) suggesting to other processes what should be the next block with | |||
the `ProposalMessage`. | |||
The processes respond by voting for a block with `VoteMessage` (there are two kinds of vote | |||
messages, prevote and precommit votes). Note that a proposal message is just a suggestion what the | |||
next block should be; a validator might vote with a `VoteMessage` for a different block. If in some | |||
round, enough number of processes vote for the same block, then this block is committed and later | |||
added to the blockchain. `ProposalMessage` and `VoteMessage` are signed by the private key of the | |||
validator. The internals of the protocol and how it ensures safety and liveness properties are | |||
explained [here](https://github.com/tendermint/spec). | |||
For efficiency reasons, validators in Tendermint consensus protocol do not agree directly on the | |||
block as the block size is big, i.e., they don't embed the block inside `Proposal` and | |||
`VoteMessage`. Instead, they reach agreement on the `BlockID` (see `BlockID` definition in | |||
[Blockchain](blockchain.md) section) that uniquely identifies each block. The block itself is | |||
disseminated to validator processes using peer-to-peer gossiping protocol. It starts by having a | |||
proposer first splitting a block into a number of block parts, that are then gossiped between | |||
processes using `BlockPartMessage`. | |||
Validators in Tendermint communicate by peer-to-peer gossiping protocol. Each validator is connected | |||
only to a subset of processes called peers. By the gossiping protocol, a validator send to its peers | |||
all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can | |||
reach agreement on some block, and also obtain the content of the chosen block (block parts). As | |||
part of the gossiping protocol, processes also send auxiliary messages that inform peers about the | |||
executed steps of the core consensus algorithm (`NewRoundStepMessage` and `CommitStepMessage`), and | |||
also messages that inform peers what votes the process has seen (`HasVoteMessage`, | |||
`VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping | |||
protocol to determine what messages a process should send to its peers. | |||
We now describe the content of each message exchanged during Tendermint consensus protocol. | |||
## ProposalMessage | |||
ProposalMessage is sent when a new block is proposed. It is a suggestion of what the | |||
next block in the blockchain should be. | |||
```go | |||
type ProposalMessage struct { | |||
Proposal Proposal | |||
} | |||
``` | |||
### Proposal | |||
Proposal contains height and round for which this proposal is made, BlockID as a unique identifier | |||
of proposed block, timestamp, and two fields (POLRound and POLBlockID) that are needed for | |||
termination of the consensus. The message is signed by the validator private key. | |||
```go | |||
type Proposal struct { | |||
Height int64 | |||
Round int | |||
Timestamp Time | |||
BlockID BlockID | |||
POLRound int | |||
POLBlockID BlockID | |||
Signature Signature | |||
} | |||
``` | |||
NOTE: In the current version of the Tendermint, the consensus value in proposal is represented with | |||
PartSetHeader, and with BlockID in vote message. It should be aligned as suggested in this spec as | |||
BlockID contains PartSetHeader. | |||
## VoteMessage | |||
VoteMessage is sent to vote for some block (or to inform others that a process does not vote in the | |||
current round). Vote is defined in [Blockchain](blockchain.md) section and contains validator's | |||
information (validator address and index), height and round for which the vote is sent, vote type, | |||
blockID if process vote for some block (`nil` otherwise) and a timestamp when the vote is sent. The | |||
message is signed by the validator private key. | |||
```go | |||
type VoteMessage struct { | |||
Vote Vote | |||
} | |||
``` | |||
## BlockPartMessage | |||
BlockPartMessage is sent when gossipping a piece of the proposed block. It contains height, round | |||
and the block part. | |||
```go | |||
type BlockPartMessage struct { | |||
Height int64 | |||
Round int | |||
Part Part | |||
} | |||
``` | |||
## ProposalHeartbeatMessage | |||
ProposalHeartbeatMessage is sent to signal that a node is alive and waiting for transactions | |||
to be able to create a next block proposal. | |||
```go | |||
type ProposalHeartbeatMessage struct { | |||
Heartbeat Heartbeat | |||
} | |||
``` | |||
### Heartbeat | |||
Heartbeat contains validator information (address and index), | |||
height, round and sequence number. It is signed by the private key of the validator. | |||
```go | |||
type Heartbeat struct { | |||
ValidatorAddress []byte | |||
ValidatorIndex int | |||
Height int64 | |||
Round int | |||
Sequence int | |||
Signature Signature | |||
} | |||
``` | |||
## NewRoundStepMessage | |||
NewRoundStepMessage is sent for every step transition during the core consensus algorithm execution. | |||
It is used in the gossip part of the Tendermint protocol to inform peers about a current | |||
height/round/step a process is in. | |||
```go | |||
type NewRoundStepMessage struct { | |||
Height int64 | |||
Round int | |||
Step RoundStepType | |||
SecondsSinceStartTime int | |||
LastCommitRound int | |||
} | |||
``` | |||
## CommitStepMessage | |||
CommitStepMessage is sent when an agreement on some block is reached. It contains height for which | |||
agreement is reached, block parts header that describes the decided block and is used to obtain all | |||
block parts, and a bit array of the block parts a process currently has, so its peers can know what | |||
parts it is missing so they can send them. | |||
```go | |||
type CommitStepMessage struct { | |||
Height int64 | |||
BlockID BlockID | |||
BlockParts BitArray | |||
} | |||
``` | |||
TODO: We use BlockID instead of BlockPartsHeader (in current implementation) for symmetry. | |||
## ProposalPOLMessage | |||
ProposalPOLMessage is sent when a previous block is re-proposed. | |||
It is used to inform peers in what round the process learned for this block (ProposalPOLRound), | |||
and what prevotes for the re-proposed block the process has. | |||
```go | |||
type ProposalPOLMessage struct { | |||
Height int64 | |||
ProposalPOLRound int | |||
ProposalPOL BitArray | |||
} | |||
``` | |||
## HasVoteMessage | |||
HasVoteMessage is sent to indicate that a particular vote has been received. It contains height, | |||
round, vote type and the index of the validator that is the originator of the corresponding vote. | |||
```go | |||
type HasVoteMessage struct { | |||
Height int64 | |||
Round int | |||
Type byte | |||
Index int | |||
} | |||
``` | |||
## VoteSetMaj23Message | |||
VoteSetMaj23Message is sent to indicate that a process has seen +2/3 votes for some BlockID. | |||
It contains height, round, vote type and the BlockID. | |||
```go | |||
type VoteSetMaj23Message struct { | |||
Height int64 | |||
Round int | |||
Type byte | |||
BlockID BlockID | |||
} | |||
``` | |||
## VoteSetBitsMessage | |||
VoteSetBitsMessage is sent to communicate the bit-array of votes a process has seen for a given | |||
BlockID. It contains height, round, vote type, BlockID and a bit array of | |||
the votes a process has. | |||
```go | |||
type VoteSetBitsMessage struct { | |||
Height int64 | |||
Round int | |||
Type byte | |||
BlockID BlockID | |||
Votes BitArray | |||
} | |||
``` |
@ -0,0 +1,47 @@ | |||
# Proposer selection procedure in Tendermint | |||
This document specifies the Proposer Selection Procedure that is used in Tendermint to choose a round proposer. | |||
As Tendermint is “leader-based protocol”, the proposer selection is critical for its correct functioning. | |||
Let denote with `proposer_p(h,r)` a process returned by the Proposer Selection Procedure at the process p, at height h | |||
and round r. Then the Proposer Selection procedure should fulfill the following properties: | |||
`Agreement`: Given a validator set V, and two honest validators, | |||
p and q, for each height h, and each round r, | |||
proposer_p(h,r) = proposer_q(h,r) | |||
`Liveness`: In every consecutive sequence of rounds of size K (K is system parameter), at least a | |||
single round has an honest proposer. | |||
`Fairness`: The proposer selection is proportional to the validator voting power, i.e., a validator with more | |||
voting power is selected more frequently, proportional to its power. More precisely, given a set of processes | |||
with the total voting power N, during a sequence of rounds of size N, every process is proposer in a number of rounds | |||
equal to its voting power. | |||
We now look at a few particular cases to understand better how fairness should be implemented. | |||
If we have 4 processes with the following voting power distribution (p0,4), (p1, 2), (p2, 2), (p3, 2) at some round r, | |||
we have the following sequence of proposer selections in the following rounds: | |||
`p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, p0, p1, p2, p3, p0, etc` | |||
Let consider now the following scenario where a total voting power of faulty processes is aggregated in a single process | |||
p0: (p0,3), (p1, 1), (p2, 1), (p3, 1), (p4, 1), (p5, 1), (p6, 1), (p7, 1). | |||
In this case the sequence of proposer selections looks like this: | |||
`p0, p1, p2, p3, p0, p4, p5, p6, p7, p0, p0, p1, p2, p3, p0, p4, p5, p6, p7, p0, etc` | |||
In this case, we see that a number of rounds coordinated by a faulty process is proportional to its voting power. | |||
We consider also the case where we have voting power uniformly distributed among processes, i.e., we have 10 processes | |||
each with voting power of 1. And let consider that there are 3 faulty processes with consecutive addresses, | |||
for example the first 3 processes are faulty. Then the sequence looks like this: | |||
`p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, etc` | |||
In this case, we have 3 consecutive rounds with a faulty proposer. | |||
One special case we consider is the case where a single honest process p0 has most of the voting power, for example: | |||
(p0,100), (p1, 2), (p2, 3), (p3, 4). Then the sequence of proposer selection looks like this: | |||
p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p0, p1, p0, p0, p0, p0, p0, etc | |||
This basically means that almost all rounds have the same proposer. But in this case, the process p0 has anyway enough | |||
voting power to decide whatever he wants, so the fact that he coordinates almost all rounds seems correct. | |||
@ -0,0 +1,11 @@ | |||
# Mempool Specification | |||
This package contains documents specifying the functionality | |||
of the mempool module. | |||
Components: | |||
* [Config](./config.md) - how to configure it | |||
* [External Messages](./messages.md) - The messages we accept over p2p and rpc interfaces | |||
* [Functionality](./functionality.md) - high-level description of the functionality it provides | |||
* [Concurrency Model](./concurrency.md) - What guarantees we provide, what locks we require. |
@ -0,0 +1,8 @@ | |||
# Mempool Concurrency | |||
Look at the concurrency model this uses... | |||
* Receiving CheckTx | |||
* Broadcasting new tx | |||
* Interfaces with consensus engine, reap/update while checking | |||
* Calling the ABCI app (ordering. callbacks. how proxy works alongside the blockchain proxy which actually writes blocks) |
@ -0,0 +1,59 @@ | |||
# Mempool Configuration | |||
Here we describe configuration options around mempool. | |||
For the purposes of this document, they are described | |||
as command-line flags, but they can also be passed in as | |||
environmental variables or in the config.toml file. The | |||
following are all equivalent: | |||
Flag: `--mempool.recheck_empty=false` | |||
Environment: `TM_MEMPOOL_RECHECK_EMPTY=false` | |||
Config: | |||
``` | |||
[mempool] | |||
recheck_empty = false | |||
``` | |||
## Recheck | |||
`--mempool.recheck=false` (default: true) | |||
`--mempool.recheck_empty=false` (default: true) | |||
Recheck determines if the mempool rechecks all pending | |||
transactions after a block was committed. Once a block | |||
is committed, the mempool removes all valid transactions | |||
that were successfully included in the block. | |||
If `recheck` is true, then it will rerun CheckTx on | |||
all remaining transactions with the new block state. | |||
If the block contained no transactions, it will skip the | |||
recheck unless `recheck_empty` is true. | |||
## Broadcast | |||
`--mempool.broadcast=false` (default: true) | |||
Determines whether this node gossips any valid transactions | |||
that arrive in mempool. Default is to gossip anything that | |||
passes checktx. If this is disabled, transactions are not | |||
gossiped, but instead stored locally and added to the next | |||
block this node is the proposer. | |||
## WalDir | |||
`--mempool.wal_dir=/tmp/gaia/mempool.wal` (default: $TM_HOME/data/mempool.wal) | |||
This defines the directory where mempool writes the write-ahead | |||
logs. These files can be used to reload unbroadcasted | |||
transactions if the node crashes. | |||
If the directory passed in is an absolute path, the wal file is | |||
created there. If the directory is a relative path, the path is | |||
appended to home directory of the tendermint process to | |||
generate an absolute path to the wal directory | |||
(default `$HOME/.tendermint` or set via `TM_HOME` or `--home``) |
@ -0,0 +1,37 @@ | |||
# Mempool Functionality | |||
The mempool maintains a list of potentially valid transactions, | |||
both to broadcast to other nodes, as well as to provide to the | |||
consensus reactor when it is selected as the block proposer. | |||
There are two sides to the mempool state: | |||
* External: get, check, and broadcast new transactions | |||
* Internal: return valid transaction, update list after block commit | |||
## External functionality | |||
External functionality is exposed via network interfaces | |||
to potentially untrusted actors. | |||
* CheckTx - triggered via RPC or P2P | |||
* Broadcast - gossip messages after a successful check | |||
## Internal functionality | |||
Internal functionality is exposed via method calls to other | |||
code compiled into the tendermint binary. | |||
* Reap - get tx to propose in next block | |||
* Update - remove tx that were included in last block | |||
* ABCI.CheckTx - call ABCI app to validate the tx | |||
What does it provide the consensus reactor? | |||
What guarantees does it need from the ABCI app? | |||
(talk about interleaving processes in concurrency) | |||
## Optimizations | |||
Talk about the LRU cache to make sure we don't process any | |||
tx that we have seen before |
@ -0,0 +1,60 @@ | |||
# Mempool Messages | |||
## P2P Messages | |||
There is currently only one message that Mempool broadcasts | |||
and receives over the p2p gossip network (via the reactor): | |||
`TxMessage` | |||
```go | |||
// TxMessage is a MempoolMessage containing a transaction. | |||
type TxMessage struct { | |||
Tx types.Tx | |||
} | |||
``` | |||
TxMessage is go-wire encoded and prepended with `0x1` as a | |||
"type byte". This is followed by a go-wire encoded byte-slice. | |||
Prefix of 40=0x28 byte tx is: `0x010128...` followed by | |||
the actual 40-byte tx. Prefix of 350=0x015e byte tx is: | |||
`0x0102015e...` followed by the actual 350 byte tx. | |||
(Please see the [go-wire repo](https://github.com/tendermint/go-wire#an-interface-example) for more information) | |||
## RPC Messages | |||
Mempool exposes `CheckTx([]byte)` over the RPC interface. | |||
It can be posted via `broadcast_commit`, `broadcast_sync` or | |||
`broadcast_async`. They all parse a message with one argument, | |||
`"tx": "HEX_ENCODED_BINARY"` and differ in only how long they | |||
wait before returning (sync makes sure CheckTx passes, commit | |||
makes sure it was included in a signed block). | |||
Request (`POST http://gaia.zone:46657/`): | |||
```json | |||
{ | |||
"id": "", | |||
"jsonrpc": "2.0", | |||
"method": "broadcast_sync", | |||
"params": { | |||
"tx": "F012A4BC68..." | |||
} | |||
} | |||
``` | |||
Response: | |||
```json | |||
{ | |||
"error": "", | |||
"result": { | |||
"hash": "E39AAB7A537ABAA237831742DCE1117F187C3C52", | |||
"log": "", | |||
"data": "", | |||
"code": 0 | |||
}, | |||
"id": "", | |||
"jsonrpc": "2.0" | |||
} | |||
``` |
@ -0,0 +1,98 @@ | |||
# Peer Strategy and Exchange | |||
Here we outline the design of the AddressBook | |||
and how it used by the Peer Exchange Reactor (PEX) to ensure we are connected | |||
to good peers and to gossip peers to others. | |||
## Peer Types | |||
Certain peers are special in that they are specified by the user as `persistent`, | |||
which means we auto-redial them if the connection fails. | |||
Some peers can be marked as `private`, which means | |||
we will not put them in the address book or gossip them to others. | |||
All peers except private peers are tracked using the address book. | |||
## Discovery | |||
Peer discovery begins with a list of seeds. | |||
When we have no peers, or have been unable to find enough peers from existing ones, | |||
we dial a randomly selected seed to get a list of peers to dial. | |||
So long as we have less than `MaxPeers`, we periodically request additional peers | |||
from each of our own. If sufficient time goes by and we still can't find enough peers, | |||
we try the seeds again. | |||
## Address Book | |||
Peers are tracked via their ID (their PubKey.Address()). | |||
For each ID, the address book keeps the most recent IP:PORT. | |||
Peers are added to the address book from the PEX when they first connect to us or | |||
when we hear about them from other peers. | |||
The address book is arranged in sets of buckets, and distinguishes between | |||
vetted (old) and unvetted (new) peers. It keeps different sets of buckets for vetted and | |||
unvetted peers. Buckets provide randomization over peer selection. | |||
A vetted peer can only be in one bucket. An unvetted peer can be in multiple buckets. | |||
## Vetting | |||
When a peer is first added, it is unvetted. | |||
Marking a peer as vetted is outside the scope of the `p2p` package. | |||
For Tendermint, a Peer becomes vetted once it has contributed sufficiently | |||
at the consensus layer; ie. once it has sent us valid and not-yet-known | |||
votes and/or block parts for `NumBlocksForVetted` blocks. | |||
Other users of the p2p package can determine their own conditions for when a peer is marked vetted. | |||
If a peer becomes vetted but there are already too many vetted peers, | |||
a randomly selected one of the vetted peers becomes unvetted. | |||
If a peer becomes unvetted (either a new peer, or one that was previously vetted), | |||
a randomly selected one of the unvetted peers is removed from the address book. | |||
More fine-grained tracking of peer behaviour can be done using | |||
a trust metric (see below), but it's best to start with something simple. | |||
## Select Peers to Dial | |||
When we need more peers, we pick them randomly from the addrbook with some | |||
configurable bias for unvetted peers. The bias should be lower when we have fewer peers, | |||
and can increase as we obtain more, ensuring that our first peers are more trustworthy, | |||
but always giving us the chance to discover new good peers. | |||
## Select Peers to Exchange | |||
When we’re asked for peers, we select them as follows: | |||
- select at most `maxGetSelection` peers | |||
- try to select at least `minGetSelection` peers - if we have less than that, select them all. | |||
- select a random, unbiased `getSelectionPercent` of the peers | |||
Send the selected peers. Note we select peers for sending without bias for vetted/unvetted. | |||
## Preventing Spam | |||
There are various cases where we decide a peer has misbehaved and we disconnect from them. | |||
When this happens, the peer is removed from the address book and black listed for | |||
some amount of time. We call this "Disconnect and Mark". | |||
Note that the bad behaviour may be detected outside the PEX reactor itself | |||
(for instance, in the mconnection, or another reactor), but it must be communicated to the PEX reactor | |||
so it can remove and mark the peer. | |||
In the PEX, if a peer sends us unsolicited lists of peers, | |||
or if the peer sends too many requests for more peers in a given amount of time, | |||
we Disconnect and Mark. | |||
## Trust Metric | |||
The quality of peers can be tracked in more fine-grained detail using a | |||
Proportional-Integral-Derivative (PID) controller that incorporates | |||
current, past, and rate-of-change data to inform peer quality. | |||
While a PID trust metric has been implemented, it remains for future work | |||
to use it in the PEX. | |||
See the [trustmetric](../../../architecture/adr-006-trust-metric.md ) | |||
and [trustmetric useage](../../../architecture/adr-007-trust-metric-usage.md ) | |||
architecture docs for more details. | |||
@ -1,3 +0,0 @@ | |||
- Remove BlockID from Commit | |||
- Actually validate the ValidatorsHash | |||
- Move blockHeight=1 exception for LastCommit to ValidateBasic |
@ -1,155 +0,0 @@ | |||
package lite | |||
import ( | |||
"github.com/tendermint/tendermint/types" | |||
liteErr "github.com/tendermint/tendermint/lite/errors" | |||
) | |||
// Inquiring wraps a dynamic certifier and implements an auto-update strategy. If a call to Certify | |||
// fails due to a change it validator set, Inquiring will try and find a previous FullCommit which | |||
// it can use to safely update the validator set. It uses a source provider to obtain the needed | |||
// FullCommits. It stores properly validated data on the local system. | |||
type Inquiring struct { | |||
cert *Dynamic | |||
// These are only properly validated data, from local system | |||
trusted Provider | |||
// This is a source of new info, like a node rpc, or other import method | |||
Source Provider | |||
} | |||
// NewInquiring returns a new Inquiring object. It uses the trusted provider to store validated | |||
// data and the source provider to obtain missing FullCommits. | |||
// | |||
// Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source | |||
// provider should be a client.HTTPProvider. | |||
func NewInquiring(chainID string, fc FullCommit, trusted Provider, source Provider) *Inquiring { | |||
// store the data in trusted | |||
// TODO: StoredCommit() can return an error and we need to handle this. | |||
trusted.StoreCommit(fc) | |||
return &Inquiring{ | |||
cert: NewDynamic(chainID, fc.Validators, fc.Height()), | |||
trusted: trusted, | |||
Source: source, | |||
} | |||
} | |||
// ChainID returns the chain id. | |||
func (c *Inquiring) ChainID() string { | |||
return c.cert.ChainID() | |||
} | |||
// Validators returns the validator set. | |||
func (c *Inquiring) Validators() *types.ValidatorSet { | |||
return c.cert.cert.vSet | |||
} | |||
// LastHeight returns the last height. | |||
func (c *Inquiring) LastHeight() int64 { | |||
return c.cert.lastHeight | |||
} | |||
// Certify makes sure this is checkpoint is valid. | |||
// | |||
// If the validators have changed since the last know time, it looks | |||
// for a path to prove the new validators. | |||
// | |||
// On success, it will store the checkpoint in the store for later viewing | |||
func (c *Inquiring) Certify(commit Commit) error { | |||
err := c.useClosestTrust(commit.Height()) | |||
if err != nil { | |||
return err | |||
} | |||
err = c.cert.Certify(commit) | |||
if !liteErr.IsValidatorsChangedErr(err) { | |||
return err | |||
} | |||
err = c.updateToHash(commit.Header.ValidatorsHash) | |||
if err != nil { | |||
return err | |||
} | |||
err = c.cert.Certify(commit) | |||
if err != nil { | |||
return err | |||
} | |||
// store the new checkpoint | |||
return c.trusted.StoreCommit(NewFullCommit(commit, c.Validators())) | |||
} | |||
// Update will verify if this is a valid change and update | |||
// the certifying validator set if safe to do so. | |||
func (c *Inquiring) Update(fc FullCommit) error { | |||
err := c.useClosestTrust(fc.Height()) | |||
if err != nil { | |||
return err | |||
} | |||
err = c.cert.Update(fc) | |||
if err == nil { | |||
err = c.trusted.StoreCommit(fc) | |||
} | |||
return err | |||
} | |||
func (c *Inquiring) useClosestTrust(h int64) error { | |||
closest, err := c.trusted.GetByHeight(h) | |||
if err != nil { | |||
return err | |||
} | |||
// if the best seed is not the one we currently use, | |||
// let's just reset the dynamic validator | |||
if closest.Height() != c.LastHeight() { | |||
c.cert = NewDynamic(c.ChainID(), closest.Validators, closest.Height()) | |||
} | |||
return nil | |||
} | |||
// updateToHash gets the validator hash we want to update to | |||
// if IsTooMuchChangeErr, we try to find a path by binary search over height | |||
func (c *Inquiring) updateToHash(vhash []byte) error { | |||
// try to get the match, and update | |||
fc, err := c.Source.GetByHash(vhash) | |||
if err != nil { | |||
return err | |||
} | |||
err = c.cert.Update(fc) | |||
// handle IsTooMuchChangeErr by using divide and conquer | |||
if liteErr.IsTooMuchChangeErr(err) { | |||
err = c.updateToHeight(fc.Height()) | |||
} | |||
return err | |||
} | |||
// updateToHeight will use divide-and-conquer to find a path to h | |||
func (c *Inquiring) updateToHeight(h int64) error { | |||
// try to update to this height (with checks) | |||
fc, err := c.Source.GetByHeight(h) | |||
if err != nil { | |||
return err | |||
} | |||
start, end := c.LastHeight(), fc.Height() | |||
if end <= start { | |||
return liteErr.ErrNoPathFound() | |||
} | |||
err = c.Update(fc) | |||
// we can handle IsTooMuchChangeErr specially | |||
if !liteErr.IsTooMuchChangeErr(err) { | |||
return err | |||
} | |||
// try to update to mid | |||
mid := (start + end) / 2 | |||
err = c.updateToHeight(mid) | |||
if err != nil { | |||
return err | |||
} | |||
// if we made it to mid, we recurse | |||
return c.updateToHeight(h) | |||
} |
@ -0,0 +1,163 @@ | |||
package lite | |||
import ( | |||
"github.com/tendermint/tendermint/types" | |||
liteErr "github.com/tendermint/tendermint/lite/errors" | |||
) | |||
var _ Certifier = (*InquiringCertifier)(nil) | |||
// InquiringCertifier wraps a dynamic certifier and implements an auto-update strategy. If a call | |||
// to Certify fails due to a change it validator set, InquiringCertifier will try and find a | |||
// previous FullCommit which it can use to safely update the validator set. It uses a source | |||
// provider to obtain the needed FullCommits. It stores properly validated data on the local system. | |||
type InquiringCertifier struct { | |||
cert *DynamicCertifier | |||
// These are only properly validated data, from local system | |||
trusted Provider | |||
// This is a source of new info, like a node rpc, or other import method | |||
Source Provider | |||
} | |||
// NewInquiringCertifier returns a new Inquiring object. It uses the trusted provider to store | |||
// validated data and the source provider to obtain missing FullCommits. | |||
// | |||
// Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source | |||
// provider should be a client.HTTPProvider. | |||
func NewInquiringCertifier(chainID string, fc FullCommit, trusted Provider, | |||
source Provider) (*InquiringCertifier, error) { | |||
// store the data in trusted | |||
err := trusted.StoreCommit(fc) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &InquiringCertifier{ | |||
cert: NewDynamicCertifier(chainID, fc.Validators, fc.Height()), | |||
trusted: trusted, | |||
Source: source, | |||
}, nil | |||
} | |||
// ChainID returns the chain id. | |||
// Implements Certifier. | |||
func (ic *InquiringCertifier) ChainID() string { | |||
return ic.cert.ChainID() | |||
} | |||
// Validators returns the validator set. | |||
func (ic *InquiringCertifier) Validators() *types.ValidatorSet { | |||
return ic.cert.cert.vSet | |||
} | |||
// LastHeight returns the last height. | |||
func (ic *InquiringCertifier) LastHeight() int64 { | |||
return ic.cert.lastHeight | |||
} | |||
// Certify makes sure this is checkpoint is valid. | |||
// | |||
// If the validators have changed since the last know time, it looks | |||
// for a path to prove the new validators. | |||
// | |||
// On success, it will store the checkpoint in the store for later viewing | |||
// Implements Certifier. | |||
func (ic *InquiringCertifier) Certify(commit Commit) error { | |||
err := ic.useClosestTrust(commit.Height()) | |||
if err != nil { | |||
return err | |||
} | |||
err = ic.cert.Certify(commit) | |||
if !liteErr.IsValidatorsChangedErr(err) { | |||
return err | |||
} | |||
err = ic.updateToHash(commit.Header.ValidatorsHash) | |||
if err != nil { | |||
return err | |||
} | |||
err = ic.cert.Certify(commit) | |||
if err != nil { | |||
return err | |||
} | |||
// store the new checkpoint | |||
return ic.trusted.StoreCommit(NewFullCommit(commit, ic.Validators())) | |||
} | |||
// Update will verify if this is a valid change and update | |||
// the certifying validator set if safe to do so. | |||
func (ic *InquiringCertifier) Update(fc FullCommit) error { | |||
err := ic.useClosestTrust(fc.Height()) | |||
if err != nil { | |||
return err | |||
} | |||
err = ic.cert.Update(fc) | |||
if err == nil { | |||
err = ic.trusted.StoreCommit(fc) | |||
} | |||
return err | |||
} | |||
func (ic *InquiringCertifier) useClosestTrust(h int64) error { | |||
closest, err := ic.trusted.GetByHeight(h) | |||
if err != nil { | |||
return err | |||
} | |||
// if the best seed is not the one we currently use, | |||
// let's just reset the dynamic validator | |||
if closest.Height() != ic.LastHeight() { | |||
ic.cert = NewDynamicCertifier(ic.ChainID(), closest.Validators, closest.Height()) | |||
} | |||
return nil | |||
} | |||
// updateToHash gets the validator hash we want to update to | |||
// if IsTooMuchChangeErr, we try to find a path by binary search over height | |||
func (ic *InquiringCertifier) updateToHash(vhash []byte) error { | |||
// try to get the match, and update | |||
fc, err := ic.Source.GetByHash(vhash) | |||
if err != nil { | |||
return err | |||
} | |||
err = ic.cert.Update(fc) | |||
// handle IsTooMuchChangeErr by using divide and conquer | |||
if liteErr.IsTooMuchChangeErr(err) { | |||
err = ic.updateToHeight(fc.Height()) | |||
} | |||
return err | |||
} | |||
// updateToHeight will use divide-and-conquer to find a path to h | |||
func (ic *InquiringCertifier) updateToHeight(h int64) error { | |||
// try to update to this height (with checks) | |||
fc, err := ic.Source.GetByHeight(h) | |||
if err != nil { | |||
return err | |||
} | |||
start, end := ic.LastHeight(), fc.Height() | |||
if end <= start { | |||
return liteErr.ErrNoPathFound() | |||
} | |||
err = ic.Update(fc) | |||
// we can handle IsTooMuchChangeErr specially | |||
if !liteErr.IsTooMuchChangeErr(err) { | |||
return err | |||
} | |||
// try to update to mid | |||
mid := (start + end) / 2 | |||
err = ic.updateToHeight(mid) | |||
if err != nil { | |||
return err | |||
} | |||
// if we made it to mid, we recurse | |||
return ic.updateToHeight(h) | |||
} |