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};
39pub use hash::Hash;
40pub use header::{BlockTimeError, CountedHeader, Header, ZCASH_BLOCK_VERSION};
41pub use height::{Height, HeightDiff, TryIntoHeight};
42pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES};
43
44#[cfg(any(test, feature = "proptest-impl"))]
45pub use arbitrary::LedgerState;
46
47#[derive(Clone, Debug, Eq, PartialEq)]
49#[cfg_attr(
50 any(test, feature = "proptest-impl", feature = "elasticsearch"),
51 derive(Serialize)
52)]
53pub struct Block {
54 pub header: Arc<Header>,
56 pub transactions: Vec<Arc<Transaction>>,
58}
59
60impl fmt::Display for Block {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 let mut fmter = f.debug_struct("Block");
63
64 if let Some(height) = self.coinbase_height() {
65 fmter.field("height", &height);
66 }
67 fmter.field("transactions", &self.transactions.len());
68 fmter.field("hash", &DisplayToDebug(self.hash()));
69
70 fmter.finish()
71 }
72}
73
74impl Block {
75 pub fn coinbase_height(&self) -> Option<Height> {
81 self.transactions
82 .first()
83 .and_then(|tx| tx.inputs().first())
84 .and_then(|input| match input {
85 transparent::Input::Coinbase { ref height, .. } => Some(*height),
86 _ => None,
87 })
88 }
89
90 pub fn hash(&self) -> Hash {
92 Hash::from(self)
93 }
94
95 pub fn commitment(&self, network: &Network) -> Result<Commitment, CommitmentError> {
103 match self.coinbase_height() {
104 None => Err(CommitmentError::MissingBlockHeight {
105 block_hash: self.hash(),
106 }),
107 Some(height) => Commitment::from_bytes(*self.header.commitment_bytes, network, height),
108 }
109 }
110
111 #[allow(clippy::unwrap_in_result)]
123 pub fn check_transaction_network_upgrade_consistency(
124 &self,
125 network: &Network,
126 ) -> Result<(), error::BlockError> {
127 let block_nu =
128 NetworkUpgrade::current(network, self.coinbase_height().expect("a valid height"));
129
130 if self
131 .transactions
132 .iter()
133 .filter_map(|trans| trans.as_ref().network_upgrade())
134 .any(|trans_nu| trans_nu != block_nu)
135 {
136 return Err(error::BlockError::WrongTransactionConsensusBranchId);
137 }
138
139 Ok(())
140 }
141
142 pub fn sprout_nullifiers(&self) -> impl Iterator<Item = &sprout::Nullifier> {
144 self.transactions
145 .iter()
146 .flat_map(|transaction| transaction.sprout_nullifiers())
147 }
148
149 pub fn sapling_nullifiers(&self) -> impl Iterator<Item = &sapling::Nullifier> {
151 self.transactions
152 .iter()
153 .flat_map(|transaction| transaction.sapling_nullifiers())
154 }
155
156 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
158 self.transactions
159 .iter()
160 .flat_map(|transaction| transaction.orchard_nullifiers())
161 }
162
163 pub fn sprout_note_commitments(&self) -> impl Iterator<Item = &sprout::NoteCommitment> {
165 self.transactions
166 .iter()
167 .flat_map(|transaction| transaction.sprout_note_commitments())
168 }
169
170 pub fn sapling_note_commitments(
173 &self,
174 ) -> impl Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> {
175 self.transactions
176 .iter()
177 .flat_map(|transaction| transaction.sapling_note_commitments())
178 }
179
180 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
182 self.transactions
183 .iter()
184 .flat_map(|transaction| transaction.orchard_note_commitments())
185 }
186
187 pub fn sapling_transactions_count(&self) -> u64 {
191 self.transactions
192 .iter()
193 .filter(|tx| tx.has_sapling_shielded_data())
194 .count()
195 .try_into()
196 .expect("number of transactions must fit u64")
197 }
198
199 pub fn orchard_transactions_count(&self) -> u64 {
203 self.transactions
204 .iter()
205 .filter(|tx| tx.has_orchard_shielded_data())
206 .count()
207 .try_into()
208 .expect("number of transactions must fit u64")
209 }
210
211 pub fn chain_value_pool_change(
228 &self,
229 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
230 deferred_pool_balance_change: Option<DeferredPoolBalanceChange>,
231 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
232 Ok(*self
233 .transactions
234 .iter()
235 .flat_map(|t| t.value_balance(utxos))
236 .sum::<Result<ValueBalance<NegativeAllowed>, _>>()?
237 .neg()
238 .set_deferred_amount(
239 deferred_pool_balance_change
240 .map(DeferredPoolBalanceChange::value)
241 .unwrap_or_default(),
242 ))
243 }
244
245 pub fn auth_data_root(&self) -> AuthDataRoot {
250 self.transactions.iter().collect::<AuthDataRoot>()
251 }
252}
253
254impl<'a> From<&'a Block> for Hash {
255 fn from(block: &'a Block) -> Hash {
256 block.header.as_ref().into()
257 }
258}
259
260const BLOCK_HASH_SIZE: u64 = 32;
262
263impl TrustedPreallocate for Hash {
265 fn max_allocation() -> u64 {
266 ((MAX_PROTOCOL_MESSAGE_LEN - 1) as u64) / BLOCK_HASH_SIZE
269 }
270}