zebra_chain/block.rs
1//! Blocks and block-related structures (heights, headers, etc.)
2
3use std::{collections::HashMap, fmt, ops::Neg, sync::Arc};
4
5use halo2::pasta::pallas;
6
7use crate::{
8 amount::{DeferredPoolBalanceChange, NegativeAllowed},
9 block::merkle::AuthDataRoot,
10 fmt::DisplayToDebug,
11 ironwood, orchard,
12 parameters::{Network, NetworkUpgrade},
13 sapling,
14 serialization::TrustedPreallocate,
15 sprout,
16 transaction::Transaction,
17 transparent,
18 value_balance::{ValueBalance, ValueBalanceError},
19};
20
21mod commitment;
22mod error;
23mod hash;
24mod header;
25mod height;
26mod serialize;
27
28pub mod genesis;
29pub mod merkle;
30
31#[cfg(any(test, feature = "proptest-impl"))]
32pub mod arbitrary;
33#[cfg(any(test, feature = "bench", feature = "proptest-impl"))]
34pub mod tests;
35
36pub use commitment::{
37 ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Commitment, CommitmentError,
38 CHAIN_HISTORY_ACTIVATION_RESERVED,
39};
40pub use hash::Hash;
41pub use header::{BlockTimeError, CountedHeader, Header, ZCASH_BLOCK_VERSION};
42pub use height::{Height, HeightDiff, TryIntoHeight};
43pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES};
44
45#[cfg(any(test, feature = "proptest-impl"))]
46pub use arbitrary::LedgerState;
47
48/// A Zcash block, containing a header and a list of transactions.
49#[derive(Clone, Debug, Eq, PartialEq)]
50#[cfg_attr(
51 any(test, feature = "proptest-impl", feature = "elasticsearch"),
52 derive(Serialize)
53)]
54pub struct Block {
55 /// The block header, containing block metadata.
56 pub header: Arc<Header>,
57 /// The block transactions.
58 pub transactions: Vec<Arc<Transaction>>,
59}
60
61impl fmt::Display for Block {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let mut fmter = f.debug_struct("Block");
64
65 if let Some(height) = self.coinbase_height() {
66 fmter.field("height", &height);
67 }
68 fmter.field("transactions", &self.transactions.len());
69 fmter.field("hash", &DisplayToDebug(self.hash()));
70
71 fmter.finish()
72 }
73}
74
75impl Block {
76 /// Return the block height reported in the coinbase transaction, if any.
77 ///
78 /// Note
79 ///
80 /// Verified blocks have a valid height.
81 pub fn coinbase_height(&self) -> Option<Height> {
82 self.transactions
83 .first()
84 .and_then(|tx| tx.inputs().first())
85 .and_then(|input| match input {
86 transparent::Input::Coinbase { ref height, .. } => Some(*height),
87 _ => None,
88 })
89 }
90
91 /// Compute the hash of this block.
92 pub fn hash(&self) -> Hash {
93 Hash::from(self)
94 }
95
96 /// Get the parsed block [`Commitment`] for this block.
97 ///
98 /// The interpretation of the commitment depends on the
99 /// configured `network`, and this block's height.
100 ///
101 /// Returns an error if this block does not have a block height,
102 /// or if the commitment value is structurally invalid.
103 pub fn commitment(&self, network: &Network) -> Result<Commitment, CommitmentError> {
104 match self.coinbase_height() {
105 None => Err(CommitmentError::MissingBlockHeight {
106 block_hash: self.hash(),
107 }),
108 Some(height) => Commitment::from_bytes(*self.header.commitment_bytes, network, height),
109 }
110 }
111
112 /// Check if the `network_upgrade` fields from each transaction in the block matches
113 /// the network upgrade calculated from the `network` and block height.
114 ///
115 /// # Consensus
116 ///
117 /// > [NU5 onward] The nConsensusBranchId field MUST match the consensus branch ID used
118 /// > for SIGHASH transaction hashes, as specified in [ZIP-244].
119 ///
120 /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
121 ///
122 /// [ZIP-244]: https://zips.z.cash/zip-0244
123 #[allow(clippy::unwrap_in_result)]
124 pub fn check_transaction_network_upgrade_consistency(
125 &self,
126 network: &Network,
127 ) -> Result<(), error::BlockError> {
128 let block_nu =
129 NetworkUpgrade::current(network, self.coinbase_height().expect("a valid height"));
130
131 if self
132 .transactions
133 .iter()
134 .filter_map(|trans| trans.as_ref().network_upgrade())
135 .any(|trans_nu| trans_nu != block_nu)
136 {
137 return Err(error::BlockError::WrongTransactionConsensusBranchId);
138 }
139
140 Ok(())
141 }
142
143 /// Access the [`sprout::Nullifier`]s from all transactions in this block.
144 pub fn sprout_nullifiers(&self) -> impl Iterator<Item = &sprout::Nullifier> {
145 self.transactions
146 .iter()
147 .flat_map(|transaction| transaction.sprout_nullifiers())
148 }
149
150 /// Access the [`sapling::Nullifier`]s from all transactions in this block.
151 pub fn sapling_nullifiers(&self) -> impl Iterator<Item = &sapling::Nullifier> {
152 self.transactions
153 .iter()
154 .flat_map(|transaction| transaction.sapling_nullifiers())
155 }
156
157 /// Access the [`orchard::Nullifier`]s from all transactions in this block.
158 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
159 self.transactions
160 .iter()
161 .flat_map(|transaction| transaction.orchard_nullifiers())
162 }
163
164 /// Access the [`ironwood::Nullifier`]s from all transactions in this block.
165 pub fn ironwood_nullifiers(&self) -> impl Iterator<Item = ironwood::Nullifier> + '_ {
166 self.transactions
167 .iter()
168 .flat_map(|transaction| transaction.ironwood_nullifiers())
169 }
170
171 /// Access the [`sprout::NoteCommitment`]s from all transactions in this block.
172 pub fn sprout_note_commitments(&self) -> impl Iterator<Item = &sprout::NoteCommitment> {
173 self.transactions
174 .iter()
175 .flat_map(|transaction| transaction.sprout_note_commitments())
176 }
177
178 /// Access the [sapling note commitments](`sapling_crypto::note::ExtractedNoteCommitment`)
179 /// from all transactions in this block.
180 pub fn sapling_note_commitments(
181 &self,
182 ) -> impl Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> {
183 self.transactions
184 .iter()
185 .flat_map(|transaction| transaction.sapling_note_commitments())
186 }
187
188 /// Access the [orchard note commitments](pallas::Base) from all transactions in this block.
189 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
190 self.transactions
191 .iter()
192 .flat_map(|transaction| transaction.orchard_note_commitments())
193 }
194
195 /// Access the [ironwood note commitments](pallas::Base) from all transactions in this block.
196 pub fn ironwood_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
197 self.transactions
198 .iter()
199 .flat_map(|transaction| transaction.ironwood_note_commitments())
200 }
201
202 /// Count how many Sapling transactions exist in a block,
203 /// i.e. transactions "where either of vSpendsSapling or vOutputsSapling is non-empty"
204 /// <https://zips.z.cash/zip-0221#tree-node-specification>.
205 pub fn sapling_transactions_count(&self) -> u64 {
206 self.transactions
207 .iter()
208 .filter(|tx| tx.has_sapling_shielded_data())
209 .count()
210 .try_into()
211 .expect("number of transactions must fit u64")
212 }
213
214 /// Count how many Orchard transactions exist in a block,
215 /// i.e. transactions "where vActionsOrchard is non-empty."
216 /// <https://zips.z.cash/zip-0221#tree-node-specification>.
217 pub fn orchard_transactions_count(&self) -> u64 {
218 self.transactions
219 .iter()
220 .filter(|tx| tx.has_orchard_shielded_data())
221 .count()
222 .try_into()
223 .expect("number of transactions must fit u64")
224 }
225
226 /// Count how many Ironwood transactions exist in a block,
227 /// i.e. transactions where the Ironwood bundle is non-empty (NU6.3 onward).
228 pub fn ironwood_transactions_count(&self) -> u64 {
229 self.transactions
230 .iter()
231 .filter(|tx| tx.has_ironwood_shielded_data())
232 .count()
233 .try_into()
234 .expect("number of transactions must fit u64")
235 }
236
237 /// Returns the overall chain value pool change in this block---the negative sum of the
238 /// transaction value balances in this block.
239 ///
240 /// These are the changes in the transparent, Sprout, Sapling, Orchard, and
241 /// Deferred chain value pools, as a result of this block.
242 ///
243 /// Positive values are added to the corresponding chain value pool and negative values are
244 /// removed from the corresponding pool.
245 ///
246 /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
247 ///
248 /// The given `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
249 /// including UTXOs created by earlier transactions in this block. It can also contain unrelated
250 /// UTXOs, which are ignored.
251 ///
252 /// Note that the chain value pool has the opposite sign to the transaction value pool.
253 pub fn chain_value_pool_change(
254 &self,
255 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
256 deferred_pool_balance_change: DeferredPoolBalanceChange,
257 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
258 // `Result<T, E>` implements `IntoIterator`, so a `flat_map(|t| t.value_balance(utxos))`
259 // would silently drop transactions whose value balance returns `Err`. Use `try_fold`
260 // to propagate the first error instead.
261 let tx_pool_sum = self
262 .transactions
263 .iter()
264 .try_fold(ValueBalance::<NegativeAllowed>::zero(), |acc, tx| {
265 acc + tx.value_balance(utxos)?
266 })?;
267
268 Ok(*tx_pool_sum
269 .neg()
270 .set_deferred_amount(deferred_pool_balance_change.value()))
271 }
272
273 /// Compute the root of the authorizing data Merkle tree,
274 /// as defined in [ZIP-244].
275 ///
276 /// [ZIP-244]: https://zips.z.cash/zip-0244
277 pub fn auth_data_root(&self) -> AuthDataRoot {
278 self.transactions.iter().collect::<AuthDataRoot>()
279 }
280}
281
282impl<'a> From<&'a Block> for Hash {
283 fn from(block: &'a Block) -> Hash {
284 block.header.as_ref().into()
285 }
286}
287
288/// The maximum number of `block::Hash` entries Zebra will preallocate for in
289/// a single peer-deserialized vector.
290///
291/// In the P2P protocol, `Vec<block::Hash>` appears as the `known_blocks` block
292/// locator in `getblocks` and `getheaders` messages. The Bitcoin/Zcash
293/// convention encodes locators with exponentially-spaced heights (1, 2, 3, …,
294/// 10, 20, 40, …, genesis), giving `~log2(N) + 10` entries for chain length N.
295/// For current Zcash chain heights (~3M blocks) a legitimate locator has ~32
296/// entries.
297///
298/// We cap at 101 to match Bitcoin Core's `MAX_LOCATOR_SZ` constant
299/// (`net_processing.cpp`), which zcashd inherits. This avoids any risk of
300/// rejecting legitimate locators sent by compatible nodes that follow the
301/// existing Bitcoin/Zcash protocol convention.
302///
303/// Without this cap, `Hash::max_allocation` was previously derived from
304/// `MAX_PROTOCOL_MESSAGE_LEN / 32 = 65,535`, which allowed a remote peer to
305/// force ~2 MiB heap preallocation per crafted `getblocks`/`getheaders` message
306/// before any payload was read. This is the same class as
307/// GHSA-xr93-pcq3-pxf8 (`addr_limit`), fixed for AddrV1/V2 in PR #10494.
308pub const MAX_BLOCK_LOCATOR_LENGTH: u64 = 101;
309
310impl TrustedPreallocate for Hash {
311 fn max_allocation() -> u64 {
312 MAX_BLOCK_LOCATOR_LENGTH
313 }
314}