zebra_state/service/read/address/
tx_id.rs1use std::{
15 collections::{BTreeMap, HashSet},
16 ops::RangeInclusive,
17};
18
19use zebra_chain::{block::Height, transaction, transparent};
20
21use crate::{
22 service::{
23 finalized_state::ZebraDb, non_finalized_state::Chain, read::FINALIZED_STATE_QUERY_RETRIES,
24 },
25 BoxError, TransactionLocation,
26};
27
28pub fn transparent_tx_ids<C>(
35 chain: Option<C>,
36 db: &ZebraDb,
37 addresses: HashSet<transparent::Address>,
38 query_height_range: RangeInclusive<Height>,
39) -> Result<BTreeMap<TransactionLocation, transaction::Hash>, BoxError>
40where
41 C: AsRef<Chain>,
42{
43 let mut tx_id_error = None;
44
45 for _ in 0..=FINALIZED_STATE_QUERY_RETRIES {
50 let (finalized_tx_ids, finalized_tip_range) =
51 finalized_transparent_tx_ids(db, &addresses, query_height_range.clone());
52
53 let chain_tx_id_changes = chain_transparent_tx_id_changes(
55 chain.as_ref(),
56 &addresses,
57 finalized_tip_range,
58 query_height_range.clone(),
59 );
60
61 match chain_tx_id_changes {
63 Ok(chain_tx_id_changes) => {
64 let tx_ids = apply_tx_id_changes(finalized_tx_ids, chain_tx_id_changes);
65
66 return Ok(tx_ids);
67 }
68
69 Err(error) => tx_id_error = Some(Err(error)),
70 }
71 }
72
73 tx_id_error.expect("unexpected missing error: attempts should set error or return")
74}
75
76fn finalized_transparent_tx_ids(
83 db: &ZebraDb,
84 addresses: &HashSet<transparent::Address>,
85 query_height_range: RangeInclusive<Height>,
86) -> (
87 BTreeMap<TransactionLocation, transaction::Hash>,
88 Option<RangeInclusive<Height>>,
89) {
90 let start_finalized_tip = db.finalized_tip_height();
96
97 let finalized_tx_ids = db.partial_finalized_transparent_tx_ids(addresses, query_height_range);
98
99 let end_finalized_tip = db.finalized_tip_height();
100
101 let finalized_tip_range = if let (Some(start_finalized_tip), Some(end_finalized_tip)) =
102 (start_finalized_tip, end_finalized_tip)
103 {
104 Some(start_finalized_tip..=end_finalized_tip)
105 } else {
106 None
108 };
109
110 (finalized_tx_ids, finalized_tip_range)
111}
112
113fn chain_transparent_tx_id_changes<C>(
120 chain: Option<C>,
121 addresses: &HashSet<transparent::Address>,
122 finalized_tip_range: Option<RangeInclusive<Height>>,
123 query_height_range: RangeInclusive<Height>,
124) -> Result<BTreeMap<TransactionLocation, transaction::Hash>, BoxError>
125where
126 C: AsRef<Chain>,
127{
128 let address_count = addresses.len();
129
130 let finalized_tip_range = match finalized_tip_range {
131 Some(finalized_tip_range) => finalized_tip_range,
132 None => {
133 assert!(
134 chain.is_none(),
135 "unexpected non-finalized chain when finalized state is empty"
136 );
137
138 debug!(
139 ?finalized_tip_range,
140 ?address_count,
141 "chain address tx ID query: state is empty, no tx IDs available",
142 );
143
144 return Ok(Default::default());
145 }
146 };
147
148 let required_min_non_finalized_root = finalized_tip_range.start().0 + 1;
159
160 let finalized_tip_status = required_min_non_finalized_root..=finalized_tip_range.end().0;
164 let finalized_tip_status = if finalized_tip_status.is_empty() {
165 let finalized_tip_height = *finalized_tip_range.end();
166 Ok(finalized_tip_height)
167 } else {
168 let required_non_finalized_overlap = finalized_tip_status;
169 Err(required_non_finalized_overlap)
170 };
171
172 if chain.is_none() {
173 if address_count <= 1 || finalized_tip_status.is_ok() {
174 debug!(
175 ?finalized_tip_status,
176 ?required_min_non_finalized_root,
177 ?finalized_tip_range,
178 ?address_count,
179 "chain address tx ID query: \
180 finalized chain is consistent, and non-finalized chain is empty",
181 );
182
183 return Ok(Default::default());
184 } else {
185 debug!(
188 ?finalized_tip_status,
189 ?required_min_non_finalized_root,
190 ?finalized_tip_range,
191 ?address_count,
192 "chain address tx ID query: \
193 finalized tip query was inconsistent, but non-finalized chain is empty",
194 );
195
196 return Err("unable to get tx IDs: \
197 state was committing a block, and non-finalized chain is empty"
198 .into());
199 }
200 }
201
202 let chain = chain.unwrap();
203 let chain = chain.as_ref();
204
205 let non_finalized_root = chain.non_finalized_root_height();
206 let non_finalized_tip = chain.non_finalized_tip_height();
207
208 assert!(
209 non_finalized_root.0 <= required_min_non_finalized_root,
210 "unexpected chain gap: the best chain is updated after its previous root is finalized",
211 );
212
213 match finalized_tip_status {
214 Ok(finalized_tip_height) => {
215 if finalized_tip_height >= non_finalized_tip {
218 debug!(
219 ?non_finalized_root,
220 ?non_finalized_tip,
221 ?finalized_tip_status,
222 ?finalized_tip_range,
223 ?address_count,
224 "chain address tx ID query: \
225 non-finalized blocks have all been finalized, no new UTXO changes",
226 );
227
228 return Ok(Default::default());
229 }
230 }
231
232 Err(ref required_non_finalized_overlap) => {
233 if address_count > 1 && *required_non_finalized_overlap.end() > non_finalized_tip.0 {
236 debug!(
237 ?non_finalized_root,
238 ?non_finalized_tip,
239 ?finalized_tip_status,
240 ?finalized_tip_range,
241 ?address_count,
242 "chain address tx ID query: \
243 finalized tip query was inconsistent, \
244 some inconsistent blocks are missing from the non-finalized chain, \
245 and the query has multiple addresses",
246 );
247
248 return Err("unable to get tx IDs: \
249 state was committing a block, \
250 that is missing from the non-finalized chain, \
251 and the query has multiple addresses"
252 .into());
253 }
254
255 assert!(
258 address_count <= 1
259 || required_non_finalized_overlap
260 .clone()
261 .all(|height| chain.blocks.contains_key(&Height(height))),
262 "tx ID query inconsistency: \
263 chain must contain required overlap blocks \
264 or query must only have one address",
265 );
266 }
267 }
268
269 Ok(chain.partial_transparent_tx_ids(addresses, query_height_range))
270}
271
272fn apply_tx_id_changes(
274 finalized_tx_ids: BTreeMap<TransactionLocation, transaction::Hash>,
275 chain_tx_ids: BTreeMap<TransactionLocation, transaction::Hash>,
276) -> BTreeMap<TransactionLocation, transaction::Hash> {
277 finalized_tx_ids.into_iter().chain(chain_tx_ids).collect()
280}