Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(state): Avoid temporary failures verifying the first non-finalized block or attempting to fork the chain before the final checkpoint #6810

Merged
merged 7 commits into from
Aug 21, 2023
27 changes: 13 additions & 14 deletions zebra-state/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,29 +707,27 @@ impl StateService {
// Tell the block write task to stop committing checkpoint verified blocks to the finalized state,
// and move on to committing semantically verified blocks to the non-finalized state.
std::mem::drop(self.finalized_block_write_sender.take());

// Remove any checkpoint-verified block hashes from `non_finalized_block_write_sent_hashes`.
self.non_finalized_block_write_sent_hashes = SentHashes::default();
// Mark `SentHashes` as usable by the `can_fork_chain_at()` method.
self.non_finalized_block_write_sent_hashes
.can_fork_chain_at_hashes = true;
// Send blocks from non-finalized queue
self.send_ready_non_finalized_queued(self.finalized_block_write_last_sent_hash);
// We've finished committing checkpoint verified blocks to finalized state, so drop any repeated queued blocks.
self.clear_finalized_block_queue(
"already finished committing checkpoint verified blocks: dropped duplicate block, \
block is already committed to the state",
);
}

// TODO: avoid a temporary verification failure that can happen
// if the first non-finalized block arrives before the last finalized block is committed
// (#5125)
if !self.can_fork_chain_at(&parent_hash) {
} else if !self.can_fork_chain_at(&parent_hash) {
tracing::trace!("unready to verify, returning early");
return rsp_rx;
}

if self.finalized_block_write_sender.is_none() {
} else if self.finalized_block_write_sender.is_none() {
// Wait until block commit task is ready to write non-finalized blocks before dequeuing them
self.send_ready_non_finalized_queued(parent_hash);

let finalized_tip_height = self.read_service.db.finalized_tip_height().expect(
"Finalized state must have at least one block before committing non-finalized state",
);
"Finalized state must have at least one block before committing non-finalized state",
);

self.non_finalized_state_queued_blocks
.prune_by_height(finalized_tip_height);
Expand All @@ -743,7 +741,8 @@ impl StateService {

/// Returns `true` if `hash` is a valid previous block hash for new non-finalized blocks.
fn can_fork_chain_at(&self, hash: &block::Hash) -> bool {
self.non_finalized_block_write_sent_hashes.contains(hash)
self.non_finalized_block_write_sent_hashes
.can_fork_chain_at(hash)
|| &self.read_service.db.finalized_tip_hash() == hash
}

Expand Down
11 changes: 11 additions & 0 deletions zebra-state/src/service/queued_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ pub(crate) struct SentHashes {

/// Known UTXOs.
known_utxos: HashMap<transparent::OutPoint, transparent::Utxo>,

/// Whether the hashes in this struct can be used check if the chain can be forked.
/// This is set to false until all checkpoint-verified block hashes have been pruned.
pub(crate) can_fork_chain_at_hashes: bool,
}

impl SentHashes {
Expand Down Expand Up @@ -335,6 +339,8 @@ impl SentHashes {
});

self.sent.shrink_to_fit();
self.known_utxos.shrink_to_fit();
self.bufs.shrink_to_fit();

self.update_metrics_for_cache();
}
Expand All @@ -344,6 +350,11 @@ impl SentHashes {
self.sent.contains_key(hash)
}

/// Returns true if the chain can be forked at the provided hash
pub fn can_fork_chain_at(&self, hash: &block::Hash) -> bool {
self.can_fork_chain_at_hashes && self.contains(hash)
}

/// Update sent block metrics after a block is sent.
fn update_metrics_for_block(&self, height: block::Height) {
metrics::counter!("state.memory.sent.block.count", 1);
Expand Down