diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 2f0322377a..38b8cc8bc4 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -15,6 +15,7 @@ - [Synchronization](synchronization.md) - [Leader Rotation](leader-rotation.md) - [Fork Generation](fork-generation.md) + - [Managing Forks](managing-forks.md) - [Data Plane Fanout](data-plane-fanout.md) - [Ledger Replication](ledger-replication.md) - [Secure Vote Signing](vote-signing.md) @@ -39,7 +40,6 @@ - [Staking Rewards](staking-rewards.md) - [Fork Selection](fork-selection.md) - [Reliable Vote Transmission](reliable-vote-transmission.md) - - [Bank Forks](bank-forks.md) - [Persistent Account Storage](persistent-account-storage.md) - [Leader to Leader Transition](leader-leader-transition.md) - [Cluster Economics](ed_overview.md) diff --git a/book/src/bank-forks.md b/book/src/bank-forks.md deleted file mode 100644 index 771e04c7aa..0000000000 --- a/book/src/bank-forks.md +++ /dev/null @@ -1,60 +0,0 @@ -# Bank Forks - -This design describes a way to checkpoint the bank state such that it can track -multiple forks without duplicating data. It addresses the following -challenges: - -* Forks are potentially created at every slot boundary. -* Forks can be based on any previously produced block. -* Forks are eventually finalized such that rollback is impossible. -* Unreachable forks need to be pruned. - -## Architecture - -The basic design idea is to maintain a DAG of Banks checkpointed at different -slot heights. The DAG is initialized with a *root* slot height. Each -subsequent fork must descend from the root. - -## Active Forks - -An *active fork* is a direct list of connected forks that descend from the -current root to a specific fork without any descendants. - -For example: - -Forks - -The following *active forks* are in the forks DAG - -* {4, 2, 1} -* {5, 2, 1} -* {6, 3, 1} -* {7, 3, 1} - -## Squashing - -A validator votes for a frozen fork. The active fork connecting the fork to -the root is *squashed*. If the active fork is longer than -`Forks::ROLLBACK_DEPTH` the oldest two forks are squashed. The oldest fork in -the active fork is the current root, so the second oldest is a direct -descendant of the root fork. Once squashed, the current root is updated to the -root descendant. Any forks that are not descendants from the new root are -pruned since they are no longer reachable. - -Starting from the example above, consider a vote on 5 versus a vote on 6: - -Forks - -* ROLLBACK\_DEPTH=2, vote=5, *active fork*={5, 2, 1} - -Forks after pruning - -The new root is 2, and any active forks that are not descendants from 2 are -pruned. - -* ROLLBACK\_DEPTH=2, vote=6, *active fork*={6, 3, 1} - -Forks - -The tree remains with `root=1`, since the active fork starting at 6 is only 2 -forks long. diff --git a/book/src/managing-forks.md b/book/src/managing-forks.md new file mode 100644 index 0000000000..22b6eaf04f --- /dev/null +++ b/book/src/managing-forks.md @@ -0,0 +1,63 @@ +# Managing Forks in the Ledger + +The ledger is permitted to fork at slot boundaries. The resulting data +structure forms a tree called a *blocktree*. When the fullnode interprets the +blocktree, it must maintain state for each fork in the chain. We call each +instance an *active fork*. It is the responsibility of a fullnode to weigh +those forks, such that it may eventually select a fork. + +A fullnode selects a fork by submiting a vote to a slot leader on that fork. +The vote commits the fullnode for a duration of time called a *lockout period*. +The fullnode is not permitted to vote on a different fork until that lockout +period expires. Each subsequent vote on the same fork doubles the length of the +lockout period. After some cluster-configured number of votes (currently 32), +the length of the lockout period reaches what's called *max lockout*. Until the +max lockout is reached, the fullnode has the option to wait until the lockout +period is over and then vote on another fork. When it votes on another fork, it +performs a operation called *rollback*, whereby the state rolls back in time to +a shared checkpoint and then jumps forward to the tip of the fork that it just +voted on. The maximum distance that a fork may roll back is called the +*rollback depth*. Rollback depth is the number of votes required to achieve +max lockout. Whenever a fullnode votes, any checkpoints beyond the rollback +depth become unreachable. That is, there is no scenario in which the fullnode +will need to roll back beyond rollback depth. It therefore may safely *prune* +unreachable forks and *squash* all checkpoints beyond rollback depth into the +root checkpoint. + +## Active Forks + +An active fork is as a sequence of checkpoints that has a length at least one +longer than the rollback depth. The shortest fork will have a length exactly +one longer than the rollback depth. For example: + +Forks + +The following sequences are *active forks*: + +* {4, 2, 1} +* {5, 2, 1} +* {6, 3, 1} +* {7, 3, 1} + +## Pruning and Squashing + +A fullnode may vote on any checkpoint in the tree. In the diagram above, +that's every node except the leaves of the tree. After voting, the fullnode +prunes nodes that fork from a distance farther than the rollback depth and then +takes the opportunity to minimize its memory usage by squashing any nodes it +can into the root. + +Starting from the example above, wth a rollback depth of 2, consider a vote on +5 versus a vote on 6. First, a vote on 5: + +Forks after pruning + +The new root is 2, and any active forks that are not descendants from 2 are +pruned. + +Alternatively, a vote on 6: + +Forks + +The tree remains with a root of 1, since the active fork starting at 6 is only +2 checkpoints from the root.