use std::{collections::HashSet, sync::Arc};
use zebra_chain::{
amount::{self, Amount, NegativeAllowed, NonNegative},
block::Height,
transparent,
};
use crate::{
service::{
finalized_state::ZebraDb, non_finalized_state::Chain, read::FINALIZED_STATE_QUERY_RETRIES,
},
BoxError,
};
pub fn transparent_balance(
chain: Option<Arc<Chain>>,
db: &ZebraDb,
addresses: HashSet<transparent::Address>,
) -> Result<(Amount<NonNegative>, u64), BoxError> {
let mut balance_result = finalized_transparent_balance(db, &addresses);
for _ in 0..FINALIZED_STATE_QUERY_RETRIES {
if balance_result.is_ok() {
break;
}
balance_result = finalized_transparent_balance(db, &addresses);
}
let (mut balance, finalized_tip) = balance_result?;
if let Some(chain) = chain {
let chain_balance_change =
chain_transparent_balance_change(chain, &addresses, finalized_tip);
balance = apply_balance_change(balance, chain_balance_change)?;
}
Ok(balance)
}
fn finalized_transparent_balance(
db: &ZebraDb,
addresses: &HashSet<transparent::Address>,
) -> Result<((Amount<NonNegative>, u64), Option<Height>), BoxError> {
let original_finalized_tip = db.tip();
let finalized_balance = db.partial_finalized_transparent_balance(addresses);
let finalized_tip = db.tip();
if original_finalized_tip != finalized_tip {
return Err("unable to get balance: state was committing a block".into());
}
let finalized_tip = finalized_tip.map(|(height, _hash)| height);
Ok((finalized_balance, finalized_tip))
}
fn chain_transparent_balance_change(
mut chain: Arc<Chain>,
addresses: &HashSet<transparent::Address>,
finalized_tip: Option<Height>,
) -> (Amount<NegativeAllowed>, u64) {
let required_chain_root = finalized_tip
.map(|tip| (tip + 1).unwrap())
.unwrap_or(Height(0));
let chain = Arc::make_mut(&mut chain);
assert!(
chain.non_finalized_root_height() <= required_chain_root,
"unexpected chain gap: the best chain is updated after its previous root is finalized"
);
let chain_tip = chain.non_finalized_tip_height();
if chain_tip < required_chain_root {
return (Amount::zero(), 0);
}
while chain.non_finalized_root_height() < required_chain_root {
chain.pop_root();
}
chain.partial_transparent_balance_change(addresses)
}
fn apply_balance_change(
(finalized_balance, finalized_received): (Amount<NonNegative>, u64),
(chain_balance_change, chain_received_change): (Amount<NegativeAllowed>, u64),
) -> amount::Result<(Amount<NonNegative>, u64)> {
let balance = finalized_balance.constrain()? + chain_balance_change;
let received = finalized_received.saturating_add(chain_received_change);
Ok((balance?.constrain()?, received))
}