use std::sync::Arc;
use chrono::{DateTime, Utc};
use zebra_chain::{
block::{self, Block, Height},
block_info::BlockInfo,
serialization::ZcashSerialize as _,
transaction::{self, Transaction},
transparent::{self, Utxo},
};
use crate::{
response::{AnyTx, MinedTx},
service::{
finalized_state::ZebraDb,
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: &ZebraDb,
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: &ZebraDb, 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: &ZebraDb,
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: &ZebraDb,
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: &ZebraDb,
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: &ZebraDb,
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: &ZebraDb,
hash: transaction::Hash,
) -> Option<AnyTx> {
let mut best_chain = None;
let (tx, height, time, in_best_chain, containing_chain) = chains
.enumerate()
.find_map(|(i, chain)| {
chain.as_ref().transaction(hash).map(|(tx, height, time)| {
if i == 0 {
best_chain = Some(chain);
}
(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: &ZebraDb,
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: &ZebraDb,
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: &ZebraDb, 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: &ZebraDb,
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),
}
}
#[cfg(feature = "indexer")]
pub fn spending_transaction_hash<C>(
chain: Option<C>,
db: &ZebraDb,
spend: Spend,
) -> Option<transaction::Hash>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| chain.as_ref().spending_transaction_hash(&spend))
.or_else(|| db.spending_transaction_hash(&spend))
}
pub fn any_utxo(
non_finalized_state: NonFinalizedState,
db: &ZebraDb,
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: &ZebraDb,
hash_or_height: HashOrHeight,
) -> Option<BlockInfo>
where
C: AsRef<Chain>,
{
chain
.as_ref()
.and_then(|chain| chain.as_ref().block_info(hash_or_height))
.or_else(|| db.block_info(hash_or_height))
}