2020-06-15 14:41:26 -07:00
|
|
|
//! The primary implementation of the `zebra_state::Service` built upon sled
|
2020-09-09 23:07:47 -07: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-07 20:07:32 -07:00
|
|
|
use tracing::trace;
|
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-09-10 10:19:45 -07:00
|
|
|
use crate::{BoxError, Config, HashOrHeight, QueuedBlock};
|
2020-09-09 21:15:08 -07:00
|
|
|
|
2020-10-23 14:54:59 -07:00
|
|
|
mod sled_format;
|
|
|
|
|
2020-10-27 14:55:04 -07:00
|
|
|
use sled_format::{FromSled, IntoSled, SledDeserialize, SledSerialize};
|
2020-10-23 14:54:59 -07:00
|
|
|
|
2020-10-30 15:24:39 -07:00
|
|
|
use self::sled_format::TransactionLocation;
|
|
|
|
|
2020-09-10 14:16:39 -07:00
|
|
|
/// The finalized part of the chain state, stored in sled.
|
|
|
|
///
|
|
|
|
/// This structure has two categories of methods:
|
|
|
|
///
|
|
|
|
/// - *synchronous* methods that perform writes to the sled state;
|
|
|
|
/// - *asynchronous* methods that perform reads.
|
|
|
|
///
|
|
|
|
/// For more on this distinction, see RFC5. The synchronous methods are
|
2020-09-24 15:46:04 -07:00
|
|
|
/// implemented as ordinary methods on the [`FinalizedState`]. The asynchronous
|
2020-09-10 14:16:39 -07:00
|
|
|
/// methods are not implemented using `async fn`, but using normal methods that
|
|
|
|
/// return `impl Future<Output = ...>`. This allows them to move data (e.g.,
|
|
|
|
/// clones of handles for [`sled::Tree`]s) into the futures they return.
|
|
|
|
///
|
|
|
|
/// This means that the returned futures have a `'static` lifetime and don't
|
2020-09-24 15:46:04 -07:00
|
|
|
/// borrow any resources from the [`FinalizedState`], and the actual database work is
|
2020-09-10 14:16:39 -07:00
|
|
|
/// performed asynchronously when the returned future is polled, not while it is
|
|
|
|
/// created. This is analogous to the way [`tower::Service::call`] works.
|
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.
|
|
|
|
queued_by_prev_hash: HashMap<block::Hash, QueuedBlock>,
|
2020-11-12 15:49:55 -08:00
|
|
|
max_queued_height: i64,
|
2020-09-09 23:07:47 -07:00
|
|
|
|
2020-09-09 21:15:08 -07:00
|
|
|
hash_by_height: sled::Tree,
|
|
|
|
height_by_hash: sled::Tree,
|
|
|
|
block_by_height: sled::Tree,
|
2020-10-23 14:54:59 -07:00
|
|
|
tx_by_hash: sled::Tree,
|
2020-10-14 14:06:32 -07:00
|
|
|
utxo_by_outpoint: sled::Tree,
|
2020-10-23 15:29:52 -07:00
|
|
|
sprout_nullifiers: sled::Tree,
|
|
|
|
sapling_nullifiers: sled::Tree,
|
2020-09-09 21:15:08 -07:00
|
|
|
// sprout_anchors: sled::Tree,
|
|
|
|
// sapling_anchors: sled::Tree,
|
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 {
|
|
|
|
let db = config.sled_config(network).open().unwrap();
|
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-09-09 21:15:08 -07:00
|
|
|
hash_by_height: db.open_tree(b"hash_by_height").unwrap(),
|
|
|
|
height_by_hash: db.open_tree(b"height_by_hash").unwrap(),
|
|
|
|
block_by_height: db.open_tree(b"block_by_height").unwrap(),
|
2020-10-23 14:54:59 -07:00
|
|
|
tx_by_hash: db.open_tree(b"tx_by_hash").unwrap(),
|
2020-10-14 14:06:32 -07:00
|
|
|
utxo_by_outpoint: db.open_tree(b"utxo_by_outpoint").unwrap(),
|
2020-10-23 15:29:52 -07:00
|
|
|
sprout_nullifiers: db.open_tree(b"sprout_nullifiers").unwrap(),
|
|
|
|
sapling_nullifiers: db.open_tree(b"sapling_nullifiers").unwrap(),
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Synchronously flushes all dirty IO buffers and calls fsync.
|
|
|
|
///
|
|
|
|
/// Returns the number of bytes flushed during this call.
|
|
|
|
/// See sled's `Tree.flush` for more details.
|
|
|
|
pub fn flush(&self) -> sled::Result<usize> {
|
|
|
|
let mut total_flushed = 0;
|
|
|
|
|
|
|
|
total_flushed += self.hash_by_height.flush()?;
|
|
|
|
total_flushed += self.height_by_hash.flush()?;
|
|
|
|
total_flushed += self.block_by_height.flush()?;
|
2020-10-23 14:54:59 -07:00
|
|
|
total_flushed += self.tx_by_hash.flush()?;
|
2020-10-27 02:25:29 -07:00
|
|
|
total_flushed += self.utxo_by_outpoint.flush()?;
|
2020-10-23 15:29:52 -07:00
|
|
|
total_flushed += self.sprout_nullifiers.flush()?;
|
|
|
|
total_flushed += self.sapling_nullifiers.flush()?;
|
2020-10-27 02:25:29 -07:00
|
|
|
|
|
|
|
Ok(total_flushed)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If `block_height` is greater than or equal to the configured stop height,
|
|
|
|
/// stop the process.
|
|
|
|
///
|
|
|
|
/// Flushes sled trees before exiting.
|
|
|
|
///
|
|
|
|
/// `called_from` and `block_hash` are used for assertions and logging.
|
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.
|
2020-10-07 20:07:32 -07:00
|
|
|
pub fn queue_and_commit_finalized_blocks(&mut self, queued_block: QueuedBlock) {
|
2020-09-09 23:07:47 -07:00
|
|
|
let prev_hash = queued_block.block.header.previous_block_hash;
|
2020-11-12 15:49:55 -08:00
|
|
|
let height = queued_block.block.coinbase_height().unwrap();
|
2020-09-09 23:07:47 -07:00
|
|
|
self.queued_by_prev_hash.insert(prev_hash, queued_block);
|
|
|
|
|
2020-11-16 15:53:33 -08:00
|
|
|
loop {
|
|
|
|
let finalized_tip_hash = self.finalized_tip_hash();
|
|
|
|
let queued_block =
|
|
|
|
if let Some(queued_block) = self.queued_by_prev_hash.remove(&finalized_tip_hash) {
|
|
|
|
queued_block
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
|
2020-10-09 18:49:44 -07:00
|
|
|
let height = queued_block
|
|
|
|
.block
|
|
|
|
.coinbase_height()
|
|
|
|
.expect("valid blocks must have a height");
|
2020-11-16 15:53:33 -08:00
|
|
|
|
|
|
|
if self.block_by_height.is_empty() {
|
|
|
|
assert_eq!(
|
|
|
|
block::Hash([0; 32]),
|
|
|
|
prev_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)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
finalized_tip_hash,
|
|
|
|
queued_block.block.header.previous_block_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
|
|
|
}
|
|
|
|
|
|
|
|
/// Immediately commit `block` to the finalized state.
|
|
|
|
pub fn commit_finalized_direct(&mut self, block: Arc<Block>) -> Result<block::Hash, BoxError> {
|
|
|
|
use sled::Transactional;
|
2020-09-09 23:07:47 -07:00
|
|
|
|
2020-09-11 12:56:32 -07:00
|
|
|
let height = block
|
|
|
|
.coinbase_height()
|
|
|
|
.expect("finalized blocks are valid and have a coinbase height");
|
2020-08-15 23:20:01 -07:00
|
|
|
let hash = block.hash();
|
2020-06-15 14:41:26 -07:00
|
|
|
|
2020-10-07 20:07:32 -07:00
|
|
|
trace!(?height, "Finalized block");
|
|
|
|
|
2020-10-27 02:25:29 -07:00
|
|
|
let result = (
|
2020-09-09 21:15:08 -07:00
|
|
|
&self.hash_by_height,
|
|
|
|
&self.height_by_hash,
|
|
|
|
&self.block_by_height,
|
2020-10-14 14:06:32 -07:00
|
|
|
&self.utxo_by_outpoint,
|
2020-10-23 14:54:59 -07:00
|
|
|
&self.tx_by_hash,
|
2020-10-23 15:29:52 -07:00
|
|
|
&self.sprout_nullifiers,
|
|
|
|
&self.sapling_nullifiers,
|
2020-09-09 21:15:08 -07:00
|
|
|
)
|
2020-10-14 14:06:32 -07:00
|
|
|
.transaction(
|
2020-10-23 14:54:59 -07:00
|
|
|
move |(
|
|
|
|
hash_by_height,
|
|
|
|
height_by_hash,
|
|
|
|
block_by_height,
|
|
|
|
utxo_by_outpoint,
|
|
|
|
tx_by_hash,
|
2020-10-23 15:29:52 -07:00
|
|
|
sprout_nullifiers,
|
|
|
|
sapling_nullifiers,
|
2020-10-23 14:54:59 -07:00
|
|
|
)| {
|
2020-10-27 14:55:04 -07:00
|
|
|
// Index the block
|
2020-10-23 14:54:59 -07:00
|
|
|
hash_by_height.zs_insert(height, hash)?;
|
|
|
|
height_by_hash.zs_insert(hash, height)?;
|
2020-11-06 11:28:20 -08:00
|
|
|
block_by_height.zs_insert(height, &block)?;
|
2020-10-14 14:06:32 -07:00
|
|
|
|
2020-10-27 14:55:04 -07:00
|
|
|
// TODO: sprout and sapling anchors (per block)
|
|
|
|
|
2020-10-27 14:55:46 -07:00
|
|
|
// Consensus-critical bug in zcashd: transactions in the
|
|
|
|
// genesis block are ignored.
|
|
|
|
if block.header.previous_block_hash == block::Hash([0; 32]) {
|
|
|
|
return Ok(hash);
|
|
|
|
}
|
|
|
|
|
2020-10-27 14:55:04 -07:00
|
|
|
// Index each transaction
|
2020-10-30 15:24:39 -07:00
|
|
|
for (transaction_index, transaction) in block.transactions.iter().enumerate() {
|
2020-10-14 14:06:32 -07:00
|
|
|
let transaction_hash = transaction.hash();
|
2020-10-30 15:24:39 -07:00
|
|
|
let transaction_location = TransactionLocation {
|
|
|
|
height,
|
|
|
|
index: transaction_index
|
|
|
|
.try_into()
|
|
|
|
.expect("no more than 4 billion transactions per block"),
|
|
|
|
};
|
|
|
|
tx_by_hash.zs_insert(transaction_hash, transaction_location)?;
|
2020-10-23 14:54:59 -07:00
|
|
|
|
2020-10-27 14:55:04 -07:00
|
|
|
// Mark all transparent inputs as spent
|
|
|
|
for input in transaction.inputs() {
|
|
|
|
match input {
|
|
|
|
transparent::Input::PrevOut { outpoint, .. } => {
|
|
|
|
utxo_by_outpoint.remove(outpoint.as_bytes())?;
|
|
|
|
}
|
|
|
|
// Coinbase inputs represent new coins,
|
|
|
|
// so there are no UTXOs to mark as spent.
|
|
|
|
transparent::Input::Coinbase { .. } => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Index all new transparent outputs
|
2020-10-14 14:06:32 -07:00
|
|
|
for (index, output) in transaction.outputs().iter().enumerate() {
|
|
|
|
let outpoint = transparent::OutPoint {
|
|
|
|
hash: transaction_hash,
|
|
|
|
index: index as _,
|
|
|
|
};
|
2020-10-23 14:54:59 -07:00
|
|
|
utxo_by_outpoint.zs_insert(outpoint, output)?;
|
2020-10-14 14:06:32 -07:00
|
|
|
}
|
2020-10-23 15:29:52 -07:00
|
|
|
|
2020-10-27 14:55:04 -07:00
|
|
|
// Mark sprout and sapling nullifiers as spent
|
2020-10-23 15:29:52 -07:00
|
|
|
for sprout_nullifier in transaction.sprout_nullifiers() {
|
|
|
|
sprout_nullifiers.zs_insert(sprout_nullifier, ())?;
|
|
|
|
}
|
|
|
|
for sapling_nullifier in transaction.sapling_nullifiers() {
|
|
|
|
sapling_nullifiers.zs_insert(sapling_nullifier, ())?;
|
|
|
|
}
|
2020-10-14 14:06:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// for some reason type inference fails here
|
|
|
|
Ok::<_, sled::transaction::ConflictableTransactionError>(hash)
|
|
|
|
},
|
2020-10-27 02:25:29 -07:00
|
|
|
);
|
|
|
|
|
2020-11-12 17:37:52 -08:00
|
|
|
if result.is_ok() && self.is_at_stop_height(height) {
|
|
|
|
if let Err(e) = self.flush() {
|
|
|
|
tracing::error!(
|
|
|
|
?e,
|
|
|
|
?height,
|
|
|
|
?hash,
|
|
|
|
"error flushing sled state before stopping"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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`].
|
|
|
|
fn commit_finalized(&mut self, queued_block: QueuedBlock) {
|
|
|
|
let QueuedBlock { block, rsp_tx } = queued_block;
|
|
|
|
let result = self.commit_finalized_direct(block);
|
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)> {
|
|
|
|
self.hash_by_height
|
|
|
|
.iter()
|
|
|
|
.rev()
|
|
|
|
.next()
|
|
|
|
.transpose()
|
|
|
|
.expect("expected that sled errors would not occur")
|
|
|
|
.map(|(height_bytes, hash_bytes)| {
|
|
|
|
let height = block::Height::from_ivec(height_bytes);
|
|
|
|
let hash = block::Hash::from_ivec(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> {
|
|
|
|
self.height_by_hash.zs_get(&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>> {
|
|
|
|
let height = hash_or_height.height_or_else(|hash| self.height_by_hash.zs_get(&hash))?;
|
2020-06-15 14:41:26 -07:00
|
|
|
|
2020-11-01 10:49:34 -08:00
|
|
|
self.block_by_height.zs_get(&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-01 10:49:34 -08:00
|
|
|
pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Output> {
|
2020-10-14 14:06:32 -07:00
|
|
|
self.utxo_by_outpoint.zs_get(outpoint)
|
|
|
|
}
|
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> {
|
|
|
|
self.hash_by_height.zs_get(&height)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the given transaction if it exists.
|
|
|
|
pub fn transaction(&self, hash: transaction::Hash) -> Option<Arc<Transaction>> {
|
|
|
|
self.tx_by_hash
|
|
|
|
.zs_get(&hash)
|
|
|
|
.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
|
|
|
}
|