1use 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 orchard,
12 parameters::{Network, NetworkUpgrade},
13 sapling,
14 serialization::{TrustedPreallocate, MAX_PROTOCOL_MESSAGE_LEN},
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| tx.inputs().first())
85 .and_then(|input| match input {
86 transparent::Input::Coinbase { ref height, .. } => Some(*height),
87 _ => None,
88 })
89 }
90
91 pub fn hash(&self) -> Hash {
93 Hash::from(self)
94 }
95
96 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 #[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 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 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 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 pub fn sprout_note_commitments(&self) -> impl Iterator<Item = &sprout::NoteCommitment> {
166 self.transactions
167 .iter()
168 .flat_map(|transaction| transaction.sprout_note_commitments())
169 }
170
171 pub fn sapling_note_commitments(
174 &self,
175 ) -> impl Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> {
176 self.transactions
177 .iter()
178 .flat_map(|transaction| transaction.sapling_note_commitments())
179 }
180
181 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
183 self.transactions
184 .iter()
185 .flat_map(|transaction| transaction.orchard_note_commitments())
186 }
187
188 pub fn sapling_transactions_count(&self) -> u64 {
192 self.transactions
193 .iter()
194 .filter(|tx| tx.has_sapling_shielded_data())
195 .count()
196 .try_into()
197 .expect("number of transactions must fit u64")
198 }
199
200 pub fn orchard_transactions_count(&self) -> u64 {
204 self.transactions
205 .iter()
206 .filter(|tx| tx.has_orchard_shielded_data())
207 .count()
208 .try_into()
209 .expect("number of transactions must fit u64")
210 }
211
212 pub fn chain_value_pool_change(
229 &self,
230 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
231 deferred_pool_balance_change: Option<DeferredPoolBalanceChange>,
232 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
233 Ok(*self
234 .transactions
235 .iter()
236 .flat_map(|t| t.value_balance(utxos))
237 .sum::<Result<ValueBalance<NegativeAllowed>, _>>()?
238 .neg()
239 .set_deferred_amount(
240 deferred_pool_balance_change
241 .map(DeferredPoolBalanceChange::value)
242 .unwrap_or_default(),
243 ))
244 }
245
246 pub fn auth_data_root(&self) -> AuthDataRoot {
251 self.transactions.iter().collect::<AuthDataRoot>()
252 }
253}
254
255impl<'a> From<&'a Block> for Hash {
256 fn from(block: &'a Block) -> Hash {
257 block.header.as_ref().into()
258 }
259}
260
261const BLOCK_HASH_SIZE: u64 = 32;
263
264impl TrustedPreallocate for Hash {
266 fn max_allocation() -> u64 {
267 ((MAX_PROTOCOL_MESSAGE_LEN - 1) as u64) / BLOCK_HASH_SIZE
270 }
271}