State RFC: Handle duplicate block edge cases (#1136)
Handle the following duplicate block edge cases: * duplicate in finalized state * duplicate in queue * Handle the broadcast channel correctly
This commit is contained in:
parent
e2d67fc9e7
commit
fd0fac3a61
|
@ -524,49 +524,62 @@ The state service uses the following entry points:
|
||||||
|
|
||||||
## Committing non-finalized blocks
|
## Committing non-finalized blocks
|
||||||
|
|
||||||
Given the above structures for manipulating the non-finalized state new
|
New `non-finalized` blocks are commited as follows:
|
||||||
`non-finalized` blocks are commited as follows:
|
|
||||||
|
|
||||||
### `fn queue_and_commit_non_finalized_blocks(&mut self, new: QueuedBlock)`
|
### `pub(super) fn queue_and_commit_non_finalized_blocks(&mut self, new: Arc<Block>) -> tokio::sync::broadcast::Receiver<block::Hash>`
|
||||||
|
|
||||||
1. If the block itself exists in the finalized chain, it has already been
|
1. If a duplicate block exists in the queue:
|
||||||
successfully verified:
|
- Find the `QueuedBlock` for that existing duplicate block
|
||||||
- broadcast `Ok(block.hash())` via `block.rsp_tx`, and return
|
- Create an extra receiver for the existing block, using `block.rsp_tx.subscribe`,
|
||||||
|
- Drop the newly received duplicate block
|
||||||
|
- Return the extra receiver, so it can be used in the response future for the duplicate block request
|
||||||
|
|
||||||
2. Add `block` to `self.queued_blocks`
|
2. Create a `QueuedBlock` for `block`:
|
||||||
|
- Create a `tokio::sync::broadcast` channel
|
||||||
|
- Use that channel to create a `QueuedBlock` for `block`.
|
||||||
|
|
||||||
3. If `block.header.previous_block_hash` is not present in the finalized or
|
3. If a duplicate block exists in a non-finalized chain, or the finalized chain,
|
||||||
non-finalized state return early
|
it has already been successfully verified:
|
||||||
|
- Broadcast `Ok(block.hash())` via `block.rsp_tx`, and return the receiver for the block's channel
|
||||||
|
|
||||||
4. Else iteratively attempt to process queued blocks by their parent hash
|
4. Add `block` to `self.queued_blocks`
|
||||||
|
|
||||||
|
5. If `block.header.previous_block_hash` is not present in the finalized or
|
||||||
|
non-finalized state:
|
||||||
|
- Return the receiver for the block's channel
|
||||||
|
|
||||||
|
6. Else iteratively attempt to process queued blocks by their parent hash
|
||||||
starting with `block.header.previous_block_hash`
|
starting with `block.header.previous_block_hash`
|
||||||
|
|
||||||
5. While there are recently commited parent hashes to process
|
7. While there are recently commited parent hashes to process
|
||||||
- Dequeue all blocks waiting on `parent` with `let queued_children =
|
- Dequeue all blocks waiting on `parent` with `let queued_children =
|
||||||
self.queued_blocks.dequeue_children(parent);`
|
self.queued_blocks.dequeue_children(parent);`
|
||||||
- for each queued `block`
|
- for each queued `block`
|
||||||
- **Run contextual validation** on `block`
|
- **Run contextual validation** on `block`
|
||||||
- If the block fails contextual validation return the result over the
|
- contextual validation will reject blocks that are past the reorg limit,
|
||||||
|
because the finalized block at that height is already known.
|
||||||
|
- If the block fails contextual validation send the result to the
|
||||||
associated channel
|
associated channel
|
||||||
- Else if the block's previous hash is the finalized tip add to the
|
- Else if the block's previous hash is the finalized tip add to the
|
||||||
non-finalized state with `self.mem.commit_new_chain(block)`
|
non-finalized state with `self.mem.commit_new_chain(block)`
|
||||||
- Else add the new block to an existing non-finalized chain or new fork
|
- Else add the new block to an existing non-finalized chain or new fork
|
||||||
with `self.mem.commit_block(block);`
|
with `self.mem.commit_block(block);`
|
||||||
- Return `Ok(hash)` over the associated channel to indicate the block
|
- Send `Ok(hash)` over the associated channel to indicate the block
|
||||||
was successfully commited
|
was successfully commited
|
||||||
- Add `block.hash` to the set of recently commited parent hashes to
|
- Add `block.hash` to the set of recently commited parent hashes to
|
||||||
process
|
process
|
||||||
|
|
||||||
6. While the length of the non-finalized portion of the best chain is greater
|
8. While the length of the non-finalized portion of the best chain is greater
|
||||||
than the reorg limit
|
than the reorg limit
|
||||||
- Remove the lowest height block from the non-finalized state with
|
- Remove the lowest height block from the non-finalized state with
|
||||||
`self.mem.finalize();`
|
`self.mem.finalize();`
|
||||||
- Commit that block to the finalized state with
|
- Commit that block to the finalized state with
|
||||||
`self.sled.commit_finalized_direct(finalized);`
|
`self.sled.commit_finalized_direct(finalized);`
|
||||||
|
|
||||||
7. Prune orphaned blocks from `self.queued_blocks` with
|
9. Prune orphaned blocks from `self.queued_blocks` with
|
||||||
`self.queued_blocks.prune_by_height(finalized_height);`
|
`self.queued_blocks.prune_by_height(finalized_height);`
|
||||||
|
|
||||||
|
10. Return the receiver for the block's channel
|
||||||
|
|
||||||
## Sled data structures
|
## Sled data structures
|
||||||
[sled]: #sled
|
[sled]: #sled
|
||||||
|
@ -625,7 +638,7 @@ Committing a block to the sled state should be implemented as a wrapper around
|
||||||
a function also called by [`Request::CommitBlock`](#request-commit-block),
|
a function also called by [`Request::CommitBlock`](#request-commit-block),
|
||||||
which should:
|
which should:
|
||||||
|
|
||||||
### `pub fn queue_and_commit_finalized_blocks(&mut self, queued_block: QueuedBlock)`
|
### `pub(super) fn queue_and_commit_finalized_blocks(&mut self, queued_block: QueuedBlock)`
|
||||||
|
|
||||||
1. Obtain the highest entry of `hash_by_height` as `(old_height, old_tip)`.
|
1. Obtain the highest entry of `hash_by_height` as `(old_height, old_tip)`.
|
||||||
Check that `block`'s parent hash is `old_tip` and its height is
|
Check that `block`'s parent hash is `old_tip` and its height is
|
||||||
|
@ -643,8 +656,8 @@ check that `block`'s parent hash is `null` (all zeroes) and its height is `0`.
|
||||||
|
|
||||||
3. If the block is a genesis block, skip any transaction updates.
|
3. If the block is a genesis block, skip any transaction updates.
|
||||||
|
|
||||||
(Due to a [bug in zcashd](https://github.com/ZcashFoundation/zebra/issues/559), genesis block transactions
|
(Due to a [bug in zcashd](https://github.com/ZcashFoundation/zebra/issues/559),
|
||||||
are ignored during validation.)
|
genesis block transactions are ignored during validation.)
|
||||||
|
|
||||||
4. Update the `sprout_anchors` and `sapling_anchors` trees with the Sprout and Sapling anchors.
|
4. Update the `sprout_anchors` and `sapling_anchors` trees with the Sprout and Sapling anchors.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue