This repository has been archived by the owner on Nov 15, 2023. It is now read-only.
Fork-choice for parachains based on approvals and disputes #3235
Labels
S0-design
PR/Issue is in the design stage
Problem Statement
One of the goals of parachain consensus is that we never finalize any parachain block which is not considered valid. That means that we have to monitor approval and dispute state and use that to determine not only which blocks we vote to finalize but also which blocks we choose to build on.
We need two rules, one for choosing which block to build on top of, and one for choosing which block to finalize.
Block Authoring Parent Rule:
Finality Target Rule:
These two rules, when used in conjunction with each other, will provide the following properties:
Revert Signals
We assume that chains which contain invalid candidates eventually issue digests indicating that they should be abandoned as disputes conclude on these chains. These signals take the form
RevertSignal(BlockNumber)
, appearing in the block header, which indicates the ancestor of the signaling block which contains a bad candidate and should be reverted. A block may contain multiple revert signals targeting different ancestors.Solution
Create a
ForkChoiceSubsystem
which wraps a database and implements the block authoring parent rule. This can be wrapped by aSelectChain
implementation which will can be plugged into BABE and GRANDPA. The additional constraints on GRANDPA voting can be implemented via the approval voting and dispute coordinator subsystems.Data structures:
DB Schema:
Stagnation
Stagnation refers to blocks which go a long time without being approved. These are typically blocks which contain invalid candidates or the result of bugs in the approval voting subsystems.
A block is stagnant in the fork-choice rule if either
Some(Stagnant(true))
.We assume that we have two constants:
Reversion
A block is explicitly reverted if one of its descendants issues a revert signal targeting its block number.
A block is reverted if it is explicitly reverted or is the descendant of an unfinalized explicitly reverted block.
Blocks which contain candidates that have lost disputes are explicitly reverted by the runtime code handling the import of disputes. The descendants of those blocks are implicitly reverted.
Viable Leaf
A block is viable if:
Note that a block can only be viable if all of its unfinalized ancestors are viable as well.
A block is a viable leaf if:
Note that it is possible to determine whether a block is viable by inspecting its entry and only its entry. Furthermore, it is possible to determine parent viability from a child's entry. All iteration is done during updates to the state, while queries to the state are cheap. This is designed to reflect the access patterns: the selection of a block to build upon or finalize should be quick and should not involve any expensive iteration.
Operations
Import the entire unknown ancestry of the block, extracting relevant info. Blocks are imported in ascending order. The initial approval state for new blocks should be
Some(not_stagnant)
, withearliest_stagnant_ancestor
fetched from the parent andNone
if the parent is not in the database. The initial reversion state for new blocks should beexplicitly_reverted = false
andearliest_reverted_ancestor
based on the parent.Add children to any blocks that already exist.
For each revert signal in any new block, iterate backwards to the referenced ancestor, ignoring if finalized, and set
explicitly_reverted = true
. If necessary, iterate forwards from the ancestor into all descendants and update theearliest_reverted_ancestor
.Add each imported block to the
StagnantQueue
map with the slot number at which it will become stagnant -slot + STAGNANT_THRESHOLD
Update the set of active leaves:
Remove all block entries with number older than the finalized block number.
Remove all block entries with the same number as the finalized block but with different hash, and all of their children.
No entries from
StagnantQueue
are pruned here. They will remain stale but will be cleaned up later by the stagnant detection task.Remove the finalized block’s entry:
If stagnant, iterate recursively through all descendants and update the
earliest_stagnant_ancestor
as necessary and correct for each block.If explicitly reverted, iterate recursively through all descendants and update the
earliest_reverted_ancestor
as necessary and correct for each block.Update the set of active leaves:
It’s also worth noting that the only way a non-viable block can be finalized is if our view in the network vastly differs from the majority due to the slow spread of information, and that typically the finalized block is viable and finalization does not require iteration.
We assume that the approval voting subsystem sends signals about each approved block, including blocks which are instantaneously approved by virtue of including no candidates or similar protocol constraints.
The approved block should be loaded and its approval status should change to
None
. If the approved block was stagnant and theearliest_stagnant_ancestor
of the approved block isNone
, then descendants should be visited recursively and have theirearliest_stagnant_ancestor
updated accordingly.Update the set of active leaves:
earliest_stagnant_ancestor
update of descendants.This is invoked every
STAGNANT_CHECK_INTERVAL
time. Iterate and drain in ascending order every entry inStagnantQueue
up to the current timestamp.For each block hash there are 3 possibilities:
earliest_stagnant_ancestor
isNone
, iterate all descendants and setearliest_stagnant_ancestor
.Update the set of active leaves:
earliest_stagnant_ancestor
should be removed, and the parent of the block should, if unfinalized, be added to the active leaves set.The text was updated successfully, but these errors were encountered: