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}