@@ -63,11 +63,25 @@ func (v *PendingBlockVertex) Parent() (flow.Identifier, uint64) {
6363// As soon as a valid fork of certified blocks descending from the latest finalized block is observed,
6464// we pass this information to caller. Internally, the mempool utilizes the LevelledForest.
6565// PendingTree is NOT safe to use in concurrent environment.
66- // NOTE: PendingTree relies on notion of `CertifiedBlock` which is a valid block accompanied by a certifying QC (proving block validity).
67- // This works well for consensus follower as it is designed to work with certified blocks. To use this structure for consensus
68- // participant we can abstract out CertifiedBlock or replace it with a generic argument that satisfies some contract(returns View, Height, BlockID).
69- // With this change this structure can be used by consensus participant for tracking connection to the finalized state even without
70- // having QC but relying on payload validation.
66+ // Note:
67+ // - The ability to skip ahead is irrelevant for staked nodes, which continuously follow the chain.
68+ // However, light clients don't necessarily follow the chain block by block. Assume a light client
69+ // that knows the EpochCommit event, i.e. the consensus committee authorized to certify blocks. A
70+ // staked node can easily ship a proof of finalization for a block within that epoch to such a
71+ // light client. This would be much cheaper for the light client than downloading the headers for
72+ // all blocks in the epoch.
73+ // - The pending tree supports skipping ahead, as this is a more general and simpler algorithm.
74+ // Removing the ability to skip ahead would restrict the PendingTree's domain of potential
75+ // applications _and_ would require additional code and additional tests making it more complex.
76+ //
77+ // Outlook:
78+ // - At the moment, PendingTree relies on notion of a `Certified Block` which is a valid block accompanied
79+ // by a certifying QC (proving block validity). This works well for consensus follower, as it is designed
80+ // to work with certified blocks.
81+ // - In the future, we could use the PendingTree also for consensus participants. Therefore, we would need
82+ // to abstract out CertifiedBlock or replace it with a generic argument that satisfies some contract
83+ // (returns View, Height, BlockID). Then, consensus participants could use the Pending Tree without
84+ // QCs and instead fully validate inbound blocks (incl. payloads) to guarantee block validity.
7185type PendingTree struct {
7286 forest * forest.LevelledForest
7387 lastFinalizedID flow.Identifier
@@ -81,20 +95,24 @@ func NewPendingTree(finalized *flow.Header) *PendingTree {
8195 }
8296}
8397
84- // AddBlocks accepts a batch of certified blocks, adds them to the tree of pending blocks and finds blocks connected to the finalized state.
85- // This function performs processing of incoming certified blocks, implementation is split into a few different sections
86- // but tries to be optimal in terms of performance to avoid doing extra work as much as possible.
87- // This function proceeds as follows:
88- // 1. Sorts incoming batch by height. Since blocks can be submitted in random order we need to find blocks with
89- // the lowest height since they are candidates for being connected to the finalized state.
90- // 2. Filters out blocks that are already finalized.
91- // 3. Deduplicates incoming blocks. We don't store additional vertices in tree if we have that block already stored.
92- // 4. Checks for exceeding byzantine threshold. Only one certified block per view is allowed.
93- // 5. Finally, blocks with the lowest height from incoming batch that connect to the finalized state we will
94- // mark all descendants as connected, collect them and return as result of invocation.
98+ // AddBlocks accepts a batch of certified blocks, adds them to the tree of pending blocks and finds blocks connected to
99+ // the finalized state.
100+ //
101+ // Details:
102+ // Adding blocks might result in additional blocks now being connected to the latest finalized block. The returned
103+ // slice contains:
104+ // 1. the subset of `certifiedBlocks` that are connected to the finalized block
105+ // - excluding any blocks whose view is smaller or equal to the finalized block
106+ // - if a block `B ∈ certifiedBlocks` is already known to the PendingTree and connected,
107+ // `B` and all its connected descendants will be in the returned list
108+ // 2. additionally, all of the _connected_ descendants of the blocks from step 1.
109+ //
110+ // PendingTree treats its input as a potentially repetitive stream of information: repeated inputs are already
111+ // consistent with the current state. While repetitive inputs might cause repetitive outputs, the implementation
112+ // has some general heuristics to avoid extra work:
113+ // - It drops blocks whose view is smaller or equal to the finalized block
114+ // - It deduplicates incoming blocks. We don't store additional vertices in tree if we have that block already stored.
95115//
96- // This function is designed to perform resolution of connected blocks(resolved block is the one that connects to the finalized state)
97- // using incoming batch. Each block that was connected to the finalized state is reported once.
98116// Expected errors during normal operations:
99117// - model.ByzantineThresholdExceededError - detected two certified blocks at the same view
100118//
@@ -157,7 +175,7 @@ func (t *PendingTree) connectsToFinalizedBlock(block CertifiedBlock) bool {
157175// inputs might cause repetitive outputs.
158176// When a block is finalized we don't care for any blocks below it, since they were already finalized.
159177// Finalizing a block might causes the pending PendingTree to detect _additional_ blocks as now
160- // being connected to the latest finalized block. This happens of some connecting blocks are missing
178+ // being connected to the latest finalized block. This happens if some connecting blocks are missing
161179// and then a block higher than the missing blocks is finalized.
162180// In the following example, B is the last finalized block known to the PendingTree
163181//
@@ -167,8 +185,18 @@ func (t *PendingTree) connectsToFinalizedBlock(block CertifiedBlock) bool {
167185// by '←-?-?-?--' have not been received by our PendingTree. Therefore, we still consider X,Y,Z
168186// as disconnected. If the PendingTree tree is now informed that X is finalized, it can fast-
169187// forward to the respective state, as it anyway would prune all the blocks below X.
188+ // Note:
189+ // - The ability to skip ahead is irrelevant for staked nodes, which continuously follows the chain.
190+ // However, light clients don't necessarily follow the chain block by block. Assume a light client
191+ // that knows the EpochCommit event, i.e. the consensus committee authorized to certify blocks. A
192+ // staked node can easily ship a proof of finalization for a block within that epoch to such a
193+ // light client. This would be much cheaper for the light client than downloading the headers for
194+ // all blocks in the epoch.
195+ // - The pending tree supports skipping ahead, as this is a more general and simpler algorithm.
196+ // Removing the ability to skip ahead would restrict the PendingTree's its domain of potential
197+ // applications _and_ would require additional code and additional tests making it more complex.
170198//
171- // If the PendingTree detect additional blocks as descending from the latest finalized block, it
199+ // If the PendingTree detects additional blocks as descending from the latest finalized block, it
172200// returns these blocks. Returned blocks are ordered such that parents appear before their children.
173201//
174202// No errors are expected during normal operation.
0 commit comments