Merge branch 'develop' into patch-1

This commit is contained in:
Ethan Buchman
2018-01-26 17:17:28 -05:00
committed by GitHub
176 changed files with 6505 additions and 2916 deletions

6
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -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

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ test/logs
coverage.txt coverage.txt
docs/_build docs/_build
docs/tools docs/tools
*.log
scripts/wal2json/wal2json scripts/wal2json/wal2json
scripts/cutWALUntil/cutWALUntil scripts/cutWALUntil/cutWALUntil

View File

@ -5,6 +5,7 @@
BREAKING CHANGES: BREAKING CHANGES:
- Better support for injecting randomness - Better support for injecting randomness
- Upgrade consensus for more real-time use of evidence - Upgrade consensus for more real-time use of evidence
- the files usually found in `~/.tendermint` (`config.toml`, `genesis.json`, and `priv_validator.json`) are now in `~/.tendermint/config`. The `$TMHOME/data/` directory remains unchanged.
FEATURES: FEATURES:
- Peer reputation management - Peer reputation management
@ -25,6 +26,34 @@ BUG FIXES:
- Graceful handling/recovery for apps that have non-determinism or fail to halt - Graceful handling/recovery for apps that have non-determinism or fail to halt
- Graceful handling/recovery for violations of safety, or liveness - Graceful handling/recovery for violations of safety, or liveness
## 0.16.0 (TBD)
BREAKING CHANGES:
- [config] use $TMHOME/config for all config and json files
- [p2p] old `--p2p.seeds` is now `--p2p.persistent_peers` (persistent peers to which TM will always connect to)
- [p2p] now `--p2p.seeds` only used for getting addresses (if addrbook is empty; not persistent)
- [p2p] NodeInfo: remove RemoteAddr and add Channels
- we must have at least one overlapping channel with peer
- we only send msgs for channels the peer advertised
FEATURES:
- [p2p] added new `/dial_peers&persistent=_` **unsafe** endpoint
- [p2p] persistent node key in `$THMHOME/config/node_key.json`
- [p2p] introduce peer ID and authenticate peers by ID using addresses like `ID@IP:PORT`
- [p2p] new seed mode in pex reactor crawls the network and serves as a seed. TODO: `--p2p.seed_mode`
- [config] MempoolConfig.CacheSize
IMPROVEMENT:
- [p2p] stricter rules in the PEX reactor for better handling of abuse
- [p2p] various improvements to code structure including subpackages for `pex` and `conn`
- [docs] new spec!
BUG FIX:
- [blockchain] StopPeerForError on timeout
- [consensus] StopPeerForError on a bad Maj23 message
- [state] flush mempool conn before calling commit
- [types] fix priv val signing things that only differ by timestamp
## 0.15.0 (December 29, 2017) ## 0.15.0 (December 29, 2017)
BREAKING CHANGES: BREAKING CHANGES:

View File

@ -42,15 +42,18 @@ Run `bash scripts/glide/status.sh` to get a list of vendored dependencies that m
## Vagrant ## Vagrant
If you are a [Vagrant](https://www.vagrantup.com/) user, all you have to do to get started hacking Tendermint is: If you are a [Vagrant](https://www.vagrantup.com/) user, you can get started hacking Tendermint with the commands below.
NOTE: In case you installed Vagrant in 2017, you might need to run
`vagrant box update` to upgrade to the latest `ubuntu/xenial64`.
``` ```
vagrant up vagrant up
vagrant ssh vagrant ssh
cd ~/go/src/github.com/tendermint/tendermint
make test make test
``` ```
## Testing ## Testing
All repos should be hooked up to circle. All repos should be hooked up to circle.

View File

@ -1,8 +1,8 @@
FROM alpine:3.6 FROM alpine:3.6
# This is the release of tendermint to pull in. # This is the release of tendermint to pull in.
ENV TM_VERSION 0.13.0 ENV TM_VERSION 0.15.0
ENV TM_SHA256SUM 36d773d4c2890addc61cc87a72c1e9c21c89516921b0defb0edfebde719b4b85 ENV TM_SHA256SUM 71cc271c67eca506ca492c8b90b090132f104bf5dbfe0af2702a50886e88de17
# Tendermint will be looking for genesis file in /tendermint (unless you change # Tendermint will be looking for genesis file in /tendermint (unless you change
# `genesis_file` in config.toml). You can put your config.toml and private # `genesis_file` in config.toml). You can put your config.toml and private

View File

@ -1,6 +1,7 @@
# Supported tags and respective `Dockerfile` links # Supported tags and respective `Dockerfile` links
- `0.13.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/a28b3fff49dce2fb31f90abb2fc693834e0029c2/DOCKER/Dockerfile) - `0.15.0`, `latest` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/170777300ea92dc21a8aec1abc16cb51812513a4/DOCKER/Dockerfile)
- `0.13.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/a28b3fff49dce2fb31f90abb2fc693834e0029c2/DOCKER/Dockerfile)
- `0.12.1` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/457c688346b565e90735431619ca3ca597ef9007/DOCKER/Dockerfile) - `0.12.1` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/457c688346b565e90735431619ca3ca597ef9007/DOCKER/Dockerfile)
- `0.12.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/70d8afa6e952e24c573ece345560a5971bf2cc0e/DOCKER/Dockerfile) - `0.12.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/70d8afa6e952e24c573ece345560a5971bf2cc0e/DOCKER/Dockerfile)
- `0.11.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/9177cc1f64ca88a4a0243c5d1773d10fba67e201/DOCKER/Dockerfile) - `0.11.0` [(Dockerfile)](https://github.com/tendermint/tendermint/blob/9177cc1f64ca88a4a0243c5d1773d10fba67e201/DOCKER/Dockerfile)

View File

@ -2,14 +2,14 @@ GOTOOLS = \
github.com/mitchellh/gox \ github.com/mitchellh/gox \
github.com/Masterminds/glide \ github.com/Masterminds/glide \
github.com/tcnksm/ghr \ github.com/tcnksm/ghr \
gopkg.in/alecthomas/gometalinter.v2 # gopkg.in/alecthomas/gometalinter.v2
GOTOOLS_CHECK = gox glide ghr gometalinter.v2 GOTOOLS_CHECK = gox glide ghr gometalinter.v2
PACKAGES=$(shell go list ./... | grep -v '/vendor/') PACKAGES=$(shell go list ./... | grep -v '/vendor/')
BUILD_TAGS?=tendermint BUILD_TAGS?=tendermint
TMHOME = $${TMHOME:-$$HOME/.tendermint} TMHOME = $${TMHOME:-$$HOME/.tendermint}
BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short HEAD`" BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short HEAD`"
all: check build test install metalinter all: check build test install
check: check_tools get_vendor_deps check: check_tools get_vendor_deps
@ -42,7 +42,7 @@ check_tools:
get_tools: get_tools:
@echo "--> Installing tools" @echo "--> Installing tools"
go get -u -v $(GOTOOLS) go get -u -v $(GOTOOLS)
@gometalinter.v2 --install # @gometalinter.v2 --install
update_tools: update_tools:
@echo "--> Updating tools" @echo "--> Updating tools"

31
Vagrantfile vendored
View File

@ -24,26 +24,29 @@ Vagrant.configure("2") do |config|
apt-get install -y --no-install-recommends wget curl jq \ apt-get install -y --no-install-recommends wget curl jq \
make shellcheck bsdmainutils psmisc make shellcheck bsdmainutils psmisc
apt-get install -y docker-ce golang-1.9-go apt-get install -y docker-ce golang-1.9-go
apt-get install -y language-pack-en
# cleanup
apt-get autoremove -y
# needed for docker # needed for docker
usermod -a -G docker ubuntu usermod -a -G docker vagrant
# use "EOF" not EOF to avoid variable substitution of $PATH # set env variables
cat << "EOF" >> /home/ubuntu/.bash_profile echo 'export PATH=$PATH:/usr/lib/go-1.9/bin:/home/vagrant/go/bin' >> /home/vagrant/.bash_profile
export PATH=$PATH:/usr/lib/go-1.9/bin:/home/ubuntu/go/bin echo 'export GOPATH=/home/vagrant/go' >> /home/vagrant/.bash_profile
export GOPATH=/home/ubuntu/go echo 'export LC_ALL=en_US.UTF-8' >> /home/vagrant/.bash_profile
export LC_ALL=en_US.UTF-8 echo 'cd go/src/github.com/tendermint/tendermint' >> /home/vagrant/.bash_profile
cd go/src/github.com/tendermint/tendermint
EOF
mkdir -p /home/ubuntu/go/bin mkdir -p /home/vagrant/go/bin
mkdir -p /home/ubuntu/go/src/github.com/tendermint mkdir -p /home/vagrant/go/src/github.com/tendermint
ln -s /vagrant /home/ubuntu/go/src/github.com/tendermint/tendermint ln -s /vagrant /home/vagrant/go/src/github.com/tendermint/tendermint
chown -R ubuntu:ubuntu /home/ubuntu/go chown -R vagrant:vagrant /home/vagrant/go
chown ubuntu:ubuntu /home/ubuntu/.bash_profile chown vagrant:vagrant /home/vagrant/.bash_profile
# get all deps and tools, ready to install/test # get all deps and tools, ready to install/test
su - ubuntu -c 'cd /home/ubuntu/go/src/github.com/tendermint/tendermint && make get_vendor_deps && make tools' su - vagrant -c 'source /home/vagrant/.bash_profile'
su - vagrant -c 'cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps'
SHELL SHELL
end end

View File

@ -51,7 +51,7 @@ tendermint node \
--proxy_app dummy \ --proxy_app dummy \
--p2p.laddr tcp://127.0.0.1:56666 \ --p2p.laddr tcp://127.0.0.1:56666 \
--rpc.laddr tcp://127.0.0.1:56667 \ --rpc.laddr tcp://127.0.0.1:56667 \
--p2p.seeds 127.0.0.1:56656 \ --p2p.persistent_peers 127.0.0.1:56656 \
--log_level error & --log_level error &
# wait for node to start up so we only count time where we are actually syncing # wait for node to start up so we only count time where we are actually syncing

View File

@ -16,11 +16,10 @@ func BenchmarkEncodeStatusWire(b *testing.B) {
b.StopTimer() b.StopTimer()
pubKey := crypto.GenPrivKeyEd25519().PubKey() pubKey := crypto.GenPrivKeyEd25519().PubKey()
status := &ctypes.ResultStatus{ status := &ctypes.ResultStatus{
NodeInfo: &p2p.NodeInfo{ NodeInfo: p2p.NodeInfo{
PubKey: pubKey.Unwrap().(crypto.PubKeyEd25519), PubKey: pubKey,
Moniker: "SOMENAME", Moniker: "SOMENAME",
Network: "SOMENAME", Network: "SOMENAME",
RemoteAddr: "SOMEADDR",
ListenAddr: "SOMEADDR", ListenAddr: "SOMEADDR",
Version: "SOMEVER", Version: "SOMEVER",
Other: []string{"SOMESTRING", "OTHERSTRING"}, Other: []string{"SOMESTRING", "OTHERSTRING"},
@ -42,12 +41,11 @@ func BenchmarkEncodeStatusWire(b *testing.B) {
func BenchmarkEncodeNodeInfoWire(b *testing.B) { func BenchmarkEncodeNodeInfoWire(b *testing.B) {
b.StopTimer() b.StopTimer()
pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) pubKey := crypto.GenPrivKeyEd25519().PubKey()
nodeInfo := &p2p.NodeInfo{ nodeInfo := p2p.NodeInfo{
PubKey: pubKey, PubKey: pubKey,
Moniker: "SOMENAME", Moniker: "SOMENAME",
Network: "SOMENAME", Network: "SOMENAME",
RemoteAddr: "SOMEADDR",
ListenAddr: "SOMEADDR", ListenAddr: "SOMEADDR",
Version: "SOMEVER", Version: "SOMEVER",
Other: []string{"SOMESTRING", "OTHERSTRING"}, Other: []string{"SOMESTRING", "OTHERSTRING"},
@ -63,12 +61,11 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) {
func BenchmarkEncodeNodeInfoBinary(b *testing.B) { func BenchmarkEncodeNodeInfoBinary(b *testing.B) {
b.StopTimer() b.StopTimer()
pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) pubKey := crypto.GenPrivKeyEd25519().PubKey()
nodeInfo := &p2p.NodeInfo{ nodeInfo := p2p.NodeInfo{
PubKey: pubKey, PubKey: pubKey,
Moniker: "SOMENAME", Moniker: "SOMENAME",
Network: "SOMENAME", Network: "SOMENAME",
RemoteAddr: "SOMEADDR",
ListenAddr: "SOMEADDR", ListenAddr: "SOMEADDR",
Version: "SOMEVER", Version: "SOMEVER",
Other: []string{"SOMESTRING", "OTHERSTRING"}, Other: []string{"SOMESTRING", "OTHERSTRING"},
@ -87,11 +84,10 @@ func BenchmarkEncodeNodeInfoProto(b *testing.B) {
b.StopTimer() b.StopTimer()
pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519)
pubKey2 := &proto.PubKey{Ed25519: &proto.PubKeyEd25519{Bytes: pubKey[:]}} pubKey2 := &proto.PubKey{Ed25519: &proto.PubKeyEd25519{Bytes: pubKey[:]}}
nodeInfo := &proto.NodeInfo{ nodeInfo := proto.NodeInfo{
PubKey: pubKey2, PubKey: pubKey2,
Moniker: "SOMENAME", Moniker: "SOMENAME",
Network: "SOMENAME", Network: "SOMENAME",
RemoteAddr: "SOMEADDR",
ListenAddr: "SOMEADDR", ListenAddr: "SOMEADDR",
Version: "SOMEVER", Version: "SOMEVER",
Other: []string{"SOMESTRING", "OTHERSTRING"}, Other: []string{"SOMESTRING", "OTHERSTRING"},

View File

@ -5,14 +5,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
flow "github.com/tendermint/tmlibs/flowrate" flow "github.com/tendermint/tmlibs/flowrate"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/types"
) )
/* /*
eg, L = latency = 0.1s eg, L = latency = 0.1s
P = num peers = 10 P = num peers = 10
FN = num full nodes FN = num full nodes
@ -22,7 +23,6 @@ eg, L = latency = 0.1s
B/S = CB/P/BS = 12.8 blocks/s B/S = CB/P/BS = 12.8 blocks/s
12.8 * 0.1 = 1.28 blocks on conn 12.8 * 0.1 = 1.28 blocks on conn
*/ */
const ( const (
@ -56,16 +56,16 @@ type BlockPool struct {
height int64 // the lowest key in requesters. height int64 // the lowest key in requesters.
numPending int32 // number of requests pending assignment or block response numPending int32 // number of requests pending assignment or block response
// peers // peers
peers map[string]*bpPeer peers map[p2p.ID]*bpPeer
maxPeerHeight int64 maxPeerHeight int64
requestsCh chan<- BlockRequest requestsCh chan<- BlockRequest
timeoutsCh chan<- string timeoutsCh chan<- p2p.ID
} }
func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool { func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<- p2p.ID) *BlockPool {
bp := &BlockPool{ bp := &BlockPool{
peers: make(map[string]*bpPeer), peers: make(map[p2p.ID]*bpPeer),
requesters: make(map[int64]*bpRequester), requesters: make(map[int64]*bpRequester),
height: start, height: start,
@ -195,7 +195,8 @@ func (pool *BlockPool) PopRequest() {
// Invalidates the block at pool.height, // Invalidates the block at pool.height,
// Remove the peer and redo request from others. // Remove the peer and redo request from others.
func (pool *BlockPool) RedoRequest(height int64) { // Returns the ID of the removed peer.
func (pool *BlockPool) RedoRequest(height int64) p2p.ID {
pool.mtx.Lock() pool.mtx.Lock()
defer pool.mtx.Unlock() defer pool.mtx.Unlock()
@ -205,12 +206,12 @@ func (pool *BlockPool) RedoRequest(height int64) {
cmn.PanicSanity("Expected block to be non-nil") cmn.PanicSanity("Expected block to be non-nil")
} }
// RemovePeer will redo all requesters associated with this peer. // RemovePeer will redo all requesters associated with this peer.
// TODO: record this malfeasance
pool.removePeer(request.peerID) pool.removePeer(request.peerID)
return request.peerID
} }
// TODO: ensure that blocks come in order for each peer. // TODO: ensure that blocks come in order for each peer.
func (pool *BlockPool) AddBlock(peerID string, block *types.Block, blockSize int) { func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) {
pool.mtx.Lock() pool.mtx.Lock()
defer pool.mtx.Unlock() defer pool.mtx.Unlock()
@ -240,7 +241,7 @@ func (pool *BlockPool) MaxPeerHeight() int64 {
} }
// Sets the peer's alleged blockchain height. // Sets the peer's alleged blockchain height.
func (pool *BlockPool) SetPeerHeight(peerID string, height int64) { func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) {
pool.mtx.Lock() pool.mtx.Lock()
defer pool.mtx.Unlock() defer pool.mtx.Unlock()
@ -258,14 +259,14 @@ func (pool *BlockPool) SetPeerHeight(peerID string, height int64) {
} }
} }
func (pool *BlockPool) RemovePeer(peerID string) { func (pool *BlockPool) RemovePeer(peerID p2p.ID) {
pool.mtx.Lock() pool.mtx.Lock()
defer pool.mtx.Unlock() defer pool.mtx.Unlock()
pool.removePeer(peerID) pool.removePeer(peerID)
} }
func (pool *BlockPool) removePeer(peerID string) { func (pool *BlockPool) removePeer(peerID p2p.ID) {
for _, requester := range pool.requesters { for _, requester := range pool.requesters {
if requester.getPeerID() == peerID { if requester.getPeerID() == peerID {
if requester.getBlock() != nil { if requester.getBlock() != nil {
@ -321,14 +322,14 @@ func (pool *BlockPool) requestersLen() int64 {
return int64(len(pool.requesters)) return int64(len(pool.requesters))
} }
func (pool *BlockPool) sendRequest(height int64, peerID string) { func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) {
if !pool.IsRunning() { if !pool.IsRunning() {
return return
} }
pool.requestsCh <- BlockRequest{height, peerID} pool.requestsCh <- BlockRequest{height, peerID}
} }
func (pool *BlockPool) sendTimeout(peerID string) { func (pool *BlockPool) sendTimeout(peerID p2p.ID) {
if !pool.IsRunning() { if !pool.IsRunning() {
return return
} }
@ -357,7 +358,7 @@ func (pool *BlockPool) debug() string {
type bpPeer struct { type bpPeer struct {
pool *BlockPool pool *BlockPool
id string id p2p.ID
recvMonitor *flow.Monitor recvMonitor *flow.Monitor
height int64 height int64
@ -368,7 +369,7 @@ type bpPeer struct {
logger log.Logger logger log.Logger
} }
func newBPPeer(pool *BlockPool, peerID string, height int64) *bpPeer { func newBPPeer(pool *BlockPool, peerID p2p.ID, height int64) *bpPeer {
peer := &bpPeer{ peer := &bpPeer{
pool: pool, pool: pool,
id: peerID, id: peerID,
@ -434,7 +435,7 @@ type bpRequester struct {
redoCh chan struct{} redoCh chan struct{}
mtx sync.Mutex mtx sync.Mutex
peerID string peerID p2p.ID
block *types.Block block *types.Block
} }
@ -458,7 +459,7 @@ func (bpr *bpRequester) OnStart() error {
} }
// Returns true if the peer matches // Returns true if the peer matches
func (bpr *bpRequester) setBlock(block *types.Block, peerID string) bool { func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool {
bpr.mtx.Lock() bpr.mtx.Lock()
if bpr.block != nil || bpr.peerID != peerID { if bpr.block != nil || bpr.peerID != peerID {
bpr.mtx.Unlock() bpr.mtx.Unlock()
@ -477,7 +478,7 @@ func (bpr *bpRequester) getBlock() *types.Block {
return bpr.block return bpr.block
} }
func (bpr *bpRequester) getPeerID() string { func (bpr *bpRequester) getPeerID() p2p.ID {
bpr.mtx.Lock() bpr.mtx.Lock()
defer bpr.mtx.Unlock() defer bpr.mtx.Unlock()
return bpr.peerID return bpr.peerID
@ -502,7 +503,7 @@ func (bpr *bpRequester) requestRoutine() {
OUTER_LOOP: OUTER_LOOP:
for { for {
// Pick a peer to send request to. // Pick a peer to send request to.
var peer *bpPeer = nil var peer *bpPeer
PICK_PEER_LOOP: PICK_PEER_LOOP:
for { for {
if !bpr.IsRunning() || !bpr.pool.IsRunning() { if !bpr.IsRunning() || !bpr.pool.IsRunning() {
@ -551,5 +552,5 @@ OUTER_LOOP:
type BlockRequest struct { type BlockRequest struct {
Height int64 Height int64
PeerID string PeerID p2p.ID
} }

View File

@ -5,9 +5,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/types"
) )
func init() { func init() {
@ -15,14 +17,14 @@ func init() {
} }
type testPeer struct { type testPeer struct {
id string id p2p.ID
height int64 height int64
} }
func makePeers(numPeers int, minHeight, maxHeight int64) map[string]testPeer { func makePeers(numPeers int, minHeight, maxHeight int64) map[p2p.ID]testPeer {
peers := make(map[string]testPeer, numPeers) peers := make(map[p2p.ID]testPeer, numPeers)
for i := 0; i < numPeers; i++ { for i := 0; i < numPeers; i++ {
peerID := cmn.RandStr(12) peerID := p2p.ID(cmn.RandStr(12))
height := minHeight + rand.Int63n(maxHeight-minHeight) height := minHeight + rand.Int63n(maxHeight-minHeight)
peers[peerID] = testPeer{peerID, height} peers[peerID] = testPeer{peerID, height}
} }
@ -32,7 +34,7 @@ func makePeers(numPeers int, minHeight, maxHeight int64) map[string]testPeer {
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
start := int64(42) start := int64(42)
peers := makePeers(10, start+1, 1000) peers := makePeers(10, start+1, 1000)
timeoutsCh := make(chan string, 100) timeoutsCh := make(chan p2p.ID, 100)
requestsCh := make(chan BlockRequest, 100) requestsCh := make(chan BlockRequest, 100)
pool := NewBlockPool(start, requestsCh, timeoutsCh) pool := NewBlockPool(start, requestsCh, timeoutsCh)
pool.SetLogger(log.TestingLogger()) pool.SetLogger(log.TestingLogger())
@ -89,7 +91,7 @@ func TestBasic(t *testing.T) {
func TestTimeout(t *testing.T) { func TestTimeout(t *testing.T) {
start := int64(42) start := int64(42)
peers := makePeers(10, start+1, 1000) peers := makePeers(10, start+1, 1000)
timeoutsCh := make(chan string, 100) timeoutsCh := make(chan p2p.ID, 100)
requestsCh := make(chan BlockRequest, 100) requestsCh := make(chan BlockRequest, 100)
pool := NewBlockPool(start, requestsCh, timeoutsCh) pool := NewBlockPool(start, requestsCh, timeoutsCh)
pool.SetLogger(log.TestingLogger()) pool.SetLogger(log.TestingLogger())
@ -127,7 +129,7 @@ func TestTimeout(t *testing.T) {
// Pull from channels // Pull from channels
counter := 0 counter := 0
timedOut := map[string]struct{}{} timedOut := map[p2p.ID]struct{}{}
for { for {
select { select {
case peerID := <-timeoutsCh: case peerID := <-timeoutsCh:

View File

@ -3,16 +3,19 @@ package blockchain
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"reflect" "reflect"
"sync" "sync"
"time" "time"
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/p2p"
sm "github.com/tendermint/tendermint/state" sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
) )
const ( const (
@ -51,18 +54,22 @@ type BlockchainReactor struct {
store *BlockStore store *BlockStore
pool *BlockPool pool *BlockPool
fastSync bool fastSync bool
requestsCh chan BlockRequest
timeoutsCh chan string requestsCh <-chan BlockRequest
timeoutsCh <-chan p2p.ID
} }
// NewBlockchainReactor returns new reactor instance. // NewBlockchainReactor returns new reactor instance.
func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *BlockStore, fastSync bool) *BlockchainReactor { func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *BlockStore,
fastSync bool) *BlockchainReactor {
if state.LastBlockHeight != store.Height() { if state.LastBlockHeight != store.Height() {
cmn.PanicSanity(cmn.Fmt("state (%v) and store (%v) height mismatch", state.LastBlockHeight, store.Height())) cmn.PanicSanity(cmn.Fmt("state (%v) and store (%v) height mismatch", state.LastBlockHeight,
store.Height()))
} }
requestsCh := make(chan BlockRequest, defaultChannelCapacity) requestsCh := make(chan BlockRequest, defaultChannelCapacity)
timeoutsCh := make(chan string, defaultChannelCapacity) timeoutsCh := make(chan p2p.ID, defaultChannelCapacity)
pool := NewBlockPool( pool := NewBlockPool(
store.Height()+1, store.Height()+1,
requestsCh, requestsCh,
@ -122,7 +129,8 @@ func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
// AddPeer implements Reactor by sending our state to peer. // AddPeer implements Reactor by sending our state to peer.
func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) { func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
if !peer.Send(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) { if !peer.Send(BlockchainChannel,
struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) {
// doing nothing, will try later in `poolRoutine` // doing nothing, will try later in `poolRoutine`
} }
// peer is added to the pool once we receive the first // peer is added to the pool once we receive the first
@ -131,14 +139,16 @@ func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) {
// RemovePeer implements Reactor by removing peer from the pool. // RemovePeer implements Reactor by removing peer from the pool.
func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
bcR.pool.RemovePeer(peer.Key()) bcR.pool.RemovePeer(peer.ID())
} }
// respondToPeer loads a block and sends it to the requesting peer, // respondToPeer loads a block and sends it to the requesting peer,
// if we have it. Otherwise, we'll respond saying we don't have it. // if we have it. Otherwise, we'll respond saying we don't have it.
// According to the Tendermint spec, if all nodes are honest, // According to the Tendermint spec, if all nodes are honest,
// no node should be requesting for a block that's non-existent. // no node should be requesting for a block that's non-existent.
func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage, src p2p.Peer) (queued bool) { func (bcR *BlockchainReactor) respondToPeer(msg *bcBlockRequestMessage,
src p2p.Peer) (queued bool) {
block := bcR.store.LoadBlock(msg.Height) block := bcR.store.LoadBlock(msg.Height)
if block != nil { if block != nil {
msg := &bcBlockResponseMessage{Block: block} msg := &bcBlockResponseMessage{Block: block}
@ -162,7 +172,6 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
bcR.Logger.Debug("Receive", "src", src, "chID", chID, "msg", msg) bcR.Logger.Debug("Receive", "src", src, "chID", chID, "msg", msg)
// TODO: improve logic to satisfy megacheck
switch msg := msg.(type) { switch msg := msg.(type) {
case *bcBlockRequestMessage: case *bcBlockRequestMessage:
if queued := bcR.respondToPeer(msg, src); !queued { if queued := bcR.respondToPeer(msg, src); !queued {
@ -170,16 +179,17 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
} }
case *bcBlockResponseMessage: case *bcBlockResponseMessage:
// Got a block. // Got a block.
bcR.pool.AddBlock(src.Key(), msg.Block, len(msgBytes)) bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes))
case *bcStatusRequestMessage: case *bcStatusRequestMessage:
// Send peer our state. // Send peer our state.
queued := src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) queued := src.TrySend(BlockchainChannel,
struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}})
if !queued { if !queued {
// sorry // sorry
} }
case *bcStatusResponseMessage: case *bcStatusResponseMessage:
// Got a peer status. Unverified. // Got a peer status. Unverified.
bcR.pool.SetPeerHeight(src.Key(), msg.Height) bcR.pool.SetPeerHeight(src.ID(), msg.Height)
default: default:
bcR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg))) bcR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg)))
} }
@ -277,23 +287,28 @@ FOR_LOOP:
chainID, firstID, first.Height, second.LastCommit) chainID, firstID, first.Height, second.LastCommit)
if err != nil { if err != nil {
bcR.Logger.Error("Error in validation", "err", err) bcR.Logger.Error("Error in validation", "err", err)
bcR.pool.RedoRequest(first.Height) peerID := bcR.pool.RedoRequest(first.Height)
peer := bcR.Switch.Peers().Get(peerID)
if peer != nil {
bcR.Switch.StopPeerForError(peer, fmt.Errorf("BlockchainReactor validation error: %v", err))
}
break SYNC_LOOP break SYNC_LOOP
} else { } else {
bcR.pool.PopRequest() bcR.pool.PopRequest()
// TODO: batch saves so we dont persist to disk every block
bcR.store.SaveBlock(first, firstParts, second.LastCommit) bcR.store.SaveBlock(first, firstParts, second.LastCommit)
// NOTE: we could improve performance if we // TODO: same thing for app - but we would need a way to
// didn't make the app commit to disk every block // get the hash without persisting the state
// ... but we would need a way to get the hash without it persisting
var err error var err error
state, err = bcR.blockExec.ApplyBlock(state, firstID, first) state, err = bcR.blockExec.ApplyBlock(state, firstID, first)
if err != nil { if err != nil {
// TODO This is bad, are we zombie? // TODO This is bad, are we zombie?
cmn.PanicQ(cmn.Fmt("Failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err)) cmn.PanicQ(cmn.Fmt("Failed to process committed block (%d:%X): %v",
first.Height, first.Hash(), err))
} }
blocksSynced += 1 blocksSynced++
// update the consensus params // update the consensus params
bcR.updateConsensusParams(state.ConsensusParams) bcR.updateConsensusParams(state.ConsensusParams)
@ -315,7 +330,8 @@ FOR_LOOP:
// BroadcastStatusRequest broadcasts `BlockStore` height. // BroadcastStatusRequest broadcasts `BlockStore` height.
func (bcR *BlockchainReactor) BroadcastStatusRequest() error { func (bcR *BlockchainReactor) BroadcastStatusRequest() error {
bcR.Switch.Broadcast(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusRequestMessage{bcR.store.Height()}}) bcR.Switch.Broadcast(BlockchainChannel,
struct{ BlockchainMessage }{&bcStatusRequestMessage{bcR.store.Height()}})
return nil return nil
} }

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db" dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
@ -28,7 +29,8 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe
// Make the blockchainReactor itself // Make the blockchainReactor itself
fastSync := true fastSync := true
var nilApp proxy.AppConnConsensus var nilApp proxy.AppConnConsensus
blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp, types.MockMempool{}, types.MockEvidencePool{}) blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp,
types.MockMempool{}, types.MockEvidencePool{})
bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync)
bcReactor.SetLogger(logger.With("module", "blockchain")) bcReactor.SetLogger(logger.With("module", "blockchain"))
@ -47,7 +49,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe
return bcReactor return bcReactor
} }
func TestNoBlockMessageResponse(t *testing.T) { func TestNoBlockResponse(t *testing.T) {
maxBlockHeight := int64(20) maxBlockHeight := int64(20)
bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight) bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight)
@ -55,7 +57,7 @@ func TestNoBlockMessageResponse(t *testing.T) {
defer bcr.Stop() defer bcr.Stop()
// Add some peers in // Add some peers in
peer := newbcrTestPeer(cmn.RandStr(12)) peer := newbcrTestPeer(p2p.ID(cmn.RandStr(12)))
bcr.AddPeer(peer) bcr.AddPeer(peer)
chID := byte(0x01) chID := byte(0x01)
@ -71,7 +73,7 @@ func TestNoBlockMessageResponse(t *testing.T) {
} }
// receive a request message from peer, // receive a request message from peer,
// wait to hear response // wait for our response to be received on the peer
for _, tt := range tests { for _, tt := range tests {
reqBlockMsg := &bcBlockRequestMessage{tt.height} reqBlockMsg := &bcBlockRequestMessage{tt.height}
reqBlockBytes := wire.BinaryBytes(struct{ BlockchainMessage }{reqBlockMsg}) reqBlockBytes := wire.BinaryBytes(struct{ BlockchainMessage }{reqBlockMsg})
@ -95,6 +97,49 @@ func TestNoBlockMessageResponse(t *testing.T) {
} }
} }
/*
// NOTE: This is too hard to test without
// an easy way to add test peer to switch
// or without significant refactoring of the module.
// Alternatively we could actually dial a TCP conn but
// that seems extreme.
func TestBadBlockStopsPeer(t *testing.T) {
maxBlockHeight := int64(20)
bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight)
bcr.Start()
defer bcr.Stop()
// Add some peers in
peer := newbcrTestPeer(p2p.ID(cmn.RandStr(12)))
// XXX: This doesn't add the peer to anything,
// so it's hard to check that it's later removed
bcr.AddPeer(peer)
assert.True(t, bcr.Switch.Peers().Size() > 0)
// send a bad block from the peer
// default blocks already dont have commits, so should fail
block := bcr.store.LoadBlock(3)
msg := &bcBlockResponseMessage{Block: block}
peer.Send(BlockchainChannel, struct{ BlockchainMessage }{msg})
ticker := time.NewTicker(time.Millisecond * 10)
timer := time.NewTimer(time.Second * 2)
LOOP:
for {
select {
case <-ticker.C:
if bcr.Switch.Peers().Size() == 0 {
break LOOP
}
case <-timer.C:
t.Fatal("Timed out waiting to disconnect peer")
}
}
}
*/
//---------------------------------------------- //----------------------------------------------
// utility funcs // utility funcs
@ -113,16 +158,16 @@ func makeBlock(height int64, state sm.State) *types.Block {
// The Test peer // The Test peer
type bcrTestPeer struct { type bcrTestPeer struct {
cmn.Service cmn.Service
key string id p2p.ID
ch chan interface{} ch chan interface{}
} }
var _ p2p.Peer = (*bcrTestPeer)(nil) var _ p2p.Peer = (*bcrTestPeer)(nil)
func newbcrTestPeer(key string) *bcrTestPeer { func newbcrTestPeer(id p2p.ID) *bcrTestPeer {
return &bcrTestPeer{ return &bcrTestPeer{
Service: cmn.NewBaseService(nil, "bcrTestPeer", nil), Service: cmn.NewBaseService(nil, "bcrTestPeer", nil),
key: key, id: id,
ch: make(chan interface{}, 2), ch: make(chan interface{}, 2),
} }
} }
@ -130,7 +175,8 @@ func newbcrTestPeer(key string) *bcrTestPeer {
func (tp *bcrTestPeer) lastValue() interface{} { return <-tp.ch } func (tp *bcrTestPeer) lastValue() interface{} { return <-tp.ch }
func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool { func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool {
if _, ok := value.(struct{ BlockchainMessage }).BlockchainMessage.(*bcStatusResponseMessage); ok { if _, ok := value.(struct{ BlockchainMessage }).
BlockchainMessage.(*bcStatusResponseMessage); ok {
// Discard status response messages since they skew our results // Discard status response messages since they skew our results
// We only want to deal with: // We only want to deal with:
// + bcBlockResponseMessage // + bcBlockResponseMessage
@ -142,9 +188,9 @@ func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool {
} }
func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) } func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) }
func (tp *bcrTestPeer) NodeInfo() *p2p.NodeInfo { return nil } func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.NodeInfo{} }
func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} }
func (tp *bcrTestPeer) Key() string { return tp.key } func (tp *bcrTestPeer) ID() p2p.ID { return tp.id }
func (tp *bcrTestPeer) IsOutbound() bool { return false } func (tp *bcrTestPeer) IsOutbound() bool { return false }
func (tp *bcrTestPeer) IsPersistent() bool { return true } func (tp *bcrTestPeer) IsPersistent() bool { return true }
func (tp *bcrTestPeer) Get(s string) interface{} { return s } func (tp *bcrTestPeer) Get(s string) interface{} { return s }

View File

@ -8,9 +8,11 @@ import (
"sync" "sync"
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db" dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tendermint/types"
) )
/* /*

View File

@ -13,9 +13,11 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
"github.com/tendermint/tendermint/types"
) )
func TestLoadBlockStoreStateJSON(t *testing.T) { func TestLoadBlockStoreStateJSON(t *testing.T) {
@ -104,7 +106,8 @@ var (
partSet = block.MakePartSet(2) partSet = block.MakePartSet(2)
part1 = partSet.GetPart(0) part1 = partSet.GetPart(0)
part2 = partSet.GetPart(1) part2 = partSet.GetPart(1)
seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10,
Timestamp: time.Now().UTC()}}}
) )
// TODO: This test should be simplified ... // TODO: This test should be simplified ...
@ -124,7 +127,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
// save a block // save a block
block := makeBlock(bs.Height()+1, state) block := makeBlock(bs.Height()+1, state)
validPartSet := block.MakePartSet(2) validPartSet := block.MakePartSet(2)
seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10,
Timestamp: time.Now().UTC()}}}
bs.SaveBlock(block, partSet, seenCommit) bs.SaveBlock(block, partSet, seenCommit)
require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
@ -143,7 +147,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
// End of setup, test data // End of setup, test data
commitAtH10 := &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} commitAtH10 := &types.Commit{Precommits: []*types.Vote{{Height: 10,
Timestamp: time.Now().UTC()}}}
tuples := []struct { tuples := []struct {
block *types.Block block *types.Block
parts *types.PartSet parts *types.PartSet
@ -263,7 +268,8 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus")) db.Set(calcBlockCommitKey(commitHeight), []byte("foo-bogus"))
} }
bCommit := bs.LoadBlockCommit(commitHeight) bCommit := bs.LoadBlockCommit(commitHeight)
return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit, meta: bBlockMeta}, nil return &quad{block: bBlock, seenCommit: bSeenCommit, commit: bCommit,
meta: bBlockMeta}, nil
}) })
if subStr := tuple.wantPanic; subStr != "" { if subStr := tuple.wantPanic; subStr != "" {
@ -290,10 +296,12 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
continue continue
} }
if tuple.eraseSeenCommitInDB { if tuple.eraseSeenCommitInDB {
assert.Nil(t, qua.seenCommit, "erased the seenCommit in the DB hence we should get back a nil seenCommit") assert.Nil(t, qua.seenCommit,
"erased the seenCommit in the DB hence we should get back a nil seenCommit")
} }
if tuple.eraseCommitInDB { if tuple.eraseCommitInDB {
assert.Nil(t, qua.commit, "erased the commit in the DB hence we should get back a nil commit") assert.Nil(t, qua.commit,
"erased the commit in the DB hence we should get back a nil commit")
} }
} }
} }
@ -331,7 +339,8 @@ func TestLoadBlockPart(t *testing.T) {
gotPart, _, panicErr := doFn(loadPart) gotPart, _, panicErr := doFn(loadPart)
require.Nil(t, panicErr, "an existent and proper block should not panic") require.Nil(t, panicErr, "an existent and proper block should not panic")
require.Nil(t, res, "a properly saved block should return a proper block") require.Nil(t, res, "a properly saved block should return a proper block")
require.Equal(t, gotPart.(*types.Part).Hash(), part1.Hash(), "expecting successful retrieval of previously saved block") require.Equal(t, gotPart.(*types.Part).Hash(), part1.Hash(),
"expecting successful retrieval of previously saved block")
} }
func TestLoadBlockMeta(t *testing.T) { func TestLoadBlockMeta(t *testing.T) {
@ -360,7 +369,8 @@ func TestLoadBlockMeta(t *testing.T) {
gotMeta, _, panicErr := doFn(loadMeta) gotMeta, _, panicErr := doFn(loadMeta)
require.Nil(t, panicErr, "an existent and proper block should not panic") require.Nil(t, panicErr, "an existent and proper block should not panic")
require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ") require.Nil(t, res, "a properly saved blockMeta should return a proper blocMeta ")
require.Equal(t, binarySerializeIt(meta), binarySerializeIt(gotMeta), "expecting successful retrieval of previously saved blockMeta") require.Equal(t, binarySerializeIt(meta), binarySerializeIt(gotMeta),
"expecting successful retrieval of previously saved blockMeta")
} }
func TestBlockFetchAtHeight(t *testing.T) { func TestBlockFetchAtHeight(t *testing.T) {
@ -369,13 +379,15 @@ func TestBlockFetchAtHeight(t *testing.T) {
block := makeBlock(bs.Height()+1, state) block := makeBlock(bs.Height()+1, state)
partSet := block.MakePartSet(2) partSet := block.MakePartSet(2)
seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: time.Now().UTC()}}} seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10,
Timestamp: time.Now().UTC()}}}
bs.SaveBlock(block, partSet, seenCommit) bs.SaveBlock(block, partSet, seenCommit)
require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed")
blockAtHeight := bs.LoadBlock(bs.Height()) blockAtHeight := bs.LoadBlock(bs.Height())
require.Equal(t, block.Hash(), blockAtHeight.Hash(), "expecting a successful load of the last saved block") require.Equal(t, block.Hash(), blockAtHeight.Hash(),
"expecting a successful load of the last saved block")
blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1) blockAtHeightPlus1 := bs.LoadBlock(bs.Height() + 1)
require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1") require.Nil(t, blockAtHeightPlus1, "expecting an unsuccessful load of Height()+1")

View File

@ -31,4 +31,5 @@ test:
- cd "$PROJECT_PATH" && mv test_integrations.log "${CIRCLE_ARTIFACTS}" - cd "$PROJECT_PATH" && mv test_integrations.log "${CIRCLE_ARTIFACTS}"
- cd "$PROJECT_PATH" && bash <(curl -s https://codecov.io/bash) -f coverage.txt - cd "$PROJECT_PATH" && bash <(curl -s https://codecov.io/bash) -f coverage.txt
- cd "$PROJECT_PATH" && mv coverage.txt "${CIRCLE_ARTIFACTS}" - cd "$PROJECT_PATH" && mv coverage.txt "${CIRCLE_ARTIFACTS}"
- cd "$PROJECT_PATH" && cp test/logs/messages "${CIRCLE_ARTIFACTS}/docker_logs.txt" - cd "$PROJECT_PATH" && cp test/logs/messages "${CIRCLE_ARTIFACTS}/docker.log"
- cd "${CIRCLE_ARTIFACTS}" && tar czf logs.tar.gz *.log

View File

@ -18,7 +18,11 @@ var (
) )
func init() { func init() {
RootCmd.PersistentFlags().String("log_level", config.LogLevel, "Log level") registerFlagsRootCmd(RootCmd)
}
func registerFlagsRootCmd(cmd *cobra.Command) {
cmd.PersistentFlags().String("log_level", config.LogLevel, "Log level")
} }
// ParseConfig retrieves the default environment configuration, // ParseConfig retrieves the default environment configuration,

View File

@ -1,7 +1,10 @@
package commands package commands
import ( import (
"fmt"
"io/ioutil"
"os" "os"
"path/filepath"
"strconv" "strconv"
"testing" "testing"
@ -12,6 +15,7 @@ import (
cfg "github.com/tendermint/tendermint/config" cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tmlibs/cli" "github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
) )
var ( var (
@ -22,89 +26,151 @@ const (
rootName = "root" rootName = "root"
) )
// isolate provides a clean setup and returns a copy of RootCmd you can // clearConfig clears env vars, the given root dir, and resets viper.
// modify in the test cases. func clearConfig(dir string) {
// NOTE: it unsets all TM* env variables.
func isolate(cmds ...*cobra.Command) cli.Executable {
if err := os.Unsetenv("TMHOME"); err != nil { if err := os.Unsetenv("TMHOME"); err != nil {
panic(err) panic(err)
} }
if err := os.Unsetenv("TM_HOME"); err != nil { if err := os.Unsetenv("TM_HOME"); err != nil {
panic(err) panic(err)
} }
if err := os.RemoveAll(defaultRoot); err != nil {
if err := os.RemoveAll(dir); err != nil {
panic(err) panic(err)
} }
viper.Reset() viper.Reset()
config = cfg.DefaultConfig() config = cfg.DefaultConfig()
r := &cobra.Command{ }
Use: rootName,
// prepare new rootCmd
func testRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: RootCmd.Use,
PersistentPreRunE: RootCmd.PersistentPreRunE, PersistentPreRunE: RootCmd.PersistentPreRunE,
Run: func(cmd *cobra.Command, args []string) {},
} }
r.AddCommand(cmds...) registerFlagsRootCmd(rootCmd)
wr := cli.PrepareBaseCmd(r, "TM", defaultRoot) var l string
return wr rootCmd.PersistentFlags().String("log", l, "Log")
return rootCmd
} }
func TestRootConfig(t *testing.T) { func testSetup(rootDir string, args []string, env map[string]string) error {
assert, require := assert.New(t), require.New(t) clearConfig(defaultRoot)
// we pre-create a config file we can refer to in the rest of rootCmd := testRootCmd()
// the test cases. cmd := cli.PrepareBaseCmd(rootCmd, "TM", defaultRoot)
cvals := map[string]string{
"moniker": "monkey", // run with the args and env
"fast_sync": "false", args = append([]string{rootCmd.Use}, args...)
return cli.RunWithArgs(cmd, args, env)
} }
// proper types of the above settings
cfast := false
conf, err := cli.WriteDemoConfig(cvals)
require.Nil(err)
defaults := cfg.DefaultConfig()
dmax := defaults.P2P.MaxNumPeers
func TestRootHome(t *testing.T) {
newRoot := filepath.Join(defaultRoot, "something-else")
cases := []struct { cases := []struct {
args []string args []string
env map[string]string env map[string]string
root string root string
moniker string
fastSync bool
maxPeer int
}{ }{
{nil, nil, defaultRoot, defaults.Moniker, defaults.FastSync, dmax}, {nil, nil, defaultRoot},
// try multiple ways of setting root (two flags, cli vs. env) {[]string{"--home", newRoot}, nil, newRoot},
{[]string{"--home", conf}, nil, conf, cvals["moniker"], cfast, dmax}, {nil, map[string]string{"TMHOME": newRoot}, newRoot},
{nil, map[string]string{"TMHOME": conf}, conf, cvals["moniker"], cfast, dmax},
// check setting p2p subflags two different ways
{[]string{"--p2p.max_num_peers", "420"}, nil, defaultRoot, defaults.Moniker, defaults.FastSync, 420},
{nil, map[string]string{"TM_P2P_MAX_NUM_PEERS": "17"}, defaultRoot, defaults.Moniker, defaults.FastSync, 17},
// try to set env that have no flags attached...
{[]string{"--home", conf}, map[string]string{"TM_MONIKER": "funny"}, conf, "funny", cfast, dmax},
} }
for idx, tc := range cases { for i, tc := range cases {
i := strconv.Itoa(idx) idxString := strconv.Itoa(i)
// test command that does nothing, except trigger unmarshalling in root
noop := &cobra.Command{
Use: "noop",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
noop.Flags().Int("p2p.max_num_peers", defaults.P2P.MaxNumPeers, "")
cmd := isolate(noop)
args := append([]string{rootName, noop.Use}, tc.args...) err := testSetup(defaultRoot, tc.args, tc.env)
err := cli.RunWithArgs(cmd, args, tc.env) require.Nil(t, err, idxString)
require.Nil(err, i)
assert.Equal(tc.root, config.RootDir, i) assert.Equal(t, tc.root, config.RootDir, idxString)
assert.Equal(tc.root, config.P2P.RootDir, i) assert.Equal(t, tc.root, config.P2P.RootDir, idxString)
assert.Equal(tc.root, config.Consensus.RootDir, i) assert.Equal(t, tc.root, config.Consensus.RootDir, idxString)
assert.Equal(tc.root, config.Mempool.RootDir, i) assert.Equal(t, tc.root, config.Mempool.RootDir, idxString)
assert.Equal(tc.moniker, config.Moniker, i) }
assert.Equal(tc.fastSync, config.FastSync, i)
assert.Equal(tc.maxPeer, config.P2P.MaxNumPeers, i)
} }
func TestRootFlagsEnv(t *testing.T) {
// defaults
defaults := cfg.DefaultConfig()
defaultLogLvl := defaults.LogLevel
cases := []struct {
args []string
env map[string]string
logLevel string
}{
{[]string{"--log", "debug"}, nil, defaultLogLvl}, // wrong flag
{[]string{"--log_level", "debug"}, nil, "debug"}, // right flag
{nil, map[string]string{"TM_LOW": "debug"}, defaultLogLvl}, // wrong env flag
{nil, map[string]string{"MT_LOG_LEVEL": "debug"}, defaultLogLvl}, // wrong env prefix
{nil, map[string]string{"TM_LOG_LEVEL": "debug"}, "debug"}, // right env
}
for i, tc := range cases {
idxString := strconv.Itoa(i)
err := testSetup(defaultRoot, tc.args, tc.env)
require.Nil(t, err, idxString)
assert.Equal(t, tc.logLevel, config.LogLevel, idxString)
}
}
func TestRootConfig(t *testing.T) {
// write non-default config
nonDefaultLogLvl := "abc:debug"
cvals := map[string]string{
"log_level": nonDefaultLogLvl,
}
cases := []struct {
args []string
env map[string]string
logLvl string
}{
{nil, nil, nonDefaultLogLvl}, // should load config
{[]string{"--log_level=abc:info"}, nil, "abc:info"}, // flag over rides
{nil, map[string]string{"TM_LOG_LEVEL": "abc:info"}, "abc:info"}, // env over rides
}
for i, tc := range cases {
idxString := strconv.Itoa(i)
clearConfig(defaultRoot)
// XXX: path must match cfg.defaultConfigPath
configFilePath := filepath.Join(defaultRoot, "config")
err := cmn.EnsureDir(configFilePath, 0700)
require.Nil(t, err)
// write the non-defaults to a different path
// TODO: support writing sub configs so we can test that too
err = WriteConfigVals(configFilePath, cvals)
require.Nil(t, err)
rootCmd := testRootCmd()
cmd := cli.PrepareBaseCmd(rootCmd, "TM", defaultRoot)
// run with the args and env
tc.args = append([]string{rootCmd.Use}, tc.args...)
err = cli.RunWithArgs(cmd, tc.args, tc.env)
require.Nil(t, err, idxString)
assert.Equal(t, tc.logLvl, config.LogLevel, idxString)
}
}
// WriteConfigVals writes a toml file with the given values.
// It returns an error if writing was impossible.
func WriteConfigVals(dir string, vals map[string]string) error {
data := ""
for k, v := range vals {
data = data + fmt.Sprintf("%s = \"%s\"\n", k, v)
}
cfile := filepath.Join(dir, "config.toml")
return ioutil.WriteFile(cfile, []byte(data), 0666)
} }

View File

@ -29,6 +29,7 @@ func AddNodeFlags(cmd *cobra.Command) {
// p2p flags // p2p flags
cmd.Flags().String("p2p.laddr", config.P2P.ListenAddress, "Node listen address. (0.0.0.0:0 means any interface, any port)") cmd.Flags().String("p2p.laddr", config.P2P.ListenAddress, "Node listen address. (0.0.0.0:0 means any interface, any port)")
cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma delimited host:port seed nodes") cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma delimited host:port seed nodes")
cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "Comma delimited host:port persistent peers")
cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration")
cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange") cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange")

View File

@ -2,11 +2,12 @@ package commands
import ( import (
"fmt" "fmt"
"path" "path/filepath"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
@ -35,6 +36,7 @@ var TestnetFilesCmd = &cobra.Command{
func testnetFiles(cmd *cobra.Command, args []string) { func testnetFiles(cmd *cobra.Command, args []string) {
genVals := make([]types.GenesisValidator, nValidators) genVals := make([]types.GenesisValidator, nValidators)
defaultConfig := cfg.DefaultBaseConfig()
// Initialize core dir and priv_validator.json's // Initialize core dir and priv_validator.json's
for i := 0; i < nValidators; i++ { for i := 0; i < nValidators; i++ {
@ -44,7 +46,7 @@ func testnetFiles(cmd *cobra.Command, args []string) {
cmn.Exit(err.Error()) cmn.Exit(err.Error())
} }
// Read priv_validator.json to populate vals // Read priv_validator.json to populate vals
privValFile := path.Join(dataDir, mach, "priv_validator.json") privValFile := filepath.Join(dataDir, mach, defaultConfig.PrivValidator)
privVal := types.LoadPrivValidatorFS(privValFile) privVal := types.LoadPrivValidatorFS(privValFile)
genVals[i] = types.GenesisValidator{ genVals[i] = types.GenesisValidator{
PubKey: privVal.GetPubKey(), PubKey: privVal.GetPubKey(),
@ -63,7 +65,7 @@ func testnetFiles(cmd *cobra.Command, args []string) {
// Write genesis file. // Write genesis file.
for i := 0; i < nValidators; i++ { for i := 0; i < nValidators; i++ {
mach := cmn.Fmt("mach%d", i) mach := cmn.Fmt("mach%d", i)
if err := genDoc.SaveAs(path.Join(dataDir, mach, "genesis.json")); err != nil { if err := genDoc.SaveAs(filepath.Join(dataDir, mach, defaultConfig.Genesis)); err != nil {
panic(err) panic(err)
} }
} }
@ -73,14 +75,15 @@ func testnetFiles(cmd *cobra.Command, args []string) {
// Initialize per-machine core directory // Initialize per-machine core directory
func initMachCoreDirectory(base, mach string) error { func initMachCoreDirectory(base, mach string) error {
dir := path.Join(base, mach) dir := filepath.Join(base, mach)
err := cmn.EnsureDir(dir, 0777) err := cmn.EnsureDir(dir, 0777)
if err != nil { if err != nil {
return err return err
} }
// Create priv_validator.json file if not present // Create priv_validator.json file if not present
ensurePrivValidator(path.Join(dir, "priv_validator.json")) defaultConfig := cfg.DefaultBaseConfig()
ensurePrivValidator(filepath.Join(dir, defaultConfig.PrivValidator))
return nil return nil
} }

View File

@ -2,10 +2,12 @@ package main
import ( import (
"os" "os"
"path/filepath"
"github.com/tendermint/tmlibs/cli" "github.com/tendermint/tmlibs/cli"
cmd "github.com/tendermint/tendermint/cmd/tendermint/commands" cmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
cfg "github.com/tendermint/tendermint/config"
nm "github.com/tendermint/tendermint/node" nm "github.com/tendermint/tendermint/node"
) )
@ -37,7 +39,7 @@ func main() {
// Create & start node // Create & start node
rootCmd.AddCommand(cmd.NewRunNodeCmd(nodeFunc)) rootCmd.AddCommand(cmd.NewRunNodeCmd(nodeFunc))
cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv("$HOME/.tendermint")) cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv(filepath.Join("$HOME", cfg.DefaultTendermintDir)))
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
panic(err) panic(err)
} }

View File

@ -7,6 +7,30 @@ import (
"time" "time"
) )
// NOTE: Most of the structs & relevant comments + the
// default configuration options were used to manually
// generate the config.toml. Please reflect any changes
// made here in the defaultConfigTemplate constant in
// config/toml.go
// NOTE: tmlibs/cli must know to look in the config dir!
var (
DefaultTendermintDir = ".tendermint"
defaultConfigDir = "config"
defaultDataDir = "data"
defaultConfigFileName = "config.toml"
defaultGenesisJSONName = "genesis.json"
defaultPrivValName = "priv_validator.json"
defaultNodeKeyName = "node_key.json"
defaultAddrBookName = "addrbook.json"
defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName)
defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName)
defaultPrivValPath = filepath.Join(defaultConfigDir, defaultPrivValName)
defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName)
defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName)
)
// Config defines the top level configuration for a Tendermint node // Config defines the top level configuration for a Tendermint node
type Config struct { type Config struct {
// Top level options use an anonymous struct // Top level options use an anonymous struct
@ -38,9 +62,9 @@ func TestConfig() *Config {
BaseConfig: TestBaseConfig(), BaseConfig: TestBaseConfig(),
RPC: TestRPCConfig(), RPC: TestRPCConfig(),
P2P: TestP2PConfig(), P2P: TestP2PConfig(),
Mempool: DefaultMempoolConfig(), Mempool: TestMempoolConfig(),
Consensus: TestConsensusConfig(), Consensus: TestConsensusConfig(),
TxIndex: DefaultTxIndexConfig(), TxIndex: TestTxIndexConfig(),
} }
} }
@ -59,19 +83,23 @@ func (cfg *Config) SetRoot(root string) *Config {
// BaseConfig defines the base configuration for a Tendermint node // BaseConfig defines the base configuration for a Tendermint node
type BaseConfig struct { type BaseConfig struct {
// chainID is unexposed and immutable but here for convenience
chainID string
// The root directory for all data. // The root directory for all data.
// This should be set in viper so it can unmarshal into this struct // This should be set in viper so it can unmarshal into this struct
RootDir string `mapstructure:"home"` RootDir string `mapstructure:"home"`
// The ID of the chain to join (should be signed with every transaction and vote) // Path to the JSON file containing the initial validator set and other meta data
ChainID string `mapstructure:"chain_id"`
// A JSON file containing the initial validator set and other meta data
Genesis string `mapstructure:"genesis_file"` Genesis string `mapstructure:"genesis_file"`
// A JSON file containing the private key to use as a validator in the consensus protocol // Path to the JSON file containing the private key to use as a validator in the consensus protocol
PrivValidator string `mapstructure:"priv_validator_file"` PrivValidator string `mapstructure:"priv_validator_file"`
// A JSON file containing the private key to use for p2p authenticated encryption
NodeKey string `mapstructure:"node_key_file"`
// A custom human readable name for this node // A custom human readable name for this node
Moniker string `mapstructure:"moniker"` Moniker string `mapstructure:"moniker"`
@ -104,11 +132,16 @@ type BaseConfig struct {
DBPath string `mapstructure:"db_dir"` DBPath string `mapstructure:"db_dir"`
} }
func (c BaseConfig) ChainID() string {
return c.chainID
}
// DefaultBaseConfig returns a default base configuration for a Tendermint node // DefaultBaseConfig returns a default base configuration for a Tendermint node
func DefaultBaseConfig() BaseConfig { func DefaultBaseConfig() BaseConfig {
return BaseConfig{ return BaseConfig{
Genesis: "genesis.json", Genesis: defaultGenesisJSONPath,
PrivValidator: "priv_validator.json", PrivValidator: defaultPrivValPath,
NodeKey: defaultNodeKeyPath,
Moniker: defaultMoniker, Moniker: defaultMoniker,
ProxyApp: "tcp://127.0.0.1:46658", ProxyApp: "tcp://127.0.0.1:46658",
ABCI: "socket", ABCI: "socket",
@ -124,7 +157,7 @@ func DefaultBaseConfig() BaseConfig {
// TestBaseConfig returns a base configuration for testing a Tendermint node // TestBaseConfig returns a base configuration for testing a Tendermint node
func TestBaseConfig() BaseConfig { func TestBaseConfig() BaseConfig {
conf := DefaultBaseConfig() conf := DefaultBaseConfig()
conf.ChainID = "tendermint_test" conf.chainID = "tendermint_test"
conf.ProxyApp = "dummy" conf.ProxyApp = "dummy"
conf.FastSync = false conf.FastSync = false
conf.DBBackend = "memdb" conf.DBBackend = "memdb"
@ -141,6 +174,11 @@ func (b BaseConfig) PrivValidatorFile() string {
return rootify(b.PrivValidator, b.RootDir) return rootify(b.PrivValidator, b.RootDir)
} }
// NodeKeyFile returns the full path to the node_key.json file
func (b BaseConfig) NodeKeyFile() string {
return rootify(b.NodeKey, b.RootDir)
}
// DBDir returns the full path to the database directory // DBDir returns the full path to the database directory
func (b BaseConfig) DBDir() string { func (b BaseConfig) DBDir() string {
return rootify(b.DBPath, b.RootDir) return rootify(b.DBPath, b.RootDir)
@ -170,7 +208,7 @@ type RPCConfig struct {
// NOTE: This server only supports /broadcast_tx_commit // NOTE: This server only supports /broadcast_tx_commit
GRPCListenAddress string `mapstructure:"grpc_laddr"` GRPCListenAddress string `mapstructure:"grpc_laddr"`
// Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool // Activate unsafe RPC commands like /dial_persistent_peers and /unsafe_flush_mempool
Unsafe bool `mapstructure:"unsafe"` Unsafe bool `mapstructure:"unsafe"`
} }
@ -203,8 +241,13 @@ type P2PConfig struct {
ListenAddress string `mapstructure:"laddr"` ListenAddress string `mapstructure:"laddr"`
// Comma separated list of seed nodes to connect to // Comma separated list of seed nodes to connect to
// We only use these if we cant connect to peers in the addrbook
Seeds string `mapstructure:"seeds"` Seeds string `mapstructure:"seeds"`
// Comma separated list of persistent peers to connect to
// We always connect to these
PersistentPeers string `mapstructure:"persistent_peers"`
// Skip UPNP port forwarding // Skip UPNP port forwarding
SkipUPNP bool `mapstructure:"skip_upnp"` SkipUPNP bool `mapstructure:"skip_upnp"`
@ -237,7 +280,7 @@ type P2PConfig struct {
func DefaultP2PConfig() *P2PConfig { func DefaultP2PConfig() *P2PConfig {
return &P2PConfig{ return &P2PConfig{
ListenAddress: "tcp://0.0.0.0:46656", ListenAddress: "tcp://0.0.0.0:46656",
AddrBook: "addrbook.json", AddrBook: defaultAddrBookPath,
AddrBookStrict: true, AddrBookStrict: true,
MaxNumPeers: 50, MaxNumPeers: 50,
FlushThrottleTimeout: 100, FlushThrottleTimeout: 100,
@ -253,6 +296,7 @@ func TestP2PConfig() *P2PConfig {
conf := DefaultP2PConfig() conf := DefaultP2PConfig()
conf.ListenAddress = "tcp://0.0.0.0:36656" conf.ListenAddress = "tcp://0.0.0.0:36656"
conf.SkipUPNP = true conf.SkipUPNP = true
conf.FlushThrottleTimeout = 10
return conf return conf
} }
@ -271,6 +315,7 @@ type MempoolConfig struct {
RecheckEmpty bool `mapstructure:"recheck_empty"` RecheckEmpty bool `mapstructure:"recheck_empty"`
Broadcast bool `mapstructure:"broadcast"` Broadcast bool `mapstructure:"broadcast"`
WalPath string `mapstructure:"wal_dir"` WalPath string `mapstructure:"wal_dir"`
CacheSize int `mapstructure:"cache_size"`
} }
// DefaultMempoolConfig returns a default configuration for the Tendermint mempool // DefaultMempoolConfig returns a default configuration for the Tendermint mempool
@ -279,10 +324,18 @@ func DefaultMempoolConfig() *MempoolConfig {
Recheck: true, Recheck: true,
RecheckEmpty: true, RecheckEmpty: true,
Broadcast: true, Broadcast: true,
WalPath: "data/mempool.wal", WalPath: filepath.Join(defaultDataDir, "mempool.wal"),
CacheSize: 100000,
} }
} }
// TestMempoolConfig returns a configuration for testing the Tendermint mempool
func TestMempoolConfig() *MempoolConfig {
config := DefaultMempoolConfig()
config.CacheSize = 1000
return config
}
// WalDir returns the full path to the mempool's write-ahead log // WalDir returns the full path to the mempool's write-ahead log
func (m *MempoolConfig) WalDir() string { func (m *MempoolConfig) WalDir() string {
return rootify(m.WalPath, m.RootDir) return rootify(m.WalPath, m.RootDir)
@ -299,7 +352,7 @@ type ConsensusConfig struct {
WalLight bool `mapstructure:"wal_light"` WalLight bool `mapstructure:"wal_light"`
walFile string // overrides WalPath if set walFile string // overrides WalPath if set
// All timeouts are in ms // All timeouts are in milliseconds
TimeoutPropose int `mapstructure:"timeout_propose"` TimeoutPropose int `mapstructure:"timeout_propose"`
TimeoutProposeDelta int `mapstructure:"timeout_propose_delta"` TimeoutProposeDelta int `mapstructure:"timeout_propose_delta"`
TimeoutPrevote int `mapstructure:"timeout_prevote"` TimeoutPrevote int `mapstructure:"timeout_prevote"`
@ -319,7 +372,7 @@ type ConsensusConfig struct {
CreateEmptyBlocks bool `mapstructure:"create_empty_blocks"` CreateEmptyBlocks bool `mapstructure:"create_empty_blocks"`
CreateEmptyBlocksInterval int `mapstructure:"create_empty_blocks_interval"` CreateEmptyBlocksInterval int `mapstructure:"create_empty_blocks_interval"`
// Reactor sleep duration parameters are in ms // Reactor sleep duration parameters are in milliseconds
PeerGossipSleepDuration int `mapstructure:"peer_gossip_sleep_duration"` PeerGossipSleepDuration int `mapstructure:"peer_gossip_sleep_duration"`
PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"` PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"`
} }
@ -367,7 +420,7 @@ func (cfg *ConsensusConfig) PeerQueryMaj23Sleep() time.Duration {
// DefaultConsensusConfig returns a default configuration for the consensus service // DefaultConsensusConfig returns a default configuration for the consensus service
func DefaultConsensusConfig() *ConsensusConfig { func DefaultConsensusConfig() *ConsensusConfig {
return &ConsensusConfig{ return &ConsensusConfig{
WalPath: "data/cs.wal/wal", WalPath: filepath.Join(defaultDataDir, "cs.wal", "wal"),
WalLight: false, WalLight: false,
TimeoutPropose: 3000, TimeoutPropose: 3000,
TimeoutProposeDelta: 500, TimeoutProposeDelta: 500,
@ -397,6 +450,8 @@ func TestConsensusConfig() *ConsensusConfig {
config.TimeoutPrecommitDelta = 1 config.TimeoutPrecommitDelta = 1
config.TimeoutCommit = 10 config.TimeoutCommit = 10
config.SkipTimeoutCommit = true config.SkipTimeoutCommit = true
config.PeerGossipSleepDuration = 5
config.PeerQueryMaj23SleepDuration = 250
return config return config
} }
@ -448,6 +503,11 @@ func DefaultTxIndexConfig() *TxIndexConfig {
} }
} }
// TestTxIndexConfig returns a default configuration for the transaction indexer.
func TestTxIndexConfig() *TxIndexConfig {
return DefaultTxIndexConfig()
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Utils // Utils

View File

@ -1,52 +1,215 @@
package config package config
import ( import (
"bytes"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "text/template"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
var configTemplate *template.Template
func init() {
var err error
if configTemplate, err = template.New("configFileTemplate").Parse(defaultConfigTemplate); err != nil {
panic(err)
}
}
/****** these are for production settings ***********/ /****** these are for production settings ***********/
// EnsureRoot creates the root, config, and data directories if they don't exist,
// and panics if it fails.
func EnsureRoot(rootDir string) { func EnsureRoot(rootDir string) {
if err := cmn.EnsureDir(rootDir, 0700); err != nil { if err := cmn.EnsureDir(rootDir, 0700); err != nil {
cmn.PanicSanity(err.Error()) cmn.PanicSanity(err.Error())
} }
if err := cmn.EnsureDir(rootDir+"/data", 0700); err != nil { if err := cmn.EnsureDir(filepath.Join(rootDir, defaultConfigDir), 0700); err != nil {
cmn.PanicSanity(err.Error())
}
if err := cmn.EnsureDir(filepath.Join(rootDir, defaultDataDir), 0700); err != nil {
cmn.PanicSanity(err.Error()) cmn.PanicSanity(err.Error())
} }
configFilePath := path.Join(rootDir, "config.toml") configFilePath := filepath.Join(rootDir, defaultConfigFilePath)
// Write default config file if missing. // Write default config file if missing.
if !cmn.FileExists(configFilePath) { if !cmn.FileExists(configFilePath) {
cmn.MustWriteFile(configFilePath, []byte(defaultConfig(defaultMoniker)), 0644) writeConfigFile(configFilePath)
} }
} }
var defaultConfigTmpl = `# This is a TOML config file. // XXX: this func should probably be called by cmd/tendermint/commands/init.go
// alongside the writing of the genesis.json and priv_validator.json
func writeConfigFile(configFilePath string) {
var buffer bytes.Buffer
if err := configTemplate.Execute(&buffer, DefaultConfig()); err != nil {
panic(err)
}
cmn.MustWriteFile(configFilePath, buffer.Bytes(), 0644)
}
// Note: any changes to the comments/variables/mapstructure
// must be reflected in the appropriate struct in config/config.go
const defaultConfigTemplate = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml # For more information, see https://github.com/toml-lang/toml
proxy_app = "tcp://127.0.0.1:46658" ##### main base config options #####
moniker = "__MONIKER__"
fast_sync = true
db_backend = "leveldb"
log_level = "state:info,*:error"
# TCP or UNIX socket address of the ABCI application,
# or the name of an ABCI application compiled in with the Tendermint binary
proxy_app = "{{ .BaseConfig.ProxyApp }}"
# A custom human readable name for this node
moniker = "{{ .BaseConfig.Moniker }}"
# 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 = {{ .BaseConfig.FastSync }}
# Database backend: leveldb | memdb
db_backend = "{{ .BaseConfig.DBBackend }}"
# Database directory
db_path = "{{ .BaseConfig.DBPath }}"
# Output level for logging, including package level options
log_level = "{{ .BaseConfig.LogLevel }}"
##### additional base config options #####
# Path to the JSON file containing the initial validator set and other meta data
genesis_file = "{{ .BaseConfig.Genesis }}"
# Path to the JSON file containing the private key to use as a validator in the consensus protocol
priv_validator_file = "{{ .BaseConfig.PrivValidator }}"
# Path to the JSON file containing the private key to use for node authentication in the p2p protocol
node_key_file = "{{ .BaseConfig.NodeKey}}"
# Mechanism to connect to the ABCI application: socket | grpc
abci = "{{ .BaseConfig.ABCI }}"
# TCP or UNIX socket address for the profiling server to listen on
prof_laddr = "{{ .BaseConfig.ProfListenAddress }}"
# 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 = {{ .BaseConfig.FilterPeers }}
##### advanced configuration options #####
##### rpc server configuration options #####
[rpc] [rpc]
laddr = "tcp://0.0.0.0:46657"
# TCP or UNIX socket address for the RPC server to listen on
laddr = "{{ .RPC.ListenAddress }}"
# TCP or UNIX socket address for the gRPC server to listen on
# NOTE: This server only supports /broadcast_tx_commit
grpc_laddr = "{{ .RPC.GRPCListenAddress }}"
# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool
unsafe = {{ .RPC.Unsafe }}
##### peer to peer configuration options #####
[p2p] [p2p]
laddr = "tcp://0.0.0.0:46656"
seeds = ""
`
func defaultConfig(moniker string) string { # Address to listen for incoming connections
return strings.Replace(defaultConfigTmpl, "__MONIKER__", moniker, -1) laddr = "{{ .P2P.ListenAddress }}"
}
# 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 = "{{ .P2P.AddrBook }}"
# Set true for strict address routability rules
addr_book_strict = {{ .P2P.AddrBookStrict }}
# Time to wait before flushing messages out on the connection, in ms
flush_throttle_timeout = {{ .P2P.FlushThrottleTimeout }}
# Maximum number of peers to connect to
max_num_peers = {{ .P2P.MaxNumPeers }}
# Maximum size of a message packet payload, in bytes
max_msg_packet_payload_size = {{ .P2P.MaxMsgPacketPayloadSize }}
# Rate at which packets can be sent, in bytes/second
send_rate = {{ .P2P.SendRate }}
# Rate at which packets can be received, in bytes/second
recv_rate = {{ .P2P.RecvRate }}
##### mempool configuration options #####
[mempool]
recheck = {{ .Mempool.Recheck }}
recheck_empty = {{ .Mempool.RecheckEmpty }}
broadcast = {{ .Mempool.Broadcast }}
wal_dir = "{{ .Mempool.WalPath }}"
##### consensus configuration options #####
[consensus]
wal_file = "{{ .Consensus.WalPath }}"
wal_light = {{ .Consensus.WalLight }}
# All timeouts are in milliseconds
timeout_propose = {{ .Consensus.TimeoutPropose }}
timeout_propose_delta = {{ .Consensus.TimeoutProposeDelta }}
timeout_prevote = {{ .Consensus.TimeoutPrevote }}
timeout_prevote_delta = {{ .Consensus.TimeoutPrevoteDelta }}
timeout_precommit = {{ .Consensus.TimeoutPrecommit }}
timeout_precommit_delta = {{ .Consensus.TimeoutPrecommitDelta }}
timeout_commit = {{ .Consensus.TimeoutCommit }}
# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
skip_timeout_commit = {{ .Consensus.SkipTimeoutCommit }}
# BlockSize
max_block_size_txs = {{ .Consensus.MaxBlockSizeTxs }}
max_block_size_bytes = {{ .Consensus.MaxBlockSizeBytes }}
# EmptyBlocks mode and possible interval between empty blocks in seconds
create_empty_blocks = {{ .Consensus.CreateEmptyBlocks }}
create_empty_blocks_interval = {{ .Consensus.CreateEmptyBlocksInterval }}
# Reactor sleep duration parameters are in milliseconds
peer_gossip_sleep_duration = {{ .Consensus.PeerGossipSleepDuration }}
peer_query_maj23_sleep_duration = {{ .Consensus.PeerQueryMaj23SleepDuration }}
##### 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 }}
`
/****** these are for test settings ***********/ /****** these are for test settings ***********/
@ -69,17 +232,21 @@ func ResetTestRoot(testName string) *Config {
if err := cmn.EnsureDir(rootDir, 0700); err != nil { if err := cmn.EnsureDir(rootDir, 0700); err != nil {
cmn.PanicSanity(err.Error()) cmn.PanicSanity(err.Error())
} }
if err := cmn.EnsureDir(rootDir+"/data", 0700); err != nil { if err := cmn.EnsureDir(filepath.Join(rootDir, defaultConfigDir), 0700); err != nil {
cmn.PanicSanity(err.Error())
}
if err := cmn.EnsureDir(filepath.Join(rootDir, defaultDataDir), 0700); err != nil {
cmn.PanicSanity(err.Error()) cmn.PanicSanity(err.Error())
} }
configFilePath := path.Join(rootDir, "config.toml") baseConfig := DefaultBaseConfig()
genesisFilePath := path.Join(rootDir, "genesis.json") configFilePath := filepath.Join(rootDir, defaultConfigFilePath)
privFilePath := path.Join(rootDir, "priv_validator.json") genesisFilePath := filepath.Join(rootDir, baseConfig.Genesis)
privFilePath := filepath.Join(rootDir, baseConfig.PrivValidator)
// Write default config file if missing. // Write default config file if missing.
if !cmn.FileExists(configFilePath) { if !cmn.FileExists(configFilePath) {
cmn.MustWriteFile(configFilePath, []byte(testConfig(defaultMoniker)), 0644) writeConfigFile(configFilePath)
} }
if !cmn.FileExists(genesisFilePath) { if !cmn.FileExists(genesisFilePath) {
cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644) cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644)
@ -91,28 +258,6 @@ func ResetTestRoot(testName string) *Config {
return config return config
} }
var testConfigTmpl = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
proxy_app = "dummy"
moniker = "__MONIKER__"
fast_sync = false
db_backend = "memdb"
log_level = "info"
[rpc]
laddr = "tcp://0.0.0.0:36657"
[p2p]
laddr = "tcp://0.0.0.0:36656"
seeds = ""
`
func testConfig(moniker string) (testConfig string) {
testConfig = strings.Replace(testConfigTmpl, "__MONIKER__", moniker, -1)
return
}
var testGenesis = `{ var testGenesis = `{
"genesis_time": "0001-01-01T00:00:00.000Z", "genesis_time": "0001-01-01T00:00:00.000Z",
"chain_id": "tendermint_test", "chain_id": "tendermint_test",

View File

@ -4,6 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -19,7 +20,7 @@ func ensureFiles(t *testing.T, rootDir string, files ...string) {
} }
func TestEnsureRoot(t *testing.T) { func TestEnsureRoot(t *testing.T) {
assert, require := assert.New(t), require.New(t) require := require.New(t)
// setup temp dir for test // setup temp dir for test
tmpDir, err := ioutil.TempDir("", "config-test") tmpDir, err := ioutil.TempDir("", "config-test")
@ -30,15 +31,18 @@ func TestEnsureRoot(t *testing.T) {
EnsureRoot(tmpDir) EnsureRoot(tmpDir)
// make sure config is set properly // make sure config is set properly
data, err := ioutil.ReadFile(filepath.Join(tmpDir, "config.toml")) data, err := ioutil.ReadFile(filepath.Join(tmpDir, defaultConfigFilePath))
require.Nil(err) require.Nil(err)
assert.Equal([]byte(defaultConfig(defaultMoniker)), data)
if !checkConfig(string(data)) {
t.Fatalf("config file missing some information")
}
ensureFiles(t, tmpDir, "data") ensureFiles(t, tmpDir, "data")
} }
func TestEnsureTestRoot(t *testing.T) { func TestEnsureTestRoot(t *testing.T) {
assert, require := assert.New(t), require.New(t) require := require.New(t)
testName := "ensureTestRoot" testName := "ensureTestRoot"
@ -47,11 +51,44 @@ func TestEnsureTestRoot(t *testing.T) {
rootDir := cfg.RootDir rootDir := cfg.RootDir
// make sure config is set properly // make sure config is set properly
data, err := ioutil.ReadFile(filepath.Join(rootDir, "config.toml")) data, err := ioutil.ReadFile(filepath.Join(rootDir, defaultConfigFilePath))
require.Nil(err) require.Nil(err)
assert.Equal([]byte(testConfig(defaultMoniker)), data)
if !checkConfig(string(data)) {
t.Fatalf("config file missing some information")
}
// TODO: make sure the cfg returned and testconfig are the same! // TODO: make sure the cfg returned and testconfig are the same!
baseConfig := DefaultBaseConfig()
ensureFiles(t, rootDir, "data", "genesis.json", "priv_validator.json") ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidator)
}
func checkConfig(configFile string) bool {
var valid bool
// list of words we expect in the config
var elems = []string{
"moniker",
"seeds",
"proxy_app",
"fast_sync",
"create_empty_blocks",
"peer",
"timeout",
"broadcast",
"send",
"addr",
"wal",
"propose",
"max",
"genesis",
}
for _, e := range elems {
if !strings.Contains(configFile, e) {
valid = false
} else {
valid = true
}
}
return valid
} }

View File

@ -33,7 +33,9 @@ func TestByzantine(t *testing.T) {
css := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter) css := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter)
// give the byzantine validator a normal ticker // give the byzantine validator a normal ticker
css[0].SetTimeoutTicker(NewTimeoutTicker()) ticker := NewTimeoutTicker()
ticker.SetLogger(css[0].Logger)
css[0].SetTimeoutTicker(ticker)
switches := make([]*p2p.Switch, N) switches := make([]*p2p.Switch, N)
p2pLogger := logger.With("module", "p2p") p2pLogger := logger.With("module", "p2p")

View File

@ -37,7 +37,7 @@ const (
// genesis, chain_id, priv_val // genesis, chain_id, priv_val
var config *cfg.Config // NOTE: must be reset for each _test.go file var config *cfg.Config // NOTE: must be reset for each _test.go file
var ensureTimeout = time.Second * 2 var ensureTimeout = time.Second * 1 // must be in seconds because CreateEmptyBlocksInterval is
func ensureDir(dir string, mode os.FileMode) { func ensureDir(dir string, mode os.FileMode) {
if err := cmn.EnsureDir(dir, mode); err != nil { if err := cmn.EnsureDir(dir, mode); err != nil {
@ -78,7 +78,7 @@ func (vs *validatorStub) signVote(voteType byte, hash []byte, header types.PartS
Type: voteType, Type: voteType,
BlockID: types.BlockID{hash, header}, BlockID: types.BlockID{hash, header},
} }
err := vs.PrivValidator.SignVote(config.ChainID, vote) err := vs.PrivValidator.SignVote(config.ChainID(), vote)
return vote, err return vote, err
} }
@ -129,7 +129,7 @@ func decideProposal(cs1 *ConsensusState, vs *validatorStub, height int64, round
// Make proposal // Make proposal
polRound, polBlockID := cs1.Votes.POLInfo() polRound, polBlockID := cs1.Votes.POLInfo()
proposal = types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) proposal = types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID)
if err := vs.SignProposal(config.ChainID, proposal); err != nil { if err := vs.SignProposal(cs1.state.ChainID, proposal); err != nil {
panic(err) panic(err)
} }
return return
@ -267,7 +267,7 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S
stateDB := dbm.NewMemDB() stateDB := dbm.NewMemDB()
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool) blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool)
cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) cs := NewConsensusState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool)
cs.SetLogger(log.TestingLogger()) cs.SetLogger(log.TestingLogger().With("module", "consensus"))
cs.SetPrivValidator(pv) cs.SetPrivValidator(pv)
eventBus := types.NewEventBus() eventBus := types.NewEventBus()
@ -285,14 +285,6 @@ func loadPrivValidator(config *cfg.Config) *types.PrivValidatorFS {
return privValidator return privValidator
} }
func fixedConsensusStateDummy(config *cfg.Config, logger log.Logger) *ConsensusState {
state, _ := sm.MakeGenesisStateFromFile(config.GenesisFile())
privValidator := loadPrivValidator(config)
cs := newConsensusState(state, privValidator, dummy.NewDummyApplication())
cs.SetLogger(logger)
return cs
}
func randConsensusState(nValidators int) (*ConsensusState, []*validatorStub) { func randConsensusState(nValidators int) (*ConsensusState, []*validatorStub) {
// Get State // Get State
state, privVals := randGenesisState(nValidators, false, 10) state, privVals := randGenesisState(nValidators, false, 10)
@ -300,7 +292,6 @@ func randConsensusState(nValidators int) (*ConsensusState, []*validatorStub) {
vss := make([]*validatorStub, nValidators) vss := make([]*validatorStub, nValidators)
cs := newConsensusState(state, privVals[0], counter.NewCounterApplication(true)) cs := newConsensusState(state, privVals[0], counter.NewCounterApplication(true))
cs.SetLogger(log.TestingLogger())
for i := 0; i < nValidators; i++ { for i := 0; i < nValidators; i++ {
vss[i] = NewValidatorStub(privVals[i], i) vss[i] = NewValidatorStub(privVals[i], i)
@ -346,7 +337,7 @@ func consensusLogger() log.Logger {
} }
} }
return term.FgBgColor{} return term.FgBgColor{}
}) }).With("module", "consensus")
} }
func randConsensusNet(nValidators int, testName string, tickerFunc func() TimeoutTicker, appFunc func() abci.Application, configOpts ...func(*cfg.Config)) []*ConsensusState { func randConsensusNet(nValidators int, testName string, tickerFunc func() TimeoutTicker, appFunc func() abci.Application, configOpts ...func(*cfg.Config)) []*ConsensusState {
@ -366,8 +357,8 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou
app.InitChain(abci.RequestInitChain{Validators: vals}) app.InitChain(abci.RequestInitChain{Validators: vals})
css[i] = newConsensusStateWithConfig(thisConfig, state, privVals[i], app) css[i] = newConsensusStateWithConfig(thisConfig, state, privVals[i], app)
css[i].SetLogger(logger.With("validator", i))
css[i].SetTimeoutTicker(tickerFunc()) css[i].SetTimeoutTicker(tickerFunc())
css[i].SetLogger(logger.With("validator", i, "module", "consensus"))
} }
return css return css
} }
@ -395,8 +386,8 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF
app.InitChain(abci.RequestInitChain{Validators: vals}) app.InitChain(abci.RequestInitChain{Validators: vals})
css[i] = newConsensusStateWithConfig(thisConfig, state, privVal, app) css[i] = newConsensusStateWithConfig(thisConfig, state, privVal, app)
css[i].SetLogger(logger.With("validator", i))
css[i].SetTimeoutTicker(tickerFunc()) css[i].SetTimeoutTicker(tickerFunc())
css[i].SetLogger(logger.With("validator", i, "module", "consensus"))
} }
return css return css
} }
@ -426,9 +417,10 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G
privValidators[i] = privVal privValidators[i] = privVal
} }
sort.Sort(types.PrivValidatorsByAddress(privValidators)) sort.Sort(types.PrivValidatorsByAddress(privValidators))
return &types.GenesisDoc{ return &types.GenesisDoc{
GenesisTime: time.Now(), GenesisTime: time.Now(),
ChainID: config.ChainID, ChainID: config.ChainID(),
Validators: validators, Validators: validators,
}, privValidators }, privValidators
} }

View File

@ -19,7 +19,7 @@ func init() {
config = ResetConfig("consensus_mempool_test") config = ResetConfig("consensus_mempool_test")
} }
func TestNoProgressUntilTxsAvailable(t *testing.T) { func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) {
config := ResetConfig("consensus_mempool_txs_available_test") config := ResetConfig("consensus_mempool_txs_available_test")
config.Consensus.CreateEmptyBlocks = false config.Consensus.CreateEmptyBlocks = false
state, privVals := randGenesisState(1, false, 10) state, privVals := randGenesisState(1, false, 10)
@ -37,7 +37,7 @@ func TestNoProgressUntilTxsAvailable(t *testing.T) {
ensureNoNewStep(newBlockCh) ensureNoNewStep(newBlockCh)
} }
func TestProgressAfterCreateEmptyBlocksInterval(t *testing.T) { func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) {
config := ResetConfig("consensus_mempool_txs_available_test") config := ResetConfig("consensus_mempool_txs_available_test")
config.Consensus.CreateEmptyBlocksInterval = int(ensureTimeout.Seconds()) config.Consensus.CreateEmptyBlocksInterval = int(ensureTimeout.Seconds())
state, privVals := randGenesisState(1, false, 10) state, privVals := randGenesisState(1, false, 10)
@ -52,7 +52,7 @@ func TestProgressAfterCreateEmptyBlocksInterval(t *testing.T) {
ensureNewStep(newBlockCh) // until the CreateEmptyBlocksInterval has passed ensureNewStep(newBlockCh) // until the CreateEmptyBlocksInterval has passed
} }
func TestProgressInHigherRound(t *testing.T) { func TestMempoolProgressInHigherRound(t *testing.T) {
config := ResetConfig("consensus_mempool_txs_available_test") config := ResetConfig("consensus_mempool_txs_available_test")
config.Consensus.CreateEmptyBlocks = false config.Consensus.CreateEmptyBlocks = false
state, privVals := randGenesisState(1, false, 10) state, privVals := randGenesisState(1, false, 10)
@ -94,7 +94,7 @@ func deliverTxsRange(cs *ConsensusState, start, end int) {
} }
} }
func TestTxConcurrentWithCommit(t *testing.T) { func TestMempoolTxConcurrentWithCommit(t *testing.T) {
state, privVals := randGenesisState(1, false, 10) state, privVals := randGenesisState(1, false, 10)
cs := newConsensusState(state, privVals[0], NewCounterApplication()) cs := newConsensusState(state, privVals[0], NewCounterApplication())
height, round := cs.Height, cs.Round height, round := cs.Height, cs.Round
@ -116,7 +116,7 @@ func TestTxConcurrentWithCommit(t *testing.T) {
} }
} }
func TestRmBadTx(t *testing.T) { func TestMempoolRmBadTx(t *testing.T) {
state, privVals := randGenesisState(1, false, 10) state, privVals := randGenesisState(1, false, 10)
app := NewCounterApplication() app := NewCounterApplication()
cs := newConsensusState(state, privVals[0], app) cs := newConsensusState(state, privVals[0], app)

View File

@ -205,7 +205,11 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
return return
} }
// Peer claims to have a maj23 for some BlockID at H,R,S, // Peer claims to have a maj23 for some BlockID at H,R,S,
votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.Key(), msg.BlockID) err := votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.ID(), msg.BlockID)
if err != nil {
conR.Switch.StopPeerForError(src, err)
return
}
// Respond with a VoteSetBitsMessage showing which votes we have. // Respond with a VoteSetBitsMessage showing which votes we have.
// (and consequently shows which we don't have) // (and consequently shows which we don't have)
var ourVotes *cmn.BitArray var ourVotes *cmn.BitArray
@ -242,12 +246,12 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
switch msg := msg.(type) { switch msg := msg.(type) {
case *ProposalMessage: case *ProposalMessage:
ps.SetHasProposal(msg.Proposal) ps.SetHasProposal(msg.Proposal)
conR.conS.peerMsgQueue <- msgInfo{msg, src.Key()} conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()}
case *ProposalPOLMessage: case *ProposalPOLMessage:
ps.ApplyProposalPOLMessage(msg) ps.ApplyProposalPOLMessage(msg)
case *BlockPartMessage: case *BlockPartMessage:
ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Index) ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Index)
conR.conS.peerMsgQueue <- msgInfo{msg, src.Key()} conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()}
default: default:
conR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg))) conR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg)))
} }
@ -267,7 +271,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
ps.EnsureVoteBitArrays(height-1, lastCommitSize) ps.EnsureVoteBitArrays(height-1, lastCommitSize)
ps.SetHasVote(msg.Vote) ps.SetHasVote(msg.Vote)
cs.peerMsgQueue <- msgInfo{msg, src.Key()} cs.peerMsgQueue <- msgInfo{msg, src.ID()}
default: default:
// don't punish (leave room for soft upgrades) // don't punish (leave room for soft upgrades)
@ -1200,7 +1204,7 @@ func (ps *PeerState) StringIndented(indent string) string {
%s Key %v %s Key %v
%s PRS %v %s PRS %v
%s}`, %s}`,
indent, ps.Peer.Key(), indent, ps.Peer.ID(),
indent, ps.PeerRoundState.StringIndented(indent+" "), indent, ps.PeerRoundState.StringIndented(indent+" "),
indent) indent)
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"runtime"
"runtime/pprof" "runtime/pprof"
"sync" "sync"
"testing" "testing"
@ -31,31 +32,24 @@ func startConsensusNet(t *testing.T, css []*ConsensusState, N int) ([]*Consensus
reactors := make([]*ConsensusReactor, N) reactors := make([]*ConsensusReactor, N)
eventChans := make([]chan interface{}, N) eventChans := make([]chan interface{}, N)
eventBuses := make([]*types.EventBus, N) eventBuses := make([]*types.EventBus, N)
logger := consensusLogger()
for i := 0; i < N; i++ { for i := 0; i < N; i++ {
/*thisLogger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info") /*logger, err := tmflags.ParseLogLevel("consensus:info,*:error", logger, "info")
if err != nil { t.Fatal(err)}*/ if err != nil { t.Fatal(err)}*/
thisLogger := logger
reactors[i] = NewConsensusReactor(css[i], true) // so we dont start the consensus states reactors[i] = NewConsensusReactor(css[i], true) // so we dont start the consensus states
reactors[i].conS.SetLogger(thisLogger.With("validator", i)) reactors[i].SetLogger(css[i].Logger)
reactors[i].SetLogger(thisLogger.With("validator", i))
eventBuses[i] = types.NewEventBus()
eventBuses[i].SetLogger(thisLogger.With("module", "events", "validator", i))
err := eventBuses[i].Start()
require.NoError(t, err)
// eventBus is already started with the cs
eventBuses[i] = css[i].eventBus
reactors[i].SetEventBus(eventBuses[i]) reactors[i].SetEventBus(eventBuses[i])
eventChans[i] = make(chan interface{}, 1) eventChans[i] = make(chan interface{}, 1)
err = eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock, eventChans[i]) err := eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock, eventChans[i])
require.NoError(t, err) require.NoError(t, err)
} }
// make connected switches and start all reactors // make connected switches and start all reactors
p2p.MakeConnectedSwitches(config.P2P, N, func(i int, s *p2p.Switch) *p2p.Switch { p2p.MakeConnectedSwitches(config.P2P, N, func(i int, s *p2p.Switch) *p2p.Switch {
s.AddReactor("CONSENSUS", reactors[i]) s.AddReactor("CONSENSUS", reactors[i])
s.SetLogger(reactors[i].Logger.With("module", "p2p", "validator", i)) s.SetLogger(reactors[i].conS.Logger.With("module", "p2p"))
return s return s
}, p2p.Connect2Switches) }, p2p.Connect2Switches)
@ -84,15 +78,14 @@ func stopConsensusNet(logger log.Logger, reactors []*ConsensusReactor, eventBuse
} }
// Ensure a testnet makes blocks // Ensure a testnet makes blocks
func TestReactor(t *testing.T) { func TestReactorBasic(t *testing.T) {
N := 4 N := 4
css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter) css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter)
reactors, eventChans, eventBuses := startConsensusNet(t, css, N) reactors, eventChans, eventBuses := startConsensusNet(t, css, N)
defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses)
// wait till everyone makes the first new block // wait till everyone makes the first new block
timeoutWaitGroup(t, N, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, N, func(j int) {
<-eventChans[j] <-eventChans[j]
wg.Done()
}, css) }, css)
} }
@ -113,9 +106,8 @@ func TestReactorProposalHeartbeats(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
// wait till everyone sends a proposal heartbeat // wait till everyone sends a proposal heartbeat
timeoutWaitGroup(t, N, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, N, func(j int) {
<-heartbeatChans[j] <-heartbeatChans[j]
wg.Done()
}, css) }, css)
// send a tx // send a tx
@ -124,9 +116,8 @@ func TestReactorProposalHeartbeats(t *testing.T) {
} }
// wait till everyone makes the first new block // wait till everyone makes the first new block
timeoutWaitGroup(t, N, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, N, func(j int) {
<-eventChans[j] <-eventChans[j]
wg.Done()
}, css) }, css)
} }
@ -147,9 +138,8 @@ func TestReactorVotingPowerChange(t *testing.T) {
} }
// wait till everyone makes block 1 // wait till everyone makes block 1
timeoutWaitGroup(t, nVals, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, nVals, func(j int) {
<-eventChans[j] <-eventChans[j]
wg.Done()
}, css) }, css)
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -210,9 +200,8 @@ func TestReactorValidatorSetChanges(t *testing.T) {
} }
// wait till everyone makes block 1 // wait till everyone makes block 1
timeoutWaitGroup(t, nPeers, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, nPeers, func(j int) {
<-eventChans[j] <-eventChans[j]
wg.Done()
}, css) }, css)
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -300,16 +289,13 @@ func TestReactorWithTimeoutCommit(t *testing.T) {
defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses)
// wait till everyone makes the first new block // wait till everyone makes the first new block
timeoutWaitGroup(t, N-1, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, N-1, func(j int) {
<-eventChans[j] <-eventChans[j]
wg.Done()
}, css) }, css)
} }
func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState, txs ...[]byte) { func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState, txs ...[]byte) {
timeoutWaitGroup(t, n, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, n, func(j int) {
defer wg.Done()
css[j].Logger.Debug("waitForAndValidateBlock") css[j].Logger.Debug("waitForAndValidateBlock")
newBlockI, ok := <-eventChans[j] newBlockI, ok := <-eventChans[j]
if !ok { if !ok {
@ -327,8 +313,7 @@ func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{}
} }
func waitForAndValidateBlockWithTx(t *testing.T, n int, activeVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState, txs ...[]byte) { func waitForAndValidateBlockWithTx(t *testing.T, n int, activeVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState, txs ...[]byte) {
timeoutWaitGroup(t, n, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, n, func(j int) {
defer wg.Done()
ntxs := 0 ntxs := 0
BLOCK_TX_LOOP: BLOCK_TX_LOOP:
for { for {
@ -359,8 +344,7 @@ func waitForAndValidateBlockWithTx(t *testing.T, n int, activeVals map[string]st
} }
func waitForBlockWithUpdatedValsAndValidateIt(t *testing.T, n int, updatedVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState) { func waitForBlockWithUpdatedValsAndValidateIt(t *testing.T, n int, updatedVals map[string]struct{}, eventChans []chan interface{}, css []*ConsensusState) {
timeoutWaitGroup(t, n, func(wg *sync.WaitGroup, j int) { timeoutWaitGroup(t, n, func(j int) {
defer wg.Done()
var newBlock *types.Block var newBlock *types.Block
LOOP: LOOP:
@ -398,11 +382,14 @@ func validateBlock(block *types.Block, activeVals map[string]struct{}) error {
return nil return nil
} }
func timeoutWaitGroup(t *testing.T, n int, f func(*sync.WaitGroup, int), css []*ConsensusState) { func timeoutWaitGroup(t *testing.T, n int, f func(int), css []*ConsensusState) {
wg := new(sync.WaitGroup) wg := new(sync.WaitGroup)
wg.Add(n) wg.Add(n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
go f(wg, i) go func(j int) {
f(j)
wg.Done()
}(i)
} }
done := make(chan struct{}) done := make(chan struct{})
@ -424,7 +411,15 @@ func timeoutWaitGroup(t *testing.T, n int, f func(*sync.WaitGroup, int), css []*
t.Log(cs.GetRoundState()) t.Log(cs.GetRoundState())
t.Log("") t.Log("")
} }
os.Stdout.Write([]byte("pprof.Lookup('goroutine'):\n"))
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
capture()
panic("Timed out waiting for all validators to commit a block") panic("Timed out waiting for all validators to commit a block")
} }
} }
func capture() {
trace := make([]byte, 10240000)
count := runtime.Stack(trace, true)
fmt.Printf("Stack of %d bytes: %s\n", count, trace)
}

View File

@ -61,21 +61,21 @@ func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan
} }
} }
case msgInfo: case msgInfo:
peerKey := m.PeerKey peerID := m.PeerID
if peerKey == "" { if peerID == "" {
peerKey = "local" peerID = "local"
} }
switch msg := m.Msg.(type) { switch msg := m.Msg.(type) {
case *ProposalMessage: case *ProposalMessage:
p := msg.Proposal p := msg.Proposal
cs.Logger.Info("Replay: Proposal", "height", p.Height, "round", p.Round, "header", cs.Logger.Info("Replay: Proposal", "height", p.Height, "round", p.Round, "header",
p.BlockPartsHeader, "pol", p.POLRound, "peer", peerKey) p.BlockPartsHeader, "pol", p.POLRound, "peer", peerID)
case *BlockPartMessage: case *BlockPartMessage:
cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerKey) cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerID)
case *VoteMessage: case *VoteMessage:
v := msg.Vote v := msg.Vote
cs.Logger.Info("Replay: Vote", "height", v.Height, "round", v.Round, "type", v.Type, cs.Logger.Info("Replay: Vote", "height", v.Height, "round", v.Round, "type", v.Type,
"blockID", v.BlockID, "peer", peerKey) "blockID", v.BlockID, "peer", peerID)
} }
cs.handleMsg(m) cs.handleMsg(m)

View File

@ -81,13 +81,13 @@ func startNewConsensusStateAndWaitForBlock(t *testing.T, lastBlockHeight int64,
} }
func sendTxs(cs *ConsensusState, ctx context.Context) { func sendTxs(cs *ConsensusState, ctx context.Context) {
i := 0 for i := 0; i < 256; i++ {
for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
default: default:
cs.mempool.CheckTx([]byte{byte(i)}, nil) tx := []byte{byte(i)}
cs.mempool.CheckTx(tx, nil)
i++ i++
} }
} }

View File

@ -17,6 +17,7 @@ import (
cfg "github.com/tendermint/tendermint/config" cfg "github.com/tendermint/tendermint/config"
cstypes "github.com/tendermint/tendermint/consensus/types" cstypes "github.com/tendermint/tendermint/consensus/types"
"github.com/tendermint/tendermint/p2p"
sm "github.com/tendermint/tendermint/state" sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
@ -47,7 +48,7 @@ var (
// msgs from the reactor which may update the state // msgs from the reactor which may update the state
type msgInfo struct { type msgInfo struct {
Msg ConsensusMessage `json:"msg"` Msg ConsensusMessage `json:"msg"`
PeerKey string `json:"peer_key"` PeerID p2p.ID `json:"peer_key"`
} }
// internally generated messages which may update the state // internally generated messages which may update the state
@ -85,7 +86,7 @@ type ConsensusState struct {
cstypes.RoundState cstypes.RoundState
state sm.State // State until height-1. state sm.State // State until height-1.
// state changes may be triggered by msgs from peers, // state changes may be triggered by: msgs from peers,
// msgs from ourself, or by timeouts // msgs from ourself, or by timeouts
peerMsgQueue chan msgInfo peerMsgQueue chan msgInfo
internalMsgQueue chan msgInfo internalMsgQueue chan msgInfo
@ -303,17 +304,17 @@ func (cs *ConsensusState) OpenWAL(walFile string) (WAL, error) {
//------------------------------------------------------------ //------------------------------------------------------------
// Public interface for passing messages into the consensus state, possibly causing a state transition. // Public interface for passing messages into the consensus state, possibly causing a state transition.
// If peerKey == "", the msg is considered internal. // If peerID == "", the msg is considered internal.
// Messages are added to the appropriate queue (peer or internal). // Messages are added to the appropriate queue (peer or internal).
// If the queue is full, the function may block. // If the queue is full, the function may block.
// TODO: should these return anything or let callers just use events? // TODO: should these return anything or let callers just use events?
// AddVote inputs a vote. // AddVote inputs a vote.
func (cs *ConsensusState) AddVote(vote *types.Vote, peerKey string) (added bool, err error) { func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) {
if peerKey == "" { if peerID == "" {
cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""} cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""}
} else { } else {
cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerKey} cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerID}
} }
// TODO: wait for event?! // TODO: wait for event?!
@ -321,12 +322,12 @@ func (cs *ConsensusState) AddVote(vote *types.Vote, peerKey string) (added bool,
} }
// SetProposal inputs a proposal. // SetProposal inputs a proposal.
func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerKey string) error { func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2p.ID) error {
if peerKey == "" { if peerID == "" {
cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""}
} else { } else {
cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerKey} cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID}
} }
// TODO: wait for event?! // TODO: wait for event?!
@ -334,12 +335,12 @@ func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerKey string)
} }
// AddProposalBlockPart inputs a part of the proposal block. // AddProposalBlockPart inputs a part of the proposal block.
func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerKey string) error { func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) error {
if peerKey == "" { if peerID == "" {
cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""}
} else { } else {
cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerKey} cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID}
} }
// TODO: wait for event?! // TODO: wait for event?!
@ -347,13 +348,13 @@ func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *ty
} }
// SetProposalAndBlock inputs the proposal and all block parts. // SetProposalAndBlock inputs the proposal and all block parts.
func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerKey string) error { func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID p2p.ID) error {
if err := cs.SetProposal(proposal, peerKey); err != nil { if err := cs.SetProposal(proposal, peerID); err != nil {
return err return err
} }
for i := 0; i < parts.Total(); i++ { for i := 0; i < parts.Total(); i++ {
part := parts.GetPart(i) part := parts.GetPart(i)
if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerKey); err != nil { if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerID); err != nil {
return err return err
} }
} }
@ -561,7 +562,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
defer cs.mtx.Unlock() defer cs.mtx.Unlock()
var err error var err error
msg, peerKey := mi.Msg, mi.PeerKey msg, peerID := mi.Msg, mi.PeerID
switch msg := msg.(type) { switch msg := msg.(type) {
case *ProposalMessage: case *ProposalMessage:
// will not cause transition. // will not cause transition.
@ -569,14 +570,14 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
err = cs.setProposal(msg.Proposal) err = cs.setProposal(msg.Proposal)
case *BlockPartMessage: case *BlockPartMessage:
// if the proposal is complete, we'll enterPrevote or tryFinalizeCommit // if the proposal is complete, we'll enterPrevote or tryFinalizeCommit
_, err = cs.addProposalBlockPart(msg.Height, msg.Part, peerKey != "") _, err = cs.addProposalBlockPart(msg.Height, msg.Part, peerID != "")
if err != nil && msg.Round != cs.Round { if err != nil && msg.Round != cs.Round {
err = nil err = nil
} }
case *VoteMessage: case *VoteMessage:
// attempt to add the vote and dupeout the validator if its a duplicate signature // attempt to add the vote and dupeout the validator if its a duplicate signature
// if the vote gives us a 2/3-any or 2/3-one, we transition // if the vote gives us a 2/3-any or 2/3-one, we transition
err := cs.tryAddVote(msg.Vote, peerKey) err := cs.tryAddVote(msg.Vote, peerID)
if err == ErrAddingVote { if err == ErrAddingVote {
// TODO: punish peer // TODO: punish peer
} }
@ -591,7 +592,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) {
cs.Logger.Error("Unknown msg type", reflect.TypeOf(msg)) cs.Logger.Error("Unknown msg type", reflect.TypeOf(msg))
} }
if err != nil { if err != nil {
cs.Logger.Error("Error with msg", "type", reflect.TypeOf(msg), "peer", peerKey, "err", err, "msg", msg) cs.Logger.Error("Error with msg", "type", reflect.TypeOf(msg), "peer", peerID, "err", err, "msg", msg)
} }
} }
@ -770,17 +771,18 @@ func (cs *ConsensusState) enterPropose(height int64, round int) {
return return
} }
if !cs.isProposer() { // if not a validator, we're done
cs.Logger.Info("enterPropose: Not our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator) if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
if cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
cs.Logger.Debug("This node is a validator")
} else {
cs.Logger.Debug("This node is not a validator") cs.Logger.Debug("This node is not a validator")
return
} }
} else {
cs.Logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
cs.Logger.Debug("This node is a validator") cs.Logger.Debug("This node is a validator")
if cs.isProposer() {
cs.Logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
cs.decideProposal(height, round) cs.decideProposal(height, round)
} else {
cs.Logger.Info("enterPropose: Not our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)
} }
} }
@ -1308,8 +1310,8 @@ func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part, v
} }
// Attempt to add the vote. if its a duplicate signature, dupeout the validator // Attempt to add the vote. if its a duplicate signature, dupeout the validator
func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error { func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) error {
_, err := cs.addVote(vote, peerKey) _, err := cs.addVote(vote, peerID)
if err != nil { if err != nil {
// If the vote height is off, we'll just ignore it, // If the vote height is off, we'll just ignore it,
// But if it's a conflicting sig, add it to the cs.evpool. // But if it's a conflicting sig, add it to the cs.evpool.
@ -1335,7 +1337,7 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, err error) { func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) {
cs.Logger.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "valIndex", vote.ValidatorIndex, "csHeight", cs.Height) cs.Logger.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "valIndex", vote.ValidatorIndex, "csHeight", cs.Height)
// A precommit for the previous height? // A precommit for the previous height?
@ -1365,7 +1367,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool,
// A prevote/precommit for this height? // A prevote/precommit for this height?
if vote.Height == cs.Height { if vote.Height == cs.Height {
height := cs.Height height := cs.Height
added, err = cs.Votes.AddVote(vote, peerKey) added, err = cs.Votes.AddVote(vote, peerID)
if added { if added {
cs.eventBus.PublishEventVote(types.EventDataVote{vote}) cs.eventBus.PublishEventVote(types.EventDataVote{vote})

View File

@ -55,7 +55,7 @@ x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we sh
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
// ProposeSuite // ProposeSuite
func TestProposerSelection0(t *testing.T) { func TestStateProposerSelection0(t *testing.T) {
cs1, vss := randConsensusState(4) cs1, vss := randConsensusState(4)
height, round := cs1.Height, cs1.Round height, round := cs1.Height, cs1.Round
@ -89,7 +89,7 @@ func TestProposerSelection0(t *testing.T) {
} }
// Now let's do it all again, but starting from round 2 instead of 0 // Now let's do it all again, but starting from round 2 instead of 0
func TestProposerSelection2(t *testing.T) { func TestStateProposerSelection2(t *testing.T) {
cs1, vss := randConsensusState(4) // test needs more work for more than 3 validators cs1, vss := randConsensusState(4) // test needs more work for more than 3 validators
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
@ -118,7 +118,7 @@ func TestProposerSelection2(t *testing.T) {
} }
// a non-validator should timeout into the prevote round // a non-validator should timeout into the prevote round
func TestEnterProposeNoPrivValidator(t *testing.T) { func TestStateEnterProposeNoPrivValidator(t *testing.T) {
cs, _ := randConsensusState(1) cs, _ := randConsensusState(1)
cs.SetPrivValidator(nil) cs.SetPrivValidator(nil)
height, round := cs.Height, cs.Round height, round := cs.Height, cs.Round
@ -143,7 +143,7 @@ func TestEnterProposeNoPrivValidator(t *testing.T) {
} }
// a validator should not timeout of the prevote round (TODO: unless the block is really big!) // a validator should not timeout of the prevote round (TODO: unless the block is really big!)
func TestEnterProposeYesPrivValidator(t *testing.T) { func TestStateEnterProposeYesPrivValidator(t *testing.T) {
cs, _ := randConsensusState(1) cs, _ := randConsensusState(1)
height, round := cs.Height, cs.Round height, round := cs.Height, cs.Round
@ -179,7 +179,7 @@ func TestEnterProposeYesPrivValidator(t *testing.T) {
} }
} }
func TestBadProposal(t *testing.T) { func TestStateBadProposal(t *testing.T) {
cs1, vss := randConsensusState(2) cs1, vss := randConsensusState(2)
height, round := cs1.Height, cs1.Round height, round := cs1.Height, cs1.Round
vs2 := vss[1] vs2 := vss[1]
@ -204,7 +204,7 @@ func TestBadProposal(t *testing.T) {
propBlock.AppHash = stateHash propBlock.AppHash = stateHash
propBlockParts := propBlock.MakePartSet(partSize) propBlockParts := propBlock.MakePartSet(partSize)
proposal := types.NewProposal(vs2.Height, round, propBlockParts.Header(), -1, types.BlockID{}) proposal := types.NewProposal(vs2.Height, round, propBlockParts.Header(), -1, types.BlockID{})
if err := vs2.SignProposal(config.ChainID, proposal); err != nil { if err := vs2.SignProposal(config.ChainID(), proposal); err != nil {
t.Fatal("failed to sign bad proposal", err) t.Fatal("failed to sign bad proposal", err)
} }
@ -239,7 +239,7 @@ func TestBadProposal(t *testing.T) {
// FullRoundSuite // FullRoundSuite
// propose, prevote, and precommit a block // propose, prevote, and precommit a block
func TestFullRound1(t *testing.T) { func TestStateFullRound1(t *testing.T) {
cs, vss := randConsensusState(1) cs, vss := randConsensusState(1)
height, round := cs.Height, cs.Round height, round := cs.Height, cs.Round
@ -275,7 +275,7 @@ func TestFullRound1(t *testing.T) {
} }
// nil is proposed, so prevote and precommit nil // nil is proposed, so prevote and precommit nil
func TestFullRoundNil(t *testing.T) { func TestStateFullRoundNil(t *testing.T) {
cs, vss := randConsensusState(1) cs, vss := randConsensusState(1)
height, round := cs.Height, cs.Round height, round := cs.Height, cs.Round
@ -293,7 +293,7 @@ func TestFullRoundNil(t *testing.T) {
// run through propose, prevote, precommit commit with two validators // run through propose, prevote, precommit commit with two validators
// where the first validator has to wait for votes from the second // where the first validator has to wait for votes from the second
func TestFullRound2(t *testing.T) { func TestStateFullRound2(t *testing.T) {
cs1, vss := randConsensusState(2) cs1, vss := randConsensusState(2)
vs2 := vss[1] vs2 := vss[1]
height, round := cs1.Height, cs1.Round height, round := cs1.Height, cs1.Round
@ -334,7 +334,7 @@ func TestFullRound2(t *testing.T) {
// two validators, 4 rounds. // two validators, 4 rounds.
// two vals take turns proposing. val1 locks on first one, precommits nil on everything else // two vals take turns proposing. val1 locks on first one, precommits nil on everything else
func TestLockNoPOL(t *testing.T) { func TestStateLockNoPOL(t *testing.T) {
cs1, vss := randConsensusState(2) cs1, vss := randConsensusState(2)
vs2 := vss[1] vs2 := vss[1]
height := cs1.Height height := cs1.Height
@ -503,7 +503,7 @@ func TestLockNoPOL(t *testing.T) {
} }
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka
func TestLockPOLRelock(t *testing.T) { func TestStateLockPOLRelock(t *testing.T) {
cs1, vss := randConsensusState(4) cs1, vss := randConsensusState(4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3] vs2, vs3, vs4 := vss[1], vss[2], vss[3]
@ -618,7 +618,7 @@ func TestLockPOLRelock(t *testing.T) {
} }
// 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka
func TestLockPOLUnlock(t *testing.T) { func TestStateLockPOLUnlock(t *testing.T) {
cs1, vss := randConsensusState(4) cs1, vss := randConsensusState(4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3] vs2, vs3, vs4 := vss[1], vss[2], vss[3]
@ -715,7 +715,7 @@ func TestLockPOLUnlock(t *testing.T) {
// a polka at round 1 but we miss it // a polka at round 1 but we miss it
// then a polka at round 2 that we lock on // then a polka at round 2 that we lock on
// then we see the polka from round 1 but shouldn't unlock // then we see the polka from round 1 but shouldn't unlock
func TestLockPOLSafety1(t *testing.T) { func TestStateLockPOLSafety1(t *testing.T) {
cs1, vss := randConsensusState(4) cs1, vss := randConsensusState(4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3] vs2, vs3, vs4 := vss[1], vss[2], vss[3]
@ -838,7 +838,7 @@ func TestLockPOLSafety1(t *testing.T) {
// What we want: // What we want:
// dont see P0, lock on P1 at R1, dont unlock using P0 at R2 // dont see P0, lock on P1 at R1, dont unlock using P0 at R2
func TestLockPOLSafety2(t *testing.T) { func TestStateLockPOLSafety2(t *testing.T) {
cs1, vss := randConsensusState(4) cs1, vss := randConsensusState(4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3] vs2, vs3, vs4 := vss[1], vss[2], vss[3]
@ -900,7 +900,7 @@ func TestLockPOLSafety2(t *testing.T) {
// in round 2 we see the polkad block from round 0 // in round 2 we see the polkad block from round 0
newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0, propBlockID1) newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0, propBlockID1)
if err := vs3.SignProposal(config.ChainID, newProp); err != nil { if err := vs3.SignProposal(config.ChainID(), newProp); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := cs1.SetProposalAndBlock(newProp, propBlock0, propBlockParts0, "some peer"); err != nil { if err := cs1.SetProposalAndBlock(newProp, propBlock0, propBlockParts0, "some peer"); err != nil {
@ -937,7 +937,7 @@ func TestLockPOLSafety2(t *testing.T) {
// TODO: Slashing // TODO: Slashing
/* /*
func TestSlashingPrevotes(t *testing.T) { func TestStateSlashingPrevotes(t *testing.T) {
cs1, vss := randConsensusState(2) cs1, vss := randConsensusState(2)
vs2 := vss[1] vs2 := vss[1]
@ -972,7 +972,7 @@ func TestSlashingPrevotes(t *testing.T) {
// XXX: Check for existence of Dupeout info // XXX: Check for existence of Dupeout info
} }
func TestSlashingPrecommits(t *testing.T) { func TestStateSlashingPrecommits(t *testing.T) {
cs1, vss := randConsensusState(2) cs1, vss := randConsensusState(2)
vs2 := vss[1] vs2 := vss[1]
@ -1017,7 +1017,7 @@ func TestSlashingPrecommits(t *testing.T) {
// 4 vals. // 4 vals.
// we receive a final precommit after going into next round, but others might have gone to commit already! // we receive a final precommit after going into next round, but others might have gone to commit already!
func TestHalt1(t *testing.T) { func TestStateHalt1(t *testing.T) {
cs1, vss := randConsensusState(4) cs1, vss := randConsensusState(4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3] vs2, vs3, vs4 := vss[1], vss[2], vss[3]

View File

@ -1,9 +1,11 @@
package types package types
import ( import (
"fmt"
"strings" "strings"
"sync" "sync"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
) )
@ -35,7 +37,7 @@ type HeightVoteSet struct {
mtx sync.Mutex mtx sync.Mutex
round int // max tracked round round int // max tracked round
roundVoteSets map[int]RoundVoteSet // keys: [0...round] roundVoteSets map[int]RoundVoteSet // keys: [0...round]
peerCatchupRounds map[string][]int // keys: peer.Key; values: at most 2 rounds peerCatchupRounds map[p2p.ID][]int // keys: peer.ID; values: at most 2 rounds
} }
func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet { func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet {
@ -53,7 +55,7 @@ func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) {
hvs.height = height hvs.height = height
hvs.valSet = valSet hvs.valSet = valSet
hvs.roundVoteSets = make(map[int]RoundVoteSet) hvs.roundVoteSets = make(map[int]RoundVoteSet)
hvs.peerCatchupRounds = make(map[string][]int) hvs.peerCatchupRounds = make(map[p2p.ID][]int)
hvs.addRound(0) hvs.addRound(0)
hvs.round = 0 hvs.round = 0
@ -101,8 +103,8 @@ func (hvs *HeightVoteSet) addRound(round int) {
} }
// Duplicate votes return added=false, err=nil. // Duplicate votes return added=false, err=nil.
// By convention, peerKey is "" if origin is self. // By convention, peerID is "" if origin is self.
func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerKey string) (added bool, err error) { func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) {
hvs.mtx.Lock() hvs.mtx.Lock()
defer hvs.mtx.Unlock() defer hvs.mtx.Unlock()
if !types.IsVoteTypeValid(vote.Type) { if !types.IsVoteTypeValid(vote.Type) {
@ -110,10 +112,10 @@ func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerKey string) (added bool,
} }
voteSet := hvs.getVoteSet(vote.Round, vote.Type) voteSet := hvs.getVoteSet(vote.Round, vote.Type)
if voteSet == nil { if voteSet == nil {
if rndz := hvs.peerCatchupRounds[peerKey]; len(rndz) < 2 { if rndz := hvs.peerCatchupRounds[peerID]; len(rndz) < 2 {
hvs.addRound(vote.Round) hvs.addRound(vote.Round)
voteSet = hvs.getVoteSet(vote.Round, vote.Type) voteSet = hvs.getVoteSet(vote.Round, vote.Type)
hvs.peerCatchupRounds[peerKey] = append(rndz, vote.Round) hvs.peerCatchupRounds[peerID] = append(rndz, vote.Round)
} else { } else {
// Peer has sent a vote that does not match our round, // Peer has sent a vote that does not match our round,
// for more than one round. Bad peer! // for more than one round. Bad peer!
@ -206,15 +208,15 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string {
// NOTE: if there are too many peers, or too much peer churn, // NOTE: if there are too many peers, or too much peer churn,
// this can cause memory issues. // this can cause memory issues.
// TODO: implement ability to remove peers too // TODO: implement ability to remove peers too
func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID string, blockID types.BlockID) { func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) error {
hvs.mtx.Lock() hvs.mtx.Lock()
defer hvs.mtx.Unlock() defer hvs.mtx.Unlock()
if !types.IsVoteTypeValid(type_) { if !types.IsVoteTypeValid(type_) {
return return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_)
} }
voteSet := hvs.getVoteSet(round, type_) voteSet := hvs.getVoteSet(round, type_)
if voteSet == nil { if voteSet == nil {
return return nil // something we don't know about yet
} }
voteSet.SetPeerMaj23(peerID, blockID) return voteSet.SetPeerMaj23(peerID, blockID)
} }

View File

@ -18,7 +18,7 @@ func init() {
func TestPeerCatchupRounds(t *testing.T) { func TestPeerCatchupRounds(t *testing.T) {
valSet, privVals := types.RandValidatorSet(10, 1) valSet, privVals := types.RandValidatorSet(10, 1)
hvs := NewHeightVoteSet(config.ChainID, 1, valSet) hvs := NewHeightVoteSet(config.ChainID(), 1, valSet)
vote999_0 := makeVoteHR(t, 1, 999, privVals, 0) vote999_0 := makeVoteHR(t, 1, 999, privVals, 0)
added, err := hvs.AddVote(vote999_0, "peer1") added, err := hvs.AddVote(vote999_0, "peer1")
@ -59,7 +59,7 @@ func makeVoteHR(t *testing.T, height int64, round int, privVals []*types.PrivVal
Type: types.VoteTypePrecommit, Type: types.VoteTypePrecommit,
BlockID: types.BlockID{[]byte("fakehash"), types.PartSetHeader{}}, BlockID: types.BlockID{[]byte("fakehash"), types.PartSetHeader{}},
} }
chainID := config.ChainID chainID := config.ChainID()
err := privVal.SignVote(chainID, vote) err := privVal.SignVote(chainID, vote)
if err != nil { if err != nil {
panic(cmn.Fmt("Error signing vote: %v", err)) panic(cmn.Fmt("Error signing vote: %v", err))

View File

@ -121,7 +121,7 @@ func (wal *baseWAL) Save(msg WALMessage) {
if wal.light { if wal.light {
// in light mode we only write new steps, timeouts, and our own votes (no proposals, block parts) // in light mode we only write new steps, timeouts, and our own votes (no proposals, block parts)
if mi, ok := msg.(msgInfo); ok { if mi, ok := msg.(msgInfo); ok {
if mi.PeerKey != "" { if mi.PeerID != "" {
return return
} }
} }

View File

@ -41,7 +41,7 @@ func TestWALEncoderDecoder(t *testing.T) {
} }
} }
func TestSearchForEndHeight(t *testing.T) { func TestWALSearchForEndHeight(t *testing.T) {
walBody, err := WALWithNBlocks(6) walBody, err := WALWithNBlocks(6)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

1
docs/.python-version Normal file
View File

@ -0,0 +1 @@
2.7.14

View File

@ -12,6 +12,9 @@ BUILDDIR = _build
help: help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
install:
@pip install -r requirements.txt
.PHONY: help Makefile .PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new # Catch-all target: route all unknown targets to Sphinx using the new

View File

@ -66,14 +66,56 @@ The most important messages are ``deliver_tx``, ``check_tx``, and
``commit``, but there are others for convenience, configuration, and ``commit``, but there are others for convenience, configuration, and
information purposes. information purposes.
Let's start a dummy application, which was installed at the same time as We'll start a dummy application, which was installed at the same time as
``abci-cli`` above. The dummy just stores transactions in a merkle tree: ``abci-cli`` above. The dummy just stores transactions in a merkle tree.
Its code can be found `here <https://github.com/tendermint/abci/blob/master/cmd/abci-cli/abci-cli.go>`__ and looks like:
.. container:: toggle
.. container:: header
**Show/Hide Dummy Example**
.. code-block:: go
func cmdDummy(cmd *cobra.Command, args []string) error {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Create the application - in memory or persisted to disk
var app types.Application
if flagPersist == "" {
app = dummy.NewDummyApplication()
} else {
app = dummy.NewPersistentDummyApplication(flagPersist)
app.(*dummy.PersistentDummyApplication).SetLogger(logger.With("module", "dummy"))
}
// Start the listener
srv, err := server.NewServer(flagAddrD, flagAbci, app)
if err != nil {
return err
}
srv.SetLogger(logger.With("module", "abci-server"))
if err := srv.Start(); err != nil {
return err
}
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
srv.Stop()
})
return nil
}
Start by running:
:: ::
abci-cli dummy abci-cli dummy
In another terminal, run And in another terminal, run
:: ::
@ -187,6 +229,41 @@ Counter - Another Example
Now that we've got the hang of it, let's try another application, the Now that we've got the hang of it, let's try another application, the
"counter" app. "counter" app.
Like the dummy app, its code can be found `here <https://github.com/tendermint/abci/blob/master/cmd/abci-cli/abci-cli.go>`__ and looks like:
.. container:: toggle
.. container:: header
**Show/Hide Counter Example**
.. code-block:: go
func cmdCounter(cmd *cobra.Command, args []string) error {
app := counter.NewCounterApplication(flagSerial)
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Start the listener
srv, err := server.NewServer(flagAddrC, flagAbci, app)
if err != nil {
return err
}
srv.SetLogger(logger.With("module", "abci-server"))
if err := srv.Start(); err != nil {
return err
}
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
srv.Stop()
})
return nil
}
The counter app doesn't use a Merkle tree, it just counts how many times The counter app doesn't use a Merkle tree, it just counts how many times
we've sent a transaction, asked for a hash, or committed the state. The we've sent a transaction, asked for a hash, or committed the state. The
result of ``commit`` is just the number of transactions sent. result of ``commit`` is just the number of transactions sent.
@ -261,7 +338,7 @@ But the ultimate flexibility comes from being able to write the
application easily in any language. application easily in any language.
We have implemented the counter in a number of languages (see the We have implemented the counter in a number of languages (see the
example directory). `example directory <https://github.com/tendermint/abci/tree/master/example`__).
To run the Node JS version, ``cd`` to ``example/js`` and run To run the Node JS version, ``cd`` to ``example/js`` and run
@ -289,4 +366,4 @@ its own pattern of messages.
For more information, see the `application developers For more information, see the `application developers
guide <./app-development.html>`__. For examples of running an ABCI guide <./app-development.html>`__. For examples of running an ABCI
app with Tendermint, see the `getting started app with Tendermint, see the `getting started
guide <./getting-started.html>`__. guide <./getting-started.html>`__. Next is the ABCI specification.

View File

@ -171,29 +171,38 @@ texinfo_documents = [
'Database'), 'Database'),
] ]
repo = "https://raw.githubusercontent.com/tendermint/tools/" # ---- customization -------------------------
branch = "master"
tools = "./tools" tools_repo = "https://raw.githubusercontent.com/tendermint/tools/"
assets = tools + "/assets" tools_branch = "master"
if os.path.isdir(tools) != True: tools_dir = "./tools"
os.mkdir(tools) assets_dir = tools_dir + "/assets"
if os.path.isdir(assets) != True:
os.mkdir(assets)
urllib.urlretrieve(repo+branch+'/ansible/README.rst', filename=tools+'/ansible.rst') if os.path.isdir(tools_dir) != True:
urllib.urlretrieve(repo+branch+'/ansible/assets/a_plus_t.png', filename=assets+'/a_plus_t.png') os.mkdir(tools_dir)
if os.path.isdir(assets_dir) != True:
os.mkdir(assets_dir)
urllib.urlretrieve(repo+branch+'/docker/README.rst', filename=tools+'/docker.rst') urllib.urlretrieve(tools_repo+tools_branch+'/ansible/README.rst', filename=tools_dir+'/ansible.rst')
urllib.urlretrieve(tools_repo+tools_branch+'/ansible/assets/a_plus_t.png', filename=assets_dir+'/a_plus_t.png')
urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/README.rst', filename=tools+'/mintnet-kubernetes.rst') urllib.urlretrieve(tools_repo+tools_branch+'/docker/README.rst', filename=tools_dir+'/docker.rst')
urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/gce1.png', filename=assets+'/gce1.png')
urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/gce2.png', filename=assets+'/gce2.png')
urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/statefulset.png', filename=assets+'/statefulset.png')
urllib.urlretrieve(repo+branch+'/mintnet-kubernetes/assets/t_plus_k.png', filename=assets+'/t_plus_k.png')
urllib.urlretrieve(repo+branch+'/terraform-digitalocean/README.rst', filename=tools+'/terraform-digitalocean.rst') urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/README.rst', filename=tools_dir+'/mintnet-kubernetes.rst')
urllib.urlretrieve(repo+branch+'/tm-bench/README.rst', filename=tools+'/benchmarking-and-monitoring.rst') urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/gce1.png', filename=assets_dir+'/gce1.png')
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/gce2.png', filename=assets_dir+'/gce2.png')
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/statefulset.png', filename=assets_dir+'/statefulset.png')
urllib.urlretrieve(tools_repo+tools_branch+'/mintnet-kubernetes/assets/t_plus_k.png', filename=assets_dir+'/t_plus_k.png')
urllib.urlretrieve(tools_repo+tools_branch+'/terraform-digitalocean/README.rst', filename=tools_dir+'/terraform-digitalocean.rst')
urllib.urlretrieve(tools_repo+tools_branch+'/tm-bench/README.rst', filename=tools_dir+'/benchmarking-and-monitoring.rst')
# the readme for below is included in tm-bench # the readme for below is included in tm-bench
# urllib.urlretrieve('https://raw.githubusercontent.com/tendermint/tools/master/tm-monitor/README.rst', filename='tools/tm-monitor.rst') # urllib.urlretrieve('https://raw.githubusercontent.com/tendermint/tools/master/tm-monitor/README.rst', filename='tools/tm-monitor.rst')
#### abci spec #################################
abci_repo = "https://raw.githubusercontent.com/tendermint/abci/"
abci_branch = "spec-docs"
urllib.urlretrieve(abci_repo+abci_branch+'/specification.rst', filename='abci-spec.rst')

View File

@ -13,7 +13,7 @@ It's relatively easy to setup a Tendermint cluster manually. The only
requirements for a particular Tendermint node are a private key for the requirements for a particular Tendermint node are a private key for the
validator, stored as ``priv_validator.json``, and a list of the public validator, stored as ``priv_validator.json``, and a list of the public
keys of all validators, stored as ``genesis.json``. These files should keys of all validators, stored as ``genesis.json``. These files should
be stored in ``~/.tendermint``, or wherever the ``$TMHOME`` variable be stored in ``~/.tendermint/config``, or wherever the ``$TMHOME`` variable
might be set to. might be set to.
Here are the steps to setting up a testnet manually: Here are the steps to setting up a testnet manually:
@ -24,13 +24,13 @@ Here are the steps to setting up a testnet manually:
``tendermint gen_validator`` ``tendermint gen_validator``
4) Compile a list of public keys for each validator into a 4) Compile a list of public keys for each validator into a
``genesis.json`` file. ``genesis.json`` file.
5) Run ``tendermint node --p2p.seeds=< seed addresses >`` on each node, 5) Run ``tendermint node --p2p.persistent_peers=< peer addresses >`` on each node,
where ``< seed addresses >`` is a comma separated list of the IP:PORT where ``< peer addresses >`` is a comma separated list of the IP:PORT
combination for each node. The default port for Tendermint is combination for each node. The default port for Tendermint is
``46656``. Thus, if the IP addresses of your nodes were ``46656``. Thus, if the IP addresses of your nodes were
``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command ``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command
would look like: would look like:
``tendermint node --p2p.seeds=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. ``tendermint node --p2p.persistent_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``.
After a few seconds, all the nodes should connect to each other and start After a few seconds, all the nodes should connect to each other and start
making blocks! For more information, see the Tendermint Networks section making blocks! For more information, see the Tendermint Networks section

View File

@ -1,122 +1,15 @@
Tendermint Ecosystem 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>`__. Other Tools
-----------
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
----------------
See `deploy testnets <./deploy-testnets.html>`__ for information about all the tools built by Tendermint. We have Kubernetes, Ansible, and Terraform integrations. 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. 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.

View File

@ -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.

View File

@ -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

View File

@ -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 = ""

View File

@ -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":""
}

View File

@ -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"
}
}

View File

@ -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 = ""

View File

@ -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":""
}

View File

@ -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"
}
}

View File

@ -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 = ""

View File

@ -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":""
}

View File

@ -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"
}
}

View File

@ -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 = ""

View File

@ -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":""
}

View File

@ -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"
}
}

View File

@ -53,6 +53,7 @@ Tendermint 102
:maxdepth: 2 :maxdepth: 2
abci-cli.rst abci-cli.rst
abci-spec.rst
app-architecture.rst app-architecture.rst
app-development.rst app-development.rst
how-to-read-logs.rst how-to-read-logs.rst

View File

@ -2,57 +2,172 @@ Configuration
============= =============
Tendermint Core can be configured via a TOML file in Tendermint Core can be configured via a TOML file in
``$TMHOME/config.toml``. Some of these parameters can be overridden by ``$TMHOME/config/config.toml``. Some of these parameters can be overridden by
command-line flags. 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 parameters Config options
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
The main config parameters are defined The default configuration file create by ``tendermint init`` has all
`here <https://github.com/tendermint/tendermint/blob/master/config/config.go>`__. 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:
- ``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. # This is a TOML config file.
*Default*: ``10000`` # For more information, see https://github.com/toml-lang/toml
- ``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 ##### main base config options #####
- ``p2p.addr_book_file``: Peer address book. *Default*: # TCP or UNIX socket address of the ABCI application,
``"$TMHOME/addrbook.json"``. **NOT USED** # or the name of an ABCI application compiled in with the Tendermint binary
- ``p2p.laddr``: Node listen address. (0.0.0.0:0 means any interface, proxy_app = "tcp://127.0.0.1:46658"
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 # A custom human readable name for this node
required. *Default*: ``""`` moniker = "anonymous"
- ``rpc.laddr``: RPC listen address. Port required. *Default*:
``"0.0.0.0:46657"`` # If this node is many blocks behind the tip of the chain, FastSync
- ``rpc.unsafe``: Enabled unsafe rpc methods. *Default*: ``true`` # 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
##### 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 }}

View File

@ -1,7 +1,7 @@
Genesis Genesis
======= =======
The genesis.json file in ``$TMHOME`` defines the initial TendermintCore The genesis.json file in ``$TMHOME/config`` defines the initial TendermintCore
state upon genesis of the blockchain (`see state upon genesis of the blockchain (`see
definition <https://github.com/tendermint/tendermint/blob/master/types/genesis.go>`__). definition <https://github.com/tendermint/tendermint/blob/master/types/genesis.go>`__).

View File

@ -1,15 +1,40 @@
# Tendermint Specification # Tendermint Specification
This is a markdown specification of the Tendermint blockchain. This is a markdown specification of the Tendermint blockchain.
It defines the base data structures, how they are validated,
and how they are communicated over the network.
It defines the base data structures used in the blockchain and how they are validated. XXX: this spec is a work in progress and not yet complete - see github
[isses](https://github.com/tendermint/tendermint/issues) and
[pull requests](https://github.com/tendermint/tendermint/pulls)
for more details.
It contains the following components: If you find discrepancies between the spec and the code that
do not have an associated issue or pull request on github,
please submit them to our [bug bounty](https://tendermint.com/security)!
## Contents
### Data Structures
- [Overview](#overview)
- [Encoding and Digests](encoding.md) - [Encoding and Digests](encoding.md)
- [Blockchain](blockchain.md) - [Blockchain](blockchain.md)
- [State](state.md) - [State](state.md)
### P2P and Network Protocols
- [The Base P2P Layer](p2p/README.md): multiplex the protocols ("reactors") on authenticated and encrypted TCP conns
- [Peer Exchange (PEX)](pex/README.md): gossip known peer addresses so peers can find eachother
- [Block Sync](block_sync/README.md): gossip blocks so peers can catch up quickly
- [Consensus](consensus/README.md): gossip votes and block parts so new blocks can be committed
- [Mempool](mempool/README.md): gossip transactions so they get included in blocks
- [Evidence](evidence/README.md): TODO
### More
- [Light Client](light_client/README.md): TODO
- [Persistence](persistence/README.md): TODO
## Overview ## Overview
Tendermint provides Byzantine Fault Tolerant State Machine Replication using Tendermint provides Byzantine Fault Tolerant State Machine Replication using
@ -49,9 +74,3 @@ Also note that information like the transaction results and the validator set ar
directly included in the block - only their cryptographic digests (Merkle roots) are. directly included in the block - only their cryptographic digests (Merkle roots) are.
Hence, verification of a block requires a separate data structure to store this information. Hence, verification of a block requires a separate data structure to store this information.
We call this the `State`. Block verification also requires access to the previous block. We call this the `State`. Block verification also requires access to the previous block.
## TODO
- Light Client
- P2P
- Reactor protocols (consensus, mempool, blockchain, pex)

View File

@ -2,7 +2,7 @@
Here we describe the data structures in the Tendermint blockchain and the rules for validating them. Here we describe the data structures in the Tendermint blockchain and the rules for validating them.
# Data Structures ## Data Structures
The Tendermint blockchains consists of a short list of basic data types: The Tendermint blockchains consists of a short list of basic data types:
`Block`, `Header`, `Vote`, `BlockID`, `Signature`, and `Evidence`. `Block`, `Header`, `Vote`, `BlockID`, `Signature`, and `Evidence`.
@ -10,9 +10,9 @@ The Tendermint blockchains consists of a short list of basic data types:
## Block ## Block
A block consists of a header, a list of transactions, a list of votes (the commit), A block consists of a header, a list of transactions, a list of votes (the commit),
and a list of evidence if malfeasance (ie. signing conflicting votes). and a list of evidence of malfeasance (ie. signing conflicting votes).
``` ```go
type Block struct { type Block struct {
Header Header Header Header
Txs [][]byte Txs [][]byte
@ -26,7 +26,7 @@ type Block struct {
A block header contains metadata about the block and about the consensus, as well as commitments to A block header contains metadata about the block and about the consensus, as well as commitments to
the data in the current block, the previous block, and the results returned by the application: the data in the current block, the previous block, and the results returned by the application:
``` ```go
type Header struct { type Header struct {
// block metadata // block metadata
Version string // Version string Version string // Version string
@ -66,7 +66,7 @@ the block during consensus, is the Merkle root of the complete serialized block
cut into parts. The `BlockID` includes these two hashes, as well as the number of cut into parts. The `BlockID` includes these two hashes, as well as the number of
parts. parts.
``` ```go
type BlockID struct { type BlockID struct {
Hash []byte Hash []byte
Parts PartsHeader Parts PartsHeader
@ -83,7 +83,7 @@ type PartsHeader struct {
A vote is a signed message from a validator for a particular block. A vote is a signed message from a validator for a particular block.
The vote includes information about the validator signing it. The vote includes information about the validator signing it.
``` ```go
type Vote struct { type Vote struct {
Timestamp int64 Timestamp int64
Address []byte Address []byte
@ -96,7 +96,6 @@ type Vote struct {
} }
``` ```
There are two types of votes: There are two types of votes:
a prevote has `vote.Type == 1` and a prevote has `vote.Type == 1` and
a precommit has `vote.Type == 2`. a precommit has `vote.Type == 2`.
@ -111,7 +110,7 @@ Currently, Tendermint supports Ed25519 and Secp256k1.
An ED25519 signature has `Type == 0x1`. It looks like: An ED25519 signature has `Type == 0x1`. It looks like:
``` ```go
// Implements Signature // Implements Signature
type Ed25519Signature struct { type Ed25519Signature struct {
Type int8 = 0x1 Type int8 = 0x1
@ -125,7 +124,7 @@ where `Signature` is the 64 byte signature.
A `Secp256k1` signature has `Type == 0x2`. It looks like: A `Secp256k1` signature has `Type == 0x2`. It looks like:
``` ```go
// Implements Signature // Implements Signature
type Secp256k1Signature struct { type Secp256k1Signature struct {
Type int8 = 0x2 Type int8 = 0x2
@ -135,7 +134,7 @@ type Secp256k1Signature struct {
where `Signature` is the DER encoded signature, ie: where `Signature` is the DER encoded signature, ie:
``` ```hex
0x30 <length of whole message> <0x02> <length of R> <R> 0x2 <length of S> <S>. 0x30 <length of whole message> <0x02> <length of R> <R> 0x2 <length of S> <S>.
``` ```
@ -143,7 +142,7 @@ where `Signature` is the DER encoded signature, ie:
TODO TODO
# Validation ## Validation
Here we describe the validation rules for every element in a block. Here we describe the validation rules for every element in a block.
Blocks which do not satisfy these rules are considered invalid. Blocks which do not satisfy these rules are considered invalid.
@ -159,7 +158,7 @@ and other results from the application.
Elements of an object are accessed as expected, Elements of an object are accessed as expected,
ie. `block.Header`. See [here](state.md) for the definition of `state`. ie. `block.Header`. See [here](state.md) for the definition of `state`.
## Header ### Header
A Header is valid if its corresponding fields are valid. A Header is valid if its corresponding fields are valid.
@ -173,7 +172,7 @@ Arbitrary constant string.
### Height ### Height
``` ```go
block.Header.Height > 0 block.Header.Height > 0
block.Header.Height == prevBlock.Header.Height + 1 block.Header.Height == prevBlock.Header.Height + 1
``` ```
@ -190,7 +189,7 @@ block being voted on.
### NumTxs ### NumTxs
``` ```go
block.Header.NumTxs == len(block.Txs) block.Header.NumTxs == len(block.Txs)
``` ```
@ -198,7 +197,7 @@ Number of transactions included in the block.
### TxHash ### TxHash
``` ```go
block.Header.TxHash == SimpleMerkleRoot(block.Txs) block.Header.TxHash == SimpleMerkleRoot(block.Txs)
``` ```
@ -206,7 +205,7 @@ Simple Merkle root of the transactions in the block.
### LastCommitHash ### LastCommitHash
``` ```go
block.Header.LastCommitHash == SimpleMerkleRoot(block.LastCommit) block.Header.LastCommitHash == SimpleMerkleRoot(block.LastCommit)
``` ```
@ -217,7 +216,7 @@ The first block has `block.Header.LastCommitHash == []byte{}`
### TotalTxs ### TotalTxs
``` ```go
block.Header.TotalTxs == prevBlock.Header.TotalTxs + block.Header.NumTxs block.Header.TotalTxs == prevBlock.Header.TotalTxs + block.Header.NumTxs
``` ```
@ -227,7 +226,7 @@ The first block has `block.Header.TotalTxs = block.Header.NumberTxs`.
### LastBlockID ### LastBlockID
``` ```go
prevBlockParts := MakeParts(prevBlock, state.LastConsensusParams.BlockGossip.BlockPartSize) prevBlockParts := MakeParts(prevBlock, state.LastConsensusParams.BlockGossip.BlockPartSize)
block.Header.LastBlockID == BlockID { block.Header.LastBlockID == BlockID {
Hash: SimpleMerkleRoot(prevBlock.Header), Hash: SimpleMerkleRoot(prevBlock.Header),
@ -245,7 +244,7 @@ The first block has `block.Header.LastBlockID == BlockID{}`.
### ResultsHash ### ResultsHash
``` ```go
block.ResultsHash == SimpleMerkleRoot(state.LastResults) block.ResultsHash == SimpleMerkleRoot(state.LastResults)
``` ```
@ -255,7 +254,7 @@ The first block has `block.Header.ResultsHash == []byte{}`.
### AppHash ### AppHash
``` ```go
block.AppHash == state.AppHash block.AppHash == state.AppHash
``` ```
@ -265,7 +264,7 @@ The first block has `block.Header.AppHash == []byte{}`.
### ValidatorsHash ### ValidatorsHash
``` ```go
block.ValidatorsHash == SimpleMerkleRoot(state.Validators) block.ValidatorsHash == SimpleMerkleRoot(state.Validators)
``` ```
@ -275,7 +274,7 @@ May be updated by the application.
### ConsensusParamsHash ### ConsensusParamsHash
``` ```go
block.ConsensusParamsHash == SimpleMerkleRoot(state.ConsensusParams) block.ConsensusParamsHash == SimpleMerkleRoot(state.ConsensusParams)
``` ```
@ -284,7 +283,7 @@ May be updated by the application.
### Proposer ### Proposer
``` ```go
block.Header.Proposer in state.Validators block.Header.Proposer in state.Validators
``` ```
@ -296,7 +295,7 @@ and we do not track the initial round the block was proposed.
### EvidenceHash ### EvidenceHash
``` ```go
block.EvidenceHash == SimpleMerkleRoot(block.Evidence) block.EvidenceHash == SimpleMerkleRoot(block.Evidence)
``` ```
@ -310,7 +309,7 @@ Arbitrary length array of arbitrary length byte-arrays.
The first height is an exception - it requires the LastCommit to be empty: The first height is an exception - it requires the LastCommit to be empty:
``` ```go
if block.Header.Height == 1 { if block.Header.Height == 1 {
len(b.LastCommit) == 0 len(b.LastCommit) == 0
} }
@ -318,7 +317,7 @@ if block.Header.Height == 1 {
Otherwise, we require: Otherwise, we require:
``` ```go
len(block.LastCommit) == len(state.LastValidators) len(block.LastCommit) == len(state.LastValidators)
talliedVotingPower := 0 talliedVotingPower := 0
for i, vote := range block.LastCommit{ for i, vote := range block.LastCommit{
@ -356,7 +355,7 @@ For signing, votes are encoded in JSON, and the ChainID is included, in the form
We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the CanonicalSignBytes We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the CanonicalSignBytes
using the given ChainID: using the given ChainID:
``` ```go
func (v Vote) Verify(chainID string, pubKey PubKey) bool { func (v Vote) Verify(chainID string, pubKey PubKey) bool {
return pubKey.Verify(v.Signature, CanonicalSignBytes(chainID, v)) return pubKey.Verify(v.Signature, CanonicalSignBytes(chainID, v))
} }
@ -367,10 +366,10 @@ against the given signature and message bytes.
## Evidence ## Evidence
TODO
``` ```
TODO
``` ```
Every piece of evidence contains two conflicting votes from a single validator that Every piece of evidence contains two conflicting votes from a single validator that
@ -384,8 +383,36 @@ Once a block is validated, it can be executed against the state.
The state follows the recursive equation: The state follows the recursive equation:
``` ```go
app = NewABCIApp
state(1) = InitialState state(1) = InitialState
state(h+1) <- Execute(state(h), app, block(h)) state(h+1) <- Execute(state(h), ABCIApp, block(h))
``` ```
Where `InitialState` includes the initial consensus parameters and validator set,
and `ABCIApp` is an ABCI application that can return results and changes to the validator
set (TODO). Execute is defined as:
```go
Execute(s State, app ABCIApp, block Block) State {
TODO: just spell out ApplyBlock here
and remove ABCIResponses struct.
abciResponses := app.ApplyBlock(block)
return State{
LastResults: abciResponses.DeliverTxResults,
AppHash: abciResponses.AppHash,
Validators: UpdateValidators(state.Validators, abciResponses.ValidatorChanges),
LastValidators: state.Validators,
ConsensusParams: UpdateConsensusParams(state.ConsensusParams, abci.Responses.ConsensusParamChanges),
}
}
type ABCIResponses struct {
DeliverTxResults []Result
ValidatorChanges []Validator
ConsensusParamChanges ConsensusParams
AppHash []byte
}
```

View File

@ -2,9 +2,14 @@
## Binary Serialization (TMBIN) ## Binary Serialization (TMBIN)
Tendermint aims to encode data structures in a manner similar to how the corresponding Go structs are laid out in memory. Tendermint aims to encode data structures in a manner similar to how the corresponding Go structs
are laid out in memory.
Variable length items are length-prefixed. Variable length items are length-prefixed.
While the encoding was inspired by Go, it is easily implemented in other languages as well given its intuitive design. While the encoding was inspired by Go, it is easily implemented in other languages as well given its
intuitive design.
XXX: This is changing to use real varints and 4-byte-prefixes.
See https://github.com/tendermint/go-wire/tree/sdk2.
### Fixed Length Integers ### Fixed Length Integers
@ -16,7 +21,7 @@ Negative integers are encoded via twos-complement.
Examples: Examples:
``` ```go
encode(uint8(6)) == [0x06] encode(uint8(6)) == [0x06]
encode(uint32(6)) == [0x00, 0x00, 0x00, 0x06] encode(uint32(6)) == [0x00, 0x00, 0x00, 0x06]
@ -33,10 +38,9 @@ Negative integers are encoded by flipping the leading bit of the length-prefix t
Zero is encoded as `0x00`. It is not length-prefixed. Zero is encoded as `0x00`. It is not length-prefixed.
Examples: Examples:
``` ```go
encode(uint(6)) == [0x01, 0x06] encode(uint(6)) == [0x01, 0x06]
encode(uint(70000)) == [0x03, 0x01, 0x11, 0x70] encode(uint(70000)) == [0x03, 0x01, 0x11, 0x70]
@ -55,7 +59,7 @@ The empty string is encoded as `0x00`. It is not length-prefixed.
Examples: Examples:
``` ```go
encode("") == [0x00] encode("") == [0x00]
encode("a") == [0x01, 0x01, 0x61] encode("a") == [0x01, 0x01, 0x61]
encode("hello") == [0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F] encode("hello") == [0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F]
@ -69,7 +73,7 @@ There is no length-prefix.
Examples: Examples:
``` ```go
encode([4]int8{1, 2, 3, 4}) == [0x01, 0x02, 0x03, 0x04] encode([4]int8{1, 2, 3, 4}) == [0x01, 0x02, 0x03, 0x04]
encode([4]int16{1, 2, 3, 4}) == [0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04] encode([4]int16{1, 2, 3, 4}) == [0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04]
encode([4]int{1, 2, 3, 4}) == [0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01, 0x04] encode([4]int{1, 2, 3, 4}) == [0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01, 0x04]
@ -78,14 +82,15 @@ encode([2]string{"abc", "efg"}) == [0x01, 0x03, 0x61, 0x62, 0x63, 0x01, 0x03, 0x
### Slices (variable length) ### Slices (variable length)
An encoded variable-length array is a length prefix followed by the concatenation of the encoding of its elements. An encoded variable-length array is a length prefix followed by the concatenation of the encoding of
its elements.
The length-prefix is itself encoded as an `int`. The length-prefix is itself encoded as an `int`.
An empty slice is encoded as `0x00`. It is not length-prefixed. An empty slice is encoded as `0x00`. It is not length-prefixed.
Examples: Examples:
``` ```go
encode([]int8{}) == [0x00] encode([]int8{}) == [0x00]
encode([]int8{1, 2, 3, 4}) == [0x01, 0x04, 0x01, 0x02, 0x03, 0x04] encode([]int8{1, 2, 3, 4}) == [0x01, 0x04, 0x01, 0x02, 0x03, 0x04]
encode([]int16{1, 2, 3, 4}) == [0x01, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04] encode([]int16{1, 2, 3, 4}) == [0x01, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04]
@ -93,6 +98,18 @@ encode([]int{1, 2, 3, 4}) == [0x01, 0x04, 0x01, 0x01, 0x01, 0x02, 0x01, 0x
encode([]string{"abc", "efg"}) == [0x01, 0x02, 0x01, 0x03, 0x61, 0x62, 0x63, 0x01, 0x03, 0x65, 0x66, 0x67] encode([]string{"abc", "efg"}) == [0x01, 0x02, 0x01, 0x03, 0x61, 0x62, 0x63, 0x01, 0x03, 0x65, 0x66, 0x67]
``` ```
### BitArray
BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode
value of each array element.
```go
type BitArray struct {
Bits int
Elems []uint64
}
```
### Time ### Time
Time is encoded as an `int64` of the number of nanoseconds since January 1, 1970, Time is encoded as an `int64` of the number of nanoseconds since January 1, 1970,
@ -102,7 +119,7 @@ Times before then are invalid.
Examples: Examples:
``` ```go
encode(time.Time("Jan 1 00:00:00 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] encode(time.Time("Jan 1 00:00:00 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
encode(time.Time("Jan 1 00:00:01 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x3B, 0x9A, 0xCA, 0x00] // 1,000,000,000 ns encode(time.Time("Jan 1 00:00:01 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x3B, 0x9A, 0xCA, 0x00] // 1,000,000,000 ns
encode(time.Time("Mon Jan 2 15:04:05 -0700 MST 2006")) == [0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00] encode(time.Time("Mon Jan 2 15:04:05 -0700 MST 2006")) == [0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00]
@ -115,7 +132,7 @@ There is no length-prefix.
Examples: Examples:
``` ```go
type MyStruct struct{ type MyStruct struct{
A int A int
B string B string
@ -125,7 +142,6 @@ encode(MyStruct{4, "hello", time.Time("Mon Jan 2 15:04:05 -0700 MST 2006")}) ==
[0x01, 0x04, 0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00] [0x01, 0x04, 0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00]
``` ```
## Merkle Trees ## Merkle Trees
Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure. Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure.
@ -134,7 +150,7 @@ RIPEMD160 is always used as the hashing function.
The function `SimpleMerkleRoot` is a simple recursive function defined as follows: The function `SimpleMerkleRoot` is a simple recursive function defined as follows:
``` ```go
func SimpleMerkleRoot(hashes [][]byte) []byte{ func SimpleMerkleRoot(hashes [][]byte) []byte{
switch len(hashes) { switch len(hashes) {
case 0: case 0:
@ -150,7 +166,8 @@ func SimpleMerkleRoot(hashes [][]byte) []byte{
``` ```
Note we abuse notion and call `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`. Note we abuse notion and call `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`.
For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to field name and then hashing them. For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to
field name and then hashing them.
For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements. For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements.
## JSON (TMJSON) ## JSON (TMJSON)
@ -158,10 +175,12 @@ For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `str
Signed messages (eg. votes, proposals) in the consensus are encoded in TMJSON, rather than TMBIN. Signed messages (eg. votes, proposals) in the consensus are encoded in TMJSON, rather than TMBIN.
TMJSON is JSON where `[]byte` are encoded as uppercase hex, rather than base64. TMJSON is JSON where `[]byte` are encoded as uppercase hex, rather than base64.
When signing, the elements of a message are sorted by key and the sorted message is embedded in an outer JSON that includes a `chain_id` field. When signing, the elements of a message are sorted by key and the sorted message is embedded in an
We call this encoding the CanonicalSignBytes. For instance, CanonicalSignBytes for a vote would look like: outer JSON that includes a `chain_id` field.
We call this encoding the CanonicalSignBytes. For instance, CanonicalSignBytes for a vote would look
like:
``` ```json
{"chain_id":"my-chain-id","vote":{"block_id":{"hash":DEADBEEF,"parts":{"hash":BEEFDEAD,"total":3}},"height":3,"round":2,"timestamp":1234567890, "type":2} {"chain_id":"my-chain-id","vote":{"block_id":{"hash":DEADBEEF,"parts":{"hash":BEEFDEAD,"total":3}},"height":3,"round":2,"timestamp":1234567890, "type":2}
``` ```
@ -173,6 +192,16 @@ Note how the fields within each level are sorted.
TMBIN encode an object and slice it into parts. TMBIN encode an object and slice it into parts.
``` ```go
MakeParts(object, partSize) MakeParts(object, partSize)
``` ```
### Part
```go
type Part struct {
Index int
Bytes byte[]
Proof byte[]
}
```

View File

@ -0,0 +1,39 @@
# P2P Config
Here we describe configuration options around the Peer Exchange.
## 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.
Note that 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 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.

View File

@ -0,0 +1,108 @@
## 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` in comparison to the others.
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, and 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 a 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.
If a pong or message is not received in sufficient time after a ping, disconnect from the peer.
### Msg
Messages in channels are chopped into smaller msgPackets for multiplexing.
```
type msgPacket struct {
ChannelID byte
EOF byte // 1 means message ends here.
Bytes []byte
}
```
The msgPacket is serialized using go-wire, and prefixed with a 0x3.
The received `Bytes` of a sequential set of packets are appended together
until a packet with `EOF=1` is received, at which point the complete serialized message
is returned for processing by the corresponding channels `onReceive` function.
### Multiplexing
Messages are sent from a single `sendRoutine`, which loops over a select statement that 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")
}
}
```

View File

@ -0,0 +1,65 @@
# Tendermint Peer Discovery
A Tendermint P2P network has different kinds of nodes with different requirements for connectivity to others.
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 disconnect.
Seeds should operate full nodes, and 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] 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 was invalidated.
## 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.
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.

View File

@ -0,0 +1,115 @@
# 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 that 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:
```
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.

View File

@ -0,0 +1,47 @@
## 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?

View File

@ -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 the Consensus Reactor. , and turn 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

View File

@ -0,0 +1,355 @@
# Consensus Reactor
Consensus Reactor defines a reactor for the consensus service. It contains ConsensusState service that
manages the state of the Tendermint consensus internal state machine.
When Consensus Reactor is started, it starts Broadcast Routine and it starts ConsensusState service.
Furthermore, for each peer that is added to the Consensus Reactor, it creates (and manage) 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.
```
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 and 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.
```
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 denotes
RoundState and prs PeerRoundState.
### 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 (denoted rs)
and the known PeerRoundState (denotes 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 (denoted rs)
and the known PeerRoundState (denotes 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 (denoted rs) and the known PeerRoundState
(denotes 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 internal event bus to receive new round steps, votes messages and proposal
heartbeat messages, and broadcasts messages to peers upon receiving those events.
It brodcasts `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.

View File

@ -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
}
```

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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``)

View File

@ -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

View File

@ -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"
}
```

View File

@ -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 were 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.

View File

@ -2,13 +2,18 @@
## State ## State
The state contains information whose cryptographic digest is included in block headers, The state contains information whose cryptographic digest is included in block headers, and thus is
and thus is necessary for validating new blocks. necessary for validating new blocks. For instance, the set of validators and the results of
For instance, the Merkle root of the results from executing the previous block, or the Merkle root of the current validators. transactions are never included in blocks, but their Merkle roots are - the state keeps track of them.
While neither the results of transactions now the validators are ever included in the blockchain itself,
the Merkle roots are, and hence we need a separate data structure to track them.
``` Note that the `State` object itself is an implementation detail, since it is never
included in a block or gossipped over the network, and we never compute
its hash. However, the types it contains are part of the specification, since
their Merkle roots are included in blocks.
For details on an implementation of `State` with persistence, see TODO
```go
type State struct { type State struct {
LastResults []Result LastResults []Result
AppHash []byte AppHash []byte
@ -22,7 +27,7 @@ type State struct {
### Result ### Result
``` ```go
type Result struct { type Result struct {
Code uint32 Code uint32
Data []byte Data []byte
@ -46,7 +51,7 @@ represented in the tags.
A validator is an active participant in the consensus with a public key and a voting power. A validator is an active participant in the consensus with a public key and a voting power.
Validator's also contain an address which is derived from the PubKey: Validator's also contain an address which is derived from the PubKey:
``` ```go
type Validator struct { type Validator struct {
Address []byte Address []byte
PubKey PubKey PubKey PubKey
@ -59,7 +64,7 @@ so that there is a canonical order for computing the SimpleMerkleRoot.
We also define a `TotalVotingPower` function, to return the total voting power: We also define a `TotalVotingPower` function, to return the total voting power:
``` ```go
func TotalVotingPower(vals []Validators) int64{ func TotalVotingPower(vals []Validators) int64{
sum := 0 sum := 0
for v := range vals{ for v := range vals{
@ -77,28 +82,3 @@ TODO:
TODO: TODO:
## Execution
We define an `Execute` function that takes a state and a block,
executes the block against the application, and returns an updated state.
```
Execute(s State, app ABCIApp, block Block) State {
abciResponses := app.ApplyBlock(block)
return State{
LastResults: abciResponses.DeliverTxResults,
AppHash: abciResponses.AppHash,
Validators: UpdateValidators(state.Validators, abciResponses.ValidatorChanges),
LastValidators: state.Validators,
ConsensusParams: UpdateConsensusParams(state.ConsensusParams, abci.Responses.ConsensusParamChanges),
}
}
type ABCIResponses struct {
DeliverTxResults []Result
ValidatorChanges []Validator
ConsensusParamChanges ConsensusParams
AppHash []byte
}
```

View File

@ -18,7 +18,7 @@ Configuration
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
Set the ``laddr`` config parameter under ``[rpc]`` table in the Set the ``laddr`` config parameter under ``[rpc]`` table in the
$TMHOME/config.toml file or the ``--rpc.laddr`` command-line flag to the $TMHOME/config/config.toml file or the ``--rpc.laddr`` command-line flag to the
desired protocol://host:port setting. Default: ``tcp://0.0.0.0:46657``. desired protocol://host:port setting. Default: ``tcp://0.0.0.0:46657``.
Arguments Arguments
@ -112,6 +112,7 @@ An HTTP Get request to the root RPC endpoint (e.g.
http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/broadcast_tx_sync?tx=_
http://localhost:46657/commit?height=_ http://localhost:46657/commit?height=_
http://localhost:46657/dial_seeds?seeds=_ http://localhost:46657/dial_seeds?seeds=_
http://localhost:46657/dial_peers?peers=_&persistent=_
http://localhost:46657/subscribe?event=_ http://localhost:46657/subscribe?event=_
http://localhost:46657/tx?hash=_&prove=_ http://localhost:46657/tx?hash=_&prove=_
http://localhost:46657/unsafe_start_cpu_profiler?filename=_ http://localhost:46657/unsafe_start_cpu_profiler?filename=_

View File

@ -24,7 +24,8 @@ Initialize the root directory by running:
tendermint init tendermint init
This will create a new private key (``priv_validator.json``), and a This will create a new private key (``priv_validator.json``), and a
genesis file (``genesis.json``) containing the associated public key. genesis file (``genesis.json``) containing the associated public key,
in ``$TMHOME/config``.
This is all that's necessary to run a local testnet with one validator. This is all that's necessary to run a local testnet with one validator.
For more elaborate initialization, see our `testnet deployment For more elaborate initialization, see our `testnet deployment
@ -67,7 +68,7 @@ Transactions
------------ ------------
To send a transaction, use ``curl`` to make requests to the Tendermint To send a transaction, use ``curl`` to make requests to the Tendermint
RPC server: RPC server, for example:
:: ::
@ -92,6 +93,57 @@ Visit http://localhost:46657 in your browser to see the list of other
endpoints. Some take no arguments (like ``/status``), while others endpoints. Some take no arguments (like ``/status``), while others
specify the argument name and use ``_`` as a placeholder. specify the argument name and use ``_`` as a placeholder.
Formatting
~~~~~~~~~~
The following nuances when sending/formatting transactions should
be taken into account:
With ``GET``:
To send a UTF8 string byte array, quote the value of the tx pramater:
::
curl 'http://localhost:46657/broadcast_tx_commit?tx="hello"'
which sends a 5 byte transaction: "h e l l o" [68 65 6c 6c 6f].
Note the URL must be wrapped with single quoes, else bash will ignore the double quotes.
To avoid the single quotes, escape the double quotes:
::
curl http://localhost:46657/broadcast_tx_commit?tx=\"hello\"
Using a special character:
::
curl 'http://localhost:46657/broadcast_tx_commit?tx="€5"'
sends a 4 byte transaction: "€5" (UTF8) [e2 82 ac 35].
To send as raw hex, omit quotes AND prefix the hex string with ``0x``:
::
curl http://localhost:46657/broadcast_tx_commit?tx=0x01020304
which sends a 4 byte transaction: [01 02 03 04].
With ``POST`` (using ``json``), the raw hex must be ``base64`` encoded:
::
curl --data-binary '{"jsonrpc":"2.0","id":"anything","method":"broadcast_tx_commit","params": {"tx": "AQIDBA=="}}' -H 'content-type:text/plain;' http://localhost:46657
which sends the same 4 byte transaction: [01 02 03 04].
Note that raw hex cannot be used in ``POST`` transactions.
Reset Reset
----- -----
@ -127,10 +179,14 @@ Some fields from the config file can be overwritten with flags.
No Empty Blocks No Empty Blocks
--------------- ---------------
This much requested feature was implemented in version 0.10.3. While the default behaviour of ``tendermint`` is still to create blocks approximately once per second, it is possible to disable empty blocks or set a block creation interval. In the former case, blocks will be created when there are new transactions or when the AppHash changes. This much requested feature was implemented in version 0.10.3. While the
default behaviour of ``tendermint`` is still to create blocks approximately
once per second, it is possible to disable empty blocks or set a block creation
interval. In the former case, blocks will be created when there are new
transactions or when the AppHash changes.
To configure Tendermint to not produce empty blocks unless there are txs or the app hash changes, To configure Tendermint to not produce empty blocks unless there are
run Tendermint with this additional flag: transactions or the app hash changes, run Tendermint with this additional flag:
:: ::
@ -153,8 +209,7 @@ The block interval setting allows for a delay (in seconds) between the creation
create_empty_blocks_interval = 5 create_empty_blocks_interval = 5
With this setting, empty blocks will be produced every 5s if no block has been produced otherwise, With this setting, empty blocks will be produced every 5s if no block has been produced otherwise,
regardless of the value of `create_empty_blocks`. regardless of the value of ``create_empty_blocks``.
Broadcast API Broadcast API
------------- -------------
@ -192,11 +247,14 @@ can take on the order of a second. For a quick result, use
``broadcast_tx_sync``, but the transaction will not be committed until ``broadcast_tx_sync``, but the transaction will not be committed until
later, and by that point its effect on the state may change. later, and by that point its effect on the state may change.
Note: see the Transactions => Formatting section for details about
transaction formating.
Tendermint Networks Tendermint Networks
------------------- -------------------
When ``tendermint init`` is run, both a ``genesis.json`` and When ``tendermint init`` is run, both a ``genesis.json`` and
``priv_validator.json`` are created in ``~/.tendermint``. The ``priv_validator.json`` are created in ``~/.tendermint/config``. The
``genesis.json`` might look like: ``genesis.json`` might look like:
:: ::
@ -246,13 +304,17 @@ conflicting messages.
Note also that the ``pub_key`` (the public key) in the Note also that the ``pub_key`` (the public key) in the
``priv_validator.json`` is also present in the ``genesis.json``. ``priv_validator.json`` is also present in the ``genesis.json``.
The genesis file contains the list of public keys which may participate The genesis file contains the list of public keys which may participate in the
in the consensus, and their corresponding voting power. Greater than 2/3 consensus, and their corresponding voting power. Greater than 2/3 of the voting
of the voting power must be active (i.e. the corresponding private keys power must be active (i.e. the corresponding private keys must be producing
must be producing signatures) for the consensus to make progress. In our signatures) for the consensus to make progress. In our case, the genesis file
case, the genesis file contains the public key of our contains the public key of our ``priv_validator.json``, so a Tendermint node
``priv_validator.json``, so a Tendermint node started with the default started with the default root directory will be able to make progress. Voting
root directory will be able to make new blocks, as we've already seen. power uses an `int64` but must be positive, thus the range is: 0 through
9223372036854775807. Because of how the current proposer selection algorithm works,
we do not recommend having voting powers greater than 10^12 (ie. 1 trillion)
(see `Proposals section of Byzantine Consensus Algorithm
<./specification/byzantine-consensus-algorithm.html#proposals>`__ for details).
If we want to add more nodes to the network, we have two choices: we can If we want to add more nodes to the network, we have two choices: we can
add a new validator node, who will also participate in the consensus by add a new validator node, who will also participate in the consensus by
@ -263,8 +325,10 @@ with the consensus protocol.
Peers Peers
~~~~~ ~~~~~
To connect to peers on start-up, specify them in the ``config.toml`` or To connect to peers on start-up, specify them in the ``$TMHOME/config/config.toml`` or
on the command line. on the command line. Use `seeds` to specify seed nodes from which you can get many other
peer addresses, and ``persistent_peers`` to specify peers that your node will maintain
persistent connections with.
For instance, For instance,
@ -273,26 +337,35 @@ For instance,
tendermint node --p2p.seeds "1.2.3.4:46656,5.6.7.8:46656" tendermint node --p2p.seeds "1.2.3.4:46656,5.6.7.8:46656"
Alternatively, you can use the ``/dial_seeds`` endpoint of the RPC to Alternatively, you can use the ``/dial_seeds`` endpoint of the RPC to
specify peers for a running node to connect to: specify seeds for a running node to connect to:
:: ::
curl --data-urlencode "seeds=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_seeds curl 'localhost:46657/dial_seeds?seeds=\["1.2.3.4:46656","5.6.7.8:46656"\]'
Additionally, the peer-exchange protocol can be enabled using the Note, if the peer-exchange protocol (PEX) is enabled (default), you should not
``--pex`` flag, though this feature is `still under normally need seeds after the first start. Peers will be gossipping about known
development <https://github.com/tendermint/tendermint/issues/598>`__. If peers and forming a network, storing peer addresses in the addrbook.
``--pex`` is enabled, peers will gossip about known peers and form a
more resilient network. If you want Tendermint to connect to specific set of addresses and maintain a
persistent connection with each, you can use the ``--p2p.persistent_peers``
flag or the corresponding setting in the ``config.toml`` or the
``/dial_peers`` RPC endpoint to do it without stopping Tendermint
core instance.
::
tendermint node --p2p.persistent_peers "10.11.12.13:46656,10.11.12.14:46656"
curl 'localhost:46657/dial_peers?persistent=true&peers=\["1.2.3.4:46656","5.6.7.8:46656"\]'
Adding a Non-Validator Adding a Non-Validator
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
Adding a non-validator is simple. Just copy the original Adding a non-validator is simple. Just copy the original
``genesis.json`` to ``~/.tendermint`` on the new machine and start the ``genesis.json`` to ``~/.tendermint/config`` on the new machine and start the
node, specifying seeds as necessary. If no seeds are specified, the node node, specifying seeds or persistent peers as necessary. If no seeds or persistent
won't make any blocks, because it's not a validator, and it won't hear peers are specified, the node won't make any blocks, because it's not a validator,
about any blocks, because it's not connected to the other peer. and it won't hear about any blocks, because it's not connected to the other peer.
Adding a Validator Adding a Validator
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -358,12 +431,12 @@ then the new ``genesis.json`` will be:
] ]
} }
Update the ``genesis.json`` in ``~/.tendermint``. Copy the genesis file Update the ``genesis.json`` in ``~/.tendermint/config``. Copy the genesis file
and the new ``priv_validator.json`` to the ``~/.tendermint`` on a new and the new ``priv_validator.json`` to the ``~/.tendermint/config`` on a new
machine. machine.
Now run ``tendermint node`` on both machines, and use either Now run ``tendermint node`` on both machines, and use either
``--p2p.seeds`` or the ``/dial_seeds`` to get them to peer up. They ``--p2p.persistent_peers`` or the ``/dial_peers`` to get them to peer up. They
should start making blocks, and will only continue to do so as long as should start making blocks, and will only continue to do so as long as
both of them are online. both of them are online.

22
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: 072c8e685dd519c1f509da67379b70451a681bf3ef6cbd82900a1f68c55bbe16 hash: 9399a10e80d255104f8ec07b5d495c41d8a3f7a421f9da97ebd78c65189f381d
updated: 2017-12-29T11:08:17.355999228-05:00 updated: 2018-01-18T23:11:10.703734578-05:00
imports: imports:
- name: github.com/btcsuite/btcd - name: github.com/btcsuite/btcd
version: 2e60448ffcc6bf78332d1fe590260095f554dd78 version: 2e60448ffcc6bf78332d1fe590260095f554dd78
@ -10,7 +10,7 @@ imports:
- name: github.com/fsnotify/fsnotify - name: github.com/fsnotify/fsnotify
version: 4da3e2cfbabc9f751898f250b49f2439785783a1 version: 4da3e2cfbabc9f751898f250b49f2439785783a1
- name: github.com/go-kit/kit - name: github.com/go-kit/kit
version: 953e747656a7bbb5e1f998608b460458958b70cc version: 53f10af5d5c7375d4655a3d6852457ed17ab5cc7
subpackages: subpackages:
- log - log
- log/level - log/level
@ -68,13 +68,13 @@ imports:
- name: github.com/mitchellh/mapstructure - name: github.com/mitchellh/mapstructure
version: 06020f85339e21b2478f756a78e295255ffa4d6a version: 06020f85339e21b2478f756a78e295255ffa4d6a
- name: github.com/pelletier/go-toml - name: github.com/pelletier/go-toml
version: 0131db6d737cfbbfb678f8b7d92e55e27ce46224 version: 4e9e0ee19b60b13eb79915933f44d8ed5f268bdd
- name: github.com/pkg/errors - name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/rcrowley/go-metrics - name: github.com/rcrowley/go-metrics
version: e181e095bae94582363434144c61a9653aff6e50 version: e181e095bae94582363434144c61a9653aff6e50
- name: github.com/spf13/afero - name: github.com/spf13/afero
version: 57afd63c68602b63ed976de00dd066ccb3c319db version: 8d919cbe7e2627e417f3e45c3c0e489a5b7e2536
subpackages: subpackages:
- mem - mem
- name: github.com/spf13/cast - name: github.com/spf13/cast
@ -88,7 +88,7 @@ imports:
- name: github.com/spf13/viper - name: github.com/spf13/viper
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
- name: github.com/syndtr/goleveldb - name: github.com/syndtr/goleveldb
version: 34011bf325bce385408353a30b101fe5e923eb6e version: adf24ef3f94bd13ec4163060b21a5678f22b429b
subpackages: subpackages:
- leveldb - leveldb
- leveldb/cache - leveldb/cache
@ -129,7 +129,7 @@ imports:
subpackages: subpackages:
- iavl - iavl
- name: github.com/tendermint/tmlibs - name: github.com/tendermint/tmlibs
version: 91b4b534ad78e442192c8175db92a06a51064064 version: 15e51fa76086a3c505f227679c2478043ae7261b
subpackages: subpackages:
- autofile - autofile
- cli - cli
@ -144,7 +144,7 @@ imports:
- pubsub/query - pubsub/query
- test - test
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 95a4943f35d008beabde8c11e5075a1b714e6419 version: 94eea52f7b742c7cbe0b03b22f0c4c8631ece122
subpackages: subpackages:
- curve25519 - curve25519
- nacl/box - nacl/box
@ -165,11 +165,11 @@ imports:
- lex/httplex - lex/httplex
- trace - trace
- name: golang.org/x/sys - name: golang.org/x/sys
version: 83801418e1b59fb1880e363299581ee543af32ca version: 8b4580aae2a0dd0c231a45d3ccb8434ff533b840
subpackages: subpackages:
- unix - unix
- name: golang.org/x/text - name: golang.org/x/text
version: e19ae1496984b1c655b8044a65c0300a3c878dd3 version: 57961680700a5336d15015c8c50686ca5ba362a4
subpackages: subpackages:
- secure/bidirule - secure/bidirule
- transform - transform
@ -199,7 +199,7 @@ imports:
- tap - tap
- transport - transport
- name: gopkg.in/go-playground/validator.v9 - name: gopkg.in/go-playground/validator.v9
version: b1f51f36f1c98cc97f777d6fc9d4b05eaa0cabb5 version: 61caf9d3038e1af346dbf5c2e16f6678e1548364
- name: gopkg.in/yaml.v2 - name: gopkg.in/yaml.v2
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5 version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
testImports: testImports:

View File

@ -34,7 +34,7 @@ import:
subpackages: subpackages:
- iavl - iavl
- package: github.com/tendermint/tmlibs - package: github.com/tendermint/tmlibs
version: v0.6.0 version: v0.6.1
subpackages: subpackages:
- autofile - autofile
- cli - cli

View File

@ -10,6 +10,7 @@ import (
liteErr "github.com/tendermint/tendermint/lite/errors" liteErr "github.com/tendermint/tendermint/lite/errors"
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
rpctest "github.com/tendermint/tendermint/rpc/test" rpctest "github.com/tendermint/tendermint/rpc/test"
"github.com/tendermint/tendermint/types"
) )
func TestProvider(t *testing.T) { func TestProvider(t *testing.T) {
@ -17,7 +18,8 @@ func TestProvider(t *testing.T) {
cfg := rpctest.GetConfig() cfg := rpctest.GetConfig()
rpcAddr := cfg.RPC.ListenAddress rpcAddr := cfg.RPC.ListenAddress
chainID := cfg.ChainID genDoc, _ := types.GenesisDocFromFile(cfg.GenesisFile())
chainID := genDoc.ChainID
p := NewHTTPProvider(rpcAddr) p := NewHTTPProvider(rpcAddr)
require.NotNil(t, p) require.NotNil(t, p)
@ -35,7 +37,7 @@ func TestProvider(t *testing.T) {
// let's check this is valid somehow // let's check this is valid somehow
assert.Nil(seed.ValidateBasic(chainID)) assert.Nil(seed.ValidateBasic(chainID))
cert := lite.NewStatic(chainID, seed.Validators) cert := lite.NewStaticCertifier(chainID, seed.Validators)
// historical queries now work :) // historical queries now work :)
lower := sh - 5 lower := sh - 5

View File

@ -6,9 +6,9 @@ import (
liteErr "github.com/tendermint/tendermint/lite/errors" liteErr "github.com/tendermint/tendermint/lite/errors"
) )
var _ Certifier = &Dynamic{} var _ Certifier = (*DynamicCertifier)(nil)
// Dynamic uses a Static for Certify, but adds an // DynamicCertifier uses a StaticCertifier for Certify, but adds an
// Update method to allow for a change of validators. // Update method to allow for a change of validators.
// //
// You can pass in a FullCommit with another validator set, // You can pass in a FullCommit with another validator set,
@ -17,46 +17,48 @@ var _ Certifier = &Dynamic{}
// validator set for the next Certify call. // validator set for the next Certify call.
// For security, it will only follow validator set changes // For security, it will only follow validator set changes
// going forward. // going forward.
type Dynamic struct { type DynamicCertifier struct {
cert *Static cert *StaticCertifier
lastHeight int64 lastHeight int64
} }
// NewDynamic returns a new dynamic certifier. // NewDynamic returns a new dynamic certifier.
func NewDynamic(chainID string, vals *types.ValidatorSet, height int64) *Dynamic { func NewDynamicCertifier(chainID string, vals *types.ValidatorSet, height int64) *DynamicCertifier {
return &Dynamic{ return &DynamicCertifier{
cert: NewStatic(chainID, vals), cert: NewStaticCertifier(chainID, vals),
lastHeight: height, lastHeight: height,
} }
} }
// ChainID returns the chain id of this certifier. // ChainID returns the chain id of this certifier.
func (c *Dynamic) ChainID() string { // Implements Certifier.
return c.cert.ChainID() func (dc *DynamicCertifier) ChainID() string {
return dc.cert.ChainID()
} }
// Validators returns the validators of this certifier. // Validators returns the validators of this certifier.
func (c *Dynamic) Validators() *types.ValidatorSet { func (dc *DynamicCertifier) Validators() *types.ValidatorSet {
return c.cert.vSet return dc.cert.vSet
} }
// Hash returns the hash of this certifier. // Hash returns the hash of this certifier.
func (c *Dynamic) Hash() []byte { func (dc *DynamicCertifier) Hash() []byte {
return c.cert.Hash() return dc.cert.Hash()
} }
// LastHeight returns the last height of this certifier. // LastHeight returns the last height of this certifier.
func (c *Dynamic) LastHeight() int64 { func (dc *DynamicCertifier) LastHeight() int64 {
return c.lastHeight return dc.lastHeight
} }
// Certify will verify whether the commit is valid and will update the height if it is or return an // Certify will verify whether the commit is valid and will update the height if it is or return an
// error if it is not. // error if it is not.
func (c *Dynamic) Certify(check Commit) error { // Implements Certifier.
err := c.cert.Certify(check) func (dc *DynamicCertifier) Certify(check Commit) error {
err := dc.cert.Certify(check)
if err == nil { if err == nil {
// update last seen height if input is valid // update last seen height if input is valid
c.lastHeight = check.Height() dc.lastHeight = check.Height()
} }
return err return err
} }
@ -65,15 +67,15 @@ func (c *Dynamic) Certify(check Commit) error {
// the certifying validator set if safe to do so. // the certifying validator set if safe to do so.
// //
// Returns an error if update is impossible (invalid proof or IsTooMuchChangeErr) // Returns an error if update is impossible (invalid proof or IsTooMuchChangeErr)
func (c *Dynamic) Update(fc FullCommit) error { func (dc *DynamicCertifier) Update(fc FullCommit) error {
// ignore all checkpoints in the past -> only to the future // ignore all checkpoints in the past -> only to the future
h := fc.Height() h := fc.Height()
if h <= c.lastHeight { if h <= dc.lastHeight {
return liteErr.ErrPastTime() return liteErr.ErrPastTime()
} }
// first, verify if the input is self-consistent.... // first, verify if the input is self-consistent....
err := fc.ValidateBasic(c.ChainID()) err := fc.ValidateBasic(dc.ChainID())
if err != nil { if err != nil {
return err return err
} }
@ -82,14 +84,13 @@ func (c *Dynamic) Update(fc FullCommit) error {
// would be approved by the currently known validator set // would be approved by the currently known validator set
// as well as the new set // as well as the new set
commit := fc.Commit.Commit commit := fc.Commit.Commit
err = c.Validators().VerifyCommitAny(fc.Validators, c.ChainID(), err = dc.Validators().VerifyCommitAny(fc.Validators, dc.ChainID(), commit.BlockID, h, commit)
commit.BlockID, h, commit)
if err != nil { if err != nil {
return liteErr.ErrTooMuchChange() return liteErr.ErrTooMuchChange()
} }
// looks good, we can update // looks good, we can update
c.cert = NewStatic(c.ChainID(), fc.Validators) dc.cert = NewStaticCertifier(dc.ChainID(), fc.Validators)
c.lastHeight = h dc.lastHeight = h
return nil return nil
} }

View File

@ -23,7 +23,7 @@ func TestDynamicCert(t *testing.T) {
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
// and a certifier based on our known set // and a certifier based on our known set
chainID := "test-dyno" chainID := "test-dyno"
cert := lite.NewDynamic(chainID, vals, 0) cert := lite.NewDynamicCertifier(chainID, vals, 0)
cases := []struct { cases := []struct {
keys lite.ValKeys keys lite.ValKeys
@ -67,7 +67,7 @@ func TestDynamicUpdate(t *testing.T) {
chainID := "test-dyno-up" chainID := "test-dyno-up"
keys := lite.GenValKeys(5) keys := lite.GenValKeys(5)
vals := keys.ToValidators(20, 0) vals := keys.ToValidators(20, 0)
cert := lite.NewDynamic(chainID, vals, 40) cert := lite.NewDynamicCertifier(chainID, vals, 40)
// one valid block to give us a sense of time // one valid block to give us a sense of time
h := int64(100) h := int64(100)

View File

@ -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)
}

163
lite/inquiring_certifier.go Normal file
View File

@ -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)
}

View File

@ -32,18 +32,20 @@ func TestInquirerValidPath(t *testing.T) {
vals := keys.ToValidators(vote, 0) vals := keys.ToValidators(vote, 0)
h := int64(20 + 10*i) h := int64(20 + 10*i)
appHash := []byte(fmt.Sprintf("h=%d", h)) appHash := []byte(fmt.Sprintf("h=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys)) commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0,
len(keys))
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert := lite.NewInquiring(chainID, commits[0], trust, source) cert, err := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
require.Nil(err)
// this should fail validation.... // this should fail validation....
commit := commits[count-1].Commit commit := commits[count-1].Commit
err := cert.Certify(commit) err = cert.Certify(commit)
require.NotNil(err) require.NotNil(err)
// add a few seed in the middle should be insufficient // adding a few commits in the middle should be insufficient
for i := 10; i < 13; i++ { for i := 10; i < 13; i++ {
err := source.StoreCommit(commits[i]) err := source.StoreCommit(commits[i])
require.Nil(err) require.Nil(err)
@ -81,11 +83,12 @@ func TestInquirerMinimalPath(t *testing.T) {
h := int64(5 + 10*i) h := int64(5 + 10*i)
appHash := []byte(fmt.Sprintf("h=%d", h)) appHash := []byte(fmt.Sprintf("h=%d", h))
resHash := []byte(fmt.Sprintf("res=%d", h)) resHash := []byte(fmt.Sprintf("res=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys)) commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0,
len(keys))
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert := lite.NewInquiring(chainID, commits[0], trust, source) cert, _ := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
// this should fail validation.... // this should fail validation....
commit := commits[count-1].Commit commit := commits[count-1].Commit
@ -130,11 +133,12 @@ func TestInquirerVerifyHistorical(t *testing.T) {
h := int64(20 + 10*i) h := int64(20 + 10*i)
appHash := []byte(fmt.Sprintf("h=%d", h)) appHash := []byte(fmt.Sprintf("h=%d", h))
resHash := []byte(fmt.Sprintf("res=%d", h)) resHash := []byte(fmt.Sprintf("res=%d", h))
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0, len(keys)) commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0,
len(keys))
} }
// initialize a certifier with the initial state // initialize a certifier with the initial state
cert := lite.NewInquiring(chainID, commits[0], trust, source) cert, _ := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
// store a few commits as trust // store a few commits as trust
for _, i := range []int{2, 5} { for _, i := range []int{2, 5} {

View File

@ -105,7 +105,7 @@ func BenchmarkCertifyCommitSec100(b *testing.B) {
func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) { func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) {
chainID := "bench-certify" chainID := "bench-certify"
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
cert := lite.NewStatic(chainID, vals) cert := lite.NewStaticCertifier(chainID, vals)
check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 0, len(keys)) check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 0, len(keys))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
err := cert.Certify(check) err := cert.Certify(check)

View File

@ -6,7 +6,7 @@ import (
"github.com/tendermint/tendermint/lite/files" "github.com/tendermint/tendermint/lite/files"
) )
func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) { func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.InquiringCertifier, error) {
trust := lite.NewCacheProvider( trust := lite.NewCacheProvider(
lite.NewMemStoreProvider(), lite.NewMemStoreProvider(),
files.NewProvider(rootDir), files.NewProvider(rootDir),
@ -25,6 +25,11 @@ func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.Inquiring, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
cert := lite.NewInquiring(chainID, fc, trust, source)
cert, err := lite.NewInquiringCertifier(chainID, fc, trust, source)
if err != nil {
return nil, err
}
return cert, nil return cert, nil
} }

View File

@ -18,7 +18,11 @@ const (
// set up the rpc routes to proxy via the given client, // set up the rpc routes to proxy via the given client,
// and start up an http/rpc server on the location given by bind (eg. :1234) // and start up an http/rpc server on the location given by bind (eg. :1234)
func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error { func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error {
c.Start() err := c.Start()
if err != nil {
return err
}
r := RPCRoutes(c) r := RPCRoutes(c)
// build the handler... // build the handler...
@ -30,7 +34,7 @@ func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger) error
core.SetLogger(logger) core.SetLogger(logger)
mux.HandleFunc(wsEndpoint, wm.WebsocketHandler) mux.HandleFunc(wsEndpoint, wm.WebsocketHandler)
_, err := rpc.StartHTTPServer(listenAddr, mux, logger) _, err = rpc.StartHTTPServer(listenAddr, mux, logger)
return err return err
} }

View File

@ -51,7 +51,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
// make sure the proof is the proper height // make sure the proof is the proper height
if resp.IsErr() { if resp.IsErr() {
err = errors.Errorf("Query error %d: %d", resp.Code) err = errors.Errorf("Query error for key %d: %d", key, resp.Code)
return nil, nil, err return nil, nil, err
} }
if len(resp.Key) == 0 || len(resp.Proof) == 0 { if len(resp.Key) == 0 || len(resp.Proof) == 0 {
@ -79,7 +79,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, "Couldn't verify proof") return nil, nil, errors.Wrap(err, "Couldn't verify proof")
} }
return &ctypes.ResultABCIQuery{resp}, eproof, nil return &ctypes.ResultABCIQuery{Response: resp}, eproof, nil
} }
// The key wasn't found, construct a proof of non-existence. // The key wasn't found, construct a proof of non-existence.
@ -93,13 +93,12 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, "Couldn't verify proof") return nil, nil, errors.Wrap(err, "Couldn't verify proof")
} }
return &ctypes.ResultABCIQuery{resp}, aproof, ErrNoData() return &ctypes.ResultABCIQuery{Response: resp}, aproof, ErrNoData()
} }
// GetCertifiedCommit gets the signed header for a given height // GetCertifiedCommit gets the signed header for a given height
// and certifies it. Returns error if unable to get a proven header. // and certifies it. Returns error if unable to get a proven header.
func GetCertifiedCommit(h int64, node rpcclient.Client, func GetCertifiedCommit(h int64, node rpcclient.Client, cert lite.Certifier) (lite.Commit, error) {
cert lite.Certifier) (empty lite.Commit, err error) {
// FIXME: cannot use cert.GetByHeight for now, as it also requires // FIXME: cannot use cert.GetByHeight for now, as it also requires
// Validators and will fail on querying tendermint for non-current height. // Validators and will fail on querying tendermint for non-current height.
@ -107,14 +106,18 @@ func GetCertifiedCommit(h int64, node rpcclient.Client,
rpcclient.WaitForHeight(node, h, nil) rpcclient.WaitForHeight(node, h, nil)
cresp, err := node.Commit(&h) cresp, err := node.Commit(&h)
if err != nil { if err != nil {
return return lite.Commit{}, err
} }
commit := client.CommitFromResult(cresp)
commit := client.CommitFromResult(cresp)
// validate downloaded checkpoint with our request and trust store. // validate downloaded checkpoint with our request and trust store.
if commit.Height() != h { if commit.Height() != h {
return empty, certerr.ErrHeightMismatch(h, commit.Height()) return lite.Commit{}, certerr.ErrHeightMismatch(h, commit.Height())
} }
err = cert.Certify(commit)
if err = cert.Certify(commit); err != nil {
return lite.Commit{}, err
}
return commit, nil return commit, nil
} }

View File

@ -58,7 +58,7 @@ func _TestAppProofs(t *testing.T) {
source := certclient.NewProvider(cl) source := certclient.NewProvider(cl)
seed, err := source.GetByHeight(brh - 2) seed, err := source.GetByHeight(brh - 2)
require.NoError(err, "%+v", err) require.NoError(err, "%+v", err)
cert := lite.NewStatic("my-chain", seed.Validators) cert := lite.NewStaticCertifier("my-chain", seed.Validators)
client.WaitForHeight(cl, 3, nil) client.WaitForHeight(cl, 3, nil)
latest, err := source.LatestCommit() latest, err := source.LatestCommit()
@ -117,7 +117,7 @@ func _TestTxProofs(t *testing.T) {
source := certclient.NewProvider(cl) source := certclient.NewProvider(cl)
seed, err := source.GetByHeight(brh - 2) seed, err := source.GetByHeight(brh - 2)
require.NoError(err, "%+v", err) require.NoError(err, "%+v", err)
cert := lite.NewStatic("my-chain", seed.Validators) cert := lite.NewStaticCertifier("my-chain", seed.Validators)
// First let's make sure a bogus transaction hash returns a valid non-existence proof. // First let's make sure a bogus transaction hash returns a valid non-existence proof.
key := types.Tx([]byte("bogus")).Hash() key := types.Tx([]byte("bogus")).Hash()
@ -136,5 +136,4 @@ func _TestTxProofs(t *testing.T) {
commit, err := GetCertifiedCommit(br.Height, cl, cert) commit, err := GetCertifiedCommit(br.Height, cl, cert)
require.Nil(err, "%+v", err) require.Nil(err, "%+v", err)
require.Equal(res.Proof.RootHash, commit.Header.DataHash) require.Equal(res.Proof.RootHash, commit.Header.DataHash)
} }

View File

@ -15,14 +15,14 @@ var _ rpcclient.Client = Wrapper{}
// provable before passing it along. Allows you to make any rpcclient fully secure. // provable before passing it along. Allows you to make any rpcclient fully secure.
type Wrapper struct { type Wrapper struct {
rpcclient.Client rpcclient.Client
cert *lite.Inquiring cert *lite.InquiringCertifier
} }
// SecureClient uses a given certifier to wrap an connection to an untrusted // SecureClient uses a given certifier to wrap an connection to an untrusted
// host and return a cryptographically secure rpc client. // host and return a cryptographically secure rpc client.
// //
// If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface // If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface
func SecureClient(c rpcclient.Client, cert *lite.Inquiring) Wrapper { func SecureClient(c rpcclient.Client, cert *lite.InquiringCertifier) Wrapper {
wrap := Wrapper{c, cert} wrap := Wrapper{c, cert}
// TODO: no longer possible as no more such interface exposed.... // TODO: no longer possible as no more such interface exposed....
// if we wrap http client, then we can swap out the event switch to filter // if we wrap http client, then we can swap out the event switch to filter
@ -34,7 +34,9 @@ func SecureClient(c rpcclient.Client, cert *lite.Inquiring) Wrapper {
} }
// ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof // ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof
func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes, opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { func (w Wrapper) ABCIQueryWithOptions(path string, data data.Bytes,
opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
res, _, err := GetWithProofOptions(path, data, opts, w.Client, w.cert) res, _, err := GetWithProofOptions(path, data, opts, w.Client, w.cert)
return res, err return res, err
} }

View File

@ -10,62 +10,64 @@ import (
liteErr "github.com/tendermint/tendermint/lite/errors" liteErr "github.com/tendermint/tendermint/lite/errors"
) )
var _ Certifier = &Static{} var _ Certifier = (*StaticCertifier)(nil)
// Static assumes a static set of validators, set on // StaticCertifier assumes a static set of validators, set on
// initilization and checks against them. // initilization and checks against them.
// The signatures on every header is checked for > 2/3 votes // The signatures on every header is checked for > 2/3 votes
// against the known validator set upon Certify // against the known validator set upon Certify
// //
// Good for testing or really simple chains. Building block // Good for testing or really simple chains. Building block
// to support real-world functionality. // to support real-world functionality.
type Static struct { type StaticCertifier struct {
chainID string chainID string
vSet *types.ValidatorSet vSet *types.ValidatorSet
vhash []byte vhash []byte
} }
// NewStatic returns a new certifier with a static validator set. // NewStaticCertifier returns a new certifier with a static validator set.
func NewStatic(chainID string, vals *types.ValidatorSet) *Static { func NewStaticCertifier(chainID string, vals *types.ValidatorSet) *StaticCertifier {
return &Static{ return &StaticCertifier{
chainID: chainID, chainID: chainID,
vSet: vals, vSet: vals,
} }
} }
// ChainID returns the chain id. // ChainID returns the chain id.
func (c *Static) ChainID() string { // Implements Certifier.
return c.chainID func (sc *StaticCertifier) ChainID() string {
return sc.chainID
} }
// Validators returns the validator set. // Validators returns the validator set.
func (c *Static) Validators() *types.ValidatorSet { func (sc *StaticCertifier) Validators() *types.ValidatorSet {
return c.vSet return sc.vSet
} }
// Hash returns the hash of the validator set. // Hash returns the hash of the validator set.
func (c *Static) Hash() []byte { func (sc *StaticCertifier) Hash() []byte {
if len(c.vhash) == 0 { if len(sc.vhash) == 0 {
c.vhash = c.vSet.Hash() sc.vhash = sc.vSet.Hash()
} }
return c.vhash return sc.vhash
} }
// Certify makes sure that the commit is valid. // Certify makes sure that the commit is valid.
func (c *Static) Certify(commit Commit) error { // Implements Certifier.
func (sc *StaticCertifier) Certify(commit Commit) error {
// do basic sanity checks // do basic sanity checks
err := commit.ValidateBasic(c.chainID) err := commit.ValidateBasic(sc.chainID)
if err != nil { if err != nil {
return err return err
} }
// make sure it has the same validator set we have (static means static) // make sure it has the same validator set we have (static means static)
if !bytes.Equal(c.Hash(), commit.Header.ValidatorsHash) { if !bytes.Equal(sc.Hash(), commit.Header.ValidatorsHash) {
return liteErr.ErrValidatorsChanged() return liteErr.ErrValidatorsChanged()
} }
// then make sure we have the proper signatures for this // then make sure we have the proper signatures for this
err = c.vSet.VerifyCommit(c.chainID, commit.Commit.BlockID, err = sc.vSet.VerifyCommit(sc.chainID, commit.Commit.BlockID,
commit.Header.Height, commit.Commit) commit.Header.Height, commit.Commit)
return errors.WithStack(err) return errors.WithStack(err)
} }

View File

@ -21,7 +21,7 @@ func TestStaticCert(t *testing.T) {
vals := keys.ToValidators(20, 10) vals := keys.ToValidators(20, 10)
// and a certifier based on our known set // and a certifier based on our known set
chainID := "test-static" chainID := "test-static"
cert := lite.NewStatic(chainID, vals) cert := lite.NewStaticCertifier(chainID, vals)
cases := []struct { cases := []struct {
keys lite.ValKeys keys lite.ValKeys

View File

@ -3,7 +3,6 @@ package mempool
import ( import (
"bytes" "bytes"
"container/list" "container/list"
"fmt"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -49,7 +48,7 @@ TODO: Better handle abci client errors. (make it automatically handle connection
*/ */
const cacheSize = 100000 var ErrTxInCache = errors.New("Tx already exists in cache")
// Mempool is an ordered in-memory pool for transactions before they are proposed in a consensus // Mempool is an ordered in-memory pool for transactions before they are proposed in a consensus
// round. Transaction validity is checked using the CheckTx abci message before the transaction is // round. Transaction validity is checked using the CheckTx abci message before the transaction is
@ -92,9 +91,8 @@ func NewMempool(config *cfg.MempoolConfig, proxyAppConn proxy.AppConnMempool, he
recheckCursor: nil, recheckCursor: nil,
recheckEnd: nil, recheckEnd: nil,
logger: log.NewNopLogger(), logger: log.NewNopLogger(),
cache: newTxCache(cacheSize), cache: newTxCache(config.CacheSize),
} }
mempool.initWAL()
proxyAppConn.SetResponseCallback(mempool.resCb) proxyAppConn.SetResponseCallback(mempool.resCb)
return mempool return mempool
} }
@ -131,7 +129,7 @@ func (mem *Mempool) CloseWAL() bool {
return true return true
} }
func (mem *Mempool) initWAL() { func (mem *Mempool) InitWAL() {
walDir := mem.config.WalDir() walDir := mem.config.WalDir()
if walDir != "" { if walDir != "" {
err := cmn.EnsureDir(walDir, 0700) err := cmn.EnsureDir(walDir, 0700)
@ -161,6 +159,12 @@ func (mem *Mempool) Size() int {
return mem.txs.Len() return mem.txs.Len()
} }
// Flushes the mempool connection to ensure async resCb calls are done e.g.
// from CheckTx.
func (mem *Mempool) FlushAppConn() error {
return mem.proxyAppConn.FlushSync()
}
// Flush removes all transactions from the mempool and cache // Flush removes all transactions from the mempool and cache
func (mem *Mempool) Flush() { func (mem *Mempool) Flush() {
mem.proxyMtx.Lock() mem.proxyMtx.Lock()
@ -192,7 +196,7 @@ func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) {
// CACHE // CACHE
if mem.cache.Exists(tx) { if mem.cache.Exists(tx) {
return fmt.Errorf("Tx already exists in cache") return ErrTxInCache
} }
mem.cache.Push(tx) mem.cache.Push(tx)
// END CACHE // END CACHE
@ -349,9 +353,6 @@ func (mem *Mempool) collectTxs(maxTxs int) types.Txs {
// NOTE: this should be called *after* block is committed by consensus. // NOTE: this should be called *after* block is committed by consensus.
// NOTE: unsafe; Lock/Unlock must be managed by caller // NOTE: unsafe; Lock/Unlock must be managed by caller
func (mem *Mempool) Update(height int64, txs types.Txs) error { func (mem *Mempool) Update(height int64, txs types.Txs) error {
if err := mem.proxyAppConn.FlushSync(); err != nil { // To flush async resCb calls e.g. from CheckTx
return err
}
// First, create a lookup map of txns in new txs. // First, create a lookup map of txns in new txs.
txsMap := make(map[string]struct{}) txsMap := make(map[string]struct{})
for _, tx := range txs { for _, tx := range txs {
@ -449,7 +450,7 @@ func newTxCache(cacheSize int) *txCache {
// Reset resets the txCache to empty. // Reset resets the txCache to empty.
func (cache *txCache) Reset() { func (cache *txCache) Reset() {
cache.mtx.Lock() cache.mtx.Lock()
cache.map_ = make(map[string]struct{}, cacheSize) cache.map_ = make(map[string]struct{}, cache.size)
cache.list.Init() cache.list.Init()
cache.mtx.Unlock() cache.mtx.Unlock()
} }

View File

@ -236,12 +236,13 @@ func TestMempoolCloseWAL(t *testing.T) {
require.Equal(t, 0, len(m1), "no matches yet") require.Equal(t, 0, len(m1), "no matches yet")
// 3. Create the mempool // 3. Create the mempool
wcfg := *(cfg.DefaultMempoolConfig()) wcfg := cfg.DefaultMempoolConfig()
wcfg.RootDir = rootDir wcfg.RootDir = rootDir
app := dummy.NewDummyApplication() app := dummy.NewDummyApplication()
cc := proxy.NewLocalClientCreator(app) cc := proxy.NewLocalClientCreator(app)
appConnMem, _ := cc.NewABCIClient() appConnMem, _ := cc.NewABCIClient()
mempool := NewMempool(&wcfg, appConnMem, 10) mempool := NewMempool(wcfg, appConnMem, 10)
mempool.InitWAL()
// 4. Ensure that the directory contains the WAL file // 4. Ensure that the directory contains the WAL file
m2, err := filepath.Glob(filepath.Join(rootDir, "*")) m2, err := filepath.Glob(filepath.Join(rootDir, "*"))

Some files were not shown because too many files have changed in this diff Show More