use std::{collections::HashMap, env, error::Error, fs, sync::Arc};
use tempfile::TempDir;
use tokio::sync::oneshot;
use zakura_chain::serialization::ZcashDeserializeInto;
use zakura_chain::{
block::{Block, Height},
parallel::commitment_aux::BlockCommitmentRoots,
parameters::{
testnet::{ConfiguredActivationHeights, ParametersBuilder},
NetworkUpgrade,
},
LedgerState,
};
use zakura_test::prelude::*;
use crate::{
config::Config, service::arbitrary::PreparedChain, tests::FakeChainHelper, HashOrHeight,
};
use super::super::{
commitment_aux, serve_block_roots, vct::validate_final_frontiers_bytes,
CheckpointVerifiedBlock, DiskWriteBatch, FinalizedState, NextVctBlock,
};
const DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES: u32 = 1;
type TestRootMap = HashMap<
u32,
(
zakura_chain::sapling::tree::Root,
zakura_chain::orchard::tree::Root,
zakura_chain::ironwood::tree::Root,
),
>;
type SaplingTree = Arc<zakura_chain::sapling::tree::NoteCommitmentTree>;
type OrchardTree = Arc<zakura_chain::orchard::tree::NoteCommitmentTree>;
type SproutTree = Arc<zakura_chain::sprout::tree::NoteCommitmentTree>;
fn next_vct_block(block: Arc<Block>) -> Option<NextVctBlock> {
Some(NextVctBlock::from_block(block, None))
}
#[test]
fn vct_successor_witness_uses_stored_header_without_body() {
let _init_guard = zakura_test::init();
let network = zakura_chain::parameters::Network::Mainnet;
let mut state = FinalizedState::new(
&Config::ephemeral(),
&network,
#[cfg(feature = "elasticsearch")]
false,
)
.expect("opening an ephemeral finalized state succeeds");
let genesis = zakura_test::vectors::BLOCK_MAINNET_GENESIS_BYTES
.zcash_deserialize_into::<Arc<Block>>()
.expect("genesis block deserializes");
let block1 = zakura_test::vectors::BLOCK_MAINNET_1_BYTES
.zcash_deserialize_into::<Arc<Block>>()
.expect("block 1 deserializes");
state
.commit_finalized_direct(
CheckpointVerifiedBlock::from(genesis.clone()).into(),
None,
None,
"header-only VCT successor test genesis",
)
.expect("genesis commits");
let roots = BlockCommitmentRoots {
height: Height(1),
sapling_root: zakura_chain::sapling::tree::NoteCommitmentTree::default().root(),
orchard_root: zakura_chain::orchard::tree::NoteCommitmentTree::default().root(),
ironwood_root: zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
sapling_tx: 0,
orchard_tx: 0,
ironwood_tx: 0,
auth_data_root: block1.auth_data_root(),
};
let mut batch = DiskWriteBatch::new();
batch
.prepare_header_range_batch_with_roots(
&state.db,
genesis.hash(),
std::slice::from_ref(&block1.header),
&[0],
&[roots],
)
.expect("block 1 header is contextually valid");
state
.db
.write_batch(batch)
.expect("header range batch writes");
assert!(
state.db.block(Height(1).into()).is_none(),
"the successor body must remain absent"
);
let witness = state
.vct_successor_from_header_store(Height(0), genesis.hash())
.expect("the stored header and auth-data root form a successor witness");
assert_eq!(witness.header, block1.header);
assert_eq!(witness.height, Height(1));
assert_eq!(witness.hash, block1.hash());
assert_eq!(witness.auth_data_root, Some(block1.auth_data_root()));
}
fn test_handoff_frontiers(height: Height) -> commitment_aux::FinalFrontiers {
commitment_aux::FinalFrontiers {
height,
sapling: Arc::new(Default::default()),
orchard: Arc::new(Default::default()),
sprout: Arc::new(Default::default()),
ironwood: Arc::new(Default::default()),
}
}
fn enable_vct_test_fixture_source(state: &mut FinalizedState, roots: TestRootMap) {
state.enable_vct_fast_source(
Box::new(commitment_aux::FixtureSource::new(
roots,
test_handoff_frontiers(Height::MAX),
)),
false,
);
}
fn enable_vct_test_fixture_source_with_handoff(
state: &mut FinalizedState,
roots: TestRootMap,
handoff_height: Height,
sapling: SaplingTree,
orchard: OrchardTree,
sprout: SproutTree,
ironwood: Arc<zakura_chain::ironwood::tree::NoteCommitmentTree>,
) {
state.enable_vct_fast_source(
Box::new(commitment_aux::FixtureSource::new(
roots,
commitment_aux::FinalFrontiers {
height: handoff_height,
sapling,
orchard,
sprout,
ironwood,
},
)),
false,
);
}
#[test]
fn vct_generated_final_frontier_bytes_are_node_loader_compatible() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let last = (nu5 + 3) as usize;
prop_assert!(blocks.len() > last, "generated chain unexpectedly short");
let height = Height(last as u32);
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
for block in blocks.iter().take(last + 1) {
let cv = CheckpointVerifiedBlock::from(block.block.clone());
legacy
.commit_finalized_direct(cv.into(), None, None, "vct frontier bytes legacy")
.unwrap();
}
let bytes = commitment_aux::produce_final_frontiers_bytes(&legacy.db, height)
.expect("legacy DB has final frontiers at the requested height");
let temp_dir = TempDir::new().expect("temp dir");
let path = temp_dir.path().join("frontier.bin");
fs::write(&path, &bytes).expect("frontier bytes write to temp file");
let bytes_from_file = fs::read(&path).expect("frontier bytes read from temp file");
validate_final_frontiers_bytes(&bytes_from_file, height)
.expect("generated frontier bytes pass node loader validation");
let parsed = commitment_aux::FinalFrontiers::from_bytes(&bytes_from_file)
.expect("validated bytes parse as final frontiers");
prop_assert_eq!(parsed.height, height, "frontier height round-trips");
prop_assert_eq!(
parsed.sapling.root(),
legacy.db.sapling_tree_by_height(&height).unwrap().root(),
"parsed Sapling frontier matches the DB tree at the requested height"
);
prop_assert_eq!(
parsed.orchard.root(),
legacy.db.orchard_tree_by_height(&height).unwrap().root(),
"parsed Orchard frontier matches the DB tree at the requested height"
);
prop_assert_eq!(
parsed.sprout.root(),
legacy.db.sprout_tree_for_tip().root(),
"parsed Sprout frontier matches the DB tip tree"
);
let wrong_height = Height(height.0.checked_add(1).expect("test height is in range"));
prop_assert!(
validate_final_frontiers_bytes(&bytes_from_file, wrong_height).is_err(),
"node loader validation rejects a frontier whose height does not match the checkpoint"
);
});
Ok(())
}
#[test]
fn blocks_with_v5_transactions() -> Result<()> {
let _init_guard = zakura_test::init();
proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|((chain, count, network, _history_tree) in PreparedChain::default())| {
let mut state = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut height = Height(0);
for block in chain.iter().take(count) {
let checkpoint_verified = CheckpointVerifiedBlock::from(block.block.clone());
let (hash, _) = state.commit_finalized_direct(
checkpoint_verified.into(),
None,
None,
"blocks_with_v5_transactions test"
).unwrap();
prop_assert_eq!(Some(height), state.finalized_tip_height());
prop_assert_eq!(hash, block.hash);
height = Height(height.0 + 1);
}
});
Ok(())
}
#[test]
#[allow(clippy::print_stderr)]
fn all_upgrades_and_wrong_commitments_with_fake_activation_heights() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), NetworkUpgrade::Nu5, None, false);
proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy).with_valid_commitments().no_shrink())| {
let mut state = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut height = Height(0);
let heartwood_height = NetworkUpgrade::Heartwood.activation_height(&network).unwrap();
let heartwood_height_plus1 = (heartwood_height + 1).unwrap();
let nu5_height = NetworkUpgrade::Nu5.activation_height(&network).unwrap();
let nu5_height_plus1 = (nu5_height + 1).unwrap();
let mut failure_count = 0;
let mut bad_auth_root_failure_count = 0;
for block in chain.iter() {
let block_hash = block.hash;
let current_height = block.block.coinbase_height().unwrap();
match current_height {
h if h == heartwood_height ||
h == heartwood_height_plus1 ||
h == nu5_height ||
h == nu5_height_plus1 => {
let block = block.block.clone().set_block_commitment([0x42; 32]);
let checkpoint_verified = CheckpointVerifiedBlock::from(block);
state.commit_finalized_direct(
checkpoint_verified.into(),
None,
None,
"all_upgrades test"
).expect_err("Must fail commitment check");
failure_count += 1;
},
_ => {},
}
if current_height == nu5_height_plus1 {
let mut checkpoint_verified =
CheckpointVerifiedBlock::from(block.block.clone());
checkpoint_verified.0.auth_data_root = Some([0x42; 32].into());
let err = state.commit_finalized_direct(
checkpoint_verified.into(),
None,
None,
"all_upgrades bad auth root test"
).expect_err("Must fail when the supplied auth data root is incorrect");
let commit_error = err
.source()
.and_then(|source| source.downcast_ref::<crate::error::CommitBlockError>())
.expect("checkpoint commit error wraps a commit block error");
let bad_auth_root_is_rejected = matches!(
commit_error,
crate::error::CommitBlockError::ValidateContextError(source)
if matches!(
source.as_ref(),
crate::ValidateContextError::InvalidBlockCommitment(
zakura_chain::block::CommitmentError::InvalidChainHistoryBlockTxAuthCommitment { .. }
)
)
);
prop_assert!(bad_auth_root_is_rejected);
bad_auth_root_failure_count += 1;
}
let checkpoint_verified = CheckpointVerifiedBlock::from(block.block.clone());
let (hash, _) = state.commit_finalized_direct(
checkpoint_verified.into(),
None,
None,
"all_upgrades test"
).unwrap();
prop_assert_eq!(Some(height), state.finalized_tip_height());
prop_assert_eq!(hash, block_hash);
height = Height(height.0 + 1);
}
prop_assert_eq!(failure_count, 4);
prop_assert_eq!(bad_auth_root_failure_count, 1);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_fast_path_matches_legacy_and_rejects_wrong_roots() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let last = (nu5 + 3) as usize;
prop_assert!(blocks.len() > last + 1, "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct legacy")
.unwrap();
if i > seed {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
}
let golden_anchors = legacy.db.vct_anchor_digest();
let golden_history = legacy.db.history_tree().hash();
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut fast, fixture.clone());
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct fast")
.expect("verified fast commit succeeds");
}
prop_assert_eq!(fast.db.vct_anchor_digest(), golden_anchors, "fast anchors must match legacy");
prop_assert_eq!(fast.db.history_tree().hash(), golden_history, "fast history must match legacy");
prop_assert_eq!(fast.vct_fast_count(), (last - seed) as u64, "every fast-eligible block took the fast path");
prop_assert_eq!(fast.vct_prevalidated_count(), (last - seed - 1) as u64, "every fast block after the first skips its redundant own commitment check");
let mut no_successor = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut no_successor, fixture.clone());
for i in 0..last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
no_successor
.commit_finalized_direct(cv.into(), None, next, "vct no-successor seed")
.expect("verified fast commit succeeds with successor");
}
prop_assert!(!no_successor.vct_fast_needs_successor(Height(last as u32)), "a trusted fixture tip can commit without a successor");
let cv = CheckpointVerifiedBlock::from(blocks[last].block.clone());
no_successor
.commit_finalized_direct(cv.into(), None, None, "vct trusted fixture no successor")
.expect("trusted fixture tip commits without a successor");
prop_assert_eq!(
no_successor.db.finalized_tip_height(),
Some(Height(last as u32)),
"the trusted fixture tip committed"
);
let bad_height = (nu5 + 1) as usize;
let mut bad_fixture = fixture.clone();
let bad_entry = bad_fixture.get_mut(&(bad_height as u32)).unwrap();
prop_assert_ne!(bad_entry.0, Default::default(), "a V2 block must have a non-empty Sapling root");
bad_entry.0 = Default::default();
let mut bad = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut bad, bad_fixture);
let mut error_height = None;
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
if bad.commit_finalized_direct(cv.into(), None, next, "vct bad").is_err() {
error_height = Some(i);
break;
}
}
prop_assert_eq!(error_height, Some(bad_height), "a wrong fixture root is rejected at its own commit");
let bad_orchard_height = (nu5 - 1) as usize;
prop_assert!(bad_orchard_height > seed, "the corrupted height must be in the fast range");
let empty_orchard = zakura_chain::orchard::tree::NoteCommitmentTree::default().root();
let wrong_orchard = zakura_chain::orchard::tree::Root::try_from([0u8; 32])
.expect("zero is a valid pallas base field element");
prop_assert_ne!(wrong_orchard, empty_orchard, "the wrong root must differ from the empty-tree root");
let mut bad_orchard_fixture = fixture.clone();
let bad_orchard_entry = bad_orchard_fixture.get_mut(&(bad_orchard_height as u32)).unwrap();
prop_assert_eq!(bad_orchard_entry.1, empty_orchard, "a below-NU5 block has the empty Orchard root");
bad_orchard_entry.1 = wrong_orchard;
let mut bad_orchard = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut bad_orchard, bad_orchard_fixture);
let mut orchard_error_height = None;
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
if bad_orchard.commit_finalized_direct(cv.into(), None, next, "vct bad orchard").is_err() {
orchard_error_height = Some(i);
break;
}
}
prop_assert_eq!(orchard_error_height, Some(bad_orchard_height), "a wrong below-NU5 orchard root is rejected at its own commit");
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_frozen_frontier_hole_refuses_instead_of_recomputing() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let last = (nu5 + 3) as usize;
prop_assert!(blocks.len() > last + 1, "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct hole legacy")
.unwrap();
if i > seed {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
}
let hole = (nu5 + 1) as usize;
prop_assert!(hole > seed && hole < last, "the hole must be inside the fast range");
fixture.remove(&(hole as u32));
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut fast, fixture);
let mut error_height = None;
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = (i < last)
.then(|| NextVctBlock::from_block(blocks[i + 1].block.clone(), None));
match fast.commit_finalized_direct(cv.into(), None, next, "vct hole fast") {
Ok(_) => {}
Err(error) => {
prop_assert!(
format!("{error:?}").contains("VctSuppliedRootUnavailable"),
"a frozen-frontier hole returns the retryable VctSuppliedRootUnavailable error, got: {error:?}"
);
error_height = Some(i);
break;
}
}
}
prop_assert_eq!(error_height, Some(hole), "the commit refuses at the hole height, not before or after");
prop_assert_eq!(
fast.db.finalized_tip_height(),
Some(Height((hole - 1) as u32)),
"the database tip stays just below the hole — the refused block left state untouched"
);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_retryable_root_miss_keeps_checkpoint_response_pending() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let last = (nu5 + 3) as usize;
prop_assert!(blocks.len() > last, "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct response legacy")
.unwrap();
if i > seed {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
}
let hole = (nu5 + 1) as usize;
fixture.remove(&(hole as u32));
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut fast, fixture);
for i in 0..hole {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct response fast")
.expect("pre-hole fast commits succeed");
}
let cv = CheckpointVerifiedBlock::from(blocks[hole].block.clone());
let (rsp_tx, mut rsp_rx) = oneshot::channel();
let next = next_vct_block(blocks[hole + 1].block.clone());
let result = fast.commit_finalized((cv, rsp_tx), None, next);
let Err((returned_block, error)) = result else {
panic!("missing frozen-frontier root should return the queued block for retry");
};
prop_assert_eq!(returned_block.0.height, Height(hole as u32));
prop_assert!(
error.vct_supplied_root_unavailable_height().is_some(),
"the returned error is the typed retryable VCT root miss"
);
prop_assert!(
matches!(rsp_rx.try_recv(), Err(oneshot::error::TryRecvError::Empty)),
"the checkpoint response stays pending so the write loop can retry internally"
);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_peer_source_defers_unverifiable_tip_root_until_successor() -> Result<()> {
use crate::service::finalized_state::commitment_aux::PeerSource;
use zakura_chain::parallel::commitment_aux::BlockCommitmentRoots;
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let tip_target = (nu5 + 1) as usize;
prop_assert!(blocks.len() > tip_target + 1, "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut peer_roots = Vec::new();
for i in 0..=tip_target {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct defer legacy")
.unwrap();
if i > seed {
peer_roots.push(BlockCommitmentRoots {
height: Height(i as u32),
sapling_root: trees.sapling.root(),
orchard_root: trees.orchard.root(),
ironwood_root: zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
sapling_tx: 0,
orchard_tx: 0,
ironwood_tx: 0,
auth_data_root: blocks[i].block.auth_data_root(),
});
}
}
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
fast.db
.insert_zakura_header_commitment_roots(peer_roots)
.expect("writing header-sync roots to an ephemeral database succeeds");
let source = PeerSource::new(fast.db.clone(), test_handoff_frontiers(Height::MAX));
fast.enable_vct_fast_source(Box::new(source), true);
for i in 0..tip_target {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct defer pre-tip")
.expect("pre-tip fast commits succeed");
}
prop_assert_eq!(fast.db.finalized_tip_height(), Some(Height((tip_target - 1) as u32)));
prop_assert!(
fast.vct_fast_needs_successor(Height(tip_target as u32)),
"an untrusted peer tip root needs successor verification"
);
let pre_deferral_prevalidated = fast.vct_prevalidated_count();
let cv = CheckpointVerifiedBlock::from(blocks[tip_target].block.clone());
let error = fast
.commit_finalized_direct(cv.into(), None, None, "vct defer tip no successor")
.expect_err("an untrusted tip root with no successor must defer, not commit");
prop_assert!(
error.vct_supplied_root_unavailable_height().is_none(),
"deferral is not a missing-root case (the root is present): {error:?}"
);
prop_assert!(
format!("{error:?}").contains("VctSuppliedRootAwaitingSuccessor"),
"the tip defers with the await-successor error, got: {error:?}"
);
prop_assert_eq!(
fast.db.finalized_tip_height(),
Some(Height((tip_target - 1) as u32)),
"the deferred block left the database untouched"
);
let after_deferral_prevalidated = fast.vct_prevalidated_count();
prop_assert_eq!(
after_deferral_prevalidated,
pre_deferral_prevalidated + 1,
"the deferred attempt uses the predecessor look-ahead"
);
let cv = CheckpointVerifiedBlock::from(blocks[tip_target].block.clone());
let forged_witness = next_vct_block(blocks[tip_target].block.clone());
let error = fast
.commit_finalized_direct(cv.into(), None, forged_witness, "vct defer tip forged witness")
.expect_err("a non-linking witness must defer, not commit or evict");
prop_assert!(
format!("{error:?}").contains("VctSuppliedRootAwaitingSuccessor"),
"a non-linking witness defers with the await-successor error, got: {error:?}"
);
prop_assert!(
error.vct_supplied_root_unavailable_height().is_none(),
"a non-linking witness is not a root failure — the correct root stays cached: {error:?}"
);
prop_assert_eq!(
fast.db.finalized_tip_height(),
Some(Height((tip_target - 1) as u32)),
"the forged-witness attempt left the database untouched"
);
let after_forged_prevalidated = fast.vct_prevalidated_count();
prop_assert_eq!(
after_forged_prevalidated,
after_deferral_prevalidated + 1,
"the forged-witness attempt still uses the predecessor look-ahead"
);
let cv = CheckpointVerifiedBlock::from(blocks[tip_target].block.clone());
let next = next_vct_block(blocks[tip_target + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct defer tip with successor")
.expect("the deferred height commits once its successor is buffered");
prop_assert_eq!(
fast.vct_prevalidated_count(),
after_forged_prevalidated + 1,
"the retry reuses the preserved predecessor look-ahead"
);
prop_assert_eq!(
fast.db.finalized_tip_height(),
Some(Height(tip_target as u32)),
"the tip advances once the successor confirms the root"
);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_peer_source_bad_root_refill_commits_same_height() -> Result<()> {
use crate::service::finalized_state::commitment_aux::PeerSource;
use zakura_chain::parallel::commitment_aux::BlockCommitmentRoots;
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let target = (nu5 + 1) as usize;
prop_assert!(blocks.len() > target + 1, "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut peer_roots = Vec::new();
let mut correct_target_root = None;
for i in 0..=target {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct refill legacy")
.unwrap();
if i > seed {
let root = BlockCommitmentRoots {
height: Height(i as u32),
sapling_root: trees.sapling.root(),
orchard_root: trees.orchard.root(),
ironwood_root: zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
sapling_tx: 0,
orchard_tx: 0,
ironwood_tx: 0,
auth_data_root: blocks[i].block.auth_data_root(),
};
if i == target {
correct_target_root = Some(root.clone());
let mut poisoned = root;
prop_assert_ne!(
poisoned.sapling_root,
Default::default(),
"a V2 target block must have a non-empty Sapling root"
);
poisoned.sapling_root = Default::default();
peer_roots.push(poisoned);
} else {
peer_roots.push(root);
}
}
}
let correct_target_root = correct_target_root.expect("target root was produced");
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
fast.db
.insert_zakura_header_commitment_roots(peer_roots)
.expect("writing header-sync roots to an ephemeral database succeeds");
let source = PeerSource::new(fast.db.clone(), test_handoff_frontiers(Height::MAX));
fast.enable_vct_fast_source(Box::new(source), true);
for i in 0..target {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct refill pre-target")
.expect("pre-target fast commits succeed");
}
prop_assert_eq!(fast.db.finalized_tip_height(), Some(Height((target - 1) as u32)));
let cv = CheckpointVerifiedBlock::from(blocks[target].block.clone());
let next = next_vct_block(blocks[target + 1].block.clone());
let error = fast
.commit_finalized_direct(cv.into(), None, next.clone(), "vct poisoned target")
.expect_err("the poisoned peer root must be rejected before commit");
prop_assert_eq!(
error.vct_supplied_root_unavailable_height(),
Some(Height(target as u32)),
"the bad root is exposed as a retryable missing root for its own height"
);
prop_assert_eq!(
fast.db.finalized_tip_height(),
Some(Height((target - 1) as u32)),
"the rejected root left the database parked below the target"
);
fast.db
.insert_zakura_header_commitment_roots([correct_target_root])
.expect("refilling the evicted height succeeds");
let cv = CheckpointVerifiedBlock::from(blocks[target].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct refilled target")
.expect("the same height commits once the peer cache is refilled");
prop_assert_eq!(
fast.db.finalized_tip_height(),
Some(Height(target as u32)),
"the refilled root unblocks the parked height"
);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_frozen_frontier_survives_reopen() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let handoff_height = nu5 + 3;
let last = handoff_height as usize;
prop_assert!(blocks.len() > last, "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let stop = (handoff_height - 2) as usize;
let hole = stop + 1;
prop_assert!(seed < stop && hole < last, "the hole must sit inside the frozen fast range");
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
let mut handoff_trees = None;
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct reopen legacy")
.unwrap();
if i > seed {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
if i == last {
handoff_trees = Some(trees);
}
}
let handoff_trees = handoff_trees.expect("committed the handoff block");
let dir = TempDir::new().expect("temp dir");
let config = Config {
cache_dir: dir.path().to_path_buf(),
ephemeral: false,
..Config::default()
};
{
let mut fast = FinalizedState::new(&config, &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source_with_handoff(
&mut fast,
fixture.clone(),
Height(handoff_height),
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
handoff_trees.ironwood.clone(),
);
for i in 0..=stop {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct reopen fast")
.expect("verified fast commit succeeds");
}
prop_assert_eq!(fast.vct_fast_synced_below(), Some(Height(handoff_height)), "the interrupted sync left the fast-sync marker");
prop_assert_eq!(fast.db.finalized_tip_height(), Some(Height(stop as u32)), "the tip is parked below the handoff");
}
let mut reopened = FinalizedState::new_with_debug_and_storage_validation(
&config,
&network,
false,
#[cfg(feature = "elasticsearch")]
false,
false,
true,
false,
).expect("opening the finalized state should succeed");
prop_assert_eq!(reopened.vct_fast_synced_below(), Some(Height(handoff_height)), "the marker is still durable after reopen");
let mut holed = fixture.clone();
holed.remove(&(hole as u32));
enable_vct_test_fixture_source_with_handoff(
&mut reopened,
holed,
Height(handoff_height),
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
handoff_trees.ironwood.clone(),
);
let cv = CheckpointVerifiedBlock::from(blocks[hole].block.clone());
let next = next_vct_block(blocks[hole + 1].block.clone());
let error = reopened
.commit_finalized_direct(cv.into(), None, next, "vct reopen hole")
.expect_err("a frozen-frontier hole must refuse after reopen, not recompute");
prop_assert!(
format!("{error:?}").contains("VctSuppliedRootUnavailable"),
"the reopened committer returns the retryable VctSuppliedRootUnavailable, got: {error:?}"
);
prop_assert_eq!(reopened.db.finalized_tip_height(), Some(Height(stop as u32)), "the refused block left the reopened state untouched");
enable_vct_test_fixture_source_with_handoff(
&mut reopened,
fixture.clone(),
Height(handoff_height),
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
handoff_trees.ironwood.clone(),
);
let cv = CheckpointVerifiedBlock::from(blocks[hole].block.clone());
let next = next_vct_block(blocks[hole + 1].block.clone());
reopened
.commit_finalized_direct(cv.into(), None, next, "vct reopen refill")
.expect("the height commits once its root is fetched");
prop_assert_eq!(reopened.db.finalized_tip_height(), Some(Height(hole as u32)), "the tip advances past the former hole once the root arrives");
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_fast_sync_handoff_marks_database_and_resumes() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(env::var("PROPTEST_CASES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let last = (nu5 + 3) as usize;
prop_assert!(blocks.len() > last, "generated chain unexpectedly short");
let handoff = Height(last as u32);
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
let mut handoff_trees = None;
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct legacy")
.unwrap();
if i > seed {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
if i == last {
handoff_trees = Some(trees);
}
}
let golden_anchors = legacy.db.vct_anchor_digest();
let golden_history = legacy.db.history_tree().hash();
let golden_tip = legacy.db.note_commitment_trees_for_tip();
let handoff_trees = handoff_trees.expect("committed the handoff block");
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source_with_handoff(
&mut fast,
fixture.clone(),
handoff,
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
handoff_trees.ironwood.clone(),
);
prop_assert!(!fast.vct_fast_needs_successor(handoff), "the trusted handoff frontier authenticates the handoff root without a successor");
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = (i < last)
.then(|| NextVctBlock::from_block(blocks[i + 1].block.clone(), None));
fast.commit_finalized_direct(cv.into(), None, next, "vct fast handoff")
.expect("verified fast commit succeeds");
}
prop_assert_eq!(fast.vct_fast_synced_below(), Some(handoff), "fast-sync marker is set to the handoff height");
prop_assert_eq!(fast.db.vct_upgrade_height(), Some(Height(0)), "genesis fast sync records the upgrade height at genesis");
prop_assert_eq!(fast.db.vct_anchor_digest(), golden_anchors, "fast anchors must match legacy");
prop_assert_eq!(fast.db.history_tree().hash(), golden_history, "fast history must match legacy");
let fast_tip = fast.db.note_commitment_trees_for_tip();
prop_assert_eq!(fast_tip.sapling.root(), golden_tip.sapling.root(), "tip sapling frontier must match legacy");
prop_assert_eq!(fast_tip.orchard.root(), golden_tip.orchard.root(), "tip orchard frontier must match legacy");
prop_assert_eq!(fast_tip.sprout.root(), golden_tip.sprout.root(), "tip sprout frontier must match legacy");
prop_assert!(fast.db.sapling_tree_by_height(&Height(last as u32 - 1)).is_none(), "below-handoff sapling tree read is guarded");
prop_assert!(fast.db.orchard_tree_by_height(&Height(last as u32 - 1)).is_none(), "below-handoff orchard tree read is guarded");
prop_assert!(fast.db.sapling_tree_by_height(&handoff).is_some(), "handoff sapling tree is present");
prop_assert!(fast.db.orchard_tree_by_height(&handoff).is_some(), "handoff orchard tree is present");
let below_handoff = Height((seed + 1) as u32)..=Height(last as u32 - 1);
let served = fast.db.commitment_roots_by_height_range(below_handoff.clone());
let expected = commitment_aux::produce_block_roots(&legacy.db, below_handoff.clone());
prop_assert!(!served.is_empty(), "a fast-synced node serves below-handoff roots from the index");
prop_assert_eq!(served, expected.clone(), "index-served roots match the legacy per-height-tree roots");
prop_assert_eq!(serve_block_roots(&fast.db, below_handoff), expected, "serve_block_roots serves the fast-synced range from the index");
prop_assert!(fast.db.vct_historical_tree_unavailable(HashOrHeight::Height(Height(last as u32 - 1))), "RPC gate: below-handoff treestate is unavailable");
prop_assert!(!fast.db.vct_historical_tree_unavailable(HashOrHeight::Height(handoff)), "RPC gate: handoff treestate is available");
let mut bad_handoff_fixture = fixture.clone();
let bad_handoff_entry = bad_handoff_fixture
.get_mut(&(last as u32))
.expect("fixture contains the handoff root");
prop_assert_ne!(bad_handoff_entry.0, Default::default(), "a post-NU5 handoff block must have a non-empty Sapling root");
bad_handoff_entry.0 = Default::default();
let mut bad_handoff = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source_with_handoff(
&mut bad_handoff,
bad_handoff_fixture,
handoff,
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
handoff_trees.ironwood.clone(),
);
let mut error_height = None;
let mut handoff_error = None;
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = (i < last)
.then(|| NextVctBlock::from_block(blocks[i + 1].block.clone(), None));
match bad_handoff.commit_finalized_direct(cv.into(), None, next, "vct bad handoff") {
Ok(_) => {}
Err(error) => {
error_height = Some(i);
handoff_error = Some(error);
break;
}
}
}
prop_assert_eq!(error_height, Some(last), "the bad handoff root is rejected at the handoff height");
let handoff_error = handoff_error.expect("the bad handoff root failed");
prop_assert!(
format!("{handoff_error:?}").contains("VctSuppliedRootUnavailable"),
"a bad handoff root returns the retryable VctSuppliedRootUnavailable error, got: {handoff_error:?}"
);
prop_assert_eq!(
bad_handoff.db.finalized_tip_height(),
Some(Height(last as u32 - 1)),
"the refused handoff block left state untouched"
);
let mut wrong_ironwood_frontier = zakura_chain::ironwood::tree::NoteCommitmentTree::default();
wrong_ironwood_frontier
.append(halo2::pasta::pallas::Base::from(1u64))
.expect("single-note Ironwood tree is not full");
prop_assert_ne!(
wrong_ironwood_frontier.root(),
handoff_trees.ironwood.root(),
"test needs an Ironwood frontier distinct from the real (empty) one"
);
let mut bad_ironwood_handoff = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source_with_handoff(
&mut bad_ironwood_handoff,
fixture.clone(),
handoff,
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
Arc::new(wrong_ironwood_frontier),
);
let mut ironwood_error_height = None;
let mut ironwood_handoff_error = None;
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = (i < last)
.then(|| NextVctBlock::from_block(blocks[i + 1].block.clone(), None));
match bad_ironwood_handoff.commit_finalized_direct(cv.into(), None, next, "vct bad ironwood handoff") {
Ok(_) => {}
Err(error) => {
ironwood_error_height = Some(i);
ironwood_handoff_error = Some(error);
break;
}
}
}
prop_assert_eq!(ironwood_error_height, Some(last), "the bad Ironwood handoff frontier is rejected at the handoff height");
let ironwood_handoff_error = ironwood_handoff_error.expect("the bad Ironwood handoff frontier failed");
prop_assert!(
format!("{ironwood_handoff_error:?}").contains("VctSuppliedRootUnavailable"),
"a bad Ironwood handoff frontier returns the retryable VctSuppliedRootUnavailable error, got: {ironwood_handoff_error:?}"
);
prop_assert_eq!(
bad_ironwood_handoff.db.finalized_tip_height(),
Some(Height(last as u32 - 1)),
"the refused Ironwood handoff block left state untouched"
);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_mode_switches_continue_from_safe_boundaries() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let handoff_index = (nu5 + 3) as usize;
let post_handoff_tip = handoff_index + 2;
prop_assert!(blocks.len() > post_handoff_tip, "generated chain unexpectedly short");
let handoff = Height(handoff_index as u32);
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
let mut handoff_trees = None;
let mut post_handoff_roots = None;
for i in 0..=post_handoff_tip {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct switch legacy")
.unwrap();
if i > seed && i <= handoff_index {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
if i == handoff_index {
handoff_trees = Some(trees);
} else if i == handoff_index + 1 {
post_handoff_roots = Some((
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
));
}
}
let golden_anchors = legacy.db.vct_anchor_digest();
let golden_history = legacy.db.history_tree().hash();
let golden_tip = legacy.db.note_commitment_trees_for_tip();
let handoff_trees = handoff_trees.expect("committed the handoff block");
let post_handoff_roots = post_handoff_roots.expect("committed a post-handoff block");
let fast_to_manual_dir = TempDir::new().expect("temp dir");
let fast_config = Config {
cache_dir: fast_to_manual_dir.path().to_path_buf(),
ephemeral: false,
..Config::default()
};
{
let mut fast = FinalizedState::new(&fast_config, &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source_with_handoff(
&mut fast,
fixture.clone(),
handoff,
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
handoff_trees.ironwood.clone(),
);
for i in 0..=handoff_index {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = (i < handoff_index)
.then(|| NextVctBlock::from_block(blocks[i + 1].block.clone(), None));
fast.commit_finalized_direct(cv.into(), None, next, "vct switch fast prefix")
.expect("verified fast prefix commits");
}
prop_assert_eq!(fast.vct_fast_synced_below(), Some(handoff), "fast sync reached the handoff before the switch");
}
let manual_config = Config {
vct_fast_sync: false,
..fast_config
};
let mut manual = FinalizedState::new(&manual_config, &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
for i in (handoff_index + 1)..=post_handoff_tip {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
manual
.commit_finalized_direct(cv.into(), None, None, "vct switch manual suffix")
.expect("manual suffix commits after fast handoff");
}
let manual_tip = manual.db.note_commitment_trees_for_tip();
prop_assert_eq!(manual.db.vct_anchor_digest(), golden_anchors, "fast-to-manual anchors match legacy");
prop_assert_eq!(manual.db.history_tree().hash(), golden_history, "fast-to-manual history matches legacy");
prop_assert_eq!(manual_tip.sapling.root(), golden_tip.sapling.root(), "fast-to-manual sapling tip matches legacy");
prop_assert_eq!(manual_tip.orchard.root(), golden_tip.orchard.root(), "fast-to-manual orchard tip matches legacy");
prop_assert_eq!(manual_tip.sprout.root(), golden_tip.sprout.root(), "fast-to-manual sprout tip matches legacy");
let manual_to_fast_dir = TempDir::new().expect("temp dir");
let manual_prefix_config = Config {
cache_dir: manual_to_fast_dir.path().to_path_buf(),
ephemeral: false,
vct_fast_sync: false,
..Config::default()
};
{
let mut manual_prefix = FinalizedState::new(&manual_prefix_config, &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
for i in 0..=seed {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
manual_prefix
.commit_finalized_direct(cv.into(), None, None, "vct switch manual prefix")
.expect("manual prefix commits");
}
}
let fast_suffix_config = Config {
vct_fast_sync: true,
..manual_prefix_config
};
let mut fast_suffix = FinalizedState::new(&fast_suffix_config, &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut guarded_fixture = fixture;
prop_assert_ne!(
post_handoff_roots.0,
Default::default(),
"a post-NU5 post-handoff block must have a non-empty Sapling root",
);
guarded_fixture.insert(
(handoff_index + 1) as u32,
(
Default::default(),
post_handoff_roots.1,
post_handoff_roots.2,
),
);
enable_vct_test_fixture_source_with_handoff(
&mut fast_suffix,
guarded_fixture,
handoff,
handoff_trees.sapling.clone(),
handoff_trees.orchard.clone(),
handoff_trees.sprout.clone(),
handoff_trees.ironwood.clone(),
);
for i in (seed + 1)..=post_handoff_tip {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = (i < post_handoff_tip)
.then(|| NextVctBlock::from_block(blocks[i + 1].block.clone(), None));
fast_suffix
.commit_finalized_direct(cv.into(), None, next, "vct switch fast suffix")
.expect("fast suffix commits after manual prefix");
}
prop_assert_eq!(
fast_suffix.vct_fast_count(),
(handoff_index - seed) as u64,
"an above-handoff cached root must not keep the committer on the fast path",
);
let fast_suffix_tip = fast_suffix.db.note_commitment_trees_for_tip();
prop_assert_eq!(fast_suffix.db.vct_anchor_digest(), golden_anchors, "manual-to-fast anchors match legacy");
prop_assert_eq!(fast_suffix.db.history_tree().hash(), golden_history, "manual-to-fast history matches legacy");
prop_assert_eq!(fast_suffix_tip.sapling.root(), golden_tip.sapling.root(), "manual-to-fast sapling tip matches legacy");
prop_assert_eq!(fast_suffix_tip.orchard.root(), golden_tip.orchard.root(), "manual-to-fast orchard tip matches legacy");
prop_assert_eq!(fast_suffix_tip.sprout.root(), golden_tip.sprout.root(), "manual-to-fast sprout tip matches legacy");
});
Ok(())
}
#[test]
fn vct_dedup_skips_redundant_check_and_guards_stale_cache() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0 as usize;
let seed = nu5 - 2;
let last = seed + 4;
prop_assert!(blocks.len() > last + 1, "generated chain unexpectedly short");
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
for (i, prepared) in blocks.iter().take(last + 1).enumerate() {
let cv = CheckpointVerifiedBlock::from(prepared.block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct dedup legacy")
.unwrap();
if i > seed {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
}
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut fast, fixture);
let commit = |fast: &mut FinalizedState, i: usize| {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct dedup fast")
.expect("verified fast commit succeeds");
};
for i in 0..=seed {
commit(&mut fast, i);
}
prop_assert_eq!(fast.vct_prevalidated_count(), 0, "no fast blocks committed yet");
commit(&mut fast, seed + 1);
prop_assert_eq!(fast.vct_prevalidated_count(), 0, "the first fast block runs its own commitment check");
commit(&mut fast, seed + 2);
prop_assert_eq!(fast.vct_prevalidated_count(), 1, "the second fast block skips its redundant own commitment check");
let stale_hash = blocks[seed + 1].hash;
prop_assert_ne!(stale_hash, blocks[seed + 3].hash, "stale hash must differ from the real block");
fast.vct
.set_prevalidated_next(Some((Height((seed + 3) as u32), stale_hash)));
commit(&mut fast, seed + 3);
prop_assert_eq!(fast.vct_prevalidated_count(), 1, "a stale cache entry (wrong hash) must not cause a false skip");
let forged_wrapper_hash = blocks[seed + 2].hash;
let bad_block = blocks[seed + 4].block.clone().set_block_commitment([0x42; 32]);
let bad_block_hash = bad_block.hash();
prop_assert_ne!(
forged_wrapper_hash,
bad_block_hash,
"the forged wrapper hash must differ from the bad block's real hash",
);
fast.vct
.set_prevalidated_next(Some((Height((seed + 4) as u32), forged_wrapper_hash)));
let forged = CheckpointVerifiedBlock::with_hash(bad_block, forged_wrapper_hash);
let error = fast
.commit_finalized_direct(forged.into(), None, None, "vct forged wrapper hash")
.expect_err("a forged wrapper hash must not skip the bad block's own commitment check");
prop_assert!(
format!("{error:?}").contains("VctSuppliedRootUnavailable"),
"the forged wrapper hash path must reject the bad commitment, got: {error:?}",
);
prop_assert_eq!(
fast.vct_prevalidated_count(),
1,
"the forged wrapper hash must not increment the prevalidated count",
);
prop_assert_eq!(
fast.db.finalized_tip_height(),
Some(Height((seed + 3) as u32)),
"the rejected forged block must leave finalized state untouched",
);
});
Ok(())
}
#[test]
fn vct_clear_prevalidation_cache_disarms_skip_then_dedup_resumes() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0 as usize;
let seed = nu5 - 2;
let last = seed + 5;
prop_assert!(blocks.len() > last + 1, "generated chain unexpectedly short");
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let mut fixture = std::collections::HashMap::new();
for (i, prepared) in blocks.iter().take(last + 1).enumerate() {
let cv = CheckpointVerifiedBlock::from(prepared.block.clone());
let (_h, trees) = legacy
.commit_finalized_direct(cv.into(), None, None, "vct clear legacy")
.unwrap();
if i > seed {
fixture.insert(
i as u32,
(
trees.sapling.root(),
trees.orchard.root(),
zakura_chain::ironwood::tree::NoteCommitmentTree::default().root(),
),
);
}
}
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
enable_vct_test_fixture_source(&mut fast, fixture);
let commit = |fast: &mut FinalizedState, i: usize| {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct clear fast")
.expect("verified fast commit succeeds");
};
for i in 0..=seed {
commit(&mut fast, i);
}
commit(&mut fast, seed + 1);
prop_assert_eq!(fast.vct_prevalidated_count(), 0, "first fast block runs its own check");
commit(&mut fast, seed + 2);
prop_assert_eq!(fast.vct_prevalidated_count(), 1, "second fast block uses predecessor look-ahead");
fast.clear_vct_prevalidated_next();
commit(&mut fast, seed + 3);
prop_assert_eq!(
fast.vct_prevalidated_count(),
1,
"clearing the cache forces the next fast block to run its own check",
);
commit(&mut fast, seed + 4);
prop_assert_eq!(
fast.vct_prevalidated_count(),
2,
"normal successor dedup resumes after the cleared block commits",
);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_db_produced_payload_round_trips_to_byte_identical_state() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let last = (nu5 + 3) as usize;
prop_assert!(blocks.len() > last + 1, "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
for block in blocks.iter().take(last + 1) {
let cv = CheckpointVerifiedBlock::from(block.block.clone());
legacy
.commit_finalized_direct(cv.into(), None, None, "vct round-trip legacy")
.unwrap();
}
let golden_anchors = legacy.db.vct_anchor_digest();
let golden_history = legacy.db.history_tree().hash();
let last_height = Height(last as u32);
let produced_roots = commitment_aux::produce_block_roots(
&legacy.db,
Height((seed + 1) as u32)..=last_height,
);
let produced_frontiers = commitment_aux::produce_final_frontiers(&legacy.db, last_height)
.expect("legacy DB has the tip frontier");
let handoff = produced_roots.last().expect("produced a non-empty range");
prop_assert_eq!(produced_frontiers.sapling.root(), handoff.sapling_root, "produced sapling frontier matches the produced root at handoff");
prop_assert_eq!(produced_frontiers.orchard.root(), handoff.orchard_root, "produced orchard frontier matches the produced root at handoff");
prop_assert_eq!(produced_frontiers.sapling.root(), legacy.db.sapling_tree_by_height(&last_height).unwrap().root(), "produced sapling frontier matches legacy tip");
prop_assert_eq!(produced_frontiers.sprout.root(), legacy.db.sprout_tree_for_tip().root(), "produced sprout frontier matches legacy tip");
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let produced_roots = produced_roots
.into_iter()
.map(|root| {
(
root.height.0,
(root.sapling_root, root.orchard_root, root.ironwood_root),
)
})
.collect();
fast.enable_vct_fast_source(
Box::new(commitment_aux::FixtureSource::new(
produced_roots,
test_handoff_frontiers(Height::MAX),
)),
false,
);
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct round-trip fast")
.expect("verified fast commit from DB-produced roots succeeds");
}
prop_assert_eq!(fast.db.vct_anchor_digest(), golden_anchors, "fast anchors from DB-produced roots match legacy");
prop_assert_eq!(fast.db.history_tree().hash(), golden_history, "fast history from DB-produced roots match legacy");
let serve_range = Height((seed + 1) as u32)..=last_height;
let all_trees_reference =
commitment_aux::produce_block_roots(&legacy.db, serve_range.clone());
let upgrade = Height(((seed + 1 + last) / 2) as u32);
prop_assert!(
serve_range.start() < &upgrade && upgrade <= last_height,
"the chosen upgrade height splits the served range"
);
let mut batch = DiskWriteBatch::new();
batch.delete_range_commitment_roots_by_height(&legacy.db, &Height(0), &upgrade);
batch.update_vct_upgrade_marker(&legacy.db, upgrade);
legacy
.db
.write_batch(batch)
.expect("simulating a mid-chain upgrade succeeds");
prop_assert!(
legacy
.db
.commitment_roots_by_height_range(Height(0)..=Height(upgrade.0 - 1))
.is_empty(),
"the serving index is dropped below the upgrade height"
);
let stitched = serve_block_roots(&legacy.db, serve_range);
prop_assert_eq!(
stitched,
all_trees_reference,
"serve_block_roots stitches the trees below U with the index at/above U into one gap-free run"
);
});
Ok(())
}
#[test]
#[allow(clippy::needless_range_loop)] fn vct_peer_source_filled_incrementally_drives_byte_identical_state() -> Result<()> {
let _init_guard = zakura_test::init();
let network = ParametersBuilder::default()
.with_activation_heights(ConfiguredActivationHeights {
before_overwinter: Some(1),
overwinter: Some(10),
sapling: Some(15),
blossom: Some(20),
heartwood: Some(25),
canopy: Some(30),
nu5: Some(35),
nu6: Some(40),
nu6_1: Some(45),
nu6_2: Some(47),
nu6_3: Some(48),
nu7: Some(50),
})
.expect("failed to set activation heights")
.extend_funding_streams()
.to_network()
.expect("failed to build configured network");
let ledger_strategy =
LedgerState::genesis_strategy(Some(network), None::<NetworkUpgrade>, None, false);
proptest!(ProptestConfig::with_cases(1),
|((chain, _count, network, _history_tree) in PreparedChain::default().with_ledger_strategy(ledger_strategy.clone()).with_valid_commitments().no_shrink())| {
let blocks: Vec<_> = chain.iter().collect();
let nu5 = NetworkUpgrade::Nu5.activation_height(&network).unwrap().0;
let heartwood = NetworkUpgrade::Heartwood.activation_height(&network).unwrap().0;
let last = ((nu5 + 3) as usize).min(blocks.len().saturating_sub(2));
prop_assert!(last > (nu5 as usize), "generated chain unexpectedly short");
let seed = (heartwood - 1) as usize;
let mut legacy = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
for block in blocks.iter().take(last + 1) {
let cv = CheckpointVerifiedBlock::from(block.block.clone());
legacy
.commit_finalized_direct(cv.into(), None, None, "vct peer-source legacy")
.unwrap();
}
let golden_anchors = legacy.db.vct_anchor_digest();
let golden_history = legacy.db.history_tree().hash();
let produced_roots = commitment_aux::produce_block_roots(
&legacy.db,
Height((seed + 1) as u32)..=Height(last as u32),
);
let mut fast = FinalizedState::new(&Config::ephemeral(), &network, #[cfg(feature = "elasticsearch")] false).expect("opening an ephemeral database should succeed");
let split = produced_roots.len() / 2;
fast.db
.insert_zakura_header_commitment_roots(produced_roots[..split].iter().cloned())
.expect("writing the first header-sync root chunk succeeds");
fast.db
.insert_zakura_header_commitment_roots(produced_roots[split..].iter().cloned())
.expect("writing the second header-sync root chunk succeeds");
let peer_source =
commitment_aux::PeerSource::new(fast.db.clone(), test_handoff_frontiers(Height::MAX));
fast.enable_vct_fast_source(Box::new(peer_source), true);
for i in 0..=last {
let cv = CheckpointVerifiedBlock::from(blocks[i].block.clone());
let next = next_vct_block(blocks[i + 1].block.clone());
fast.commit_finalized_direct(cv.into(), None, next, "vct peer-source fast")
.expect("verified fast commit from peer-source roots succeeds");
}
prop_assert_eq!(fast.db.vct_anchor_digest(), golden_anchors, "fast anchors from peer-source roots match legacy");
prop_assert_eq!(fast.db.history_tree().hash(), golden_history, "fast history from peer-source roots match legacy");
});
Ok(())
}