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::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 `chain` or finalized `db`.
42pub fn block<C>(chain: Option<C>, db: &ZebraDb, hash_or_height: HashOrHeight) -> Option<Arc<Block>>
43where
44    C: AsRef<Chain>,
45{
46    // # Correctness
47    //
48    // Since blocks are the same in the finalized and non-finalized state, we
49    // check the most efficient alternative first. (`chain` is always in memory,
50    // but `db` stores blocks on disk, with a memory cache.)
51    chain
52        .as_ref()
53        .and_then(|chain| chain.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_and_size<C>(
61    chain: Option<C>,
62    db: &ZebraDb,
63    hash_or_height: HashOrHeight,
64) -> Option<(Arc<Block>, usize)>
65where
66    C: AsRef<Chain>,
67{
68    // # Correctness
69    //
70    // Since blocks are the same in the finalized and non-finalized state, we
71    // check the most efficient alternative first. (`chain` is always in memory,
72    // but `db` stores blocks on disk, with a memory cache.)
73    chain
74        .as_ref()
75        .and_then(|chain| chain.as_ref().block(hash_or_height))
76        .map(|contextual| {
77            let size = contextual.block.zcash_serialize_to_vec().unwrap().len();
78            (contextual.block.clone(), size)
79        })
80        .or_else(|| db.block_and_size(hash_or_height))
81}
82
83/// Returns the [`block::Header`] with [`block::Hash`] or
84/// [`Height`], if it exists in the non-finalized `chain` or finalized `db`.
85pub fn block_header<C>(
86    chain: Option<C>,
87    db: &ZebraDb,
88    hash_or_height: HashOrHeight,
89) -> Option<Arc<block::Header>>
90where
91    C: AsRef<Chain>,
92{
93    // # Correctness
94    //
95    // Since blocks are the same in the finalized and non-finalized state, we
96    // check the most efficient alternative first. (`chain` is always in memory,
97    // but `db` stores blocks on disk, with a memory cache.)
98    chain
99        .as_ref()
100        .and_then(|chain| chain.as_ref().block(hash_or_height))
101        .map(|contextual| contextual.block.header.clone())
102        .or_else(|| db.block_header(hash_or_height))
103}
104
105/// Returns the [`Transaction`] with [`transaction::Hash`], if it exists in the
106/// non-finalized `chain` or finalized `db`.
107fn transaction<C>(
108    chain: Option<C>,
109    db: &ZebraDb,
110    hash: transaction::Hash,
111) -> Option<(Arc<Transaction>, Height, DateTime<Utc>)>
112where
113    C: AsRef<Chain>,
114{
115    // # Correctness
116    //
117    // Since transactions are the same in the finalized and non-finalized state,
118    // we check the most efficient alternative first. (`chain` is always in
119    // memory, but `db` stores transactions on disk, with a memory cache.)
120    chain
121        .and_then(|chain| {
122            chain
123                .as_ref()
124                .transaction(hash)
125                .map(|(tx, height, time)| (tx.clone(), height, time))
126        })
127        .or_else(|| db.transaction(hash))
128}
129
130/// Returns a [`MinedTx`] for a [`Transaction`] with [`transaction::Hash`],
131/// if one exists in the non-finalized `chain` or finalized `db`.
132pub fn mined_transaction<C>(
133    chain: Option<C>,
134    db: &ZebraDb,
135    hash: transaction::Hash,
136) -> Option<MinedTx>
137where
138    C: AsRef<Chain>,
139{
140    // # Correctness
141    //
142    // It is ok to do this lookup in two different calls. Finalized state updates
143    // can only add overlapping blocks, and hashes are unique.
144    let chain = chain.as_ref();
145
146    let (tx, height, time) = transaction(chain, db, hash)?;
147    let confirmations = 1 + tip_height(chain, db)?.0 - height.0;
148
149    Some(MinedTx::new(tx, height, confirmations, time))
150}
151
152/// Returns the [`transaction::Hash`]es for the block with `hash_or_height`,
153/// if it exists in the non-finalized `chain` or finalized `db`.
154///
155/// The returned hashes are in block order.
156///
157/// Returns `None` if the block is not found.
158pub fn transaction_hashes_for_block<C>(
159    chain: Option<C>,
160    db: &ZebraDb,
161    hash_or_height: HashOrHeight,
162) -> Option<Arc<[transaction::Hash]>>
163where
164    C: AsRef<Chain>,
165{
166    // # Correctness
167    //
168    // Since blocks are the same in the finalized and non-finalized state, we
169    // check the most efficient alternative first. (`chain` is always in memory,
170    // but `db` stores blocks on disk, with a memory cache.)
171    chain
172        .as_ref()
173        .and_then(|chain| chain.as_ref().transaction_hashes_for_block(hash_or_height))
174        .or_else(|| db.transaction_hashes_for_block(hash_or_height))
175}
176
177/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists in the
178/// non-finalized `chain` or finalized `db`.
179///
180/// Non-finalized UTXOs are returned regardless of whether they have been spent.
181///
182/// Finalized UTXOs are only returned if they are unspent in the finalized chain.
183/// They may have been spent in the non-finalized chain,
184/// but this function returns them without checking for non-finalized spends,
185/// because we don't know which non-finalized chain will be committed to the finalized state.
186pub fn utxo<C>(chain: Option<C>, db: &ZebraDb, outpoint: transparent::OutPoint) -> Option<Utxo>
187where
188    C: AsRef<Chain>,
189{
190    // # Correctness
191    //
192    // Since UTXOs are the same in the finalized and non-finalized state,
193    // we check the most efficient alternative first. (`chain` is always in
194    // memory, but `db` stores transactions on disk, with a memory cache.)
195    chain
196        .and_then(|chain| chain.as_ref().created_utxo(&outpoint))
197        .or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
198}
199
200/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists and is unspent in the
201/// non-finalized `chain` or finalized `db`.
202pub fn unspent_utxo<C>(
203    chain: Option<C>,
204    db: &ZebraDb,
205    outpoint: transparent::OutPoint,
206) -> Option<Utxo>
207where
208    C: AsRef<Chain>,
209{
210    match chain {
211        Some(chain) if chain.as_ref().spent_utxos.contains_key(&outpoint) => None,
212        chain => utxo(chain, db, outpoint),
213    }
214}
215
216/// Returns the [`Hash`](transaction::Hash) of the transaction that spent an output at
217/// the provided [`transparent::OutPoint`] or revealed the provided nullifier, if it exists
218/// and is spent or revealed in the non-finalized `chain` or finalized `db` and its
219/// spending transaction hash has been indexed.
220#[cfg(feature = "indexer")]
221pub fn spending_transaction_hash<C>(
222    chain: Option<C>,
223    db: &ZebraDb,
224    spend: Spend,
225) -> Option<transaction::Hash>
226where
227    C: AsRef<Chain>,
228{
229    chain
230        .and_then(|chain| chain.as_ref().spending_transaction_hash(&spend))
231        .or_else(|| db.spending_transaction_hash(&spend))
232}
233
234/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists in any chain
235/// in the `non_finalized_state`, or in the finalized `db`.
236///
237/// Non-finalized UTXOs are returned regardless of whether they have been spent.
238///
239/// Finalized UTXOs are only returned if they are unspent in the finalized chain.
240/// They may have been spent in one or more non-finalized chains,
241/// but this function returns them without checking for non-finalized spends,
242/// because we don't know which non-finalized chain the request belongs to.
243///
244/// UTXO spends are checked once the block reaches the non-finalized state,
245/// by [`check::utxo::transparent_spend()`](crate::service::check::utxo::transparent_spend).
246pub fn any_utxo(
247    non_finalized_state: NonFinalizedState,
248    db: &ZebraDb,
249    outpoint: transparent::OutPoint,
250) -> Option<Utxo> {
251    // # Correctness
252    //
253    // Since UTXOs are the same in the finalized and non-finalized state,
254    // we check the most efficient alternative first. (`non_finalized_state` is always in
255    // memory, but `db` stores transactions on disk, with a memory cache.)
256    non_finalized_state
257        .any_utxo(&outpoint)
258        .or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo))
259}
260
261/// Returns the [`BlockInfo`] with [`block::Hash`] or
262/// [`Height`], if it exists in the non-finalized `chain` or finalized `db`.
263pub fn block_info<C>(
264    chain: Option<C>,
265    db: &ZebraDb,
266    hash_or_height: HashOrHeight,
267) -> Option<BlockInfo>
268where
269    C: AsRef<Chain>,
270{
271    // # Correctness
272    //
273    // Since blocks are the same in the finalized and non-finalized state, we
274    // check the most efficient alternative first. (`chain` is always in memory,
275    // but `db` stores blocks on disk, with a memory cache.)
276    chain
277        .as_ref()
278        .and_then(|chain| chain.as_ref().block_info(hash_or_height))
279        .or_else(|| db.block_info(hash_or_height))
280}