1use std::{collections::HashMap, fmt, ops::Neg, sync::Arc};
4
5use halo2::pasta::{group::ff::PrimeField, 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#[derive(Clone, Debug, Eq, PartialEq)]
50#[cfg_attr(
51 any(test, feature = "proptest-impl", feature = "elasticsearch"),
52 derive(Serialize)
53)]
54pub struct Block {
55 pub header: Arc<Header>,
57 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 pub fn coinbase_height(&self) -> Option<Height> {
82 self.transactions
83 .first()
84 .and_then(|tx| {
85 let inputs = tx.inputs();
86 inputs.into_iter().next()
87 })
88 .and_then(|input| match input {
89 transparent::Input::Coinbase { height, .. } => Some(height),
90 _ => None,
91 })
92 }
93
94 pub fn hash(&self) -> Hash {
96 Hash::from(self)
97 }
98
99 pub fn commitment(&self, network: &Network) -> Result<Commitment, CommitmentError> {
107 match self.coinbase_height() {
108 None => Err(CommitmentError::MissingBlockHeight {
109 block_hash: self.hash(),
110 }),
111 Some(height) => Commitment::from_bytes(*self.header.commitment_bytes, network, height),
112 }
113 }
114
115 #[allow(clippy::unwrap_in_result)]
127 pub fn check_transaction_network_upgrade_consistency(
128 &self,
129 network: &Network,
130 ) -> Result<(), error::BlockError> {
131 let block_nu =
132 NetworkUpgrade::current(network, self.coinbase_height().expect("a valid height"));
133
134 if self
135 .transactions
136 .iter()
137 .filter_map(|trans| trans.as_ref().network_upgrade())
138 .any(|trans_nu| trans_nu != block_nu)
139 {
140 return Err(error::BlockError::WrongTransactionConsensusBranchId);
141 }
142
143 Ok(())
144 }
145
146 pub fn sprout_nullifiers(&self) -> impl Iterator<Item = sprout::Nullifier> + '_ {
148 self.transactions
149 .iter()
150 .flat_map(|transaction| transaction.sprout_nullifiers().collect::<Vec<_>>())
151 }
152
153 pub fn sapling_nullifiers(&self) -> impl Iterator<Item = sapling::Nullifier> + '_ {
155 self.transactions
156 .iter()
157 .flat_map(|transaction| transaction.sapling_nullifiers().collect::<Vec<_>>())
158 }
159
160 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = orchard::Nullifier> + '_ {
162 self.transactions
163 .iter()
164 .flat_map(|transaction| transaction.orchard_nullifiers().collect::<Vec<_>>())
165 }
166
167 pub fn ironwood_nullifiers(&self) -> impl Iterator<Item = ironwood::Nullifier> + '_ {
169 self.transactions
170 .iter()
171 .flat_map(|transaction| transaction.ironwood_nullifiers().collect::<Vec<_>>())
172 }
173
174 pub fn sprout_note_commitments(
176 &self,
177 ) -> impl Iterator<Item = sprout::commitment::NoteCommitment> + '_ {
178 self.transactions
179 .iter()
180 .flat_map(|transaction| transaction.sprout_note_commitments().collect::<Vec<_>>())
181 }
182
183 pub fn sapling_note_commitments(
185 &self,
186 ) -> impl Iterator<Item = sapling_crypto::note::ExtractedNoteCommitment> + '_ {
187 self.transactions
188 .iter()
189 .flat_map(|transaction| transaction.sapling_note_commitments().collect::<Vec<_>>())
190 }
191
192 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = pallas::Base> + '_ {
195 self.transactions.iter().flat_map(|transaction| {
196 transaction
197 .orchard_note_commitments()
198 .map(|cmx| {
199 let bytes = cmx.to_bytes();
200 pallas::Base::from_repr(bytes)
201 .expect("orchard note commitment is a valid pallas::Base")
202 })
203 .collect::<Vec<_>>()
204 })
205 }
206
207 pub fn ironwood_note_commitments(&self) -> impl Iterator<Item = pallas::Base> + '_ {
210 self.transactions.iter().flat_map(|transaction| {
211 transaction
212 .ironwood_note_commitments()
213 .map(|cmx| {
214 let bytes = cmx.to_bytes();
215 pallas::Base::from_repr(bytes)
216 .expect("ironwood note commitment is a valid pallas::Base")
217 })
218 .collect::<Vec<_>>()
219 })
220 }
221
222 pub fn sapling_transactions_count(&self) -> u64 {
226 self.transactions
227 .iter()
228 .filter(|tx| tx.has_sapling_shielded_data())
229 .count()
230 .try_into()
231 .expect("number of transactions must fit u64")
232 }
233
234 pub fn orchard_transactions_count(&self) -> u64 {
238 self.transactions
239 .iter()
240 .filter(|tx| tx.has_orchard_shielded_data())
241 .count()
242 .try_into()
243 .expect("number of transactions must fit u64")
244 }
245
246 pub fn ironwood_transactions_count(&self) -> u64 {
249 self.transactions
250 .iter()
251 .filter(|tx| tx.has_ironwood_shielded_data())
252 .count()
253 .try_into()
254 .expect("number of transactions must fit u64")
255 }
256
257 pub fn chain_value_pool_change(
274 &self,
275 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
276 deferred_pool_balance_change: DeferredPoolBalanceChange,
277 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
278 let tx_pool_sum = self
282 .transactions
283 .iter()
284 .try_fold(ValueBalance::<NegativeAllowed>::zero(), |acc, tx| {
285 acc + tx.value_balance(utxos)?
286 })?;
287
288 Ok(*tx_pool_sum
289 .neg()
290 .set_deferred_amount(deferred_pool_balance_change.value()))
291 }
292
293 pub fn auth_data_root(&self) -> AuthDataRoot {
298 self.transactions.iter().collect::<AuthDataRoot>()
299 }
300}
301
302impl<'a> From<&'a Block> for Hash {
303 fn from(block: &'a Block) -> Hash {
304 block.header.as_ref().into()
305 }
306}
307
308pub const MAX_BLOCK_LOCATOR_LENGTH: u64 = 101;
329
330impl TrustedPreallocate for Hash {
331 fn max_allocation() -> u64 {
332 MAX_BLOCK_LOCATOR_LENGTH
333 }
334}