fix(state): Fix minute-long delays in block verification after a chain fork (#6122)
* Split Chain fields into sections * Replace Chain.sprout_note_commitment_tree with a lookup method * Add TODOs * Show full debug info when tests fail because chains aren't equal * Print sprout and sapling tree Nodes as hex when debugging * Correctly revert temporary finalized tip trees and anchors * Fix tests * Refactor removal functions * Replace the Chain.sapling_note_commitment_tree field with a lookup method * Replace the Chain.orchard_note_commitment_tree field with a lookup method * Replace the Chain.history_tree field with a lookup method and remove redundant code * Update comments * Ignore clippy::unwrap_in_result * Remove redundant fork() Result * Put conditional code in blocks * fastmod history_tree_at_tip history_block_commitment_tree zebra-state
This commit is contained in:
parent
09faf48e1f
commit
9452487c61
|
|
@ -169,9 +169,15 @@ impl ZcashDeserialize for Root {
|
||||||
///
|
///
|
||||||
/// Note that it's handled as a byte buffer and not a point coordinate (jubjub::Fq)
|
/// Note that it's handled as a byte buffer and not a point coordinate (jubjub::Fq)
|
||||||
/// because that's how the spec handles the MerkleCRH^Sapling function inputs and outputs.
|
/// because that's how the spec handles the MerkleCRH^Sapling function inputs and outputs.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
struct Node([u8; 32]);
|
struct Node([u8; 32]);
|
||||||
|
|
||||||
|
impl fmt::Debug for Node {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("Node").field(&hex::encode(self.0)).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Required to convert [`NoteCommitmentTree`] into [`SerializedTree`].
|
/// Required to convert [`NoteCommitmentTree`] into [`SerializedTree`].
|
||||||
///
|
///
|
||||||
/// Zebra stores Sapling note commitment trees as [`Frontier`][1]s while the
|
/// Zebra stores Sapling note commitment trees as [`Frontier`][1]s while the
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ use std::{fmt, ops::Deref};
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use incrementalmerkletree::{bridgetree, Frontier};
|
use incrementalmerkletree::{bridgetree, Frontier};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use sha2::digest::generic_array::GenericArray;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::commitment::NoteCommitment;
|
use super::commitment::NoteCommitment;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
use proptest_derive::Arbitrary;
|
use proptest_derive::Arbitrary;
|
||||||
use sha2::digest::generic_array::GenericArray;
|
|
||||||
|
|
||||||
/// Sprout note commitment trees have a max depth of 29.
|
/// Sprout note commitment trees have a max depth of 29.
|
||||||
///
|
///
|
||||||
|
|
@ -127,9 +127,15 @@ impl From<&Root> for [u8; 32] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node of the Sprout note commitment tree.
|
/// A node of the Sprout note commitment tree.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
struct Node([u8; 32]);
|
struct Node([u8; 32]);
|
||||||
|
|
||||||
|
impl fmt::Debug for Node {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_tuple("Node").field(&hex::encode(self.0)).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl incrementalmerkletree::Hashable for Node {
|
impl incrementalmerkletree::Hashable for Node {
|
||||||
/// Returns an empty leaf.
|
/// Returns an empty leaf.
|
||||||
fn empty_leaf() -> Self {
|
fn empty_leaf() -> Self {
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,8 @@ use std::{
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
history_tree::HistoryTree,
|
|
||||||
orchard,
|
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
sapling, sprout, transparent,
|
sprout, transparent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -157,13 +155,7 @@ impl NonFinalizedState {
|
||||||
let parent_hash = prepared.block.header.previous_block_hash;
|
let parent_hash = prepared.block.header.previous_block_hash;
|
||||||
let (height, hash) = (prepared.height, prepared.hash);
|
let (height, hash) = (prepared.height, prepared.hash);
|
||||||
|
|
||||||
let parent_chain = self.parent_chain(
|
let parent_chain = self.parent_chain(parent_hash)?;
|
||||||
parent_hash,
|
|
||||||
finalized_state.sprout_note_commitment_tree(),
|
|
||||||
finalized_state.sapling_note_commitment_tree(),
|
|
||||||
finalized_state.orchard_note_commitment_tree(),
|
|
||||||
finalized_state.history_tree(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// If the block is invalid, return the error,
|
// If the block is invalid, return the error,
|
||||||
// and drop the cloned parent Arc, or newly created chain fork.
|
// and drop the cloned parent Arc, or newly created chain fork.
|
||||||
|
|
@ -185,13 +177,23 @@ impl NonFinalizedState {
|
||||||
/// Commit block to the non-finalized state as a new chain where its parent
|
/// Commit block to the non-finalized state as a new chain where its parent
|
||||||
/// is the finalized tip.
|
/// is the finalized tip.
|
||||||
#[tracing::instrument(level = "debug", skip(self, finalized_state, prepared))]
|
#[tracing::instrument(level = "debug", skip(self, finalized_state, prepared))]
|
||||||
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn commit_new_chain(
|
pub fn commit_new_chain(
|
||||||
&mut self,
|
&mut self,
|
||||||
prepared: PreparedBlock,
|
prepared: PreparedBlock,
|
||||||
finalized_state: &ZebraDb,
|
finalized_state: &ZebraDb,
|
||||||
) -> Result<(), ValidateContextError> {
|
) -> Result<(), ValidateContextError> {
|
||||||
|
let finalized_tip_height = finalized_state.finalized_tip_height();
|
||||||
|
|
||||||
|
// TODO: fix tests that don't initialize the finalized state
|
||||||
|
#[cfg(not(test))]
|
||||||
|
let finalized_tip_height = finalized_tip_height.expect("finalized state contains blocks");
|
||||||
|
#[cfg(test)]
|
||||||
|
let finalized_tip_height = finalized_tip_height.unwrap_or(zebra_chain::block::Height(0));
|
||||||
|
|
||||||
let chain = Chain::new(
|
let chain = Chain::new(
|
||||||
self.network,
|
self.network,
|
||||||
|
finalized_tip_height,
|
||||||
finalized_state.sprout_note_commitment_tree(),
|
finalized_state.sprout_note_commitment_tree(),
|
||||||
finalized_state.sapling_note_commitment_tree(),
|
finalized_state.sapling_note_commitment_tree(),
|
||||||
finalized_state.orchard_note_commitment_tree(),
|
finalized_state.orchard_note_commitment_tree(),
|
||||||
|
|
@ -279,7 +281,7 @@ impl NonFinalizedState {
|
||||||
// Clone function arguments for different threads
|
// Clone function arguments for different threads
|
||||||
let block = contextual.block.clone();
|
let block = contextual.block.clone();
|
||||||
let network = new_chain.network();
|
let network = new_chain.network();
|
||||||
let history_tree = new_chain.history_tree.clone();
|
let history_tree = new_chain.history_block_commitment_tree();
|
||||||
|
|
||||||
let block2 = contextual.block.clone();
|
let block2 = contextual.block.clone();
|
||||||
let height = contextual.height;
|
let height = contextual.height;
|
||||||
|
|
@ -435,7 +437,10 @@ impl NonFinalizedState {
|
||||||
|
|
||||||
/// Returns `true` if the best chain contains `sapling_nullifier`.
|
/// Returns `true` if the best chain contains `sapling_nullifier`.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn best_contains_sapling_nullifier(&self, sapling_nullifier: &sapling::Nullifier) -> bool {
|
pub fn best_contains_sapling_nullifier(
|
||||||
|
&self,
|
||||||
|
sapling_nullifier: &zebra_chain::sapling::Nullifier,
|
||||||
|
) -> bool {
|
||||||
self.best_chain()
|
self.best_chain()
|
||||||
.map(|best_chain| best_chain.sapling_nullifiers.contains(sapling_nullifier))
|
.map(|best_chain| best_chain.sapling_nullifiers.contains(sapling_nullifier))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|
|
@ -443,7 +448,10 @@ impl NonFinalizedState {
|
||||||
|
|
||||||
/// Returns `true` if the best chain contains `orchard_nullifier`.
|
/// Returns `true` if the best chain contains `orchard_nullifier`.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn best_contains_orchard_nullifier(&self, orchard_nullifier: &orchard::Nullifier) -> bool {
|
pub fn best_contains_orchard_nullifier(
|
||||||
|
&self,
|
||||||
|
orchard_nullifier: &zebra_chain::orchard::Nullifier,
|
||||||
|
) -> bool {
|
||||||
self.best_chain()
|
self.best_chain()
|
||||||
.map(|best_chain| best_chain.orchard_nullifiers.contains(orchard_nullifier))
|
.map(|best_chain| best_chain.orchard_nullifiers.contains(orchard_nullifier))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|
|
@ -456,19 +464,12 @@ impl NonFinalizedState {
|
||||||
|
|
||||||
/// Return the chain whose tip block hash is `parent_hash`.
|
/// Return the chain whose tip block hash is `parent_hash`.
|
||||||
///
|
///
|
||||||
/// The chain can be an existing chain in the non-finalized state or a freshly
|
/// The chain can be an existing chain in the non-finalized state, or a freshly
|
||||||
/// created fork, if needed.
|
/// created fork.
|
||||||
///
|
|
||||||
/// The trees must be the trees of the finalized tip.
|
|
||||||
/// They are used to recreate the trees if a fork is needed.
|
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
fn parent_chain(
|
fn parent_chain(
|
||||||
&mut self,
|
&mut self,
|
||||||
parent_hash: block::Hash,
|
parent_hash: block::Hash,
|
||||||
sprout_note_commitment_tree: Arc<sprout::tree::NoteCommitmentTree>,
|
|
||||||
sapling_note_commitment_tree: Arc<sapling::tree::NoteCommitmentTree>,
|
|
||||||
orchard_note_commitment_tree: Arc<orchard::tree::NoteCommitmentTree>,
|
|
||||||
history_tree: Arc<HistoryTree>,
|
|
||||||
) -> Result<Arc<Chain>, ValidateContextError> {
|
) -> Result<Arc<Chain>, ValidateContextError> {
|
||||||
match self.find_chain(|chain| chain.non_finalized_tip_hash() == parent_hash) {
|
match self.find_chain(|chain| chain.non_finalized_tip_hash() == parent_hash) {
|
||||||
// Clone the existing Arc<Chain> in the non-finalized state
|
// Clone the existing Arc<Chain> in the non-finalized state
|
||||||
|
|
@ -480,18 +481,7 @@ impl NonFinalizedState {
|
||||||
let fork_chain = self
|
let fork_chain = self
|
||||||
.chain_set
|
.chain_set
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|chain| {
|
.find_map(|chain| chain.fork(parent_hash))
|
||||||
chain
|
|
||||||
.fork(
|
|
||||||
parent_hash,
|
|
||||||
sprout_note_commitment_tree.clone(),
|
|
||||||
sapling_note_commitment_tree.clone(),
|
|
||||||
orchard_note_commitment_tree.clone(),
|
|
||||||
history_tree.clone(),
|
|
||||||
)
|
|
||||||
.transpose()
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.ok_or(ValidateContextError::NotReadyToBeCommitted)?;
|
.ok_or(ValidateContextError::NotReadyToBeCommitted)?;
|
||||||
|
|
||||||
Ok(Arc::new(fork_chain))
|
Ok(Arc::new(fork_chain))
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -6,7 +6,7 @@ use zebra_test::prelude::*;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::NonNegative,
|
amount::NonNegative,
|
||||||
block::{self, arbitrary::allow_all_transparent_coinbase_spends, Block},
|
block::{self, arbitrary::allow_all_transparent_coinbase_spends, Block, Height},
|
||||||
fmt::DisplayToDebug,
|
fmt::DisplayToDebug,
|
||||||
history_tree::{HistoryTree, NonEmptyHistoryTree},
|
history_tree::{HistoryTree, NonEmptyHistoryTree},
|
||||||
parameters::NetworkUpgrade::*,
|
parameters::NetworkUpgrade::*,
|
||||||
|
|
@ -47,7 +47,7 @@ fn push_genesis_chain() -> Result<()> {
|
||||||
|((chain, count, network, empty_tree) in PreparedChain::default())| {
|
|((chain, count, network, empty_tree) in PreparedChain::default())| {
|
||||||
prop_assert!(empty_tree.is_none());
|
prop_assert!(empty_tree.is_none());
|
||||||
|
|
||||||
let mut only_chain = Chain::new(network, Default::default(), Default::default(), Default::default(), empty_tree, ValueBalance::zero());
|
let mut only_chain = Chain::new(network, Height(0), Default::default(), Default::default(), Default::default(), empty_tree, ValueBalance::zero());
|
||||||
// contains the block value pool changes and chain value pool balances for each height
|
// contains the block value pool changes and chain value pool balances for each height
|
||||||
let mut chain_values = BTreeMap::new();
|
let mut chain_values = BTreeMap::new();
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ fn push_history_tree_chain() -> Result<()> {
|
||||||
let count = std::cmp::min(count, chain.len() - 1);
|
let count = std::cmp::min(count, chain.len() - 1);
|
||||||
let chain = &chain[1..];
|
let chain = &chain[1..];
|
||||||
|
|
||||||
let mut only_chain = Chain::new(network, Default::default(), Default::default(), Default::default(), finalized_tree, ValueBalance::zero());
|
let mut only_chain = Chain::new(network, Height(0), Default::default(), Default::default(), Default::default(), finalized_tree, ValueBalance::zero());
|
||||||
|
|
||||||
for block in chain
|
for block in chain
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -143,6 +143,7 @@ fn forked_equals_pushed_genesis() -> Result<()> {
|
||||||
// correspond to the blocks in the original chain before the fork.
|
// correspond to the blocks in the original chain before the fork.
|
||||||
let mut partial_chain = Chain::new(
|
let mut partial_chain = Chain::new(
|
||||||
network,
|
network,
|
||||||
|
Height(0),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|
@ -162,10 +163,11 @@ fn forked_equals_pushed_genesis() -> Result<()> {
|
||||||
// This chain will be forked.
|
// This chain will be forked.
|
||||||
let mut full_chain = Chain::new(
|
let mut full_chain = Chain::new(
|
||||||
network,
|
network,
|
||||||
|
Height(0),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
empty_tree.clone(),
|
empty_tree,
|
||||||
ValueBalance::zero(),
|
ValueBalance::zero(),
|
||||||
);
|
);
|
||||||
for block in chain.iter().cloned() {
|
for block in chain.iter().cloned() {
|
||||||
|
|
@ -195,18 +197,12 @@ fn forked_equals_pushed_genesis() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use [`fork_at_count`] as the fork tip.
|
// Use [`fork_at_count`] as the fork tip.
|
||||||
let fork_tip_hash = chain[fork_at_count - 1].hash;
|
let fork_tip_height = fork_at_count - 1;
|
||||||
|
let fork_tip_hash = chain[fork_tip_height].hash;
|
||||||
|
|
||||||
// Fork the chain.
|
// Fork the chain.
|
||||||
let mut forked = full_chain
|
let mut forked = full_chain
|
||||||
.fork(
|
.fork(fork_tip_hash)
|
||||||
fork_tip_hash,
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
empty_tree,
|
|
||||||
)
|
|
||||||
.expect("fork works")
|
|
||||||
.expect("hash is present");
|
.expect("hash is present");
|
||||||
|
|
||||||
// This check is redundant, but it's useful for debugging.
|
// This check is redundant, but it's useful for debugging.
|
||||||
|
|
@ -254,8 +250,8 @@ fn forked_equals_pushed_history_tree() -> Result<()> {
|
||||||
// use `fork_at_count` as the fork tip
|
// use `fork_at_count` as the fork tip
|
||||||
let fork_tip_hash = chain[fork_at_count - 1].hash;
|
let fork_tip_hash = chain[fork_at_count - 1].hash;
|
||||||
|
|
||||||
let mut full_chain = Chain::new(network, Default::default(), Default::default(), Default::default(), finalized_tree.clone(), ValueBalance::zero());
|
let mut full_chain = Chain::new(network, Height(0), Default::default(), Default::default(), Default::default(), finalized_tree.clone(), ValueBalance::zero());
|
||||||
let mut partial_chain = Chain::new(network, Default::default(), Default::default(), Default::default(), finalized_tree.clone(), ValueBalance::zero());
|
let mut partial_chain = Chain::new(network, Height(0), Default::default(), Default::default(), Default::default(), finalized_tree, ValueBalance::zero());
|
||||||
|
|
||||||
for block in chain
|
for block in chain
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -271,14 +267,7 @@ fn forked_equals_pushed_history_tree() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut forked = full_chain
|
let mut forked = full_chain
|
||||||
.fork(
|
.fork(fork_tip_hash)
|
||||||
fork_tip_hash,
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
finalized_tree,
|
|
||||||
)
|
|
||||||
.expect("fork works")
|
|
||||||
.expect("hash is present");
|
.expect("hash is present");
|
||||||
|
|
||||||
// the first check is redundant, but it's useful for debugging
|
// the first check is redundant, but it's useful for debugging
|
||||||
|
|
@ -318,14 +307,17 @@ fn finalized_equals_pushed_genesis() -> Result<()> {
|
||||||
|
|
||||||
prop_assert!(empty_tree.is_none());
|
prop_assert!(empty_tree.is_none());
|
||||||
|
|
||||||
|
// TODO: fix this test or the code so the full_chain temporary trees aren't overwritten
|
||||||
|
let chain = chain.iter().filter(|block| block.height != Height(0));
|
||||||
|
|
||||||
// use `end_count` as the number of non-finalized blocks at the end of the chain
|
// use `end_count` as the number of non-finalized blocks at the end of the chain
|
||||||
let finalized_count = chain.len() - end_count;
|
let finalized_count = chain.clone().count() - end_count;
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
|
|
||||||
let mut full_chain = Chain::new(network, Default::default(), Default::default(), Default::default(), empty_tree, fake_value_pool);
|
let mut full_chain = Chain::new(network, Height(0), Default::default(), Default::default(), Default::default(), empty_tree, fake_value_pool);
|
||||||
for block in chain
|
for block in chain
|
||||||
.iter()
|
.clone()
|
||||||
.take(finalized_count)
|
.take(finalized_count)
|
||||||
.map(ContextuallyValidBlock::test_with_zero_spent_utxos) {
|
.map(ContextuallyValidBlock::test_with_zero_spent_utxos) {
|
||||||
full_chain = full_chain.push(block)?;
|
full_chain = full_chain.push(block)?;
|
||||||
|
|
@ -333,21 +325,21 @@ fn finalized_equals_pushed_genesis() -> Result<()> {
|
||||||
|
|
||||||
let mut partial_chain = Chain::new(
|
let mut partial_chain = Chain::new(
|
||||||
network,
|
network,
|
||||||
full_chain.sprout_note_commitment_tree.clone(),
|
full_chain.non_finalized_tip_height(),
|
||||||
full_chain.sapling_note_commitment_tree.clone(),
|
full_chain.sprout_note_commitment_tree(),
|
||||||
full_chain.orchard_note_commitment_tree.clone(),
|
full_chain.sapling_note_commitment_tree(),
|
||||||
full_chain.history_tree.clone(),
|
full_chain.orchard_note_commitment_tree(),
|
||||||
|
full_chain.history_block_commitment_tree(),
|
||||||
full_chain.chain_value_pools,
|
full_chain.chain_value_pools,
|
||||||
);
|
);
|
||||||
for block in chain
|
for block in chain
|
||||||
.iter()
|
.clone()
|
||||||
.skip(finalized_count)
|
.skip(finalized_count)
|
||||||
.map(ContextuallyValidBlock::test_with_zero_spent_utxos) {
|
.map(ContextuallyValidBlock::test_with_zero_spent_utxos) {
|
||||||
partial_chain = partial_chain.push(block.clone())?;
|
partial_chain = partial_chain.push(block.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for block in chain
|
for block in chain
|
||||||
.iter()
|
|
||||||
.skip(finalized_count)
|
.skip(finalized_count)
|
||||||
.map(ContextuallyValidBlock::test_with_zero_spent_utxos) {
|
.map(ContextuallyValidBlock::test_with_zero_spent_utxos) {
|
||||||
full_chain = full_chain.push(block.clone())?;
|
full_chain = full_chain.push(block.clone())?;
|
||||||
|
|
@ -357,8 +349,18 @@ fn finalized_equals_pushed_genesis() -> Result<()> {
|
||||||
full_chain.pop_root();
|
full_chain.pop_root();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the temporary trees from finalized tip forks are removed.
|
||||||
|
// TODO: update the test or the code so this extra step isn't needed?
|
||||||
|
full_chain.pop_root();
|
||||||
|
partial_chain.pop_root();
|
||||||
|
|
||||||
prop_assert_eq!(full_chain.blocks.len(), partial_chain.blocks.len());
|
prop_assert_eq!(full_chain.blocks.len(), partial_chain.blocks.len());
|
||||||
prop_assert!(full_chain.eq_internal_state(&partial_chain));
|
prop_assert!(
|
||||||
|
full_chain.eq_internal_state(&partial_chain),
|
||||||
|
"\n\
|
||||||
|
full chain:\n{full_chain:#?}\n\n\
|
||||||
|
partial chain:\n{partial_chain:#?}\n",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -393,7 +395,7 @@ fn finalized_equals_pushed_history_tree() -> Result<()> {
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
|
|
||||||
let mut full_chain = Chain::new(network, Default::default(), Default::default(), Default::default(), finalized_tree, fake_value_pool);
|
let mut full_chain = Chain::new(network, Height(0), Default::default(), Default::default(), Default::default(), finalized_tree, fake_value_pool);
|
||||||
for block in chain
|
for block in chain
|
||||||
.iter()
|
.iter()
|
||||||
.take(finalized_count)
|
.take(finalized_count)
|
||||||
|
|
@ -403,10 +405,11 @@ fn finalized_equals_pushed_history_tree() -> Result<()> {
|
||||||
|
|
||||||
let mut partial_chain = Chain::new(
|
let mut partial_chain = Chain::new(
|
||||||
network,
|
network,
|
||||||
full_chain.sprout_note_commitment_tree.clone(),
|
Height(finalized_count.try_into().unwrap()),
|
||||||
full_chain.sapling_note_commitment_tree.clone(),
|
full_chain.sprout_note_commitment_tree(),
|
||||||
full_chain.orchard_note_commitment_tree.clone(),
|
full_chain.sapling_note_commitment_tree(),
|
||||||
full_chain.history_tree.clone(),
|
full_chain.orchard_note_commitment_tree(),
|
||||||
|
full_chain.history_block_commitment_tree(),
|
||||||
full_chain.chain_value_pools,
|
full_chain.chain_value_pools,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -428,8 +431,18 @@ fn finalized_equals_pushed_history_tree() -> Result<()> {
|
||||||
full_chain.pop_root();
|
full_chain.pop_root();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the temporary trees from finalized tip forks are removed.
|
||||||
|
// TODO: update the test or the code so this extra step isn't needed?
|
||||||
|
full_chain.pop_root();
|
||||||
|
partial_chain.pop_root();
|
||||||
|
|
||||||
prop_assert_eq!(full_chain.blocks.len(), partial_chain.blocks.len());
|
prop_assert_eq!(full_chain.blocks.len(), partial_chain.blocks.len());
|
||||||
prop_assert!(full_chain.eq_internal_state(&partial_chain));
|
prop_assert!(
|
||||||
|
full_chain.eq_internal_state(&partial_chain),
|
||||||
|
"\n\
|
||||||
|
full chain:\n{full_chain:#?}\n\n\
|
||||||
|
partial chain:\n{partial_chain:#?}\n",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -570,8 +583,8 @@ fn different_blocks_different_chains() -> Result<()> {
|
||||||
Default::default()
|
Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let chain1 = Chain::new(Network::Mainnet, Default::default(), Default::default(), Default::default(), finalized_tree1, ValueBalance::fake_populated_pool());
|
let chain1 = Chain::new(Network::Mainnet, Height(0), Default::default(), Default::default(), Default::default(), finalized_tree1, ValueBalance::fake_populated_pool());
|
||||||
let chain2 = Chain::new(Network::Mainnet, Default::default(), Default::default(), Default::default(), finalized_tree2, ValueBalance::fake_populated_pool());
|
let chain2 = Chain::new(Network::Mainnet, Height(0), Default::default(), Default::default(), Default::default(), finalized_tree2, ValueBalance::fake_populated_pool());
|
||||||
|
|
||||||
let block1 = vec1[1].clone().prepare().test_with_zero_spent_utxos();
|
let block1 = vec1[1].clone().prepare().test_with_zero_spent_utxos();
|
||||||
let block2 = vec2[1].clone().prepare().test_with_zero_spent_utxos();
|
let block2 = vec2[1].clone().prepare().test_with_zero_spent_utxos();
|
||||||
|
|
@ -606,16 +619,12 @@ fn different_blocks_different_chains() -> Result<()> {
|
||||||
chain1.spent_utxos = chain2.spent_utxos.clone();
|
chain1.spent_utxos = chain2.spent_utxos.clone();
|
||||||
|
|
||||||
// note commitment trees
|
// note commitment trees
|
||||||
chain1.sprout_note_commitment_tree = chain2.sprout_note_commitment_tree.clone();
|
|
||||||
chain1.sprout_trees_by_anchor = chain2.sprout_trees_by_anchor.clone();
|
chain1.sprout_trees_by_anchor = chain2.sprout_trees_by_anchor.clone();
|
||||||
chain1.sprout_trees_by_height = chain2.sprout_trees_by_height.clone();
|
chain1.sprout_trees_by_height = chain2.sprout_trees_by_height.clone();
|
||||||
chain1.sapling_note_commitment_tree = chain2.sapling_note_commitment_tree.clone();
|
|
||||||
chain1.sapling_trees_by_height = chain2.sapling_trees_by_height.clone();
|
chain1.sapling_trees_by_height = chain2.sapling_trees_by_height.clone();
|
||||||
chain1.orchard_note_commitment_tree = chain2.orchard_note_commitment_tree.clone();
|
|
||||||
chain1.orchard_trees_by_height = chain2.orchard_trees_by_height.clone();
|
chain1.orchard_trees_by_height = chain2.orchard_trees_by_height.clone();
|
||||||
|
|
||||||
// history trees
|
// history trees
|
||||||
chain1.history_tree = chain2.history_tree.clone();
|
|
||||||
chain1.history_trees_by_height = chain2.history_trees_by_height.clone();
|
chain1.history_trees_by_height = chain2.history_trees_by_height.clone();
|
||||||
|
|
||||||
// anchors
|
// anchors
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::NonNegative,
|
amount::NonNegative,
|
||||||
block::Block,
|
block::{Block, Height},
|
||||||
history_tree::NonEmptyHistoryTree,
|
history_tree::NonEmptyHistoryTree,
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
serialization::ZcashDeserializeInto,
|
serialization::ZcashDeserializeInto,
|
||||||
|
|
@ -27,6 +27,7 @@ fn construct_empty() {
|
||||||
let _init_guard = zebra_test::init();
|
let _init_guard = zebra_test::init();
|
||||||
let _chain = Chain::new(
|
let _chain = Chain::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
|
Height(0),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|
@ -43,6 +44,7 @@ fn construct_single() -> Result<()> {
|
||||||
|
|
||||||
let mut chain = Chain::new(
|
let mut chain = Chain::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
|
Height(0),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|
@ -73,6 +75,7 @@ fn construct_many() -> Result<()> {
|
||||||
|
|
||||||
let mut chain = Chain::new(
|
let mut chain = Chain::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
|
Height(block.coinbase_height().unwrap().0 - 1),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|
@ -99,6 +102,7 @@ fn ord_matches_work() -> Result<()> {
|
||||||
|
|
||||||
let mut lesser_chain = Chain::new(
|
let mut lesser_chain = Chain::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
|
Height(0),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|
@ -109,6 +113,7 @@ fn ord_matches_work() -> Result<()> {
|
||||||
|
|
||||||
let mut bigger_chain = Chain::new(
|
let mut bigger_chain = Chain::new(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
|
Height(0),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|
@ -434,12 +439,12 @@ fn history_tree_is_updated_for_network_upgrade(
|
||||||
let chain = state.best_chain().unwrap();
|
let chain = state.best_chain().unwrap();
|
||||||
if network_upgrade == NetworkUpgrade::Heartwood {
|
if network_upgrade == NetworkUpgrade::Heartwood {
|
||||||
assert!(
|
assert!(
|
||||||
chain.history_tree.as_ref().is_none(),
|
chain.history_block_commitment_tree().as_ref().is_none(),
|
||||||
"history tree must not exist yet"
|
"history tree must not exist yet"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
assert!(
|
assert!(
|
||||||
chain.history_tree.as_ref().is_some(),
|
chain.history_block_commitment_tree().as_ref().is_some(),
|
||||||
"history tree must already exist"
|
"history tree must already exist"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -453,11 +458,16 @@ fn history_tree_is_updated_for_network_upgrade(
|
||||||
|
|
||||||
let chain = state.best_chain().unwrap();
|
let chain = state.best_chain().unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
chain.history_tree.as_ref().is_some(),
|
chain.history_block_commitment_tree().as_ref().is_some(),
|
||||||
"history tree must have been (re)created"
|
"history tree must have been (re)created"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
chain.history_tree.as_ref().as_ref().unwrap().size(),
|
chain
|
||||||
|
.history_block_commitment_tree()
|
||||||
|
.as_ref()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.size(),
|
||||||
1,
|
1,
|
||||||
"history tree must have a single node"
|
"history tree must have a single node"
|
||||||
);
|
);
|
||||||
|
|
@ -466,8 +476,8 @@ fn history_tree_is_updated_for_network_upgrade(
|
||||||
let tree = NonEmptyHistoryTree::from_block(
|
let tree = NonEmptyHistoryTree::from_block(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
activation_block.clone(),
|
activation_block.clone(),
|
||||||
&chain.sapling_note_commitment_tree.root(),
|
&chain.sapling_note_commitment_tree().root(),
|
||||||
&chain.orchard_note_commitment_tree.root(),
|
&chain.orchard_note_commitment_tree().root(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
@ -480,7 +490,12 @@ fn history_tree_is_updated_for_network_upgrade(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
state.best_chain().unwrap().history_tree.as_ref().is_some(),
|
state
|
||||||
|
.best_chain()
|
||||||
|
.unwrap()
|
||||||
|
.history_block_commitment_tree()
|
||||||
|
.as_ref()
|
||||||
|
.is_some(),
|
||||||
"history tree must still exist"
|
"history tree must still exist"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -541,8 +556,8 @@ fn commitment_is_validated_for_network_upgrade(network: Network, network_upgrade
|
||||||
let tree = NonEmptyHistoryTree::from_block(
|
let tree = NonEmptyHistoryTree::from_block(
|
||||||
Network::Mainnet,
|
Network::Mainnet,
|
||||||
activation_block.clone(),
|
activation_block.clone(),
|
||||||
&chain.sapling_note_commitment_tree.root(),
|
&chain.sapling_note_commitment_tree().root(),
|
||||||
&chain.orchard_note_commitment_tree.root(),
|
&chain.orchard_note_commitment_tree().root(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue