2020-11-18 18:05:06 -08:00
|
|
|
//! The primary implementation of the `zebra_state::Service` built upon rocksdb
|
2020-09-09 23:07:47 -07:00
|
|
|
|
2020-11-16 16:05:59 -08:00
|
|
|
mod disk_format;
|
2020-11-16 16:05:35 -08:00
|
|
|
|
2020-10-30 15:24:39 -07:00
|
|
|
use std::{collections::HashMap, convert::TryInto, sync::Arc};
|
2020-09-09 23:07:47 -07:00
|
|
|
|
2020-10-23 14:54:59 -07:00
|
|
|
use zebra_chain::transparent;
|
2020-06-15 14:41:26 -07:00
|
|
|
use zebra_chain::{
|
2020-09-10 10:19:45 -07:00
|
|
|
block::{self, Block},
|
2020-10-12 14:08:23 -07:00
|
|
|
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
2020-11-01 10:49:34 -08:00
|
|
|
transaction::{self, Transaction},
|
2020-06-15 14:41:26 -07:00
|
|
|
};
|
|
|
|
|
2020-11-23 12:02:57 -08:00
|
|
|
use crate::{BoxError, Config, FinalizedBlock, HashOrHeight, Utxo};
|
2020-10-23 14:54:59 -07:00
|
|
|
|
2020-11-16 16:05:59 -08:00
|
|
|
use self::disk_format::{DiskDeserialize, DiskSerialize, FromDisk, IntoDisk, TransactionLocation};
|
2020-10-23 14:54:59 -07:00
|
|
|
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
use super::QueuedFinalized;
|
2020-10-30 15:24:39 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
/// The finalized part of the chain state, stored in the db.
|
2020-09-24 15:46:04 -07:00
|
|
|
pub struct FinalizedState {
|
2020-09-09 23:07:47 -07:00
|
|
|
/// Queued blocks that arrived out of order, indexed by their parent block hash.
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
queued_by_prev_hash: HashMap<block::Hash, QueuedFinalized>,
|
2020-11-12 15:49:55 -08:00
|
|
|
max_queued_height: i64,
|
2020-09-09 23:07:47 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
db: rocksdb::DB,
|
|
|
|
ephemeral: bool,
|
2020-10-27 02:25:29 -07:00
|
|
|
/// Commit blocks to the finalized state up to this height, then exit Zebra.
|
|
|
|
debug_stop_at_height: Option<block::Height>,
|
2020-06-15 14:41:26 -07:00
|
|
|
}
|
|
|
|
|
2020-09-24 15:46:04 -07:00
|
|
|
impl FinalizedState {
|
2020-09-09 21:15:08 -07:00
|
|
|
pub fn new(config: &Config, network: Network) -> Self {
|
2020-11-18 18:05:06 -08:00
|
|
|
let db = config.open_db(network);
|
2020-06-15 14:41:26 -07:00
|
|
|
|
2020-10-27 02:25:29 -07:00
|
|
|
let new_state = Self {
|
2020-09-09 23:07:47 -07:00
|
|
|
queued_by_prev_hash: HashMap::new(),
|
2020-11-12 15:49:55 -08:00
|
|
|
max_queued_height: -1,
|
2020-11-18 18:05:06 -08:00
|
|
|
db,
|
|
|
|
ephemeral: config.ephemeral,
|
2020-10-27 02:25:29 -07:00
|
|
|
debug_stop_at_height: config.debug_stop_at_height.map(block::Height),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(tip_height) = new_state.finalized_tip_height() {
|
2020-11-12 17:37:52 -08:00
|
|
|
if new_state.is_at_stop_height(tip_height) {
|
|
|
|
let debug_stop_at_height = new_state
|
|
|
|
.debug_stop_at_height
|
|
|
|
.expect("true from `is_at_stop_height` implies `debug_stop_at_height` is Some");
|
|
|
|
|
|
|
|
let tip_hash = new_state.finalized_tip_hash();
|
|
|
|
|
|
|
|
if tip_height > debug_stop_at_height {
|
|
|
|
tracing::error!(
|
|
|
|
?debug_stop_at_height,
|
|
|
|
?tip_height,
|
|
|
|
?tip_hash,
|
|
|
|
"previous state height is greater than the stop height",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
tracing::info!(
|
|
|
|
?debug_stop_at_height,
|
|
|
|
?tip_height,
|
|
|
|
?tip_hash,
|
|
|
|
"state is already at the configured height"
|
|
|
|
);
|
|
|
|
|
|
|
|
// There's no need to sync before exit, because the trees have just been opened
|
|
|
|
std::process::exit(0);
|
|
|
|
}
|
2020-10-27 02:25:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
new_state
|
|
|
|
}
|
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
/// Stop the process if `block_height` is greater than or equal to the
|
|
|
|
/// configured stop height.
|
2020-11-12 17:37:52 -08:00
|
|
|
fn is_at_stop_height(&self, block_height: block::Height) -> bool {
|
2020-10-27 02:25:29 -07:00
|
|
|
let debug_stop_at_height = match self.debug_stop_at_height {
|
|
|
|
Some(debug_stop_at_height) => debug_stop_at_height,
|
2020-11-12 17:37:52 -08:00
|
|
|
None => return false,
|
2020-10-27 02:25:29 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
if block_height < debug_stop_at_height {
|
2020-11-12 17:37:52 -08:00
|
|
|
return false;
|
2020-10-27 02:25:29 -07:00
|
|
|
}
|
|
|
|
|
2020-11-12 17:37:52 -08:00
|
|
|
true
|
2020-06-15 14:41:26 -07:00
|
|
|
}
|
|
|
|
|
2020-09-09 23:07:47 -07:00
|
|
|
/// Queue a finalized block to be committed to the state.
|
2020-09-10 14:16:39 -07:00
|
|
|
///
|
2020-09-10 14:42:20 -07:00
|
|
|
/// After queueing a finalized block, this method checks whether the newly
|
|
|
|
/// queued block (and any of its descendants) can be committed to the state.
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
pub fn queue_and_commit_finalized(&mut self, queued: QueuedFinalized) {
|
|
|
|
let prev_hash = queued.0.block.header.previous_block_hash;
|
|
|
|
let height = queued.0.height;
|
|
|
|
self.queued_by_prev_hash.insert(prev_hash, queued);
|
2020-09-09 23:07:47 -07:00
|
|
|
|
2020-11-16 18:25:35 -08:00
|
|
|
while let Some(queued_block) = self.queued_by_prev_hash.remove(&self.finalized_tip_hash()) {
|
2020-10-09 18:49:44 -07:00
|
|
|
self.commit_finalized(queued_block);
|
2020-11-12 15:49:55 -08:00
|
|
|
metrics::counter!("state.finalized.committed.block.count", 1);
|
|
|
|
metrics::gauge!("state.finalized.committed.block.height", height.0 as _);
|
2020-09-09 23:07:47 -07:00
|
|
|
}
|
2020-10-09 18:49:44 -07:00
|
|
|
|
2020-11-12 15:49:55 -08:00
|
|
|
if self.queued_by_prev_hash.is_empty() {
|
|
|
|
// use -1 as a sentinel value for "None", because 0 is a valid height
|
|
|
|
self.max_queued_height = -1;
|
|
|
|
} else {
|
|
|
|
self.max_queued_height = std::cmp::max(self.max_queued_height, height.0 as _);
|
|
|
|
}
|
|
|
|
|
|
|
|
metrics::gauge!("state.finalized.queued.max.height", self.max_queued_height);
|
2020-10-09 18:49:44 -07:00
|
|
|
metrics::gauge!(
|
2020-11-12 15:49:55 -08:00
|
|
|
"state.finalized.queued.block.count",
|
2020-10-09 18:49:44 -07:00
|
|
|
self.queued_by_prev_hash.len() as _
|
|
|
|
);
|
2020-09-09 23:07:47 -07:00
|
|
|
}
|
|
|
|
|
2020-10-07 20:07:32 -07:00
|
|
|
/// Returns the hash of the current finalized tip block.
|
|
|
|
pub fn finalized_tip_hash(&self) -> block::Hash {
|
2020-10-24 17:09:50 -07:00
|
|
|
self.tip()
|
2020-10-07 20:07:32 -07:00
|
|
|
.map(|(_, hash)| hash)
|
|
|
|
// if the state is empty, return the genesis previous block hash
|
2020-10-12 14:08:23 -07:00
|
|
|
.unwrap_or(GENESIS_PREVIOUS_BLOCK_HASH)
|
2020-10-07 20:07:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the height of the current finalized tip block.
|
|
|
|
pub fn finalized_tip_height(&self) -> Option<block::Height> {
|
2020-11-01 10:49:34 -08:00
|
|
|
self.tip().map(|(height, _)| height)
|
2020-10-07 20:07:32 -07:00
|
|
|
}
|
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
fn is_empty(&self, cf: &rocksdb::ColumnFamily) -> bool {
|
|
|
|
// use iterator to check if it's empty
|
|
|
|
!self
|
|
|
|
.db
|
|
|
|
.iterator_cf(cf, rocksdb::IteratorMode::Start)
|
|
|
|
.valid()
|
|
|
|
}
|
|
|
|
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
/// Immediately commit `finalized` to the finalized state.
|
|
|
|
pub fn commit_finalized_direct(
|
|
|
|
&mut self,
|
|
|
|
finalized: FinalizedBlock,
|
|
|
|
) -> Result<block::Hash, BoxError> {
|
|
|
|
block_precommit_metrics(&finalized);
|
2020-06-15 14:41:26 -07:00
|
|
|
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
let FinalizedBlock {
|
|
|
|
block,
|
|
|
|
hash,
|
|
|
|
height,
|
2020-11-23 12:02:57 -08:00
|
|
|
new_outputs,
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
} = finalized;
|
2020-11-16 18:25:35 -08:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
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();
|
|
|
|
|
2020-11-16 18:25:35 -08:00
|
|
|
// Assert that callers (including unit tests) get the chain order correct
|
2020-11-18 18:05:06 -08:00
|
|
|
if self.is_empty(hash_by_height) {
|
2020-11-16 18:25:35 -08:00
|
|
|
assert_eq!(
|
|
|
|
block::Hash([0; 32]),
|
|
|
|
block.header.previous_block_hash,
|
|
|
|
"the first block added to an empty state must be a genesis block"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
block::Height(0),
|
|
|
|
height,
|
|
|
|
"cannot commit genesis: invalid height"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
assert_eq!(
|
|
|
|
self.finalized_tip_height()
|
|
|
|
.expect("state must have a genesis block committed")
|
|
|
|
+ 1,
|
|
|
|
Some(height),
|
|
|
|
"committed block height must be 1 more than the finalized tip height"
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
self.finalized_tip_hash(),
|
|
|
|
block.header.previous_block_hash,
|
|
|
|
"committed block must be a child of the finalized tip"
|
|
|
|
);
|
|
|
|
}
|
2020-10-07 20:07:32 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
// We use a closure so we can use an early return for control flow in
|
|
|
|
// the genesis case
|
|
|
|
let prepare_commit = || -> rocksdb::WriteBatch {
|
|
|
|
let mut batch = rocksdb::WriteBatch::default();
|
2020-10-27 14:55:46 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
// 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);
|
2020-10-27 14:55:04 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
// TODO: sprout and sapling anchors (per block)
|
2020-10-23 15:29:52 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
// Consensus-critical bug in zcashd: transactions in the
|
|
|
|
// genesis block are ignored.
|
|
|
|
if block.header.previous_block_hash == block::Hash([0; 32]) {
|
|
|
|
return batch;
|
|
|
|
}
|
|
|
|
|
2020-11-23 12:02:57 -08:00
|
|
|
// Index all new transparent outputs
|
|
|
|
for (outpoint, utxo) in new_outputs.into_iter() {
|
|
|
|
batch.zs_insert(utxo_by_outpoint, outpoint, utxo);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Index each transaction, spent inputs, nullifiers
|
|
|
|
// TODO: move computation into FinalizedBlock as with transparent outputs
|
2020-11-18 18:05:06 -08:00
|
|
|
for (transaction_index, transaction) in block.transactions.iter().enumerate() {
|
|
|
|
let transaction_hash = transaction.hash();
|
|
|
|
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
|
|
|
|
for input in transaction.inputs() {
|
|
|
|
match input {
|
|
|
|
transparent::Input::PrevOut { outpoint, .. } => {
|
|
|
|
batch.delete_cf(utxo_by_outpoint, outpoint.as_bytes());
|
2020-10-23 15:29:52 -07:00
|
|
|
}
|
2020-11-18 18:05:06 -08:00
|
|
|
// Coinbase inputs represent new coins,
|
|
|
|
// so there are no UTXOs to mark as spent.
|
|
|
|
transparent::Input::Coinbase { .. } => {}
|
2020-10-14 14:06:32 -07:00
|
|
|
}
|
2020-11-18 18:05:06 -08:00
|
|
|
}
|
2020-10-14 14:06:32 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
// Mark sprout and sapling 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, ());
|
|
|
|
}
|
2020-11-12 17:37:52 -08:00
|
|
|
}
|
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
batch
|
|
|
|
};
|
|
|
|
|
|
|
|
let batch = prepare_commit();
|
|
|
|
|
|
|
|
let result = self.db.write(batch).map(|()| hash);
|
|
|
|
|
|
|
|
if result.is_ok() && self.is_at_stop_height(height) {
|
2020-11-12 17:37:52 -08:00
|
|
|
tracing::info!(?height, ?hash, "stopping at configured height");
|
|
|
|
|
|
|
|
std::process::exit(0);
|
2020-10-27 02:25:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
result.map_err(Into::into)
|
2020-10-07 20:07:32 -07:00
|
|
|
}
|
2020-09-01 18:20:32 -07:00
|
|
|
|
2020-10-07 20:07:32 -07:00
|
|
|
/// Commit a finalized block to the state.
|
|
|
|
///
|
|
|
|
/// It's the caller's responsibility to ensure that blocks are committed in
|
|
|
|
/// order. This function is called by [`queue`], which ensures order.
|
|
|
|
/// It is intentionally not exposed as part of the public API of the
|
|
|
|
/// [`FinalizedState`].
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
fn commit_finalized(&mut self, queued_block: QueuedFinalized) {
|
|
|
|
let (finalized, rsp_tx) = queued_block;
|
|
|
|
let result = self.commit_finalized_direct(finalized);
|
2020-10-09 01:37:24 -07:00
|
|
|
let _ = rsp_tx.send(result.map_err(Into::into));
|
2020-06-15 14:41:26 -07:00
|
|
|
}
|
|
|
|
|
2020-11-01 10:49:34 -08:00
|
|
|
/// Returns the tip height and hash if there is one.
|
|
|
|
pub fn tip(&self) -> Option<(block::Height, block::Hash)> {
|
2020-11-18 18:05:06 -08:00
|
|
|
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
|
|
|
self.db
|
|
|
|
.iterator_cf(hash_by_height, rocksdb::IteratorMode::End)
|
2020-11-01 10:49:34 -08:00
|
|
|
.next()
|
|
|
|
.map(|(height_bytes, hash_bytes)| {
|
2020-11-18 18:05:06 -08:00
|
|
|
let height = block::Height::from_bytes(height_bytes);
|
|
|
|
let hash = block::Hash::from_bytes(hash_bytes);
|
2020-10-23 14:54:59 -07:00
|
|
|
|
2020-11-01 10:49:34 -08:00
|
|
|
(height, hash)
|
|
|
|
})
|
2020-06-15 14:41:26 -07:00
|
|
|
}
|
|
|
|
|
2020-11-01 10:49:34 -08:00
|
|
|
/// Returns the height of the given block if it exists.
|
|
|
|
pub fn height(&self, hash: block::Hash) -> Option<block::Height> {
|
2020-11-18 18:05:06 -08:00
|
|
|
let height_by_hash = self.db.cf_handle("height_by_hash").unwrap();
|
|
|
|
self.db.zs_get(&height_by_hash, &hash)
|
2020-07-30 16:21:54 -07:00
|
|
|
}
|
|
|
|
|
2020-11-01 10:49:34 -08:00
|
|
|
/// Returns the given block if it exists.
|
|
|
|
pub fn block(&self, hash_or_height: HashOrHeight) -> Option<Arc<Block>> {
|
2020-11-18 18:05:06 -08:00
|
|
|
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))?;
|
2020-06-15 14:41:26 -07:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
self.db.zs_get(block_by_height, &height)
|
2020-09-10 10:19:45 -07:00
|
|
|
}
|
2020-10-14 14:06:32 -07:00
|
|
|
|
|
|
|
/// Returns the `transparent::Output` pointed to by the given
|
|
|
|
/// `transparent::OutPoint` if it is present.
|
2020-11-23 12:02:57 -08:00
|
|
|
pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<Utxo> {
|
2020-11-18 18:05:06 -08:00
|
|
|
let utxo_by_outpoint = self.db.cf_handle("utxo_by_outpoint").unwrap();
|
|
|
|
self.db.zs_get(utxo_by_outpoint, outpoint)
|
2020-10-14 14:06:32 -07:00
|
|
|
}
|
2020-10-26 13:54:19 -07:00
|
|
|
|
|
|
|
/// Returns the finalized hash for a given `block::Height` if it is present.
|
2020-11-01 10:49:34 -08:00
|
|
|
pub fn hash(&self, height: block::Height) -> Option<block::Hash> {
|
2020-11-18 18:05:06 -08:00
|
|
|
let hash_by_height = self.db.cf_handle("hash_by_height").unwrap();
|
|
|
|
self.db.zs_get(hash_by_height, &height)
|
2020-11-01 10:49:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the given transaction if it exists.
|
|
|
|
pub fn transaction(&self, hash: transaction::Hash) -> Option<Arc<Transaction>> {
|
2020-11-18 18:05:06 -08:00
|
|
|
let tx_by_hash = self.db.cf_handle("tx_by_hash").unwrap();
|
|
|
|
self.db
|
|
|
|
.zs_get(tx_by_hash, &hash)
|
2020-11-01 10:49:34 -08:00
|
|
|
.map(|TransactionLocation { index, height }| {
|
|
|
|
let block = self
|
|
|
|
.block(height.into())
|
|
|
|
.expect("block will exist if TransactionLocation does");
|
|
|
|
|
|
|
|
block.transactions[index as usize].clone()
|
|
|
|
})
|
2020-10-26 13:54:19 -07:00
|
|
|
}
|
2020-07-30 16:21:54 -07:00
|
|
|
}
|
2020-11-12 20:34:49 -08:00
|
|
|
|
2020-11-18 18:05:06 -08:00
|
|
|
// Drop isn't guaranteed to run, such as when we panic, or if someone stored
|
|
|
|
// their FinalizedState in a static, but it should be fine if we don't clean
|
|
|
|
// this up since the files are placed in the os temp dir and should be cleaned
|
|
|
|
// up automatically eventually.
|
|
|
|
impl Drop for FinalizedState {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if self.ephemeral {
|
|
|
|
let path = self.db.path();
|
|
|
|
tracing::debug!("removing temporary database files {:?}", path);
|
|
|
|
let _res = std::fs::remove_dir_all(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
fn block_precommit_metrics(finalized: &FinalizedBlock) {
|
|
|
|
let (hash, height, block) = (finalized.hash, finalized.height, finalized.block.as_ref());
|
|
|
|
|
2020-11-12 20:34:49 -08:00
|
|
|
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();
|
|
|
|
|
|
|
|
tracing::debug!(
|
|
|
|
?hash,
|
|
|
|
?height,
|
|
|
|
transaction_count,
|
|
|
|
transparent_prevout_count,
|
|
|
|
transparent_newout_count,
|
|
|
|
sprout_nullifier_count,
|
|
|
|
sapling_nullifier_count,
|
|
|
|
"preparing to commit finalized block"
|
|
|
|
);
|
|
|
|
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
|
|
|
|
);
|
|
|
|
}
|