feat(state): Send treestate from non-finalized state to finalized state (#4721)
* Add history trees for each height in non-fin state * Refactor formatting * Pass the treestate to the finalized state I created a new structure `FinalizedBlockWithTrees` that wraps the treestate and the finalized block. I did that because the original `FinalizedBlock` is `Eq`, but `HistoryTree` can't be `Eq`. This makes Zebra faster because: 1. The finalized state doesn't retrieve the treestate from the disk if the non-finalized state supplies it. 2.The finalized state doesn't recompute the treestate if the non-finalized state supplies it. * Check block commitment before updating hist tree * Store Sprout commitment trees in non-fin state * Send trees for the root block to fin-state When committing a block and sending the treestate from the non-finalized state to the finalized state, Zebra was sending trees that correspond to the tip block instead of trees that correspond to the root block of the best chain. This commit fixes that. * Refactor doc comments * Refactor block finalization Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
fec012a006
commit
b8712d9a1e
|
@ -9,8 +9,12 @@ use std::{
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::NegativeAllowed,
|
amount::NegativeAllowed,
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
|
history_tree::HistoryTree,
|
||||||
|
orchard,
|
||||||
|
parallel::tree::NoteCommitmentTrees,
|
||||||
|
sapling,
|
||||||
serialization::SerializationError,
|
serialization::SerializationError,
|
||||||
transaction,
|
sprout, transaction,
|
||||||
transparent::{self, utxos_from_ordered_utxos},
|
transparent::{self, utxos_from_ordered_utxos},
|
||||||
value_balance::{ValueBalance, ValueBalanceError},
|
value_balance::{ValueBalance, ValueBalanceError},
|
||||||
};
|
};
|
||||||
|
@ -177,6 +181,72 @@ pub struct FinalizedBlock {
|
||||||
pub transaction_hashes: Arc<[transaction::Hash]>,
|
pub transaction_hashes: Arc<[transaction::Hash]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wraps note commitment trees and the history tree together.
|
||||||
|
pub struct Treestate {
|
||||||
|
/// Note commitment trees.
|
||||||
|
pub note_commitment_trees: NoteCommitmentTrees,
|
||||||
|
/// History tree.
|
||||||
|
pub history_tree: Arc<HistoryTree>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Treestate {
|
||||||
|
pub fn new(
|
||||||
|
sprout: Arc<sprout::tree::NoteCommitmentTree>,
|
||||||
|
sapling: Arc<sapling::tree::NoteCommitmentTree>,
|
||||||
|
orchard: Arc<orchard::tree::NoteCommitmentTree>,
|
||||||
|
history_tree: Arc<HistoryTree>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
note_commitment_trees: NoteCommitmentTrees {
|
||||||
|
sprout,
|
||||||
|
sapling,
|
||||||
|
orchard,
|
||||||
|
},
|
||||||
|
history_tree,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains a block ready to be committed together with its associated
|
||||||
|
/// treestate.
|
||||||
|
///
|
||||||
|
/// Zebra's non-finalized state passes this `struct` over to the finalized state
|
||||||
|
/// when committing a block. The associated treestate is passed so that the
|
||||||
|
/// finalized state does not have to retrieve the previous treestate from the
|
||||||
|
/// database and recompute the new one.
|
||||||
|
pub struct FinalizedWithTrees {
|
||||||
|
/// A block ready to be committed.
|
||||||
|
pub finalized: FinalizedBlock,
|
||||||
|
/// The tresstate associated with the block.
|
||||||
|
pub treestate: Option<Treestate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FinalizedWithTrees {
|
||||||
|
pub fn new(block: ContextuallyValidBlock, treestate: Treestate) -> Self {
|
||||||
|
let finalized = FinalizedBlock::from(block);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
finalized,
|
||||||
|
treestate: Some(treestate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arc<Block>> for FinalizedWithTrees {
|
||||||
|
fn from(block: Arc<Block>) -> Self {
|
||||||
|
Self::from(FinalizedBlock::from(block))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FinalizedBlock> for FinalizedWithTrees {
|
||||||
|
fn from(block: FinalizedBlock) -> Self {
|
||||||
|
Self {
|
||||||
|
finalized: block,
|
||||||
|
treestate: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&PreparedBlock> for PreparedBlock {
|
impl From<&PreparedBlock> for PreparedBlock {
|
||||||
fn from(prepared: &PreparedBlock) -> Self {
|
fn from(prepared: &PreparedBlock) -> Self {
|
||||||
prepared.clone()
|
prepared.clone()
|
||||||
|
|
|
@ -296,9 +296,9 @@ impl StateService {
|
||||||
|
|
||||||
while self.mem.best_chain_len() > crate::constants::MAX_BLOCK_REORG_HEIGHT {
|
while self.mem.best_chain_len() > crate::constants::MAX_BLOCK_REORG_HEIGHT {
|
||||||
tracing::trace!("finalizing block past the reorg limit");
|
tracing::trace!("finalizing block past the reorg limit");
|
||||||
let finalized = self.mem.finalize();
|
let finalized_with_trees = self.mem.finalize();
|
||||||
self.disk
|
self.disk
|
||||||
.commit_finalized_direct(finalized, "best non-finalized chain root")
|
.commit_finalized_direct(finalized_with_trees, "best non-finalized chain root")
|
||||||
.expect(
|
.expect(
|
||||||
"expected that errors would not occur when writing to disk or updating note commitment and history trees",
|
"expected that errors would not occur when writing to disk or updating note commitment and history trees",
|
||||||
);
|
);
|
||||||
|
|
|
@ -82,7 +82,7 @@ proptest! {
|
||||||
// randomly choose to commit the block to the finalized or non-finalized state
|
// randomly choose to commit the block to the finalized or non-finalized state
|
||||||
if use_finalized_state {
|
if use_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(Arc::new(block1));
|
let block1 = FinalizedBlock::from(Arc::new(block1));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
|
@ -332,7 +332,7 @@ proptest! {
|
||||||
// randomly choose to commit the next block to the finalized or non-finalized state
|
// randomly choose to commit the next block to the finalized or non-finalized state
|
||||||
if duplicate_in_finalized_state {
|
if duplicate_in_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(Arc::new(block1));
|
let block1 = FinalizedBlock::from(Arc::new(block1));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
prop_assert!(commit_result.is_ok());
|
prop_assert!(commit_result.is_ok());
|
||||||
|
@ -416,7 +416,7 @@ proptest! {
|
||||||
// randomly choose to commit the block to the finalized or non-finalized state
|
// randomly choose to commit the block to the finalized or non-finalized state
|
||||||
if use_finalized_state {
|
if use_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(Arc::new(block1));
|
let block1 = FinalizedBlock::from(Arc::new(block1));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
prop_assert!(commit_result.is_ok());
|
prop_assert!(commit_result.is_ok());
|
||||||
|
@ -582,7 +582,7 @@ proptest! {
|
||||||
// randomly choose to commit the next block to the finalized or non-finalized state
|
// randomly choose to commit the next block to the finalized or non-finalized state
|
||||||
if duplicate_in_finalized_state {
|
if duplicate_in_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(Arc::new(block1));
|
let block1 = FinalizedBlock::from(Arc::new(block1));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
prop_assert!(commit_result.is_ok());
|
prop_assert!(commit_result.is_ok());
|
||||||
|
@ -660,7 +660,7 @@ proptest! {
|
||||||
// randomly choose to commit the block to the finalized or non-finalized state
|
// randomly choose to commit the block to the finalized or non-finalized state
|
||||||
if use_finalized_state {
|
if use_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(Arc::new(block1));
|
let block1 = FinalizedBlock::from(Arc::new(block1));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
prop_assert!(commit_result.is_ok());
|
prop_assert!(commit_result.is_ok());
|
||||||
|
@ -834,7 +834,7 @@ proptest! {
|
||||||
// randomly choose to commit the next block to the finalized or non-finalized state
|
// randomly choose to commit the next block to the finalized or non-finalized state
|
||||||
if duplicate_in_finalized_state {
|
if duplicate_in_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(Arc::new(block1));
|
let block1 = FinalizedBlock::from(Arc::new(block1));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
prop_assert!(commit_result.is_ok());
|
prop_assert!(commit_result.is_ok());
|
||||||
|
|
|
@ -176,7 +176,7 @@ proptest! {
|
||||||
// randomly choose to commit the block to the finalized or non-finalized state
|
// randomly choose to commit the block to the finalized or non-finalized state
|
||||||
if use_finalized_state {
|
if use_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(Arc::new(block1));
|
let block1 = FinalizedBlock::from(Arc::new(block1));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
|
@ -262,7 +262,7 @@ proptest! {
|
||||||
|
|
||||||
if use_finalized_state_spend {
|
if use_finalized_state_spend {
|
||||||
let block2 = FinalizedBlock::from(Arc::new(block2));
|
let block2 = FinalizedBlock::from(Arc::new(block2));
|
||||||
let commit_result = state.disk.commit_finalized_direct(block2.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block2.clone().into(), "test");
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
prop_assert_eq!(Some((Height(2), block2.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(2), block2.hash)), state.best_tip());
|
||||||
|
@ -591,7 +591,7 @@ proptest! {
|
||||||
|
|
||||||
if use_finalized_state_spend {
|
if use_finalized_state_spend {
|
||||||
let block2 = FinalizedBlock::from(block2.clone());
|
let block2 = FinalizedBlock::from(block2.clone());
|
||||||
let commit_result = state.disk.commit_finalized_direct(block2.clone(), "test");
|
let commit_result = state.disk.commit_finalized_direct(block2.clone().into(), "test");
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
prop_assert_eq!(Some((Height(2), block2.hash)), state.best_tip());
|
prop_assert_eq!(Some((Height(2), block2.hash)), state.best_tip());
|
||||||
|
@ -846,7 +846,9 @@ fn new_state_with_mainnet_transparent_data(
|
||||||
|
|
||||||
if use_finalized_state {
|
if use_finalized_state {
|
||||||
let block1 = FinalizedBlock::from(block1.clone());
|
let block1 = FinalizedBlock::from(block1.clone());
|
||||||
let commit_result = state.disk.commit_finalized_direct(block1.clone(), "test");
|
let commit_result = state
|
||||||
|
.disk
|
||||||
|
.commit_finalized_direct(block1.clone().into(), "test");
|
||||||
|
|
||||||
// the block was committed
|
// the block was committed
|
||||||
assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
assert_eq!(Some((Height(1), block1.hash)), state.best_tip());
|
||||||
|
|
|
@ -19,11 +19,13 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{stderr, stdout, Write},
|
io::{stderr, stdout, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_chain::{block, parameters::Network};
|
use zebra_chain::{block, parameters::Network};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
request::FinalizedWithTrees,
|
||||||
service::{check, QueuedFinalized},
|
service::{check, QueuedFinalized},
|
||||||
BoxError, Config, FinalizedBlock,
|
BoxError, Config, FinalizedBlock,
|
||||||
};
|
};
|
||||||
|
@ -188,7 +190,8 @@ impl FinalizedState {
|
||||||
/// public API of the [`FinalizedState`].
|
/// public API of the [`FinalizedState`].
|
||||||
fn commit_finalized(&mut self, queued_block: QueuedFinalized) -> Result<FinalizedBlock, ()> {
|
fn commit_finalized(&mut self, queued_block: QueuedFinalized) -> Result<FinalizedBlock, ()> {
|
||||||
let (finalized, rsp_tx) = queued_block;
|
let (finalized, rsp_tx) = queued_block;
|
||||||
let result = self.commit_finalized_direct(finalized.clone(), "CommitFinalized request");
|
let result =
|
||||||
|
self.commit_finalized_direct(finalized.clone().into(), "CommitFinalized request");
|
||||||
|
|
||||||
let block_result = if result.is_ok() {
|
let block_result = if result.is_ok() {
|
||||||
metrics::counter!("state.checkpoint.finalized.block.count", 1);
|
metrics::counter!("state.checkpoint.finalized.block.count", 1);
|
||||||
|
@ -238,9 +241,10 @@ impl FinalizedState {
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn commit_finalized_direct(
|
pub fn commit_finalized_direct(
|
||||||
&mut self,
|
&mut self,
|
||||||
finalized: FinalizedBlock,
|
finalized_with_trees: FinalizedWithTrees,
|
||||||
source: &str,
|
source: &str,
|
||||||
) -> Result<block::Hash, BoxError> {
|
) -> Result<block::Hash, BoxError> {
|
||||||
|
let finalized = finalized_with_trees.finalized;
|
||||||
let committed_tip_hash = self.db.finalized_tip_hash();
|
let committed_tip_hash = self.db.finalized_tip_hash();
|
||||||
let committed_tip_height = self.db.finalized_tip_height();
|
let committed_tip_height = self.db.finalized_tip_height();
|
||||||
|
|
||||||
|
@ -272,28 +276,73 @@ impl FinalizedState {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the block commitment. For Nu5-onward, the block hash commits only
|
let (history_tree, note_commitment_trees) = match finalized_with_trees.treestate {
|
||||||
// to non-authorizing data (see ZIP-244). This checks the authorizing data
|
// If the treestate associated with the block was supplied, use it
|
||||||
// commitment, making sure the entire block contents were committed to.
|
// without recomputing it.
|
||||||
// The test is done here (and not during semantic validation) because it needs
|
Some(ref treestate) => (
|
||||||
// the history tree root. While it _is_ checked during contextual validation,
|
treestate.history_tree.clone(),
|
||||||
// that is not called by the checkpoint verifier, and keeping a history tree there
|
treestate.note_commitment_trees.clone(),
|
||||||
// would be harder to implement.
|
),
|
||||||
|
// If the treestate was not supplied, retrieve a previous treestate
|
||||||
|
// from the database, and update it for the block being committed.
|
||||||
|
None => {
|
||||||
|
let mut history_tree = self.db.history_tree();
|
||||||
|
let mut note_commitment_trees = self.db.note_commitment_trees();
|
||||||
|
|
||||||
|
// Update the note commitment trees.
|
||||||
|
note_commitment_trees.update_trees_parallel(&finalized.block)?;
|
||||||
|
|
||||||
|
// Check the block commitment if the history tree was not
|
||||||
|
// supplied by the non-finalized state. Note that we don't do
|
||||||
|
// this check for history trees supplied by the non-finalized
|
||||||
|
// state because the non-finalized state checks the block
|
||||||
|
// commitment.
|
||||||
//
|
//
|
||||||
// TODO: run this CPU-intensive cryptography in a parallel rayon thread, if it shows up in profiles
|
// For Nu5-onward, the block hash commits only to
|
||||||
let history_tree = self.db.history_tree();
|
// non-authorizing data (see ZIP-244). This checks the
|
||||||
|
// authorizing data commitment, making sure the entire block
|
||||||
|
// contents were committed to. The test is done here (and not
|
||||||
|
// during semantic validation) because it needs the history tree
|
||||||
|
// root. While it _is_ checked during contextual validation,
|
||||||
|
// that is not called by the checkpoint verifier, and keeping a
|
||||||
|
// history tree there would be harder to implement.
|
||||||
|
//
|
||||||
|
// TODO: run this CPU-intensive cryptography in a parallel rayon
|
||||||
|
// thread, if it shows up in profiles
|
||||||
check::block_commitment_is_valid_for_chain_history(
|
check::block_commitment_is_valid_for_chain_history(
|
||||||
finalized.block.clone(),
|
finalized.block.clone(),
|
||||||
self.network,
|
self.network,
|
||||||
&history_tree,
|
&history_tree,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Update the history tree.
|
||||||
|
//
|
||||||
|
// TODO: run this CPU-intensive cryptography in a parallel rayon
|
||||||
|
// thread, if it shows up in profiles
|
||||||
|
let history_tree_mut = Arc::make_mut(&mut history_tree);
|
||||||
|
let sapling_root = note_commitment_trees.sapling.root();
|
||||||
|
let orchard_root = note_commitment_trees.orchard.root();
|
||||||
|
history_tree_mut.push(
|
||||||
|
self.network(),
|
||||||
|
finalized.block.clone(),
|
||||||
|
sapling_root,
|
||||||
|
orchard_root,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
(history_tree, note_commitment_trees)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let finalized_height = finalized.height;
|
let finalized_height = finalized.height;
|
||||||
let finalized_hash = finalized.hash;
|
let finalized_hash = finalized.hash;
|
||||||
|
|
||||||
let result = self
|
let result = self.db.write_block(
|
||||||
.db
|
finalized,
|
||||||
.write_block(finalized, history_tree, self.network, source);
|
history_tree,
|
||||||
|
note_commitment_trees,
|
||||||
|
self.network,
|
||||||
|
source,
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: move the stop height check to the syncer (#3442)
|
// TODO: move the stop height check to the syncer (#3442)
|
||||||
if result.is_ok() && self.is_at_stop_height(finalized_height) {
|
if result.is_ok() && self.is_at_stop_height(finalized_height) {
|
||||||
|
|
|
@ -28,8 +28,9 @@ fn blocks_with_v5_transactions() -> Result<()> {
|
||||||
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
|
||||||
for block in chain.iter().take(count) {
|
for block in chain.iter().take(count) {
|
||||||
|
let finalized = FinalizedBlock::from(block.block.clone());
|
||||||
let hash = state.commit_finalized_direct(
|
let hash = state.commit_finalized_direct(
|
||||||
FinalizedBlock::from(block.block.clone()),
|
finalized.into(),
|
||||||
"blocks_with_v5_transactions test"
|
"blocks_with_v5_transactions test"
|
||||||
);
|
);
|
||||||
prop_assert_eq!(Some(height), state.finalized_tip_height());
|
prop_assert_eq!(Some(height), state.finalized_tip_height());
|
||||||
|
@ -83,16 +84,18 @@ fn all_upgrades_and_wrong_commitments_with_fake_activation_heights() -> Result<(
|
||||||
h == nu5_height ||
|
h == nu5_height ||
|
||||||
h == nu5_height_plus1 => {
|
h == nu5_height_plus1 => {
|
||||||
let block = block.block.clone().set_block_commitment([0x42; 32]);
|
let block = block.block.clone().set_block_commitment([0x42; 32]);
|
||||||
|
let finalized = FinalizedBlock::from(block);
|
||||||
state.commit_finalized_direct(
|
state.commit_finalized_direct(
|
||||||
FinalizedBlock::from(block),
|
finalized.into(),
|
||||||
"all_upgrades test"
|
"all_upgrades test"
|
||||||
).expect_err("Must fail commitment check");
|
).expect_err("Must fail commitment check");
|
||||||
failure_count += 1;
|
failure_count += 1;
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
let finalized = FinalizedBlock::from(block.block.clone());
|
||||||
let hash = state.commit_finalized_direct(
|
let hash = state.commit_finalized_direct(
|
||||||
FinalizedBlock::from(block.block.clone()),
|
finalized.into(),
|
||||||
"all_upgrades test"
|
"all_upgrades test"
|
||||||
).unwrap();
|
).unwrap();
|
||||||
prop_assert_eq!(Some(height), state.finalized_tip_height());
|
prop_assert_eq!(Some(height), state.finalized_tip_height());
|
||||||
|
|
|
@ -242,6 +242,7 @@ impl ZebraDb {
|
||||||
&mut self,
|
&mut self,
|
||||||
finalized: FinalizedBlock,
|
finalized: FinalizedBlock,
|
||||||
history_tree: Arc<HistoryTree>,
|
history_tree: Arc<HistoryTree>,
|
||||||
|
note_commitment_trees: NoteCommitmentTrees,
|
||||||
network: Network,
|
network: Network,
|
||||||
source: &str,
|
source: &str,
|
||||||
) -> Result<block::Hash, BoxError> {
|
) -> Result<block::Hash, BoxError> {
|
||||||
|
@ -329,8 +330,8 @@ impl ZebraDb {
|
||||||
spent_utxos_by_outpoint,
|
spent_utxos_by_outpoint,
|
||||||
spent_utxos_by_out_loc,
|
spent_utxos_by_out_loc,
|
||||||
address_balances,
|
address_balances,
|
||||||
self.note_commitment_trees(),
|
|
||||||
history_tree,
|
history_tree,
|
||||||
|
note_commitment_trees,
|
||||||
self.finalized_value_pool(),
|
self.finalized_value_pool(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -382,8 +383,8 @@ impl DiskWriteBatch {
|
||||||
spent_utxos_by_outpoint: HashMap<transparent::OutPoint, transparent::Utxo>,
|
spent_utxos_by_outpoint: HashMap<transparent::OutPoint, transparent::Utxo>,
|
||||||
spent_utxos_by_out_loc: BTreeMap<OutputLocation, transparent::Utxo>,
|
spent_utxos_by_out_loc: BTreeMap<OutputLocation, transparent::Utxo>,
|
||||||
address_balances: HashMap<transparent::Address, AddressBalanceLocation>,
|
address_balances: HashMap<transparent::Address, AddressBalanceLocation>,
|
||||||
mut note_commitment_trees: NoteCommitmentTrees,
|
|
||||||
history_tree: Arc<HistoryTree>,
|
history_tree: Arc<HistoryTree>,
|
||||||
|
note_commitment_trees: NoteCommitmentTrees,
|
||||||
value_pool: ValueBalance<NonNegative>,
|
value_pool: ValueBalance<NonNegative>,
|
||||||
) -> Result<(), BoxError> {
|
) -> Result<(), BoxError> {
|
||||||
let FinalizedBlock {
|
let FinalizedBlock {
|
||||||
|
@ -419,7 +420,7 @@ impl DiskWriteBatch {
|
||||||
&spent_utxos_by_out_loc,
|
&spent_utxos_by_out_loc,
|
||||||
address_balances,
|
address_balances,
|
||||||
)?;
|
)?;
|
||||||
self.prepare_shielded_transaction_batch(db, &finalized, &mut note_commitment_trees)?;
|
self.prepare_shielded_transaction_batch(db, &finalized)?;
|
||||||
|
|
||||||
self.prepare_note_commitment_batch(db, &finalized, note_commitment_trees, history_tree)?;
|
self.prepare_note_commitment_batch(db, &finalized, note_commitment_trees, history_tree)?;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ use std::{borrow::Borrow, collections::HashMap, sync::Arc};
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::NonNegative,
|
amount::NonNegative,
|
||||||
history_tree::{HistoryTree, NonEmptyHistoryTree},
|
history_tree::{HistoryTree, NonEmptyHistoryTree},
|
||||||
orchard, sapling, transparent,
|
transparent,
|
||||||
value_balance::ValueBalance,
|
value_balance::ValueBalance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -71,17 +71,11 @@ impl DiskWriteBatch {
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &DiskDb,
|
db: &DiskDb,
|
||||||
finalized: &FinalizedBlock,
|
finalized: &FinalizedBlock,
|
||||||
sapling_root: sapling::tree::Root,
|
history_tree: Arc<HistoryTree>,
|
||||||
orchard_root: orchard::tree::Root,
|
|
||||||
mut history_tree: Arc<HistoryTree>,
|
|
||||||
) -> Result<(), BoxError> {
|
) -> Result<(), BoxError> {
|
||||||
let history_tree_cf = db.cf_handle("history_tree").unwrap();
|
let history_tree_cf = db.cf_handle("history_tree").unwrap();
|
||||||
|
|
||||||
let FinalizedBlock { block, height, .. } = finalized;
|
let FinalizedBlock { height, .. } = finalized;
|
||||||
|
|
||||||
// TODO: run this CPU-intensive cryptography in a parallel rayon thread, if it shows up in profiles
|
|
||||||
let history_tree_mut = Arc::make_mut(&mut history_tree);
|
|
||||||
history_tree_mut.push(self.network(), block.clone(), sapling_root, orchard_root)?;
|
|
||||||
|
|
||||||
// Update the tree in state
|
// Update the tree in state
|
||||||
let current_tip_height = *height - 1;
|
let current_tip_height = *height - 1;
|
||||||
|
|
|
@ -180,7 +180,6 @@ impl DiskWriteBatch {
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &DiskDb,
|
db: &DiskDb,
|
||||||
finalized: &FinalizedBlock,
|
finalized: &FinalizedBlock,
|
||||||
note_commitment_trees: &mut NoteCommitmentTrees,
|
|
||||||
) -> Result<(), BoxError> {
|
) -> Result<(), BoxError> {
|
||||||
let FinalizedBlock { block, .. } = finalized;
|
let FinalizedBlock { block, .. } = finalized;
|
||||||
|
|
||||||
|
@ -189,8 +188,6 @@ impl DiskWriteBatch {
|
||||||
self.prepare_nullifier_batch(db, transaction)?;
|
self.prepare_nullifier_batch(db, transaction)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
note_commitment_trees.update_trees_parallel(block)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +287,7 @@ impl DiskWriteBatch {
|
||||||
note_commitment_trees.orchard,
|
note_commitment_trees.orchard,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.prepare_history_batch(db, finalized, sapling_root, orchard_root, history_tree)
|
self.prepare_history_batch(db, finalized, history_tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare a database batch containing the initial note commitment trees,
|
/// Prepare a database batch containing the initial note commitment trees,
|
||||||
|
|
|
@ -17,9 +17,9 @@ use zebra_chain::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
request::ContextuallyValidBlock,
|
request::{ContextuallyValidBlock, FinalizedWithTrees},
|
||||||
service::{check, finalized_state::ZebraDb},
|
service::{check, finalized_state::ZebraDb},
|
||||||
FinalizedBlock, PreparedBlock, ValidateContextError,
|
PreparedBlock, ValidateContextError,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod chain;
|
mod chain;
|
||||||
|
@ -80,7 +80,7 @@ impl NonFinalizedState {
|
||||||
|
|
||||||
/// Finalize the lowest height block in the non-finalized portion of the best
|
/// Finalize the lowest height block in the non-finalized portion of the best
|
||||||
/// chain and update all side-chains to match.
|
/// chain and update all side-chains to match.
|
||||||
pub fn finalize(&mut self) -> FinalizedBlock {
|
pub fn finalize(&mut self) -> FinalizedWithTrees {
|
||||||
// Chain::cmp uses the partial cumulative work, and the hash of the tip block.
|
// Chain::cmp uses the partial cumulative work, and the hash of the tip block.
|
||||||
// Neither of these fields has interior mutability.
|
// Neither of these fields has interior mutability.
|
||||||
// (And when the tip block is dropped for a chain, the chain is also dropped.)
|
// (And when the tip block is dropped for a chain, the chain is also dropped.)
|
||||||
|
@ -90,14 +90,16 @@ impl NonFinalizedState {
|
||||||
|
|
||||||
// extract best chain
|
// extract best chain
|
||||||
let mut best_chain = chains.next_back().expect("there's at least one chain");
|
let mut best_chain = chains.next_back().expect("there's at least one chain");
|
||||||
|
|
||||||
// clone if required
|
// clone if required
|
||||||
let write_best_chain = Arc::make_mut(&mut best_chain);
|
let mut_best_chain = Arc::make_mut(&mut best_chain);
|
||||||
|
|
||||||
// extract the rest into side_chains so they can be mutated
|
// extract the rest into side_chains so they can be mutated
|
||||||
let side_chains = chains;
|
let side_chains = chains;
|
||||||
|
|
||||||
// remove the lowest height block from the best_chain to be finalized
|
// Pop the lowest height block from the best chain to be finalized, and
|
||||||
let finalizing = write_best_chain.pop_root();
|
// also obtain its associated treestate.
|
||||||
|
let (best_chain_root, root_treestate) = mut_best_chain.pop_root();
|
||||||
|
|
||||||
// add best_chain back to `self.chain_set`
|
// add best_chain back to `self.chain_set`
|
||||||
if !best_chain.is_empty() {
|
if !best_chain.is_empty() {
|
||||||
|
@ -105,11 +107,11 @@ impl NonFinalizedState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// for each remaining chain in side_chains
|
// for each remaining chain in side_chains
|
||||||
for mut chain in side_chains {
|
for mut side_chain in side_chains {
|
||||||
if chain.non_finalized_root_hash() != finalizing.hash {
|
if side_chain.non_finalized_root_hash() != best_chain_root.hash {
|
||||||
// If we popped the root, the chain would be empty or orphaned,
|
// If we popped the root, the chain would be empty or orphaned,
|
||||||
// so just drop it now.
|
// so just drop it now.
|
||||||
drop(chain);
|
drop(side_chain);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -117,19 +119,20 @@ impl NonFinalizedState {
|
||||||
// otherwise, the popped root block is the same as the finalizing block
|
// otherwise, the popped root block is the same as the finalizing block
|
||||||
|
|
||||||
// clone if required
|
// clone if required
|
||||||
let write_chain = Arc::make_mut(&mut chain);
|
let mut_side_chain = Arc::make_mut(&mut side_chain);
|
||||||
|
|
||||||
// remove the first block from `chain`
|
// remove the first block from `chain`
|
||||||
let chain_start = write_chain.pop_root();
|
let (side_chain_root, _treestate) = mut_side_chain.pop_root();
|
||||||
assert_eq!(chain_start.hash, finalizing.hash);
|
assert_eq!(side_chain_root.hash, best_chain_root.hash);
|
||||||
|
|
||||||
// add the chain back to `self.chain_set`
|
// add the chain back to `self.chain_set`
|
||||||
self.chain_set.insert(chain);
|
self.chain_set.insert(side_chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_metrics_for_chains();
|
self.update_metrics_for_chains();
|
||||||
|
|
||||||
finalizing.into()
|
// Add the treestate to the finalized block.
|
||||||
|
FinalizedWithTrees::new(best_chain_root, root_treestate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit block to the non-finalized state, on top of:
|
/// Commit block to the non-finalized state, on top of:
|
||||||
|
|
|
@ -30,8 +30,8 @@ use zebra_chain::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
service::check, ContextuallyValidBlock, HashOrHeight, OutputLocation, TransactionLocation,
|
request::Treestate, service::check, ContextuallyValidBlock, HashOrHeight, OutputLocation,
|
||||||
ValidateContextError,
|
TransactionLocation, ValidateContextError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::index::TransparentTransfers;
|
use self::index::TransparentTransfers;
|
||||||
|
@ -71,6 +71,9 @@ pub struct Chain {
|
||||||
/// This is required for interstitial states.
|
/// This is required for interstitial states.
|
||||||
pub(crate) sprout_trees_by_anchor:
|
pub(crate) sprout_trees_by_anchor:
|
||||||
HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
|
HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>>,
|
||||||
|
/// The Sprout note commitment tree for each height.
|
||||||
|
pub(crate) sprout_trees_by_height:
|
||||||
|
BTreeMap<block::Height, Arc<sprout::tree::NoteCommitmentTree>>,
|
||||||
/// The Sapling note commitment tree of the tip of this [`Chain`],
|
/// The Sapling note commitment tree of the tip of this [`Chain`],
|
||||||
/// including all finalized notes, and the non-finalized notes in this chain.
|
/// including all finalized notes, and the non-finalized notes in this chain.
|
||||||
pub(super) sapling_note_commitment_tree: Arc<sapling::tree::NoteCommitmentTree>,
|
pub(super) sapling_note_commitment_tree: Arc<sapling::tree::NoteCommitmentTree>,
|
||||||
|
@ -150,6 +153,7 @@ impl Chain {
|
||||||
sprout_anchors: MultiSet::new(),
|
sprout_anchors: MultiSet::new(),
|
||||||
sprout_anchors_by_height: Default::default(),
|
sprout_anchors_by_height: Default::default(),
|
||||||
sprout_trees_by_anchor: Default::default(),
|
sprout_trees_by_anchor: Default::default(),
|
||||||
|
sprout_trees_by_height: Default::default(),
|
||||||
sapling_anchors: MultiSet::new(),
|
sapling_anchors: MultiSet::new(),
|
||||||
sapling_anchors_by_height: Default::default(),
|
sapling_anchors_by_height: Default::default(),
|
||||||
sapling_trees_by_height: Default::default(),
|
sapling_trees_by_height: Default::default(),
|
||||||
|
@ -191,6 +195,7 @@ impl Chain {
|
||||||
// note commitment trees
|
// note commitment trees
|
||||||
self.sprout_note_commitment_tree.root() == other.sprout_note_commitment_tree.root() &&
|
self.sprout_note_commitment_tree.root() == other.sprout_note_commitment_tree.root() &&
|
||||||
self.sprout_trees_by_anchor == other.sprout_trees_by_anchor &&
|
self.sprout_trees_by_anchor == other.sprout_trees_by_anchor &&
|
||||||
|
self.sprout_trees_by_height == other.sprout_trees_by_height &&
|
||||||
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.sapling_trees_by_height == other.sapling_trees_by_height &&
|
self.sapling_trees_by_height == other.sapling_trees_by_height &&
|
||||||
self.orchard_note_commitment_tree.root() == other.orchard_note_commitment_tree.root() &&
|
self.orchard_note_commitment_tree.root() == other.orchard_note_commitment_tree.root() &&
|
||||||
|
@ -240,22 +245,28 @@ impl Chain {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the lowest height block of the non-finalized portion of a chain.
|
/// Pops the lowest height block of the non-finalized portion of a chain,
|
||||||
|
/// and returns it with its associated treestate.
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
pub(crate) fn pop_root(&mut self) -> ContextuallyValidBlock {
|
pub(crate) fn pop_root(&mut self) -> (ContextuallyValidBlock, Treestate) {
|
||||||
|
// Obtain the lowest height.
|
||||||
let block_height = self.non_finalized_root_height();
|
let block_height = self.non_finalized_root_height();
|
||||||
|
|
||||||
// remove the lowest height block from self.blocks
|
// Obtain the treestate associated with the block being finalized.
|
||||||
|
let treestate = self
|
||||||
|
.treestate(block_height.into())
|
||||||
|
.expect("The treestate must be present for the root height.");
|
||||||
|
|
||||||
|
// Remove the lowest height block from `self.blocks`.
|
||||||
let block = self
|
let block = self
|
||||||
.blocks
|
.blocks
|
||||||
.remove(&block_height)
|
.remove(&block_height)
|
||||||
.expect("only called while blocks is populated");
|
.expect("only called while blocks is populated");
|
||||||
|
|
||||||
// update cumulative data members
|
// Update cumulative data members.
|
||||||
self.revert_chain_with(&block, RevertPosition::Root);
|
self.revert_chain_with(&block, RevertPosition::Root);
|
||||||
|
|
||||||
// return the prepared block
|
(block, treestate)
|
||||||
block
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the height of the chain root.
|
/// Returns the height of the chain root.
|
||||||
|
@ -481,9 +492,22 @@ impl Chain {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Sprout
|
||||||
|
/// [`NoteCommitmentTree`](sprout::tree::NoteCommitmentTree) specified by a
|
||||||
|
/// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
|
||||||
|
pub fn sprout_tree(
|
||||||
|
&self,
|
||||||
|
hash_or_height: HashOrHeight,
|
||||||
|
) -> Option<Arc<sprout::tree::NoteCommitmentTree>> {
|
||||||
|
let height =
|
||||||
|
hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
|
||||||
|
|
||||||
|
self.sprout_trees_by_height.get(&height).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the Sapling
|
/// Returns the Sapling
|
||||||
/// [`NoteCommitmentTree`](sapling::tree::NoteCommitmentTree) specified by a
|
/// [`NoteCommitmentTree`](sapling::tree::NoteCommitmentTree) specified by a
|
||||||
/// hash or height, if it exists in the non-finalized `chain`.
|
/// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
|
||||||
pub fn sapling_tree(
|
pub fn sapling_tree(
|
||||||
&self,
|
&self,
|
||||||
hash_or_height: HashOrHeight,
|
hash_or_height: HashOrHeight,
|
||||||
|
@ -496,7 +520,7 @@ impl Chain {
|
||||||
|
|
||||||
/// Returns the Orchard
|
/// Returns the Orchard
|
||||||
/// [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by a
|
/// [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by a
|
||||||
/// hash or height, if it exists in the non-finalized `chain`.
|
/// [`HashOrHeight`], if it exists in the non-finalized [`Chain`].
|
||||||
pub fn orchard_tree(
|
pub fn orchard_tree(
|
||||||
&self,
|
&self,
|
||||||
hash_or_height: HashOrHeight,
|
hash_or_height: HashOrHeight,
|
||||||
|
@ -507,6 +531,29 @@ impl Chain {
|
||||||
self.orchard_trees_by_height.get(&height).cloned()
|
self.orchard_trees_by_height.get(&height).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`HistoryTree`] specified by a [`HashOrHeight`], if it
|
||||||
|
/// exists in the non-finalized [`Chain`].
|
||||||
|
pub fn history_tree(&self, hash_or_height: HashOrHeight) -> Option<Arc<HistoryTree>> {
|
||||||
|
let height =
|
||||||
|
hash_or_height.height_or_else(|hash| self.height_by_hash.get(&hash).cloned())?;
|
||||||
|
|
||||||
|
self.history_trees_by_height.get(&height).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn treestate(&self, hash_or_height: HashOrHeight) -> Option<Treestate> {
|
||||||
|
let sprout_tree = self.sprout_tree(hash_or_height)?;
|
||||||
|
let sapling_tree = self.sapling_tree(hash_or_height)?;
|
||||||
|
let orchard_tree = self.orchard_tree(hash_or_height)?;
|
||||||
|
let history_tree = self.history_tree(hash_or_height)?;
|
||||||
|
|
||||||
|
Some(Treestate::new(
|
||||||
|
sprout_tree,
|
||||||
|
sapling_tree,
|
||||||
|
orchard_tree,
|
||||||
|
history_tree,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the block hash of the tip block.
|
/// Returns the block hash of the tip block.
|
||||||
pub fn non_finalized_tip_hash(&self) -> block::Hash {
|
pub fn non_finalized_tip_hash(&self) -> block::Hash {
|
||||||
self.blocks
|
self.blocks
|
||||||
|
@ -739,6 +786,7 @@ impl Chain {
|
||||||
spent_utxos: self.spent_utxos.clone(),
|
spent_utxos: self.spent_utxos.clone(),
|
||||||
sprout_note_commitment_tree,
|
sprout_note_commitment_tree,
|
||||||
sprout_trees_by_anchor: self.sprout_trees_by_anchor.clone(),
|
sprout_trees_by_anchor: self.sprout_trees_by_anchor.clone(),
|
||||||
|
sprout_trees_by_height: self.sprout_trees_by_height.clone(),
|
||||||
sapling_note_commitment_tree,
|
sapling_note_commitment_tree,
|
||||||
sapling_trees_by_height: self.sapling_trees_by_height.clone(),
|
sapling_trees_by_height: self.sapling_trees_by_height.clone(),
|
||||||
orchard_note_commitment_tree,
|
orchard_note_commitment_tree,
|
||||||
|
@ -808,6 +856,8 @@ impl Chain {
|
||||||
// Do the Chain updates with data dependencies on note commitment tree updates
|
// Do the Chain updates with data dependencies on note commitment tree updates
|
||||||
|
|
||||||
// Update the note commitment trees indexed by height.
|
// Update the note commitment trees indexed by height.
|
||||||
|
self.sprout_trees_by_height
|
||||||
|
.insert(height, self.sprout_note_commitment_tree.clone());
|
||||||
self.sapling_trees_by_height
|
self.sapling_trees_by_height
|
||||||
.insert(height, self.sapling_note_commitment_tree.clone());
|
.insert(height, self.sapling_note_commitment_tree.clone());
|
||||||
self.orchard_trees_by_height
|
self.orchard_trees_by_height
|
||||||
|
@ -1115,6 +1165,9 @@ impl UpdateWith<ContextuallyValidBlock> for Chain {
|
||||||
if !self.sprout_anchors.contains(&anchor) {
|
if !self.sprout_anchors.contains(&anchor) {
|
||||||
self.sprout_trees_by_anchor.remove(&anchor);
|
self.sprout_trees_by_anchor.remove(&anchor);
|
||||||
}
|
}
|
||||||
|
self.sprout_trees_by_height
|
||||||
|
.remove(&height)
|
||||||
|
.expect("Sprout note commitment tree must be present if block was added to chain");
|
||||||
|
|
||||||
let anchor = self
|
let anchor = self
|
||||||
.sapling_anchors_by_height
|
.sapling_anchors_by_height
|
||||||
|
|
|
@ -354,7 +354,7 @@ fn finalized_equals_pushed_genesis() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..finalized_count {
|
for _ in 0..finalized_count {
|
||||||
let _finalized = full_chain.pop_root();
|
full_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());
|
||||||
|
@ -425,7 +425,7 @@ fn finalized_equals_pushed_history_tree() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..finalized_count {
|
for _ in 0..finalized_count {
|
||||||
let _finalized = full_chain.pop_root();
|
full_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());
|
||||||
|
@ -608,6 +608,7 @@ fn different_blocks_different_chains() -> Result<()> {
|
||||||
// note commitment trees
|
// note commitment trees
|
||||||
chain1.sprout_note_commitment_tree = chain2.sprout_note_commitment_tree.clone();
|
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.sapling_note_commitment_tree = chain2.sapling_note_commitment_tree.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_note_commitment_tree = chain2.orchard_note_commitment_tree.clone();
|
||||||
|
|
|
@ -198,10 +198,12 @@ fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
|
||||||
state.commit_block(block2.clone().prepare(), &finalized_state)?;
|
state.commit_block(block2.clone().prepare(), &finalized_state)?;
|
||||||
state.commit_block(child.prepare(), &finalized_state)?;
|
state.commit_block(child.prepare(), &finalized_state)?;
|
||||||
|
|
||||||
let finalized = state.finalize();
|
let finalized_with_trees = state.finalize();
|
||||||
|
let finalized = finalized_with_trees.finalized;
|
||||||
assert_eq!(block1, finalized.block);
|
assert_eq!(block1, finalized.block);
|
||||||
|
|
||||||
let finalized = state.finalize();
|
let finalized_with_trees = state.finalize();
|
||||||
|
let finalized = finalized_with_trees.finalized;
|
||||||
assert_eq!(block2, finalized.block);
|
assert_eq!(block2, finalized.block);
|
||||||
|
|
||||||
assert!(state.best_chain().is_none());
|
assert!(state.best_chain().is_none());
|
||||||
|
|
|
@ -93,7 +93,7 @@ pub(crate) fn new_state_with_mainnet_genesis() -> (StateService, FinalizedBlock)
|
||||||
let genesis = FinalizedBlock::from(genesis);
|
let genesis = FinalizedBlock::from(genesis);
|
||||||
state
|
state
|
||||||
.disk
|
.disk
|
||||||
.commit_finalized_direct(genesis.clone(), "test")
|
.commit_finalized_direct(genesis.clone().into(), "test")
|
||||||
.expect("unexpected invalid genesis block test vector");
|
.expect("unexpected invalid genesis block test vector");
|
||||||
|
|
||||||
assert_eq!(Some((Height(0), genesis.hash)), state.best_tip());
|
assert_eq!(Some((Height(0), genesis.hash)), state.best_tip());
|
||||||
|
|
Loading…
Reference in New Issue