Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
ChainSync jumping (CSJ) is an optimization for the ChainSync protocol that allows nodes to sync without downloading headers from all of the honest peers. This load is undesirable as it slows down all the peers involved.
The idea is to download the headers of a chain from a single peer (the dynamo) and then ask periodically to the other peers (the jumpers) whether they agree with the dynamo's chain.
When the jumpers disagree with the dynamo, the jumper with the oldest intersection is asked to compete with the dynamo in the GDD logic (becoming an objector). If the dynamo is disconnected, a new dynamo is elected and the objector is demoted to a jumper.
If the objector is disconnected, the syncing process continues with the dynamo and the remaining jumpers.
The main property of the algorithm is that it never downloads headers from more than two plausibly honest peers at a time (a dynamo and an objector). All other peers are either waiting their turn to compete with the dynamo, or are in agreement with it, or are disengaged (see next section).
The algorithm might still download headers redundantly from peers that do historical rollbacks. These rollbacks, however, constitute dishonest behavior, and CSJ does not concern itself with avoiding load to dishonest peers. Avoiding the load induced by dishonest peers on the syncing node would require additionally to disconnect peers that do historical rollbacks. This is not done by CSJ.
Interactions with the Genesis Density Disconnection logic ---------------------------------------------------------
It is possible that neither the dynamo nor the objector are disconnected. This could happen if: 1. They both serve the same chain, or 2. They both claim to have no more headers.
To avoid (1) CSJ checks that the objector disagrees with the dynamo at the point it claimed to disagree as a jumper. If the objector agrees with the dynamo, it is disengaged. A disengaged peer is not asked to jump or act as dynamo or objector. Instead, it continues to offer headers for the rest of the syncing. When the objector is disengaged, a new objector is elected among the dissenting jumpers. If there are no dissenting jumpers left, the syncing continues with the dynamo and the remaining jumpers.
To prevent the dynamo from agreeing with the objector instead, the dynamo is not allowed to rollback before the last jump it requested. If the dynamo tries to rollback before the last jump, it is disengaged and a new dynamo is elected.
To avoid (2) CSJ disengages a peer as soon as it claims to have no more headers. Syncing continues with a new elected dynamo or objector depending on the disengaged peer's role.
CSJ finishes and is turned off when all peers have been disengaged.
Interactions with the ChainSync client --------------------------------------
The ChainSync client interacts with CSJ through some callbacks that determine
when the client should pause, download headers, or ask about agreement with
a given point (jumping). See the Jumping
type for more details.
Interactions with the Limit on Patience ---------------------------------------
Jumpers don't leak the Limit on Patience (LoP) bucket until they are promoted to dynamos or objectors. And the leaking is stopped as soon as they are demoted.
If a jumper refrains from answering to jumps, they will be disconnected with
the intersectTimeout
(in ChainSyncTimeout
).
A jumper answering just before the timeout will not delay the syncing process by a large amount. If they agree with the dynamo, the dynamo will be busy downloading headers and validating blocks while the jumper answers. If the jumper disagrees with the dynamo, CSJ will look for the precise intersection with the dynamo's chain. This could take a few minutes, but it is a path that will end up in one of the dynamo and the jumper being disconnected or disengaged.
Overview of the state transitions ---------------------------------
See ChainSyncJumpingState
for the implementation of the states.
j ╔════════╗ ╭────────── ║ Dynamo ║ ◀─────────╮ │ ╚════════╝ │f ▼ ▲ │ ┌────────────┐ │ k ┌──────────┐ │ Disengaged │ ◀───────────│────────── │ Objector │ └────────────┘ ╭─────│────────── └──────────┘ │ │ ▲ ▲ │ g│ │e b │ │ │ │ │ ╭─────╯ i│ │c ╭╌╌╌╌╌╌╌▼╌╌╌╌╌╌╌╌╌╌╌╌╌│╌╌╌╌╌╌╌╌╌╌│╌▼╌╌╌╮ ┆ ╔═══════╗ a ┌──────┐ d ┌─────┐ | ┆ ║ Happy ║ ───▶ │ LFI* │ ───▶ │ FI* │ | ┆ ╚═══════╝ ◀─╮ └──────┘ └─────┘ | ┆ Jumper ╰─────┴────────────╯h | ╰╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯
- : LookingForIntersection and FoundIntersection, abbreviated for this drawing only; this abbreviation will not be used elsewhere.
A new peer starts as the dynamo if there is no other peer or as a Happy jumper otherwise. The dynamo periodically requests jumps from happy jumpers who, in the ideal case, accept them.
In the event that a jumper rejects a jump, it goes from Happy to LFI* (a). From there starts a back-and-forth of intersection search messages until the exact point of disagreement with the dynamo is found.
Once the exact point of disagreement is found, and if there is no objector yet, the jumper becomes the objector (b). If there is an objector, then we compare the intersections of the objector and the jumper. If the jumper's intersection is strictly older, then the jumper replaces the objector (b+c). Otherwise, the jumper is marked as FI* (d).
If the dynamo disconnects or is disengaged, one peer is elected as the new dynamo (e|f) and all other peers revert to being happy jumpers (g+h).
If the objector disconnects or is disengaged, and there are FI* jumpers, then the one with the oldest intersection with the dynamo gets elected (i).
If the dynamo rolls back to a point older than the last jump it requested, it is disengaged (j) and a new dynamo is elected (e|f).
If the objector agrees with the dynamo, it is disengaged (k). If there are FI* jumpers, then one of them gets elected as the new objector (i).
If dynamo or objector claim to have no more headers, they are disengaged (j|k).
Synopsis
- type Context = ContextWith () ()
- data ContextWith peerField handleField m peer blk = Context {
- peer ∷ !peerField
- handle ∷ !handleField
- handlesVar ∷ !(StrictTVar m (Map peer (ChainSyncClientHandle m blk)))
- jumpSize ∷ !SlotNo
- data Instruction blk
- = RunNormally
- | Restart
- | JumpInstruction !(JumpInstruction blk)
- data JumpInstruction blk
- = JumpTo !(JumpInfo blk)
- | JumpToGoodPoint !(JumpInfo blk)
- data JumpResult blk
- = AcceptedJump !(JumpInstruction blk)
- | RejectedJump !(JumpInstruction blk)
- data Jumping m blk = Jumping {
- jgNextInstruction ∷ !(m (Instruction blk))
- jgOnAwaitReply ∷ !(m ())
- jgOnRollForward ∷ !(Point (Header blk) → m ())
- jgOnRollBackward ∷ !(WithOrigin SlotNo → m ())
- jgProcessJumpResult ∷ !(JumpResult blk → m ())
- jgUpdateJumpInfo ∷ !(JumpInfo blk → STM m ())
- makeContext ∷ MonadSTM m ⇒ StrictTVar m (Map peer (ChainSyncClientHandle m blk)) → SlotNo → STM m (Context m peer blk)
- mkJumping ∷ (MonadSTM m, Eq peer, LedgerSupportsProtocol blk) ⇒ PeerContext m peer blk → Jumping m blk
- noJumping ∷ MonadSTM m ⇒ Jumping m blk
- registerClient ∷ (Ord peer, LedgerSupportsProtocol blk, IOLike m) ⇒ Context m peer blk → peer → StrictTVar m (ChainSyncState blk) → (StrictTVar m (ChainSyncJumpingState m blk) → ChainSyncClientHandle m blk) → STM m (PeerContext m peer blk)
- unregisterClient ∷ (MonadSTM m, Ord peer, LedgerSupportsProtocol blk) ⇒ PeerContext m peer blk → STM m ()
Documentation
type Context = ContextWith () () Source #
A non-specific, generic context for ChainSync jumping.
data ContextWith peerField handleField m peer blk Source #
A context for ChainSync jumping
Invariants:
- If
handlesVar
is not empty, then there is exactly one dynamo in it. - There is at most one objector in
handlesVar
. - If there exist
FoundIntersection
jumpers inhandlesVar
, then there is an objector and the intersection of the objector with the dynamo is at least as old as the oldest intersection of theFoundIntersection
jumpers with the dynamo.
Context | |
|
data Instruction blk Source #
Instruction from the jumping governor, either to run normal ChainSync, or to jump to follow a dynamo with the given fragment, or to restart ChainSync.
RunNormally | |
Restart | The restart instruction restarts the ChainSync protocol. This is necessary when disengaging a peer of which we know no point that we could set the intersection of the ChainSync server to. |
JumpInstruction !(JumpInstruction blk) | Jump to the tip of the given fragment. |
Instances
data JumpInstruction blk Source #
JumpTo !(JumpInfo blk) | |
JumpToGoodPoint !(JumpInfo blk) | Used to set the intersection of the ChainSync servers of starting objectors and dynamos. Otherwise, the ChainSync server wouldn't know which headers to start serving. |
Instances
data JumpResult blk Source #
The result of a jump request, either accepted or rejected.
AcceptedJump !(JumpInstruction blk) | |
RejectedJump !(JumpInstruction blk) |
Instances
Hooks for ChainSync jumping.
Jumping | |
|
Instances
∷ MonadSTM m | |
⇒ StrictTVar m (Map peer (ChainSyncClientHandle m blk)) | |
→ SlotNo | The size of jumps, in number of slots. |
→ STM m (Context m peer blk) |
mkJumping ∷ (MonadSTM m, Eq peer, LedgerSupportsProtocol blk) ⇒ PeerContext m peer blk → Jumping m blk Source #
Create the callbacks for a given peer.
∷ (Ord peer, LedgerSupportsProtocol blk, IOLike m) | |
⇒ Context m peer blk | |
→ peer | |
→ StrictTVar m (ChainSyncState blk) | |
→ (StrictTVar m (ChainSyncJumpingState m blk) → ChainSyncClientHandle m blk) | A function to make a client handle from a jumping state. |
→ STM m (PeerContext m peer blk) |
Register a new ChainSync client to a context, returning a PeerContext
for
that peer. If there is no dynamo, the peer starts as dynamo; otherwise, it
starts as a jumper.
unregisterClient ∷ (MonadSTM m, Ord peer, LedgerSupportsProtocol blk) ⇒ PeerContext m peer blk → STM m () Source #
Unregister a client from a PeerContext
; this might trigger the election
of a new dynamo or objector if the peer was one of these two.