mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-21 15:21:57 +00:00
Compare commits
46 Commits
v0.20.0-rc
...
v0.19.8
Author | SHA1 | Date | |
---|---|---|---|
|
5727916c5b | ||
|
876c8f14e7 | ||
|
fedd07c522 | ||
|
3fa734ef5a | ||
|
cd6bfdc42f | ||
|
98b0c51b5f | ||
|
c777be256a | ||
|
d66f8bf829 | ||
|
1c643701f5 | ||
|
0b0290bdb2 | ||
|
a4779fdf51 | ||
|
7030d5c2a7 | ||
|
f34d1009c4 | ||
|
0e3dc32b3d | ||
|
d292fa4541 | ||
|
3255c076e5 | ||
|
978277a4c1 | ||
|
58eb76f34d | ||
|
a017f2fdd4 | ||
|
aaaa5f23e2 | ||
|
178e357d7f | ||
|
683b527534 | ||
|
d584e03427 | ||
|
d454b1b25f | ||
|
432f21452d | ||
|
f0ce8b3883 | ||
|
3da5198631 | ||
|
252a0a392b | ||
|
f7106bfb39 | ||
|
2a517ac98c | ||
|
b542dce2e1 | ||
|
82ded582f2 | ||
|
e0d4fe2dba | ||
|
83c6f2864d | ||
|
33ec8cb609 | ||
|
43a652bcbf | ||
|
094a40084d | ||
|
eec9f142b5 | ||
|
5796e879b9 | ||
|
846ca5ce56 | ||
|
fd6021876b | ||
|
2763c8539c | ||
|
1c0bfe1158 | ||
|
1e87ef7f75 | ||
|
c8be091d4a | ||
|
f55725ebfa |
@@ -133,18 +133,21 @@ jobs:
|
|||||||
key: v1-pkg-cache
|
key: v1-pkg-cache
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||||
|
- run: mkdir -p /tmp/logs
|
||||||
- run:
|
- run:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
command: |
|
command: |
|
||||||
for pkg in $(go list github.com/tendermint/tendermint/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
|
for pkg in $(go list github.com/tendermint/tendermint/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
|
||||||
id=$(basename "$pkg")
|
id=$(basename "$pkg")
|
||||||
|
|
||||||
go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg"
|
GOCACHE=off go test -v -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
|
||||||
done
|
done
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: /tmp/workspace
|
root: /tmp/workspace
|
||||||
paths:
|
paths:
|
||||||
- "profiles/*"
|
- "profiles/*"
|
||||||
|
- store_artifacts:
|
||||||
|
path: /tmp/logs
|
||||||
|
|
||||||
test_persistence:
|
test_persistence:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
@@ -196,9 +199,6 @@ workflows:
|
|||||||
test-suite:
|
test-suite:
|
||||||
jobs:
|
jobs:
|
||||||
- setup_dependencies
|
- setup_dependencies
|
||||||
- build_slate:
|
|
||||||
requires:
|
|
||||||
- setup_dependencies
|
|
||||||
- setup_abci:
|
- setup_abci:
|
||||||
requires:
|
requires:
|
||||||
- setup_dependencies
|
- setup_dependencies
|
||||||
|
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,23 +1,51 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.20.0
|
## 0.19.8
|
||||||
|
|
||||||
|
*June 4th, 2018*
|
||||||
|
|
||||||
|
BREAKING:
|
||||||
|
|
||||||
|
- [p2p] Remove `auth_enc` config option, peer connections are always auth
|
||||||
|
encrypted. Technically a breaking change but seems no one was using it and
|
||||||
|
arguably a bug fix :)
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [mempool] Fix deadlock under high load when `skip_timeout_commit=true` and
|
||||||
|
`create_empty_blocks=false`
|
||||||
|
|
||||||
|
## 0.19.7
|
||||||
|
|
||||||
|
*May 31st, 2018*
|
||||||
|
|
||||||
BREAKING:
|
BREAKING:
|
||||||
|
|
||||||
- [libs/pubsub] TagMap#Get returns a string value
|
- [libs/pubsub] TagMap#Get returns a string value
|
||||||
- [libs/pubsub] NewTagMap accepts a map of strings
|
- [libs/pubsub] NewTagMap accepts a map of strings
|
||||||
|
|
||||||
## 0.19.6
|
|
||||||
|
|
||||||
FEATURES
|
FEATURES
|
||||||
|
|
||||||
- [rpc] the RPC documentation is now published to https://tendermint.github.io/slate
|
- [rpc] the RPC documentation is now published to https://tendermint.github.io/slate
|
||||||
|
- [p2p] AllowDuplicateIP config option to refuse connections from same IP.
|
||||||
|
- true by default for now, false by default in next breaking release
|
||||||
|
- [docs] Add docs for query, tx indexing, events, pubsub
|
||||||
|
- [docs] Add some notes about running Tendermint in production
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
|
|
||||||
- [consensus] consensus reactor now receives events from a separate event bus,
|
- [consensus] consensus reactor now receives events from a separate event bus,
|
||||||
which is not dependant on external RPC load
|
which is not dependant on external RPC load
|
||||||
- [consensus/wal] do not look for height in older files if we've seen height - 1
|
- [consensus/wal] do not look for height in older files if we've seen height - 1
|
||||||
|
- [docs] Various cleanup and link fixes
|
||||||
|
|
||||||
|
## 0.19.6
|
||||||
|
|
||||||
|
*May 29th, 2018*
|
||||||
|
|
||||||
|
BUG FIXES
|
||||||
|
|
||||||
|
- [blockchain] Fix fast-sync deadlock during high peer turnover
|
||||||
|
|
||||||
## 0.19.5
|
## 0.19.5
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
@@ -66,11 +67,13 @@ type BlockPool struct {
|
|||||||
// block requests
|
// block requests
|
||||||
requesters map[int64]*bpRequester
|
requesters map[int64]*bpRequester
|
||||||
height int64 // the lowest key in requesters.
|
height int64 // the lowest key in requesters.
|
||||||
numPending int32 // number of requests pending assignment or block response
|
|
||||||
// peers
|
// peers
|
||||||
peers map[p2p.ID]*bpPeer
|
peers map[p2p.ID]*bpPeer
|
||||||
maxPeerHeight int64
|
maxPeerHeight int64
|
||||||
|
|
||||||
|
// atomic
|
||||||
|
numPending int32 // number of requests pending assignment or block response
|
||||||
|
|
||||||
requestsCh chan<- BlockRequest
|
requestsCh chan<- BlockRequest
|
||||||
errorsCh chan<- peerError
|
errorsCh chan<- peerError
|
||||||
}
|
}
|
||||||
@@ -151,7 +154,7 @@ func (pool *BlockPool) GetStatus() (height int64, numPending int32, lenRequester
|
|||||||
pool.mtx.Lock()
|
pool.mtx.Lock()
|
||||||
defer pool.mtx.Unlock()
|
defer pool.mtx.Unlock()
|
||||||
|
|
||||||
return pool.height, pool.numPending, len(pool.requesters)
|
return pool.height, atomic.LoadInt32(&pool.numPending), len(pool.requesters)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: relax conditions, prevent abuse.
|
// TODO: relax conditions, prevent abuse.
|
||||||
@@ -245,7 +248,7 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if requester.setBlock(block, peerID) {
|
if requester.setBlock(block, peerID) {
|
||||||
pool.numPending--
|
atomic.AddInt32(&pool.numPending, -1)
|
||||||
peer := pool.peers[peerID]
|
peer := pool.peers[peerID]
|
||||||
if peer != nil {
|
if peer != nil {
|
||||||
peer.decrPending(blockSize)
|
peer.decrPending(blockSize)
|
||||||
@@ -291,10 +294,7 @@ func (pool *BlockPool) RemovePeer(peerID p2p.ID) {
|
|||||||
func (pool *BlockPool) removePeer(peerID p2p.ID) {
|
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 {
|
requester.redo()
|
||||||
pool.numPending++
|
|
||||||
}
|
|
||||||
go requester.redo() // pick another peer and ...
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(pool.peers, peerID)
|
delete(pool.peers, peerID)
|
||||||
@@ -332,7 +332,7 @@ func (pool *BlockPool) makeNextRequester() {
|
|||||||
// request.SetLogger(pool.Logger.With("height", nextHeight))
|
// request.SetLogger(pool.Logger.With("height", nextHeight))
|
||||||
|
|
||||||
pool.requesters[nextHeight] = request
|
pool.requesters[nextHeight] = request
|
||||||
pool.numPending++
|
atomic.AddInt32(&pool.numPending, 1)
|
||||||
|
|
||||||
err := request.Start()
|
err := request.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -360,7 +360,7 @@ func (pool *BlockPool) sendError(err error, peerID p2p.ID) {
|
|||||||
|
|
||||||
// unused by tendermint; left for debugging purposes
|
// unused by tendermint; left for debugging purposes
|
||||||
func (pool *BlockPool) debug() string {
|
func (pool *BlockPool) debug() string {
|
||||||
pool.mtx.Lock() // Lock
|
pool.mtx.Lock()
|
||||||
defer pool.mtx.Unlock()
|
defer pool.mtx.Unlock()
|
||||||
|
|
||||||
str := ""
|
str := ""
|
||||||
@@ -466,8 +466,8 @@ func newBPRequester(pool *BlockPool, height int64) *bpRequester {
|
|||||||
bpr := &bpRequester{
|
bpr := &bpRequester{
|
||||||
pool: pool,
|
pool: pool,
|
||||||
height: height,
|
height: height,
|
||||||
gotBlockCh: make(chan struct{}),
|
gotBlockCh: make(chan struct{}, 1),
|
||||||
redoCh: make(chan struct{}),
|
redoCh: make(chan struct{}, 1),
|
||||||
|
|
||||||
peerID: "",
|
peerID: "",
|
||||||
block: nil,
|
block: nil,
|
||||||
@@ -481,7 +481,7 @@ func (bpr *bpRequester) OnStart() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the peer matches
|
// Returns true if the peer matches and block doesn't already exist.
|
||||||
func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) 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 {
|
||||||
@@ -491,7 +491,10 @@ func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool {
|
|||||||
bpr.block = block
|
bpr.block = block
|
||||||
bpr.mtx.Unlock()
|
bpr.mtx.Unlock()
|
||||||
|
|
||||||
bpr.gotBlockCh <- struct{}{}
|
select {
|
||||||
|
case bpr.gotBlockCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,17 +510,27 @@ func (bpr *bpRequester) getPeerID() p2p.ID {
|
|||||||
return bpr.peerID
|
return bpr.peerID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called from the requestRoutine, upon redo().
|
||||||
func (bpr *bpRequester) reset() {
|
func (bpr *bpRequester) reset() {
|
||||||
bpr.mtx.Lock()
|
bpr.mtx.Lock()
|
||||||
|
defer bpr.mtx.Unlock()
|
||||||
|
|
||||||
|
if bpr.block != nil {
|
||||||
|
atomic.AddInt32(&bpr.pool.numPending, 1)
|
||||||
|
}
|
||||||
|
|
||||||
bpr.peerID = ""
|
bpr.peerID = ""
|
||||||
bpr.block = nil
|
bpr.block = nil
|
||||||
bpr.mtx.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tells bpRequester to pick another peer and try again.
|
// Tells bpRequester to pick another peer and try again.
|
||||||
// NOTE: blocking
|
// NOTE: Nonblocking, and does nothing if another redo
|
||||||
|
// was already requested.
|
||||||
func (bpr *bpRequester) redo() {
|
func (bpr *bpRequester) redo() {
|
||||||
bpr.redoCh <- struct{}{}
|
select {
|
||||||
|
case bpr.redoCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Responsible for making more requests as necessary
|
// Responsible for making more requests as necessary
|
||||||
@@ -546,17 +559,8 @@ OUTER_LOOP:
|
|||||||
|
|
||||||
// Send request and wait.
|
// Send request and wait.
|
||||||
bpr.pool.sendRequest(bpr.height, peer.id)
|
bpr.pool.sendRequest(bpr.height, peer.id)
|
||||||
select {
|
WAIT_LOOP:
|
||||||
case <-bpr.pool.Quit():
|
for {
|
||||||
bpr.Stop()
|
|
||||||
return
|
|
||||||
case <-bpr.Quit():
|
|
||||||
return
|
|
||||||
case <-bpr.redoCh:
|
|
||||||
bpr.reset()
|
|
||||||
continue OUTER_LOOP // When peer is removed
|
|
||||||
case <-bpr.gotBlockCh:
|
|
||||||
// We got the block, now see if it's good.
|
|
||||||
select {
|
select {
|
||||||
case <-bpr.pool.Quit():
|
case <-bpr.pool.Quit():
|
||||||
bpr.Stop()
|
bpr.Stop()
|
||||||
@@ -566,6 +570,10 @@ OUTER_LOOP:
|
|||||||
case <-bpr.redoCh:
|
case <-bpr.redoCh:
|
||||||
bpr.reset()
|
bpr.reset()
|
||||||
continue OUTER_LOOP
|
continue OUTER_LOOP
|
||||||
|
case <-bpr.gotBlockCh:
|
||||||
|
// We got a block!
|
||||||
|
// Continue the for-loop and wait til Quit.
|
||||||
|
continue WAIT_LOOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -287,11 +287,11 @@ type P2PConfig struct {
|
|||||||
// Does not work if the peer-exchange reactor is disabled.
|
// Does not work if the peer-exchange reactor is disabled.
|
||||||
SeedMode bool `mapstructure:"seed_mode"`
|
SeedMode bool `mapstructure:"seed_mode"`
|
||||||
|
|
||||||
// Authenticated encryption
|
|
||||||
AuthEnc bool `mapstructure:"auth_enc"`
|
|
||||||
|
|
||||||
// Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
// Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
PrivatePeerIDs string `mapstructure:"private_peer_ids"`
|
PrivatePeerIDs string `mapstructure:"private_peer_ids"`
|
||||||
|
|
||||||
|
// Toggle to disable guard against peers connecting from the same ip.
|
||||||
|
AllowDuplicateIP bool `mapstructure:"allow_duplicate_ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultP2PConfig returns a default configuration for the peer-to-peer layer
|
// DefaultP2PConfig returns a default configuration for the peer-to-peer layer
|
||||||
@@ -307,7 +307,7 @@ func DefaultP2PConfig() *P2PConfig {
|
|||||||
RecvRate: 512000, // 500 kB/s
|
RecvRate: 512000, // 500 kB/s
|
||||||
PexReactor: true,
|
PexReactor: true,
|
||||||
SeedMode: false,
|
SeedMode: false,
|
||||||
AuthEnc: true,
|
AllowDuplicateIP: true, // so non-breaking yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +317,7 @@ func TestP2PConfig() *P2PConfig {
|
|||||||
cfg.ListenAddress = "tcp://0.0.0.0:36656"
|
cfg.ListenAddress = "tcp://0.0.0.0:36656"
|
||||||
cfg.SkipUPNP = true
|
cfg.SkipUPNP = true
|
||||||
cfg.FlushThrottleTimeout = 10
|
cfg.FlushThrottleTimeout = 10
|
||||||
|
cfg.AllowDuplicateIP = true
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -165,9 +165,6 @@ pex = {{ .P2P.PexReactor }}
|
|||||||
# Does not work if the peer-exchange reactor is disabled.
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
seed_mode = {{ .P2P.SeedMode }}
|
seed_mode = {{ .P2P.SeedMode }}
|
||||||
|
|
||||||
# Authenticated encryption
|
|
||||||
auth_enc = {{ .P2P.AuthEnc }}
|
|
||||||
|
|
||||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
private_peer_ids = "{{ .P2P.PrivatePeerIDs }}"
|
private_peer_ids = "{{ .P2P.PrivatePeerIDs }}"
|
||||||
|
|
||||||
|
@@ -1,133 +1,42 @@
|
|||||||
Application Architecture Guide
|
Application Architecture Guide
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Overview
|
Here we provide a brief guide on the recommended architecture of a Tendermint blockchain
|
||||||
--------
|
application.
|
||||||
|
|
||||||
A blockchain application is more than the consensus engine and the
|
The following diagram provides a superb example:
|
||||||
transaction logic (eg. smart contracts, business logic) as implemented
|
|
||||||
in the ABCI app. There are also (mobile, web, desktop) clients that will
|
|
||||||
need to connect and make use of the app. We will assume for now that you
|
|
||||||
have a well designed transactions and database model, but maybe this
|
|
||||||
will be the topic of another article. This article is more interested in
|
|
||||||
various ways of setting up the "plumbing" and connecting these pieces,
|
|
||||||
and demonstrating some evolving best practices.
|
|
||||||
|
|
||||||
Security
|
https://drive.google.com/open?id=1yR2XpRi9YCY9H9uMfcw8-RMJpvDyvjz9
|
||||||
--------
|
|
||||||
|
|
||||||
A very important aspect when constructing a blockchain is security. The
|
The end-user application here is the Cosmos Voyager, at the bottom left.
|
||||||
consensus model can be DoSed (no consensus possible) by corrupting 1/3
|
Voyager communicates with a REST API exposed by a local Light-Client Daemon.
|
||||||
of the validators and exploited (writing arbitrary blocks) by corrupting
|
The Light-Client Daemon is an application specific program that communicates with
|
||||||
2/3 of the validators. So, while the security is not that of the
|
Tendermint nodes and verifies Tendermint light-client proofs through the Tendermint Core RPC.
|
||||||
"weakest link", you should take care that the "average link" is
|
The Tendermint Core process communicates with a local ABCI application, where the
|
||||||
sufficiently hardened.
|
user query or transaction is actually processed.
|
||||||
|
|
||||||
One big attack surface on the validators is the communication between
|
The ABCI application must be a deterministic result of the Tendermint consensus - any external influence
|
||||||
the ABCI app and the tendermint core. This should be highly protected.
|
on the application state that didn't come through Tendermint could cause a
|
||||||
Ideally, the app and the core are running on the same machine, so no
|
consensus failure. Thus *nothing* should communicate with the application except Tendermint via ABCI.
|
||||||
external agent can target the communication channel. You can use unix
|
|
||||||
sockets (with permissions preventing access from other users), or even
|
|
||||||
compile the two apps into one binary if the ABCI app is also writen in
|
|
||||||
go. If you are unable to do that due to language support, then the ABCI
|
|
||||||
app should bind a TCP connection to localhost (127.0.0.1), which is less
|
|
||||||
efficient and secure, but still not reachable from outside. If you must
|
|
||||||
run the ABCI app and tendermint core on separate machines, make sure you
|
|
||||||
have a secure communication channel (ssh tunnel?)
|
|
||||||
|
|
||||||
Now assuming, you have linked together your app and the core securely,
|
If the application is written in Go, it can be compiled into the Tendermint binary.
|
||||||
you must also make sure no one can get on the machine it is hosted on.
|
Otherwise, it should use a unix socket to communicate with Tendermint.
|
||||||
At this point it is basic network security. Run on a secure operating
|
If it's necessary to use TCP, extra care must be taken to encrypt and authenticate the connection.
|
||||||
system (SELinux?). Limit who has access to the machine (user accounts,
|
|
||||||
but also where the physical machine is hosted). Turn off all services
|
|
||||||
except for ssh, which should only be accessible by some well-guarded
|
|
||||||
public/private key pairs (no password). And maybe even firewall off
|
|
||||||
access to the ports used by the validators, so only known validators can
|
|
||||||
connect.
|
|
||||||
|
|
||||||
There was also a suggestion on slack from @jhon about compiling
|
All reads from the app happen through the Tendermint `/abci_query` endpoint.
|
||||||
everything together with a unikernel for more security, such as
|
All writes to the app happen through the Tendermint `/broadcast_tx_*` endpoints.
|
||||||
`Mirage <https://mirage.io>`__ or
|
|
||||||
`UNIK <https://github.com/emc-advanced-dev/unik>`__.
|
|
||||||
|
|
||||||
Connecting your client to the blockchain
|
The Light-Client Daemon is what provides light clients (end users) with nearly all the security of a full node.
|
||||||
----------------------------------------
|
It formats and broadcasts transactions, and verifies proofs of queries and transaction results.
|
||||||
|
Note that it need not be a daemon - the Light-Client logic could instead be implemented in the same process as the end-user application.
|
||||||
|
|
||||||
Tendermint Core RPC
|
Note for those ABCI applications with weaker security requirements, the functionality of the Light-Client Daemon can be moved
|
||||||
~~~~~~~~~~~~~~~~~~~
|
into the ABCI application process itself. That said, exposing the application process to anything besides Tendermint over ABCI
|
||||||
|
requires extreme caution, as all transactions, and possibly all queries, should still pass through Tendermint.
|
||||||
|
|
||||||
The concept is that the ABCI app is completely hidden from the outside
|
See the following for more extensive documentation:
|
||||||
world and only communicated through a tested and secured `interface
|
- [Interchain Standard for the Light-Client REST API](https://github.com/cosmos/cosmos-sdk/pull/1028)
|
||||||
exposed by the tendermint core <./specification/rpc.html>`__. This interface
|
- [Tendermint RPC Docs](https://tendermint.github.io/slate/)
|
||||||
exposes a lot of data on the block header and consensus process, which
|
- [Tendermint in Production](https://github.com/tendermint/tendermint/pull/1618)
|
||||||
is quite useful for externally verifying the system. It also includes
|
- [Tendermint Basics](https://tendermint.readthedocs.io/en/master/using-tendermint.html)
|
||||||
3(!) methods to broadcast a transaction (propose it for the blockchain,
|
- [ABCI spec](https://github.com/tendermint/abci/blob/master/specification.rst)
|
||||||
and possibly await a response). And one method to query app-specific
|
|
||||||
data from the ABCI application.
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
|
|
||||||
- Server code already written
|
|
||||||
- Access to block headers to validate merkle proofs (nice for light clients)
|
|
||||||
- Basic read/write functionality is supported
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
|
|
||||||
- Limited interface to app. All queries must be serialized into []byte (less expressive than JSON over HTTP) and there is no way to push data from ABCI app to the client (eg. notify me if account X receives a transaction)
|
|
||||||
|
|
||||||
Custom ABCI server
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This was proposed by @wolfposd on slack and demonstrated by
|
|
||||||
`TMChat <https://github.com/wolfposd/TMChat>`__, a sample app. The
|
|
||||||
concept is to write a custom server for your app (with typical REST
|
|
||||||
API/websockets/etc for easy use by a mobile app). This custom server is
|
|
||||||
in the same binary as the ABCI app and data store, so can easily react
|
|
||||||
to complex events there that involve understanding the data format (send
|
|
||||||
a message if my balance drops below 500). All "writes" sent to this
|
|
||||||
server are proxied via websocket/JSON-RPC to tendermint core. When they
|
|
||||||
come back as deliver\_tx over ABCI, they will be written to the data
|
|
||||||
store. For "reads", we can do any queries we wish that are supported by
|
|
||||||
our architecture, using any web technology that is useful. The general
|
|
||||||
architecture is shown in the following diagram:
|
|
||||||
|
|
||||||
.. figure:: assets/tm-application-example.png
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
|
|
||||||
- Separates application logic from blockchain logic
|
|
||||||
- Allows much richer, more flexible client-facing API
|
|
||||||
- Allows pub-sub, watching certain fields, etc.
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
|
|
||||||
- Access to ABCI app can be dangerous (be VERY careful not to write unless it comes from the validator node)
|
|
||||||
- No direct access to the blockchain headers to verify tx
|
|
||||||
- You must write your own API (but maybe that's a pro...)
|
|
||||||
|
|
||||||
Hybrid solutions
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Likely the least secure but most versatile. The client can access both
|
|
||||||
the tendermint node for all blockchain info, as well as a custom app
|
|
||||||
server, for complex queries and pub-sub on the abci app.
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
|
|
||||||
- All from both above solutions
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
|
|
||||||
- Even more complexity; even more attack vectors (less
|
|
||||||
security)
|
|
||||||
|
|
||||||
Scalability
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Read replica using non-validating nodes? They could forward transactions
|
|
||||||
to the validators (fewer connections, more security), and locally allow
|
|
||||||
all queries in any of the above configurations. Thus, while
|
|
||||||
transaction-processing speed is limited by the speed of the abci app and
|
|
||||||
the number of validators, one should be able to scale our read
|
|
||||||
performance to quite an extent (until the replication process drains too
|
|
||||||
many resources from the validator nodes).
|
|
||||||
|
@@ -103,9 +103,6 @@ pex = true
|
|||||||
# Does not work if the peer-exchange reactor is disabled.
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
seed_mode = false
|
seed_mode = false
|
||||||
|
|
||||||
# Authenticated encryption
|
|
||||||
auth_enc = true
|
|
||||||
|
|
||||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
private_peer_ids = ""
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
@@ -103,9 +103,6 @@ pex = true
|
|||||||
# Does not work if the peer-exchange reactor is disabled.
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
seed_mode = false
|
seed_mode = false
|
||||||
|
|
||||||
# Authenticated encryption
|
|
||||||
auth_enc = true
|
|
||||||
|
|
||||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
private_peer_ids = ""
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
@@ -103,9 +103,6 @@ pex = true
|
|||||||
# Does not work if the peer-exchange reactor is disabled.
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
seed_mode = false
|
seed_mode = false
|
||||||
|
|
||||||
# Authenticated encryption
|
|
||||||
auth_enc = true
|
|
||||||
|
|
||||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
private_peer_ids = ""
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
@@ -103,9 +103,6 @@ pex = true
|
|||||||
# Does not work if the peer-exchange reactor is disabled.
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
seed_mode = false
|
seed_mode = false
|
||||||
|
|
||||||
# Authenticated encryption
|
|
||||||
auth_enc = true
|
|
||||||
|
|
||||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
private_peer_ids = ""
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
@@ -55,7 +55,10 @@ Tendermint 102
|
|||||||
abci-spec.rst
|
abci-spec.rst
|
||||||
app-architecture.rst
|
app-architecture.rst
|
||||||
app-development.rst
|
app-development.rst
|
||||||
|
subscribing-to-events-via-websocket.rst
|
||||||
|
indexing-transactions.rst
|
||||||
how-to-read-logs.rst
|
how-to-read-logs.rst
|
||||||
|
running-in-production.rst
|
||||||
|
|
||||||
Tendermint 201
|
Tendermint 201
|
||||||
--------------
|
--------------
|
||||||
|
100
docs/indexing-transactions.rst
Normal file
100
docs/indexing-transactions.rst
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
Indexing Transactions
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Tendermint allows you to index transactions and later query or subscribe to
|
||||||
|
their results.
|
||||||
|
|
||||||
|
Let's take a look at the ``[tx_index]`` config section:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
##### 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 = "kv"
|
||||||
|
|
||||||
|
# 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 = ""
|
||||||
|
|
||||||
|
# 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 = false
|
||||||
|
|
||||||
|
By default, Tendermint will index all transactions by their respective hashes
|
||||||
|
using an embedded simple indexer. Note, we are planning to add more options in
|
||||||
|
the future (e.g., Postgresql indexer).
|
||||||
|
|
||||||
|
Adding tags
|
||||||
|
-----------
|
||||||
|
|
||||||
|
In your application's ``DeliverTx`` method, add the ``Tags`` field with the
|
||||||
|
pairs of UTF-8 encoded strings (e.g. "account.owner": "Bob", "balance":
|
||||||
|
"100.0", "date": "2018-01-02").
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
func (app *KVStoreApplication) DeliverTx(tx []byte) types.Result {
|
||||||
|
...
|
||||||
|
tags := []cmn.KVPair{
|
||||||
|
{[]byte("account.name"), []byte("igor")},
|
||||||
|
{[]byte("account.address"), []byte("0xdeadbeef")},
|
||||||
|
{[]byte("tx.amount"), []byte("7")},
|
||||||
|
}
|
||||||
|
return types.ResponseDeliverTx{Code: code.CodeTypeOK, Tags: tags}
|
||||||
|
}
|
||||||
|
|
||||||
|
If you want Tendermint to only index transactions by "account.name" tag, in the
|
||||||
|
config set ``tx_index.index_tags="account.name"``. If you to index all tags,
|
||||||
|
set ``index_all_tags=true``
|
||||||
|
|
||||||
|
Note, there are a few predefined tags:
|
||||||
|
|
||||||
|
- ``tm.event`` (event type)
|
||||||
|
- ``tx.hash`` (transaction's hash)
|
||||||
|
- ``tx.height`` (height of the block transaction was committed in)
|
||||||
|
|
||||||
|
Tendermint will throw a warning if you try to use any of the above keys.
|
||||||
|
|
||||||
|
Quering transactions
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
You can query the transaction results by calling ``/tx_search`` RPC endpoint:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
curl "localhost:46657/tx_search?query=\"account.name='igor'\"&prove=true"
|
||||||
|
|
||||||
|
Check out `API docs <https://tendermint.github.io/slate/?shell#txsearch>`__ for more
|
||||||
|
information on query syntax and other options.
|
||||||
|
|
||||||
|
Subscribing to transactions
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Clients can subscribe to transactions with the given tags via Websocket by
|
||||||
|
providing a query to ``/subscribe`` RPC endpoint.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "subscribe",
|
||||||
|
"id": "0",
|
||||||
|
"params": {
|
||||||
|
"query": "account.name='igor'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Check out `API docs <https://tendermint.github.io/slate/#subscribe>`__ for more
|
||||||
|
information on query syntax and other options.
|
203
docs/running-in-production.rst
Normal file
203
docs/running-in-production.rst
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
Running in production
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Logging
|
||||||
|
-------
|
||||||
|
|
||||||
|
Default logging level (``main:info,state:info,*:``) should suffice for normal
|
||||||
|
operation mode. Read `this post
|
||||||
|
<https://blog.cosmos.network/one-of-the-exciting-new-features-in-0-10-0-release-is-smart-log-level-flag-e2506b4ab756>`__
|
||||||
|
for details on how to configure ``log_level`` config variable. Some of the
|
||||||
|
modules can be found `here <./how-to-read-logs.html#list-of-modules>`__. If
|
||||||
|
you're trying to debug Tendermint or asked to provide logs with debug logging
|
||||||
|
level, you can do so by running tendermint with ``--log_level="*:debug"``.
|
||||||
|
|
||||||
|
DOS Exposure and Mitigation
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Validators are supposed to setup `Sentry Node Architecture
|
||||||
|
<https://blog.cosmos.network/tendermint-explained-bringing-bft-based-pos-to-the-public-blockchain-domain-f22e274a0fdb>`__
|
||||||
|
to prevent Denial-of-service attacks. You can read more about it `here
|
||||||
|
<https://github.com/tendermint/aib-data/blob/develop/medium/TendermintBFT.md>`__.
|
||||||
|
|
||||||
|
P2P
|
||||||
|
~~~
|
||||||
|
|
||||||
|
The core of the Tendermint peer-to-peer system is ``MConnection``. Each
|
||||||
|
connection has ``MaxPacketMsgPayloadSize``, which is the maximum packet size
|
||||||
|
and bounded send & receive queues. One can impose restrictions on send &
|
||||||
|
receive rate per connection (``SendRate``, ``RecvRate``).
|
||||||
|
|
||||||
|
RPC
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Endpoints returning multiple entries are limited by default to return 30
|
||||||
|
elements (100 max).
|
||||||
|
|
||||||
|
Rate-limiting and authentication are another key aspects to help protect
|
||||||
|
against DOS attacks. While in the future we may implement these features, for
|
||||||
|
now, validators are supposed to use external tools like `NGINX
|
||||||
|
<https://www.nginx.com/blog/rate-limiting-nginx/>`__ or `traefik
|
||||||
|
<https://docs.traefik.io/configuration/commons/#rate-limiting>`__ to achieve
|
||||||
|
the same things.
|
||||||
|
|
||||||
|
Debugging Tendermint
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
If you ever have to debug Tendermint, the first thing you should probably do is
|
||||||
|
to check out the logs. See `"How to read logs" <./how-to-read-logs.html>`__,
|
||||||
|
where we explain what certain log statements mean.
|
||||||
|
|
||||||
|
If, after skimming through the logs, things are not clear still, the second
|
||||||
|
TODO is to query the `/status` RPC endpoint. It provides the necessary info:
|
||||||
|
whenever the node is syncing or not, what height it is on, etc.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl http(s)://{ip}:{rpcPort}/status
|
||||||
|
```
|
||||||
|
|
||||||
|
`/dump_consensus_state` will give you a detailed overview of the consensus
|
||||||
|
state (proposer, lastest validators, peers states). From it, you should be able
|
||||||
|
to figure out why, for example, the network had halted.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl http(s)://{ip}:{rpcPort}/dump_consensus_state
|
||||||
|
```
|
||||||
|
|
||||||
|
There is a reduced version of this endpoint - `/consensus_state`, which
|
||||||
|
returns just the votes seen at the current height.
|
||||||
|
|
||||||
|
- `Github Issues <https://github.com/tendermint/tendermint/issues>`__
|
||||||
|
- `StackOverflow questions <https://stackoverflow.com/questions/tagged/tendermint>`__
|
||||||
|
|
||||||
|
Monitoring Tendermint
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Each Tendermint instance has a standard `/health` RPC endpoint, which responds
|
||||||
|
with 200 (OK) if everything is fine and 500 (or no response) - if something is
|
||||||
|
wrong.
|
||||||
|
|
||||||
|
Other useful endpoints include mentioned earlier `/status`, `/net_info` and
|
||||||
|
`/validators`.
|
||||||
|
|
||||||
|
We have a small tool, called tm-monitor, which outputs information from the
|
||||||
|
endpoints above plus some statistics. The tool can be found `here
|
||||||
|
<https://github.com/tendermint/tools/tree/master/tm-monitor>`__.
|
||||||
|
|
||||||
|
What happens when my app dies?
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
You are supposed to run Tendermint under a `process supervisor
|
||||||
|
<https://en.wikipedia.org/wiki/Process_supervision>`__ (like systemd or runit).
|
||||||
|
It will ensure Tendermint is always running (despite possible errors).
|
||||||
|
|
||||||
|
Getting back to the original question, if your application dies, Tendermint
|
||||||
|
will panic. After a process supervisor restarts your application, Tendermint
|
||||||
|
should be able to reconnect successfully. The order of restart does not matter
|
||||||
|
for it.
|
||||||
|
|
||||||
|
Signal handling
|
||||||
|
---------------
|
||||||
|
|
||||||
|
We catch SIGINT and SIGTERM and try to clean up nicely. For other signals we
|
||||||
|
use the default behaviour in Go: `Default behavior of signals in Go programs
|
||||||
|
<https://golang.org/pkg/os/signal/#hdr-Default_behavior_of_signals_in_Go_programs>`__.
|
||||||
|
|
||||||
|
Hardware
|
||||||
|
--------
|
||||||
|
|
||||||
|
Processor and Memory
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
While actual specs vary depending on the load and validators count, minimal requirements are:
|
||||||
|
|
||||||
|
- 1GB RAM
|
||||||
|
- 25GB of disk space
|
||||||
|
- 1.4 GHz CPU
|
||||||
|
|
||||||
|
SSD disks are preferable for applications with high transaction throughput.
|
||||||
|
|
||||||
|
Recommended:
|
||||||
|
|
||||||
|
- 2GB RAM
|
||||||
|
- 100GB SSD
|
||||||
|
- x64 2.0 GHz 2v CPU
|
||||||
|
|
||||||
|
While for now, Tendermint stores all the history and it may require significant
|
||||||
|
disk space over time, we are planning to implement state syncing (See `#828
|
||||||
|
<https://github.com/tendermint/tendermint/issues/828>`__). So, storing all the
|
||||||
|
past blocks will not be necessary.
|
||||||
|
|
||||||
|
Operating Systems
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tendermint can be compiled for a wide range of operating systems thanks to Go
|
||||||
|
language (the list of $OS/$ARCH pairs can be found `here
|
||||||
|
<https://golang.org/doc/install/source#environment>`__).
|
||||||
|
|
||||||
|
While we do not favor any operation system, more secure and stable Linux server
|
||||||
|
distributions (like Centos) should be preferred over desktop operation systems
|
||||||
|
(like Mac OS).
|
||||||
|
|
||||||
|
Misc.
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
NOTE: if you are going to use Tendermint in a public domain, make sure you read
|
||||||
|
`hardware recommendations (see "4. Hardware")
|
||||||
|
<https://cosmos.network/validators>`__ for a validator in the Cosmos network.
|
||||||
|
|
||||||
|
Configuration parameters
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
- ``p2p.flush_throttle_timeout``
|
||||||
|
``p2p.max_packet_msg_payload_size``
|
||||||
|
``p2p.send_rate``
|
||||||
|
``p2p.recv_rate``
|
||||||
|
|
||||||
|
If you are going to use Tendermint in a private domain and you have a private
|
||||||
|
high-speed network among your peers, it makes sense to lower flush throttle
|
||||||
|
timeout and increase other params.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[p2p]
|
||||||
|
|
||||||
|
send_rate=20000000 # 2MB/s
|
||||||
|
recv_rate=20000000 # 2MB/s
|
||||||
|
flush_throttle_timeout=10
|
||||||
|
max_packet_msg_payload_size=10240 # 10KB
|
||||||
|
|
||||||
|
- ``mempool.recheck``
|
||||||
|
|
||||||
|
After every block, Tendermint rechecks every transaction left in the mempool to
|
||||||
|
see if transactions committed in that block affected the application state, so
|
||||||
|
some of the transactions left may become invalid. If that does not apply to
|
||||||
|
your application, you can disable it by setting ``mempool.recheck=false``.
|
||||||
|
|
||||||
|
- ``mempool.broadcast``
|
||||||
|
|
||||||
|
Setting this to false will stop the mempool from relaying transactions to other
|
||||||
|
peers until they are included in a block. It means only the peer you send the
|
||||||
|
tx to will see it until it is included in a block.
|
||||||
|
|
||||||
|
- ``consensus.skip_timeout_commit``
|
||||||
|
|
||||||
|
We want skip_timeout_commit=false when there is economics on the line because
|
||||||
|
proposers should wait to hear for more votes. But if you don't care about that
|
||||||
|
and want the fastest consensus, you can skip it. It will be kept false by
|
||||||
|
default for public deployments (e.g. `Cosmos Hub
|
||||||
|
<https://cosmos.network/intro/hub>`__) while for enterprise applications,
|
||||||
|
setting it to true is not a problem.
|
||||||
|
|
||||||
|
- ``consensus.peer_gossip_sleep_duration``
|
||||||
|
|
||||||
|
You can try to reduce the time your node sleeps before checking if theres something to send its peers.
|
||||||
|
|
||||||
|
- ``consensus.timeout_commit``
|
||||||
|
|
||||||
|
You can also try lowering ``timeout_commit`` (time we sleep before proposing the next block).
|
||||||
|
|
||||||
|
- ``consensus.max_block_size_txs``
|
||||||
|
|
||||||
|
By default, the maximum number of transactions per a block is 10_000. Feel free
|
||||||
|
to change it to suit your needs.
|
@@ -39,7 +39,7 @@ place of the public key. Here we list the concrete types, their names,
|
|||||||
and prefix bytes for public keys and signatures, as well as the address schemes
|
and prefix bytes for public keys and signatures, as well as the address schemes
|
||||||
for each PubKey. Note for brevity we don't
|
for each PubKey. Note for brevity we don't
|
||||||
include details of the private keys beyond their type and name, as they can be
|
include details of the private keys beyond their type and name, as they can be
|
||||||
derrived the same way as the others using Amino.
|
derived the same way as the others using Amino.
|
||||||
|
|
||||||
All registered objects are encoded by Amino using a 4-byte PrefixBytes that
|
All registered objects are encoded by Amino using a 4-byte PrefixBytes that
|
||||||
uniquely identifies the object and includes information about its underlying
|
uniquely identifies the object and includes information about its underlying
|
||||||
@@ -49,107 +49,35 @@ spec](https://github.com/tendermint/go-amino#computing-the-prefix-and-disambigua
|
|||||||
In what follows, we provide the type names and prefix bytes directly.
|
In what follows, we provide the type names and prefix bytes directly.
|
||||||
Notice that when encoding byte-arrays, the length of the byte-array is appended
|
Notice that when encoding byte-arrays, the length of the byte-array is appended
|
||||||
to the PrefixBytes. Thus the encoding of a byte array becomes `<PrefixBytes>
|
to the PrefixBytes. Thus the encoding of a byte array becomes `<PrefixBytes>
|
||||||
<Length> <ByteArray>`
|
<Length> <ByteArray>`. In other words, to encode any type listed below you do not need to be
|
||||||
|
familiar with amino encoding.
|
||||||
|
You can simply use below table and concatenate Prefix || Length (of raw bytes) || raw bytes
|
||||||
|
( while || stands for byte concatenation here).
|
||||||
|
|
||||||
NOTE: the remainder of this section on Public Key Cryptography can be generated
|
| Type | Name | Prefix | Length |
|
||||||
from [this script](https://github.com/tendermint/tendermint/blob/master/docs/spec/scripts/crypto.go)
|
| ---- | ---- | ------ | ----- |
|
||||||
|
| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE62 | 0x20 |
|
||||||
|
| PubKeyLedgerEd25519 | tendermint/PubKeyLedgerEd25519 | 0x5C3453B2 | 0x20 |
|
||||||
|
| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE982 | 0x21 |
|
||||||
|
| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288912 | 0x40 |
|
||||||
|
| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79A | 0x20 |
|
||||||
|
| PrivKeyLedgerSecp256k1 | tendermint/PrivKeyLedgerSecp256k1 | 0x10CAB393 | variable |
|
||||||
|
| PrivKeyLedgerEd25519 | tendermint/PrivKeyLedgerEd25519 | 0x0CFEEF9B | variable |
|
||||||
|
| SignatureEd25519 | tendermint/SignatureKeyEd25519 | 0x3DA1DB2A | 0x40 |
|
||||||
|
| SignatureSecp256k1 | tendermint/SignatureKeySecp256k1 | 0x16E1FEEA | variable |
|
||||||
|
|
||||||
### PubKeyEd25519
|
### Examples
|
||||||
|
|
||||||
```
|
1. For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey
|
||||||
// Name: tendermint/PubKeyEd25519
|
`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||||
// PrefixBytes: 0x1624DE62
|
|
||||||
// Length: 0x20
|
|
||||||
// Notes: raw 32-byte Ed25519 pubkey
|
|
||||||
type PubKeyEd25519 [32]byte
|
|
||||||
|
|
||||||
func (pubkey PubKeyEd25519) Address() []byte {
|
|
||||||
// NOTE: hash of the Amino encoded bytes!
|
|
||||||
return RIPEMD160(AminoEncode(pubkey))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For example, the 32-byte Ed25519 pubkey
|
|
||||||
`CCACD52F9B29D04393F01CD9AF6535455668115641F3D8BAEFD2295F24BAF60E` would be
|
|
||||||
encoded as
|
|
||||||
`1624DE6220CCACD52F9B29D04393F01CD9AF6535455668115641F3D8BAEFD2295F24BAF60E`.
|
|
||||||
|
|
||||||
The address would then be
|
|
||||||
`RIPEMD160(0x1624DE6220CCACD52F9B29D04393F01CD9AF6535455668115641F3D8BAEFD2295F24BAF60E)`
|
|
||||||
or `430FF75BAF1EC4B0D51BB3EEC2955479D0071605`
|
|
||||||
|
|
||||||
### SignatureEd25519
|
|
||||||
|
|
||||||
```
|
|
||||||
// Name: tendermint/SignatureKeyEd25519
|
|
||||||
// PrefixBytes: 0x3DA1DB2A
|
|
||||||
// Length: 0x40
|
|
||||||
// Notes: raw 64-byte Ed25519 signature
|
|
||||||
type SignatureEd25519 [64]byte
|
|
||||||
```
|
|
||||||
|
|
||||||
For example, the 64-byte Ed25519 signature
|
|
||||||
`1B6034A8ED149D3C94FDA13EC03B26CC0FB264D9B0E47D3FA3DEF9FCDE658E49C80B35F9BE74949356401B15B18FB817D6E54495AD1C4A8401B248466CB0DB0B`
|
|
||||||
would be encoded as
|
would be encoded as
|
||||||
`3DA1DB2A401B6034A8ED149D3C94FDA13EC03B26CC0FB264D9B0E47D3FA3DEF9FCDE658E49C80B35F9BE74949356401B15B18FB817D6E54495AD1C4A8401B248466CB0DB0B`
|
|
||||||
|
|
||||||
### PrivKeyEd25519
|
|
||||||
|
|
||||||
```
|
|
||||||
// Name: tendermint/PrivKeyEd25519
|
|
||||||
// Notes: raw 32-byte priv key concatenated to raw 32-byte pub key
|
|
||||||
type PrivKeyEd25519 [64]byte
|
|
||||||
```
|
|
||||||
|
|
||||||
### PubKeySecp256k1
|
|
||||||
|
|
||||||
```
|
|
||||||
// Name: tendermint/PubKeySecp256k1
|
|
||||||
// PrefixBytes: 0xEB5AE982
|
|
||||||
// Length: 0x21
|
|
||||||
// Notes: OpenSSL compressed pubkey prefixed with 0x02 or 0x03
|
|
||||||
type PubKeySecp256k1 [33]byte
|
|
||||||
|
|
||||||
func (pubkey PubKeySecp256k1) Address() []byte {
|
|
||||||
// NOTE: hash of the raw pubkey bytes (not Amino encoded!).
|
|
||||||
// Compatible with Bitcoin addresses.
|
|
||||||
return RIPEMD160(SHA256(pubkey[:]))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For example, the 33-byte Secp256k1 pubkey
|
|
||||||
`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` would be
|
|
||||||
encoded as
|
|
||||||
`EB5AE98221020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
`EB5AE98221020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||||
|
|
||||||
The address would then be
|
2. For example, the variable size Secp256k1 signature (in this particular example 70 or 0x46 bytes)
|
||||||
`RIPEMD160(SHA256(0x020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9))`
|
|
||||||
or `0AE5BEE929ABE51BAD345DB925EEA652680783FC`
|
|
||||||
|
|
||||||
### SignatureSecp256k1
|
|
||||||
|
|
||||||
```
|
|
||||||
// Name: tendermint/SignatureKeySecp256k1
|
|
||||||
// PrefixBytes: 0x16E1FEEA
|
|
||||||
// Length: Variable
|
|
||||||
// Encoding prefix: Variable
|
|
||||||
// Notes: raw bytes of the Secp256k1 signature
|
|
||||||
type SignatureSecp256k1 []byte
|
|
||||||
```
|
|
||||||
|
|
||||||
For example, the Secp256k1 signature
|
|
||||||
`304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
`304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
||||||
would be encoded as
|
would be encoded as
|
||||||
`16E1FEEA46304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
`16E1FEEA46304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
||||||
|
|
||||||
### PrivKeySecp256k1
|
|
||||||
|
|
||||||
```
|
|
||||||
// Name: tendermint/PrivKeySecp256k1
|
|
||||||
// Notes: raw 32-byte priv key
|
|
||||||
type PrivKeySecp256k1 [32]byte
|
|
||||||
```
|
|
||||||
|
|
||||||
## Other Common Types
|
## Other Common Types
|
||||||
|
|
||||||
### BitArray
|
### BitArray
|
||||||
|
33
docs/spec/consensus/wal.md
Normal file
33
docs/spec/consensus/wal.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# WAL
|
||||||
|
|
||||||
|
Consensus module writes every message to the WAL (write-ahead log).
|
||||||
|
|
||||||
|
It also issues fsync syscall through
|
||||||
|
[File#Sync](https://golang.org/pkg/os/#File.Sync) for messages signed by this
|
||||||
|
node (to prevent double signing).
|
||||||
|
|
||||||
|
Under the hood, it uses
|
||||||
|
[autofile.Group](https://godoc.org/github.com/tendermint/tmlibs/autofile#Group),
|
||||||
|
which rotates files when those get too big (> 10MB).
|
||||||
|
|
||||||
|
The total maximum size is 1GB. We only need the latest block and the block before it,
|
||||||
|
but if the former is dragging on across many rounds, we want all those rounds.
|
||||||
|
|
||||||
|
## Replay
|
||||||
|
|
||||||
|
Consensus module will replay all the messages of the last height written to WAL
|
||||||
|
before a crash (if such occurs).
|
||||||
|
|
||||||
|
The private validator may try to sign messages during replay because it runs
|
||||||
|
somewhat autonomously and does not know about replay process.
|
||||||
|
|
||||||
|
For example, if we got all the way to precommit in the WAL and then crash,
|
||||||
|
after we replay the proposal message, the private validator will try to sign a
|
||||||
|
prevote. But it will fail. That's ok because we’ll see the prevote later in the
|
||||||
|
WAL. Then it will go to precommit, and that time it will work because the
|
||||||
|
private validator contains the `LastSignBytes` and then we’ll replay the
|
||||||
|
precommit from the WAL.
|
||||||
|
|
||||||
|
Make sure to read about [WAL
|
||||||
|
corruption](https://tendermint.readthedocs.io/projects/tools/en/master/specification/corruption.html#wal-corruption)
|
||||||
|
and recovery strategies.
|
@@ -17,9 +17,6 @@ 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
|
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.
|
corresponding to `<ID>`. This prevents man-in-the-middle attacks on the peer layer.
|
||||||
|
|
||||||
If `auth_enc = false`, peers can use an arbitrary ID, but they must always use
|
|
||||||
one. Authentication can then happen out-of-band of Tendermint, for instance via VPN.
|
|
||||||
|
|
||||||
## Connections
|
## Connections
|
||||||
|
|
||||||
All p2p connections use TCP.
|
All p2p connections use TCP.
|
||||||
|
@@ -47,3 +47,13 @@ type bcStatusResponseMessage struct {
|
|||||||
## Protocol
|
## Protocol
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
Defines `maxMsgSize` for the maximum size of incoming messages,
|
||||||
|
`SendQueueCapacity` and `RecvBufferCapacity` for maximum sending and
|
||||||
|
receiving buffers respectively. These are supposed to prevent amplification
|
||||||
|
attacks by setting up the upper limit on how much data we can receive & send to
|
||||||
|
a peer.
|
||||||
|
|
||||||
|
Sending incorrectly encoded data will result in stopping the peer.
|
||||||
|
@@ -342,3 +342,11 @@ It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state
|
|||||||
broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel.
|
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.
|
Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel.
|
||||||
`ProposalHeartbeatMessage` is sent the same way on the StateChannel.
|
`ProposalHeartbeatMessage` is sent the same way on the StateChannel.
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
Defines 4 channels: state, data, vote and vote_set_bits. Each channel
|
||||||
|
has `SendQueueCapacity` and `RecvBufferCapacity` and
|
||||||
|
`RecvMessageCapacity` set to `maxMsgSize`.
|
||||||
|
|
||||||
|
Sending incorrectly encoded data will result in stopping the peer.
|
||||||
|
10
docs/spec/reactors/evidence/reactor.md
Normal file
10
docs/spec/reactors/evidence/reactor.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Evidence Reactor
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
[#1503](https://github.com/tendermint/tendermint/issues/1503)
|
||||||
|
|
||||||
|
Sending invalid evidence will result in stopping the peer.
|
||||||
|
|
||||||
|
Sending incorrectly encoded data or data exceeding `maxMsgSize` will result
|
||||||
|
in stopping the peer.
|
14
docs/spec/reactors/mempool/reactor.md
Normal file
14
docs/spec/reactors/mempool/reactor.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Mempool Reactor
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
[#1503](https://github.com/tendermint/tendermint/issues/1503)
|
||||||
|
|
||||||
|
Mempool maintains a cache of the last 10000 transactions to prevent
|
||||||
|
replaying old transactions (plus transactions coming from other
|
||||||
|
validators, who are continually exchanging transactions). Read [Replay
|
||||||
|
Protection](https://tendermint.readthedocs.io/projects/tools/en/master/app-development.html?#replay-protection)
|
||||||
|
for details.
|
||||||
|
|
||||||
|
Sending incorrectly encoded data or data exceeding `maxMsgSize` will result
|
||||||
|
in stopping the peer.
|
12
docs/spec/reactors/pex/reactor.md
Normal file
12
docs/spec/reactors/pex/reactor.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# PEX Reactor
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
Defines only `SendQueueCapacity`. [#1503](https://github.com/tendermint/tendermint/issues/1503)
|
||||||
|
|
||||||
|
Implements rate-limiting by enforcing minimal time between two consecutive
|
||||||
|
`pexRequestMessage` requests. If the peer sends us addresses we did not ask,
|
||||||
|
it is stopped.
|
||||||
|
|
||||||
|
Sending incorrectly encoded data or data exceeding `maxMsgSize` will result
|
||||||
|
in stopping the peer.
|
@@ -122,9 +122,6 @@ like the file below, however, double check by inspecting the
|
|||||||
# Does not work if the peer-exchange reactor is disabled.
|
# Does not work if the peer-exchange reactor is disabled.
|
||||||
seed_mode = false
|
seed_mode = false
|
||||||
|
|
||||||
# Authenticated encryption
|
|
||||||
auth_enc = true
|
|
||||||
|
|
||||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||||
private_peer_ids = ""
|
private_peer_ids = ""
|
||||||
|
|
||||||
|
@@ -65,9 +65,7 @@ are connected to at least one validator.
|
|||||||
Config
|
Config
|
||||||
------
|
------
|
||||||
|
|
||||||
Authenticated encryption is enabled by default. If you wish to use another
|
Authenticated encryption is enabled by default.
|
||||||
authentication scheme or your peers are connected via VPN, you can turn it off
|
|
||||||
by setting ``auth_enc`` to ``false`` in the config file.
|
|
||||||
|
|
||||||
Additional Reading
|
Additional Reading
|
||||||
------------------
|
------------------
|
||||||
|
28
docs/subscribing-to-events-via-websocket.rst
Normal file
28
docs/subscribing-to-events-via-websocket.rst
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
Subscribing to events via Websocket
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Tendermint emits different events, to which you can subscribe via `Websocket
|
||||||
|
<https://en.wikipedia.org/wiki/WebSocket>`__. This can be useful for
|
||||||
|
third-party applications (for analysys) or inspecting state.
|
||||||
|
|
||||||
|
`List of events <https://godoc.org/github.com/tendermint/tendermint/types#pkg-constants>`__
|
||||||
|
|
||||||
|
You can subscribe to any of the events above by calling ``subscribe`` RPC method via Websocket.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "subscribe",
|
||||||
|
"id": "0",
|
||||||
|
"params": {
|
||||||
|
"query": "tm.event='NewBlock'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Check out `API docs <https://tendermint.github.io/slate/#subscribe>`__ for more
|
||||||
|
information on query syntax and other options.
|
||||||
|
|
||||||
|
You can also use tags, given you had included them into DeliverTx response, to
|
||||||
|
query transaction results. See `Indexing transactions
|
||||||
|
<./indexing-transactions.html>`__ for details.
|
@@ -72,8 +72,8 @@ type Mempool struct {
|
|||||||
rechecking int32 // for re-checking filtered txs on Update()
|
rechecking int32 // for re-checking filtered txs on Update()
|
||||||
recheckCursor *clist.CElement // next expected response
|
recheckCursor *clist.CElement // next expected response
|
||||||
recheckEnd *clist.CElement // re-checking stops here
|
recheckEnd *clist.CElement // re-checking stops here
|
||||||
notifiedTxsAvailable bool // true if fired on txsAvailable for this height
|
notifiedTxsAvailable bool
|
||||||
txsAvailable chan int64 // fires the next height once for each height, when the mempool is not empty
|
txsAvailable chan int64 // fires the next height once for each height, when the mempool is not empty
|
||||||
|
|
||||||
// Keep a cache of already-seen txs.
|
// Keep a cache of already-seen txs.
|
||||||
// This reduces the pressure on the proxyApp.
|
// This reduces the pressure on the proxyApp.
|
||||||
@@ -328,8 +328,12 @@ func (mem *Mempool) notifyTxsAvailable() {
|
|||||||
panic("notified txs available but mempool is empty!")
|
panic("notified txs available but mempool is empty!")
|
||||||
}
|
}
|
||||||
if mem.txsAvailable != nil && !mem.notifiedTxsAvailable {
|
if mem.txsAvailable != nil && !mem.notifiedTxsAvailable {
|
||||||
|
select {
|
||||||
|
case mem.txsAvailable <- mem.height + 1:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
mem.notifiedTxsAvailable = true
|
mem.notifiedTxsAvailable = true
|
||||||
mem.txsAvailable <- mem.height + 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +386,7 @@ func (mem *Mempool) Update(height int64, txs types.Txs) error {
|
|||||||
// Recheck mempool txs if any txs were committed in the block
|
// Recheck mempool txs if any txs were committed in the block
|
||||||
// NOTE/XXX: in some apps a tx could be invalidated due to EndBlock,
|
// NOTE/XXX: in some apps a tx could be invalidated due to EndBlock,
|
||||||
// so we really still do need to recheck, but this is for debugging
|
// so we really still do need to recheck, but this is for debugging
|
||||||
if mem.config.Recheck && (mem.config.RecheckEmpty || len(txs) > 0) {
|
if mem.config.Recheck && (mem.config.RecheckEmpty || len(goodTxs) > 0) {
|
||||||
mem.logger.Info("Recheck txs", "numtxs", len(goodTxs), "height", height)
|
mem.logger.Info("Recheck txs", "numtxs", len(goodTxs), "height", height)
|
||||||
mem.recheckTxs(goodTxs)
|
mem.recheckTxs(goodTxs)
|
||||||
// At this point, mem.txs are being rechecked.
|
// At this point, mem.txs are being rechecked.
|
||||||
|
@@ -269,9 +269,6 @@ func NewNode(config *cfg.Config,
|
|||||||
// but it would still be nice to have a clear list of the current "PersistentPeers"
|
// but it would still be nice to have a clear list of the current "PersistentPeers"
|
||||||
// somewhere that we can return with net_info.
|
// somewhere that we can return with net_info.
|
||||||
//
|
//
|
||||||
// Let's assume we always have IDs ... and we just dont authenticate them
|
|
||||||
// if auth_enc=false.
|
|
||||||
//
|
|
||||||
// If PEX is on, it should handle dialing the seeds. Otherwise the switch does it.
|
// If PEX is on, it should handle dialing the seeds. Otherwise the switch does it.
|
||||||
// Note we currently use the addrBook regardless at least for AddOurAddress
|
// Note we currently use the addrBook regardless at least for AddOurAddress
|
||||||
addrBook := pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict)
|
addrBook := pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict)
|
||||||
|
23
p2p/peer.go
23
p2p/peer.go
@@ -116,8 +116,6 @@ func newPeer(pc peerConn, nodeInfo NodeInfo,
|
|||||||
|
|
||||||
// PeerConfig is a Peer configuration.
|
// PeerConfig is a Peer configuration.
|
||||||
type PeerConfig struct {
|
type PeerConfig struct {
|
||||||
AuthEnc bool `mapstructure:"auth_enc"` // authenticated encryption
|
|
||||||
|
|
||||||
// times are in seconds
|
// times are in seconds
|
||||||
HandshakeTimeout time.Duration `mapstructure:"handshake_timeout"`
|
HandshakeTimeout time.Duration `mapstructure:"handshake_timeout"`
|
||||||
DialTimeout time.Duration `mapstructure:"dial_timeout"`
|
DialTimeout time.Duration `mapstructure:"dial_timeout"`
|
||||||
@@ -132,7 +130,6 @@ type PeerConfig struct {
|
|||||||
// DefaultPeerConfig returns the default config.
|
// DefaultPeerConfig returns the default config.
|
||||||
func DefaultPeerConfig() *PeerConfig {
|
func DefaultPeerConfig() *PeerConfig {
|
||||||
return &PeerConfig{
|
return &PeerConfig{
|
||||||
AuthEnc: true,
|
|
||||||
HandshakeTimeout: 20, // * time.Second,
|
HandshakeTimeout: 20, // * time.Second,
|
||||||
DialTimeout: 3, // * time.Second,
|
DialTimeout: 3, // * time.Second,
|
||||||
MConfig: tmconn.DefaultMConnConfig(),
|
MConfig: tmconn.DefaultMConnConfig(),
|
||||||
@@ -159,7 +156,7 @@ func newOutboundPeerConn(addr *NetAddress, config *PeerConfig, persistent bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure dialed ID matches connection ID
|
// ensure dialed ID matches connection ID
|
||||||
if config.AuthEnc && addr.ID != pc.ID() {
|
if addr.ID != pc.ID() {
|
||||||
if err2 := conn.Close(); err2 != nil {
|
if err2 := conn.Close(); err2 != nil {
|
||||||
return pc, cmn.ErrorWrap(err, err2.Error())
|
return pc, cmn.ErrorWrap(err, err2.Error())
|
||||||
}
|
}
|
||||||
@@ -187,17 +184,15 @@ func newPeerConn(rawConn net.Conn,
|
|||||||
conn = FuzzConnAfterFromConfig(conn, 10*time.Second, config.FuzzConfig)
|
conn = FuzzConnAfterFromConfig(conn, 10*time.Second, config.FuzzConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AuthEnc {
|
// Set deadline for secret handshake
|
||||||
// Set deadline for secret handshake
|
if err := conn.SetDeadline(time.Now().Add(config.HandshakeTimeout * time.Second)); err != nil {
|
||||||
if err := conn.SetDeadline(time.Now().Add(config.HandshakeTimeout * time.Second)); err != nil {
|
return pc, cmn.ErrorWrap(err, "Error setting deadline while encrypting connection")
|
||||||
return pc, cmn.ErrorWrap(err, "Error setting deadline while encrypting connection")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt connection
|
// Encrypt connection
|
||||||
conn, err = tmconn.MakeSecretConnection(conn, ourNodePrivKey)
|
conn, err = tmconn.MakeSecretConnection(conn, ourNodePrivKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pc, cmn.ErrorWrap(err, "Error creating peer")
|
return pc, cmn.ErrorWrap(err, "Error creating peer")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only the information we already have
|
// Only the information we already have
|
||||||
|
@@ -47,10 +47,6 @@ func (ps *PeerSet) Add(peer Peer) error {
|
|||||||
return ErrSwitchDuplicatePeerID{peer.ID()}
|
return ErrSwitchDuplicatePeerID{peer.ID()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ps.hasIP(peer.RemoteIP()) {
|
|
||||||
return ErrSwitchDuplicatePeerIP{peer.RemoteIP()}
|
|
||||||
}
|
|
||||||
|
|
||||||
index := len(ps.list)
|
index := len(ps.list)
|
||||||
// Appending is safe even with other goroutines
|
// Appending is safe even with other goroutines
|
||||||
// iterating over the ps.list slice.
|
// iterating over the ps.list slice.
|
||||||
|
@@ -143,20 +143,6 @@ func TestPeerSetAddDuplicate(t *testing.T) {
|
|||||||
assert.Equal(t, wantNilErrCount, gotNilErrCount, "invalid nil errCount")
|
assert.Equal(t, wantNilErrCount, gotNilErrCount, "invalid nil errCount")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerSetAddDuplicateIP(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
peerSet := NewPeerSet()
|
|
||||||
|
|
||||||
if err := peerSet.Add(randPeer(net.IP{172, 0, 0, 1})); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add peer with same IP.
|
|
||||||
err := peerSet.Add(randPeer(net.IP{172, 0, 0, 1}))
|
|
||||||
assert.Equal(t, ErrSwitchDuplicatePeerIP{IP: net.IP{172, 0, 0, 1}}, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerSetGet(t *testing.T) {
|
func TestPeerSetGet(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@@ -41,32 +41,10 @@ func TestPeerBasic(t *testing.T) {
|
|||||||
assert.Equal(rp.ID(), p.ID())
|
assert.Equal(rp.ID(), p.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerWithoutAuthEnc(t *testing.T) {
|
|
||||||
assert, require := assert.New(t), require.New(t)
|
|
||||||
|
|
||||||
config := DefaultPeerConfig()
|
|
||||||
config.AuthEnc = false
|
|
||||||
|
|
||||||
// simulate remote peer
|
|
||||||
rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config}
|
|
||||||
rp.Start()
|
|
||||||
defer rp.Stop()
|
|
||||||
|
|
||||||
p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), config)
|
|
||||||
require.Nil(err)
|
|
||||||
|
|
||||||
err = p.Start()
|
|
||||||
require.Nil(err)
|
|
||||||
defer p.Stop()
|
|
||||||
|
|
||||||
assert.True(p.IsRunning())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerSend(t *testing.T) {
|
func TestPeerSend(t *testing.T) {
|
||||||
assert, require := assert.New(t), require.New(t)
|
assert, require := assert.New(t), require.New(t)
|
||||||
|
|
||||||
config := DefaultPeerConfig()
|
config := DefaultPeerConfig()
|
||||||
config.AuthEnc = false
|
|
||||||
|
|
||||||
// simulate remote peer
|
// simulate remote peer
|
||||||
rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config}
|
rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config}
|
||||||
|
@@ -27,6 +27,7 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
config = cfg.DefaultP2PConfig()
|
config = cfg.DefaultP2PConfig()
|
||||||
config.PexReactor = true
|
config.PexReactor = true
|
||||||
|
config.AllowDuplicateIP = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPEXReactorBasic(t *testing.T) {
|
func TestPEXReactorBasic(t *testing.T) {
|
||||||
@@ -48,15 +49,12 @@ func TestPEXReactorAddRemovePeer(t *testing.T) {
|
|||||||
assert.Equal(t, size+1, book.Size())
|
assert.Equal(t, size+1, book.Size())
|
||||||
|
|
||||||
r.RemovePeer(peer, "peer not available")
|
r.RemovePeer(peer, "peer not available")
|
||||||
assert.Equal(t, size+1, book.Size())
|
|
||||||
|
|
||||||
outboundPeer := p2p.CreateRandomPeer(true)
|
outboundPeer := p2p.CreateRandomPeer(true)
|
||||||
|
|
||||||
r.AddPeer(outboundPeer)
|
r.AddPeer(outboundPeer)
|
||||||
assert.Equal(t, size+1, book.Size(), "outbound peers should not be added to the address book")
|
|
||||||
|
|
||||||
r.RemovePeer(outboundPeer, "peer not available")
|
r.RemovePeer(outboundPeer, "peer not available")
|
||||||
assert.Equal(t, size+1, book.Size())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- FAIL: TestPEXReactorRunning (11.10s)
|
// --- FAIL: TestPEXReactorRunning (11.10s)
|
||||||
@@ -69,59 +67,59 @@ func TestPEXReactorAddRemovePeer(t *testing.T) {
|
|||||||
// peers have different IP addresses, they all have the same underlying remote
|
// peers have different IP addresses, they all have the same underlying remote
|
||||||
// IP: 127.0.0.1.
|
// IP: 127.0.0.1.
|
||||||
//
|
//
|
||||||
// func TestPEXReactorRunning(t *testing.T) {
|
func TestPEXReactorRunning(t *testing.T) {
|
||||||
// N := 3
|
N := 3
|
||||||
// switches := make([]*p2p.Switch, N)
|
switches := make([]*p2p.Switch, N)
|
||||||
|
|
||||||
// // directory to store address books
|
// directory to store address books
|
||||||
// dir, err := ioutil.TempDir("", "pex_reactor")
|
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||||
// require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
// defer os.RemoveAll(dir) // nolint: errcheck
|
defer os.RemoveAll(dir) // nolint: errcheck
|
||||||
|
|
||||||
// books := make([]*addrBook, N)
|
books := make([]*addrBook, N)
|
||||||
// logger := log.TestingLogger()
|
logger := log.TestingLogger()
|
||||||
|
|
||||||
// // create switches
|
// create switches
|
||||||
// for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
// switches[i] = p2p.MakeSwitch(config, i, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
switches[i] = p2p.MakeSwitch(config, i, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||||
// books[i] = NewAddrBook(filepath.Join(dir, fmt.Sprintf("addrbook%d.json", i)), false)
|
books[i] = NewAddrBook(filepath.Join(dir, fmt.Sprintf("addrbook%d.json", i)), false)
|
||||||
// books[i].SetLogger(logger.With("pex", i))
|
books[i].SetLogger(logger.With("pex", i))
|
||||||
// sw.SetAddrBook(books[i])
|
sw.SetAddrBook(books[i])
|
||||||
|
|
||||||
// sw.SetLogger(logger.With("pex", i))
|
sw.SetLogger(logger.With("pex", i))
|
||||||
|
|
||||||
// r := NewPEXReactor(books[i], &PEXReactorConfig{})
|
r := NewPEXReactor(books[i], &PEXReactorConfig{})
|
||||||
// r.SetLogger(logger.With("pex", i))
|
r.SetLogger(logger.With("pex", i))
|
||||||
// r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
||||||
// sw.AddReactor("pex", r)
|
sw.AddReactor("pex", r)
|
||||||
|
|
||||||
// return sw
|
return sw
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
// addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) {
|
addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) {
|
||||||
// addr := switches[otherSwitchIndex].NodeInfo().NetAddress()
|
addr := switches[otherSwitchIndex].NodeInfo().NetAddress()
|
||||||
// books[switchIndex].AddAddress(addr, addr)
|
books[switchIndex].AddAddress(addr, addr)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// addOtherNodeAddrToAddrBook(0, 1)
|
addOtherNodeAddrToAddrBook(0, 1)
|
||||||
// addOtherNodeAddrToAddrBook(1, 0)
|
addOtherNodeAddrToAddrBook(1, 0)
|
||||||
// addOtherNodeAddrToAddrBook(2, 1)
|
addOtherNodeAddrToAddrBook(2, 1)
|
||||||
|
|
||||||
// for i, sw := range switches {
|
for i, sw := range switches {
|
||||||
// sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, true, logger.With("pex", i)))
|
sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, true, logger.With("pex", i)))
|
||||||
|
|
||||||
// err := sw.Start() // start switch and reactors
|
err := sw.Start() // start switch and reactors
|
||||||
// require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// assertPeersWithTimeout(t, switches, 10*time.Millisecond, 10*time.Second, N-1)
|
assertPeersWithTimeout(t, switches, 10*time.Millisecond, 10*time.Second, N-1)
|
||||||
|
|
||||||
// // stop them
|
// stop them
|
||||||
// for _, s := range switches {
|
for _, s := range switches {
|
||||||
// s.Stop()
|
s.Stop()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
func TestPEXReactorReceive(t *testing.T) {
|
func TestPEXReactorReceive(t *testing.T) {
|
||||||
r, book := createReactor(&PEXReactorConfig{})
|
r, book := createReactor(&PEXReactorConfig{})
|
||||||
|
@@ -95,7 +95,6 @@ func NewSwitch(config *cfg.P2PConfig) *Switch {
|
|||||||
sw.peerConfig.MConfig.SendRate = config.SendRate
|
sw.peerConfig.MConfig.SendRate = config.SendRate
|
||||||
sw.peerConfig.MConfig.RecvRate = config.RecvRate
|
sw.peerConfig.MConfig.RecvRate = config.RecvRate
|
||||||
sw.peerConfig.MConfig.MaxPacketMsgPayloadSize = config.MaxPacketMsgPayloadSize
|
sw.peerConfig.MConfig.MaxPacketMsgPayloadSize = config.MaxPacketMsgPayloadSize
|
||||||
sw.peerConfig.AuthEnc = config.AuthEnc
|
|
||||||
|
|
||||||
sw.BaseService = *cmn.NewBaseService(nil, "P2P Switch", sw)
|
sw.BaseService = *cmn.NewBaseService(nil, "P2P Switch", sw)
|
||||||
return sw
|
return sw
|
||||||
@@ -534,10 +533,6 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: if AuthEnc==false, we don't have a peerID until after the handshake.
|
|
||||||
// If AuthEnc==true then we already know the ID and could do the checks first before the handshake,
|
|
||||||
// but it's simple to just deal with both cases the same after the handshake.
|
|
||||||
|
|
||||||
// Exchange NodeInfo on the conn
|
// Exchange NodeInfo on the conn
|
||||||
peerNodeInfo, err := pc.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second))
|
peerNodeInfo, err := pc.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -547,13 +542,14 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
|||||||
peerID := peerNodeInfo.ID
|
peerID := peerNodeInfo.ID
|
||||||
|
|
||||||
// ensure connection key matches self reported key
|
// ensure connection key matches self reported key
|
||||||
if pc.config.AuthEnc {
|
connID := pc.ID()
|
||||||
connID := pc.ID()
|
|
||||||
|
|
||||||
if peerID != connID {
|
if peerID != connID {
|
||||||
return fmt.Errorf("nodeInfo.ID() (%v) doesn't match conn.ID() (%v)",
|
return fmt.Errorf(
|
||||||
peerID, connID)
|
"nodeInfo.ID() (%v) doesn't match conn.ID() (%v)",
|
||||||
}
|
peerID,
|
||||||
|
connID,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the peers nodeInfo
|
// Validate the peers nodeInfo
|
||||||
@@ -577,8 +573,9 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate connection or peer info IP.
|
// Check for duplicate connection or peer info IP.
|
||||||
if sw.peers.HasIP(pc.RemoteIP()) ||
|
if !sw.config.AllowDuplicateIP &&
|
||||||
sw.peers.HasIP(peerNodeInfo.NetAddress().IP) {
|
(sw.peers.HasIP(pc.RemoteIP()) ||
|
||||||
|
sw.peers.HasIP(peerNodeInfo.NetAddress().IP)) {
|
||||||
return ErrSwitchDuplicatePeerIP{pc.RemoteIP()}
|
return ErrSwitchDuplicatePeerIP{pc.RemoteIP()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
config = cfg.DefaultP2PConfig()
|
config = cfg.DefaultP2PConfig()
|
||||||
config.PexReactor = true
|
config.PexReactor = true
|
||||||
|
config.AllowDuplicateIP = true
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeerMessage struct {
|
type PeerMessage struct {
|
||||||
@@ -180,7 +181,7 @@ func TestConnAddrFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSwitchFiltersOutItself(t *testing.T) {
|
func TestSwitchFiltersOutItself(t *testing.T) {
|
||||||
s1 := MakeSwitch(config, 1, "127.0.0.2", "123.123.123", initSwitchFunc)
|
s1 := MakeSwitch(config, 1, "127.0.0.1", "123.123.123", initSwitchFunc)
|
||||||
// addr := s1.NodeInfo().NetAddress()
|
// addr := s1.NodeInfo().NetAddress()
|
||||||
|
|
||||||
// // add ourselves like we do in node.go#427
|
// // add ourselves like we do in node.go#427
|
||||||
@@ -322,7 +323,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
|||||||
Config: DefaultPeerConfig(),
|
Config: DefaultPeerConfig(),
|
||||||
// Use different interface to prevent duplicate IP filter, this will break
|
// Use different interface to prevent duplicate IP filter, this will break
|
||||||
// beyond two peers.
|
// beyond two peers.
|
||||||
listenAddr: "127.0.0.2:0",
|
listenAddr: "127.0.0.1:0",
|
||||||
}
|
}
|
||||||
rp.Start()
|
rp.Start()
|
||||||
defer rp.Stop()
|
defer rp.Stop()
|
||||||
|
@@ -3,7 +3,6 @@ package p2p
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
crypto "github.com/tendermint/go-crypto"
|
||||||
cmn "github.com/tendermint/tmlibs/common"
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
@@ -132,8 +131,6 @@ func StartSwitches(switches []*Switch) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var listenAddrSuffix uint32 = 1
|
|
||||||
|
|
||||||
func MakeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch {
|
func MakeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch {
|
||||||
// new switch, add reactors
|
// new switch, add reactors
|
||||||
// TODO: let the config be passed in?
|
// TODO: let the config be passed in?
|
||||||
@@ -148,7 +145,7 @@ func MakeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f
|
|||||||
Moniker: cmn.Fmt("switch%d", i),
|
Moniker: cmn.Fmt("switch%d", i),
|
||||||
Network: network,
|
Network: network,
|
||||||
Version: version,
|
Version: version,
|
||||||
ListenAddr: fmt.Sprintf("127.0.0.%d:%d", atomic.AddUint32(&listenAddrSuffix, 1), cmn.RandIntn(64512)+1023),
|
ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023),
|
||||||
}
|
}
|
||||||
for ch := range sw.reactorsByCh {
|
for ch := range sw.reactorsByCh {
|
||||||
ni.Channels = append(ni.Channels, ch)
|
ni.Channels = append(ni.Channels, ch)
|
||||||
|
@@ -4,13 +4,13 @@ package version
|
|||||||
const (
|
const (
|
||||||
Maj = "0"
|
Maj = "0"
|
||||||
Min = "19"
|
Min = "19"
|
||||||
Fix = "6"
|
Fix = "8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Version is the current version of Tendermint
|
// Version is the current version of Tendermint
|
||||||
// Must be a string because scripts like dist.sh read this file.
|
// Must be a string because scripts like dist.sh read this file.
|
||||||
Version = "0.19.6-dev"
|
Version = "0.19.8"
|
||||||
|
|
||||||
// GitCommit is the current HEAD set using ldflags.
|
// GitCommit is the current HEAD set using ldflags.
|
||||||
GitCommit string
|
GitCommit string
|
||||||
|
Reference in New Issue
Block a user