3. refactor(state): move database reads and writes to a new zebra_db module (#3579)
* refactor(state): move disk_db reads to a new zebra_db module * refactor(state): make finalized value pool method names consistent * refactor(state): split database writes into the zebra_db module * refactor(state): move the block batch method to DiskWriteBatch * refactor(state): actually add the zebra_db module Unfortunately, I've lost the interim changes to this file, so this commit might be the only one that compiles. * refactor(state): add a newly created file to the cached state CI job
This commit is contained in:
parent
fc7ecfea3b
commit
22b8a6003c
|
@ -158,15 +158,17 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: '2'
|
fetch-depth: '2'
|
||||||
|
|
||||||
|
# only run this job if the database format might have changed
|
||||||
- name: Get specific changed files
|
- name: Get specific changed files
|
||||||
id: changed-files-specific
|
id: changed-files-specific
|
||||||
uses: tj-actions/changed-files@v14.4
|
uses: tj-actions/changed-files@v14.4
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
|
/zebra-state/**/constants.rs
|
||||||
|
/zebra-state/**/finalized_state.rs
|
||||||
/zebra-state/**/disk_format.rs
|
/zebra-state/**/disk_format.rs
|
||||||
/zebra-state/**/disk_db.rs
|
/zebra-state/**/disk_db.rs
|
||||||
/zebra-state/**/finalized_state.rs
|
/zebra-state/**/zebra_db.rs
|
||||||
/zebra-state/**/constants.rs
|
|
||||||
|
|
||||||
- name: Inject slug/short variables
|
- name: Inject slug/short variables
|
||||||
uses: rlespinasse/github-slug-action@v4
|
uses: rlespinasse/github-slug-action@v4
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
//! The primary implementation of the `zebra_state::Service` built upon rocksdb
|
//! The primary implementation of the `zebra_state::Service` built upon rocksdb.
|
||||||
|
//!
|
||||||
|
//! Zebra's database is implemented in 4 layers:
|
||||||
|
//! - [`FinalizedState`]: queues, validates, and commits blocks, using...
|
||||||
|
//! - [`zebra_db`]: reads and writes [`zebra_chain`] types to the database, using...
|
||||||
|
//! - [`disk_db`]: reads and writes format-specific types to the database, using...
|
||||||
|
//! - [`disk_format`]: converts types to raw database bytes.
|
||||||
|
//!
|
||||||
|
//! These layers allow us to split [`zebra_chain`] types for efficient database storage.
|
||||||
|
//! They reduce the risk of data corruption bugs, runtime inconsistencies, and panics.
|
||||||
//!
|
//!
|
||||||
//! # Correctness
|
//! # Correctness
|
||||||
//!
|
//!
|
||||||
|
@ -6,40 +15,26 @@
|
||||||
//! be incremented each time the database format (column, serialization, etc) changes.
|
//! be incremented each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::TryInto,
|
|
||||||
io::{stderr, stdout, Write},
|
io::{stderr, stdout, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::NonNegative,
|
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
history_tree::{HistoryTree, NonEmptyHistoryTree},
|
history_tree::HistoryTree,
|
||||||
orchard,
|
|
||||||
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
||||||
sapling, sprout,
|
|
||||||
transaction::{self, Transaction},
|
|
||||||
transparent,
|
|
||||||
value_balance::ValueBalance,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
service::{
|
service::{check, finalized_state::disk_db::DiskDb, QueuedFinalized},
|
||||||
check,
|
BoxError, Config, FinalizedBlock,
|
||||||
finalized_state::{
|
|
||||||
disk_db::{DiskDb, DiskWriteBatch, ReadDisk, WriteDisk},
|
|
||||||
disk_format::{FromDisk, TransactionLocation},
|
|
||||||
},
|
|
||||||
QueuedFinalized,
|
|
||||||
},
|
|
||||||
BoxError, Config, FinalizedBlock, HashOrHeight,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod disk_db;
|
mod disk_db;
|
||||||
mod disk_format;
|
mod disk_format;
|
||||||
|
mod zebra_db;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
mod arbitrary;
|
mod arbitrary;
|
||||||
|
@ -118,19 +113,29 @@ impl FinalizedState {
|
||||||
new_state
|
new_state
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the process if `block_height` is greater than or equal to the
|
/// Returns the `Path` where the files used by this database are located.
|
||||||
/// configured stop height.
|
#[allow(dead_code)]
|
||||||
fn is_at_stop_height(&self, block_height: block::Height) -> bool {
|
pub fn path(&self) -> &Path {
|
||||||
let debug_stop_at_height = match self.debug_stop_at_height {
|
self.db.path()
|
||||||
Some(debug_stop_at_height) => debug_stop_at_height,
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if block_height < debug_stop_at_height {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
/// Returns the hash of the current finalized tip block.
|
||||||
|
pub fn finalized_tip_hash(&self) -> block::Hash {
|
||||||
|
self.tip()
|
||||||
|
.map(|(_, hash)| hash)
|
||||||
|
// if the state is empty, return the genesis previous block hash
|
||||||
|
.unwrap_or(GENESIS_PREVIOUS_BLOCK_HASH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the height of the current finalized tip block.
|
||||||
|
pub fn finalized_tip_height(&self) -> Option<block::Height> {
|
||||||
|
self.tip().map(|(height, _)| height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the tip block, if there is one.
|
||||||
|
pub fn tip_block(&self) -> Option<Arc<Block>> {
|
||||||
|
let (height, _hash) = self.tip()?;
|
||||||
|
self.block(height.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue a finalized block to be committed to the state.
|
/// Queue a finalized block to be committed to the state.
|
||||||
|
@ -180,319 +185,6 @@ impl FinalizedState {
|
||||||
highest_queue_commit
|
highest_queue_commit
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the hash of the current finalized tip block.
|
|
||||||
pub fn finalized_tip_hash(&self) -> block::Hash {
|
|
||||||
self.tip()
|
|
||||||
.map(|(_, hash)| hash)
|
|
||||||
// if the state is empty, return the genesis previous block hash
|
|
||||||
.unwrap_or(GENESIS_PREVIOUS_BLOCK_HASH)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the height of the current finalized tip block.
|
|
||||||
pub fn finalized_tip_height(&self) -> Option<block::Height> {
|
|
||||||
self.tip().map(|(height, _)| height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Immediately commit `finalized` to the finalized state.
|
|
||||||
///
|
|
||||||
/// This can be called either by the non-finalized state (when finalizing
|
|
||||||
/// a block) or by the checkpoint verifier.
|
|
||||||
///
|
|
||||||
/// Use `source` as the source of the block in log messages.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// - Propagates any errors from writing to the DB
|
|
||||||
/// - Propagates any errors from updating history and note commitment trees
|
|
||||||
/// - If `hashFinalSaplingRoot` / `hashLightClientRoot` / `hashBlockCommitments`
|
|
||||||
/// does not match the expected value
|
|
||||||
pub fn commit_finalized_direct(
|
|
||||||
&mut self,
|
|
||||||
finalized: FinalizedBlock,
|
|
||||||
source: &str,
|
|
||||||
) -> Result<block::Hash, BoxError> {
|
|
||||||
let finalized_tip_height = self.finalized_tip_height();
|
|
||||||
|
|
||||||
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
|
||||||
let height_by_hash = self.db.cf_handle("height_by_hash").unwrap();
|
|
||||||
let block_by_height = self.db.cf_handle("block_by_height").unwrap();
|
|
||||||
let tx_by_hash = self.db.cf_handle("tx_by_hash").unwrap();
|
|
||||||
let utxo_by_outpoint = self.db.cf_handle("utxo_by_outpoint").unwrap();
|
|
||||||
|
|
||||||
let sprout_nullifiers = self.db.cf_handle("sprout_nullifiers").unwrap();
|
|
||||||
let sapling_nullifiers = self.db.cf_handle("sapling_nullifiers").unwrap();
|
|
||||||
let orchard_nullifiers = self.db.cf_handle("orchard_nullifiers").unwrap();
|
|
||||||
|
|
||||||
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
|
||||||
let sapling_anchors = self.db.cf_handle("sapling_anchors").unwrap();
|
|
||||||
let orchard_anchors = self.db.cf_handle("orchard_anchors").unwrap();
|
|
||||||
|
|
||||||
let sprout_note_commitment_tree_cf =
|
|
||||||
self.db.cf_handle("sprout_note_commitment_tree").unwrap();
|
|
||||||
let sapling_note_commitment_tree_cf =
|
|
||||||
self.db.cf_handle("sapling_note_commitment_tree").unwrap();
|
|
||||||
let orchard_note_commitment_tree_cf =
|
|
||||||
self.db.cf_handle("orchard_note_commitment_tree").unwrap();
|
|
||||||
let history_tree_cf = self.db.cf_handle("history_tree").unwrap();
|
|
||||||
|
|
||||||
let tip_chain_value_pool = self.db.cf_handle("tip_chain_value_pool").unwrap();
|
|
||||||
|
|
||||||
// Assert that callers (including unit tests) get the chain order correct
|
|
||||||
if self.db.is_empty(hash_by_height) {
|
|
||||||
assert_eq!(
|
|
||||||
GENESIS_PREVIOUS_BLOCK_HASH, finalized.block.header.previous_block_hash,
|
|
||||||
"the first block added to an empty state must be a genesis block, source: {}",
|
|
||||||
source,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
block::Height(0),
|
|
||||||
finalized.height,
|
|
||||||
"cannot commit genesis: invalid height, source: {}",
|
|
||||||
source,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
assert_eq!(
|
|
||||||
finalized_tip_height.expect("state must have a genesis block committed") + 1,
|
|
||||||
Some(finalized.height),
|
|
||||||
"committed block height must be 1 more than the finalized tip height, source: {}",
|
|
||||||
source,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
self.finalized_tip_hash(),
|
|
||||||
finalized.block.header.previous_block_hash,
|
|
||||||
"committed block must be a child of the finalized tip, source: {}",
|
|
||||||
source,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the current note commitment trees. If there are no blocks in the
|
|
||||||
// state, these will contain the empty trees.
|
|
||||||
let mut sprout_note_commitment_tree = self.sprout_note_commitment_tree();
|
|
||||||
let mut sapling_note_commitment_tree = self.sapling_note_commitment_tree();
|
|
||||||
let mut orchard_note_commitment_tree = self.orchard_note_commitment_tree();
|
|
||||||
let mut history_tree = self.history_tree();
|
|
||||||
|
|
||||||
// Check the block commitment. For Nu5-onward, the block hash commits only
|
|
||||||
// to 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.
|
|
||||||
check::finalized_block_commitment_is_valid_for_chain_history(
|
|
||||||
&finalized,
|
|
||||||
self.network,
|
|
||||||
&history_tree,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let FinalizedBlock {
|
|
||||||
block,
|
|
||||||
hash,
|
|
||||||
height,
|
|
||||||
new_outputs,
|
|
||||||
transaction_hashes,
|
|
||||||
} = finalized;
|
|
||||||
|
|
||||||
// Prepare a batch of DB modifications and return it (without actually writing anything).
|
|
||||||
// We use a closure so we can use an early return for control flow in
|
|
||||||
// the genesis case.
|
|
||||||
// If the closure returns an error it will be propagated and the batch will not be written
|
|
||||||
// to the BD afterwards.
|
|
||||||
let prepare_commit = || -> Result<DiskWriteBatch, BoxError> {
|
|
||||||
let mut batch = DiskWriteBatch::new();
|
|
||||||
|
|
||||||
// Index the block
|
|
||||||
batch.zs_insert(hash_by_height, height, hash);
|
|
||||||
batch.zs_insert(height_by_hash, hash, height);
|
|
||||||
batch.zs_insert(block_by_height, height, &block);
|
|
||||||
|
|
||||||
// # Consensus
|
|
||||||
//
|
|
||||||
// > A transaction MUST NOT spend an output of the genesis block coinbase transaction.
|
|
||||||
// > (There is one such zero-valued output, on each of Testnet and Mainnet.)
|
|
||||||
//
|
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
||||||
if block.header.previous_block_hash == GENESIS_PREVIOUS_BLOCK_HASH {
|
|
||||||
// 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.
|
|
||||||
batch.zs_insert(
|
|
||||||
sprout_note_commitment_tree_cf,
|
|
||||||
height,
|
|
||||||
sprout_note_commitment_tree,
|
|
||||||
);
|
|
||||||
batch.zs_insert(
|
|
||||||
sapling_note_commitment_tree_cf,
|
|
||||||
height,
|
|
||||||
sapling_note_commitment_tree,
|
|
||||||
);
|
|
||||||
batch.zs_insert(
|
|
||||||
orchard_note_commitment_tree_cf,
|
|
||||||
height,
|
|
||||||
orchard_note_commitment_tree,
|
|
||||||
);
|
|
||||||
return Ok(batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index all new transparent outputs
|
|
||||||
for (outpoint, utxo) in new_outputs.borrow().iter() {
|
|
||||||
batch.zs_insert(utxo_by_outpoint, outpoint, utxo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a map for all the utxos spent by the block
|
|
||||||
let mut all_utxos_spent_by_block = HashMap::new();
|
|
||||||
|
|
||||||
// Index each transaction, spent inputs, nullifiers
|
|
||||||
for (transaction_index, (transaction, transaction_hash)) in block
|
|
||||||
.transactions
|
|
||||||
.iter()
|
|
||||||
.zip(transaction_hashes.iter())
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
let transaction_location = TransactionLocation {
|
|
||||||
height,
|
|
||||||
index: transaction_index
|
|
||||||
.try_into()
|
|
||||||
.expect("no more than 4 billion transactions per block"),
|
|
||||||
};
|
|
||||||
batch.zs_insert(tx_by_hash, transaction_hash, transaction_location);
|
|
||||||
|
|
||||||
// Mark all transparent inputs as spent, collect them as well.
|
|
||||||
for input in transaction.inputs() {
|
|
||||||
match input {
|
|
||||||
transparent::Input::PrevOut { outpoint, .. } => {
|
|
||||||
if let Some(utxo) = self.utxo(outpoint) {
|
|
||||||
all_utxos_spent_by_block.insert(*outpoint, utxo);
|
|
||||||
}
|
|
||||||
batch.zs_delete(utxo_by_outpoint, outpoint);
|
|
||||||
}
|
|
||||||
// Coinbase inputs represent new coins,
|
|
||||||
// so there are no UTXOs to mark as spent.
|
|
||||||
transparent::Input::Coinbase { .. } => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark sprout, sapling and orchard nullifiers as spent
|
|
||||||
for sprout_nullifier in transaction.sprout_nullifiers() {
|
|
||||||
batch.zs_insert(sprout_nullifiers, sprout_nullifier, ());
|
|
||||||
}
|
|
||||||
for sapling_nullifier in transaction.sapling_nullifiers() {
|
|
||||||
batch.zs_insert(sapling_nullifiers, sapling_nullifier, ());
|
|
||||||
}
|
|
||||||
for orchard_nullifier in transaction.orchard_nullifiers() {
|
|
||||||
batch.zs_insert(orchard_nullifiers, orchard_nullifier, ());
|
|
||||||
}
|
|
||||||
|
|
||||||
for sprout_note_commitment in transaction.sprout_note_commitments() {
|
|
||||||
sprout_note_commitment_tree.append(*sprout_note_commitment)?;
|
|
||||||
}
|
|
||||||
for sapling_note_commitment in transaction.sapling_note_commitments() {
|
|
||||||
sapling_note_commitment_tree.append(*sapling_note_commitment)?;
|
|
||||||
}
|
|
||||||
for orchard_note_commitment in transaction.orchard_note_commitments() {
|
|
||||||
orchard_note_commitment_tree.append(*orchard_note_commitment)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sprout_root = sprout_note_commitment_tree.root();
|
|
||||||
let sapling_root = sapling_note_commitment_tree.root();
|
|
||||||
let orchard_root = orchard_note_commitment_tree.root();
|
|
||||||
|
|
||||||
history_tree.push(self.network, block.clone(), sapling_root, orchard_root)?;
|
|
||||||
|
|
||||||
// Compute the new anchors and index them
|
|
||||||
// Note: if the root hasn't changed, we write the same value again.
|
|
||||||
batch.zs_insert(sprout_anchors, sprout_root, &sprout_note_commitment_tree);
|
|
||||||
batch.zs_insert(sapling_anchors, sapling_root, ());
|
|
||||||
batch.zs_insert(orchard_anchors, orchard_root, ());
|
|
||||||
|
|
||||||
// Update the trees in state
|
|
||||||
if let Some(h) = finalized_tip_height {
|
|
||||||
batch.zs_delete(sprout_note_commitment_tree_cf, h);
|
|
||||||
batch.zs_delete(sapling_note_commitment_tree_cf, h);
|
|
||||||
batch.zs_delete(orchard_note_commitment_tree_cf, h);
|
|
||||||
batch.zs_delete(history_tree_cf, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.zs_insert(
|
|
||||||
sprout_note_commitment_tree_cf,
|
|
||||||
height,
|
|
||||||
sprout_note_commitment_tree,
|
|
||||||
);
|
|
||||||
|
|
||||||
batch.zs_insert(
|
|
||||||
sapling_note_commitment_tree_cf,
|
|
||||||
height,
|
|
||||||
sapling_note_commitment_tree,
|
|
||||||
);
|
|
||||||
|
|
||||||
batch.zs_insert(
|
|
||||||
orchard_note_commitment_tree_cf,
|
|
||||||
height,
|
|
||||||
orchard_note_commitment_tree,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(history_tree) = history_tree.as_ref() {
|
|
||||||
batch.zs_insert(history_tree_cf, height, history_tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some utxos are spent in the same block so they will be in `new_outputs`.
|
|
||||||
all_utxos_spent_by_block.extend(new_outputs);
|
|
||||||
|
|
||||||
let current_pool = self.current_value_pool();
|
|
||||||
let new_pool = current_pool.add_block(block.borrow(), &all_utxos_spent_by_block)?;
|
|
||||||
batch.zs_insert(tip_chain_value_pool, (), new_pool);
|
|
||||||
|
|
||||||
Ok(batch)
|
|
||||||
};
|
|
||||||
|
|
||||||
// In case of errors, propagate and do not write the batch.
|
|
||||||
let batch = prepare_commit()?;
|
|
||||||
|
|
||||||
// The block has passed contextual validation, so update the metrics
|
|
||||||
block_precommit_metrics(&block, hash, height);
|
|
||||||
|
|
||||||
let result = self.db.write(batch).map(|()| hash);
|
|
||||||
|
|
||||||
tracing::trace!(?source, "committed block from");
|
|
||||||
|
|
||||||
// TODO: move the stop height check to the syncer (#3442)
|
|
||||||
if result.is_ok() && self.is_at_stop_height(height) {
|
|
||||||
tracing::info!(?source, "committed block from");
|
|
||||||
tracing::info!(
|
|
||||||
?height,
|
|
||||||
?hash,
|
|
||||||
"stopping at configured height, flushing database to disk"
|
|
||||||
);
|
|
||||||
|
|
||||||
self.db.shutdown();
|
|
||||||
|
|
||||||
Self::exit_process();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Exit the host process.
|
|
||||||
///
|
|
||||||
/// Designed for debugging and tests.
|
|
||||||
///
|
|
||||||
/// TODO: move the stop height check to the syncer (#3442)
|
|
||||||
fn exit_process() -> ! {
|
|
||||||
tracing::info!("exiting Zebra");
|
|
||||||
|
|
||||||
// Some OSes require a flush to send all output to the terminal.
|
|
||||||
// Zebra's logging doesn't depend on `tokio`, so we flush the stdlib sync streams.
|
|
||||||
//
|
|
||||||
// TODO: if this doesn't work, send an empty line as well.
|
|
||||||
let _ = stdout().lock().flush();
|
|
||||||
let _ = stderr().lock().flush();
|
|
||||||
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commit a finalized block to the state.
|
/// Commit a finalized block to the state.
|
||||||
///
|
///
|
||||||
/// It's the caller's responsibility to ensure that blocks are committed in
|
/// It's the caller's responsibility to ensure that blocks are committed in
|
||||||
|
@ -532,267 +224,171 @@ impl FinalizedState {
|
||||||
block_result
|
block_result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the tip height and hash if there is one.
|
/// Immediately commit a `finalized` block to the finalized state.
|
||||||
pub fn tip(&self) -> Option<(block::Height, block::Hash)> {
|
|
||||||
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
|
||||||
self.db
|
|
||||||
.reverse_iterator(hash_by_height)
|
|
||||||
.next()
|
|
||||||
.map(|(height_bytes, hash_bytes)| {
|
|
||||||
let height = block::Height::from_bytes(height_bytes);
|
|
||||||
let hash = block::Hash::from_bytes(hash_bytes);
|
|
||||||
|
|
||||||
(height, hash)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the tip block, if there is one.
|
|
||||||
pub fn tip_block(&self) -> Option<Arc<Block>> {
|
|
||||||
let (height, _hash) = self.tip()?;
|
|
||||||
self.block(height.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the height of the given block if it exists.
|
|
||||||
pub fn height(&self, hash: block::Hash) -> Option<block::Height> {
|
|
||||||
let height_by_hash = self.db.cf_handle("height_by_hash").unwrap();
|
|
||||||
self.db.zs_get(height_by_hash, &hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the given block if it exists.
|
|
||||||
pub fn block(&self, hash_or_height: HashOrHeight) -> Option<Arc<Block>> {
|
|
||||||
let height_by_hash = self.db.cf_handle("height_by_hash").unwrap();
|
|
||||||
let block_by_height = self.db.cf_handle("block_by_height").unwrap();
|
|
||||||
let height = hash_or_height.height_or_else(|hash| self.db.zs_get(height_by_hash, &hash))?;
|
|
||||||
|
|
||||||
self.db.zs_get(block_by_height, &height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the `transparent::Output` pointed to by the given
|
|
||||||
/// `transparent::OutPoint` if it is present.
|
|
||||||
pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
|
|
||||||
let utxo_by_outpoint = self.db.cf_handle("utxo_by_outpoint").unwrap();
|
|
||||||
self.db.zs_get(utxo_by_outpoint, outpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the finalized state contains `sprout_nullifier`.
|
|
||||||
pub fn contains_sprout_nullifier(&self, sprout_nullifier: &sprout::Nullifier) -> bool {
|
|
||||||
let sprout_nullifiers = self.db.cf_handle("sprout_nullifiers").unwrap();
|
|
||||||
self.db.zs_contains(sprout_nullifiers, &sprout_nullifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the finalized state contains `sapling_nullifier`.
|
|
||||||
pub fn contains_sapling_nullifier(&self, sapling_nullifier: &sapling::Nullifier) -> bool {
|
|
||||||
let sapling_nullifiers = self.db.cf_handle("sapling_nullifiers").unwrap();
|
|
||||||
self.db.zs_contains(sapling_nullifiers, &sapling_nullifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the finalized state contains `orchard_nullifier`.
|
|
||||||
pub fn contains_orchard_nullifier(&self, orchard_nullifier: &orchard::Nullifier) -> bool {
|
|
||||||
let orchard_nullifiers = self.db.cf_handle("orchard_nullifiers").unwrap();
|
|
||||||
self.db.zs_contains(orchard_nullifiers, &orchard_nullifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the finalized state contains `sprout_anchor`.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
|
|
||||||
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
|
||||||
self.db.zs_contains(sprout_anchors, &sprout_anchor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the finalized state contains `sapling_anchor`.
|
|
||||||
pub fn contains_sapling_anchor(&self, sapling_anchor: &sapling::tree::Root) -> bool {
|
|
||||||
let sapling_anchors = self.db.cf_handle("sapling_anchors").unwrap();
|
|
||||||
self.db.zs_contains(sapling_anchors, &sapling_anchor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the finalized state contains `orchard_anchor`.
|
|
||||||
pub fn contains_orchard_anchor(&self, orchard_anchor: &orchard::tree::Root) -> bool {
|
|
||||||
let orchard_anchors = self.db.cf_handle("orchard_anchors").unwrap();
|
|
||||||
self.db.zs_contains(orchard_anchors, &orchard_anchor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the finalized hash for a given `block::Height` if it is present.
|
|
||||||
pub fn hash(&self, height: block::Height) -> Option<block::Hash> {
|
|
||||||
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
|
||||||
self.db.zs_get(hash_by_height, &height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the given transaction if it exists.
|
|
||||||
pub fn transaction(&self, hash: transaction::Hash) -> Option<Arc<Transaction>> {
|
|
||||||
let tx_by_hash = self.db.cf_handle("tx_by_hash").unwrap();
|
|
||||||
self.db
|
|
||||||
.zs_get(tx_by_hash, &hash)
|
|
||||||
.map(|TransactionLocation { index, height }| {
|
|
||||||
let block = self
|
|
||||||
.block(height.into())
|
|
||||||
.expect("block will exist if TransactionLocation does");
|
|
||||||
|
|
||||||
block.transactions[index as usize].clone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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) -> sprout::tree::NoteCommitmentTree {
|
|
||||||
let height = match self.finalized_tip_height() {
|
|
||||||
Some(h) => h,
|
|
||||||
None => return Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let sprout_note_commitment_tree = self.db.cf_handle("sprout_note_commitment_tree").unwrap();
|
|
||||||
|
|
||||||
self.db
|
|
||||||
.zs_get(sprout_note_commitment_tree, &height)
|
|
||||||
.expect("Sprout note commitment tree must exist if there is a finalized tip")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the Sprout note commitment tree matching the given anchor.
|
|
||||||
///
|
///
|
||||||
/// This is used for interstitial tree building, which is unique to Sprout.
|
/// This can be called either by the non-finalized state (when finalizing
|
||||||
pub fn sprout_note_commitment_tree_by_anchor(
|
/// a block) or by the checkpoint verifier.
|
||||||
&self,
|
///
|
||||||
sprout_anchor: &sprout::tree::Root,
|
/// Use `source` as the source of the block in log messages.
|
||||||
) -> Option<sprout::tree::NoteCommitmentTree> {
|
///
|
||||||
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// - Propagates any errors from writing to the DB
|
||||||
|
/// - Propagates any errors from updating history and note commitment trees
|
||||||
|
/// - If `hashFinalSaplingRoot` / `hashLightClientRoot` / `hashBlockCommitments`
|
||||||
|
/// does not match the expected value
|
||||||
|
pub fn commit_finalized_direct(
|
||||||
|
&mut self,
|
||||||
|
finalized: FinalizedBlock,
|
||||||
|
source: &str,
|
||||||
|
) -> Result<block::Hash, BoxError> {
|
||||||
|
let committed_tip_hash = self.finalized_tip_hash();
|
||||||
|
let committed_tip_height = self.finalized_tip_height();
|
||||||
|
|
||||||
self.db.zs_get(sprout_anchors, sprout_anchor)
|
// Assert that callers (including unit tests) get the chain order correct
|
||||||
}
|
if self.is_empty() {
|
||||||
|
assert_eq!(
|
||||||
/// Returns the Sapling note commitment tree of the finalized tip
|
committed_tip_hash, finalized.block.header.previous_block_hash,
|
||||||
/// or the empty tree if the state is empty.
|
"the first block added to an empty state must be a genesis block, source: {}",
|
||||||
pub fn sapling_note_commitment_tree(&self) -> sapling::tree::NoteCommitmentTree {
|
source,
|
||||||
let height = match self.finalized_tip_height() {
|
);
|
||||||
Some(h) => h,
|
assert_eq!(
|
||||||
None => return Default::default(),
|
block::Height(0),
|
||||||
};
|
finalized.height,
|
||||||
|
"cannot commit genesis: invalid height, source: {}",
|
||||||
let sapling_note_commitment_tree =
|
source,
|
||||||
self.db.cf_handle("sapling_note_commitment_tree").unwrap();
|
);
|
||||||
|
|
||||||
self.db
|
|
||||||
.zs_get(sapling_note_commitment_tree, &height)
|
|
||||||
.expect("Sapling note commitment tree must exist if there is a finalized tip")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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) -> orchard::tree::NoteCommitmentTree {
|
|
||||||
let height = match self.finalized_tip_height() {
|
|
||||||
Some(h) => h,
|
|
||||||
None => return Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let orchard_note_commitment_tree =
|
|
||||||
self.db.cf_handle("orchard_note_commitment_tree").unwrap();
|
|
||||||
|
|
||||||
self.db
|
|
||||||
.zs_get(orchard_note_commitment_tree, &height)
|
|
||||||
.expect("Orchard note commitment tree must exist if there is a finalized tip")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the ZIP-221 history tree of the finalized tip or `None`
|
|
||||||
/// if it does not exist yet in the state (pre-Heartwood).
|
|
||||||
pub fn history_tree(&self) -> HistoryTree {
|
|
||||||
match self.finalized_tip_height() {
|
|
||||||
Some(height) => {
|
|
||||||
let history_tree_cf = self.db.cf_handle("history_tree").unwrap();
|
|
||||||
let history_tree: Option<NonEmptyHistoryTree> =
|
|
||||||
self.db.zs_get(history_tree_cf, &height);
|
|
||||||
if let Some(non_empty_tree) = history_tree {
|
|
||||||
HistoryTree::from(non_empty_tree)
|
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
assert_eq!(
|
||||||
}
|
committed_tip_height.expect("state must have a genesis block committed") + 1,
|
||||||
}
|
Some(finalized.height),
|
||||||
None => Default::default(),
|
"committed block height must be 1 more than the finalized tip height, source: {}",
|
||||||
}
|
source,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
committed_tip_hash, finalized.block.header.previous_block_hash,
|
||||||
|
"committed block must be a child of the finalized tip, source: {}",
|
||||||
|
source,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `Path` where the files used by this database are located.
|
// Check the block commitment. For Nu5-onward, the block hash commits only
|
||||||
#[allow(dead_code)]
|
// to non-authorizing data (see ZIP-244). This checks the authorizing data
|
||||||
pub fn path(&self) -> &Path {
|
// commitment, making sure the entire block contents were committed to.
|
||||||
self.db.path()
|
// 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.
|
||||||
|
let history_tree = self.history_tree();
|
||||||
|
check::finalized_block_commitment_is_valid_for_chain_history(
|
||||||
|
&finalized,
|
||||||
|
self.network,
|
||||||
|
&history_tree,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let finalized_height = finalized.height;
|
||||||
|
let finalized_hash = finalized.hash;
|
||||||
|
|
||||||
|
let result = self.write_block(finalized, history_tree, source);
|
||||||
|
|
||||||
|
// TODO: move the stop height check to the syncer (#3442)
|
||||||
|
if result.is_ok() && self.is_at_stop_height(finalized_height) {
|
||||||
|
tracing::info!(
|
||||||
|
height = ?finalized_height,
|
||||||
|
hash = ?finalized_hash,
|
||||||
|
block_source = ?source,
|
||||||
|
"stopping at configured height, flushing database to disk"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.db.shutdown();
|
||||||
|
|
||||||
|
Self::exit_process();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the stored `ValueBalance` for the best chain at the finalized tip height.
|
result
|
||||||
pub fn current_value_pool(&self) -> ValueBalance<NonNegative> {
|
|
||||||
let value_pool_cf = self.db.cf_handle("tip_chain_value_pool").unwrap();
|
|
||||||
self.db
|
|
||||||
.zs_get(value_pool_cf, &())
|
|
||||||
.unwrap_or_else(ValueBalance::zero)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_precommit_metrics(block: &Block, hash: block::Hash, height: block::Height) {
|
/// Write `finalized` to the finalized state.
|
||||||
let transaction_count = block.transactions.len();
|
///
|
||||||
let transparent_prevout_count = block
|
/// Uses:
|
||||||
|
/// - `history_tree`: the current tip's history tree
|
||||||
|
/// - `source`: the source of the block in log messages
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// - Propagates any errors from writing to the DB
|
||||||
|
/// - Propagates any errors from updating history and note commitment trees
|
||||||
|
fn write_block(
|
||||||
|
&mut self,
|
||||||
|
finalized: FinalizedBlock,
|
||||||
|
history_tree: HistoryTree,
|
||||||
|
source: &str,
|
||||||
|
) -> Result<block::Hash, BoxError> {
|
||||||
|
let finalized_hash = finalized.hash;
|
||||||
|
|
||||||
|
let all_utxos_spent_by_block = finalized
|
||||||
|
.block
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|t| t.inputs().iter())
|
.flat_map(|tx| tx.inputs().iter())
|
||||||
.count()
|
.flat_map(|input| input.outpoint())
|
||||||
// Each block has a single coinbase input which is not a previous output.
|
.flat_map(|outpoint| self.utxo(&outpoint).map(|utxo| (outpoint, utxo)))
|
||||||
- 1;
|
.collect();
|
||||||
let transparent_newout_count = block
|
|
||||||
.transactions
|
|
||||||
.iter()
|
|
||||||
.flat_map(|t| t.outputs().iter())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let sprout_nullifier_count = block
|
let batch = disk_db::DiskWriteBatch::new();
|
||||||
.transactions
|
|
||||||
.iter()
|
|
||||||
.flat_map(|t| t.sprout_nullifiers())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let sapling_nullifier_count = block
|
// In case of errors, propagate and do not write the batch.
|
||||||
.transactions
|
let batch = batch.prepare_block_batch(
|
||||||
.iter()
|
&self.db,
|
||||||
.flat_map(|t| t.sapling_nullifiers())
|
finalized,
|
||||||
.count();
|
self.network,
|
||||||
|
self.finalized_tip_height(),
|
||||||
|
all_utxos_spent_by_block,
|
||||||
|
self.sprout_note_commitment_tree(),
|
||||||
|
self.sapling_note_commitment_tree(),
|
||||||
|
self.orchard_note_commitment_tree(),
|
||||||
|
history_tree,
|
||||||
|
self.finalized_value_pool(),
|
||||||
|
)?;
|
||||||
|
|
||||||
let orchard_nullifier_count = block
|
self.db.write(batch)?;
|
||||||
.transactions
|
|
||||||
.iter()
|
|
||||||
.flat_map(|t| t.orchard_nullifiers())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::trace!(?source, "committed block from");
|
||||||
?hash,
|
|
||||||
?height,
|
|
||||||
transaction_count,
|
|
||||||
transparent_prevout_count,
|
|
||||||
transparent_newout_count,
|
|
||||||
sprout_nullifier_count,
|
|
||||||
sapling_nullifier_count,
|
|
||||||
orchard_nullifier_count,
|
|
||||||
"preparing to commit finalized block"
|
|
||||||
);
|
|
||||||
|
|
||||||
metrics::counter!("state.finalized.block.count", 1);
|
Ok(finalized_hash)
|
||||||
metrics::gauge!("state.finalized.block.height", height.0 as _);
|
}
|
||||||
|
|
||||||
metrics::counter!(
|
/// Stop the process if `block_height` is greater than or equal to the
|
||||||
"state.finalized.cumulative.transactions",
|
/// configured stop height.
|
||||||
transaction_count as u64
|
fn is_at_stop_height(&self, block_height: block::Height) -> bool {
|
||||||
);
|
let debug_stop_at_height = match self.debug_stop_at_height {
|
||||||
metrics::counter!(
|
Some(debug_stop_at_height) => debug_stop_at_height,
|
||||||
"state.finalized.cumulative.transparent_prevouts",
|
None => return false,
|
||||||
transparent_prevout_count as u64
|
};
|
||||||
);
|
|
||||||
metrics::counter!(
|
if block_height < debug_stop_at_height {
|
||||||
"state.finalized.cumulative.transparent_newouts",
|
return false;
|
||||||
transparent_newout_count as u64
|
}
|
||||||
);
|
|
||||||
metrics::counter!(
|
true
|
||||||
"state.finalized.cumulative.sprout_nullifiers",
|
}
|
||||||
sprout_nullifier_count as u64
|
|
||||||
);
|
/// Exit the host process.
|
||||||
metrics::counter!(
|
///
|
||||||
"state.finalized.cumulative.sapling_nullifiers",
|
/// Designed for debugging and tests.
|
||||||
sapling_nullifier_count as u64
|
///
|
||||||
);
|
/// TODO: move the stop height check to the syncer (#3442)
|
||||||
metrics::counter!(
|
fn exit_process() -> ! {
|
||||||
"state.finalized.cumulative.orchard_nullifiers",
|
tracing::info!("exiting Zebra");
|
||||||
orchard_nullifier_count as u64
|
|
||||||
);
|
// Some OSes require a flush to send all output to the terminal.
|
||||||
|
// Zebra's logging doesn't depend on `tokio`, so we flush the stdlib sync streams.
|
||||||
|
//
|
||||||
|
// TODO: if this doesn't work, send an empty line as well.
|
||||||
|
let _ = stdout().lock().flush();
|
||||||
|
let _ = stderr().lock().flush();
|
||||||
|
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ where
|
||||||
|
|
||||||
impl FinalizedState {
|
impl FinalizedState {
|
||||||
/// Allow to set up a fake value pool in the database for testing purposes.
|
/// Allow to set up a fake value pool in the database for testing purposes.
|
||||||
pub fn set_current_value_pool(&self, fake_value_pool: ValueBalance<NonNegative>) {
|
pub fn set_finalized_value_pool(&self, fake_value_pool: ValueBalance<NonNegative>) {
|
||||||
let mut batch = DiskWriteBatch::new();
|
let mut batch = DiskWriteBatch::new();
|
||||||
let value_pool_cf = self.db.cf_handle("tip_chain_value_pool").unwrap();
|
let value_pool_cf = self.db.cf_handle("tip_chain_value_pool").unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Module defining access to RocksDB via accessor traits.
|
//! Provides low-level access to RocksDB using some database-specific types.
|
||||||
//!
|
//!
|
||||||
//! This module makes sure that:
|
//! This module makes sure that:
|
||||||
//! - all disk writes happen inside a RocksDB transaction, and
|
//! - all disk writes happen inside a RocksDB transaction ([`WriteBatch`]), and
|
||||||
//! - format-specific invariants are maintained.
|
//! - format-specific invariants are maintained.
|
||||||
//!
|
//!
|
||||||
//! # Correctness
|
//! # Correctness
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Module defining the serialization format for finalized data.
|
//! Serialization formats for finalized data.
|
||||||
//!
|
//!
|
||||||
//! # Correctness
|
//! # Correctness
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -0,0 +1,509 @@
|
||||||
|
//! Provides high-level access to the database using [`zebra_chain`] types.
|
||||||
|
//!
|
||||||
|
//! This module makes sure that:
|
||||||
|
//! - all disk writes happen inside a RocksDB transaction, and
|
||||||
|
//! - format-specific invariants are maintained.
|
||||||
|
//!
|
||||||
|
//! # Correctness
|
||||||
|
//!
|
||||||
|
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
|
||||||
|
//! be incremented each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
|
use std::{borrow::Borrow, collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
use zebra_chain::{
|
||||||
|
amount::NonNegative,
|
||||||
|
block::{self, Block, Height},
|
||||||
|
history_tree::{HistoryTree, NonEmptyHistoryTree},
|
||||||
|
orchard,
|
||||||
|
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
||||||
|
sapling, sprout,
|
||||||
|
transaction::{self, Transaction},
|
||||||
|
transparent,
|
||||||
|
value_balance::ValueBalance,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
service::finalized_state::{
|
||||||
|
disk_db::{DiskDb, ReadDisk, WriteDisk},
|
||||||
|
disk_format::{FromDisk, TransactionLocation},
|
||||||
|
FinalizedBlock, FinalizedState,
|
||||||
|
},
|
||||||
|
BoxError, HashOrHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::disk_db::DiskWriteBatch;
|
||||||
|
|
||||||
|
impl FinalizedState {
|
||||||
|
// Read block methods
|
||||||
|
|
||||||
|
/// Returns true if the database is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
||||||
|
self.db.is_empty(hash_by_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the tip height and hash, if there is one.
|
||||||
|
pub fn tip(&self) -> Option<(block::Height, block::Hash)> {
|
||||||
|
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
||||||
|
self.db
|
||||||
|
.reverse_iterator(hash_by_height)
|
||||||
|
.next()
|
||||||
|
.map(|(height_bytes, hash_bytes)| {
|
||||||
|
let height = block::Height::from_bytes(height_bytes);
|
||||||
|
let hash = block::Hash::from_bytes(hash_bytes);
|
||||||
|
|
||||||
|
(height, hash)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the finalized hash for a given `block::Height` if it is present.
|
||||||
|
pub fn hash(&self, height: block::Height) -> Option<block::Hash> {
|
||||||
|
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
||||||
|
self.db.zs_get(hash_by_height, &height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the height of the given block if it exists.
|
||||||
|
pub fn height(&self, hash: block::Hash) -> Option<block::Height> {
|
||||||
|
let height_by_hash = self.db.cf_handle("height_by_hash").unwrap();
|
||||||
|
self.db.zs_get(height_by_hash, &hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the given block if it exists.
|
||||||
|
pub fn block(&self, hash_or_height: HashOrHeight) -> Option<Arc<Block>> {
|
||||||
|
let height_by_hash = self.db.cf_handle("height_by_hash").unwrap();
|
||||||
|
let block_by_height = self.db.cf_handle("block_by_height").unwrap();
|
||||||
|
let height = hash_or_height.height_or_else(|hash| self.db.zs_get(height_by_hash, &hash))?;
|
||||||
|
|
||||||
|
self.db.zs_get(block_by_height, &height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read transaction methods
|
||||||
|
|
||||||
|
/// Returns the given transaction if it exists.
|
||||||
|
pub fn transaction(&self, hash: transaction::Hash) -> Option<Arc<Transaction>> {
|
||||||
|
let tx_by_hash = self.db.cf_handle("tx_by_hash").unwrap();
|
||||||
|
self.db
|
||||||
|
.zs_get(tx_by_hash, &hash)
|
||||||
|
.map(|TransactionLocation { index, height }| {
|
||||||
|
let block = self
|
||||||
|
.block(height.into())
|
||||||
|
.expect("block will exist if TransactionLocation does");
|
||||||
|
|
||||||
|
block.transactions[index as usize].clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read transparent methods
|
||||||
|
|
||||||
|
/// Returns the `transparent::Output` pointed to by the given
|
||||||
|
/// `transparent::OutPoint` if it is present.
|
||||||
|
pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
|
||||||
|
let utxo_by_outpoint = self.db.cf_handle("utxo_by_outpoint").unwrap();
|
||||||
|
self.db.zs_get(utxo_by_outpoint, outpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read shielded methods
|
||||||
|
|
||||||
|
/// Returns `true` if the finalized state contains `sprout_nullifier`.
|
||||||
|
pub fn contains_sprout_nullifier(&self, sprout_nullifier: &sprout::Nullifier) -> bool {
|
||||||
|
let sprout_nullifiers = self.db.cf_handle("sprout_nullifiers").unwrap();
|
||||||
|
self.db.zs_contains(sprout_nullifiers, &sprout_nullifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the finalized state contains `sapling_nullifier`.
|
||||||
|
pub fn contains_sapling_nullifier(&self, sapling_nullifier: &sapling::Nullifier) -> bool {
|
||||||
|
let sapling_nullifiers = self.db.cf_handle("sapling_nullifiers").unwrap();
|
||||||
|
self.db.zs_contains(sapling_nullifiers, &sapling_nullifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the finalized state contains `orchard_nullifier`.
|
||||||
|
pub fn contains_orchard_nullifier(&self, orchard_nullifier: &orchard::Nullifier) -> bool {
|
||||||
|
let orchard_nullifiers = self.db.cf_handle("orchard_nullifiers").unwrap();
|
||||||
|
self.db.zs_contains(orchard_nullifiers, &orchard_nullifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the finalized state contains `sprout_anchor`.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
|
||||||
|
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||||
|
self.db.zs_contains(sprout_anchors, &sprout_anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the finalized state contains `sapling_anchor`.
|
||||||
|
pub fn contains_sapling_anchor(&self, sapling_anchor: &sapling::tree::Root) -> bool {
|
||||||
|
let sapling_anchors = self.db.cf_handle("sapling_anchors").unwrap();
|
||||||
|
self.db.zs_contains(sapling_anchors, &sapling_anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the finalized state contains `orchard_anchor`.
|
||||||
|
pub fn contains_orchard_anchor(&self, orchard_anchor: &orchard::tree::Root) -> bool {
|
||||||
|
let orchard_anchors = self.db.cf_handle("orchard_anchors").unwrap();
|
||||||
|
self.db.zs_contains(orchard_anchors, &orchard_anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) -> sprout::tree::NoteCommitmentTree {
|
||||||
|
let height = match self.finalized_tip_height() {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sprout_note_commitment_tree = self.db.cf_handle("sprout_note_commitment_tree").unwrap();
|
||||||
|
|
||||||
|
self.db
|
||||||
|
.zs_get(sprout_note_commitment_tree, &height)
|
||||||
|
.expect("Sprout note commitment tree must exist if there is a finalized tip")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the Sprout note commitment tree matching the given anchor.
|
||||||
|
///
|
||||||
|
/// This is used for interstitial tree building, which is unique to Sprout.
|
||||||
|
pub fn sprout_note_commitment_tree_by_anchor(
|
||||||
|
&self,
|
||||||
|
sprout_anchor: &sprout::tree::Root,
|
||||||
|
) -> Option<sprout::tree::NoteCommitmentTree> {
|
||||||
|
let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
|
||||||
|
|
||||||
|
self.db.zs_get(sprout_anchors, sprout_anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) -> sapling::tree::NoteCommitmentTree {
|
||||||
|
let height = match self.finalized_tip_height() {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sapling_note_commitment_tree =
|
||||||
|
self.db.cf_handle("sapling_note_commitment_tree").unwrap();
|
||||||
|
|
||||||
|
self.db
|
||||||
|
.zs_get(sapling_note_commitment_tree, &height)
|
||||||
|
.expect("Sapling note commitment tree must exist if there is a finalized tip")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) -> orchard::tree::NoteCommitmentTree {
|
||||||
|
let height = match self.finalized_tip_height() {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let orchard_note_commitment_tree =
|
||||||
|
self.db.cf_handle("orchard_note_commitment_tree").unwrap();
|
||||||
|
|
||||||
|
self.db
|
||||||
|
.zs_get(orchard_note_commitment_tree, &height)
|
||||||
|
.expect("Orchard note commitment tree must exist if there is a finalized tip")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read chain methods
|
||||||
|
|
||||||
|
/// Returns the ZIP-221 history tree of the finalized tip or `None`
|
||||||
|
/// if it does not exist yet in the state (pre-Heartwood).
|
||||||
|
pub fn history_tree(&self) -> HistoryTree {
|
||||||
|
match self.finalized_tip_height() {
|
||||||
|
Some(height) => {
|
||||||
|
let history_tree_cf = self.db.cf_handle("history_tree").unwrap();
|
||||||
|
let history_tree: Option<NonEmptyHistoryTree> =
|
||||||
|
self.db.zs_get(history_tree_cf, &height);
|
||||||
|
if let Some(non_empty_tree) = history_tree {
|
||||||
|
HistoryTree::from(non_empty_tree)
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the stored `ValueBalance` for the best chain at the finalized tip height.
|
||||||
|
pub fn finalized_value_pool(&self) -> ValueBalance<NonNegative> {
|
||||||
|
let value_pool_cf = self.db.cf_handle("tip_chain_value_pool").unwrap();
|
||||||
|
self.db
|
||||||
|
.zs_get(value_pool_cf, &())
|
||||||
|
.unwrap_or_else(ValueBalance::zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics methods
|
||||||
|
|
||||||
|
/// Update metrics before committing a block.
|
||||||
|
fn block_precommit_metrics(block: &Block, hash: block::Hash, height: block::Height) {
|
||||||
|
let transaction_count = block.transactions.len();
|
||||||
|
let transparent_prevout_count = block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|t| t.inputs().iter())
|
||||||
|
.count()
|
||||||
|
// Each block has a single coinbase input which is not a previous output.
|
||||||
|
- 1;
|
||||||
|
let transparent_newout_count = block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|t| t.outputs().iter())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let sprout_nullifier_count = block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|t| t.sprout_nullifiers())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let sapling_nullifier_count = block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|t| t.sapling_nullifiers())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let orchard_nullifier_count = block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|t| t.orchard_nullifiers())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
?hash,
|
||||||
|
?height,
|
||||||
|
transaction_count,
|
||||||
|
transparent_prevout_count,
|
||||||
|
transparent_newout_count,
|
||||||
|
sprout_nullifier_count,
|
||||||
|
sapling_nullifier_count,
|
||||||
|
orchard_nullifier_count,
|
||||||
|
"preparing to commit finalized block"
|
||||||
|
);
|
||||||
|
|
||||||
|
metrics::counter!("state.finalized.block.count", 1);
|
||||||
|
metrics::gauge!("state.finalized.block.height", height.0 as _);
|
||||||
|
|
||||||
|
metrics::counter!(
|
||||||
|
"state.finalized.cumulative.transactions",
|
||||||
|
transaction_count as u64
|
||||||
|
);
|
||||||
|
metrics::counter!(
|
||||||
|
"state.finalized.cumulative.transparent_prevouts",
|
||||||
|
transparent_prevout_count as u64
|
||||||
|
);
|
||||||
|
metrics::counter!(
|
||||||
|
"state.finalized.cumulative.transparent_newouts",
|
||||||
|
transparent_newout_count as u64
|
||||||
|
);
|
||||||
|
metrics::counter!(
|
||||||
|
"state.finalized.cumulative.sprout_nullifiers",
|
||||||
|
sprout_nullifier_count as u64
|
||||||
|
);
|
||||||
|
metrics::counter!(
|
||||||
|
"state.finalized.cumulative.sapling_nullifiers",
|
||||||
|
sapling_nullifier_count as u64
|
||||||
|
);
|
||||||
|
metrics::counter!(
|
||||||
|
"state.finalized.cumulative.orchard_nullifiers",
|
||||||
|
orchard_nullifier_count as u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write methods
|
||||||
|
|
||||||
|
impl DiskWriteBatch {
|
||||||
|
/// Prepare a database batch containing a `finalized` block,
|
||||||
|
/// and return it (without actually writing anything).
|
||||||
|
///
|
||||||
|
/// If this method returns an error, it will be propagated,
|
||||||
|
/// and the batch will not be written to the database.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// - Propagates any errors from writing to the DB
|
||||||
|
/// - Propagates any errors from updating history and note commitment trees
|
||||||
|
///
|
||||||
|
/// TODO: split up this function in the next PR.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn prepare_block_batch(
|
||||||
|
mut self,
|
||||||
|
db: &DiskDb,
|
||||||
|
finalized: FinalizedBlock,
|
||||||
|
network: Network,
|
||||||
|
current_tip_height: Option<Height>,
|
||||||
|
mut all_utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
|
||||||
|
mut sprout_note_commitment_tree: sprout::tree::NoteCommitmentTree,
|
||||||
|
mut sapling_note_commitment_tree: sapling::tree::NoteCommitmentTree,
|
||||||
|
mut orchard_note_commitment_tree: orchard::tree::NoteCommitmentTree,
|
||||||
|
mut history_tree: HistoryTree,
|
||||||
|
current_value_pool: ValueBalance<NonNegative>,
|
||||||
|
) -> Result<DiskWriteBatch, BoxError> {
|
||||||
|
let hash_by_height = db.cf_handle("hash_by_height").unwrap();
|
||||||
|
let height_by_hash = db.cf_handle("height_by_hash").unwrap();
|
||||||
|
let block_by_height = db.cf_handle("block_by_height").unwrap();
|
||||||
|
let tx_by_hash = db.cf_handle("tx_by_hash").unwrap();
|
||||||
|
let utxo_by_outpoint = db.cf_handle("utxo_by_outpoint").unwrap();
|
||||||
|
|
||||||
|
let sprout_nullifiers = db.cf_handle("sprout_nullifiers").unwrap();
|
||||||
|
let sapling_nullifiers = db.cf_handle("sapling_nullifiers").unwrap();
|
||||||
|
let orchard_nullifiers = db.cf_handle("orchard_nullifiers").unwrap();
|
||||||
|
|
||||||
|
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 history_tree_cf = db.cf_handle("history_tree").unwrap();
|
||||||
|
|
||||||
|
let tip_chain_value_pool = db.cf_handle("tip_chain_value_pool").unwrap();
|
||||||
|
|
||||||
|
let FinalizedBlock {
|
||||||
|
block,
|
||||||
|
hash,
|
||||||
|
height,
|
||||||
|
new_outputs,
|
||||||
|
transaction_hashes,
|
||||||
|
} = finalized;
|
||||||
|
|
||||||
|
// The block has passed contextual validation, so update the metrics
|
||||||
|
FinalizedState::block_precommit_metrics(&block, hash, height);
|
||||||
|
|
||||||
|
// Index the block
|
||||||
|
self.zs_insert(hash_by_height, height, hash);
|
||||||
|
self.zs_insert(height_by_hash, hash, height);
|
||||||
|
self.zs_insert(block_by_height, height, &block);
|
||||||
|
|
||||||
|
// # Consensus
|
||||||
|
//
|
||||||
|
// > A transaction MUST NOT spend an output of the genesis block coinbase transaction.
|
||||||
|
// > (There is one such zero-valued output, on each of Testnet and Mainnet.)
|
||||||
|
//
|
||||||
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
|
if block.header.previous_block_hash == GENESIS_PREVIOUS_BLOCK_HASH {
|
||||||
|
// 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_note_commitment_tree,
|
||||||
|
);
|
||||||
|
self.zs_insert(
|
||||||
|
sapling_note_commitment_tree_cf,
|
||||||
|
height,
|
||||||
|
sapling_note_commitment_tree,
|
||||||
|
);
|
||||||
|
self.zs_insert(
|
||||||
|
orchard_note_commitment_tree_cf,
|
||||||
|
height,
|
||||||
|
orchard_note_commitment_tree,
|
||||||
|
);
|
||||||
|
return Ok(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index all new transparent outputs
|
||||||
|
for (outpoint, utxo) in new_outputs.borrow().iter() {
|
||||||
|
self.zs_insert(utxo_by_outpoint, outpoint, utxo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index each transaction, spent inputs, nullifiers
|
||||||
|
for (transaction_index, (transaction, transaction_hash)) in block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.zip(transaction_hashes.iter())
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let transaction_location = TransactionLocation {
|
||||||
|
height,
|
||||||
|
index: transaction_index
|
||||||
|
.try_into()
|
||||||
|
.expect("no more than 4 billion transactions per block"),
|
||||||
|
};
|
||||||
|
self.zs_insert(tx_by_hash, transaction_hash, transaction_location);
|
||||||
|
|
||||||
|
// Mark all transparent inputs as spent.
|
||||||
|
//
|
||||||
|
// Coinbase inputs represent new coins,
|
||||||
|
// so there are no UTXOs to mark as spent.
|
||||||
|
for outpoint in transaction
|
||||||
|
.inputs()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|input| input.outpoint())
|
||||||
|
{
|
||||||
|
self.zs_delete(utxo_by_outpoint, outpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark sprout, sapling and orchard nullifiers as spent
|
||||||
|
for sprout_nullifier in transaction.sprout_nullifiers() {
|
||||||
|
self.zs_insert(sprout_nullifiers, sprout_nullifier, ());
|
||||||
|
}
|
||||||
|
for sapling_nullifier in transaction.sapling_nullifiers() {
|
||||||
|
self.zs_insert(sapling_nullifiers, sapling_nullifier, ());
|
||||||
|
}
|
||||||
|
for orchard_nullifier in transaction.orchard_nullifiers() {
|
||||||
|
self.zs_insert(orchard_nullifiers, orchard_nullifier, ());
|
||||||
|
}
|
||||||
|
|
||||||
|
for sprout_note_commitment in transaction.sprout_note_commitments() {
|
||||||
|
sprout_note_commitment_tree.append(*sprout_note_commitment)?;
|
||||||
|
}
|
||||||
|
for sapling_note_commitment in transaction.sapling_note_commitments() {
|
||||||
|
sapling_note_commitment_tree.append(*sapling_note_commitment)?;
|
||||||
|
}
|
||||||
|
for orchard_note_commitment in transaction.orchard_note_commitments() {
|
||||||
|
orchard_note_commitment_tree.append(*orchard_note_commitment)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sprout_root = sprout_note_commitment_tree.root();
|
||||||
|
let sapling_root = sapling_note_commitment_tree.root();
|
||||||
|
let orchard_root = orchard_note_commitment_tree.root();
|
||||||
|
|
||||||
|
history_tree.push(network, block.clone(), sapling_root, orchard_root)?;
|
||||||
|
|
||||||
|
// Compute the new anchors and index them
|
||||||
|
// Note: if the root hasn't changed, we write the same value again.
|
||||||
|
self.zs_insert(sprout_anchors, sprout_root, &sprout_note_commitment_tree);
|
||||||
|
self.zs_insert(sapling_anchors, sapling_root, ());
|
||||||
|
self.zs_insert(orchard_anchors, orchard_root, ());
|
||||||
|
|
||||||
|
// Update the trees in state
|
||||||
|
if let Some(h) = current_tip_height {
|
||||||
|
self.zs_delete(sprout_note_commitment_tree_cf, h);
|
||||||
|
self.zs_delete(sapling_note_commitment_tree_cf, h);
|
||||||
|
self.zs_delete(orchard_note_commitment_tree_cf, h);
|
||||||
|
self.zs_delete(history_tree_cf, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.zs_insert(
|
||||||
|
sprout_note_commitment_tree_cf,
|
||||||
|
height,
|
||||||
|
sprout_note_commitment_tree,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.zs_insert(
|
||||||
|
sapling_note_commitment_tree_cf,
|
||||||
|
height,
|
||||||
|
sapling_note_commitment_tree,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.zs_insert(
|
||||||
|
orchard_note_commitment_tree_cf,
|
||||||
|
height,
|
||||||
|
orchard_note_commitment_tree,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(history_tree) = history_tree.as_ref() {
|
||||||
|
self.zs_insert(history_tree_cf, height, history_tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some utxos are spent in the same block so they will be in `new_outputs`.
|
||||||
|
all_utxos_spent_by_block.extend(new_outputs);
|
||||||
|
|
||||||
|
let new_pool = current_value_pool.add_block(block.borrow(), &all_utxos_spent_by_block)?;
|
||||||
|
self.zs_insert(tip_chain_value_pool, (), new_pool);
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -175,7 +175,7 @@ impl NonFinalizedState {
|
||||||
finalized_state.sapling_note_commitment_tree(),
|
finalized_state.sapling_note_commitment_tree(),
|
||||||
finalized_state.orchard_note_commitment_tree(),
|
finalized_state.orchard_note_commitment_tree(),
|
||||||
finalized_state.history_tree(),
|
finalized_state.history_tree(),
|
||||||
finalized_state.current_value_pool(),
|
finalized_state.finalized_value_pool(),
|
||||||
);
|
);
|
||||||
let (height, hash) = (prepared.height, prepared.hash);
|
let (height, hash) = (prepared.height, prepared.hash);
|
||||||
|
|
||||||
|
|
|
@ -466,7 +466,7 @@ fn rejection_restores_internal_state_genesis() -> Result<()> {
|
||||||
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
finalized_state.set_current_value_pool(fake_value_pool);
|
finalized_state.set_finalized_value_pool(fake_value_pool);
|
||||||
|
|
||||||
// use `valid_count` as the number of valid blocks before an invalid block
|
// use `valid_count` as the number of valid blocks before an invalid block
|
||||||
let valid_tip_height = chain[valid_count - 1].height;
|
let valid_tip_height = chain[valid_count - 1].height;
|
||||||
|
|
|
@ -190,7 +190,7 @@ fn finalize_pops_from_best_chain_for_network(network: Network) -> Result<()> {
|
||||||
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
finalized_state.set_current_value_pool(fake_value_pool);
|
finalized_state.set_finalized_value_pool(fake_value_pool);
|
||||||
|
|
||||||
state.commit_new_chain(block1.clone().prepare(), &finalized_state)?;
|
state.commit_new_chain(block1.clone().prepare(), &finalized_state)?;
|
||||||
state.commit_block(block2.clone().prepare(), &finalized_state)?;
|
state.commit_block(block2.clone().prepare(), &finalized_state)?;
|
||||||
|
@ -241,7 +241,7 @@ fn commit_block_extending_best_chain_doesnt_drop_worst_chains_for_network(
|
||||||
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
finalized_state.set_current_value_pool(fake_value_pool);
|
finalized_state.set_finalized_value_pool(fake_value_pool);
|
||||||
|
|
||||||
assert_eq!(0, state.chain_set.len());
|
assert_eq!(0, state.chain_set.len());
|
||||||
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
||||||
|
@ -288,7 +288,7 @@ fn shorter_chain_can_be_best_chain_for_network(network: Network) -> Result<()> {
|
||||||
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
finalized_state.set_current_value_pool(fake_value_pool);
|
finalized_state.set_finalized_value_pool(fake_value_pool);
|
||||||
|
|
||||||
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
||||||
state.commit_block(long_chain_block1.prepare(), &finalized_state)?;
|
state.commit_block(long_chain_block1.prepare(), &finalized_state)?;
|
||||||
|
@ -335,7 +335,7 @@ fn longer_chain_with_more_work_wins_for_network(network: Network) -> Result<()>
|
||||||
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
finalized_state.set_current_value_pool(fake_value_pool);
|
finalized_state.set_finalized_value_pool(fake_value_pool);
|
||||||
|
|
||||||
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
||||||
state.commit_block(long_chain_block1.prepare(), &finalized_state)?;
|
state.commit_block(long_chain_block1.prepare(), &finalized_state)?;
|
||||||
|
@ -380,7 +380,7 @@ fn equal_length_goes_to_more_work_for_network(network: Network) -> Result<()> {
|
||||||
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
let finalized_state = FinalizedState::new(&Config::ephemeral(), network);
|
||||||
|
|
||||||
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
let fake_value_pool = ValueBalance::<NonNegative>::fake_populated_pool();
|
||||||
finalized_state.set_current_value_pool(fake_value_pool);
|
finalized_state.set_finalized_value_pool(fake_value_pool);
|
||||||
|
|
||||||
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
state.commit_new_chain(block1.prepare(), &finalized_state)?;
|
||||||
state.commit_block(less_work_child.prepare(), &finalized_state)?;
|
state.commit_block(less_work_child.prepare(), &finalized_state)?;
|
||||||
|
|
|
@ -471,7 +471,7 @@ proptest! {
|
||||||
|
|
||||||
let (mut state_service, _, _) = StateService::new(Config::ephemeral(), network);
|
let (mut state_service, _, _) = StateService::new(Config::ephemeral(), network);
|
||||||
|
|
||||||
prop_assert_eq!(state_service.disk.current_value_pool(), ValueBalance::zero());
|
prop_assert_eq!(state_service.disk.finalized_value_pool(), ValueBalance::zero());
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
state_service.mem.best_chain().map(|chain| chain.chain_value_pools).unwrap_or_else(ValueBalance::zero),
|
state_service.mem.best_chain().map(|chain| chain.chain_value_pools).unwrap_or_else(ValueBalance::zero),
|
||||||
ValueBalance::zero()
|
ValueBalance::zero()
|
||||||
|
@ -495,7 +495,7 @@ proptest! {
|
||||||
state_service.queue_and_commit_finalized(block.clone());
|
state_service.queue_and_commit_finalized(block.clone());
|
||||||
|
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
state_service.disk.current_value_pool(),
|
state_service.disk.finalized_value_pool(),
|
||||||
expected_finalized_value_pool.clone()?.constrain()?
|
expected_finalized_value_pool.clone()?.constrain()?
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -504,7 +504,7 @@ proptest! {
|
||||||
let transparent_value = ValueBalance::from_transparent_amount(transparent_value);
|
let transparent_value = ValueBalance::from_transparent_amount(transparent_value);
|
||||||
expected_transparent_pool = (expected_transparent_pool + transparent_value).unwrap();
|
expected_transparent_pool = (expected_transparent_pool + transparent_value).unwrap();
|
||||||
prop_assert_eq!(
|
prop_assert_eq!(
|
||||||
state_service.disk.current_value_pool(),
|
state_service.disk.finalized_value_pool(),
|
||||||
expected_transparent_pool
|
expected_transparent_pool
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue