Integrate bank-forks proposal into the book
This commit is contained in:
parent
63477dabcd
commit
799ed24113
|
@ -15,6 +15,7 @@
|
||||||
- [Synchronization](synchronization.md)
|
- [Synchronization](synchronization.md)
|
||||||
- [Leader Rotation](leader-rotation.md)
|
- [Leader Rotation](leader-rotation.md)
|
||||||
- [Fork Generation](fork-generation.md)
|
- [Fork Generation](fork-generation.md)
|
||||||
|
- [Managing Forks](managing-forks.md)
|
||||||
- [Data Plane Fanout](data-plane-fanout.md)
|
- [Data Plane Fanout](data-plane-fanout.md)
|
||||||
- [Ledger Replication](ledger-replication.md)
|
- [Ledger Replication](ledger-replication.md)
|
||||||
- [Secure Vote Signing](vote-signing.md)
|
- [Secure Vote Signing](vote-signing.md)
|
||||||
|
@ -39,7 +40,6 @@
|
||||||
- [Staking Rewards](staking-rewards.md)
|
- [Staking Rewards](staking-rewards.md)
|
||||||
- [Fork Selection](fork-selection.md)
|
- [Fork Selection](fork-selection.md)
|
||||||
- [Reliable Vote Transmission](reliable-vote-transmission.md)
|
- [Reliable Vote Transmission](reliable-vote-transmission.md)
|
||||||
- [Bank Forks](bank-forks.md)
|
|
||||||
- [Persistent Account Storage](persistent-account-storage.md)
|
- [Persistent Account Storage](persistent-account-storage.md)
|
||||||
- [Leader to Leader Transition](leader-leader-transition.md)
|
- [Leader to Leader Transition](leader-leader-transition.md)
|
||||||
- [Cluster Economics](ed_overview.md)
|
- [Cluster Economics](ed_overview.md)
|
||||||
|
|
|
@ -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:
|
|
||||||
|
|
||||||
<img alt="Forks" src="img/forks.svg" class="center"/>
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
<img alt="Forks" src="img/forks.svg" class="center"/>
|
|
||||||
|
|
||||||
* ROLLBACK\_DEPTH=2, vote=5, *active fork*={5, 2, 1}
|
|
||||||
|
|
||||||
<img alt="Forks after pruning" src="img/forks-pruned.svg" class="center"/>
|
|
||||||
|
|
||||||
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}
|
|
||||||
|
|
||||||
<img alt="Forks" src="img/forks-pruned2.svg" class="center"/>
|
|
||||||
|
|
||||||
The tree remains with `root=1`, since the active fork starting at 6 is only 2
|
|
||||||
forks long.
|
|
|
@ -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:
|
||||||
|
|
||||||
|
<img alt="Forks" src="img/forks.svg" class="center"/>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<img alt="Forks after pruning" src="img/forks-pruned.svg" class="center"/>
|
||||||
|
|
||||||
|
The new root is 2, and any active forks that are not descendants from 2 are
|
||||||
|
pruned.
|
||||||
|
|
||||||
|
Alternatively, a vote on 6:
|
||||||
|
|
||||||
|
<img alt="Forks" src="img/forks-pruned2.svg" class="center"/>
|
||||||
|
|
||||||
|
The tree remains with a root of 1, since the active fork starting at 6 is only
|
||||||
|
2 checkpoints from the root.
|
Loading…
Reference in New Issue