Synchronization
The synchronization algorithm determines when blocks are sent to a peer.
Goals
High-level:
- Connected nodes MUST converge to the same chain
- The amount of data sent SHOULD be minimized
This leads to the following rules:
- A block MUST NOT be sent to the peer unless its parent was known to the peer at some previous time
- A block which is possible to commit, but which is not yet committed MUST NOT be discarded
- A block SHOULD NOT be sent to the peer unless it is in the current best chain
- A block SHOULD NOT be sent to a peer that already has it
- A snapshot MUST be sent if synchronization is impossible without it
- A snapshot MAY be sent if it is smaller than the blocks that would need to be sent to synchronize
Identifying a Common Block
When two nodes connect, they first find a common ancestor, which will be the base for synchronization. If both nodes have complete block logs the common ancestor must exist.
If a node has a truncated block log, then logStart
is the first block such that the node has all the blocks in [logStart, HEAD]
.
Each node sends a series of block IDs to its peer. This sequence is terminated by a committed block ID. The message includes a flag marking the last block. If the block log is empty, the special id 0000000000000000000000000000000000000000000000000000000000000000 is sent as the committed block ID.
If the list sent by the peer contains a block in the current chain (after logStart
), synchronization will start at that block. If this ID was not already sent, echo it back to the peer.
If the the peer's committed block is before logStart
and we have not already found a common ancestor, send a snapshot.
Sending Snapshots
A snapshot can be sent when the connection starts if the peer is too far behind. It can also be sent any time the node loads a snapshot that it received over another connection.
When sending a snapshot we first send a series of block headers that are sufficient to validate the producers of the snapshot. Then we send the snapshot contents, and finally, the snapshot checksum and signature.
States
stateDiagram-v2 [*] --> init init --> ready: receive known block id init --> ffout: receive block id before block log init --> ffin ffin --> snapin ffout --> snapout snapout --> ready: send snapshot verify snapin --> ready: receive snapshot verify ready --> ffin: receive header after committed ffout --> ffin: receive header after committed snapout --> ffin: receive header after committed snapin --> ffin: receive header after snapshot snapin --> snapin: receive snapshot part for newer snapshot snapout --> ffout: loaded snapshot snapin --> ffout: loaded snapshot later than incoming ffin --> ffout: loaded snapshot later than incoming ready --> ffout: loaded snapshot init: Send Block IDs ffin: Receive Light Headers ffout: Send Light Headers snapin: Receive Snapshot snapout: Send Snapshot ready: Ready