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:
teor 2020-10-10 11:50:12 +10:00 committed by GitHub
parent e2d67fc9e7
commit fd0fac3a61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 32 additions and 19 deletions

View File

@ -524,49 +524,62 @@ The state service uses the following entry points:
## Committing non-finalized blocks
Given the above structures for manipulating the non-finalized state new
`non-finalized` blocks are commited as follows:
New `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
successfully verified:
- broadcast `Ok(block.hash())` via `block.rsp_tx`, and return
1. If a duplicate block exists in the queue:
- Find the `QueuedBlock` for that existing duplicate block
- 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
non-finalized state return early
3. If a duplicate block exists in a non-finalized chain, or the finalized chain,
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`
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 =
self.queued_blocks.dequeue_children(parent);`
- for each queued `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
- Else if the block's previous hash is the finalized tip add to the
non-finalized state with `self.mem.commit_new_chain(block)`
- Else add the new block to an existing non-finalized chain or new fork
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
- Add `block.hash` to the set of recently commited parent hashes to
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
- Remove the lowest height block from the non-finalized state with
`self.mem.finalize();`
- Commit that block to the finalized state with
`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);`
10. Return the receiver for the block's channel
## Sled data structures
[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),
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)`.
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.
(Due to a [bug in zcashd](https://github.com/ZcashFoundation/zebra/issues/559), genesis block transactions
are ignored during validation.)
(Due to a [bug in zcashd](https://github.com/ZcashFoundation/zebra/issues/559),
genesis block transactions are ignored during validation.)
4. Update the `sprout_anchors` and `sapling_anchors` trees with the Sprout and Sapling anchors.