use std::{
collections::{HashMap, HashSet},
ops::{Add, Deref, DerefMut, RangeInclusive},
pin::Pin,
sync::Arc,
};
use tower::{BoxError, Service, ServiceExt};
use zebra_chain::{
amount::{DeferredPoolBalanceChange, NegativeAllowed},
block::{self, Block, HeightDiff},
diagnostic::{task::WaitForPanics, CodeTimer},
history_tree::HistoryTree,
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),
}
#[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)
}
}
#[cfg(feature = "indexer")]
impl From<orchard::Nullifier> for Spend {
fn from(orchard_nullifier: orchard::Nullifier) -> Self {
Self::Orchard(orchard_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>,
}
#[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)]
pub(crate) fn new(
sprout: Arc<sprout::tree::NoteCommitmentTree>,
sapling: Arc<sapling::tree::NoteCommitmentTree>,
orchard: Arc<orchard::tree::NoteCommitmentTree>,
sapling_subtree: Option<NoteCommitmentSubtree<sapling_crypto::Node>>,
orchard_subtree: Option<NoteCommitmentSubtree<orchard::tree::Node>>,
history_tree: Arc<HistoryTree>,
) -> Self {
Self {
note_commitment_trees: NoteCommitmentTrees {
sprout,
sapling,
sapling_subtree,
orchard,
orchard_subtree,
},
history_tree,
}
}
}
#[allow(missing_docs)]
pub enum FinalizableBlock {
Checkpoint {
checkpoint_verified: CheckpointVerifiedBlock,
},
Contextual {
contextually_verified: ContextuallyVerifiedBlock,
treestate: 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,
}
}
#[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,
} = 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.deferred_pool_balance_change = deferred_pool_balance_change;
block
}
pub fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
Self(SemanticallyVerifiedBlock::with_hash(block, hash))
}
}
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: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_pool_balance_change: None,
}
}
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: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_pool_balance_change: None,
}
}
}
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(),
)),
}
}
}
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,
}
}
}
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
}
}
impl DerefMut for CheckpointVerifiedBlock {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut 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"),
}
}
}
#[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),
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::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,
Tip,
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>,
},
SaplingTree(HashOrHeight),
OrchardTree(HashOrHeight),
SaplingSubtrees {
start_index: NoteCommitmentSubtreeIndex,
limit: Option<NoteCommitmentSubtreeIndex>,
},
OrchardSubtrees {
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,
IsTransparentOutputSpent(transparent::OutPoint),
}
impl ReadRequest {
pub fn variant_name(&self) -> &'static str {
match self {
ReadRequest::UsageInfo => "usage_info",
ReadRequest::Tip => "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::SaplingTree { .. } => "sapling_tree",
ReadRequest::OrchardTree { .. } => "orchard_tree",
ReadRequest::SaplingSubtrees { .. } => "sapling_subtrees",
ReadRequest::OrchardSubtrees { .. } => "orchard_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::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,
}
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()
}
}