Skip to main content

zebra_state/service/read/
block.rs

1//! Shared block, header, and transaction reading code.
2//!
3//! In the functions in this module:
4//!
5//! The block write task commits blocks to the finalized state before updating
6//! `chain` or `non_finalized_state` with a cached copy of the non-finalized chains
7//! in `NonFinalizedState.chain_set`. Then the block commit task can
8//! commit additional blocks to the finalized state after we've cloned the
9//! `chain` or `non_finalized_state`.
10//!
11//! This means that some blocks can be in both:
12//! - the cached [`Chain`] or [`NonFinalizedState`], and
13//! - the shared finalized [`ZebraDb`] reference.
14
15use std::sync::Arc;
16
17use chrono::{DateTime, Utc};
18
19use zebra_chain::{
20    block::{self, Block, Height},
21    block_info::BlockInfo,
22    serialization::ZcashSerialize as _,
23    transaction::{self, Transaction},
24    transparent::{self, Utxo},
25};
26
27use crate::{
28    response::{AnyTx, MinedTx},
29    service::{
30        finalized_state::ZebraDb,
31        non_finalized_state::{Chain, NonFinalizedState},
32        read::tip_height,
33    },
34    HashOrHeight,
35};
36
37#[cfg(feature = "indexer")]
38use crate::request::Spend;
39
40/// Returns the [`Block`] with [`block::Hash`] or
41/// [`Height`], if it exists in the non-finalized `chains` or finalized `db`.
42pub fn any_block<'a, C: AsRef<Chain> + 'a>(
43    mut chains: impl Iterator<Item = &'a C>,
44    db: &ZebraDb,
45    hash_or_height: HashOrHeight,
46) -> Option<Arc<Block>> {
47    // # Correctness
48    //
49    // Since blocks are the same in the finalized and non-finalized state, we
50    // check the most efficient alternative first. (`chain` is always in memory,
51    // but `db` stores blocks on disk, with a memory cache.)
52    chains
53        .find_map(|c| c.as_ref().block(hash_or_height))
54        .map(|contextual| contextual.block.clone())
55        .or_else(|| db.block(hash_or_height))
56}
57
58/// Returns the [`Block`] with [`block::Hash`] or
59/// [`Height`], if it exists in the non-finalized `chain` or finalized `db`.
60pub fn block<C>(chain: Option<C>, db: &ZebraDb, hash_or_height: HashOrHeight) -> Option<Arc<Block>>
61where
62    C: AsRef<Chain>,
63{
64    any_block(chain.iter(), db, hash_or_height)
65}
66
67/// Returns the [`Block`] with [`block::Hash`] or
68/// [`Height`], if it exists in the non-finalized `chain` or finalized `db`.
69pub fn block_and_size<C>(
70    chain: Option<C>,
71    db: &ZebraDb,
72    hash_or_height: HashOrHeight,
73) -> Option<(Arc<Block>, usize)>
74where
75    C: AsRef<Chain>,
76{
77    // # Correctness
78    //
79    // Since blocks are the same in the finalized and non-finalized state, we
80    // check the most efficient alternative first. (`chain` is always in memory,
81    // but `db` stores blocks on disk, with a memory cache.)
82    chain
83        .as_ref()
84        .and_then(|chain| chain.as_ref().block(hash_or_height))
85        .map(|contextual| {
86            let size = contextual.block.zcash_serialize_to_vec().unwrap().len();
87            (contextual.block.clone(), size)
88        })
89        .or_else(|| db.block_and_size(hash_or_height))
90}
91
92/// Returns the [`block::Header`] with [`block::Hash`] or
93/// [`Height`], if it exists in the non-finalized `chain` or finalized `db`.
94pub fn block_header<C>(
95    chain: Option<C>,
96    db: &ZebraDb,
97    hash_or_height: HashOrHeight,
98) -> Option<Arc<block::Header>>
99where
100    C: AsRef<Chain>,
101{
102    // # Correctness
103    //
104    // Since blocks are the same in the finalized and non-finalized state, we
105    // check the most efficient alternative first. (`chain` is always in memory,
106    // but `db` stores blocks on disk, with a memory cache.)
107    chain
108        .as_ref()
109        .and_then(|chain| chain.as_ref().block(hash_or_height))
110        .map(|contextual| contextual.block.header.clone())
111        .or_else(|| db.block_header(hash_or_height))
112}
113
114/// Returns the [`Transaction`] with [`transaction::Hash`], if it exists in the
115/// non-finalized `chain` or finalized `db`.
116fn transaction<C>(
117    chain: Option<C>,
118    db: &ZebraDb,
119    hash: transaction::Hash,
120) -> Option<(Arc<Transaction>, Height, DateTime<Utc>)>
121where
122    C: AsRef<Chain>,
123{
124    // # Correctness
125    //
126    // Since transactions are the same in the finalized and non-finalized state,
127    // we check the most efficient alternative first. (`chain` is always in
128    // memory, but `db` stores transactions on disk, with a memory cache.)
129    chain
130        .and_then(|chain| {
131            chain
132                .as_ref()
133                .transaction(hash)
134                .map(|(tx, height, time)| (tx.clone(), height, time))
135        })
136        .or_else(|| db.transaction(hash))
137}
138
139/// Returns a [`MinedTx`] for a [`Transaction`] with [`transaction::Hash`],
140/// if one exists in the non-finalized `chain` or finalized `db`.
141pub fn mined_transaction<C>(
142    chain: Option<C>,
143    db: &ZebraDb,
144    hash: transaction::Hash,
145) -> Option<MinedTx>
146where
147    C: AsRef<Chain>,
148{
149    // # Correctness
150    //
151    // It is ok to do this lookup in two different calls. Finalized state updates
152    // can only add overlapping blocks, and hashes are unique.
153    let chain = chain.as_ref();
154
155    let (tx, height, time) = transaction(chain, db, hash)?;
156    let confirmations = 1 + tip_height(chain, db)?.0 - height.0;
157
158    Some(MinedTx::new(tx, height, confirmations, time))
159}
160
161/// Returns a [`AnyTx`] for a [`Transaction`] with [`transaction::Hash`],
162/// if one exists in any chain in `chains` or finalized `db`.
163/// The first chain in `chains` must be the best chain.
164pub fn any_transaction<'a>(
165    chains: impl Iterator<Item = &'a Arc<Chain>>,
166    db: &ZebraDb,
167    hash: transaction::Hash,
168) -> Option<AnyTx> {
169    // # Correctness
170    //
171    // It is ok to do this lookup in multiple different calls. Finalized state updates
172    // can only add overlapping blocks, and hashes are unique.
173    let mut best_chain = None;
174    let (tx, height, time, in_best_chain, containing_chain) = chains
175        .enumerate()
176        .find_map(|(i, chain)| {
177            chain.as_ref().transaction(hash).map(|(tx, height, time)| {
178                if i == 0 {
179                    best_chain = Some(chain);
180                }
181                (tx.clone(), height, time, i == 0, Some(chain))
182            })
183        })
184        .or_else(|| {
185            db.transaction(hash)
186                .map(|(tx, height, time)| (tx.clone(), height, time, true, None))
187        })?;
188
189    if in_best_chain {
190        let confirmations = 1 + tip_height(best_chain, db)?.0 - height.0;
191        Some(AnyTx::Mined(MinedTx::new(tx, height, confirmations, time)))
192    } else {
193        let block_hash = containing_chain?.block(height.into())?.hash;
194        Some(AnyTx::Side((tx, block_hash)))
195    }
196}
197
198/// Returns the [`transaction::Hash`]es for the block with `hash_or_height`,
199/// if it exists in the non-finalized `chain` or finalized `db`.
200///
201/// The returned hashes are in block order.
202///
203/// Returns `None` if the block is not found.
204pub fn transaction_hashes_for_block<C>(
205    chain: Option<C>,
206    db: &ZebraDb,
207    hash_or_height: HashOrHeight,
208) -> Option<Arc<[transaction::Hash]>>
209where
210    C: AsRef<Chain>,
211{
212    // # Correctness
213    //
214    // Since blocks are the same in the finalized and non-finalized state, we
215    // check the most efficient alternative first. (`chain` is always in memory,
216    // but `db` stores blocks on disk, with a memory cache.)
217    chain
218        .as_ref()
219        .and_then(|chain| chain.as_ref().transaction_hashes_for_block(hash_or_height))
220        .or_else(|| db.transaction_hashes_for_block(hash_or_height))
221}
222
223/// Returns the [`transaction::Hash`]es for the block with `hash_or_height`,
224/// if it exists in any chain in `chains` or finalized `db`.
225/// The first chain in `chains` must be the best chain.
226///
227/// The returned hashes are in block order.
228///
229/// Returns `None` if the block is not found.
230pub fn transaction_hashes_for_any_block<'a>(
231    chains: impl Iterator<Item = &'a Arc<Chain>>,
232    db: &ZebraDb,
233    hash_or_height: HashOrHeight,
234) -> Option<(Arc<[transaction::Hash]>, bool)> {
235    // # Correctness
236    //
237    // Since blocks are the same in the finalized and non-finalized state, we
238    // check the most efficient alternative first. (`chain` is always in memory,
239    // but `db` stores blocks on disk, with a memory cache.)
240    chains
241        .enumerate()
242        .find_map(|(i, chain)| {
243            chain
244                .as_ref()
245                .transaction_hashes_for_block(hash_or_height)
246                .map(|hashes| (hashes.clone(), i == 0))
247        })
248        .or_else(|| {
249            db.transaction_hashes_for_block(hash_or_height)
250                .map(|hashes| (hashes, true))
251        })
252}
253
254/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists in the
255/// non-finalized `chain` or finalized `db`.
256///
257/// Non-finalized UTXOs are returned regardless of whether they have been spent.
258///
259/// Finalized UTXOs are only returned if they are unspent in the finalized chain.
260/// They may have been spent in the non-finalized chain,
261/// but this function returns them without checking for non-finalized spends,
262/// because we don't know which non-finalized chain will be committed to the finalized state.
263pub fn utxo<C>(chain: Option<C>, db: &ZebraDb, outpoint: transparent::OutPoint) -> Option<Utxo>
264where
265    C: AsRef<Chain>,
266{
267    // # Correctness
268    //
269    // Since UTXOs are the same in the finalized and non-finalized state,
270    // we check the most efficient alternative first. (`chain` is always in
271    // memory, but `db` stores transactions on disk, with a memory cache.)
272    chain
273        .and_then(|chain| chain.as_ref().created_utxo(&outpoint))
274        .or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
275}
276
277/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists and is unspent in the
278/// non-finalized `chain` or finalized `db`.
279pub fn unspent_utxo<C>(
280    chain: Option<C>,
281    db: &ZebraDb,
282    outpoint: transparent::OutPoint,
283) -> Option<Utxo>
284where
285    C: AsRef<Chain>,
286{
287    match chain {
288        Some(chain) if chain.as_ref().spent_utxos.contains_key(&outpoint) => None,
289        chain => utxo(chain, db, outpoint),
290    }
291}
292
293/// Returns the [`Hash`](transaction::Hash) of the transaction that spent an output at
294/// the provided [`transparent::OutPoint`] or revealed the provided nullifier, if it exists
295/// and is spent or revealed in the non-finalized `chain` or finalized `db` and its
296/// spending transaction hash has been indexed.
297#[cfg(feature = "indexer")]
298pub fn spending_transaction_hash<C>(
299    chain: Option<C>,
300    db: &ZebraDb,
301    spend: Spend,
302) -> Option<transaction::Hash>
303where
304    C: AsRef<Chain>,
305{
306    chain
307        .and_then(|chain| chain.as_ref().spending_transaction_hash(&spend))
308        .or_else(|| db.spending_transaction_hash(&spend))
309}
310
311/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists in any chain
312/// in the `non_finalized_state`, or in the finalized `db`.
313///
314/// Non-finalized UTXOs are returned regardless of whether they have been spent.
315///
316/// Finalized UTXOs are only returned if they are unspent in the finalized chain.
317/// They may have been spent in one or more non-finalized chains,
318/// but this function returns them without checking for non-finalized spends,
319/// because we don't know which non-finalized chain the request belongs to.
320///
321/// UTXO spends are checked once the block reaches the non-finalized state,
322/// by [`check::utxo::transparent_spend()`](crate::service::check::utxo::transparent_spend).
323pub fn any_utxo(
324    non_finalized_state: NonFinalizedState,
325    db: &ZebraDb,
326    outpoint: transparent::OutPoint,
327) -> Option<Utxo> {
328    // # Correctness
329    //
330    // Since UTXOs are the same in the finalized and non-finalized state,
331    // we check the most efficient alternative first. (`non_finalized_state` is always in
332    // memory, but `db` stores transactions on disk, with a memory cache.)
333    non_finalized_state
334        .any_utxo(&outpoint)
335        .or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
336}
337
338/// Returns the [`BlockInfo`] with [`block::Hash`] or
339/// [`Height`], if it exists in the non-finalized `chain` or finalized `db`.
340pub fn block_info<C>(
341    chain: Option<C>,
342    db: &ZebraDb,
343    hash_or_height: HashOrHeight,
344) -> Option<BlockInfo>
345where
346    C: AsRef<Chain>,
347{
348    // # Correctness
349    //
350    // Since blocks are the same in the finalized and non-finalized state, we
351    // check the most efficient alternative first. (`chain` is always in memory,
352    // but `db` stores blocks on disk, with a memory cache.)
353    chain
354        .as_ref()
355        .and_then(|chain| chain.as_ref().block_info(hash_or_height))
356        .or_else(|| db.block_info(hash_or_height))
357}