change(state): Insert only the first tree in each series of identical trees into finalized state (#7266)
* Pass ZebraDB to batch preparation * Dedup the insertion of Sapling trees into database * Dedup the insertion of Orchard trees into database * Update snapshots * Rename batch preparation of trees * Simplify the naming of note commitment trees * Correctly retrieve Sapling trees from fin state * Correctly retrieve Orchard trees from fin state * Simplify the naming of methods for Sprout trees * Simplify the naming of methods for Sapling trees * Simplify the naming of methods for Orchard trees * Reduce disk reads by caching trees. (#7276) * Bump the state minor version * Reset the state patch version * Simplify the preparation of genesis trees * Store the roots of the trees of the genesis block * Add the genesis roots to snapshots * fix(test): Don't include shielded data in genesis blocks (#7302) * fix(state): Fix marking format upgrades (#7304) --------- Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
parent
cce81b35c9
commit
57c9249141
|
@ -109,20 +109,32 @@ impl Transaction {
|
|||
option::of(any::<sapling::ShieldedData<sapling::PerSpendAnchor>>()),
|
||||
)
|
||||
.prop_map(
|
||||
|(
|
||||
inputs,
|
||||
outputs,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
joinsplit_data,
|
||||
sapling_shielded_data,
|
||||
)| Transaction::V4 {
|
||||
move |(
|
||||
inputs,
|
||||
outputs,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
joinsplit_data,
|
||||
sapling_shielded_data,
|
||||
)| {
|
||||
Transaction::V4 {
|
||||
inputs,
|
||||
outputs,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
joinsplit_data: if ledger_state.height.is_min() {
|
||||
// The genesis block should not contain any joinsplits.
|
||||
None
|
||||
} else {
|
||||
joinsplit_data
|
||||
},
|
||||
sapling_shielded_data: if ledger_state.height.is_min() {
|
||||
// The genesis block should not contain any shielded data.
|
||||
None
|
||||
} else {
|
||||
sapling_shielded_data
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
|
@ -159,8 +171,18 @@ impl Transaction {
|
|||
expiry_height,
|
||||
inputs,
|
||||
outputs,
|
||||
sapling_shielded_data,
|
||||
orchard_shielded_data,
|
||||
sapling_shielded_data: if ledger_state.height.is_min() {
|
||||
// The genesis block should not contain any shielded data.
|
||||
None
|
||||
} else {
|
||||
sapling_shielded_data
|
||||
},
|
||||
orchard_shielded_data: if ledger_state.height.is_min() {
|
||||
// The genesis block should not contain any shielded data.
|
||||
None
|
||||
} else {
|
||||
orchard_shielded_data
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
@ -48,11 +48,11 @@ pub(crate) const DATABASE_FORMAT_VERSION: u64 = 25;
|
|||
/// - adding new column families,
|
||||
/// - changing the format of a column family in a compatible way, or
|
||||
/// - breaking changes with compatibility code in all supported Zebra versions.
|
||||
pub(crate) const DATABASE_FORMAT_MINOR_VERSION: u64 = 0;
|
||||
pub(crate) const DATABASE_FORMAT_MINOR_VERSION: u64 = 1;
|
||||
|
||||
/// The database format patch version, incremented each time the on-disk database format has a
|
||||
/// significant format compatibility fix.
|
||||
pub(crate) const DATABASE_FORMAT_PATCH_VERSION: u64 = 2;
|
||||
pub(crate) const DATABASE_FORMAT_PATCH_VERSION: u64 = 0;
|
||||
|
||||
/// The name of the file containing the minor and patch database versions.
|
||||
///
|
||||
|
|
|
@ -152,7 +152,7 @@ fn fetch_sprout_final_treestates(
|
|||
|
||||
let input_tree = parent_chain
|
||||
.and_then(|chain| chain.sprout_trees_by_anchor.get(&joinsplit.anchor).cloned())
|
||||
.or_else(|| finalized_state.sprout_note_commitment_tree_by_anchor(&joinsplit.anchor));
|
||||
.or_else(|| finalized_state.sprout_tree_by_anchor(&joinsplit.anchor));
|
||||
|
||||
if let Some(input_tree) = input_tree {
|
||||
sprout_final_treestates.insert(joinsplit.anchor, input_tree);
|
||||
|
|
|
@ -85,7 +85,7 @@ proptest! {
|
|||
// randomly choose to commit the block to the finalized or non-finalized state
|
||||
if use_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(Arc::new(block1));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), None, "test");
|
||||
|
||||
// the block was committed
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
@ -352,7 +352,7 @@ proptest! {
|
|||
// randomly choose to commit the next block to the finalized or non-finalized state
|
||||
if duplicate_in_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(Arc::new(block1));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), None, "test");
|
||||
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
prop_assert!(commit_result.is_ok());
|
||||
|
@ -452,7 +452,7 @@ proptest! {
|
|||
// randomly choose to commit the block to the finalized or non-finalized state
|
||||
if use_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(Arc::new(block1));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(),None, "test");
|
||||
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
prop_assert!(commit_result.is_ok());
|
||||
|
@ -634,7 +634,7 @@ proptest! {
|
|||
// randomly choose to commit the next block to the finalized or non-finalized state
|
||||
if duplicate_in_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(Arc::new(block1));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(),None, "test");
|
||||
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
prop_assert!(commit_result.is_ok());
|
||||
|
@ -732,7 +732,7 @@ proptest! {
|
|||
// randomly choose to commit the block to the finalized or non-finalized state
|
||||
if use_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(Arc::new(block1));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), None, "test");
|
||||
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
prop_assert!(commit_result.is_ok());
|
||||
|
@ -923,7 +923,7 @@ proptest! {
|
|||
// randomly choose to commit the next block to the finalized or non-finalized state
|
||||
if duplicate_in_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(Arc::new(block1));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), None, "test");
|
||||
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
prop_assert!(commit_result.is_ok());
|
||||
|
|
|
@ -185,7 +185,7 @@ proptest! {
|
|||
// randomly choose to commit the block to the finalized or non-finalized state
|
||||
if use_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(Arc::new(block1));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), None, "test");
|
||||
|
||||
// the block was committed
|
||||
prop_assert_eq!(Some((Height(1), block1.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
@ -273,7 +273,7 @@ proptest! {
|
|||
|
||||
if use_finalized_state_spend {
|
||||
let block2 = CheckpointVerifiedBlock::from(Arc::new(block2));
|
||||
let commit_result = finalized_state.commit_finalized_direct(block2.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block2.clone().into(),None, "test");
|
||||
|
||||
// the block was committed
|
||||
prop_assert_eq!(Some((Height(2), block2.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
@ -612,7 +612,7 @@ proptest! {
|
|||
|
||||
if use_finalized_state_spend {
|
||||
let block2 = CheckpointVerifiedBlock::from(block2.clone());
|
||||
let commit_result = finalized_state.commit_finalized_direct(block2.clone().into(), "test");
|
||||
let commit_result = finalized_state.commit_finalized_direct(block2.clone().into(), None, "test");
|
||||
|
||||
// the block was committed
|
||||
prop_assert_eq!(Some((Height(2), block2.hash)), read::best_tip(&non_finalized_state, &finalized_state.db));
|
||||
|
@ -884,7 +884,8 @@ fn new_state_with_mainnet_transparent_data(
|
|||
|
||||
if use_finalized_state {
|
||||
let block1 = CheckpointVerifiedBlock::from(block1.clone());
|
||||
let commit_result = finalized_state.commit_finalized_direct(block1.clone().into(), "test");
|
||||
let commit_result =
|
||||
finalized_state.commit_finalized_direct(block1.clone().into(), None, "test");
|
||||
|
||||
// the block was committed
|
||||
assert_eq!(
|
||||
|
|
|
@ -20,7 +20,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use zebra_chain::{block, parameters::Network};
|
||||
use zebra_chain::{block, parallel::tree::NoteCommitmentTrees, parameters::Network};
|
||||
|
||||
use crate::{
|
||||
request::{FinalizableBlock, SemanticallyVerifiedBlockWithTrees, Treestate},
|
||||
|
@ -168,10 +168,12 @@ impl FinalizedState {
|
|||
pub fn commit_finalized(
|
||||
&mut self,
|
||||
ordered_block: QueuedCheckpointVerified,
|
||||
) -> Result<CheckpointVerifiedBlock, BoxError> {
|
||||
prev_note_commitment_trees: Option<NoteCommitmentTrees>,
|
||||
) -> Result<(CheckpointVerifiedBlock, NoteCommitmentTrees), BoxError> {
|
||||
let (checkpoint_verified, rsp_tx) = ordered_block;
|
||||
let result = self.commit_finalized_direct(
|
||||
checkpoint_verified.clone().into(),
|
||||
prev_note_commitment_trees,
|
||||
"commit checkpoint-verified request",
|
||||
);
|
||||
|
||||
|
@ -202,10 +204,10 @@ impl FinalizedState {
|
|||
// and the block write task.
|
||||
let result = result.map_err(CloneError::from);
|
||||
|
||||
let _ = rsp_tx.send(result.clone().map_err(BoxError::from));
|
||||
let _ = rsp_tx.send(result.clone().map(|(hash, _)| hash).map_err(BoxError::from));
|
||||
|
||||
result
|
||||
.map(|_hash| checkpoint_verified)
|
||||
.map(|(_hash, note_commitment_trees)| (checkpoint_verified, note_commitment_trees))
|
||||
.map_err(BoxError::from)
|
||||
}
|
||||
|
||||
|
@ -226,9 +228,10 @@ impl FinalizedState {
|
|||
pub fn commit_finalized_direct(
|
||||
&mut self,
|
||||
finalizable_block: FinalizableBlock,
|
||||
prev_note_commitment_trees: Option<NoteCommitmentTrees>,
|
||||
source: &str,
|
||||
) -> Result<block::Hash, BoxError> {
|
||||
let (height, hash, finalized) = match finalizable_block {
|
||||
) -> Result<(block::Hash, NoteCommitmentTrees), BoxError> {
|
||||
let (height, hash, finalized, prev_note_commitment_trees) = match finalizable_block {
|
||||
FinalizableBlock::Checkpoint {
|
||||
checkpoint_verified,
|
||||
} => {
|
||||
|
@ -240,9 +243,11 @@ impl FinalizedState {
|
|||
|
||||
let block = checkpoint_verified.block.clone();
|
||||
let mut history_tree = self.db.history_tree();
|
||||
let mut note_commitment_trees = self.db.note_commitment_trees();
|
||||
let prev_note_commitment_trees =
|
||||
prev_note_commitment_trees.unwrap_or_else(|| self.db.note_commitment_trees());
|
||||
|
||||
// Update the note commitment trees.
|
||||
let mut note_commitment_trees = prev_note_commitment_trees.clone();
|
||||
note_commitment_trees.update_trees_parallel(&block)?;
|
||||
|
||||
// Check the block commitment if the history tree was not
|
||||
|
@ -287,6 +292,7 @@ impl FinalizedState {
|
|||
history_tree,
|
||||
},
|
||||
},
|
||||
Some(prev_note_commitment_trees),
|
||||
)
|
||||
}
|
||||
FinalizableBlock::Contextual {
|
||||
|
@ -299,6 +305,7 @@ impl FinalizedState {
|
|||
verified: contextually_verified.into(),
|
||||
treestate,
|
||||
},
|
||||
prev_note_commitment_trees,
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -331,8 +338,11 @@ impl FinalizedState {
|
|||
|
||||
#[cfg(feature = "elasticsearch")]
|
||||
let finalized_block = finalized.verified.block.clone();
|
||||
let note_commitment_trees = finalized.treestate.note_commitment_trees.clone();
|
||||
|
||||
let result = self.db.write_block(finalized, self.network, source);
|
||||
let result =
|
||||
self.db
|
||||
.write_block(finalized, prev_note_commitment_trees, self.network, source);
|
||||
|
||||
if result.is_ok() {
|
||||
// Save blocks to elasticsearch if the feature is enabled.
|
||||
|
@ -360,7 +370,7 @@ impl FinalizedState {
|
|||
}
|
||||
}
|
||||
|
||||
result
|
||||
result.map(|hash| (hash, note_commitment_trees))
|
||||
}
|
||||
|
||||
#[cfg(feature = "elasticsearch")]
|
||||
|
|
|
@ -103,7 +103,7 @@ fn test_raw_rocksdb_column_families_with_network(network: Network) {
|
|||
.expect("test data deserializes");
|
||||
|
||||
state
|
||||
.commit_finalized_direct(block.into(), "snapshot tests")
|
||||
.commit_finalized_direct(block.into(), None, "snapshot tests")
|
||||
.expect("test block is valid");
|
||||
|
||||
let mut settings = insta::Settings::clone_current();
|
||||
|
|
|
@ -5,6 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,10 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,14 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
KV(
|
||||
k: "000002",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,6 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,10 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,14 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
KV(
|
||||
k: "000002",
|
||||
v: "0001ae2935f1dfd8a24aed7c70df7de3a668eb7a49b1319880dde2bbd9031ae5d82f",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,6 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,10 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,14 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
KV(
|
||||
k: "000002",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,6 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,10 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,14 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
),
|
||||
KV(
|
||||
k: "000001",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
KV(
|
||||
k: "000002",
|
||||
v: "0001fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,6 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
v: "0001d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd259",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -5,6 +5,6 @@ expression: cf_data
|
|||
[
|
||||
KV(
|
||||
k: "000000",
|
||||
v: "0000",
|
||||
v: "0001d7c612c817793191a1e68652121876d6b3bde40f4fa52bc314145ce6e5cdd259",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -287,18 +287,18 @@ impl DbFormatChange {
|
|||
|
||||
upgrade_height = (upgrade_height + 1).expect("task exits before maximum height");
|
||||
}
|
||||
}
|
||||
|
||||
// At the end of each format upgrade, the database is marked as upgraded to that version.
|
||||
// Upgrades can be run more than once if Zebra is restarted, so this is just a performance
|
||||
// optimisation.
|
||||
info!(
|
||||
?initial_tip_height,
|
||||
?newer_running_version,
|
||||
?older_disk_version,
|
||||
"marking database as upgraded"
|
||||
);
|
||||
Self::mark_as_upgraded_to(&database_format_add_format_change_task, &config, network);
|
||||
// At the end of each format upgrade, the database is marked as upgraded to that version.
|
||||
// Upgrades can be run more than once if Zebra is restarted, so this is just a performance
|
||||
// optimisation.
|
||||
info!(
|
||||
?initial_tip_height,
|
||||
?newer_running_version,
|
||||
?older_disk_version,
|
||||
"marking database as upgraded"
|
||||
);
|
||||
Self::mark_as_upgraded_to(&database_format_add_format_change_task, &config, network);
|
||||
}
|
||||
|
||||
// End of example format change.
|
||||
|
||||
|
|
|
@ -29,12 +29,13 @@ fn blocks_with_v5_transactions() -> Result<()> {
|
|||
// use `count` to minimize test failures, so they are easier to diagnose
|
||||
for block in chain.iter().take(count) {
|
||||
let checkpoint_verified = CheckpointVerifiedBlock::from(block.block.clone());
|
||||
let hash = state.commit_finalized_direct(
|
||||
let (hash, _) = state.commit_finalized_direct(
|
||||
checkpoint_verified.into(),
|
||||
None,
|
||||
"blocks_with_v5_transactions test"
|
||||
);
|
||||
).unwrap();
|
||||
prop_assert_eq!(Some(height), state.finalized_tip_height());
|
||||
prop_assert_eq!(hash.unwrap(), block.hash);
|
||||
prop_assert_eq!(hash, block.hash);
|
||||
height = Height(height.0 + 1);
|
||||
}
|
||||
});
|
||||
|
@ -86,6 +87,7 @@ fn all_upgrades_and_wrong_commitments_with_fake_activation_heights() -> Result<(
|
|||
let checkpoint_verified = CheckpointVerifiedBlock::from(block);
|
||||
state.commit_finalized_direct(
|
||||
checkpoint_verified.into(),
|
||||
None,
|
||||
"all_upgrades test"
|
||||
).expect_err("Must fail commitment check");
|
||||
failure_count += 1;
|
||||
|
@ -93,8 +95,9 @@ fn all_upgrades_and_wrong_commitments_with_fake_activation_heights() -> Result<(
|
|||
_ => {},
|
||||
}
|
||||
let checkpoint_verified = CheckpointVerifiedBlock::from(block.block.clone());
|
||||
let hash = state.commit_finalized_direct(
|
||||
let (hash, _) = state.commit_finalized_direct(
|
||||
checkpoint_verified.into(),
|
||||
None,
|
||||
"all_upgrades test"
|
||||
).unwrap();
|
||||
prop_assert_eq!(Some(height), state.finalized_tip_height());
|
||||
|
|
|
@ -20,9 +20,11 @@ use zebra_chain::{
|
|||
amount::NonNegative,
|
||||
block::{self, Block, Height},
|
||||
orchard,
|
||||
parallel::tree::NoteCommitmentTrees,
|
||||
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
||||
sapling,
|
||||
serialization::TrustedPreallocate,
|
||||
sprout,
|
||||
transaction::{self, Transaction},
|
||||
transparent,
|
||||
value_balance::ValueBalance,
|
||||
|
@ -147,34 +149,28 @@ impl ZebraDb {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Returns the Sapling
|
||||
/// [`NoteCommitmentTree`](sapling::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the finalized `db`.
|
||||
/// Returns the Sapling [`note commitment tree`](sapling::tree::NoteCommitmentTree) specified by
|
||||
/// a hash or height, if it exists in the finalized state.
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
pub fn sapling_tree(
|
||||
pub fn sapling_tree_by_hash_or_height(
|
||||
&self,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<Arc<sapling::tree::NoteCommitmentTree>> {
|
||||
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
|
||||
|
||||
let sapling_tree_handle = self.db.cf_handle("sapling_note_commitment_tree").unwrap();
|
||||
|
||||
self.db.zs_get(&sapling_tree_handle, &height)
|
||||
self.sapling_tree_by_height(&height)
|
||||
}
|
||||
|
||||
/// Returns the Orchard
|
||||
/// [`NoteCommitmentTree`](orchard::tree::NoteCommitmentTree) specified by a
|
||||
/// hash or height, if it exists in the finalized `db`.
|
||||
/// Returns the Orchard [`note commitment tree`](orchard::tree::NoteCommitmentTree) specified by
|
||||
/// a hash or height, if it exists in the finalized state.
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
pub fn orchard_tree(
|
||||
pub fn orchard_tree_by_hash_or_height(
|
||||
&self,
|
||||
hash_or_height: HashOrHeight,
|
||||
) -> Option<Arc<orchard::tree::NoteCommitmentTree>> {
|
||||
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
|
||||
|
||||
let orchard_tree_handle = self.db.cf_handle("orchard_note_commitment_tree").unwrap();
|
||||
|
||||
self.db.zs_get(&orchard_tree_handle, &height)
|
||||
self.orchard_tree_by_height(&height)
|
||||
}
|
||||
|
||||
// Read tip block methods
|
||||
|
@ -281,6 +277,7 @@ impl ZebraDb {
|
|||
pub(in super::super) fn write_block(
|
||||
&mut self,
|
||||
finalized: SemanticallyVerifiedBlockWithTrees,
|
||||
prev_note_commitment_trees: Option<NoteCommitmentTrees>,
|
||||
network: Network,
|
||||
source: &str,
|
||||
) -> Result<block::Hash, BoxError> {
|
||||
|
@ -375,13 +372,14 @@ impl ZebraDb {
|
|||
|
||||
// In case of errors, propagate and do not write the batch.
|
||||
batch.prepare_block_batch(
|
||||
&self.db,
|
||||
self,
|
||||
&finalized,
|
||||
new_outputs_by_out_loc,
|
||||
spent_utxos_by_outpoint,
|
||||
spent_utxos_by_out_loc,
|
||||
address_balances,
|
||||
self.finalized_value_pool(),
|
||||
prev_note_commitment_trees,
|
||||
)?;
|
||||
|
||||
self.db.write(batch)?;
|
||||
|
@ -426,14 +424,16 @@ impl DiskWriteBatch {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn prepare_block_batch(
|
||||
&mut self,
|
||||
db: &DiskDb,
|
||||
zebra_db: &ZebraDb,
|
||||
finalized: &SemanticallyVerifiedBlockWithTrees,
|
||||
new_outputs_by_out_loc: BTreeMap<OutputLocation, transparent::Utxo>,
|
||||
spent_utxos_by_outpoint: HashMap<transparent::OutPoint, transparent::Utxo>,
|
||||
spent_utxos_by_out_loc: BTreeMap<OutputLocation, transparent::Utxo>,
|
||||
address_balances: HashMap<transparent::Address, AddressBalanceLocation>,
|
||||
value_pool: ValueBalance<NonNegative>,
|
||||
prev_note_commitment_trees: Option<NoteCommitmentTrees>,
|
||||
) -> Result<(), BoxError> {
|
||||
let db = &zebra_db.db;
|
||||
// Commit block and transaction data.
|
||||
// (Transaction indexes, note commitments, and UTXOs are committed later.)
|
||||
self.prepare_block_header_and_transaction_data_batch(db, &finalized.verified)?;
|
||||
|
@ -447,7 +447,7 @@ impl DiskWriteBatch {
|
|||
//
|
||||
// By returning early, Zebra commits the genesis block and transaction data,
|
||||
// but it ignores the genesis UTXO and value pool updates.
|
||||
if self.prepare_genesis_batch(db, &finalized.verified) {
|
||||
if self.prepare_genesis_batch(db, finalized) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -462,7 +462,7 @@ impl DiskWriteBatch {
|
|||
)?;
|
||||
self.prepare_shielded_transaction_batch(db, &finalized.verified)?;
|
||||
|
||||
self.prepare_note_commitment_batch(db, finalized)?;
|
||||
self.prepare_trees_batch(zebra_db, finalized, prev_note_commitment_trees)?;
|
||||
|
||||
// Commit UTXOs and value pools
|
||||
self.prepare_chain_value_pools_batch(
|
||||
|
@ -538,29 +538,71 @@ impl DiskWriteBatch {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// If `finalized.block` is a genesis block,
|
||||
/// prepare a database batch that finishes initializing the database,
|
||||
/// and return `true` (without actually writing anything).
|
||||
/// If `finalized.block` is a genesis block, prepares a database batch that finishes
|
||||
/// initializing the database, and returns `true` without actually writing anything.
|
||||
///
|
||||
/// Since the genesis block's transactions are skipped,
|
||||
/// the returned genesis batch should be written to the database immediately.
|
||||
/// Since the genesis block's transactions are skipped, the returned genesis batch should be
|
||||
/// written to the database immediately.
|
||||
///
|
||||
/// If `finalized.block` is not a genesis block, does nothing.
|
||||
///
|
||||
/// This method never returns an error.
|
||||
/// # Panics
|
||||
///
|
||||
/// If `finalized.block` is a genesis block, and a note commitment tree in `finalized` doesn't
|
||||
/// match its corresponding empty tree.
|
||||
pub fn prepare_genesis_batch(
|
||||
&mut self,
|
||||
db: &DiskDb,
|
||||
finalized: &SemanticallyVerifiedBlock,
|
||||
finalized: &SemanticallyVerifiedBlockWithTrees,
|
||||
) -> bool {
|
||||
let SemanticallyVerifiedBlock { block, .. } = finalized;
|
||||
if finalized.verified.block.header.previous_block_hash == GENESIS_PREVIOUS_BLOCK_HASH {
|
||||
assert_eq!(
|
||||
*finalized.treestate.note_commitment_trees.sprout,
|
||||
sprout::tree::NoteCommitmentTree::default(),
|
||||
"The Sprout tree in the finalized block must match the empty Sprout tree."
|
||||
);
|
||||
assert_eq!(
|
||||
*finalized.treestate.note_commitment_trees.sapling,
|
||||
sapling::tree::NoteCommitmentTree::default(),
|
||||
"The Sapling tree in the finalized block must match the empty Sapling tree."
|
||||
);
|
||||
assert_eq!(
|
||||
*finalized.treestate.note_commitment_trees.orchard,
|
||||
orchard::tree::NoteCommitmentTree::default(),
|
||||
"The Orchard tree in the finalized block must match the empty Orchard tree."
|
||||
);
|
||||
|
||||
if block.header.previous_block_hash == GENESIS_PREVIOUS_BLOCK_HASH {
|
||||
self.prepare_genesis_note_commitment_tree_batch(db, finalized);
|
||||
// We want to store the trees of the genesis block together with their roots, and since
|
||||
// the trees cache the roots after their computation, we trigger the computation.
|
||||
//
|
||||
// At the time of writing this comment, the roots are precomputed before this function
|
||||
// is called, so the roots should already be cached.
|
||||
finalized.treestate.note_commitment_trees.sprout.root();
|
||||
finalized.treestate.note_commitment_trees.sapling.root();
|
||||
finalized.treestate.note_commitment_trees.orchard.root();
|
||||
|
||||
return true;
|
||||
// Insert the empty note commitment trees. Note that these can't be used too early
|
||||
// (e.g. the Orchard tree before Nu5 activates) since the block validation will make
|
||||
// sure only appropriate transactions are allowed in a block.
|
||||
self.zs_insert(
|
||||
&db.cf_handle("sprout_note_commitment_tree").unwrap(),
|
||||
finalized.verified.height,
|
||||
finalized.treestate.note_commitment_trees.sprout.clone(),
|
||||
);
|
||||
self.zs_insert(
|
||||
&db.cf_handle("sapling_note_commitment_tree").unwrap(),
|
||||
finalized.verified.height,
|
||||
finalized.treestate.note_commitment_trees.sapling.clone(),
|
||||
);
|
||||
self.zs_insert(
|
||||
&db.cf_handle("orchard_note_commitment_tree").unwrap(),
|
||||
finalized.verified.height,
|
||||
finalized.treestate.note_commitment_trees.orchard.clone(),
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ fn test_block_and_transaction_data_with_network(network: Network) {
|
|||
.expect("test data deserializes");
|
||||
|
||||
state
|
||||
.commit_finalized_direct(block.into(), "snapshot tests")
|
||||
.commit_finalized_direct(block.into(), None, "snapshot tests")
|
||||
.expect("test block is valid");
|
||||
|
||||
let mut settings = insta::Settings::clone_current();
|
||||
|
@ -220,10 +220,10 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) {
|
|||
//
|
||||
// We only store the sprout tree for the tip by height, so we can't check sprout here.
|
||||
let sapling_tree = state
|
||||
.sapling_note_commitment_tree_by_height(&block::Height::MIN)
|
||||
.sapling_tree_by_height(&block::Height::MIN)
|
||||
.expect("the genesis block in the database has a Sapling tree");
|
||||
let orchard_tree = state
|
||||
.orchard_note_commitment_tree_by_height(&block::Height::MIN)
|
||||
.orchard_tree_by_height(&block::Height::MIN)
|
||||
.expect("the genesis block in the database has an Orchard tree");
|
||||
|
||||
assert_eq!(*sapling_tree, sapling::tree::NoteCommitmentTree::default());
|
||||
|
@ -243,13 +243,13 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) {
|
|||
|
||||
// Shielded
|
||||
|
||||
let stored_sprout_trees = state.sprout_note_commitments_full_map();
|
||||
let stored_sprout_trees = state.sprout_trees_full_map();
|
||||
let mut stored_sapling_trees = Vec::new();
|
||||
let mut stored_orchard_trees = Vec::new();
|
||||
|
||||
let sprout_tree_at_tip = state.sprout_note_commitment_tree();
|
||||
let sapling_tree_at_tip = state.sapling_note_commitment_tree();
|
||||
let orchard_tree_at_tip = state.orchard_note_commitment_tree();
|
||||
let sprout_tree_at_tip = state.sprout_tree();
|
||||
let sapling_tree_at_tip = state.sapling_tree();
|
||||
let orchard_tree_at_tip = state.orchard_tree();
|
||||
|
||||
// Test the history tree.
|
||||
//
|
||||
|
@ -278,10 +278,10 @@ fn snapshot_block_and_transaction_data(state: &FinalizedState) {
|
|||
//
|
||||
// TODO: test the rest of the shielded data (anchors, nullifiers)
|
||||
let sapling_tree_by_height = state
|
||||
.sapling_note_commitment_tree_by_height(&query_height)
|
||||
.sapling_tree_by_height(&query_height)
|
||||
.expect("heights up to tip have Sapling trees");
|
||||
let orchard_tree_by_height = state
|
||||
.orchard_note_commitment_tree_by_height(&query_height)
|
||||
.orchard_tree_by_height(&query_height)
|
||||
.expect("heights up to tip have Orchard trees");
|
||||
|
||||
// We don't need to snapshot the heights,
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_orchard_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Base(
|
||||
bytes: (174, 41, 53, 241, 223, 216, 162, 74, 237, 124, 112, 223, 125, 227, 166, 104, 235, 122, 73, 177, 49, 152, 128, 221, 226, 187, 217, 3, 26, 229, 216, 47),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_orchard_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Base(
|
||||
bytes: (174, 41, 53, 241, 223, 216, 162, 74, 237, 124, 112, 223, 125, 227, 166, 104, 235, 122, 73, 177, 49, 152, 128, 221, 226, 187, 217, 3, 26, 229, 216, 47),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_orchard_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Base(
|
||||
bytes: (174, 41, 53, 241, 223, 216, 162, 74, 237, 124, 112, 223, 125, 227, 166, 104, 235, 122, 73, 177, 49, 152, 128, 221, 226, 187, 217, 3, 26, 229, 216, 47),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_orchard_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Base(
|
||||
bytes: (174, 41, 53, 241, 223, 216, 162, 74, 237, 124, 112, 223, 125, 227, 166, 104, 235, 122, 73, 177, 49, 152, 128, 221, 226, 187, 217, 3, 26, 229, 216, 47),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_sapling_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Fq(
|
||||
bytes: (251, 194, 244, 48, 12, 1, 240, 183, 130, 13, 0, 227, 52, 124, 141, 164, 238, 97, 70, 116, 55, 108, 188, 69, 53, 157, 170, 84, 249, 181, 73, 62),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_sapling_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Fq(
|
||||
bytes: (251, 194, 244, 48, 12, 1, 240, 183, 130, 13, 0, 227, 52, 124, 141, 164, 238, 97, 70, 116, 55, 108, 188, 69, 53, 157, 170, 84, 249, 181, 73, 62),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_sapling_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Fq(
|
||||
bytes: (251, 194, 244, 48, 12, 1, 240, 183, 130, 13, 0, 227, 52, 124, 141, 164, 238, 97, 70, 116, 55, 108, 188, 69, 53, 157, 170, 84, 249, 181, 73, 62),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -7,7 +7,9 @@ expression: stored_sapling_trees
|
|||
inner: Frontier(
|
||||
frontier: None,
|
||||
),
|
||||
cached_root: None,
|
||||
cached_root: Some(Root(Fq(
|
||||
bytes: (251, 194, 244, 48, 12, 1, 240, 183, 130, 13, 0, 227, 52, 124, 141, 164, 238, 97, 70, 116, 55, 108, 188, 69, 53, 157, 170, 84, 249, 181, 73, 62),
|
||||
))),
|
||||
)),
|
||||
(Height(1), NoteCommitmentTree(
|
||||
inner: Frontier(
|
||||
|
|
|
@ -70,7 +70,7 @@ impl ZebraDb {
|
|||
|
||||
/// Returns the Sprout note commitment tree of the finalized tip
|
||||
/// or the empty tree if the state is empty.
|
||||
pub fn sprout_note_commitment_tree(&self) -> Arc<sprout::tree::NoteCommitmentTree> {
|
||||
pub fn sprout_tree(&self) -> Arc<sprout::tree::NoteCommitmentTree> {
|
||||
let height = match self.finalized_tip_height() {
|
||||
Some(h) => h,
|
||||
None => return Default::default(),
|
||||
|
@ -88,7 +88,7 @@ impl ZebraDb {
|
|||
///
|
||||
/// This is used for interstitial tree building, which is unique to Sprout.
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
pub fn sprout_note_commitment_tree_by_anchor(
|
||||
pub fn sprout_tree_by_anchor(
|
||||
&self,
|
||||
sprout_anchor: &sprout::tree::Root,
|
||||
) -> Option<Arc<sprout::tree::NoteCommitmentTree>> {
|
||||
|
@ -103,7 +103,7 @@ impl ZebraDb {
|
|||
///
|
||||
/// Calling this method can load a lot of data into RAM, and delay block commit transactions.
|
||||
#[allow(dead_code, clippy::unwrap_in_result)]
|
||||
pub fn sprout_note_commitments_full_map(
|
||||
pub fn sprout_trees_full_map(
|
||||
&self,
|
||||
) -> HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> {
|
||||
let sprout_anchors_handle = self.db.cf_handle("sprout_anchors").unwrap();
|
||||
|
@ -114,20 +114,20 @@ impl ZebraDb {
|
|||
|
||||
/// Returns the Sapling note commitment tree of the finalized tip
|
||||
/// or the empty tree if the state is empty.
|
||||
pub fn sapling_note_commitment_tree(&self) -> Arc<sapling::tree::NoteCommitmentTree> {
|
||||
pub fn sapling_tree(&self) -> Arc<sapling::tree::NoteCommitmentTree> {
|
||||
let height = match self.finalized_tip_height() {
|
||||
Some(h) => h,
|
||||
None => return Default::default(),
|
||||
};
|
||||
|
||||
self.sapling_note_commitment_tree_by_height(&height)
|
||||
self.sapling_tree_by_height(&height)
|
||||
.expect("Sapling note commitment tree must exist if there is a finalized tip")
|
||||
}
|
||||
|
||||
/// Returns the Sapling note commitment tree matching the given block height,
|
||||
/// or `None` if the height is above the finalized tip.
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
pub fn sapling_note_commitment_tree_by_height(
|
||||
pub fn sapling_tree_by_height(
|
||||
&self,
|
||||
height: &Height,
|
||||
) -> Option<Arc<sapling::tree::NoteCommitmentTree>> {
|
||||
|
@ -159,20 +159,20 @@ impl ZebraDb {
|
|||
|
||||
/// Returns the Orchard note commitment tree of the finalized tip
|
||||
/// or the empty tree if the state is empty.
|
||||
pub fn orchard_note_commitment_tree(&self) -> Arc<orchard::tree::NoteCommitmentTree> {
|
||||
pub fn orchard_tree(&self) -> Arc<orchard::tree::NoteCommitmentTree> {
|
||||
let height = match self.finalized_tip_height() {
|
||||
Some(h) => h,
|
||||
None => return Default::default(),
|
||||
};
|
||||
|
||||
self.orchard_note_commitment_tree_by_height(&height)
|
||||
self.orchard_tree_by_height(&height)
|
||||
.expect("Orchard note commitment tree must exist if there is a finalized tip")
|
||||
}
|
||||
|
||||
/// Returns the Orchard note commitment tree matching the given block height,
|
||||
/// or `None` if the height is above the finalized tip.
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
pub fn orchard_note_commitment_tree_by_height(
|
||||
pub fn orchard_tree_by_height(
|
||||
&self,
|
||||
height: &Height,
|
||||
) -> Option<Arc<orchard::tree::NoteCommitmentTree>> {
|
||||
|
@ -203,9 +203,9 @@ impl ZebraDb {
|
|||
/// or the empty trees if the state is empty.
|
||||
pub fn note_commitment_trees(&self) -> NoteCommitmentTrees {
|
||||
NoteCommitmentTrees {
|
||||
sprout: self.sprout_note_commitment_tree(),
|
||||
sapling: self.sapling_note_commitment_tree(),
|
||||
orchard: self.orchard_note_commitment_tree(),
|
||||
sprout: self.sprout_tree(),
|
||||
sapling: self.sapling_tree(),
|
||||
orchard: self.orchard_tree(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,97 +275,67 @@ impl DiskWriteBatch {
|
|||
///
|
||||
/// - Propagates any errors from updating the history tree
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
pub fn prepare_note_commitment_batch(
|
||||
pub fn prepare_trees_batch(
|
||||
&mut self,
|
||||
db: &DiskDb,
|
||||
zebra_db: &ZebraDb,
|
||||
finalized: &SemanticallyVerifiedBlockWithTrees,
|
||||
prev_note_commitment_trees: Option<NoteCommitmentTrees>,
|
||||
) -> Result<(), BoxError> {
|
||||
let db = &zebra_db.db;
|
||||
|
||||
let sprout_anchors = db.cf_handle("sprout_anchors").unwrap();
|
||||
let sapling_anchors = db.cf_handle("sapling_anchors").unwrap();
|
||||
let orchard_anchors = db.cf_handle("orchard_anchors").unwrap();
|
||||
|
||||
let sprout_note_commitment_tree_cf = db.cf_handle("sprout_note_commitment_tree").unwrap();
|
||||
let sapling_note_commitment_tree_cf = db.cf_handle("sapling_note_commitment_tree").unwrap();
|
||||
let orchard_note_commitment_tree_cf = db.cf_handle("orchard_note_commitment_tree").unwrap();
|
||||
let sprout_tree_cf = db.cf_handle("sprout_note_commitment_tree").unwrap();
|
||||
let sapling_tree_cf = db.cf_handle("sapling_note_commitment_tree").unwrap();
|
||||
let orchard_tree_cf = db.cf_handle("orchard_note_commitment_tree").unwrap();
|
||||
|
||||
let height = finalized.verified.height;
|
||||
let note_commitment_trees = finalized.treestate.note_commitment_trees.clone();
|
||||
let trees = finalized.treestate.note_commitment_trees.clone();
|
||||
|
||||
// Use the cached values that were previously calculated in parallel.
|
||||
let sprout_root = note_commitment_trees.sprout.root();
|
||||
let sapling_root = note_commitment_trees.sapling.root();
|
||||
let orchard_root = note_commitment_trees.orchard.root();
|
||||
let sprout_root = trees.sprout.root();
|
||||
let sapling_root = trees.sapling.root();
|
||||
let orchard_root = trees.orchard.root();
|
||||
|
||||
// Index the new anchors.
|
||||
// Note: if the root hasn't changed, we write the same value again.
|
||||
self.zs_insert(&sprout_anchors, sprout_root, ¬e_commitment_trees.sprout);
|
||||
self.zs_insert(&sprout_anchors, sprout_root, &trees.sprout);
|
||||
self.zs_insert(&sapling_anchors, sapling_root, ());
|
||||
self.zs_insert(&orchard_anchors, orchard_root, ());
|
||||
|
||||
// Delete the previously stored Sprout note commitment tree.
|
||||
let current_tip_height = height - 1;
|
||||
if let Some(h) = current_tip_height {
|
||||
self.zs_delete(&sprout_note_commitment_tree_cf, h);
|
||||
self.zs_delete(&sprout_tree_cf, h);
|
||||
}
|
||||
|
||||
// TODO: if we ever need concurrent read-only access to the sprout tree,
|
||||
// store it by `()`, not height. Otherwise, the ReadStateService could
|
||||
// access a height that was just deleted by a concurrent StateService
|
||||
// write. This requires a database version update.
|
||||
self.zs_insert(
|
||||
&sprout_note_commitment_tree_cf,
|
||||
height,
|
||||
note_commitment_trees.sprout,
|
||||
);
|
||||
self.zs_insert(&sprout_tree_cf, height, trees.sprout);
|
||||
|
||||
self.zs_insert(
|
||||
&sapling_note_commitment_tree_cf,
|
||||
height,
|
||||
note_commitment_trees.sapling,
|
||||
);
|
||||
// Store the Sapling tree only if it is not already present at the previous height.
|
||||
if height.is_min()
|
||||
|| prev_note_commitment_trees
|
||||
.as_ref()
|
||||
.map_or_else(|| zebra_db.sapling_tree(), |trees| trees.sapling.clone())
|
||||
!= trees.sapling
|
||||
{
|
||||
self.zs_insert(&sapling_tree_cf, height, trees.sapling);
|
||||
}
|
||||
|
||||
self.zs_insert(
|
||||
&orchard_note_commitment_tree_cf,
|
||||
height,
|
||||
note_commitment_trees.orchard,
|
||||
);
|
||||
// Store the Orchard tree only if it is not already present at the previous height.
|
||||
if height.is_min()
|
||||
|| prev_note_commitment_trees
|
||||
.map_or_else(|| zebra_db.orchard_tree(), |trees| trees.orchard)
|
||||
!= trees.orchard
|
||||
{
|
||||
self.zs_insert(&orchard_tree_cf, height, trees.orchard);
|
||||
}
|
||||
|
||||
self.prepare_history_batch(db, finalized)
|
||||
}
|
||||
|
||||
/// Prepare a database batch containing the initial note commitment trees,
|
||||
/// and return it (without actually writing anything).
|
||||
///
|
||||
/// This method never returns an error.
|
||||
pub fn prepare_genesis_note_commitment_tree_batch(
|
||||
&mut self,
|
||||
db: &DiskDb,
|
||||
finalized: &SemanticallyVerifiedBlock,
|
||||
) {
|
||||
let sprout_note_commitment_tree_cf = db.cf_handle("sprout_note_commitment_tree").unwrap();
|
||||
let sapling_note_commitment_tree_cf = db.cf_handle("sapling_note_commitment_tree").unwrap();
|
||||
let orchard_note_commitment_tree_cf = db.cf_handle("orchard_note_commitment_tree").unwrap();
|
||||
|
||||
let SemanticallyVerifiedBlock { height, .. } = finalized;
|
||||
|
||||
// Insert empty note commitment trees. Note that these can't be
|
||||
// used too early (e.g. the Orchard tree before Nu5 activates)
|
||||
// since the block validation will make sure only appropriate
|
||||
// transactions are allowed in a block.
|
||||
self.zs_insert(
|
||||
&sprout_note_commitment_tree_cf,
|
||||
height,
|
||||
sprout::tree::NoteCommitmentTree::default(),
|
||||
);
|
||||
self.zs_insert(
|
||||
&sapling_note_commitment_tree_cf,
|
||||
height,
|
||||
sapling::tree::NoteCommitmentTree::default(),
|
||||
);
|
||||
self.zs_insert(
|
||||
&orchard_note_commitment_tree_cf,
|
||||
height,
|
||||
orchard::tree::NoteCommitmentTree::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -284,9 +284,9 @@ impl NonFinalizedState {
|
|||
let chain = Chain::new(
|
||||
self.network,
|
||||
finalized_tip_height,
|
||||
finalized_state.sprout_note_commitment_tree(),
|
||||
finalized_state.sapling_note_commitment_tree(),
|
||||
finalized_state.orchard_note_commitment_tree(),
|
||||
finalized_state.sprout_tree(),
|
||||
finalized_state.sapling_tree(),
|
||||
finalized_state.orchard_tree(),
|
||||
finalized_state.history_tree(),
|
||||
finalized_state.finalized_value_pool(),
|
||||
);
|
||||
|
|
|
@ -38,7 +38,7 @@ where
|
|||
// in memory, but `db` stores blocks on disk, with a memory cache.)
|
||||
chain
|
||||
.and_then(|chain| chain.as_ref().sapling_tree(hash_or_height))
|
||||
.or_else(|| db.sapling_tree(hash_or_height))
|
||||
.or_else(|| db.sapling_tree_by_hash_or_height(hash_or_height))
|
||||
}
|
||||
|
||||
/// Returns the Orchard
|
||||
|
@ -59,7 +59,7 @@ where
|
|||
// in memory, but `db` stores blocks on disk, with a memory cache.)
|
||||
chain
|
||||
.and_then(|chain| chain.as_ref().orchard_tree(hash_or_height))
|
||||
.or_else(|| db.orchard_tree(hash_or_height))
|
||||
.or_else(|| db.orchard_tree_by_hash_or_height(hash_or_height))
|
||||
}
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
|
|
|
@ -140,6 +140,7 @@ pub fn write_blocks_from_channels(
|
|||
non_finalized_state_sender: watch::Sender<NonFinalizedState>,
|
||||
) {
|
||||
let mut last_zebra_mined_log_height = None;
|
||||
let mut prev_finalized_note_commitment_trees = None;
|
||||
|
||||
// Write all the finalized blocks sent by the state,
|
||||
// until the state closes the finalized block channel's sender.
|
||||
|
@ -178,9 +179,12 @@ pub fn write_blocks_from_channels(
|
|||
}
|
||||
|
||||
// Try committing the block
|
||||
match finalized_state.commit_finalized(ordered_block) {
|
||||
Ok(finalized) => {
|
||||
match finalized_state
|
||||
.commit_finalized(ordered_block, prev_finalized_note_commitment_trees.take())
|
||||
{
|
||||
Ok((finalized, note_commitment_trees)) => {
|
||||
let tip_block = ChainTipBlock::from(finalized);
|
||||
prev_finalized_note_commitment_trees = Some(note_commitment_trees);
|
||||
|
||||
log_if_mined_by_zebra(&tip_block, &mut last_zebra_mined_log_height);
|
||||
|
||||
|
@ -289,11 +293,11 @@ pub fn write_blocks_from_channels(
|
|||
while non_finalized_state.best_chain_len() > MAX_BLOCK_REORG_HEIGHT {
|
||||
tracing::trace!("finalizing block past the reorg limit");
|
||||
let contextually_verified_with_trees = non_finalized_state.finalize();
|
||||
finalized_state
|
||||
.commit_finalized_direct(contextually_verified_with_trees, "commit contextually-verified request")
|
||||
prev_finalized_note_commitment_trees = finalized_state
|
||||
.commit_finalized_direct(contextually_verified_with_trees, prev_finalized_note_commitment_trees.take(), "commit contextually-verified request")
|
||||
.expect(
|
||||
"unexpected finalized block commit error: note commitment and history trees were already checked by the non-finalized state",
|
||||
);
|
||||
).1.into();
|
||||
}
|
||||
|
||||
// Update the metrics if semantic and contextual validation passes
|
||||
|
|
|
@ -107,7 +107,7 @@ pub(crate) fn new_state_with_mainnet_genesis(
|
|||
|
||||
let genesis = CheckpointVerifiedBlock::from(genesis);
|
||||
finalized_state
|
||||
.commit_finalized_direct(genesis.clone().into(), "test")
|
||||
.commit_finalized_direct(genesis.clone().into(), None, "test")
|
||||
.expect("unexpected invalid genesis block test vector");
|
||||
|
||||
assert_eq!(
|
||||
|
|
Loading…
Reference in New Issue