Update consensus docs (#32482)

Update tower-bft docs
This commit is contained in:
Ashwin Sekar 2023-07-14 19:08:11 -07:00 committed by GitHub
parent cfb028819a
commit 8c480d6d2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 31 deletions

View File

@ -2,35 +2,59 @@
title: Managing Forks
---
The ledger is permitted to fork at slot boundaries. The resulting data structure forms a tree called a _blockstore_. When the validator interprets the blockstore, it must maintain state for each fork in the chain. We call each instance an _active fork_. It is the responsibility of a validator to weigh those forks, such that it may eventually select a fork.
The ledger is permitted to fork at slot boundaries. The resulting data structure forms a tree called a _blockstore_. When the validator interprets the blockstore, it must maintain state for each fork in the chain. It is the responsibility of a validator to weigh those forks, such that it may eventually select a fork. Details for selection and voting on these forks can be found in [Tower Bft](../implemented-proposals/tower-bft.md)
A validator selects a fork by submitting a vote to a slot leader on that fork. The vote commits the validator for a duration of time called a _lockout period_. The validator 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 validator 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 an 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 validator votes, any checkpoints beyond the rollback depth become unreachable. That is, there is no scenario in which the validator 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.
## Forks
## Active Forks
A fork is as a sequence of slots originating from some root. For example:
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:
```
2 - 4 - 6 - 8
/
0 - 1 12 - 13
\ /
3 - 5
\
7 - 9 - 10 - 11
```
![Forks](/img/forks.svg)
The following sequences are forks:
The following sequences are _active forks_:
- {4, 2, 1}
- {5, 2, 1}
- {6, 3, 1}
- {7, 3, 1}
```
- {0, 1, 2, 4, 6, 8}
- {0, 1, 3, 5, 12, 13}
- {0, 1, 3, 5, 7, 9, 10, 11}
```
## Pruning and Squashing
A validator 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 validator 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.
As the chain grows, storing the local forks view becomes detrimental to performance. Fortunately we can take advantage of the properties of tower bft roots to prune this data structure. Recall a root is a slot that has reached the max lockout depth. The assumption is that this slot has accrued enough lockout that it would be impossible to roll this slot back.
Starting from the example above, with a rollback depth of 2, consider a vote on 5 versus a vote on 6. First, a vote on 5:
Thus, the validator prunes forks that do not originate from its local root, and then takes the opportunity to minimize its memory usage by squashing any nodes it can into the root. Although not necessary for consensus, to enable some RPC use cases the validator chooses to keep ancestors of its local root up until the last slot rooted by the super majority of the cluster. We call this the super majority root (SMR).
![Forks after pruning](/img/forks-pruned.svg)
Starting from the above example imagine a max lockout depth of 3. Our validator votes on slots `0, 1, 3, 5, 7, 9`. Upon the final vote at `9`, our local root is `3`. Assume the latest super majority root is `0`. After pruning this is our local fork view.
The new root is 2, and any active forks that are not descendants from 2 are pruned.
```
SMR
0 - 1 12 - 13
\ /
3 - 5
ROOT \
7 - 9 - 10 - 11
```
Alternatively, a vote on 6:
Now imagine we vote on `10`, which roots `5`. At the same time the cluster catches up and the latest super majority root is now `3`. After pruning this is our local fork view.
![Forks](/img/forks-pruned2.svg)
```
12 - 13
/
3 - 5 ROOT
SMR \
7 - 9 - 10 - 11
```
The tree remains with a root of 1, since the active fork starting at 6 is only 2 checkpoints from the root.
Finally a vote on `11` will root `7`, pruning the final fork
```
3 - 5 - 7 - 9 - 10 - 11
SMR ROOT
```

View File

@ -43,7 +43,7 @@ The basic idea to this approach is to stack consensus votes and double lockouts.
We call this stack the Vote Tower.
When a vote is added to the tower, the lockouts of all the previous votes in the tower are doubled \(more on this in [Vote Tower](tower-bft.md#Vote Tower)\). With each new vote, a validator commits the previous votes to an ever-increasing lockout. At 32 votes we can consider the vote to be at `max lockout` any votes with a lockout equal to or above `1<<32` are dequeued \(FIFO\). Dequeuing a vote is the trigger for a reward. If a vote expires before it is dequeued, it and all the votes above it are popped \(LIFO\) from the vote tower. The validator needs to start rebuilding the tower from that point.
When a vote is added to the tower, the lockouts of all the previous votes in the tower are doubled (more on this in [Vote Tower](#vote-tower)). With each new vote, a validator commits the previous votes to an ever-increasing lockout. At 32 votes we can consider the vote to be at `max lockout` any votes with a lockout equal to or above `1<<32` are dequeued \(FIFO\). Dequeuing a vote is the trigger for a reward. If the vote on the top of the tower expires before it is dequeued, it and subsequent expired votes are popped in a LIFO fashion from the vote tower. The validator needs to start rebuilding the tower from that point.
### Vote Tower
@ -76,14 +76,23 @@ _Vote 6_ is at slot 10
| 2 | 2 | 8 | 10 |
| 1 | 1 | 16 | 17 |
At slot 10 the new votes caught up to the previous votes. But _vote 2_ expires at 10, so the when _vote 7_ at slot 11 is applied the votes including and above _vote 2_ will be popped.
At slot 10 the new votes caught up to the previous votes. When _vote 7_ at slot 11 is applied we scan top down to pop expired votes. Although _vote 2_ has expired, since _vote 6_ has not expired, we do not continue scanning. Finally we have reached a new stack depth, lockouts are doubled
| vote | vote slot | lockout | lock expiration slot |
| ---: | --------: | ------: | -------------------: |
| 7 | 11 | 2 | 13 |
| 1 | 1 | 16 | 17 |
| 6 | 10 | 4 | 14 |
| 5 | 9 | 8 | 17 |
| 2 | 2 | 16 | 18 |
| 1 | 1 | 32 | 33 |
The lockout for vote 1 will not increase from 16 until the tower contains 5 votes.
Finally we have _vote 8_ at slot 18, this leads to the expiry of _vote 7_, _vote 6_, and _vote 5_.
| vote | vote slot | lockout | lock expiration slot |
| ---: | --------: | ------: | -------------------: |
| 8 | 18 | 2 | 20 |
| 2 | 2 | 16 | 18 |
| 1 | 1 | 32 | 33 |
### Cost of Rollback
@ -143,20 +152,21 @@ ancestors.
### Voting Algorithm
Each validator maintains a vote tower `T` which follows the rules described above in `[Vote Tower](tower-bft.md#Vote Tower)`, which is a sequence of blocks it has voted for (initially empty). The variable `l` records the length of the stack. For each entry in the tower, denoted by `B = T(x)` for `x < l` where `B` is the `xth` entry in the tower, we record also a value `lockexp(B)`.
Each validator maintains a vote tower `T` which follows the rules described above in [Vote Tower](#vote-tower), which is a sequence of blocks it has voted for (initially empty). The variable `l` records the length of the stack. For each entry in the tower, denoted by `B = T(x)` for `x < l` where `B` is the `xth` entry in the tower, we record also a value `confcount(B)`. Define the lock expiration slot `lockexp(B) := slot(B) + 2 ^ confcount(B)`.
The validator `i` runs a voting loop as as follows. Let `B` be the heaviest
block returned by the fork choice rule above `[Fork Choice](tower-bft.md#Fork Choice)`. If `i` has not voted for `B` before, then `i` votes for `B` so long as the following conditions are satisfied:
block returned by the fork choice rule above [Fork Choice](#fork-choice). If `i` has not voted for `B` before, then `i` votes for `B` so long as the following conditions are satisfied:
1. Respecting lockouts: For any block `B` in the tower that is not an ancestor of `B`, `lockexp(B) ≤ slot(B)`.
2. Threshold check: Described above in `[Threshold Check](tower-bft.md#Threshold Check)`
2. Threshold check: Described above in [Threshold Check](#threshold-check)
3. Switching threshold: Have sufficiently many votes on other forks if switching forks. Let `Btop` denote the block at the top of the stack. If `Btop` is not an ancestor of `B`, then:
- Let `VBtop V` be the set of votes on `Btop` or ancestors or descendents of `Btop`.
- We need `|V \VBtop | > 38%`. More details on this can be found in `[Optimistic Confirmation](optimistic_confirmation.md#Primitives)`
- Let `VBtop V` be the set of votes on `Btop` or ancestors or descendents of `Btop`.
- We need `|V \ VBtop | > 38%`. More details on this can be found in [Optimistic Confirmation](../proposals/optimistic_confirmation.md)
If all the conditions are satisfied and validator `i` votes for block `B` then it adjusts its tower as follows (same rules described above in `[Vote Tower](tower-bft.md#Vote Tower)`).
1. Add block to tower. `T(l) := B`, `lockexp(B) := slot(B) + 2`, and sets `l := l + 1`.
2. Remove expired blocks. For each element `B = S(x)` for `x < l 1`, if `lockexp(B) ≤ slot(B)`, remove `B` from the tower.
If all the conditions are satisfied and validator `i` votes for block `B` then it adjusts its tower as follows (same rules described above in [Vote Tower](#vote-tower)).
1. Remove expired blocks top down. Let `x := l - 1`. While `x >= 0 && lockexp(T(x)) < slot(B)`, remove `T(x)` from the tower, and set `l := l - 1` and `x := x - 1`.
2. Add block to tower. `T(l) := B`, `confcount(B) := 1`, and set `l := l + 1`.
3. Double lockouts. For each element `B = T(x)` if `l > x + confcount(B)`, then `confcount(B) := confcount(B) + 1`.
## PoH ASIC Resistance
@ -170,4 +180,4 @@ An attacker generates a concurrent fork from an older block to try to rollback t
- 2 votes have a lockout of 4 slots. Concurrent fork must be at least 4 slots ahead and produced in 2 slots. Therefore requires an ASIC 2x faster.
- 3 votes have a lockout of 8 slots. Concurrent fork must be at least 8 slots ahead and produced in 3 slots. Therefore requires an ASIC 2.6x faster.
- 10 votes have a lockout of 1024 slots. 1024/10, or 102.4x faster ASIC.
- 20 votes have a lockout of 2^20 slots. 2^20/20, or 52,428.8x faster ASIC.
- 20 votes have a lockout of 2^20 slots. 2^20/20, or 52,428.8x faster ASIC.