change(state): Refactor the structure of verified blocks (#7025)

* Refactor `CheckpointVerifiedBlock`

This commit turns `CheckpointVerifiedBlock` into a wrapper of
`SemanticallyVerifiedBlock` since both structs have the same fields.

* Refactor `ContextuallyVerifiedBlockWithTrees`

This commit uses `SemanticallyVerifiedBlock` in
`ContextuallyVerifiedBlockWithTrees` instead of
`CheckpointVerifiedBlock`.
This commit is contained in:
Marek 2023-06-21 18:58:11 +02:00 committed by GitHub
parent 343a683cea
commit 006c2ae42b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 100 additions and 88 deletions

View File

@ -186,12 +186,12 @@ impl CheckpointVerifiedBlock {
let new_outputs =
transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes);
Self {
Self(SemanticallyVerifiedBlock {
block,
hash,
height,
new_outputs,
transaction_hashes,
}
})
}
}

View File

@ -2,7 +2,7 @@
use std::{
collections::{HashMap, HashSet},
ops::RangeInclusive,
ops::{Deref, DerefMut, RangeInclusive},
sync::Arc,
};
@ -162,6 +162,17 @@ pub struct SemanticallyVerifiedBlock {
pub transaction_hashes: Arc<[transaction::Hash]>,
}
/// A block ready to be committed directly to the finalized state with
/// no checks.
///
/// This is exposed for use in checkpointing.
///
/// Note: The difference between a `CheckpointVerifiedBlock` and a `ContextuallyVerifiedBlock` is
/// that the `CheckpointVerifier` doesn't bind the transaction authorizing data to the
/// `ChainHistoryBlockTxAuthCommitmentHash`, but the `NonFinalizedState` and `FinalizedState` do.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CheckpointVerifiedBlock(pub(crate) SemanticallyVerifiedBlock);
// Some fields are pub(crate), so we can add whatever db-format-dependent
// precomputation we want here without leaking internal details.
@ -211,36 +222,6 @@ pub struct ContextuallyVerifiedBlock {
pub(crate) chain_value_pool_change: ValueBalance<NegativeAllowed>,
}
/// A block ready to be committed directly to the finalized state with
/// no checks.
///
/// This is exposed for use in checkpointing.
///
/// Note: The difference between a `CheckpointVerifiedBlock` and a `ContextuallyVerifiedBlock` is
/// that the `CheckpointVerifier` doesn't bind the transaction authorizing data to the
/// `ChainHistoryBlockTxAuthCommitmentHash`, but the `NonFinalizedState` and `FinalizedState` do.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CheckpointVerifiedBlock {
/// The block to commit to the state.
pub block: Arc<Block>,
/// The hash of the block.
pub hash: block::Hash,
/// The height of the block.
pub height: block::Height,
/// New transparent outputs created in this block, indexed by
/// [`OutPoint`](transparent::OutPoint).
///
/// Note: although these transparent outputs are newly created, they may not
/// be unspent, since a later transaction in a block can spend outputs of an
/// earlier transaction.
///
/// This field can also contain unrelated outputs, which are ignored.
pub(crate) new_outputs: HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
/// A precomputed list of the hashes of the transactions in this block,
/// in the same order as `block.transactions`.
pub transaction_hashes: Arc<[transaction::Hash]>,
}
/// Wraps note commitment trees and the history tree together.
pub struct Treestate {
/// Note commitment trees.
@ -273,20 +254,18 @@ impl 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.
/// database and recompute a new one.
pub struct ContextuallyVerifiedBlockWithTrees {
/// A block ready to be committed.
pub checkpoint_verified: CheckpointVerifiedBlock,
pub block: SemanticallyVerifiedBlock,
/// The tresstate associated with the block.
pub treestate: Option<Treestate>,
}
impl ContextuallyVerifiedBlockWithTrees {
pub fn new(block: ContextuallyVerifiedBlock, treestate: Treestate) -> Self {
let checkpoint_verified = CheckpointVerifiedBlock::from(block);
pub fn new(contextually_verified: ContextuallyVerifiedBlock, treestate: Treestate) -> Self {
Self {
checkpoint_verified,
block: SemanticallyVerifiedBlock::from(contextually_verified),
treestate: Some(treestate),
}
}
@ -294,14 +273,23 @@ impl ContextuallyVerifiedBlockWithTrees {
impl From<Arc<Block>> for ContextuallyVerifiedBlockWithTrees {
fn from(block: Arc<Block>) -> Self {
Self::from(CheckpointVerifiedBlock::from(block))
Self::from(SemanticallyVerifiedBlock::from(block))
}
}
impl From<SemanticallyVerifiedBlock> for ContextuallyVerifiedBlockWithTrees {
fn from(semantically_verified: SemanticallyVerifiedBlock) -> Self {
Self {
block: semantically_verified,
treestate: None,
}
}
}
impl From<CheckpointVerifiedBlock> for ContextuallyVerifiedBlockWithTrees {
fn from(block: CheckpointVerifiedBlock) -> Self {
fn from(checkpoint_verified: CheckpointVerifiedBlock) -> Self {
Self {
checkpoint_verified: block,
block: checkpoint_verified.0,
treestate: None,
}
}
@ -358,6 +346,24 @@ impl ContextuallyVerifiedBlock {
}
}
impl SemanticallyVerifiedBlock {
fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
let height = block
.coinbase_height()
.expect("coinbase height was already checked");
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
SemanticallyVerifiedBlock {
block,
hash,
height,
new_outputs,
transaction_hashes,
}
}
}
impl CheckpointVerifiedBlock {
/// Create a block that's ready to be committed to the finalized state,
/// using a precalculated [`block::Hash`].
@ -365,19 +371,7 @@ impl CheckpointVerifiedBlock {
/// Note: a [`CheckpointVerifiedBlock`] isn't actually finalized
/// until [`Request::CommitCheckpointVerifiedBlock`] returns success.
pub fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
let height = block
.coinbase_height()
.expect("coinbase height was already checked");
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self {
block,
hash,
height,
new_outputs,
transaction_hashes,
}
Self(SemanticallyVerifiedBlock::with_hash(block, hash))
}
}
@ -389,7 +383,15 @@ impl From<Arc<Block>> for CheckpointVerifiedBlock {
}
}
impl From<ContextuallyVerifiedBlock> for CheckpointVerifiedBlock {
impl From<Arc<Block>> for SemanticallyVerifiedBlock {
fn from(block: Arc<Block>) -> Self {
let hash = block.hash();
SemanticallyVerifiedBlock::with_hash(block, hash)
}
}
impl From<ContextuallyVerifiedBlock> for SemanticallyVerifiedBlock {
fn from(contextually_valid: ContextuallyVerifiedBlock) -> Self {
let ContextuallyVerifiedBlock {
block,
@ -411,6 +413,19 @@ impl From<ContextuallyVerifiedBlock> for CheckpointVerifiedBlock {
}
}
impl Deref for CheckpointVerifiedBlock {
type Target = SemanticallyVerifiedBlock;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for CheckpointVerifiedBlock {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
/// A query about or modification to the chain state, via the
/// [`StateService`](crate::service::StateService).

View File

@ -21,7 +21,7 @@ use zebra_chain::{
use crate::{
request::ContextuallyVerifiedBlock, service::watch_receiver::WatchReceiver,
CheckpointVerifiedBlock,
CheckpointVerifiedBlock, SemanticallyVerifiedBlock,
};
use TipAction::*;
@ -109,13 +109,13 @@ impl From<ContextuallyVerifiedBlock> for ChainTipBlock {
impl From<CheckpointVerifiedBlock> for ChainTipBlock {
fn from(finalized: CheckpointVerifiedBlock) -> Self {
let CheckpointVerifiedBlock {
let CheckpointVerifiedBlock(SemanticallyVerifiedBlock {
block,
hash,
height,
transaction_hashes,
..
} = finalized;
}) = finalized;
Self {
hash,

View File

@ -228,7 +228,7 @@ impl FinalizedState {
contextually_verified_with_trees: ContextuallyVerifiedBlockWithTrees,
source: &str,
) -> Result<block::Hash, BoxError> {
let finalized = contextually_verified_with_trees.checkpoint_verified;
let finalized = contextually_verified_with_trees.block;
let committed_tip_hash = self.db.finalized_tip_hash();
let committed_tip_height = self.db.finalized_tip_height();

View File

@ -38,9 +38,8 @@ use crate::{
transparent::{AddressBalanceLocation, OutputLocation},
},
zebra_db::{metrics::block_precommit_metrics, ZebraDb},
CheckpointVerifiedBlock,
},
BoxError, HashOrHeight,
BoxError, HashOrHeight, SemanticallyVerifiedBlock,
};
#[cfg(test)]
@ -282,7 +281,7 @@ impl ZebraDb {
/// - Propagates any errors from updating history and note commitment trees
pub(in super::super) fn write_block(
&mut self,
finalized: CheckpointVerifiedBlock,
finalized: SemanticallyVerifiedBlock,
history_tree: Arc<HistoryTree>,
note_commitment_trees: NoteCommitmentTrees,
network: Network,
@ -430,7 +429,7 @@ impl DiskWriteBatch {
pub fn prepare_block_batch(
&mut self,
db: &DiskDb,
finalized: CheckpointVerifiedBlock,
finalized: SemanticallyVerifiedBlock,
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>,
@ -439,7 +438,7 @@ impl DiskWriteBatch {
note_commitment_trees: NoteCommitmentTrees,
value_pool: ValueBalance<NonNegative>,
) -> Result<(), BoxError> {
let CheckpointVerifiedBlock {
let SemanticallyVerifiedBlock {
block,
hash,
height,
@ -495,7 +494,7 @@ impl DiskWriteBatch {
pub fn prepare_block_header_and_transaction_data_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
finalized: &SemanticallyVerifiedBlock,
) -> Result<(), BoxError> {
// Blocks
let block_header_by_height = db.cf_handle("block_header_by_height").unwrap();
@ -507,7 +506,7 @@ impl DiskWriteBatch {
let hash_by_tx_loc = db.cf_handle("hash_by_tx_loc").unwrap();
let tx_loc_by_hash = db.cf_handle("tx_loc_by_hash").unwrap();
let CheckpointVerifiedBlock {
let SemanticallyVerifiedBlock {
block,
hash,
height,
@ -554,9 +553,9 @@ impl DiskWriteBatch {
pub fn prepare_genesis_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
finalized: &SemanticallyVerifiedBlock,
) -> bool {
let CheckpointVerifiedBlock { block, .. } = finalized;
let SemanticallyVerifiedBlock { block, .. } = finalized;
if block.header.previous_block_hash == GENESIS_PREVIOUS_BLOCK_HASH {
self.prepare_genesis_note_commitment_tree_batch(db, finalized);

View File

@ -24,9 +24,8 @@ use crate::{
service::finalized_state::{
disk_db::{DiskDb, DiskWriteBatch, ReadDisk, WriteDisk},
zebra_db::ZebraDb,
CheckpointVerifiedBlock,
},
BoxError,
BoxError, SemanticallyVerifiedBlock,
};
impl ZebraDb {
@ -70,12 +69,12 @@ impl DiskWriteBatch {
pub fn prepare_history_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
finalized: &SemanticallyVerifiedBlock,
history_tree: Arc<HistoryTree>,
) -> Result<(), BoxError> {
let history_tree_cf = db.cf_handle("history_tree").unwrap();
let CheckpointVerifiedBlock { height, .. } = finalized;
let SemanticallyVerifiedBlock { height, .. } = finalized;
// Update the tree in state
let current_tip_height = *height - 1;
@ -108,13 +107,13 @@ impl DiskWriteBatch {
pub fn prepare_chain_value_pools_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
finalized: &SemanticallyVerifiedBlock,
utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
value_pool: ValueBalance<NonNegative>,
) -> Result<(), BoxError> {
let tip_chain_value_pool = db.cf_handle("tip_chain_value_pool").unwrap();
let CheckpointVerifiedBlock { block, .. } = finalized;
let SemanticallyVerifiedBlock { block, .. } = finalized;
let new_pool = value_pool.add_block(block.borrow(), &utxos_spent_by_block)?;
self.zs_insert(&tip_chain_value_pool, (), new_pool);

View File

@ -23,9 +23,8 @@ use crate::{
service::finalized_state::{
disk_db::{DiskDb, DiskWriteBatch, ReadDisk, WriteDisk},
zebra_db::ZebraDb,
CheckpointVerifiedBlock,
},
BoxError,
BoxError, SemanticallyVerifiedBlock,
};
impl ZebraDb {
@ -210,9 +209,9 @@ impl DiskWriteBatch {
pub fn prepare_shielded_transaction_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
finalized: &SemanticallyVerifiedBlock,
) -> Result<(), BoxError> {
let CheckpointVerifiedBlock { block, .. } = finalized;
let SemanticallyVerifiedBlock { block, .. } = finalized;
// Index each transaction's shielded data
for transaction in &block.transactions {
@ -265,7 +264,7 @@ impl DiskWriteBatch {
pub fn prepare_note_commitment_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
finalized: &SemanticallyVerifiedBlock,
note_commitment_trees: NoteCommitmentTrees,
history_tree: Arc<HistoryTree>,
) -> Result<(), BoxError> {
@ -277,7 +276,7 @@ impl DiskWriteBatch {
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 CheckpointVerifiedBlock { height, .. } = finalized;
let SemanticallyVerifiedBlock { height, .. } = finalized;
// Use the cached values that were previously calculated in parallel.
let sprout_root = note_commitment_trees.sprout.root();
@ -328,13 +327,13 @@ impl DiskWriteBatch {
pub fn prepare_genesis_note_commitment_tree_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
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 CheckpointVerifiedBlock { height, .. } = finalized;
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)

View File

@ -35,7 +35,7 @@ use crate::{
},
zebra_db::ZebraDb,
},
BoxError, CheckpointVerifiedBlock,
BoxError, SemanticallyVerifiedBlock,
};
impl ZebraDb {
@ -369,13 +369,13 @@ impl DiskWriteBatch {
pub fn prepare_transparent_transaction_batch(
&mut self,
db: &DiskDb,
finalized: &CheckpointVerifiedBlock,
finalized: &SemanticallyVerifiedBlock,
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>,
mut address_balances: HashMap<transparent::Address, AddressBalanceLocation>,
) -> Result<(), BoxError> {
let CheckpointVerifiedBlock { block, height, .. } = finalized;
let SemanticallyVerifiedBlock { block, height, .. } = finalized;
// Update created and spent transparent outputs
self.prepare_new_transparent_outputs_batch(

View File

@ -214,11 +214,11 @@ fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
state.commit_block(child.prepare(), &finalized_state)?;
let finalized_with_trees = state.finalize();
let finalized = finalized_with_trees.checkpoint_verified;
let finalized = finalized_with_trees.block;
assert_eq!(block1, finalized.block);
let finalized_with_trees = state.finalize();
let finalized = finalized_with_trees.checkpoint_verified;
let finalized = finalized_with_trees.block;
assert_eq!(block2, finalized.block);
assert!(state.best_chain().is_none());