5.3 KiB
Communication between peers and components within the blocksync reactor
A newly joined or recovering node is connecting to a number of peers in order to sync up to the latest height in the blockchain. The peers do not neccessarily have to be validators but we assume that at least one of the peers is correct. The node requests the status of peers in ordeer to learn the maximum and minimum heights for which the peer has blocks. Based on this, the node decides on the maximum height it needs to sync up to. There is no additional check on whether the peers were lying about their heights.
Each peer has an open p2p channel. The number of total requests in flight is limited (maxPendingRequests initially set to maxTotalRequesters). Additionally, there is an upper limit on requests per peer (20).
Once a node receives messages via the p2p channel, they are propagated further via the reactor's go channels. This section contains details on each of the open communication channels, their capacity and when they get activated.
On startup, the reactor fires up four go routines:
- Process requests
- Pool routine
- Handle p2p channel messages
- Process peer updates
The pool routine picks out blocks form the block pool and processes them. It also checks whether the node should switch to consensus. ToDo - change wording (Remove we, discuss whether the pool routing should be the one checking the condition for consensus).
All messages that go through the p2p channel are processed within the processBlockSyncCh routine.
On peer update messages the reactor adds or removes the sending peer.
Note There is currently a check for whether we have a message from an empty peer.
// XXX: Pool#RedoRequest can sometimes give us an empty peer.
if len(peerUpdate.NodeID) == 0 {
return
}
Communication channels
BlockSyncCh: a p2p channel for sending/receiving requests to/from peers.
- Channel id: 0x40
- Size of receive buffer: 1024 messages
- Size of send queue: 1000 messages
- Message size: maximum size of a block + size of proto block messages (response message prefix and key size)
Messages processed via the channel:
| Message name | Message fields | Description |
|---|---|---|
BlockRequest |
height int64, peerID types.NodeID |
request block at height height from peer peerID |
BlockResponse |
block types.Block |
Send block to peer that requested it |
NoBlockResponse |
height int64 |
Indicates that a peer does not have a block at height |
StatusRequest |
{} |
Sent to a peer to request its status |
StatusResponse |
height int64, base int64 |
Send to a peer the lowest and heights height of blocks within it's store (store.Height(), store.Base()) |
HeaderRequest |
height: int64 |
Request a header from peer for verification |
HeaderResponse |
header: Header |
Return the header for the corresponding height |
Reactor channels
This section describes, per reactor component, the open channels and information they process.
BlockPool
| Channel name | Channel msg type | Description |
|---|---|---|
requestsCh |
BlockRequest |
The number of requests is capped by a fixed parameter maxPendingRequestsPerPeer (initially set to 20) |
errorsCh |
peerError |
Channel buffer size is limited |
The reactor sends a p2p block request once it receives a signal via this channel from the block pool. The block pool will first pick a peer (in round robin) and assign it this particular height. Once this is done, the reactor can request a block.
bpRequester
| Channel name | Channel msg type | Description |
|---|---|---|
gotBlock |
struct{} |
capped at 1; Here we simply register a received block and keep waiting for the reactor to terminate or a redo request. Note. It is not clear why we need this. |
redoCh |
types.NodeID |
capped at 1 ; Signals the requester to redo a request for aparticular block after replacing the peer for this height |
When a block is received by a requester, the requester does a number of checks on the received block. Before marking the block as available, the requester verifies the following:
- that we expected a block at the particular height.
- the block came from the peer we assigned to it.
- the block is not nil
In the code there is the following ToDo listed:
// TODO: ensure that blocks come in order for each peer. This needs further specification.
If the checks pass, the block field of the requester is populated with the new block and i sthus made available to the blocksync reactor.
Reactor
| Channel name | Channel msg type | Description |
|---|---|---|
requestsCh |
BlockRequest |
size maxTotalRequesters |
errorsCh |
peerError |
size maxPeerErrBuffer |
didProcessCh |
struct{} |
size 1 |
The channel is created within the pool routine of the reactor and is used to signal that the reactor should check the block pool for new blocks. A message is sent to the channel after a fixed timeout (trySyncTicker). As we need two blocks to verify one of them (this is more clearly defined in verification, if we miss only on of them, we will not wait for the sync timer to time out, but rather try quickly again until we fetch both.
switchToConsensusTicker. In addition to the sync timeout, in the same routine, the reactor checks periodically whether the conditions to switch to consensus are fullfilled.