use std::sync::Arc;
use chrono::{DateTime, Utc};
use zakura_chain::{
block::{self, Block, Height},
block_info::BlockInfo,
serialization::ZcashSerialize as _,
transaction::{self, Transaction},
transparent::{self, Utxo},
};
use crate::{
response::{AnyTx, MinedTx, ParentInputs},
service::{
finalized_state::ZakuraDb,
non_finalized_state::{Chain, NonFinalizedState},
read::tip_height,
},
HashOrHeight,
};
#[cfg(feature = "indexer")]
use crate::request::Spend;
pub fn any_block<'a, C: AsRef<Chain> + 'a>(
mut chains: impl Iterator<Item = &'a C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<Block>> {
chains
.find_map(|c| c.as_ref().block(hash_or_height))
.map(|contextual| contextual.block.clone())
.or_else(|| db.block(hash_or_height))
}
pub fn block<C>(chain: Option<C>, db: &ZakuraDb, hash_or_height: HashOrHeight) -> Option<Arc<Block>>
where
C: AsRef<Chain>,
{
any_block(chain.iter(), db, hash_or_height)
}
pub fn block_and_size<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<(Arc<Block>, usize)>
where
C: AsRef<Chain>,
{
chain
.as_ref()
.and_then(|chain| chain.as_ref().block(hash_or_height))
.map(|contextual| {
let size = contextual.block.zcash_serialize_to_vec().unwrap().len();
(contextual.block.clone(), size)
})
.or_else(|| db.block_and_size(hash_or_height))
}
pub fn block_header<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<block::Header>>
where
C: AsRef<Chain>,
{
chain
.as_ref()
.and_then(|chain| chain.as_ref().block(hash_or_height))
.map(|contextual| contextual.block.header.clone())
.or_else(|| db.block_header(hash_or_height))
}
fn transaction<C>(
chain: Option<C>,
db: &ZakuraDb,
hash: transaction::Hash,
) -> Option<(Arc<Transaction>, Height, DateTime<Utc>)>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| {
chain
.as_ref()
.transaction(hash)
.map(|(tx, height, time)| (tx.clone(), height, time))
})
.or_else(|| db.transaction(hash))
}
pub fn mined_transaction<C>(
chain: Option<C>,
db: &ZakuraDb,
hash: transaction::Hash,
) -> Option<MinedTx>
where
C: AsRef<Chain>,
{
let chain = chain.as_ref();
let (tx, height, time) = transaction(chain, db, hash)?;
let confirmations = 1 + tip_height(chain, db)?.0 - height.0;
Some(MinedTx::new(tx, height, confirmations, time))
}
pub fn any_transaction<'a>(
chains: impl Iterator<Item = &'a Arc<Chain>>,
db: &ZakuraDb,
hash: transaction::Hash,
) -> Option<AnyTx> {
let mut chains = chains.peekable();
let best_chain = chains.peek().copied();
let (tx, height, time, in_best_chain, containing_chain) = chains
.enumerate()
.find_map(|(i, chain)| {
chain
.as_ref()
.transaction(hash)
.map(|(tx, height, time)| (tx.clone(), height, time, i == 0, Some(chain)))
})
.or_else(|| {
db.transaction(hash)
.map(|(tx, height, time)| (tx.clone(), height, time, true, None))
})?;
if in_best_chain {
let confirmations = 1 + tip_height(best_chain, db)?.0 - height.0;
Some(AnyTx::Mined(MinedTx::new(tx, height, confirmations, time)))
} else {
let block_hash = containing_chain?.block(height.into())?.hash;
Some(AnyTx::Side((tx, block_hash)))
}
}
pub fn transaction_hashes_for_block<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<[transaction::Hash]>>
where
C: AsRef<Chain>,
{
chain
.as_ref()
.and_then(|chain| chain.as_ref().transaction_hashes_for_block(hash_or_height))
.or_else(|| db.transaction_hashes_for_block(hash_or_height))
}
pub fn transaction_hashes_for_any_block<'a>(
chains: impl Iterator<Item = &'a Arc<Chain>>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<(Arc<[transaction::Hash]>, bool)> {
chains
.enumerate()
.find_map(|(i, chain)| {
chain
.as_ref()
.transaction_hashes_for_block(hash_or_height)
.map(|hashes| (hashes.clone(), i == 0))
})
.or_else(|| {
db.transaction_hashes_for_block(hash_or_height)
.map(|hashes| (hashes, true))
})
}
pub fn utxo<C>(chain: Option<C>, db: &ZakuraDb, outpoint: transparent::OutPoint) -> Option<Utxo>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| chain.as_ref().created_utxo(&outpoint))
.or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
}
pub fn unspent_utxo<C>(
chain: Option<C>,
db: &ZakuraDb,
outpoint: transparent::OutPoint,
) -> Option<Utxo>
where
C: AsRef<Chain>,
{
match chain {
Some(chain) if chain.as_ref().spent_utxos.contains_key(&outpoint) => None,
chain => utxo(chain, db, outpoint),
}
}
pub fn parent_inputs(
non_finalized_state: &NonFinalizedState,
db: &ZakuraDb,
(finalized_height, finalized_hash): (Height, block::Hash),
parent: block::Hash,
outpoints: &[transparent::OutPoint],
) -> ParentInputs {
let first_missing = |chain: Option<&Arc<Chain>>| {
outpoints
.iter()
.copied()
.find(|outpoint| unspent_utxo(chain, db, *outpoint).is_none())
.map_or(ParentInputs::Inconclusive, ParentInputs::Missing)
};
if parent == finalized_hash {
return first_missing(None);
}
if db.height(parent).is_some() {
return ParentInputs::ParentUnavailable;
}
let Some(chain) = non_finalized_state
.find_chain(|chain| chain.non_finalized_tip_hash() == parent)
.or_else(|| {
non_finalized_state
.chain_iter()
.find_map(|chain| chain.fork(parent))
.map(Arc::new)
})
else {
return ParentInputs::ParentUnavailable;
};
let root_height = chain.non_finalized_root_height();
let continues_finalized_tip = if finalized_height.next().ok() == Some(root_height) {
chain
.blocks
.values()
.next()
.map(|root| root.block.header.previous_block_hash)
== Some(finalized_hash)
} else {
root_height <= finalized_height
&& chain.hash_by_height(finalized_height) == Some(finalized_hash)
};
if !continues_finalized_tip {
return ParentInputs::Inconclusive;
}
first_missing(Some(&chain))
}
#[cfg(feature = "indexer")]
pub fn spending_transaction_hash<C>(
chain: Option<C>,
db: &ZakuraDb,
spend: Spend,
) -> Result<Option<transaction::Hash>, crate::TransparentIndexesUnavailable>
where
C: AsRef<Chain>,
{
if matches!(spend, Spend::OutPoint(_)) {
db.check_transparent_indexes_available()?;
}
match chain.and_then(|chain| chain.as_ref().spending_transaction_hash(&spend)) {
Some(hash) => Ok(Some(hash)),
None => db.spending_transaction_hash(&spend),
}
}
pub fn any_utxo(
non_finalized_state: NonFinalizedState,
db: &ZakuraDb,
outpoint: transparent::OutPoint,
) -> Option<Utxo> {
non_finalized_state
.any_utxo(&outpoint)
.or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
}
pub fn block_info<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<BlockInfo>
where
C: AsRef<Chain>,
{
any_block_info(chain.iter(), db, hash_or_height)
}
pub fn block_info_by_hash_or_best_chain_height(
non_finalized_state: &NonFinalizedState,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<BlockInfo> {
match hash_or_height {
HashOrHeight::Hash(_) => {
any_block_info(non_finalized_state.chain_iter(), db, hash_or_height)
}
HashOrHeight::Height(_) => block_info(non_finalized_state.best_chain(), db, hash_or_height),
}
}
pub fn any_block_info<'a, C: AsRef<Chain> + 'a>(
mut chains: impl Iterator<Item = &'a C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<BlockInfo> {
chains
.find_map(|chain| chain.as_ref().block_info(hash_or_height))
.or_else(|| db.block_info(hash_or_height))
}