use std::sync::{mpsc, Arc};
use hex_literal::hex;
use itertools::Itertools;
use tracing::instrument;
use zebra_chain::{
block::Height,
orchard,
parallel::tree::NoteCommitmentTrees,
parameters::Network::*,
sapling,
subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeIndex},
};
use crate::service::finalized_state::{
disk_format::upgrade::CancelFormatChange, DiskWriteBatch, ZebraDb,
};
#[allow(clippy::unwrap_in_result)]
#[instrument(skip(upgrade_db, cancel_receiver))]
pub fn run(
initial_tip_height: Height,
upgrade_db: &ZebraDb,
cancel_receiver: &mpsc::Receiver<CancelFormatChange>,
) -> Result<(), CancelFormatChange> {
let subtrees = upgrade_db
.sapling_tree_by_reversed_height_range(..=initial_tip_height)
.tuple_windows()
.map(|((end_height, tree), (prev_end_height, prev_tree))| {
(prev_end_height, prev_tree, end_height, tree)
})
.filter(|(_prev_end_height, prev_tree, _end_height, tree)| {
tree.contains_new_subtree(prev_tree)
});
for (prev_end_height, prev_tree, end_height, tree) in subtrees {
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let subtree =
calculate_sapling_subtree(upgrade_db, prev_end_height, prev_tree, end_height, tree);
write_sapling_subtree(upgrade_db, subtree);
}
let subtrees = upgrade_db
.orchard_tree_by_reversed_height_range(..=initial_tip_height)
.tuple_windows()
.map(|((end_height, tree), (prev_end_height, prev_tree))| {
(prev_end_height, prev_tree, end_height, tree)
})
.filter(|(_prev_end_height, prev_tree, _end_height, tree)| {
tree.contains_new_subtree(prev_tree)
});
for (prev_end_height, prev_tree, end_height, tree) in subtrees {
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let subtree =
calculate_orchard_subtree(upgrade_db, prev_end_height, prev_tree, end_height, tree);
write_orchard_subtree(upgrade_db, subtree);
}
Ok(())
}
#[allow(clippy::unwrap_in_result)]
#[instrument(skip(upgrade_db, cancel_receiver))]
pub fn reset(
_initial_tip_height: Height,
upgrade_db: &ZebraDb,
cancel_receiver: &mpsc::Receiver<CancelFormatChange>,
) -> Result<(), CancelFormatChange> {
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let mut batch = DiskWriteBatch::new();
batch.delete_range_sapling_subtree(upgrade_db, 0.into(), u16::MAX.into());
upgrade_db
.write_batch(batch)
.expect("deleting old sapling note commitment subtrees is a valid database operation");
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let mut batch = DiskWriteBatch::new();
batch.delete_range_orchard_subtree(upgrade_db, 0.into(), u16::MAX.into());
upgrade_db
.write_batch(batch)
.expect("deleting old orchard note commitment subtrees is a valid database operation");
Ok(())
}
pub fn subtree_format_calculation_pre_checks(db: &ZebraDb) -> Result<(), String> {
let sapling_result = quick_check_sapling_subtrees(db);
let orchard_result = quick_check_orchard_subtrees(db);
if sapling_result.is_err() || orchard_result.is_err() {
let err = Err(format!(
"missing or bad first subtree: sapling: {sapling_result:?}, orchard: {orchard_result:?}"
));
warn!(?err);
return err;
}
Ok(())
}
fn first_sapling_mainnet_subtree() -> NoteCommitmentSubtree<sapling::tree::Node> {
NoteCommitmentSubtree {
index: 0.into(),
root: hex!("754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13")
.as_slice()
.try_into()
.expect("test vector is valid"),
end_height: Height(558822),
}
}
fn first_orchard_mainnet_subtree() -> NoteCommitmentSubtree<orchard::tree::Node> {
NoteCommitmentSubtree {
index: 0.into(),
root: hex!("d4e323b3ae0cabfb6be4087fec8c66d9a9bbfc354bf1d9588b6620448182063b")
.as_slice()
.try_into()
.expect("test vector is valid"),
end_height: Height(1707429),
}
}
fn quick_check_sapling_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
if db.network() != Mainnet {
return Ok(());
}
let Some(NoteCommitmentSubtreeIndex(tip_subtree_index)) =
db.sapling_tree_for_tip().subtree_index()
else {
return Ok(());
};
if tip_subtree_index == 0 && !db.sapling_tree_for_tip().is_complete_subtree() {
return Ok(());
}
let first_complete_subtree = db
.sapling_tree_by_height_range(..)
.tuple_windows()
.map(|((prev_end_height, prev_tree), (end_height, tree))| {
(prev_end_height, prev_tree, end_height, tree)
})
.find(|(_prev_end_height, prev_tree, _end_height, tree)| {
tree.contains_new_subtree(prev_tree)
});
let Some((prev_end_height, prev_tree, end_height, tree)) = first_complete_subtree else {
let result = Err("iterator did not find complete subtree, but the tree has it");
error!(?result);
return result;
};
let expected_subtree = first_sapling_mainnet_subtree();
let db_subtree = calculate_sapling_subtree(db, prev_end_height, prev_tree, end_height, tree);
if db_subtree != expected_subtree {
let result = Err("first subtree did not match expected test vector");
error!(?result, ?db_subtree, ?expected_subtree);
return result;
}
Ok(())
}
fn quick_check_orchard_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
if db.network() != Mainnet {
return Ok(());
}
let Some(NoteCommitmentSubtreeIndex(tip_subtree_index)) =
db.orchard_tree_for_tip().subtree_index()
else {
return Ok(());
};
if tip_subtree_index == 0 && !db.orchard_tree_for_tip().is_complete_subtree() {
return Ok(());
}
let first_complete_subtree = db
.orchard_tree_by_height_range(..)
.tuple_windows()
.map(|((prev_end_height, prev_tree), (end_height, tree))| {
(prev_end_height, prev_tree, end_height, tree)
})
.find(|(_prev_end_height, prev_tree, _end_height, tree)| {
tree.contains_new_subtree(prev_tree)
});
let Some((prev_end_height, prev_tree, end_height, tree)) = first_complete_subtree else {
let result = Err("iterator did not find complete subtree, but the tree has it");
error!(?result);
return result;
};
let expected_subtree = first_orchard_mainnet_subtree();
let db_subtree = calculate_orchard_subtree(db, prev_end_height, prev_tree, end_height, tree);
if db_subtree != expected_subtree {
let result = Err("first subtree did not match expected test vector");
error!(?result, ?db_subtree, ?expected_subtree);
return result;
}
Ok(())
}
pub fn subtree_format_validity_checks_detailed(
db: &ZebraDb,
cancel_receiver: &mpsc::Receiver<CancelFormatChange>,
) -> Result<Result<(), String>, CancelFormatChange> {
let quick_result = subtree_format_calculation_pre_checks(db);
let sapling_result = check_sapling_subtrees(db, cancel_receiver)?;
let orchard_result = check_orchard_subtrees(db, cancel_receiver)?;
if quick_result.is_err() || sapling_result.is_err() || orchard_result.is_err() {
let err = Err(format!(
"missing or invalid subtree(s): \
quick: {quick_result:?}, sapling: {sapling_result:?}, orchard: {orchard_result:?}"
));
warn!(?err);
return Ok(err);
}
Ok(Ok(()))
}
fn check_sapling_subtrees(
db: &ZebraDb,
cancel_receiver: &mpsc::Receiver<CancelFormatChange>,
) -> Result<Result<(), &'static str>, CancelFormatChange> {
let Some(NoteCommitmentSubtreeIndex(mut first_incomplete_subtree_index)) =
db.sapling_tree_for_tip().subtree_index()
else {
return Ok(Ok(()));
};
if db.sapling_tree_for_tip().is_complete_subtree() {
first_incomplete_subtree_index += 1;
}
let mut result = Ok(());
for index in 0..first_incomplete_subtree_index {
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let Some(subtree) = db.sapling_subtree_by_index(index) else {
result = Err("missing subtree");
error!(?result, index);
continue;
};
let Some(tree) = db.sapling_tree_by_height(&subtree.end_height) else {
result = Err("missing note commitment tree at subtree completion height");
error!(?result, ?subtree.end_height);
continue;
};
if let Some((index, node)) = tree.completed_subtree_index_and_root() {
if subtree.index != index {
result = Err("completed subtree indexes should match");
error!(?result);
}
if subtree.root != node {
result = Err("completed subtree roots should match");
error!(?result);
}
}
else {
let prev_height = subtree
.end_height
.previous()
.expect("Note commitment subtrees should not end at the minimal height.");
let Some(prev_tree) = db.sapling_tree_by_height(&prev_height) else {
result = Err("missing note commitment tree below subtree completion height");
error!(?result, ?subtree.end_height);
continue;
};
let prev_subtree_index = prev_tree.subtree_index();
let subtree_index = tree.subtree_index();
if subtree_index <= prev_subtree_index {
result =
Err("note commitment tree at end height should have incremented subtree index");
error!(?result, ?subtree_index, ?prev_subtree_index,);
}
}
}
let mut subtree_count = 0;
for (index, height, tree) in db
.sapling_tree_by_height_range(..)
.filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree)))
.filter_map(|(subtree_index, height, tree)| {
if tree.is_complete_subtree() || subtree_index.0 > subtree_count {
let subtree_index = subtree_count;
subtree_count += 1;
Some((subtree_index, height, tree))
} else {
None
}
})
{
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let Some(subtree) = db.sapling_subtree_by_index(index) else {
result = Err("missing subtree");
error!(?result, index);
continue;
};
if subtree.end_height != height {
let is_complete = tree.is_complete_subtree();
result = Err("bad sapling subtree end height");
error!(?result, ?subtree.end_height, ?height, ?index, ?is_complete, );
}
if let Some((_index, node)) = tree.completed_subtree_index_and_root() {
if subtree.root != node {
result = Err("completed subtree roots should match");
error!(?result);
}
}
}
if result.is_err() {
error!(
?result,
?subtree_count,
first_incomplete_subtree_index,
"missing or bad sapling subtrees"
);
}
Ok(result)
}
fn check_orchard_subtrees(
db: &ZebraDb,
cancel_receiver: &mpsc::Receiver<CancelFormatChange>,
) -> Result<Result<(), &'static str>, CancelFormatChange> {
let Some(NoteCommitmentSubtreeIndex(mut first_incomplete_subtree_index)) =
db.orchard_tree_for_tip().subtree_index()
else {
return Ok(Ok(()));
};
if db.orchard_tree_for_tip().is_complete_subtree() {
first_incomplete_subtree_index += 1;
}
let mut result = Ok(());
for index in 0..first_incomplete_subtree_index {
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let Some(subtree) = db.orchard_subtree_by_index(index) else {
result = Err("missing subtree");
error!(?result, index);
continue;
};
let Some(tree) = db.orchard_tree_by_height(&subtree.end_height) else {
result = Err("missing note commitment tree at subtree completion height");
error!(?result, ?subtree.end_height);
continue;
};
if let Some((index, node)) = tree.completed_subtree_index_and_root() {
if subtree.index != index {
result = Err("completed subtree indexes should match");
error!(?result);
}
if subtree.root != node {
result = Err("completed subtree roots should match");
error!(?result);
}
}
else {
let prev_height = subtree
.end_height
.previous()
.expect("Note commitment subtrees should not end at the minimal height.");
let Some(prev_tree) = db.orchard_tree_by_height(&prev_height) else {
result = Err("missing note commitment tree below subtree completion height");
error!(?result, ?subtree.end_height);
continue;
};
let prev_subtree_index = prev_tree.subtree_index();
let subtree_index = tree.subtree_index();
if subtree_index <= prev_subtree_index {
result =
Err("note commitment tree at end height should have incremented subtree index");
error!(?result, ?subtree_index, ?prev_subtree_index,);
}
}
}
let mut subtree_count = 0;
for (index, height, tree) in db
.orchard_tree_by_height_range(..)
.filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree)))
.filter_map(|(subtree_index, height, tree)| {
if tree.is_complete_subtree() || subtree_index.0 > subtree_count {
let subtree_index = subtree_count;
subtree_count += 1;
Some((subtree_index, height, tree))
} else {
None
}
})
{
if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
let Some(subtree) = db.orchard_subtree_by_index(index) else {
result = Err("missing subtree");
error!(?result, index);
continue;
};
if subtree.end_height != height {
let is_complete = tree.is_complete_subtree();
result = Err("bad orchard subtree end height");
error!(?result, ?subtree.end_height, ?height, ?index, ?is_complete, );
}
if let Some((_index, node)) = tree.completed_subtree_index_and_root() {
if subtree.root != node {
result = Err("completed subtree roots should match");
error!(?result);
}
}
}
if result.is_err() {
error!(
?result,
?subtree_count,
first_incomplete_subtree_index,
"missing or bad orchard subtrees"
);
}
Ok(result)
}
#[must_use = "subtree should be written to the database after it is calculated"]
#[instrument(skip(read_db, prev_tree, tree))]
fn calculate_sapling_subtree(
read_db: &ZebraDb,
prev_end_height: Height,
prev_tree: Arc<sapling::tree::NoteCommitmentTree>,
end_height: Height,
tree: Arc<sapling::tree::NoteCommitmentTree>,
) -> NoteCommitmentSubtree<sapling::tree::Node> {
if let Some((index, node)) = tree.completed_subtree_index_and_root() {
NoteCommitmentSubtree::new(index, end_height, node)
} else {
let prev_position = prev_tree.position().unwrap_or_else(|| {
panic!(
"previous block must have a partial subtree:\n\
previous subtree:\n\
height: {prev_end_height:?}\n\
current subtree:\n\
height: {end_height:?}"
)
});
let prev_index = prev_tree
.subtree_index()
.expect("previous block must have a partial subtree");
let prev_remaining_notes = prev_tree.remaining_subtree_leaf_nodes();
let current_position = tree.position().unwrap_or_else(|| {
panic!(
"current block must have a subtree:\n\
previous subtree:\n\
height: {prev_end_height:?}\n\
index: {prev_index}\n\
position: {prev_position}\n\
remaining: {prev_remaining_notes}\n\
current subtree:\n\
height: {end_height:?}"
)
});
let current_index = tree
.subtree_index()
.expect("current block must have a subtree");
let current_remaining_notes = tree.remaining_subtree_leaf_nodes();
assert_eq!(
prev_index.0 + 1,
current_index.0,
"subtree must have been completed by the current block:\n\
previous subtree:\n\
height: {prev_end_height:?}\n\
index: {prev_index}\n\
position: {prev_position}\n\
remaining: {prev_remaining_notes}\n\
current subtree:\n\
height: {end_height:?}\n\
index: {current_index}\n\
position: {current_position}\n\
remaining: {current_remaining_notes}"
);
let block = read_db
.block(end_height.into())
.expect("height with note commitment tree should have block");
let sapling_note_commitments = block
.sapling_note_commitments()
.take(prev_remaining_notes)
.cloned()
.collect();
let (sapling_nct, subtree) = NoteCommitmentTrees::update_sapling_note_commitment_tree(
prev_tree,
sapling_note_commitments,
)
.expect("finalized notes should append successfully");
let (index, node) = subtree.unwrap_or_else(|| {
panic!(
"already checked that the block completed a subtree:\n\
updated subtree:\n\
index: {:?}\n\
position: {:?}\n\
remaining notes: {}\n\
original previous subtree:\n\
height: {prev_end_height:?}\n\
index: {prev_index}\n\
position: {prev_position}\n\
remaining: {prev_remaining_notes}\n\
original current subtree:\n\
height: {end_height:?}\n\
index: {current_index}\n\
position: {current_position}\n\
remaining: {current_remaining_notes}",
sapling_nct.subtree_index(),
sapling_nct.position(),
sapling_nct.remaining_subtree_leaf_nodes(),
)
});
NoteCommitmentSubtree::new(index, end_height, node)
}
}
#[must_use = "subtree should be written to the database after it is calculated"]
#[instrument(skip(read_db, prev_tree, tree))]
fn calculate_orchard_subtree(
read_db: &ZebraDb,
prev_end_height: Height,
prev_tree: Arc<orchard::tree::NoteCommitmentTree>,
end_height: Height,
tree: Arc<orchard::tree::NoteCommitmentTree>,
) -> NoteCommitmentSubtree<orchard::tree::Node> {
if let Some((index, node)) = tree.completed_subtree_index_and_root() {
NoteCommitmentSubtree::new(index, end_height, node)
} else {
let prev_position = prev_tree.position().unwrap_or_else(|| {
panic!(
"previous block must have a partial subtree:\n\
previous subtree:\n\
height: {prev_end_height:?}\n\
current subtree:\n\
height: {end_height:?}"
)
});
let prev_index = prev_tree
.subtree_index()
.expect("previous block must have a partial subtree");
let prev_remaining_notes = prev_tree.remaining_subtree_leaf_nodes();
let current_position = tree.position().unwrap_or_else(|| {
panic!(
"current block must have a subtree:\n\
previous subtree:\n\
height: {prev_end_height:?}\n\
index: {prev_index}\n\
position: {prev_position}\n\
remaining: {prev_remaining_notes}\n\
current subtree:\n\
height: {end_height:?}"
)
});
let current_index = tree
.subtree_index()
.expect("current block must have a subtree");
let current_remaining_notes = tree.remaining_subtree_leaf_nodes();
assert_eq!(
prev_index.0 + 1,
current_index.0,
"subtree must have been completed by the current block:\n\
previous subtree:\n\
height: {prev_end_height:?}\n\
index: {prev_index}\n\
position: {prev_position}\n\
remaining: {prev_remaining_notes}\n\
current subtree:\n\
height: {end_height:?}\n\
index: {current_index}\n\
position: {current_position}\n\
remaining: {current_remaining_notes}"
);
let block = read_db
.block(end_height.into())
.expect("height with note commitment tree should have block");
let orchard_note_commitments = block
.orchard_note_commitments()
.take(prev_remaining_notes)
.cloned()
.collect();
let (orchard_nct, subtree) = NoteCommitmentTrees::update_orchard_note_commitment_tree(
prev_tree,
orchard_note_commitments,
)
.expect("finalized notes should append successfully");
let (index, node) = subtree.unwrap_or_else(|| {
panic!(
"already checked that the block completed a subtree:\n\
updated subtree:\n\
index: {:?}\n\
position: {:?}\n\
remaining notes: {}\n\
original previous subtree:\n\
height: {prev_end_height:?}\n\
index: {prev_index}\n\
position: {prev_position}\n\
remaining: {prev_remaining_notes}\n\
original current subtree:\n\
height: {end_height:?}\n\
index: {current_index}\n\
position: {current_position}\n\
remaining: {current_remaining_notes}",
orchard_nct.subtree_index(),
orchard_nct.position(),
orchard_nct.remaining_subtree_leaf_nodes(),
)
});
NoteCommitmentSubtree::new(index, end_height, node)
}
}
fn write_sapling_subtree(
upgrade_db: &ZebraDb,
subtree: NoteCommitmentSubtree<sapling::tree::Node>,
) {
let mut batch = DiskWriteBatch::new();
batch.insert_sapling_subtree(upgrade_db, &subtree);
upgrade_db
.write_batch(batch)
.expect("writing sapling note commitment subtrees should always succeed.");
if subtree.index.0 % 100 == 0 {
info!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added sapling subtree");
}
debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added sapling subtree");
}
fn write_orchard_subtree(
upgrade_db: &ZebraDb,
subtree: NoteCommitmentSubtree<orchard::tree::Node>,
) {
let mut batch = DiskWriteBatch::new();
batch.insert_orchard_subtree(upgrade_db, &subtree);
upgrade_db
.write_batch(batch)
.expect("writing orchard note commitment subtrees should always succeed.");
if subtree.index.0 % 100 == 0 {
info!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added orchard subtree");
}
debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added orchard subtree");
}