1use proptest::prelude::*;
4
5use crate::{
6 amount::NonNegative,
7 block,
8 fmt::{HexDebug, SummaryDebug},
9 history_tree::HistoryTree,
10 parameters::{NetworkUpgrade::*, GENESIS_PREVIOUS_BLOCK_HASH},
11 primitives::zcash_history::BlockCommitmentTreeRoots,
12 serialization::{self, BytesInDisplayOrder},
13 transaction::arbitrary::MAX_ARBITRARY_ITEMS,
14 transparent::{
15 new_transaction_ordered_outputs, CoinbaseSpendRestriction,
16 MIN_TRANSPARENT_COINBASE_MATURITY,
17 },
18 work::{difficulty::CompactDifficulty, equihash},
19};
20
21use super::*;
22
23pub const PREVOUTS_CHAIN_HEIGHT: usize = 4;
42
43pub const MAX_PARTIAL_CHAIN_BLOCKS: usize =
52 MIN_TRANSPARENT_COINBASE_MATURITY as usize + PREVOUTS_CHAIN_HEIGHT;
53
54impl Arbitrary for Height {
55 type Parameters = ();
56
57 fn arbitrary_with(_args: ()) -> Self::Strategy {
58 (Height::MIN.0..=Height::MAX.0).prop_map(Height).boxed()
59 }
60
61 type Strategy = BoxedStrategy<Self>;
62}
63
64#[derive(Debug, Clone)]
65#[non_exhaustive]
66pub struct LedgerState {
68 pub height: Height,
75
76 pub network: Network,
78
79 network_upgrade_override: Option<NetworkUpgrade>,
83
84 previous_block_hash_override: Option<block::Hash>,
86
87 transaction_version_override: Option<u32>,
89
90 transaction_has_valid_network_upgrade: bool,
94
95 pub(crate) has_coinbase: bool,
103}
104
105#[derive(Debug, Clone)]
107pub struct LedgerStateOverride {
108 pub network_override: Option<Network>,
110
111 pub height_override: Option<Height>,
113
114 pub previous_block_hash_override: Option<block::Hash>,
117
118 pub network_upgrade_override: Option<NetworkUpgrade>,
121
122 pub transaction_version_override: Option<u32>,
124
125 pub transaction_has_valid_network_upgrade: bool,
129
130 pub always_has_coinbase: bool,
133}
134
135impl LedgerState {
136 pub fn default_strategy() -> BoxedStrategy<Self> {
138 Self::arbitrary_with(LedgerStateOverride::default())
139 }
140
141 pub fn no_override_strategy() -> BoxedStrategy<Self> {
144 Self::arbitrary_with(LedgerStateOverride {
145 network_override: None,
146 height_override: None,
147 previous_block_hash_override: None,
148 network_upgrade_override: None,
149 transaction_version_override: None,
150 transaction_has_valid_network_upgrade: false,
151 always_has_coinbase: false,
152 })
153 }
154
155 pub fn network_upgrade_strategy(
160 network_upgrade_override: NetworkUpgrade,
161 transaction_version_override: impl Into<Option<u32>>,
162 transaction_has_valid_network_upgrade: bool,
163 ) -> BoxedStrategy<Self> {
164 Self::arbitrary_with(LedgerStateOverride {
165 network_override: None,
166 height_override: None,
167 previous_block_hash_override: None,
168 network_upgrade_override: Some(network_upgrade_override),
169 transaction_version_override: transaction_version_override.into(),
170 transaction_has_valid_network_upgrade,
171 always_has_coinbase: false,
172 })
173 }
174
175 pub fn coinbase_strategy(
180 network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
181 transaction_version_override: impl Into<Option<u32>>,
182 transaction_has_valid_network_upgrade: bool,
183 ) -> BoxedStrategy<Self> {
184 Self::arbitrary_with(LedgerStateOverride {
185 network_override: None,
186 height_override: None,
187 previous_block_hash_override: None,
188 network_upgrade_override: network_upgrade_override.into(),
189 transaction_version_override: transaction_version_override.into(),
190 transaction_has_valid_network_upgrade,
191 always_has_coinbase: true,
192 })
193 }
194
195 pub fn genesis_strategy(
204 network_override: impl Into<Option<Network>>,
205 network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
206 transaction_version_override: impl Into<Option<u32>>,
207 transaction_has_valid_network_upgrade: bool,
208 ) -> BoxedStrategy<Self> {
209 Self::arbitrary_with(LedgerStateOverride {
210 network_override: network_override.into(),
211 height_override: Some(Height(0)),
212 previous_block_hash_override: Some(GENESIS_PREVIOUS_BLOCK_HASH),
213 network_upgrade_override: network_upgrade_override.into(),
214 transaction_version_override: transaction_version_override.into(),
215 transaction_has_valid_network_upgrade,
216 always_has_coinbase: true,
217 })
218 }
219
220 pub fn height_strategy(
225 height: Height,
226 network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
227 transaction_version_override: impl Into<Option<u32>>,
228 transaction_has_valid_network_upgrade: bool,
229 ) -> BoxedStrategy<Self> {
230 Self::arbitrary_with(LedgerStateOverride {
231 network_override: None,
232 height_override: Some(height),
233 previous_block_hash_override: None,
234 network_upgrade_override: network_upgrade_override.into(),
235 transaction_version_override: transaction_version_override.into(),
236 transaction_has_valid_network_upgrade,
237 always_has_coinbase: true,
238 })
239 }
240
241 pub fn network_upgrade(&self) -> NetworkUpgrade {
246 if let Some(network_upgrade_override) = self.network_upgrade_override {
247 network_upgrade_override
248 } else {
249 NetworkUpgrade::current(&self.network, self.height)
250 }
251 }
252
253 pub fn transaction_version_override(&self) -> Option<u32> {
255 self.transaction_version_override
256 }
257
258 pub fn transaction_has_valid_network_upgrade(&self) -> bool {
262 self.transaction_has_valid_network_upgrade
263 }
264}
265
266impl Default for LedgerState {
267 fn default() -> Self {
268 let default_network = Network::default();
270 let default_override = LedgerStateOverride::default();
271
272 let most_recent_nu = NetworkUpgrade::current(&default_network, Height::MAX);
273 let most_recent_activation_height =
274 most_recent_nu.activation_height(&default_network).unwrap();
275
276 LedgerState {
277 height: most_recent_activation_height,
278 network: default_network,
279 network_upgrade_override: default_override.network_upgrade_override,
280 previous_block_hash_override: default_override.previous_block_hash_override,
281 transaction_version_override: default_override.transaction_version_override,
282 transaction_has_valid_network_upgrade: default_override
283 .transaction_has_valid_network_upgrade,
284 has_coinbase: default_override.always_has_coinbase,
285 }
286 }
287}
288
289impl Default for LedgerStateOverride {
290 fn default() -> Self {
291 let default_network = Network::default();
292
293 let nu5_activation_height = Nu5.activation_height(&default_network);
295 let nu5_override = if nu5_activation_height.is_some() {
296 None
297 } else {
298 Some(Nu5)
299 };
300
301 LedgerStateOverride {
302 network_override: None,
303 height_override: None,
304 previous_block_hash_override: None,
305 network_upgrade_override: nu5_override,
306 transaction_version_override: None,
307 transaction_has_valid_network_upgrade: false,
308 always_has_coinbase: true,
309 }
310 }
311}
312
313impl Arbitrary for LedgerState {
314 type Parameters = LedgerStateOverride;
315
316 fn arbitrary_with(ledger_override: Self::Parameters) -> Self::Strategy {
322 (
323 any::<Height>(),
324 any::<Network>(),
325 any::<bool>(),
326 any::<bool>(),
327 )
328 .prop_map(
329 move |(height, network, transaction_has_valid_network_upgrade, has_coinbase)| {
330 LedgerState {
331 height: ledger_override.height_override.unwrap_or(height),
332 network: ledger_override
333 .network_override
334 .as_ref()
335 .unwrap_or(&network)
336 .clone(),
337 network_upgrade_override: ledger_override.network_upgrade_override,
338 previous_block_hash_override: ledger_override.previous_block_hash_override,
339 transaction_version_override: ledger_override.transaction_version_override,
340 transaction_has_valid_network_upgrade: ledger_override
341 .transaction_has_valid_network_upgrade
342 || transaction_has_valid_network_upgrade,
343 has_coinbase: ledger_override.always_has_coinbase || has_coinbase,
344 }
345 },
346 )
347 .boxed()
348 }
349
350 type Strategy = BoxedStrategy<Self>;
351}
352
353impl Arbitrary for Block {
354 type Parameters = LedgerState;
355
356 fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
357 let transactions_strategy = {
358 let ledger_state = ledger_state.clone();
359 (0..MAX_ARBITRARY_ITEMS).prop_flat_map(move |transaction_count| {
362 Transaction::vec_strategy(ledger_state.clone(), transaction_count)
363 })
364 };
365
366 (Header::arbitrary_with(ledger_state), transactions_strategy)
371 .prop_map(move |(header, transactions)| Self {
372 header: header.into(),
373 transactions,
374 })
375 .boxed()
376 }
377
378 type Strategy = BoxedStrategy<Self>;
379}
380
381#[allow(clippy::result_unit_err)]
383pub fn allow_all_transparent_coinbase_spends(
384 _: transparent::OutPoint,
385 _: transparent::CoinbaseSpendRestriction,
386 _: &transparent::Utxo,
387) -> Result<(), ()> {
388 Ok(())
389}
390
391impl Block {
392 pub fn partial_chain_strategy<F, E>(
404 mut current: LedgerState,
405 count: usize,
406 check_transparent_coinbase_spend: F,
407 generate_valid_commitments: bool,
408 ) -> BoxedStrategy<SummaryDebug<Vec<Arc<Self>>>>
409 where
410 F: Fn(
411 transparent::OutPoint,
412 transparent::CoinbaseSpendRestriction,
413 &transparent::Utxo,
414 ) -> Result<(), E>
415 + Copy
416 + 'static,
417 {
418 let mut vec = Vec::with_capacity(count);
419
420 for _ in 0..count {
422 vec.push((Just(current.height), Block::arbitrary_with(current.clone())));
423 current.height.0 += 1;
424 }
425
426 vec.prop_map(move |mut vec| {
428 let mut previous_block_hash = None;
429 let mut utxos = HashMap::new();
430 let mut chain_value_pools = ValueBalance::zero();
431 let mut sapling_tree = sapling::tree::NoteCommitmentTree::default();
432 let mut orchard_tree = orchard::tree::NoteCommitmentTree::default();
433 let mut ironwood_tree = orchard::tree::NoteCommitmentTree::default();
437 let mut history_tree: Option<HistoryTree> = None;
444
445 for (height, block) in vec.iter_mut() {
446 if let Some(previous_block_hash) = previous_block_hash {
448 Arc::make_mut(&mut block.header).previous_block_hash = previous_block_hash;
449 }
450
451 let mut new_transactions = Vec::new();
452 for (tx_index_in_block, transaction) in block.transactions.drain(..).enumerate() {
453 if let Some(transaction) = fix_generated_transaction(
454 (*transaction).clone(),
455 tx_index_in_block,
456 *height,
457 &mut chain_value_pools,
458 &mut utxos,
459 check_transparent_coinbase_spend,
460 ) {
461 if generate_valid_commitments && *height != Height(0) {
470 for sapling_note_commitment in transaction.sapling_note_commitments() {
471 sapling_tree.append(*sapling_note_commitment).unwrap();
472 }
473 for orchard_note_commitment in transaction.orchard_note_commitments() {
474 orchard_tree.append(*orchard_note_commitment).unwrap();
475 }
476 for ironwood_note_commitment in transaction.ironwood_note_commitments()
477 {
478 ironwood_tree.append(*ironwood_note_commitment).unwrap();
479 }
480 }
481 new_transactions.push(Arc::new(transaction));
482 }
483 }
484
485 block.transactions = new_transactions;
487
488 if generate_valid_commitments {
490 let current_height = block.coinbase_height().unwrap();
491 let heartwood_height = NetworkUpgrade::Heartwood
492 .activation_height(¤t.network)
493 .unwrap();
494 let nu5_height = NetworkUpgrade::Nu5.activation_height(¤t.network);
495
496 match current_height.cmp(&heartwood_height) {
497 std::cmp::Ordering::Less => {
498 let block_header = Arc::make_mut(&mut block.header);
503 block_header.commitment_bytes = [0u8; 32].into();
504 block_header.commitment_bytes[0] = 1;
505 }
506 std::cmp::Ordering::Equal => {
507 let block_header = Arc::make_mut(&mut block.header);
509 block_header.commitment_bytes = [0u8; 32].into();
510 }
511 std::cmp::Ordering::Greater => {
512 let history_tree_root = match &history_tree {
514 Some(tree) => tree.hash().unwrap_or_else(|| [0u8; 32].into()),
515 None => [0u8; 32].into(),
516 };
517 if nu5_height.is_some() && current_height >= nu5_height.unwrap() {
518 let auth_data_root = block.auth_data_root();
520 let hash_block_commitments =
521 ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
522 &history_tree_root,
523 &auth_data_root,
524 );
525 let block_header = Arc::make_mut(&mut block.header);
526 block_header.commitment_bytes =
527 hash_block_commitments.bytes_in_serialized_order().into();
528 } else {
529 let block_header = Arc::make_mut(&mut block.header);
530 block_header.commitment_bytes =
531 history_tree_root.bytes_in_serialized_order().into();
532 }
533 }
534 }
535 if let Some(history_tree) = history_tree.as_mut() {
537 history_tree
538 .push(
539 ¤t.network,
540 Arc::new(block.clone()),
541 BlockCommitmentTreeRoots {
542 sapling: &sapling_tree.root(),
543 orchard: &orchard_tree.root(),
544 ironwood: &ironwood_tree.root(),
545 },
546 )
547 .unwrap();
548 } else {
549 history_tree = Some(
550 HistoryTree::from_block(
551 ¤t.network,
552 Arc::new(block.clone()),
553 BlockCommitmentTreeRoots {
554 sapling: &sapling_tree.root(),
555 orchard: &orchard_tree.root(),
556 ironwood: &ironwood_tree.root(),
557 },
558 )
559 .unwrap(),
560 );
561 }
562 }
563
564 previous_block_hash = Some(block.hash());
567 }
568 SummaryDebug(
569 vec.into_iter()
570 .map(|(_height, block)| Arc::new(block))
571 .collect(),
572 )
573 })
574 .boxed()
575 }
576}
577
578pub fn fix_generated_transaction<F, E>(
584 mut transaction: Transaction,
585 tx_index_in_block: usize,
586 height: Height,
587 chain_value_pools: &mut ValueBalance<NonNegative>,
588 utxos: &mut HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
589 check_transparent_coinbase_spend: F,
590) -> Option<Transaction>
591where
592 F: Fn(
593 transparent::OutPoint,
594 transparent::CoinbaseSpendRestriction,
595 &transparent::Utxo,
596 ) -> Result<(), E>
597 + Copy
598 + 'static,
599{
600 if transaction.is_coinbase() {
607 match &mut transaction {
608 Transaction::V4 {
609 sapling_shielded_data,
610 ..
611 } => *sapling_shielded_data = None,
612 Transaction::V5 {
613 sapling_shielded_data,
614 ..
615 } => *sapling_shielded_data = None,
616 Transaction::V6 {
617 sapling_shielded_data,
618 ..
619 } => *sapling_shielded_data = None,
620 Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {}
621 }
622 }
623
624 let mut spend_restriction = transaction.coinbase_spend_restriction(&Network::Mainnet, height);
625 let mut new_inputs = Vec::new();
626 let mut spent_outputs = HashMap::new();
627
628 let original_inputs = transaction.inputs().to_vec();
630 for mut input in original_inputs.into_iter() {
631 if input.outpoint().is_some() {
632 if let Some(selected_outpoint) = find_valid_utxo_for_spend(
635 &mut transaction,
636 &mut spend_restriction,
637 height,
638 utxos,
639 check_transparent_coinbase_spend,
640 ) {
641 input.set_outpoint(selected_outpoint);
642 new_inputs.push(input);
643
644 let spent_utxo = utxos.remove(&selected_outpoint)?;
645 spent_outputs.insert(selected_outpoint, spent_utxo.utxo.output);
646 }
647 } else {
649 new_inputs.push(input.clone());
651 }
652 }
653
654 *transaction.inputs_mut() = new_inputs;
656
657 let (_remaining_transaction_value, new_chain_value_pools) = transaction
658 .fix_chain_value_pools(*chain_value_pools, &spent_outputs)
659 .expect("value fixes produce valid chain value pools and remaining transaction values");
660
661 if transaction.has_transparent_or_shielded_inputs() {
663 if height > Height(0) {
666 *chain_value_pools = new_chain_value_pools;
667
668 utxos.extend(new_transaction_ordered_outputs(
669 &transaction,
670 transaction.hash(),
671 tx_index_in_block,
672 height,
673 ));
674 }
675
676 Some(transaction)
677 } else {
678 None
679 }
680}
681
682pub fn find_valid_utxo_for_spend<F, E>(
688 transaction: &mut Transaction,
689 spend_restriction: &mut CoinbaseSpendRestriction,
690 spend_height: Height,
691 utxos: &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
692 check_transparent_coinbase_spend: F,
693) -> Option<transparent::OutPoint>
694where
695 F: Fn(
696 transparent::OutPoint,
697 transparent::CoinbaseSpendRestriction,
698 &transparent::Utxo,
699 ) -> Result<(), E>
700 + Copy
701 + 'static,
702{
703 let has_shielded_outputs = transaction.has_shielded_outputs();
704 let delete_transparent_outputs =
705 CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height };
706 for (candidate_outpoint, candidate_utxo) in utxos.iter().take(100) {
708 if check_transparent_coinbase_spend(
712 *candidate_outpoint,
713 *spend_restriction,
714 candidate_utxo.as_ref(),
715 )
716 .is_ok()
717 {
718 return Some(*candidate_outpoint);
719 } else if has_shielded_outputs
720 && check_transparent_coinbase_spend(
721 *candidate_outpoint,
722 delete_transparent_outputs,
723 candidate_utxo.as_ref(),
724 )
725 .is_ok()
726 {
727 *transaction.outputs_mut() = Vec::new();
728 *spend_restriction = delete_transparent_outputs;
729
730 return Some(*candidate_outpoint);
731 }
732 }
733
734 None
735}
736
737impl Arbitrary for Commitment {
738 type Parameters = ();
739
740 fn arbitrary_with(_args: ()) -> Self::Strategy {
741 (any::<[u8; 32]>(), any::<Network>(), any::<Height>())
742 .prop_map(|(commitment_bytes, network, block_height)| {
743 if block_height == Heartwood.activation_height(&network).unwrap() {
744 Commitment::ChainHistoryActivationReserved
745 } else {
746 Commitment::from_bytes(commitment_bytes, &network, block_height)
747 .expect("unexpected failure in from_bytes parsing")
748 }
749 })
750 .boxed()
751 }
752
753 type Strategy = BoxedStrategy<Self>;
754}
755
756impl Arbitrary for Header {
757 type Parameters = LedgerState;
758
759 fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
760 (
761 (4u32..(i32::MAX as u32)),
763 any::<Hash>(),
764 any::<merkle::Root>(),
765 any::<HexDebug<[u8; 32]>>(),
766 serialization::arbitrary::datetime_u32(),
767 any::<CompactDifficulty>(),
768 any::<HexDebug<[u8; 32]>>(),
769 any::<equihash::Solution>(),
770 )
771 .prop_map(
772 move |(
773 version,
774 mut previous_block_hash,
775 merkle_root,
776 commitment_bytes,
777 time,
778 difficulty_threshold,
779 nonce,
780 solution,
781 )| {
782 if let Some(previous_block_hash_override) =
783 ledger_state.previous_block_hash_override
784 {
785 previous_block_hash = previous_block_hash_override;
786 } else if ledger_state.height == Height(0) {
787 previous_block_hash = GENESIS_PREVIOUS_BLOCK_HASH;
788 }
789
790 Header {
791 version,
792 previous_block_hash,
793 merkle_root,
794 commitment_bytes,
795 time,
796 difficulty_threshold,
797 nonce,
798 solution,
799 }
800 },
801 )
802 .boxed()
803 }
804
805 type Strategy = BoxedStrategy<Self>;
806}