use std::{
collections::{HashMap, HashSet},
ops::{Add, Deref, RangeInclusive},
pin::Pin,
sync::Arc,
};
use tower::{BoxError, Service, ServiceExt};
use zakura_chain::{
amount::{DeferredPoolBalanceChange, NegativeAllowed},
block::{
self,
merkle::{AuthDataRoot, AUTH_DIGEST_PLACEHOLDER},
Block, HeightDiff,
},
diagnostic::{task::WaitForPanics, CodeTimer},
history_tree::HistoryTree,
ironwood, orchard,
parallel::tree::NoteCommitmentTrees,
sapling,
serialization::SerializationError,
sprout,
subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeIndex},
transaction::{self, UnminedTx},
transparent::{self, utxos_from_ordered_utxos},
value_balance::{ValueBalance, ValueBalanceError},
};
#[allow(unused_imports)]
use crate::{
constants::{MAX_FIND_BLOCK_HASHES_RESULTS, MAX_FIND_BLOCK_HEADERS_RESULTS},
ReadResponse, Response,
};
use crate::{
error::{CommitCheckpointVerifiedError, InvalidateError, LayeredStateError, ReconsiderError},
CommitSemanticallyVerifiedError,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg(feature = "indexer")]
pub enum Spend {
OutPoint(transparent::OutPoint),
Sprout(sprout::Nullifier),
Sapling(sapling::Nullifier),
Orchard(orchard::Nullifier),
Ironwood(ironwood::Nullifier),
}
#[cfg(feature = "indexer")]
impl From<transparent::OutPoint> for Spend {
fn from(outpoint: transparent::OutPoint) -> Self {
Self::OutPoint(outpoint)
}
}
#[cfg(feature = "indexer")]
impl From<sprout::Nullifier> for Spend {
fn from(sprout_nullifier: sprout::Nullifier) -> Self {
Self::Sprout(sprout_nullifier)
}
}
#[cfg(feature = "indexer")]
impl From<sapling::Nullifier> for Spend {
fn from(sapling_nullifier: sapling::Nullifier) -> Self {
Self::Sapling(sapling_nullifier)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum HashOrHeight {
Hash(block::Hash),
Height(block::Height),
}
impl HashOrHeight {
pub fn height_or_else<F>(self, op: F) -> Option<block::Height>
where
F: FnOnce(block::Hash) -> Option<block::Height>,
{
match self {
HashOrHeight::Hash(hash) => op(hash),
HashOrHeight::Height(height) => Some(height),
}
}
pub fn hash_or_else<F>(self, op: F) -> Option<block::Hash>
where
F: FnOnce(block::Height) -> Option<block::Hash>,
{
match self {
HashOrHeight::Hash(hash) => Some(hash),
HashOrHeight::Height(height) => op(height),
}
}
pub fn hash(&self) -> Option<block::Hash> {
if let HashOrHeight::Hash(hash) = self {
Some(*hash)
} else {
None
}
}
pub fn height(&self) -> Option<block::Height> {
if let HashOrHeight::Height(height) = self {
Some(*height)
} else {
None
}
}
pub fn new(hash_or_height: &str, tip_height: Option<block::Height>) -> Result<Self, String> {
hash_or_height
.parse()
.map(Self::Hash)
.or_else(|_| hash_or_height.parse().map(Self::Height))
.or_else(|_| {
hash_or_height
.parse()
.map_err(|_| "could not parse negative height")
.and_then(|d: HeightDiff| {
if d.is_negative() {
{
Ok(HashOrHeight::Height(
tip_height
.ok_or("missing tip height")?
.add(d)
.ok_or("underflow when adding negative height to tip")?
.next()
.map_err(|_| "height -1 needs to point to tip")?,
))
}
} else {
Err("height was not negative")
}
})
})
.map_err(|_| {
"parse error: could not convert the input string to a hash or height".to_string()
})
}
}
impl std::fmt::Display for HashOrHeight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HashOrHeight::Hash(hash) => write!(f, "{hash}"),
HashOrHeight::Height(height) => write!(f, "{}", height.0),
}
}
}
impl From<block::Hash> for HashOrHeight {
fn from(hash: block::Hash) -> Self {
Self::Hash(hash)
}
}
impl From<block::Height> for HashOrHeight {
fn from(height: block::Height) -> Self {
Self::Height(height)
}
}
impl From<(block::Height, block::Hash)> for HashOrHeight {
fn from((_height, hash): (block::Height, block::Hash)) -> Self {
hash.into()
}
}
impl From<(block::Hash, block::Height)> for HashOrHeight {
fn from((hash, _height): (block::Hash, block::Height)) -> Self {
hash.into()
}
}
impl std::str::FromStr for HashOrHeight {
type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse()
.map(Self::Hash)
.or_else(|_| s.parse().map(Self::Height))
.map_err(|_| {
SerializationError::Parse("could not convert the input string to a hash or height")
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SemanticallyVerifiedBlock {
pub block: Arc<Block>,
pub hash: block::Hash,
pub height: block::Height,
pub new_outputs: HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
pub transaction_hashes: Arc<[transaction::Hash]>,
pub deferred_pool_balance_change: Option<DeferredPoolBalanceChange>,
pub auth_data_root: Option<AuthDataRoot>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CheckpointVerifiedBlock(pub(crate) SemanticallyVerifiedBlock);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ContextuallyVerifiedBlock {
pub(crate) block: Arc<Block>,
pub(crate) hash: block::Hash,
pub(crate) height: block::Height,
pub(crate) new_outputs: HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
pub(crate) spent_outputs: HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
pub(crate) transaction_hashes: Arc<[transaction::Hash]>,
pub(crate) chain_value_pool_change: ValueBalance<NegativeAllowed>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Treestate {
pub note_commitment_trees: NoteCommitmentTrees,
pub history_tree: Arc<HistoryTree>,
}
impl Treestate {
#[allow(missing_docs)]
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
sprout: Arc<sprout::tree::NoteCommitmentTree>,
sapling: Arc<sapling::tree::NoteCommitmentTree>,
orchard: Arc<orchard::tree::NoteCommitmentTree>,
ironwood: Arc<ironwood::tree::NoteCommitmentTree>,
sapling_subtree: Option<NoteCommitmentSubtree<sapling_crypto::Node>>,
orchard_subtree: Option<NoteCommitmentSubtree<orchard::tree::Node>>,
ironwood_subtree: Option<NoteCommitmentSubtree<ironwood::tree::Node>>,
history_tree: Arc<HistoryTree>,
) -> Self {
Self {
note_commitment_trees: NoteCommitmentTrees {
sprout,
sapling,
sapling_subtree,
orchard,
orchard_subtree,
ironwood,
ironwood_subtree,
},
history_tree,
}
}
}
#[allow(missing_docs)]
pub enum FinalizableBlock {
Checkpoint {
checkpoint_verified: CheckpointVerifiedBlock,
},
Contextual {
contextually_verified: ContextuallyVerifiedBlock,
treestate: Box<Treestate>,
},
}
pub struct FinalizedBlock {
pub(super) block: Arc<Block>,
pub(super) hash: block::Hash,
pub(super) height: block::Height,
pub(super) new_outputs: HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
pub(super) transaction_hashes: Arc<[transaction::Hash]>,
pub(super) treestate: Treestate,
pub(super) deferred_pool_balance_change: Option<DeferredPoolBalanceChange>,
}
impl FinalizedBlock {
pub fn from_checkpoint_verified(block: CheckpointVerifiedBlock, treestate: Treestate) -> Self {
Self::from_semantically_verified(SemanticallyVerifiedBlock::from(block), treestate)
}
pub fn from_contextually_verified(
block: ContextuallyVerifiedBlock,
treestate: Treestate,
) -> Self {
Self::from_semantically_verified(SemanticallyVerifiedBlock::from(block), treestate)
}
fn from_semantically_verified(block: SemanticallyVerifiedBlock, treestate: Treestate) -> Self {
Self {
block: block.block,
hash: block.hash,
height: block.height,
new_outputs: block.new_outputs,
transaction_hashes: block.transaction_hashes,
treestate,
deferred_pool_balance_change: block.deferred_pool_balance_change,
}
}
}
impl FinalizableBlock {
pub fn new(contextually_verified: ContextuallyVerifiedBlock, treestate: Treestate) -> Self {
Self::Contextual {
contextually_verified,
treestate: Box::new(treestate),
}
}
#[cfg(test)]
pub fn inner_block(&self) -> Arc<Block> {
match self {
FinalizableBlock::Checkpoint {
checkpoint_verified,
} => checkpoint_verified.block.clone(),
FinalizableBlock::Contextual {
contextually_verified,
..
} => contextually_verified.block.clone(),
}
}
}
impl From<CheckpointVerifiedBlock> for FinalizableBlock {
fn from(checkpoint_verified: CheckpointVerifiedBlock) -> Self {
Self::Checkpoint {
checkpoint_verified,
}
}
}
impl From<Arc<Block>> for FinalizableBlock {
fn from(block: Arc<Block>) -> Self {
Self::from(CheckpointVerifiedBlock::from(block))
}
}
impl From<&SemanticallyVerifiedBlock> for SemanticallyVerifiedBlock {
fn from(semantically_verified: &SemanticallyVerifiedBlock) -> Self {
semantically_verified.clone()
}
}
impl ContextuallyVerifiedBlock {
pub fn with_block_and_spent_utxos(
semantically_verified: SemanticallyVerifiedBlock,
mut spent_outputs: HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
) -> Result<Self, ValueBalanceError> {
let SemanticallyVerifiedBlock {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_pool_balance_change,
auth_data_root: _,
} = semantically_verified;
spent_outputs.extend(new_outputs.clone());
Ok(Self {
block: block.clone(),
hash,
height,
new_outputs,
spent_outputs: spent_outputs.clone(),
transaction_hashes,
chain_value_pool_change: block.chain_value_pool_change(
&utxos_from_ordered_utxos(spent_outputs),
deferred_pool_balance_change,
)?,
})
}
}
impl CheckpointVerifiedBlock {
pub fn new(
block: Arc<Block>,
hash: Option<block::Hash>,
deferred_pool_balance_change: Option<DeferredPoolBalanceChange>,
) -> Self {
let mut block = Self::with_hash(block.clone(), hash.unwrap_or(block.hash()));
block.0.deferred_pool_balance_change = deferred_pool_balance_change;
block
}
pub fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
let height = block
.coinbase_height()
.expect("checkpoint verified block should have a coinbase height");
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self(SemanticallyVerifiedBlock {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_pool_balance_change: None,
auth_data_root: None,
})
}
pub fn with_precomputed_auth_data_root(mut self) -> Self {
self.0.auth_data_root = Some(self.0.block.auth_data_root());
self
}
}
impl SemanticallyVerifiedBlock {
pub fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
let height = block
.coinbase_height()
.expect("semantically verified block should have a coinbase height");
let (transaction_hashes, auth_data_root) = transaction_hashes_and_auth_data_root(&block);
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_pool_balance_change: None,
auth_data_root: Some(auth_data_root),
}
}
pub fn with_deferred_pool_balance_change(
mut self,
deferred_pool_balance_change: Option<DeferredPoolBalanceChange>,
) -> Self {
self.deferred_pool_balance_change = deferred_pool_balance_change;
self
}
}
impl From<Arc<Block>> for CheckpointVerifiedBlock {
fn from(block: Arc<Block>) -> Self {
CheckpointVerifiedBlock(SemanticallyVerifiedBlock::from(block))
}
}
impl From<Arc<Block>> for SemanticallyVerifiedBlock {
fn from(block: Arc<Block>) -> Self {
let hash = block.hash();
let height = block
.coinbase_height()
.expect("semantically verified block should have a coinbase height");
let (transaction_hashes, auth_data_root) = transaction_hashes_and_auth_data_root(&block);
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_pool_balance_change: None,
auth_data_root: Some(auth_data_root),
}
}
}
fn transaction_hashes_and_auth_data_root(
block: &Block,
) -> (Arc<[transaction::Hash]>, AuthDataRoot) {
use rayon::prelude::*;
let (transaction_hashes, auth_digests): (Vec<_>, Vec<_>) = block
.transactions
.par_iter()
.map(|transaction| transaction.txid_and_auth_digest())
.unzip();
let auth_data_root = auth_digests
.into_iter()
.map(|auth_digest| auth_digest.unwrap_or(AUTH_DIGEST_PLACEHOLDER))
.collect();
(transaction_hashes.into(), auth_data_root)
}
#[cfg(test)]
mod tests {
use super::*;
use zakura_chain::serialization::ZcashDeserializeInto;
#[test]
fn transaction_hashes_and_auth_data_root_matches_separate_computation() {
let _init_guard = zakura_test::init();
let block = zakura_test::vectors::BLOCK_MAINNET_1687107_BYTES
.zcash_deserialize_into::<Block>()
.expect("NU5 mainnet block deserializes");
let (transaction_hashes, auth_data_root) = transaction_hashes_and_auth_data_root(&block);
let expected_transaction_hashes: Vec<_> = block
.transactions
.iter()
.map(|transaction| transaction.hash())
.collect();
assert_eq!(transaction_hashes.as_ref(), expected_transaction_hashes);
assert_eq!(auth_data_root, block.auth_data_root());
}
#[test]
fn checkpoint_precomputed_auth_data_root_matches_its_block() {
let _init_guard = zakura_test::init();
let block = Arc::new(
zakura_test::vectors::BLOCK_MAINNET_1687107_BYTES
.zcash_deserialize_into::<Block>()
.expect("NU5 mainnet block deserializes"),
);
let checkpoint = CheckpointVerifiedBlock::with_hash(block.clone(), block.hash());
assert!(checkpoint.auth_data_root.is_none());
let checkpoint = checkpoint.with_precomputed_auth_data_root();
assert_eq!(checkpoint.auth_data_root, Some(block.auth_data_root()));
}
}
impl From<ContextuallyVerifiedBlock> for SemanticallyVerifiedBlock {
fn from(valid: ContextuallyVerifiedBlock) -> Self {
Self {
block: valid.block,
hash: valid.hash,
height: valid.height,
new_outputs: valid.new_outputs,
transaction_hashes: valid.transaction_hashes,
deferred_pool_balance_change: Some(DeferredPoolBalanceChange::new(
valid.chain_value_pool_change.deferred_amount(),
)),
auth_data_root: None,
}
}
}
impl From<FinalizedBlock> for SemanticallyVerifiedBlock {
fn from(finalized: FinalizedBlock) -> Self {
Self {
block: finalized.block,
hash: finalized.hash,
height: finalized.height,
new_outputs: finalized.new_outputs,
transaction_hashes: finalized.transaction_hashes,
deferred_pool_balance_change: finalized.deferred_pool_balance_change,
auth_data_root: None,
}
}
}
impl From<CheckpointVerifiedBlock> for SemanticallyVerifiedBlock {
fn from(checkpoint_verified: CheckpointVerifiedBlock) -> Self {
checkpoint_verified.0
}
}
impl Deref for CheckpointVerifiedBlock {
type Target = SemanticallyVerifiedBlock;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub trait MappedRequest: Sized + Send + 'static {
type MappedResponse;
type Error: std::error::Error + std::fmt::Display + 'static;
fn map_request(self) -> Request;
fn map_response(response: Response) -> Self::MappedResponse;
#[allow(async_fn_in_trait)]
async fn mapped_oneshot<State>(
self,
state: &mut State,
) -> Result<Self::MappedResponse, LayeredStateError<Self::Error>>
where
State: Service<Request, Response = Response, Error = BoxError>,
State::Future: Send,
{
let response = state.ready().await?.call(self.map_request()).await?;
Ok(Self::map_response(response))
}
}
pub struct CommitSemanticallyVerifiedBlockRequest(pub SemanticallyVerifiedBlock);
impl MappedRequest for CommitSemanticallyVerifiedBlockRequest {
type MappedResponse = block::Hash;
type Error = CommitSemanticallyVerifiedError;
fn map_request(self) -> Request {
Request::CommitSemanticallyVerifiedBlock(self.0)
}
fn map_response(response: Response) -> Self::MappedResponse {
match response {
Response::Committed(hash) => hash,
_ => unreachable!("wrong response variant for request"),
}
}
}
#[allow(dead_code)]
pub struct CommitCheckpointVerifiedBlockRequest(pub CheckpointVerifiedBlock);
impl MappedRequest for CommitCheckpointVerifiedBlockRequest {
type MappedResponse = block::Hash;
type Error = CommitCheckpointVerifiedError;
fn map_request(self) -> Request {
Request::CommitCheckpointVerifiedBlock(self.0)
}
fn map_response(response: Response) -> Self::MappedResponse {
match response {
Response::Committed(hash) => hash,
_ => unreachable!("wrong response variant for request"),
}
}
}
pub struct AuthenticateHeaderRootsRequest {
pub expected_state: crate::HeaderRootAuthState,
pub anchor: block::Hash,
pub start: block::Height,
pub headers: Vec<Arc<block::Header>>,
pub roots: Vec<zakura_chain::parallel::commitment_aux::BlockCommitmentRoots>,
}
impl MappedRequest for AuthenticateHeaderRootsRequest {
type MappedResponse = crate::AuthenticatedHeaderRoots;
type Error = crate::AuthenticateHeaderRootsError;
fn map_request(self) -> Request {
Request::AuthenticateHeaderRoots {
expected_state: self.expected_state,
anchor: self.anchor,
start: self.start,
headers: self.headers,
roots: self.roots,
}
}
fn map_response(response: Response) -> Self::MappedResponse {
match response {
Response::AuthenticatedHeaderRoots(success) => success,
_ => unreachable!("wrong response variant for request"),
}
}
}
#[allow(dead_code)]
pub struct InvalidateBlockRequest(pub block::Hash);
impl MappedRequest for InvalidateBlockRequest {
type MappedResponse = block::Hash;
type Error = InvalidateError;
fn map_request(self) -> Request {
Request::InvalidateBlock(self.0)
}
fn map_response(response: Response) -> Self::MappedResponse {
match response {
Response::Invalidated(hash) => hash,
_ => unreachable!("wrong response variant for request"),
}
}
}
#[allow(dead_code)]
pub struct ReconsiderBlockRequest(pub block::Hash);
impl MappedRequest for ReconsiderBlockRequest {
type MappedResponse = Vec<block::Hash>;
type Error = ReconsiderError;
fn map_request(self) -> Request {
Request::ReconsiderBlock(self.0)
}
fn map_response(response: Response) -> Self::MappedResponse {
match response {
Response::Reconsidered(hashes) => hashes,
_ => unreachable!("wrong response variant for request"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Request {
CommitSemanticallyVerifiedBlock(SemanticallyVerifiedBlock),
CommitCheckpointVerifiedBlock(CheckpointVerifiedBlock),
CommitHeaderRange {
anchor: block::Hash,
headers: Vec<Arc<block::Header>>,
body_sizes: Vec<u32>,
tree_aux_roots: Vec<zakura_chain::parallel::commitment_aux::BlockCommitmentRoots>,
},
AuthenticateHeaderRoots {
expected_state: crate::HeaderRootAuthState,
anchor: block::Hash,
start: block::Height,
headers: Vec<Arc<block::Header>>,
roots: Vec<zakura_chain::parallel::commitment_aux::BlockCommitmentRoots>,
},
Depth(block::Hash),
Tip,
BlockLocator,
Transaction(transaction::Hash),
AnyChainTransaction(transaction::Hash),
UnspentBestChainUtxo(transparent::OutPoint),
Block(HashOrHeight),
AnyChainBlock(HashOrHeight),
BlockAndSize(HashOrHeight),
BlockHeader(HashOrHeight),
AwaitUtxo(transparent::OutPoint),
FindBlockHashes {
known_blocks: Vec<block::Hash>,
stop: Option<block::Hash>,
},
FindBlockHeaders {
known_blocks: Vec<block::Hash>,
stop: Option<block::Hash>,
},
CheckBestChainTipNullifiersAndAnchors(UnminedTx),
BestChainNextMedianTimePast,
BestChainBlockHash(block::Height),
KnownBlock(block::Hash),
InvalidateBlock(block::Hash),
ReconsiderBlock(block::Hash),
CheckBlockProposalValidity(SemanticallyVerifiedBlock),
}
impl Request {
pub fn variant_name(&self) -> &'static str {
match self {
Request::CommitSemanticallyVerifiedBlock(_) => "commit_semantically_verified_block",
Request::CommitCheckpointVerifiedBlock(_) => "commit_checkpoint_verified_block",
Request::CommitHeaderRange { .. } => "commit_header_range",
Request::AuthenticateHeaderRoots { .. } => "authenticate_header_roots",
Request::AwaitUtxo(_) => "await_utxo",
Request::Depth(_) => "depth",
Request::Tip => "tip",
Request::BlockLocator => "block_locator",
Request::Transaction(_) => "transaction",
Request::AnyChainTransaction(_) => "any_chain_transaction",
Request::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo",
Request::Block(_) => "block",
Request::AnyChainBlock(_) => "any_chain_block",
Request::BlockAndSize(_) => "block_and_size",
Request::BlockHeader(_) => "block_header",
Request::FindBlockHashes { .. } => "find_block_hashes",
Request::FindBlockHeaders { .. } => "find_block_headers",
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
"best_chain_tip_nullifiers_anchors"
}
Request::BestChainNextMedianTimePast => "best_chain_next_median_time_past",
Request::BestChainBlockHash(_) => "best_chain_block_hash",
Request::KnownBlock(_) => "known_block",
Request::InvalidateBlock(_) => "invalidate_block",
Request::ReconsiderBlock(_) => "reconsider_block",
Request::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
}
}
pub fn count_metric(&self) {
metrics::counter!(
"state.requests",
"service" => "state",
"type" => self.variant_name()
)
.increment(1);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReadRequest {
UsageInfo,
IsPruned,
Tip,
FinalizedTip,
TipPoolValues,
BlockInfo(HashOrHeight),
Depth(block::Hash),
Block(HashOrHeight),
AnyChainBlock(HashOrHeight),
BlockAndSize(HashOrHeight),
BlockHeader(HashOrHeight),
Transaction(transaction::Hash),
AnyChainTransaction(transaction::Hash),
TransactionIdsForBlock(HashOrHeight),
AnyChainTransactionIdsForBlock(HashOrHeight),
UnspentBestChainUtxo(transparent::OutPoint),
AnyChainUtxo(transparent::OutPoint),
BlockLocator,
FindBlockHashes {
known_blocks: Vec<block::Hash>,
stop: Option<block::Hash>,
},
FindBlockHeaders {
known_blocks: Vec<block::Hash>,
stop: Option<block::Hash>,
},
HeadersByHeightRange {
start: block::Height,
count: u32,
},
BlockRoots {
start_height: block::Height,
count: u32,
},
BestDurableHeaderTip,
MissingBlockBodies {
from: block::Height,
limit: u32,
},
MissingBlockBodyMetadata {
from: block::Height,
limit: u32,
},
BlockSizeHints {
from: block::Height,
count: u32,
},
BlocksByHeightRange {
start: block::Height,
count: u32,
},
SaplingTree(HashOrHeight),
OrchardTree(HashOrHeight),
IronwoodTree(HashOrHeight),
SaplingSubtrees {
start_index: NoteCommitmentSubtreeIndex,
limit: Option<NoteCommitmentSubtreeIndex>,
},
OrchardSubtrees {
start_index: NoteCommitmentSubtreeIndex,
limit: Option<NoteCommitmentSubtreeIndex>,
},
IronwoodSubtrees {
start_index: NoteCommitmentSubtreeIndex,
limit: Option<NoteCommitmentSubtreeIndex>,
},
AddressBalance(HashSet<transparent::Address>),
TransactionIdsByAddresses {
addresses: HashSet<transparent::Address>,
height_range: RangeInclusive<block::Height>,
},
#[cfg(feature = "indexer")]
SpendingTransactionId(Spend),
UtxosByAddresses(HashSet<transparent::Address>),
CheckBestChainTipNullifiersAndAnchors(UnminedTx),
BestChainNextMedianTimePast,
BestChainBlockHash(block::Height),
ChainInfo,
SolutionRate {
num_blocks: usize,
height: Option<block::Height>,
},
CheckBlockProposalValidity(SemanticallyVerifiedBlock),
TipBlockSize,
NonFinalizedBlocksListener {
known_chain_tips: HashSet<block::Hash>,
},
IsTransparentOutputSpent(transparent::OutPoint),
}
impl ReadRequest {
pub fn variant_name(&self) -> &'static str {
match self {
ReadRequest::UsageInfo => "usage_info",
ReadRequest::IsPruned => "is_pruned",
ReadRequest::Tip => "tip",
ReadRequest::FinalizedTip => "finalized_tip",
ReadRequest::TipPoolValues => "tip_pool_values",
ReadRequest::BlockInfo(_) => "block_info",
ReadRequest::Depth(_) => "depth",
ReadRequest::Block(_) => "block",
ReadRequest::AnyChainBlock(_) => "any_chain_block",
ReadRequest::BlockAndSize(_) => "block_and_size",
ReadRequest::BlockHeader(_) => "block_header",
ReadRequest::Transaction(_) => "transaction",
ReadRequest::AnyChainTransaction(_) => "any_chain_transaction",
ReadRequest::TransactionIdsForBlock(_) => "transaction_ids_for_block",
ReadRequest::AnyChainTransactionIdsForBlock(_) => "any_chain_transaction_ids_for_block",
ReadRequest::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo",
ReadRequest::AnyChainUtxo { .. } => "any_chain_utxo",
ReadRequest::BlockLocator => "block_locator",
ReadRequest::FindBlockHashes { .. } => "find_block_hashes",
ReadRequest::FindBlockHeaders { .. } => "find_block_headers",
ReadRequest::HeadersByHeightRange { .. } => "headers_by_height_range",
ReadRequest::BlockRoots { .. } => "block_roots",
ReadRequest::BestDurableHeaderTip => "best_durable_header_tip",
ReadRequest::MissingBlockBodies { .. } => "missing_block_bodies",
ReadRequest::MissingBlockBodyMetadata { .. } => "missing_block_body_metadata",
ReadRequest::BlockSizeHints { .. } => "block_size_hints",
ReadRequest::BlocksByHeightRange { .. } => "blocks_by_height_range",
ReadRequest::SaplingTree { .. } => "sapling_tree",
ReadRequest::OrchardTree { .. } => "orchard_tree",
ReadRequest::IronwoodTree { .. } => "ironwood_tree",
ReadRequest::SaplingSubtrees { .. } => "sapling_subtrees",
ReadRequest::OrchardSubtrees { .. } => "orchard_subtrees",
ReadRequest::IronwoodSubtrees { .. } => "ironwood_subtrees",
ReadRequest::AddressBalance { .. } => "address_balance",
ReadRequest::TransactionIdsByAddresses { .. } => "transaction_ids_by_addresses",
ReadRequest::UtxosByAddresses(_) => "utxos_by_addresses",
ReadRequest::CheckBestChainTipNullifiersAndAnchors(_) => {
"best_chain_tip_nullifiers_anchors"
}
ReadRequest::BestChainNextMedianTimePast => "best_chain_next_median_time_past",
ReadRequest::BestChainBlockHash(_) => "best_chain_block_hash",
#[cfg(feature = "indexer")]
ReadRequest::SpendingTransactionId(_) => "spending_transaction_id",
ReadRequest::ChainInfo => "chain_info",
ReadRequest::SolutionRate { .. } => "solution_rate",
ReadRequest::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
ReadRequest::TipBlockSize => "tip_block_size",
ReadRequest::NonFinalizedBlocksListener { .. } => "non_finalized_blocks_listener",
ReadRequest::IsTransparentOutputSpent(_) => "is_transparent_output_spent",
}
}
pub fn count_metric(&self) {
metrics::counter!(
"state.requests",
"service" => "read_state",
"type" => self.variant_name()
)
.increment(1);
}
}
impl TryFrom<Request> for ReadRequest {
type Error = &'static str;
fn try_from(request: Request) -> Result<ReadRequest, Self::Error> {
match request {
Request::Tip => Ok(ReadRequest::Tip),
Request::Depth(hash) => Ok(ReadRequest::Depth(hash)),
Request::BestChainNextMedianTimePast => Ok(ReadRequest::BestChainNextMedianTimePast),
Request::BestChainBlockHash(hash) => Ok(ReadRequest::BestChainBlockHash(hash)),
Request::Block(hash_or_height) => Ok(ReadRequest::Block(hash_or_height)),
Request::AnyChainBlock(hash_or_height) => {
Ok(ReadRequest::AnyChainBlock(hash_or_height))
}
Request::BlockAndSize(hash_or_height) => Ok(ReadRequest::BlockAndSize(hash_or_height)),
Request::BlockHeader(hash_or_height) => Ok(ReadRequest::BlockHeader(hash_or_height)),
Request::Transaction(tx_hash) => Ok(ReadRequest::Transaction(tx_hash)),
Request::AnyChainTransaction(tx_hash) => Ok(ReadRequest::AnyChainTransaction(tx_hash)),
Request::UnspentBestChainUtxo(outpoint) => {
Ok(ReadRequest::UnspentBestChainUtxo(outpoint))
}
Request::BlockLocator => Ok(ReadRequest::BlockLocator),
Request::FindBlockHashes { known_blocks, stop } => {
Ok(ReadRequest::FindBlockHashes { known_blocks, stop })
}
Request::FindBlockHeaders { known_blocks, stop } => {
Ok(ReadRequest::FindBlockHeaders { known_blocks, stop })
}
Request::CheckBestChainTipNullifiersAndAnchors(tx) => {
Ok(ReadRequest::CheckBestChainTipNullifiersAndAnchors(tx))
}
Request::CommitSemanticallyVerifiedBlock(_)
| Request::CommitCheckpointVerifiedBlock(_)
| Request::CommitHeaderRange { .. }
| Request::AuthenticateHeaderRoots { .. }
| Request::InvalidateBlock(_)
| Request::ReconsiderBlock(_) => Err("ReadService does not write blocks"),
Request::AwaitUtxo(_) => Err("ReadService does not track pending UTXOs. \
Manually convert the request to ReadRequest::AnyChainUtxo, \
and handle pending UTXOs"),
Request::KnownBlock(_) => Err("ReadService does not track queued blocks"),
Request::CheckBlockProposalValidity(semantically_verified) => Ok(
ReadRequest::CheckBlockProposalValidity(semantically_verified),
),
}
}
}
#[derive(Debug)]
pub struct TimedSpan {
timer: CodeTimer,
span: tracing::Span,
}
#[cfg(test)]
mod header_root_auth_tests {
use super::*;
#[test]
fn typed_header_root_auth_request_dispatches_typed_response() {
let state = crate::HeaderRootAuthState {
authenticated_height: block::Height(10),
authenticated_hash: block::Hash([1; 32]),
completed_checkpoint_height: block::Height(20),
completed_checkpoint_hash: block::Hash([2; 32]),
header_witness: None,
};
let request = AuthenticateHeaderRootsRequest {
expected_state: state,
anchor: state.authenticated_hash,
start: block::Height(11),
headers: Vec::new(),
roots: Vec::new(),
}
.map_request();
assert_eq!(request.variant_name(), "authenticate_header_roots");
assert!(matches!(
request,
Request::AuthenticateHeaderRoots {
expected_state,
anchor,
start: block::Height(11),
..
} if expected_state == state && anchor == state.authenticated_hash
));
let success = crate::AuthenticatedHeaderRoots {
state,
update: crate::HeaderRootAuthUpdate::Advanced {
authenticated: block::Height(11)..=block::Height(19),
},
};
assert_eq!(
AuthenticateHeaderRootsRequest::map_response(Response::AuthenticatedHeaderRoots(
success.clone()
)),
success
);
}
}
impl TimedSpan {
pub fn new(timer: CodeTimer, span: tracing::Span) -> Self {
Self { timer, span }
}
#[track_caller]
pub fn spawn_blocking<T: Send + 'static>(
mut self,
f: impl FnOnce() -> Result<T, BoxError> + Send + 'static,
) -> Pin<Box<dyn futures::Future<Output = Result<T, BoxError>> + Send>> {
let location = std::panic::Location::caller();
tokio::task::spawn_blocking(move || {
self.span.in_scope(move || {
let result = f();
self.timer
.finish_inner(Some(location.file()), Some(location.line()), "");
result
})
})
.wait_for_panics()
}
}