Skip to content

Commit e67d347

Browse files
authored
Merge of #9167
2 parents 97460cf + a4af5d1 commit e67d347

File tree

3 files changed

+154
-4
lines changed

3 files changed

+154
-4
lines changed

zebra-state/src/service/non_finalized_state.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
};
1010

1111
use zebra_chain::{
12-
block::{self, Block},
12+
block::{self, Block, Hash},
1313
parameters::Network,
1414
sprout, transparent,
1515
};
@@ -45,6 +45,10 @@ pub struct NonFinalizedState {
4545
/// callers should migrate to `chain_iter().next()`.
4646
chain_set: BTreeSet<Arc<Chain>>,
4747

48+
/// Blocks that have been invalidated in, and removed from, the non finalized
49+
/// state.
50+
invalidated_blocks: HashMap<Hash, Arc<Vec<ContextuallyVerifiedBlock>>>,
51+
4852
// Configuration
4953
//
5054
/// The configured Zcash network.
@@ -92,6 +96,7 @@ impl Clone for NonFinalizedState {
9296
Self {
9397
chain_set: self.chain_set.clone(),
9498
network: self.network.clone(),
99+
invalidated_blocks: self.invalidated_blocks.clone(),
95100

96101
#[cfg(feature = "getblocktemplate-rpcs")]
97102
should_count_metrics: self.should_count_metrics,
@@ -112,6 +117,7 @@ impl NonFinalizedState {
112117
NonFinalizedState {
113118
chain_set: Default::default(),
114119
network: network.clone(),
120+
invalidated_blocks: Default::default(),
115121
#[cfg(feature = "getblocktemplate-rpcs")]
116122
should_count_metrics: true,
117123
#[cfg(feature = "progress-bar")]
@@ -264,6 +270,37 @@ impl NonFinalizedState {
264270
Ok(())
265271
}
266272

273+
/// Invalidate block with hash `block_hash` and all descendants from the non-finalized state. Insert
274+
/// the new chain into the chain_set and discard the previous.
275+
pub fn invalidate_block(&mut self, block_hash: Hash) {
276+
let Some(chain) = self.find_chain(|chain| chain.contains_block_hash(block_hash)) else {
277+
return;
278+
};
279+
280+
let invalidated_blocks = if chain.non_finalized_root_hash() == block_hash {
281+
self.chain_set.remove(&chain);
282+
chain.blocks.values().cloned().collect()
283+
} else {
284+
let (new_chain, invalidated_blocks) = chain
285+
.invalidate_block(block_hash)
286+
.expect("already checked that chain contains hash");
287+
288+
// Add the new chain fork or updated chain to the set of recent chains, and
289+
// remove the chain containing the hash of the block from chain set
290+
self.insert_with(Arc::new(new_chain.clone()), |chain_set| {
291+
chain_set.retain(|c| !c.contains_block_hash(block_hash))
292+
});
293+
294+
invalidated_blocks
295+
};
296+
297+
self.invalidated_blocks
298+
.insert(block_hash, Arc::new(invalidated_blocks));
299+
300+
self.update_metrics_for_chains();
301+
self.update_metrics_bars();
302+
}
303+
267304
/// Commit block to the non-finalized state as a new chain where its parent
268305
/// is the finalized tip.
269306
#[tracing::instrument(level = "debug", skip(self, finalized_state, prepared))]
@@ -586,6 +623,11 @@ impl NonFinalizedState {
586623
self.chain_set.len()
587624
}
588625

626+
/// Return the invalidated blocks.
627+
pub fn invalidated_blocks(&self) -> HashMap<block::Hash, Arc<Vec<ContextuallyVerifiedBlock>>> {
628+
self.invalidated_blocks.clone()
629+
}
630+
589631
/// Return the chain whose tip block hash is `parent_hash`.
590632
///
591633
/// The chain can be an existing chain in the non-finalized state, or a freshly

zebra-state/src/service/non_finalized_state/chain.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,26 @@ impl Chain {
359359
(block, treestate)
360360
}
361361

362+
// Returns the block at the provided height and all of its descendant blocks.
363+
pub fn child_blocks(&self, block_height: &block::Height) -> Vec<ContextuallyVerifiedBlock> {
364+
self.blocks
365+
.range(block_height..)
366+
.map(|(_h, b)| b.clone())
367+
.collect()
368+
}
369+
370+
// Returns a new chain without the invalidated block or its descendants.
371+
pub fn invalidate_block(
372+
&self,
373+
block_hash: block::Hash,
374+
) -> Option<(Self, Vec<ContextuallyVerifiedBlock>)> {
375+
let block_height = self.height_by_hash(block_hash)?;
376+
let mut new_chain = self.fork(block_hash)?;
377+
new_chain.pop_tip();
378+
new_chain.last_fork_height = None;
379+
Some((new_chain, self.child_blocks(&block_height)))
380+
}
381+
362382
/// Returns the height of the chain root.
363383
pub fn non_finalized_root_height(&self) -> block::Height {
364384
self.blocks
@@ -1600,7 +1620,7 @@ impl DerefMut for Chain {
16001620

16011621
/// The revert position being performed on a chain.
16021622
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1603-
enum RevertPosition {
1623+
pub(crate) enum RevertPosition {
16041624
/// The chain root is being reverted via [`Chain::pop_root`], when a block
16051625
/// is finalized.
16061626
Root,
@@ -1619,7 +1639,7 @@ enum RevertPosition {
16191639
/// and [`Chain::pop_tip`] functions, and fear that it would be easy to
16201640
/// introduce bugs when updating them, unless the code was reorganized to keep
16211641
/// related operations adjacent to each other.
1622-
trait UpdateWith<T> {
1642+
pub(crate) trait UpdateWith<T> {
16231643
/// When `T` is added to the chain tip,
16241644
/// update [`Chain`] cumulative data members to add data that are derived from `T`.
16251645
fn update_chain_tip_with(&mut self, _: &T) -> Result<(), ValidateContextError>;

zebra-state/src/service/non_finalized_state/tests/vectors.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::Arc;
44

55
use zebra_chain::{
66
amount::NonNegative,
7-
block::{Block, Height},
7+
block::{self, Block, Height},
88
history_tree::NonEmptyHistoryTree,
99
parameters::{Network, NetworkUpgrade},
1010
serialization::ZcashDeserializeInto,
@@ -216,6 +216,94 @@ fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
216216
Ok(())
217217
}
218218

219+
fn invalidate_block_removes_block_and_descendants_from_chain_for_network(
220+
network: Network,
221+
) -> Result<()> {
222+
let block1: Arc<Block> = Arc::new(network.test_block(653599, 583999).unwrap());
223+
let block2 = block1.make_fake_child().set_work(10);
224+
let block3 = block2.make_fake_child().set_work(1);
225+
226+
let mut state = NonFinalizedState::new(&network);
227+
let finalized_state = FinalizedState::new(
228+
&Config::ephemeral(),
229+
&network,
230+
#[cfg(feature = "elasticsearch")]
231+
false,
232+
);
233+
234+
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
235+
finalized_state.set_finalized_value_pool(fake_value_pool);
236+
237+
state.commit_new_chain(block1.clone().prepare(), &finalized_state)?;
238+
state.commit_block(block2.clone().prepare(), &finalized_state)?;
239+
state.commit_block(block3.clone().prepare(), &finalized_state)?;
240+
241+
assert_eq!(
242+
state
243+
.best_chain()
244+
.unwrap_or(&Arc::new(Chain::default()))
245+
.blocks
246+
.len(),
247+
3
248+
);
249+
250+
state.invalidate_block(block2.hash());
251+
252+
let post_invalidated_chain = state.best_chain().unwrap();
253+
254+
assert_eq!(post_invalidated_chain.blocks.len(), 1);
255+
assert!(
256+
post_invalidated_chain.contains_block_hash(block1.hash()),
257+
"the new modified chain should contain block1"
258+
);
259+
260+
assert!(
261+
!post_invalidated_chain.contains_block_hash(block2.hash()),
262+
"the new modified chain should not contain block2"
263+
);
264+
assert!(
265+
!post_invalidated_chain.contains_block_hash(block3.hash()),
266+
"the new modified chain should not contain block3"
267+
);
268+
269+
let invalidated_blocks_state = &state.invalidated_blocks;
270+
assert!(
271+
invalidated_blocks_state.contains_key(&block2.hash()),
272+
"invalidated blocks map should reference the hash of block2"
273+
);
274+
275+
let invalidated_blocks_state_descendants =
276+
invalidated_blocks_state.get(&block2.hash()).unwrap();
277+
278+
match network {
279+
Network::Mainnet => assert!(
280+
invalidated_blocks_state_descendants
281+
.iter()
282+
.any(|block| block.height == block::Height(653601)),
283+
"invalidated descendants vec should contain block3"
284+
),
285+
Network::Testnet(_parameters) => assert!(
286+
invalidated_blocks_state_descendants
287+
.iter()
288+
.any(|block| block.height == block::Height(584001)),
289+
"invalidated descendants vec should contain block3"
290+
),
291+
}
292+
293+
Ok(())
294+
}
295+
296+
#[test]
297+
fn invalidate_block_removes_block_and_descendants_from_chain() -> Result<()> {
298+
let _init_guard = zebra_test::init();
299+
300+
for network in Network::iter() {
301+
invalidate_block_removes_block_and_descendants_from_chain_for_network(network)?;
302+
}
303+
304+
Ok(())
305+
}
306+
219307
#[test]
220308
// This test gives full coverage for `take_chain_if`
221309
fn commit_block_extending_best_chain_doesnt_drop_worst_chains() -> Result<()> {

0 commit comments

Comments
 (0)