Compare commits

...

28 Commits

Author SHA1 Message Date
Ethan Buchman
a017f2fdd4 Merge pull request #1654 from tendermint/release/v0.19.7
Release/v0.19.7
2018-05-31 09:15:13 -04:00
Ethan Buchman
aaaa5f23e2 changelog and version 2018-05-30 23:36:36 -04:00
Ethan Buchman
178e357d7f Merge pull request #1618 from tendermint/1494-production-notes
[docs] notes about running a production system
2018-05-30 23:23:51 -04:00
Ethan Buchman
683b527534 Merge pull request #1636 from tendermint/bucky/docs
gut docs/app-arch
2018-05-30 23:17:26 -04:00
Ethan Buchman
d584e03427 fix link and typo 2018-05-30 23:23:39 -04:00
Ethan Buchman
d454b1b25f SkipDuplicate -> AllowDuplicate; fix p2p test on mac 2018-05-30 21:44:39 -04:00
Ethan Buchman
432f21452d Merge branch 'master' into develop 2018-05-30 21:43:31 -04:00
Anton Kaliaev
f0ce8b3883 note about state syncing 2018-05-30 16:55:39 +04:00
Anton Kaliaev
3da5198631 fixes after @zramsay's review 2018-05-30 16:51:20 +04:00
Anton Kaliaev
252a0a392b move reactor descriptions to relevant specs 2018-05-30 16:51:19 +04:00
Anton Kaliaev
f7106bfb39 more config variables
Refs #1494
2018-05-30 16:51:19 +04:00
Anton Kaliaev
2a517ac98c hardware specs and configuration params
Refs #1494
2018-05-30 16:51:19 +04:00
Anton Kaliaev
b542dce2e1 [docs] signal handling
Refs #1494
2018-05-30 16:51:19 +04:00
Anton Kaliaev
82ded582f2 [docs] debugging/monitoring sections, restart handling
Refs #1494
2018-05-30 16:51:19 +04:00
Anton Kaliaev
e0d4fe2dba document DOS exposure and mitigation
Refs #1494
2018-05-30 16:51:19 +04:00
Anton Kaliaev
83c6f2864d document the consensus WAL
Refs #1494
2018-05-30 16:51:19 +04:00
Anton Kaliaev
33ec8cb609 document logging
Refs #1494
2018-05-30 16:51:19 +04:00
Ethan Buchman
43a652bcbf Merge pull request #1647 from tendermint/1643-events-query-pubsub
[docs] indexing transactions and subscribing to events
2018-05-30 08:16:08 -04:00
Ethan Buchman
094a40084d Merge pull request #1646 from tendermint/xla/duplicate-ip-check-config
Introduce option to skip duplicate ip check
2018-05-30 08:12:46 -04:00
Anton Kaliaev
eec9f142b5 [docs] indexing transactions and subscribing to events
Refs #1643
2018-05-30 13:10:04 +04:00
Alexander Simmerl
5796e879b9 Introduce option to skip duplicate ip check
In some scenarios like tests we want to disable the guard which prevents
peers connecting from the same ip.

Fixes #1632
Closes #1634
2018-05-30 10:40:22 +02:00
Ethan Buchman
846ca5ce56 Merge pull request #1641 from tendermint/release/v0.19.6
Release/v0.19.6
2018-05-29 13:14:44 -04:00
Jae Kwon
fd6021876b Potential fix for blockchain pool halting issue 2018-05-29 12:32:22 -04:00
Ethan Buchman
2763c8539c changelog and version 2018-05-29 12:31:01 -04:00
Ethan Buchman
1c0bfe1158 Merge pull request #1633 from tendermint/jae/blockchain_pool
Potential fix for blockchain pool halting issue
2018-05-29 10:27:53 -04:00
Ethan Buchman
1e87ef7f75 circle: add GOCACHE=off and -v to tests 2018-05-29 09:28:29 -04:00
Ethan Buchman
c8be091d4a gut docs/app-arch 2018-05-28 16:58:38 -04:00
Jae Kwon
f55725ebfa Potential fix for blockchain pool halting issue 2018-05-26 22:08:42 -07:00
22 changed files with 559 additions and 221 deletions

View File

@@ -139,7 +139,7 @@ jobs:
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"
done
- persist_to_workspace:
root: /tmp/workspace

View File

@@ -1,23 +1,36 @@
# Changelog
## 0.20.0
## 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

View File

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

View File

@@ -292,6 +292,9 @@ type P2PConfig struct {
// 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
@@ -308,6 +311,7 @@ func DefaultP2PConfig() *P2PConfig {
PexReactor: true,
SeedMode: false,
AuthEnc: true,
AllowDuplicateIP: true, // so non-breaking yet
}
}
@@ -317,6 +321,7 @@ func TestP2PConfig() *P2PConfig {
cfg.ListenAddress = "tcp://0.0.0.0:36656"
cfg.SkipUPNP = true
cfg.FlushThrottleTimeout = 10
cfg.AllowDuplicateIP = true
return cfg
}

View File

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

View File

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

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

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

View 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 well 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 well 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.

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

@@ -27,6 +27,7 @@ var (
func init() {
config = cfg.DefaultP2PConfig()
config.PexReactor = true
config.AllowDuplicateIP = true
}
func TestPEXReactorBasic(t *testing.T) {
@@ -69,59 +70,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{})

View File

@@ -577,8 +577,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()}
}

View File

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

View File

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

View File

@@ -4,13 +4,13 @@ package version
const (
Maj = "0"
Min = "19"
Fix = "6"
Fix = "7"
)
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.7"
// GitCommit is the current HEAD set using ldflags.
GitCommit string