use std::{collections::HashMap, 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,
},
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 (tip_height, tip_hash) = tip(chain, db)?;
let confirmations = 1 + tip_height.0 - height.0;
Some(MinedTx::new(tx, height, confirmations, time, tip_hash))
}
pub fn spent_outputs_for_block<C>(
chain: Option<C>,
db: &ZebraDb,
hash_or_height: HashOrHeight,
) -> Option<HashMap<transparent::OutPoint, Utxo>>
where
C: AsRef<Chain>,
{
let chain = chain.as_ref();
let block = block(chain, db, hash_or_height)?;
let is_finalized = match (block.coinbase_height(), db.finalized_tip_height()) {
(Some(height), Some(finalized_tip)) => height <= finalized_tip,
_ => false,
};
let mut spent_outputs = HashMap::new();
let mut by_parent: HashMap<transaction::Hash, Vec<transparent::OutPoint>> = HashMap::new();
for tx in block.transactions.iter().filter(|tx| !tx.is_coinbase()) {
for outpoint in tx.spent_outpoints() {
let unspent = if is_finalized {
None
} else {
utxo(chain, db, outpoint)
};
if let Some(utxo) = unspent {
spent_outputs.insert(outpoint, utxo);
} else {
by_parent.entry(outpoint.hash).or_default().push(outpoint);
}
}
}
for (parent_hash, outpoints) in by_parent {
let Some((parent_tx, height, _)) = transaction(chain, db, parent_hash) else {
continue;
};
let from_coinbase = parent_tx.is_coinbase();
let outputs = parent_tx.outputs();
for outpoint in outpoints {
if let Some(output) = outputs.get(outpoint.index as usize) {
spent_outputs.insert(outpoint, Utxo::new(output.clone(), height, from_coinbase));
}
}
}
Some(spent_outputs)
}
pub fn any_transaction<'a>(
chains: impl Iterator<Item = &'a Arc<Chain>>,
db: &ZebraDb,
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 (tip_height, tip_hash) = tip(best_chain, db)?;
let confirmations = 1 + tip_height.0 - height.0;
Some(AnyTx::Mined(MinedTx::new(
tx,
height,
confirmations,
time,
tip_hash,
)))
} 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))
}