This document explains how Tendermint Peers are identified, how they connect to one another, and how other peers are found.
Tendermint peers are expected to maintain long-term persistent identities in the form of a private key.
Each peer has an ID defined as peer.ID == peer.PrivKey.Address()
, where Address
uses the scheme defined in go-crypto.
Peer ID's must come with some Proof-of-Work; that is,
they must satisfy peer.PrivKey.Address() < target
for some difficulty target.
This ensures they are not too easy to generate. To begin, let target == 2^240
.
A single peer ID can have multiple IP addresses associated with it. For simplicity, we only keep track of the latest one.
When attempting to connect to a peer, we use the PeerURL: <ID>@<IP>:<PORT>
.
We will attempt to connect to the peer at IP:PORT, and verify,
via authenticated encryption, that it is in possession of the private key
corresponding to <ID>
. This prevents man-in-the-middle attacks on the peer layer.
Peers can also be connected to without specifying an ID, ie. just <IP>:<PORT>
.
In this case, the peer must be authenticated out-of-band of Tendermint,
for instance via VPN
All p2p connections use TCP. Upon establishing a successful TCP connection with a peer, two handhsakes are performed: one for authenticated encryption, and one for Tendermint versioning. Both handshakes have configurable timeouts (they should complete quickly).
Tendermint implements the Station-to-Station protocol using ED25519 keys for Diffie-Helman key-exchange and NACL SecretBox for encryption. It goes as follows:
If this is an outgoing connection (we dialed the peer) and we used a peer ID,
then finally verify that the peer's persistent public key corresponds to the peer ID we dialed,
ie. peer.PubKey.Address() == <ID>
.
The connection has now been authenticated. All traffic is encrypted.
Note that only the dialer can authenticate the identity of the peer, but this is what we care about since when we join the network we wish to ensure we have reached the intended peer (and are not being MITMd).
Before continuing, we check if the new peer has the same ID as ourselves or an existing peer. If so, we disconnect.
We also check the peer's address and public key against an optional whitelist which can be managed through the ABCI app - if the whitelist is enabled and the peer does not qualigy, the connection is terminated.
The Tendermint Version Handshake allows the peers to exchange their NodeInfo:
type NodeInfo struct {
PubKey crypto.PubKey `json:"pub_key"`
Moniker string `json:"moniker"`
Network string `json:"network"`
RemoteAddr string `json:"remote_addr"`
ListenAddr string `json:"listen_addr"` // accepting in
Version string `json:"version"` // major.minor.revision
Channels []int8 `json:"channels"` // active reactor channels
Other []string `json:"other"` // other application specific data
}
The connection is disconnected if:
peer.NodeInfo.PubKey != peer.PubKey
peer.NodeInfo.Version
is not formatted as X.X.X
where X are integers known as Major, Minor, and Revisionpeer.NodeInfo.Version
Major is not the same as ourspeer.NodeInfo.Version
Minor is not the same as ourspeer.NodeInfo.Network
is not the same as ourspeer.Channels
does not intersect with our known Channels.At this point, if we have not disconnected, the peer is valid.
It is added to the switch and hence all reactors via the AddPeer
method.
Note that each reactor may handle multiple channels.
Once a peer is added, incoming messages for a given reactor are handled through
that reactor's Receive
method, and output messages are sent directly by the Reactors
on each peer. A typical reactor maintains per-peer go-routine/s that handle this.