zebra_state/service/read/address/
balance.rs1use std::{collections::HashSet, sync::Arc};
15
16use zebra_chain::{
17 amount::{self, Amount, NegativeAllowed, NonNegative},
18 block::Height,
19 transparent,
20};
21
22use crate::{
23 service::{
24 finalized_state::ZebraDb, non_finalized_state::Chain, read::FINALIZED_STATE_QUERY_RETRIES,
25 },
26 BoxError,
27};
28
29pub fn transparent_balance(
33 chain: Option<Arc<Chain>>,
34 db: &ZebraDb,
35 addresses: HashSet<transparent::Address>,
36) -> Result<(Amount<NonNegative>, u64), BoxError> {
37 let mut balance_result = finalized_transparent_balance(db, &addresses);
38
39 for _ in 0..FINALIZED_STATE_QUERY_RETRIES {
43 if balance_result.is_ok() {
44 break;
45 }
46
47 balance_result = finalized_transparent_balance(db, &addresses);
48 }
49
50 let (mut balance, finalized_tip) = balance_result?;
51
52 if let Some(chain) = chain {
54 let chain_balance_change =
55 chain_transparent_balance_change(chain, &addresses, finalized_tip);
56
57 balance = apply_balance_change(balance, chain_balance_change).expect(
58 "unexpected amount overflow: value balances are valid, so partial sum should be valid",
59 );
60 }
61
62 Ok(balance)
63}
64
65fn finalized_transparent_balance(
72 db: &ZebraDb,
73 addresses: &HashSet<transparent::Address>,
74) -> Result<((Amount<NonNegative>, u64), Option<Height>), BoxError> {
75 let original_finalized_tip = db.tip();
81
82 let finalized_balance = db.partial_finalized_transparent_balance(addresses);
83
84 let finalized_tip = db.tip();
85
86 if original_finalized_tip != finalized_tip {
87 return Err("unable to get balance: state was committing a block".into());
89 }
90
91 let finalized_tip = finalized_tip.map(|(height, _hash)| height);
92
93 Ok((finalized_balance, finalized_tip))
94}
95
96fn chain_transparent_balance_change(
101 mut chain: Arc<Chain>,
102 addresses: &HashSet<transparent::Address>,
103 finalized_tip: Option<Height>,
104) -> (Amount<NegativeAllowed>, u64) {
105 let required_chain_root = finalized_tip
111 .map(|tip| (tip + 1).unwrap())
112 .unwrap_or(Height(0));
113
114 let chain = Arc::make_mut(&mut chain);
115
116 assert!(
117 chain.non_finalized_root_height() <= required_chain_root,
118 "unexpected chain gap: the best chain is updated after its previous root is finalized"
119 );
120
121 let chain_tip = chain.non_finalized_tip_height();
122
123 if chain_tip < required_chain_root {
126 return (Amount::zero(), 0);
127 }
128
129 while chain.non_finalized_root_height() < required_chain_root {
132 chain.pop_root();
134 }
135
136 chain.partial_transparent_balance_change(addresses)
137}
138
139fn apply_balance_change(
142 (finalized_balance, finalized_received): (Amount<NonNegative>, u64),
143 (chain_balance_change, chain_received_change): (Amount<NegativeAllowed>, u64),
144) -> amount::Result<(Amount<NonNegative>, u64)> {
145 let balance = finalized_balance.constrain()? + chain_balance_change;
146 let received = finalized_received.saturating_add(chain_received_change);
149 Ok((balance?.constrain()?, received))
150}