mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-16 04:41:59 +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
|
||||
- restore_cache:
|
||||
key: v1-tree-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: mkdir -p /tmp/logs
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
for pkg in $(go list github.com/tendermint/tendermint/... | grep -v /vendor/ | circleci tests split --split-by=timings); do
|
||||
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
|
||||
- persist_to_workspace:
|
||||
root: /tmp/workspace
|
||||
paths:
|
||||
- "profiles/*"
|
||||
- store_artifacts:
|
||||
path: /tmp/logs
|
||||
|
||||
test_persistence:
|
||||
<<: *defaults
|
||||
@@ -196,9 +199,6 @@ workflows:
|
||||
test-suite:
|
||||
jobs:
|
||||
- setup_dependencies
|
||||
- build_slate:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
- setup_abci:
|
||||
requires:
|
||||
- setup_dependencies
|
||||
|
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,23 +1,51 @@
|
||||
# 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:
|
||||
|
||||
- [libs/pubsub] TagMap#Get returns a string value
|
||||
- [libs/pubsub] NewTagMap accepts a map of strings
|
||||
|
||||
## 0.19.6
|
||||
|
||||
FEATURES
|
||||
|
||||
- [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:
|
||||
|
||||
- [consensus] consensus reactor now receives events from a separate event bus,
|
||||
which is not dependant on external RPC load
|
||||
- [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
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
@@ -66,11 +67,13 @@ type BlockPool struct {
|
||||
// block requests
|
||||
requesters map[int64]*bpRequester
|
||||
height int64 // the lowest key in requesters.
|
||||
numPending int32 // number of requests pending assignment or block response
|
||||
// peers
|
||||
peers map[p2p.ID]*bpPeer
|
||||
maxPeerHeight int64
|
||||
|
||||
// atomic
|
||||
numPending int32 // number of requests pending assignment or block response
|
||||
|
||||
requestsCh chan<- BlockRequest
|
||||
errorsCh chan<- peerError
|
||||
}
|
||||
@@ -151,7 +154,7 @@ func (pool *BlockPool) GetStatus() (height int64, numPending int32, lenRequester
|
||||
pool.mtx.Lock()
|
||||
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.
|
||||
@@ -245,7 +248,7 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int
|
||||
}
|
||||
|
||||
if requester.setBlock(block, peerID) {
|
||||
pool.numPending--
|
||||
atomic.AddInt32(&pool.numPending, -1)
|
||||
peer := pool.peers[peerID]
|
||||
if peer != nil {
|
||||
peer.decrPending(blockSize)
|
||||
@@ -291,10 +294,7 @@ func (pool *BlockPool) RemovePeer(peerID p2p.ID) {
|
||||
func (pool *BlockPool) removePeer(peerID p2p.ID) {
|
||||
for _, requester := range pool.requesters {
|
||||
if requester.getPeerID() == peerID {
|
||||
if requester.getBlock() != nil {
|
||||
pool.numPending++
|
||||
}
|
||||
go requester.redo() // pick another peer and ...
|
||||
requester.redo()
|
||||
}
|
||||
}
|
||||
delete(pool.peers, peerID)
|
||||
@@ -332,7 +332,7 @@ func (pool *BlockPool) makeNextRequester() {
|
||||
// request.SetLogger(pool.Logger.With("height", nextHeight))
|
||||
|
||||
pool.requesters[nextHeight] = request
|
||||
pool.numPending++
|
||||
atomic.AddInt32(&pool.numPending, 1)
|
||||
|
||||
err := request.Start()
|
||||
if err != nil {
|
||||
@@ -360,7 +360,7 @@ func (pool *BlockPool) sendError(err error, peerID p2p.ID) {
|
||||
|
||||
// unused by tendermint; left for debugging purposes
|
||||
func (pool *BlockPool) debug() string {
|
||||
pool.mtx.Lock() // Lock
|
||||
pool.mtx.Lock()
|
||||
defer pool.mtx.Unlock()
|
||||
|
||||
str := ""
|
||||
@@ -466,8 +466,8 @@ func newBPRequester(pool *BlockPool, height int64) *bpRequester {
|
||||
bpr := &bpRequester{
|
||||
pool: pool,
|
||||
height: height,
|
||||
gotBlockCh: make(chan struct{}),
|
||||
redoCh: make(chan struct{}),
|
||||
gotBlockCh: make(chan struct{}, 1),
|
||||
redoCh: make(chan struct{}, 1),
|
||||
|
||||
peerID: "",
|
||||
block: nil,
|
||||
@@ -481,7 +481,7 @@ func (bpr *bpRequester) OnStart() error {
|
||||
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 {
|
||||
bpr.mtx.Lock()
|
||||
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.mtx.Unlock()
|
||||
|
||||
bpr.gotBlockCh <- struct{}{}
|
||||
select {
|
||||
case bpr.gotBlockCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -507,17 +510,27 @@ func (bpr *bpRequester) getPeerID() p2p.ID {
|
||||
return bpr.peerID
|
||||
}
|
||||
|
||||
// This is called from the requestRoutine, upon redo().
|
||||
func (bpr *bpRequester) reset() {
|
||||
bpr.mtx.Lock()
|
||||
defer bpr.mtx.Unlock()
|
||||
|
||||
if bpr.block != nil {
|
||||
atomic.AddInt32(&bpr.pool.numPending, 1)
|
||||
}
|
||||
|
||||
bpr.peerID = ""
|
||||
bpr.block = nil
|
||||
bpr.mtx.Unlock()
|
||||
}
|
||||
|
||||
// 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() {
|
||||
bpr.redoCh <- struct{}{}
|
||||
select {
|
||||
case bpr.redoCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Responsible for making more requests as necessary
|
||||
@@ -546,17 +559,8 @@ OUTER_LOOP:
|
||||
|
||||
// Send request and wait.
|
||||
bpr.pool.sendRequest(bpr.height, peer.id)
|
||||
select {
|
||||
case <-bpr.pool.Quit():
|
||||
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.
|
||||
WAIT_LOOP:
|
||||
for {
|
||||
select {
|
||||
case <-bpr.pool.Quit():
|
||||
bpr.Stop()
|
||||
@@ -566,6 +570,10 @@ OUTER_LOOP:
|
||||
case <-bpr.redoCh:
|
||||
bpr.reset()
|
||||
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.
|
||||
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)
|
||||
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
|
||||
@@ -307,7 +307,7 @@ func DefaultP2PConfig() *P2PConfig {
|
||||
RecvRate: 512000, // 500 kB/s
|
||||
PexReactor: true,
|
||||
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.SkipUPNP = true
|
||||
cfg.FlushThrottleTimeout = 10
|
||||
cfg.AllowDuplicateIP = true
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
@@ -165,9 +165,6 @@ pex = {{ .P2P.PexReactor }}
|
||||
# Does not work if the peer-exchange reactor is disabled.
|
||||
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)
|
||||
private_peer_ids = "{{ .P2P.PrivatePeerIDs }}"
|
||||
|
||||
|
@@ -1,133 +1,42 @@
|
||||
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
|
||||
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.
|
||||
The following diagram provides a superb example:
|
||||
|
||||
Security
|
||||
--------
|
||||
https://drive.google.com/open?id=1yR2XpRi9YCY9H9uMfcw8-RMJpvDyvjz9
|
||||
|
||||
A very important aspect when constructing a blockchain is security. The
|
||||
consensus model can be DoSed (no consensus possible) by corrupting 1/3
|
||||
of the validators and exploited (writing arbitrary blocks) by corrupting
|
||||
2/3 of the validators. So, while the security is not that of the
|
||||
"weakest link", you should take care that the "average link" is
|
||||
sufficiently hardened.
|
||||
The end-user application here is the Cosmos Voyager, at the bottom left.
|
||||
Voyager communicates with a REST API exposed by a local Light-Client Daemon.
|
||||
The Light-Client Daemon is an application specific program that communicates with
|
||||
Tendermint nodes and verifies Tendermint light-client proofs through the Tendermint Core RPC.
|
||||
The Tendermint Core process communicates with a local ABCI application, where the
|
||||
user query or transaction is actually processed.
|
||||
|
||||
One big attack surface on the validators is the communication between
|
||||
the ABCI app and the tendermint core. This should be highly protected.
|
||||
Ideally, the app and the core are running on the same machine, so no
|
||||
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?)
|
||||
The ABCI application must be a deterministic result of the Tendermint consensus - any external influence
|
||||
on the application state that didn't come through Tendermint could cause a
|
||||
consensus failure. Thus *nothing* should communicate with the application except Tendermint via ABCI.
|
||||
|
||||
Now assuming, you have linked together your app and the core securely,
|
||||
you must also make sure no one can get on the machine it is hosted on.
|
||||
At this point it is basic network security. Run on a secure operating
|
||||
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.
|
||||
If the application is written in Go, it can be compiled into the Tendermint binary.
|
||||
Otherwise, it should use a unix socket to communicate with Tendermint.
|
||||
If it's necessary to use TCP, extra care must be taken to encrypt and authenticate the connection.
|
||||
|
||||
There was also a suggestion on slack from @jhon about compiling
|
||||
everything together with a unikernel for more security, such as
|
||||
`Mirage <https://mirage.io>`__ or
|
||||
`UNIK <https://github.com/emc-advanced-dev/unik>`__.
|
||||
All reads from the app happen through the Tendermint `/abci_query` endpoint.
|
||||
All writes to the app happen through the Tendermint `/broadcast_tx_*` endpoints.
|
||||
|
||||
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
|
||||
world and only communicated through a tested and secured `interface
|
||||
exposed by the tendermint core <./specification/rpc.html>`__. This interface
|
||||
exposes a lot of data on the block header and consensus process, which
|
||||
is quite useful for externally verifying the system. It also includes
|
||||
3(!) methods to broadcast a transaction (propose it for the blockchain,
|
||||
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).
|
||||
See the following for more extensive documentation:
|
||||
- [Interchain Standard for the Light-Client REST API](https://github.com/cosmos/cosmos-sdk/pull/1028)
|
||||
- [Tendermint RPC Docs](https://tendermint.github.io/slate/)
|
||||
- [Tendermint in Production](https://github.com/tendermint/tendermint/pull/1618)
|
||||
- [Tendermint Basics](https://tendermint.readthedocs.io/en/master/using-tendermint.html)
|
||||
- [ABCI spec](https://github.com/tendermint/abci/blob/master/specification.rst)
|
||||
|
@@ -103,9 +103,6 @@ pex = true
|
||||
# Does not work if the peer-exchange reactor is disabled.
|
||||
seed_mode = false
|
||||
|
||||
# Authenticated encryption
|
||||
auth_enc = true
|
||||
|
||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
private_peer_ids = ""
|
||||
|
||||
|
@@ -103,9 +103,6 @@ pex = true
|
||||
# Does not work if the peer-exchange reactor is disabled.
|
||||
seed_mode = false
|
||||
|
||||
# Authenticated encryption
|
||||
auth_enc = true
|
||||
|
||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
private_peer_ids = ""
|
||||
|
||||
|
@@ -103,9 +103,6 @@ pex = true
|
||||
# Does not work if the peer-exchange reactor is disabled.
|
||||
seed_mode = false
|
||||
|
||||
# Authenticated encryption
|
||||
auth_enc = true
|
||||
|
||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
private_peer_ids = ""
|
||||
|
||||
|
@@ -103,9 +103,6 @@ pex = true
|
||||
# Does not work if the peer-exchange reactor is disabled.
|
||||
seed_mode = false
|
||||
|
||||
# Authenticated encryption
|
||||
auth_enc = true
|
||||
|
||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
private_peer_ids = ""
|
||||
|
||||
|
@@ -55,7 +55,10 @@ Tendermint 102
|
||||
abci-spec.rst
|
||||
app-architecture.rst
|
||||
app-development.rst
|
||||
subscribing-to-events-via-websocket.rst
|
||||
indexing-transactions.rst
|
||||
how-to-read-logs.rst
|
||||
running-in-production.rst
|
||||
|
||||
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
|
||||
for each PubKey. Note for brevity we don't
|
||||
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
|
||||
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.
|
||||
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>
|
||||
<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
|
||||
from [this script](https://github.com/tendermint/tendermint/blob/master/docs/spec/scripts/crypto.go)
|
||||
| Type | Name | Prefix | Length |
|
||||
| ---- | ---- | ------ | ----- |
|
||||
| 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
|
||||
|
||||
```
|
||||
// Name: tendermint/PubKeyEd25519
|
||||
// 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`
|
||||
1. For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey
|
||||
`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||
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`
|
||||
|
||||
The address would then be
|
||||
`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
|
||||
2. For example, the variable size Secp256k1 signature (in this particular example 70 or 0x46 bytes)
|
||||
`304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
||||
would be encoded as
|
||||
`16E1FEEA46304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
||||
|
||||
### PrivKeySecp256k1
|
||||
|
||||
```
|
||||
// Name: tendermint/PrivKeySecp256k1
|
||||
// Notes: raw 32-byte priv key
|
||||
type PrivKeySecp256k1 [32]byte
|
||||
```
|
||||
|
||||
## Other Common Types
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
All p2p connections use TCP.
|
||||
|
@@ -47,3 +47,13 @@ type bcStatusResponseMessage struct {
|
||||
## Protocol
|
||||
|
||||
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.
|
||||
Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers 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.
|
||||
seed_mode = false
|
||||
|
||||
# Authenticated encryption
|
||||
auth_enc = true
|
||||
|
||||
# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
|
||||
private_peer_ids = ""
|
||||
|
||||
|
@@ -65,9 +65,7 @@ are connected to at least one validator.
|
||||
Config
|
||||
------
|
||||
|
||||
Authenticated encryption is enabled by default. If you wish to use another
|
||||
authentication scheme or your peers are connected via VPN, you can turn it off
|
||||
by setting ``auth_enc`` to ``false`` in the config file.
|
||||
Authenticated encryption is enabled by default.
|
||||
|
||||
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()
|
||||
recheckCursor *clist.CElement // next expected response
|
||||
recheckEnd *clist.CElement // re-checking stops here
|
||||
notifiedTxsAvailable bool // true if fired on txsAvailable for this height
|
||||
txsAvailable chan int64 // fires the next height once for each height, when the mempool is not empty
|
||||
notifiedTxsAvailable bool
|
||||
txsAvailable chan int64 // fires the next height once for each height, when the mempool is not empty
|
||||
|
||||
// Keep a cache of already-seen txs.
|
||||
// This reduces the pressure on the proxyApp.
|
||||
@@ -328,8 +328,12 @@ func (mem *Mempool) notifyTxsAvailable() {
|
||||
panic("notified txs available but mempool is empty!")
|
||||
}
|
||||
if mem.txsAvailable != nil && !mem.notifiedTxsAvailable {
|
||||
select {
|
||||
case mem.txsAvailable <- mem.height + 1:
|
||||
default:
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
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.recheckTxs(goodTxs)
|
||||
// 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"
|
||||
// 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.
|
||||
// Note we currently use the addrBook regardless at least for AddOurAddress
|
||||
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.
|
||||
type PeerConfig struct {
|
||||
AuthEnc bool `mapstructure:"auth_enc"` // authenticated encryption
|
||||
|
||||
// times are in seconds
|
||||
HandshakeTimeout time.Duration `mapstructure:"handshake_timeout"`
|
||||
DialTimeout time.Duration `mapstructure:"dial_timeout"`
|
||||
@@ -132,7 +130,6 @@ type PeerConfig struct {
|
||||
// DefaultPeerConfig returns the default config.
|
||||
func DefaultPeerConfig() *PeerConfig {
|
||||
return &PeerConfig{
|
||||
AuthEnc: true,
|
||||
HandshakeTimeout: 20, // * time.Second,
|
||||
DialTimeout: 3, // * time.Second,
|
||||
MConfig: tmconn.DefaultMConnConfig(),
|
||||
@@ -159,7 +156,7 @@ func newOutboundPeerConn(addr *NetAddress, config *PeerConfig, persistent bool,
|
||||
}
|
||||
|
||||
// ensure dialed ID matches connection ID
|
||||
if config.AuthEnc && addr.ID != pc.ID() {
|
||||
if addr.ID != pc.ID() {
|
||||
if err2 := conn.Close(); err2 != nil {
|
||||
return pc, cmn.ErrorWrap(err, err2.Error())
|
||||
}
|
||||
@@ -187,17 +184,15 @@ func newPeerConn(rawConn net.Conn,
|
||||
conn = FuzzConnAfterFromConfig(conn, 10*time.Second, config.FuzzConfig)
|
||||
}
|
||||
|
||||
if config.AuthEnc {
|
||||
// Set deadline for secret handshake
|
||||
if err := conn.SetDeadline(time.Now().Add(config.HandshakeTimeout * time.Second)); err != nil {
|
||||
return pc, cmn.ErrorWrap(err, "Error setting deadline while encrypting connection")
|
||||
}
|
||||
// Set deadline for secret handshake
|
||||
if err := conn.SetDeadline(time.Now().Add(config.HandshakeTimeout * time.Second)); err != nil {
|
||||
return pc, cmn.ErrorWrap(err, "Error setting deadline while encrypting connection")
|
||||
}
|
||||
|
||||
// Encrypt connection
|
||||
conn, err = tmconn.MakeSecretConnection(conn, ourNodePrivKey)
|
||||
if err != nil {
|
||||
return pc, cmn.ErrorWrap(err, "Error creating peer")
|
||||
}
|
||||
// Encrypt connection
|
||||
conn, err = tmconn.MakeSecretConnection(conn, ourNodePrivKey)
|
||||
if err != nil {
|
||||
return pc, cmn.ErrorWrap(err, "Error creating peer")
|
||||
}
|
||||
|
||||
// Only the information we already have
|
||||
|
@@ -47,10 +47,6 @@ func (ps *PeerSet) Add(peer Peer) error {
|
||||
return ErrSwitchDuplicatePeerID{peer.ID()}
|
||||
}
|
||||
|
||||
if ps.hasIP(peer.RemoteIP()) {
|
||||
return ErrSwitchDuplicatePeerIP{peer.RemoteIP()}
|
||||
}
|
||||
|
||||
index := len(ps.list)
|
||||
// Appending is safe even with other goroutines
|
||||
// iterating over the ps.list slice.
|
||||
|
@@ -143,20 +143,6 @@ func TestPeerSetAddDuplicate(t *testing.T) {
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
|
@@ -41,32 +41,10 @@ func TestPeerBasic(t *testing.T) {
|
||||
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) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
config := DefaultPeerConfig()
|
||||
config.AuthEnc = false
|
||||
|
||||
// simulate remote peer
|
||||
rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config}
|
||||
|
@@ -27,6 +27,7 @@ var (
|
||||
func init() {
|
||||
config = cfg.DefaultP2PConfig()
|
||||
config.PexReactor = true
|
||||
config.AllowDuplicateIP = true
|
||||
}
|
||||
|
||||
func TestPEXReactorBasic(t *testing.T) {
|
||||
@@ -48,15 +49,12 @@ func TestPEXReactorAddRemovePeer(t *testing.T) {
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
|
||||
r.RemovePeer(peer, "peer not available")
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
|
||||
outboundPeer := p2p.CreateRandomPeer(true)
|
||||
|
||||
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")
|
||||
assert.Equal(t, size+1, book.Size())
|
||||
}
|
||||
|
||||
// --- 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
|
||||
// IP: 127.0.0.1.
|
||||
//
|
||||
// func TestPEXReactorRunning(t *testing.T) {
|
||||
// N := 3
|
||||
// switches := make([]*p2p.Switch, N)
|
||||
func TestPEXReactorRunning(t *testing.T) {
|
||||
N := 3
|
||||
switches := make([]*p2p.Switch, N)
|
||||
|
||||
// // directory to store address books
|
||||
// dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
// require.Nil(t, err)
|
||||
// defer os.RemoveAll(dir) // nolint: errcheck
|
||||
// directory to store address books
|
||||
dir, err := ioutil.TempDir("", "pex_reactor")
|
||||
require.Nil(t, err)
|
||||
defer os.RemoveAll(dir) // nolint: errcheck
|
||||
|
||||
// books := make([]*addrBook, N)
|
||||
// logger := log.TestingLogger()
|
||||
books := make([]*addrBook, N)
|
||||
logger := log.TestingLogger()
|
||||
|
||||
// // create switches
|
||||
// for i := 0; i < N; i++ {
|
||||
// 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].SetLogger(logger.With("pex", i))
|
||||
// sw.SetAddrBook(books[i])
|
||||
// create switches
|
||||
for i := 0; i < N; i++ {
|
||||
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].SetLogger(logger.With("pex", i))
|
||||
sw.SetAddrBook(books[i])
|
||||
|
||||
// sw.SetLogger(logger.With("pex", i))
|
||||
sw.SetLogger(logger.With("pex", i))
|
||||
|
||||
// r := NewPEXReactor(books[i], &PEXReactorConfig{})
|
||||
// r.SetLogger(logger.With("pex", i))
|
||||
// r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
||||
// sw.AddReactor("pex", r)
|
||||
r := NewPEXReactor(books[i], &PEXReactorConfig{})
|
||||
r.SetLogger(logger.With("pex", i))
|
||||
r.SetEnsurePeersPeriod(250 * time.Millisecond)
|
||||
sw.AddReactor("pex", r)
|
||||
|
||||
// return sw
|
||||
// })
|
||||
// }
|
||||
return sw
|
||||
})
|
||||
}
|
||||
|
||||
// addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) {
|
||||
// addr := switches[otherSwitchIndex].NodeInfo().NetAddress()
|
||||
// books[switchIndex].AddAddress(addr, addr)
|
||||
// }
|
||||
addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) {
|
||||
addr := switches[otherSwitchIndex].NodeInfo().NetAddress()
|
||||
books[switchIndex].AddAddress(addr, addr)
|
||||
}
|
||||
|
||||
// addOtherNodeAddrToAddrBook(0, 1)
|
||||
// addOtherNodeAddrToAddrBook(1, 0)
|
||||
// addOtherNodeAddrToAddrBook(2, 1)
|
||||
addOtherNodeAddrToAddrBook(0, 1)
|
||||
addOtherNodeAddrToAddrBook(1, 0)
|
||||
addOtherNodeAddrToAddrBook(2, 1)
|
||||
|
||||
// for i, sw := range switches {
|
||||
// sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, true, logger.With("pex", i)))
|
||||
for i, sw := range switches {
|
||||
sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, true, logger.With("pex", i)))
|
||||
|
||||
// err := sw.Start() // start switch and reactors
|
||||
// require.Nil(t, err)
|
||||
// }
|
||||
err := sw.Start() // start switch and reactors
|
||||
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
|
||||
// for _, s := range switches {
|
||||
// s.Stop()
|
||||
// }
|
||||
// }
|
||||
// stop them
|
||||
for _, s := range switches {
|
||||
s.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPEXReactorReceive(t *testing.T) {
|
||||
r, book := createReactor(&PEXReactorConfig{})
|
||||
|
@@ -95,7 +95,6 @@ func NewSwitch(config *cfg.P2PConfig) *Switch {
|
||||
sw.peerConfig.MConfig.SendRate = config.SendRate
|
||||
sw.peerConfig.MConfig.RecvRate = config.RecvRate
|
||||
sw.peerConfig.MConfig.MaxPacketMsgPayloadSize = config.MaxPacketMsgPayloadSize
|
||||
sw.peerConfig.AuthEnc = config.AuthEnc
|
||||
|
||||
sw.BaseService = *cmn.NewBaseService(nil, "P2P Switch", sw)
|
||||
return sw
|
||||
@@ -534,10 +533,6 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
||||
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
|
||||
peerNodeInfo, err := pc.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second))
|
||||
if err != nil {
|
||||
@@ -547,13 +542,14 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
||||
peerID := peerNodeInfo.ID
|
||||
|
||||
// ensure connection key matches self reported key
|
||||
if pc.config.AuthEnc {
|
||||
connID := pc.ID()
|
||||
connID := pc.ID()
|
||||
|
||||
if peerID != connID {
|
||||
return fmt.Errorf("nodeInfo.ID() (%v) doesn't match conn.ID() (%v)",
|
||||
peerID, connID)
|
||||
}
|
||||
if peerID != connID {
|
||||
return fmt.Errorf(
|
||||
"nodeInfo.ID() (%v) doesn't match conn.ID() (%v)",
|
||||
peerID,
|
||||
connID,
|
||||
)
|
||||
}
|
||||
|
||||
// Validate the peers nodeInfo
|
||||
@@ -577,8 +573,9 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
||||
}
|
||||
|
||||
// Check for duplicate connection or peer info IP.
|
||||
if sw.peers.HasIP(pc.RemoteIP()) ||
|
||||
sw.peers.HasIP(peerNodeInfo.NetAddress().IP) {
|
||||
if !sw.config.AllowDuplicateIP &&
|
||||
(sw.peers.HasIP(pc.RemoteIP()) ||
|
||||
sw.peers.HasIP(peerNodeInfo.NetAddress().IP)) {
|
||||
return ErrSwitchDuplicatePeerIP{pc.RemoteIP()}
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,7 @@ var (
|
||||
func init() {
|
||||
config = cfg.DefaultP2PConfig()
|
||||
config.PexReactor = true
|
||||
config.AllowDuplicateIP = true
|
||||
}
|
||||
|
||||
type PeerMessage struct {
|
||||
@@ -180,7 +181,7 @@ func TestConnAddrFilter(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()
|
||||
|
||||
// // add ourselves like we do in node.go#427
|
||||
@@ -322,7 +323,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
||||
Config: DefaultPeerConfig(),
|
||||
// Use different interface to prevent duplicate IP filter, this will break
|
||||
// beyond two peers.
|
||||
listenAddr: "127.0.0.2:0",
|
||||
listenAddr: "127.0.0.1:0",
|
||||
}
|
||||
rp.Start()
|
||||
defer rp.Stop()
|
||||
|
@@ -3,7 +3,6 @@ package p2p
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
@@ -132,8 +131,6 @@ func StartSwitches(switches []*Switch) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var listenAddrSuffix uint32 = 1
|
||||
|
||||
func MakeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch {
|
||||
// new switch, add reactors
|
||||
// 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),
|
||||
Network: network,
|
||||
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 {
|
||||
ni.Channels = append(ni.Channels, ch)
|
||||
|
@@ -4,13 +4,13 @@ package version
|
||||
const (
|
||||
Maj = "0"
|
||||
Min = "19"
|
||||
Fix = "6"
|
||||
Fix = "8"
|
||||
)
|
||||
|
||||
var (
|
||||
// Version is the current version of Tendermint
|
||||
// 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 string
|
||||
|
Reference in New Issue
Block a user