Add ZIP-221 history tree to non-finalized state (#2583)
* Refactor HistoryTree into NonEmptyHistoryTree and HistoryTree * HistoryTree: use Deref instead of AsRef; remove unneeded PartialEq * ZIP-221: Validate chain history commitments in the non-finalized state (#2301) * sketch of implementation * refined implementation; still incomplete * update librustzcash, change zcash_history to work with it * simplified code per review; renamed MMR to HistoryTree * expand HistoryTree implementation * handle and propagate errors * simplify check.rs tracing * add suggested TODO * add HistoryTree::prune * fix bug in pruning * fix compilation of tests; still need to make them pass * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * improvements from code review * improve check.rs comments and variable names * fix HistoryTree which should use BTreeMap and not HashMap; fix non_finalized_state prop tests * fix finalized_state proptest * fix non_finalized_state tests by setting the correct commitments * renamed mmr.rs to history_tree.rs * Add HistoryTree struct * expand non_finalized_state protest * fix typo * Add HistoryTree struct * Update zebra-chain/src/primitives/zcash_history.rs Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> * fix formatting * Apply suggestions from code review Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> * history_tree.rs: fixes from code review * fixes to work with updated HistoryTree * Improvements from code review * Add Debug implementations to allow comparing Chains with proptest_assert_eq Co-authored-by: teor <teor@riseup.net> Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * Improvements from code review * Restore blocks returned by PreparedChain since other tests broken; adjust tests with history trees Co-authored-by: teor <teor@riseup.net> Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>
This commit is contained in:
parent
298ececabe
commit
94175c6955
|
@ -149,6 +149,12 @@ impl From<[u8; 32]> for ChainHistoryMmrRootHash {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ChainHistoryMmrRootHash> for [u8; 32] {
|
||||||
|
fn from(hash: ChainHistoryMmrRootHash) -> Self {
|
||||||
|
hash.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A block commitment to chain history and transaction auth.
|
/// A block commitment to chain history and transaction auth.
|
||||||
/// - the chain history tree for all ancestors in the current network upgrade,
|
/// - the chain history tree for all ancestors in the current network upgrade,
|
||||||
/// and
|
/// and
|
||||||
|
@ -170,7 +176,7 @@ pub struct ChainHistoryBlockTxAuthCommitmentHash([u8; 32]);
|
||||||
/// implement, and ensures that we don't reject blocks or transactions
|
/// implement, and ensures that we don't reject blocks or transactions
|
||||||
/// for a non-enumerated reason.
|
/// for a non-enumerated reason.
|
||||||
#[allow(dead_code, missing_docs)]
|
#[allow(dead_code, missing_docs)]
|
||||||
#[derive(Error, Debug, PartialEq)]
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
pub enum CommitmentError {
|
pub enum CommitmentError {
|
||||||
#[error("invalid final sapling root: expected {expected:?}, actual: {actual:?}")]
|
#[error("invalid final sapling root: expected {expected:?}, actual: {actual:?}")]
|
||||||
InvalidFinalSaplingRoot {
|
InvalidFinalSaplingRoot {
|
||||||
|
|
|
@ -33,6 +33,16 @@ pub enum HistoryTreeError {
|
||||||
IOError(#[from] io::Error),
|
IOError(#[from] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for HistoryTreeError {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
// Workaround since subtypes do not implement Eq.
|
||||||
|
// This is only used for tests anyway.
|
||||||
|
format!("{:?}", self) == format!("{:?}", other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for HistoryTreeError {}
|
||||||
|
|
||||||
/// The inner [Tree] in one of its supported versions.
|
/// The inner [Tree] in one of its supported versions.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum InnerHistoryTree {
|
enum InnerHistoryTree {
|
||||||
|
@ -403,6 +413,30 @@ impl Clone for NonEmptyHistoryTree {
|
||||||
pub struct HistoryTree(Option<NonEmptyHistoryTree>);
|
pub struct HistoryTree(Option<NonEmptyHistoryTree>);
|
||||||
|
|
||||||
impl HistoryTree {
|
impl HistoryTree {
|
||||||
|
/// Create a HistoryTree from a block.
|
||||||
|
///
|
||||||
|
/// If the block is pre-Heartwood, it returns an empty history tree.
|
||||||
|
pub fn from_block(
|
||||||
|
network: Network,
|
||||||
|
block: Arc<Block>,
|
||||||
|
sapling_root: &sapling::tree::Root,
|
||||||
|
orchard_root: &orchard::tree::Root,
|
||||||
|
) -> Result<Self, HistoryTreeError> {
|
||||||
|
let heartwood_height = NetworkUpgrade::Heartwood
|
||||||
|
.activation_height(network)
|
||||||
|
.expect("Heartwood height is known");
|
||||||
|
match block
|
||||||
|
.coinbase_height()
|
||||||
|
.expect("must have height")
|
||||||
|
.cmp(&heartwood_height)
|
||||||
|
{
|
||||||
|
std::cmp::Ordering::Less => Ok(HistoryTree(None)),
|
||||||
|
_ => Ok(
|
||||||
|
NonEmptyHistoryTree::from_block(network, block, sapling_root, orchard_root)?.into(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Push a block to a maybe-existing HistoryTree, handling network upgrades.
|
/// Push a block to a maybe-existing HistoryTree, handling network upgrades.
|
||||||
///
|
///
|
||||||
/// The tree is updated in-place. It is created when pushing the Heartwood
|
/// The tree is updated in-place. It is created when pushing the Heartwood
|
||||||
|
|
|
@ -4,8 +4,8 @@ use chrono::{DateTime, Utc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount, block, orchard, sapling, sprout, transparent, value_balance::ValueBalanceError,
|
amount, block, history_tree::HistoryTreeError, orchard, sapling, sprout, transparent,
|
||||||
work::difficulty::CompactDifficulty,
|
value_balance::ValueBalanceError, work::difficulty::CompactDifficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::constants::MIN_TRANSPARENT_COINBASE_MATURITY;
|
use crate::constants::MIN_TRANSPARENT_COINBASE_MATURITY;
|
||||||
|
@ -36,12 +36,12 @@ impl From<BoxError> for CloneError {
|
||||||
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||||
|
|
||||||
/// An error describing the reason a block could not be committed to the state.
|
/// An error describing the reason a block could not be committed to the state.
|
||||||
#[derive(Debug, Error, Clone, PartialEq, Eq)]
|
#[derive(Debug, Error, PartialEq, Eq)]
|
||||||
#[error("block is not contextually valid")]
|
#[error("block is not contextually valid")]
|
||||||
pub struct CommitBlockError(#[from] ValidateContextError);
|
pub struct CommitBlockError(#[from] ValidateContextError);
|
||||||
|
|
||||||
/// An error describing why a block failed contextual validation.
|
/// An error describing why a block failed contextual validation.
|
||||||
#[derive(Debug, Error, Clone, PartialEq, Eq)]
|
#[derive(Debug, Error, PartialEq, Eq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum ValidateContextError {
|
pub enum ValidateContextError {
|
||||||
|
@ -185,6 +185,9 @@ pub enum ValidateContextError {
|
||||||
|
|
||||||
#[error("error in Orchard note commitment tree")]
|
#[error("error in Orchard note commitment tree")]
|
||||||
OrchardNoteCommitmentTreeError(#[from] zebra_chain::orchard::tree::NoteCommitmentTreeError),
|
OrchardNoteCommitmentTreeError(#[from] zebra_chain::orchard::tree::NoteCommitmentTreeError),
|
||||||
|
|
||||||
|
#[error("error building the history tree")]
|
||||||
|
HistoryTreeError(#[from] HistoryTreeError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for creating the corresponding duplicate nullifier error from a nullifier.
|
/// Trait for creating the corresponding duplicate nullifier error from a nullifier.
|
||||||
|
|
|
@ -10,6 +10,7 @@ use proptest::{
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
fmt::SummaryDebug,
|
fmt::SummaryDebug,
|
||||||
|
history_tree::HistoryTree,
|
||||||
parameters::NetworkUpgrade,
|
parameters::NetworkUpgrade,
|
||||||
LedgerState,
|
LedgerState,
|
||||||
};
|
};
|
||||||
|
@ -34,6 +35,7 @@ pub struct PreparedChainTree {
|
||||||
chain: Arc<SummaryDebug<Vec<PreparedBlock>>>,
|
chain: Arc<SummaryDebug<Vec<PreparedBlock>>>,
|
||||||
count: BinarySearch,
|
count: BinarySearch,
|
||||||
network: Network,
|
network: Network,
|
||||||
|
history_tree: HistoryTree,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueTree for PreparedChainTree {
|
impl ValueTree for PreparedChainTree {
|
||||||
|
@ -41,10 +43,16 @@ impl ValueTree for PreparedChainTree {
|
||||||
Arc<SummaryDebug<Vec<PreparedBlock>>>,
|
Arc<SummaryDebug<Vec<PreparedBlock>>>,
|
||||||
<BinarySearch as ValueTree>::Value,
|
<BinarySearch as ValueTree>::Value,
|
||||||
Network,
|
Network,
|
||||||
|
HistoryTree,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn current(&self) -> Self::Value {
|
fn current(&self) -> Self::Value {
|
||||||
(self.chain.clone(), self.count.current(), self.network)
|
(
|
||||||
|
self.chain.clone(),
|
||||||
|
self.count.current(),
|
||||||
|
self.network,
|
||||||
|
self.history_tree.clone(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simplify(&mut self) -> bool {
|
fn simplify(&mut self) -> bool {
|
||||||
|
@ -59,7 +67,36 @@ impl ValueTree for PreparedChainTree {
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct PreparedChain {
|
pub struct PreparedChain {
|
||||||
// the proptests are threaded (not async), so we want to use a threaded mutex here
|
// the proptests are threaded (not async), so we want to use a threaded mutex here
|
||||||
chain: std::sync::Mutex<Option<(Network, Arc<SummaryDebug<Vec<PreparedBlock>>>)>>,
|
chain: std::sync::Mutex<Option<(Network, Arc<SummaryDebug<Vec<PreparedBlock>>>, HistoryTree)>>,
|
||||||
|
// the strategy for generating LedgerStates. If None, it calls [`LedgerState::genesis_strategy`].
|
||||||
|
ledger_strategy: Option<BoxedStrategy<LedgerState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreparedChain {
|
||||||
|
/// Create a PreparedChain strategy with Heartwood-onward blocks.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn new_heartwood() -> Self {
|
||||||
|
// The history tree only works with Heartwood onward.
|
||||||
|
// Since the network will be chosen later, we pick the larger
|
||||||
|
// between the mainnet and testnet Heartwood activation heights.
|
||||||
|
let main_height = NetworkUpgrade::Heartwood
|
||||||
|
.activation_height(Network::Mainnet)
|
||||||
|
.expect("must have height");
|
||||||
|
let test_height = NetworkUpgrade::Heartwood
|
||||||
|
.activation_height(Network::Testnet)
|
||||||
|
.expect("must have height");
|
||||||
|
let height = std::cmp::max(main_height, test_height);
|
||||||
|
|
||||||
|
PreparedChain {
|
||||||
|
ledger_strategy: Some(LedgerState::height_strategy(
|
||||||
|
height,
|
||||||
|
NetworkUpgrade::Nu5,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Strategy for PreparedChain {
|
impl Strategy for PreparedChain {
|
||||||
|
@ -70,7 +107,12 @@ impl Strategy for PreparedChain {
|
||||||
let mut chain = self.chain.lock().unwrap();
|
let mut chain = self.chain.lock().unwrap();
|
||||||
if chain.is_none() {
|
if chain.is_none() {
|
||||||
// TODO: use the latest network upgrade (#1974)
|
// TODO: use the latest network upgrade (#1974)
|
||||||
let ledger_strategy = LedgerState::genesis_strategy(NetworkUpgrade::Nu5, None, false);
|
let default_ledger_strategy =
|
||||||
|
LedgerState::genesis_strategy(NetworkUpgrade::Nu5, None, false);
|
||||||
|
let ledger_strategy = self
|
||||||
|
.ledger_strategy
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_ledger_strategy);
|
||||||
|
|
||||||
let (network, blocks) = ledger_strategy
|
let (network, blocks) = ledger_strategy
|
||||||
.prop_flat_map(|ledger| {
|
.prop_flat_map(|ledger| {
|
||||||
|
@ -93,7 +135,16 @@ impl Strategy for PreparedChain {
|
||||||
})
|
})
|
||||||
.new_tree(runner)?
|
.new_tree(runner)?
|
||||||
.current();
|
.current();
|
||||||
*chain = Some((network, Arc::new(SummaryDebug(blocks))));
|
// Generate a history tree from the first block
|
||||||
|
let history_tree = HistoryTree::from_block(
|
||||||
|
network,
|
||||||
|
blocks[0].block.clone(),
|
||||||
|
// Dummy roots since this is only used for tests
|
||||||
|
&Default::default(),
|
||||||
|
&Default::default(),
|
||||||
|
)
|
||||||
|
.expect("history tree should be created");
|
||||||
|
*chain = Some((network, Arc::new(SummaryDebug(blocks)), history_tree));
|
||||||
}
|
}
|
||||||
|
|
||||||
let chain = chain.clone().expect("should be generated");
|
let chain = chain.clone().expect("should be generated");
|
||||||
|
@ -102,6 +153,7 @@ impl Strategy for PreparedChain {
|
||||||
chain: chain.1,
|
chain: chain.1,
|
||||||
count,
|
count,
|
||||||
network: chain.0,
|
network: chain.0,
|
||||||
|
history_tree: chain.2,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ fn blocks_with_v5_transactions() -> Result<()> {
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
||||||
|((chain, count, network) in PreparedChain::default())| {
|
|((chain, count, network, _history_tree) in PreparedChain::default())| {
|
||||||
let mut state = FinalizedState::new(&Config::ephemeral(), network);
|
let mut state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
let mut height = Height(0);
|
let mut height = Height(0);
|
||||||
// use `count` to minimize test failures, so they are easier to diagnose
|
// use `count` to minimize test failures, so they are easier to diagnose
|
||||||
|
|
|
@ -14,6 +14,7 @@ use std::{collections::BTreeSet, mem, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
|
history_tree::HistoryTree,
|
||||||
orchard,
|
orchard,
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
sapling,
|
sapling,
|
||||||
|
@ -129,6 +130,7 @@ impl NonFinalizedState {
|
||||||
parent_hash,
|
parent_hash,
|
||||||
finalized_state.sapling_note_commitment_tree(),
|
finalized_state.sapling_note_commitment_tree(),
|
||||||
finalized_state.orchard_note_commitment_tree(),
|
finalized_state.orchard_note_commitment_tree(),
|
||||||
|
finalized_state.history_tree(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// We might have taken a chain, so all validation must happen within
|
// We might have taken a chain, so all validation must happen within
|
||||||
|
@ -161,8 +163,10 @@ impl NonFinalizedState {
|
||||||
finalized_state: &FinalizedState,
|
finalized_state: &FinalizedState,
|
||||||
) -> Result<(), ValidateContextError> {
|
) -> Result<(), ValidateContextError> {
|
||||||
let chain = Chain::new(
|
let chain = Chain::new(
|
||||||
|
self.network,
|
||||||
finalized_state.sapling_note_commitment_tree(),
|
finalized_state.sapling_note_commitment_tree(),
|
||||||
finalized_state.orchard_note_commitment_tree(),
|
finalized_state.orchard_note_commitment_tree(),
|
||||||
|
finalized_state.history_tree(),
|
||||||
);
|
);
|
||||||
let (height, hash) = (prepared.height, prepared.hash);
|
let (height, hash) = (prepared.height, prepared.hash);
|
||||||
|
|
||||||
|
@ -355,13 +359,14 @@ impl NonFinalizedState {
|
||||||
/// 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, if needed.
|
||||||
///
|
///
|
||||||
/// The note commitment trees must be the trees of the finalized tip.
|
/// The trees must be the trees of the finalized tip.
|
||||||
/// They are used to recreate the trees if a fork is needed.
|
/// They are used to recreate the trees if a fork is needed.
|
||||||
fn parent_chain(
|
fn parent_chain(
|
||||||
&mut self,
|
&mut self,
|
||||||
parent_hash: block::Hash,
|
parent_hash: block::Hash,
|
||||||
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
||||||
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
||||||
|
history_tree: HistoryTree,
|
||||||
) -> Result<Box<Chain>, ValidateContextError> {
|
) -> Result<Box<Chain>, ValidateContextError> {
|
||||||
match self.take_chain_if(|chain| chain.non_finalized_tip_hash() == parent_hash) {
|
match self.take_chain_if(|chain| chain.non_finalized_tip_hash() == parent_hash) {
|
||||||
// An existing chain in the non-finalized state
|
// An existing chain in the non-finalized state
|
||||||
|
@ -376,6 +381,7 @@ impl NonFinalizedState {
|
||||||
parent_hash,
|
parent_hash,
|
||||||
sapling_note_commitment_tree.clone(),
|
sapling_note_commitment_tree.clone(),
|
||||||
orchard_note_commitment_tree.clone(),
|
orchard_note_commitment_tree.clone(),
|
||||||
|
history_tree.clone(),
|
||||||
)
|
)
|
||||||
.transpose()
|
.transpose()
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,14 +8,16 @@ use multiset::HashMultiSet;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block, orchard, primitives::Groth16Proof, sapling, sprout, transaction,
|
block, history_tree::HistoryTree, orchard, parameters::Network, primitives::Groth16Proof,
|
||||||
transaction::Transaction::*, transparent, work::difficulty::PartialCumulativeWork,
|
sapling, sprout, transaction, transaction::Transaction::*, transparent,
|
||||||
|
work::difficulty::PartialCumulativeWork,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{service::check, ContextuallyValidBlock, PreparedBlock, ValidateContextError};
|
use crate::{service::check, ContextuallyValidBlock, PreparedBlock, ValidateContextError};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Chain {
|
pub struct Chain {
|
||||||
|
network: Network,
|
||||||
/// The contextually valid blocks which form this non-finalized partial chain, in height order.
|
/// The contextually valid blocks which form this non-finalized partial chain, in height order.
|
||||||
pub(crate) blocks: BTreeMap<block::Height, ContextuallyValidBlock>,
|
pub(crate) blocks: BTreeMap<block::Height, ContextuallyValidBlock>,
|
||||||
|
|
||||||
|
@ -37,6 +39,8 @@ pub struct Chain {
|
||||||
pub(super) sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
pub(super) sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
||||||
/// The Orchard note commitment tree of the tip of this Chain.
|
/// The Orchard note commitment tree of the tip of this Chain.
|
||||||
pub(super) orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
pub(super) orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
||||||
|
/// The ZIP-221 history tree of the tip of this Chain.
|
||||||
|
pub(crate) history_tree: HistoryTree,
|
||||||
|
|
||||||
/// The Sapling anchors created by `blocks`.
|
/// The Sapling anchors created by `blocks`.
|
||||||
pub(super) sapling_anchors: HashMultiSet<sapling::tree::Root>,
|
pub(super) sapling_anchors: HashMultiSet<sapling::tree::Root>,
|
||||||
|
@ -59,12 +63,15 @@ pub struct Chain {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chain {
|
impl Chain {
|
||||||
// Create a new Chain with the given note commitment trees.
|
/// Create a new Chain with the given trees and network.
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
|
network: Network,
|
||||||
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
||||||
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
||||||
|
history_tree: HistoryTree,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
network,
|
||||||
blocks: Default::default(),
|
blocks: Default::default(),
|
||||||
height_by_hash: Default::default(),
|
height_by_hash: Default::default(),
|
||||||
tx_by_hash: Default::default(),
|
tx_by_hash: Default::default(),
|
||||||
|
@ -80,6 +87,7 @@ impl Chain {
|
||||||
sapling_nullifiers: Default::default(),
|
sapling_nullifiers: Default::default(),
|
||||||
orchard_nullifiers: Default::default(),
|
orchard_nullifiers: Default::default(),
|
||||||
partial_cumulative_work: Default::default(),
|
partial_cumulative_work: Default::default(),
|
||||||
|
history_tree,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +103,8 @@ impl Chain {
|
||||||
/// even if the blocks in the two chains are equal.
|
/// even if the blocks in the two chains are equal.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn eq_internal_state(&self, other: &Chain) -> bool {
|
pub(crate) fn eq_internal_state(&self, other: &Chain) -> bool {
|
||||||
|
use zebra_chain::history_tree::NonEmptyHistoryTree;
|
||||||
|
|
||||||
// this method must be updated every time a field is added to Chain
|
// this method must be updated every time a field is added to Chain
|
||||||
|
|
||||||
// blocks, heights, hashes
|
// blocks, heights, hashes
|
||||||
|
@ -110,6 +120,9 @@ impl Chain {
|
||||||
self.sapling_note_commitment_tree.root() == other.sapling_note_commitment_tree.root() &&
|
self.sapling_note_commitment_tree.root() == other.sapling_note_commitment_tree.root() &&
|
||||||
self.orchard_note_commitment_tree.root() == other.orchard_note_commitment_tree.root() &&
|
self.orchard_note_commitment_tree.root() == other.orchard_note_commitment_tree.root() &&
|
||||||
|
|
||||||
|
// history tree
|
||||||
|
self.history_tree.as_ref().map(NonEmptyHistoryTree::hash) == other.history_tree.as_ref().map(NonEmptyHistoryTree::hash) &&
|
||||||
|
|
||||||
// anchors
|
// anchors
|
||||||
self.sapling_anchors == other.sapling_anchors &&
|
self.sapling_anchors == other.sapling_anchors &&
|
||||||
self.sapling_anchors_by_height == other.sapling_anchors_by_height &&
|
self.sapling_anchors_by_height == other.sapling_anchors_by_height &&
|
||||||
|
@ -169,19 +182,24 @@ impl Chain {
|
||||||
/// Fork a chain at the block with the given hash, if it is part of this
|
/// Fork a chain at the block with the given hash, if it is part of this
|
||||||
/// chain.
|
/// chain.
|
||||||
///
|
///
|
||||||
/// The note commitment trees must be the trees of the finalized tip.
|
/// The trees must match the trees of the finalized tip and are used
|
||||||
|
/// to rebuild them after the fork.
|
||||||
pub fn fork(
|
pub fn fork(
|
||||||
&self,
|
&self,
|
||||||
fork_tip: block::Hash,
|
fork_tip: block::Hash,
|
||||||
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
||||||
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
||||||
|
history_tree: HistoryTree,
|
||||||
) -> Result<Option<Self>, ValidateContextError> {
|
) -> Result<Option<Self>, ValidateContextError> {
|
||||||
if !self.height_by_hash.contains_key(&fork_tip) {
|
if !self.height_by_hash.contains_key(&fork_tip) {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut forked =
|
let mut forked = self.with_trees(
|
||||||
self.with_trees(sapling_note_commitment_tree, orchard_note_commitment_tree);
|
sapling_note_commitment_tree,
|
||||||
|
orchard_note_commitment_tree,
|
||||||
|
history_tree,
|
||||||
|
);
|
||||||
|
|
||||||
while forked.non_finalized_tip_hash() != fork_tip {
|
while forked.non_finalized_tip_hash() != fork_tip {
|
||||||
forked.pop_tip();
|
forked.pop_tip();
|
||||||
|
@ -206,6 +224,24 @@ impl Chain {
|
||||||
.expect("must work since it was already appended before the fork");
|
.expect("must work since it was already appended before the fork");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that anchors don't need to be recreated since they are already
|
||||||
|
// handled in revert_chain_state_with.
|
||||||
|
|
||||||
|
let sapling_root = forked
|
||||||
|
.sapling_anchors_by_height
|
||||||
|
.get(&block.height)
|
||||||
|
.expect("Sapling anchors must exist for pre-fork blocks");
|
||||||
|
let orchard_root = forked
|
||||||
|
.orchard_anchors_by_height
|
||||||
|
.get(&block.height)
|
||||||
|
.expect("Orchard anchors must exist for pre-fork blocks");
|
||||||
|
forked.history_tree.push(
|
||||||
|
self.network,
|
||||||
|
block.block.clone(),
|
||||||
|
*sapling_root,
|
||||||
|
*orchard_root,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(forked))
|
Ok(Some(forked))
|
||||||
|
@ -267,8 +303,10 @@ impl Chain {
|
||||||
&self,
|
&self,
|
||||||
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
||||||
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
||||||
|
history_tree: HistoryTree,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Chain {
|
Chain {
|
||||||
|
network: self.network,
|
||||||
blocks: self.blocks.clone(),
|
blocks: self.blocks.clone(),
|
||||||
height_by_hash: self.height_by_hash.clone(),
|
height_by_hash: self.height_by_hash.clone(),
|
||||||
tx_by_hash: self.tx_by_hash.clone(),
|
tx_by_hash: self.tx_by_hash.clone(),
|
||||||
|
@ -284,6 +322,7 @@ impl Chain {
|
||||||
sapling_nullifiers: self.sapling_nullifiers.clone(),
|
sapling_nullifiers: self.sapling_nullifiers.clone(),
|
||||||
orchard_nullifiers: self.orchard_nullifiers.clone(),
|
orchard_nullifiers: self.orchard_nullifiers.clone(),
|
||||||
partial_cumulative_work: self.partial_cumulative_work,
|
partial_cumulative_work: self.partial_cumulative_work,
|
||||||
|
history_tree,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,12 +434,19 @@ impl UpdateWith<ContextuallyValidBlock> for Chain {
|
||||||
// Having updated all the note commitment trees and nullifier sets in
|
// Having updated all the note commitment trees and nullifier sets in
|
||||||
// this block, the roots of the note commitment trees as of the last
|
// this block, the roots of the note commitment trees as of the last
|
||||||
// transaction are the treestates of this block.
|
// transaction are the treestates of this block.
|
||||||
let root = self.sapling_note_commitment_tree.root();
|
let sapling_root = self.sapling_note_commitment_tree.root();
|
||||||
self.sapling_anchors.insert(root);
|
self.sapling_anchors.insert(sapling_root);
|
||||||
self.sapling_anchors_by_height.insert(height, root);
|
self.sapling_anchors_by_height.insert(height, sapling_root);
|
||||||
let root = self.orchard_note_commitment_tree.root();
|
let orchard_root = self.orchard_note_commitment_tree.root();
|
||||||
self.orchard_anchors.insert(root);
|
self.orchard_anchors.insert(orchard_root);
|
||||||
self.orchard_anchors_by_height.insert(height, root);
|
self.orchard_anchors_by_height.insert(height, orchard_root);
|
||||||
|
|
||||||
|
self.history_tree.push(
|
||||||
|
self.network,
|
||||||
|
contextually_valid.block.clone(),
|
||||||
|
sapling_root,
|
||||||
|
orchard_root,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -429,6 +475,11 @@ impl UpdateWith<ContextuallyValidBlock> for Chain {
|
||||||
.expect("work has already been validated");
|
.expect("work has already been validated");
|
||||||
self.partial_cumulative_work -= block_work;
|
self.partial_cumulative_work -= block_work;
|
||||||
|
|
||||||
|
// Note: the history tree is not modified in this method.
|
||||||
|
// This method is called on two scenarios:
|
||||||
|
// - When popping the root: the history tree does not change.
|
||||||
|
// - When popping the tip: the history tree is rebuilt in fork().
|
||||||
|
|
||||||
// for each transaction in block
|
// for each transaction in block
|
||||||
for (transaction, transaction_hash) in
|
for (transaction, transaction_hash) in
|
||||||
block.transactions.iter().zip(transaction_hashes.iter())
|
block.transactions.iter().zip(transaction_hashes.iter())
|
||||||
|
|
|
@ -3,9 +3,11 @@ use std::{env, sync::Arc};
|
||||||
use zebra_test::prelude::*;
|
use zebra_test::prelude::*;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block},
|
block::{self, arbitrary::allow_all_transparent_coinbase_spends, Block},
|
||||||
fmt::DisplayToDebug,
|
fmt::DisplayToDebug,
|
||||||
|
history_tree::{HistoryTree, NonEmptyHistoryTree},
|
||||||
parameters::NetworkUpgrade::*,
|
parameters::NetworkUpgrade::*,
|
||||||
|
parameters::{Network, *},
|
||||||
LedgerState,
|
LedgerState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,12 +35,15 @@ fn forked_equals_pushed() -> Result<()> {
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
||||||
|((chain, fork_at_count, _network) in PreparedChain::default())| {
|
|((chain, fork_at_count, network, finalized_tree) in PreparedChain::new_heartwood())| {
|
||||||
|
// Skip first block which was used for the history tree; make sure fork_at_count is still valid
|
||||||
|
let fork_at_count = std::cmp::min(fork_at_count, chain.len() - 1);
|
||||||
|
let chain = &chain[1..];
|
||||||
// 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(Default::default(), Default::default());
|
let mut full_chain = Chain::new(network, Default::default(), Default::default(), finalized_tree.clone());
|
||||||
let mut partial_chain = Chain::new(Default::default(), Default::default());
|
let mut partial_chain = Chain::new(network, Default::default(), Default::default(), finalized_tree.clone());
|
||||||
|
|
||||||
for block in chain.iter().take(fork_at_count) {
|
for block in chain.iter().take(fork_at_count) {
|
||||||
partial_chain = partial_chain.push(block.clone())?;
|
partial_chain = partial_chain.push(block.clone())?;
|
||||||
|
@ -65,11 +70,12 @@ fn forked_equals_pushed() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let forked = full_chain
|
let mut forked = full_chain
|
||||||
.fork(
|
.fork(
|
||||||
fork_tip_hash,
|
fork_tip_hash,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
finalized_tree,
|
||||||
)
|
)
|
||||||
.expect("fork works")
|
.expect("fork works")
|
||||||
.expect("hash is present");
|
.expect("hash is present");
|
||||||
|
@ -77,6 +83,15 @@ fn forked_equals_pushed() -> Result<()> {
|
||||||
// the first check is redundant, but it's useful for debugging
|
// the first check is redundant, but it's useful for debugging
|
||||||
prop_assert_eq!(forked.blocks.len(), partial_chain.blocks.len());
|
prop_assert_eq!(forked.blocks.len(), partial_chain.blocks.len());
|
||||||
prop_assert!(forked.eq_internal_state(&partial_chain));
|
prop_assert!(forked.eq_internal_state(&partial_chain));
|
||||||
|
|
||||||
|
// Re-add blocks to the fork and check if we arrive at the
|
||||||
|
// same original full chain
|
||||||
|
for block in chain.iter().skip(fork_at_count) {
|
||||||
|
forked = forked.push(block.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
prop_assert_eq!(forked.blocks.len(), full_chain.blocks.len());
|
||||||
|
prop_assert!(forked.eq_internal_state(&full_chain));
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -92,17 +107,22 @@ fn finalized_equals_pushed() -> Result<()> {
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
||||||
|((chain, end_count, _network) in PreparedChain::default())| {
|
|((chain, end_count, network, finalized_tree) in PreparedChain::new_heartwood())| {
|
||||||
|
// Skip first block which was used for the history tree; make sure end_count is still valid
|
||||||
|
let end_count = std::cmp::min(end_count, chain.len() - 1);
|
||||||
|
let chain = &chain[1..];
|
||||||
// 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.len() - end_count;
|
||||||
let mut full_chain = Chain::new(Default::default(), Default::default());
|
let mut full_chain = Chain::new(network, Default::default(), Default::default(), finalized_tree);
|
||||||
|
|
||||||
for block in chain.iter().take(finalized_count) {
|
for block in chain.iter().take(finalized_count) {
|
||||||
full_chain = full_chain.push(block.clone())?;
|
full_chain = full_chain.push(block.clone())?;
|
||||||
}
|
}
|
||||||
let mut partial_chain = Chain::new(
|
let mut partial_chain = Chain::new(
|
||||||
|
network,
|
||||||
full_chain.sapling_note_commitment_tree.clone(),
|
full_chain.sapling_note_commitment_tree.clone(),
|
||||||
full_chain.orchard_note_commitment_tree.clone(),
|
full_chain.orchard_note_commitment_tree.clone(),
|
||||||
|
full_chain.history_tree.clone(),
|
||||||
);
|
);
|
||||||
for block in chain.iter().skip(finalized_count) {
|
for block in chain.iter().skip(finalized_count) {
|
||||||
partial_chain = partial_chain.push(block.clone())?;
|
partial_chain = partial_chain.push(block.clone())?;
|
||||||
|
@ -133,7 +153,7 @@ fn rejection_restores_internal_state() -> Result<()> {
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
||||||
|((chain, valid_count, network, mut bad_block) in (PreparedChain::default(), any::<bool>(), any::<bool>())
|
|((chain, valid_count, network, mut bad_block) in (PreparedChain::default(), any::<bool>(), any::<bool>())
|
||||||
.prop_flat_map(|((chain, valid_count, network), is_nu5, is_v5)| {
|
.prop_flat_map(|((chain, valid_count, network, _history_tree), is_nu5, is_v5)| {
|
||||||
let next_height = chain[valid_count - 1].height;
|
let next_height = chain[valid_count - 1].height;
|
||||||
(
|
(
|
||||||
Just(chain),
|
Just(chain),
|
||||||
|
@ -205,7 +225,7 @@ fn different_blocks_different_chains() -> Result<()> {
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
.unwrap_or(DEFAULT_PARTIAL_CHAIN_PROPTEST_CASES)),
|
||||||
|((block1, block2) in (any::<bool>(), any::<bool>())
|
|((vec1, vec2) in (any::<bool>(), any::<bool>())
|
||||||
.prop_flat_map(|(is_nu5, is_v5)| {
|
.prop_flat_map(|(is_nu5, is_v5)| {
|
||||||
// generate a Canopy or NU5 block with v4 or v5 transactions
|
// generate a Canopy or NU5 block with v4 or v5 transactions
|
||||||
LedgerState::coinbase_strategy(
|
LedgerState::coinbase_strategy(
|
||||||
|
@ -213,15 +233,28 @@ fn different_blocks_different_chains() -> Result<()> {
|
||||||
if is_nu5 && is_v5 { 5 } else { 4 },
|
if is_nu5 && is_v5 { 5 } else { 4 },
|
||||||
true,
|
true,
|
||||||
)})
|
)})
|
||||||
.prop_map(Block::arbitrary_with)
|
.prop_map(|ledger_state| Block::partial_chain_strategy(ledger_state, 2, allow_all_transparent_coinbase_spends))
|
||||||
.prop_flat_map(|block_strategy| (block_strategy.clone(), block_strategy))
|
.prop_flat_map(|block_strategy| (block_strategy.clone(), block_strategy))
|
||||||
.prop_map(|(block1, block2)| (DisplayToDebug(block1), DisplayToDebug(block2)))
|
|
||||||
)| {
|
)| {
|
||||||
let chain1 = Chain::new(Default::default(), Default::default());
|
let prev_block1 = vec1[0].clone();
|
||||||
let chain2 = Chain::new(Default::default(), Default::default());
|
let prev_block2 = vec2[0].clone();
|
||||||
|
let height1 = prev_block1.coinbase_height().unwrap();
|
||||||
|
let height2 = prev_block1.coinbase_height().unwrap();
|
||||||
|
let finalized_tree1: HistoryTree = if height1 >= Heartwood.activation_height(Network::Mainnet).unwrap() {
|
||||||
|
NonEmptyHistoryTree::from_block(Network::Mainnet, prev_block1, &Default::default(), &Default::default()).unwrap().into()
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
let finalized_tree2 = if height2 >= NetworkUpgrade::Heartwood.activation_height(Network::Mainnet).unwrap() {
|
||||||
|
NonEmptyHistoryTree::from_block(Network::Mainnet, prev_block2, &Default::default(), &Default::default()).unwrap().into()
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
let chain1 = Chain::new(Network::Mainnet, Default::default(), Default::default(), finalized_tree1);
|
||||||
|
let chain2 = Chain::new(Network::Mainnet, Default::default(), Default::default(), finalized_tree2);
|
||||||
|
|
||||||
let block1 = Arc::new(block1.0).prepare();
|
let block1 = vec1[1].clone().prepare();
|
||||||
let block2 = Arc::new(block2.0).prepare();
|
let block2 = vec2[1].clone().prepare();
|
||||||
|
|
||||||
let result1 = chain1.push(block1.clone());
|
let result1 = chain1.push(block1.clone());
|
||||||
let result2 = chain2.push(block2.clone());
|
let result2 = chain2.push(block2.clone());
|
||||||
|
@ -256,6 +289,9 @@ fn different_blocks_different_chains() -> Result<()> {
|
||||||
chain1.sapling_note_commitment_tree = chain2.sapling_note_commitment_tree.clone();
|
chain1.sapling_note_commitment_tree = chain2.sapling_note_commitment_tree.clone();
|
||||||
chain1.orchard_note_commitment_tree = chain2.orchard_note_commitment_tree.clone();
|
chain1.orchard_note_commitment_tree = chain2.orchard_note_commitment_tree.clone();
|
||||||
|
|
||||||
|
// history tree
|
||||||
|
chain1.history_tree = chain2.history_tree.clone();
|
||||||
|
|
||||||
// anchors
|
// anchors
|
||||||
chain1.sapling_anchors = chain2.sapling_anchors.clone();
|
chain1.sapling_anchors = chain2.sapling_anchors.clone();
|
||||||
chain1.sapling_anchors_by_height = chain2.sapling_anchors_by_height.clone();
|
chain1.sapling_anchors_by_height = chain2.sapling_anchors_by_height.clone();
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use zebra_chain::{block::Block, parameters::Network, serialization::ZcashDeserializeInto};
|
use zebra_chain::{
|
||||||
|
block::Block,
|
||||||
|
parameters::{Network, NetworkUpgrade},
|
||||||
|
serialization::ZcashDeserializeInto,
|
||||||
|
};
|
||||||
use zebra_test::prelude::*;
|
use zebra_test::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -18,7 +22,12 @@ use self::assert_eq;
|
||||||
#[test]
|
#[test]
|
||||||
fn construct_empty() {
|
fn construct_empty() {
|
||||||
zebra_test::init();
|
zebra_test::init();
|
||||||
let _chain = Chain::new(Default::default(), Default::default());
|
let _chain = Chain::new(
|
||||||
|
Network::Mainnet,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -27,7 +36,12 @@ fn construct_single() -> Result<()> {
|
||||||
let block: Arc<Block> =
|
let block: Arc<Block> =
|
||||||
zebra_test::vectors::BLOCK_MAINNET_434873_BYTES.zcash_deserialize_into()?;
|
zebra_test::vectors::BLOCK_MAINNET_434873_BYTES.zcash_deserialize_into()?;
|
||||||
|
|
||||||
let mut chain = Chain::new(Default::default(), Default::default());
|
let mut chain = Chain::new(
|
||||||
|
Network::Mainnet,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
chain = chain.push(block.prepare())?;
|
chain = chain.push(block.prepare())?;
|
||||||
|
|
||||||
assert_eq!(1, chain.blocks.len());
|
assert_eq!(1, chain.blocks.len());
|
||||||
|
@ -49,7 +63,12 @@ fn construct_many() -> Result<()> {
|
||||||
block = next_block;
|
block = next_block;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut chain = Chain::new(Default::default(), Default::default());
|
let mut chain = Chain::new(
|
||||||
|
Network::Mainnet,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
chain = chain.push(block.prepare())?;
|
chain = chain.push(block.prepare())?;
|
||||||
|
@ -68,10 +87,20 @@ fn ord_matches_work() -> Result<()> {
|
||||||
.set_work(1);
|
.set_work(1);
|
||||||
let more_block = less_block.clone().set_work(10);
|
let more_block = less_block.clone().set_work(10);
|
||||||
|
|
||||||
let mut lesser_chain = Chain::new(Default::default(), Default::default());
|
let mut lesser_chain = Chain::new(
|
||||||
|
Network::Mainnet,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
lesser_chain = lesser_chain.push(less_block.prepare())?;
|
lesser_chain = lesser_chain.push(less_block.prepare())?;
|
||||||
|
|
||||||
let mut bigger_chain = Chain::new(Default::default(), Default::default());
|
let mut bigger_chain = Chain::new(
|
||||||
|
Network::Mainnet,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
bigger_chain = bigger_chain.push(more_block.prepare())?;
|
bigger_chain = bigger_chain.push(more_block.prepare())?;
|
||||||
|
|
||||||
assert!(bigger_chain > lesser_chain);
|
assert!(bigger_chain > lesser_chain);
|
||||||
|
@ -91,11 +120,14 @@ fn best_chain_wins() -> Result<()> {
|
||||||
|
|
||||||
fn best_chain_wins_for_network(network: Network) -> Result<()> {
|
fn best_chain_wins_for_network(network: Network) -> Result<()> {
|
||||||
let block1: Arc<Block> = match network {
|
let block1: Arc<Block> = match network {
|
||||||
|
// Since the brand new FinalizedState below will pass a None history tree
|
||||||
|
// to the NonFinalizedState, we must use pre-Heartwood blocks since
|
||||||
|
// they won't trigger the history tree update in the NonFinalizedState.
|
||||||
Network::Mainnet => {
|
Network::Mainnet => {
|
||||||
zebra_test::vectors::BLOCK_MAINNET_1180900_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_MAINNET_653599_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
Network::Testnet => {
|
Network::Testnet => {
|
||||||
zebra_test::vectors::BLOCK_TESTNET_1326100_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_TESTNET_583999_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,11 +160,14 @@ fn finalize_pops_from_best_chain() -> Result<()> {
|
||||||
|
|
||||||
fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
|
fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
|
||||||
let block1: Arc<Block> = match network {
|
let block1: Arc<Block> = match network {
|
||||||
|
// Since the brand new FinalizedState below will pass a None history tree
|
||||||
|
// to the NonFinalizedState, we must use pre-Heartwood blocks since
|
||||||
|
// they won't trigger the history tree update in the NonFinalizedState.
|
||||||
Network::Mainnet => {
|
Network::Mainnet => {
|
||||||
zebra_test::vectors::BLOCK_MAINNET_1180900_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_MAINNET_653599_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
Network::Testnet => {
|
Network::Testnet => {
|
||||||
zebra_test::vectors::BLOCK_TESTNET_1326100_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_TESTNET_583999_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -172,11 +207,14 @@ fn commit_block_extending_best_chain_doesnt_drop_worst_chains_for_network(
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let block1: Arc<Block> = match network {
|
let block1: Arc<Block> = match network {
|
||||||
|
// Since the brand new FinalizedState below will pass a None history tree
|
||||||
|
// to the NonFinalizedState, we must use pre-Heartwood blocks since
|
||||||
|
// they won't trigger the history tree update in the NonFinalizedState.
|
||||||
Network::Mainnet => {
|
Network::Mainnet => {
|
||||||
zebra_test::vectors::BLOCK_MAINNET_1180900_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_MAINNET_653599_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
Network::Testnet => {
|
Network::Testnet => {
|
||||||
zebra_test::vectors::BLOCK_TESTNET_1326100_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_TESTNET_583999_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -212,11 +250,14 @@ fn shorter_chain_can_be_best_chain() -> Result<()> {
|
||||||
|
|
||||||
fn shorter_chain_can_be_best_chain_for_network(network: Network) -> Result<()> {
|
fn shorter_chain_can_be_best_chain_for_network(network: Network) -> Result<()> {
|
||||||
let block1: Arc<Block> = match network {
|
let block1: Arc<Block> = match network {
|
||||||
|
// Since the brand new FinalizedState below will pass a None history tree
|
||||||
|
// to the NonFinalizedState, we must use pre-Heartwood blocks since
|
||||||
|
// they won't trigger the history tree update in the NonFinalizedState.
|
||||||
Network::Mainnet => {
|
Network::Mainnet => {
|
||||||
zebra_test::vectors::BLOCK_MAINNET_1180900_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_MAINNET_653599_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
Network::Testnet => {
|
Network::Testnet => {
|
||||||
zebra_test::vectors::BLOCK_TESTNET_1326100_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_TESTNET_583999_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -251,11 +292,14 @@ fn longer_chain_with_more_work_wins() -> Result<()> {
|
||||||
|
|
||||||
fn longer_chain_with_more_work_wins_for_network(network: Network) -> Result<()> {
|
fn longer_chain_with_more_work_wins_for_network(network: Network) -> Result<()> {
|
||||||
let block1: Arc<Block> = match network {
|
let block1: Arc<Block> = match network {
|
||||||
|
// Since the brand new FinalizedState below will pass a None history tree
|
||||||
|
// to the NonFinalizedState, we must use pre-Heartwood blocks since
|
||||||
|
// they won't trigger the history tree update in the NonFinalizedState.
|
||||||
Network::Mainnet => {
|
Network::Mainnet => {
|
||||||
zebra_test::vectors::BLOCK_MAINNET_1180900_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_MAINNET_653599_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
Network::Testnet => {
|
Network::Testnet => {
|
||||||
zebra_test::vectors::BLOCK_TESTNET_1326100_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_TESTNET_583999_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -293,11 +337,14 @@ fn equal_length_goes_to_more_work() -> Result<()> {
|
||||||
}
|
}
|
||||||
fn equal_length_goes_to_more_work_for_network(network: Network) -> Result<()> {
|
fn equal_length_goes_to_more_work_for_network(network: Network) -> Result<()> {
|
||||||
let block1: Arc<Block> = match network {
|
let block1: Arc<Block> = match network {
|
||||||
|
// Since the brand new FinalizedState below will pass a None history tree
|
||||||
|
// to the NonFinalizedState, we must use pre-Heartwood blocks since
|
||||||
|
// they won't trigger the history tree update in the NonFinalizedState.
|
||||||
Network::Mainnet => {
|
Network::Mainnet => {
|
||||||
zebra_test::vectors::BLOCK_MAINNET_1180900_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_MAINNET_653599_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
Network::Testnet => {
|
Network::Testnet => {
|
||||||
zebra_test::vectors::BLOCK_TESTNET_1326100_BYTES.zcash_deserialize_into()?
|
zebra_test::vectors::BLOCK_TESTNET_583999_BYTES.zcash_deserialize_into()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -318,3 +365,73 @@ fn equal_length_goes_to_more_work_for_network(network: Network) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn history_tree_is_updated() -> Result<()> {
|
||||||
|
history_tree_is_updated_for_network_upgrade(Network::Mainnet, NetworkUpgrade::Heartwood)?;
|
||||||
|
history_tree_is_updated_for_network_upgrade(Network::Testnet, NetworkUpgrade::Heartwood)?;
|
||||||
|
// TODO: we can't test other upgrades until we have a method for creating a FinalizedState
|
||||||
|
// with a HistoryTree.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn history_tree_is_updated_for_network_upgrade(
|
||||||
|
network: Network,
|
||||||
|
network_upgrade: NetworkUpgrade,
|
||||||
|
) -> Result<()> {
|
||||||
|
let blocks = match network {
|
||||||
|
Network::Mainnet => &*zebra_test::vectors::MAINNET_BLOCKS,
|
||||||
|
Network::Testnet => &*zebra_test::vectors::TESTNET_BLOCKS,
|
||||||
|
};
|
||||||
|
let height = network_upgrade.activation_height(network).unwrap().0;
|
||||||
|
|
||||||
|
let prev_block = Arc::new(
|
||||||
|
blocks
|
||||||
|
.get(&(height - 1))
|
||||||
|
.expect("test vector exists")
|
||||||
|
.zcash_deserialize_into::<Block>()
|
||||||
|
.expect("block is structurally valid"),
|
||||||
|
);
|
||||||
|
let activation_block = prev_block.make_fake_child();
|
||||||
|
let next_block = activation_block.make_fake_child();
|
||||||
|
|
||||||
|
let mut state = NonFinalizedState::new(network);
|
||||||
|
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
|
|
||||||
|
state.commit_new_chain(prev_block.prepare(), &finalized_state)?;
|
||||||
|
|
||||||
|
let chain = state.best_chain().unwrap();
|
||||||
|
if network_upgrade == NetworkUpgrade::Heartwood {
|
||||||
|
assert!(
|
||||||
|
chain.history_tree.as_ref().is_none(),
|
||||||
|
"history tree must not exist yet"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(
|
||||||
|
chain.history_tree.as_ref().is_some(),
|
||||||
|
"history tree must already exist"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.commit_block(activation_block.prepare(), &finalized_state)?;
|
||||||
|
|
||||||
|
let chain = state.best_chain().unwrap();
|
||||||
|
assert!(
|
||||||
|
chain.history_tree.as_ref().is_some(),
|
||||||
|
"history tree must have been (re)created"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
chain.history_tree.as_ref().as_ref().unwrap().size(),
|
||||||
|
1,
|
||||||
|
"history tree must have a single node"
|
||||||
|
);
|
||||||
|
|
||||||
|
state.commit_block(next_block.prepare(), &finalized_state)?;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
state.best_chain().unwrap().history_tree.as_ref().is_some(),
|
||||||
|
"history tree must still exist"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue