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

merge queue: embarking main (0b8bef3) and #9260 together #9278

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions zebra-state/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[error("block is not contextually valid: {}", .0)]
pub struct CommitSemanticallyVerifiedError(#[from] ValidateContextError);

/// An error describing the reason a block or its descendants could not be reconsidered after
/// potentially being invalidated from the chain_set.
#[derive(Debug, Error)]
pub enum ReconsiderError {
#[error("Block with hash {0} was not previously invalidated")]
NonPreviouslyInvalidatedBlock(block::Hash),
#[error("Parent chain not found for block {0}")]
ParentChainNotFound(block::Hash),
#[error("{0}")]
ValidationError(#[from] ValidateContextError),
}

/// An error describing why a block failed contextual validation.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
Expand Down
55 changes: 54 additions & 1 deletion zebra-state/src/service/non_finalized_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ use std::{
use zebra_chain::{
block::{self, Block, Hash},
parameters::Network,
sprout, transparent,
sprout::{self},
transparent,
};

use crate::{
constants::MAX_NON_FINALIZED_CHAIN_FORKS,
error::ReconsiderError,
request::{ContextuallyVerifiedBlock, FinalizableBlock},
service::{check, finalized_state::ZebraDb},
SemanticallyVerifiedBlock, ValidateContextError,
Expand Down Expand Up @@ -301,6 +303,57 @@ impl NonFinalizedState {
self.update_metrics_bars();
}

/// Reconsiders a previously invalidated block and its descendants into the non-finalized state
/// based on a block_hash. Reconsidered blocks are inserted into the previous chain and re-inserted
/// into the chain_set.
pub fn reconsider_block(&mut self, block_hash: block::Hash) -> Result<(), ReconsiderError> {
// Get the invalidated blocks that were invalidated by the given block_hash
let Some(invalidated_blocks) = self.invalidated_blocks.get(&block_hash) else {
return Err(ReconsiderError::NonPreviouslyInvalidatedBlock(block_hash));
};

let mut blocks = invalidated_blocks.clone();
let mut_blocks = Arc::make_mut(&mut blocks);

let Some(invalidated_root) = mut_blocks.first() else {
return Err(ReconsiderError::NonPreviouslyInvalidatedBlock(block_hash));
};

// Find and fork the parent chain of the invalidated_root. Update the parent chain
// with the invalidated_descendants
let prev_block_hash = invalidated_root.block.header.previous_block_hash;
let Ok(parent_chain) = self.parent_chain(prev_block_hash) else {
return Err(ReconsiderError::ParentChainNotFound(block_hash));
};

let Some(new_chain) = parent_chain.fork(parent_chain.non_finalized_tip_hash()) else {
return Err(ReconsiderError::ValidationError(
ValidateContextError::NonSequentialBlock {
candidate_height: invalidated_root.height,
parent_height: parent_chain.non_finalized_tip_height(),
},
));
};

let mut chain = new_chain
.push(mut_blocks.remove(0))
.map_err(ReconsiderError::ValidationError)?;
for block in mut_blocks {
chain = chain
.push(block.clone())
.map_err(ReconsiderError::ValidationError)?;
}

self.insert_with(Arc::new(chain), |chain_set| {
chain_set.retain(|c| c != &parent_chain)
});

self.update_metrics_for_chains();
self.update_metrics_bars();

Ok(())
}

/// Commit block to the non-finalized state as a new chain where its parent
/// is the finalized tip.
#[tracing::instrument(level = "debug", skip(self, finalized_state, prepared))]
Expand Down
4 changes: 2 additions & 2 deletions zebra-state/src/service/non_finalized_state/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,15 @@ impl Chain {
(block, treestate)
}

// Returns the block at the provided height and all of its descendant blocks.
/// Returns the block at the provided height and all of its descendant blocks.
pub fn child_blocks(&self, block_height: &block::Height) -> Vec<ContextuallyVerifiedBlock> {
self.blocks
.range(block_height..)
.map(|(_h, b)| b.clone())
.collect()
}

// Returns a new chain without the invalidated block or its descendants.
/// Returns a new chain without the invalidated block or its descendants.
pub fn invalidate_block(
&self,
block_hash: block::Hash,
Expand Down
87 changes: 85 additions & 2 deletions zebra-state/src/service/non_finalized_state/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
Ok(())
}

#[test]
fn invalidate_block_removes_block_and_descendants_from_chain() -> Result<()> {
let _init_guard = zebra_test::init();

for network in Network::iter() {
invalidate_block_removes_block_and_descendants_from_chain_for_network(network)?;
}

Ok(())
}

fn invalidate_block_removes_block_and_descendants_from_chain_for_network(
network: Network,
) -> Result<()> {
Expand Down Expand Up @@ -294,16 +305,88 @@ fn invalidate_block_removes_block_and_descendants_from_chain_for_network(
}

#[test]
fn invalidate_block_removes_block_and_descendants_from_chain() -> Result<()> {
fn reconsider_block_and_reconsider_chain_correctly_reconsiders_blocks_and_descendants() -> Result<()>
{
let _init_guard = zebra_test::init();

for network in Network::iter() {
invalidate_block_removes_block_and_descendants_from_chain_for_network(network)?;
reconsider_block_inserts_block_and_descendants_into_chain_for_network(network.clone())?;
}

Ok(())
}

fn reconsider_block_inserts_block_and_descendants_into_chain_for_network(
network: Network,
) -> Result<()> {
let block1: Arc<Block> = Arc::new(network.test_block(653599, 583999).unwrap());
let block2 = block1.make_fake_child().set_work(10);
let block3 = block2.make_fake_child().set_work(1);

let mut state = NonFinalizedState::new(&network);
let finalized_state = FinalizedState::new(
&Config::ephemeral(),
&network,
#[cfg(feature = "elasticsearch")]
false,
);

let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
finalized_state.set_finalized_value_pool(fake_value_pool);

state.commit_new_chain(block1.clone().prepare(), &finalized_state)?;
state.commit_block(block2.clone().prepare(), &finalized_state)?;
state.commit_block(block3.clone().prepare(), &finalized_state)?;

assert_eq!(
state
.best_chain()
.unwrap_or(&Arc::new(Chain::default()))
.blocks
.len(),
3
);

// Invalidate block2 to update the invalidated_blocks NonFinalizedState
state.invalidate_block(block2.hash());

// Perform checks to ensure the invalidated_block and descendants were added to the invalidated_block
// state
let post_invalidated_chain = state.best_chain().unwrap();

assert_eq!(post_invalidated_chain.blocks.len(), 1);
assert!(
post_invalidated_chain.contains_block_hash(block1.hash()),
"the new modified chain should contain block1"
);

assert!(
!post_invalidated_chain.contains_block_hash(block2.hash()),
"the new modified chain should not contain block2"
);
assert!(
!post_invalidated_chain.contains_block_hash(block3.hash()),
"the new modified chain should not contain block3"
);

// Reconsider block2 and check that both block2 and block3 were `reconsidered` into the
// best chain
state.reconsider_block(block2.hash())?;

let best_chain = state.best_chain().unwrap();

assert!(
best_chain.contains_block_hash(block2.hash()),
"the best chain should again contain block2"
);
assert!(
best_chain.contains_block_hash(block3.hash()),
"the best chain should again contain block3"
);

Ok(())
}

#[test]
// This test gives full coverage for `take_chain_if`
fn commit_block_extending_best_chain_doesnt_drop_worst_chains() -> Result<()> {
Expand Down
Loading