1use std::{collections::HashMap, fmt, ops::Neg, sync::Arc};
4
5use halo2::pasta::pallas;
6
7use crate::{
8 amount::{Amount, NegativeAllowed, NonNegative},
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(&self) -> impl Iterator<Item = &jubjub::Fq> {
172 self.transactions
173 .iter()
174 .flat_map(|transaction| transaction.sapling_note_commitments())
175 }
176
177 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
179 self.transactions
180 .iter()
181 .flat_map(|transaction| transaction.orchard_note_commitments())
182 }
183
184 pub fn sapling_transactions_count(&self) -> u64 {
188 self.transactions
189 .iter()
190 .filter(|tx| tx.has_sapling_shielded_data())
191 .count()
192 .try_into()
193 .expect("number of transactions must fit u64")
194 }
195
196 pub fn orchard_transactions_count(&self) -> u64 {
200 self.transactions
201 .iter()
202 .filter(|tx| tx.has_orchard_shielded_data())
203 .count()
204 .try_into()
205 .expect("number of transactions must fit u64")
206 }
207
208 pub fn chain_value_pool_change(
225 &self,
226 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
227 deferred_balance: Option<Amount<NonNegative>>,
228 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
229 Ok(*self
230 .transactions
231 .iter()
232 .flat_map(|t| t.value_balance(utxos))
233 .sum::<Result<ValueBalance<NegativeAllowed>, _>>()?
234 .neg()
235 .set_deferred_amount(
236 deferred_balance
237 .unwrap_or(Amount::zero())
238 .constrain::<NegativeAllowed>()
239 .map_err(ValueBalanceError::Deferred)?,
240 ))
241 }
242
243 pub fn auth_data_root(&self) -> AuthDataRoot {
248 self.transactions.iter().collect::<AuthDataRoot>()
249 }
250}
251
252impl<'a> From<&'a Block> for Hash {
253 fn from(block: &'a Block) -> Hash {
254 block.header.as_ref().into()
255 }
256}
257
258const BLOCK_HASH_SIZE: u64 = 32;
260
261impl TrustedPreallocate for Hash {
263 fn max_allocation() -> u64 {
264 ((MAX_PROTOCOL_MESSAGE_LEN - 1) as u64) / BLOCK_HASH_SIZE
267 }
268}