zebra_chain/block/
arbitrary.rs

1//! Randomised property testing for [`Block`]s.
2
3use 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    serialization,
12    transaction::arbitrary::MAX_ARBITRARY_ITEMS,
13    transparent::{
14        new_transaction_ordered_outputs, CoinbaseSpendRestriction,
15        MIN_TRANSPARENT_COINBASE_MATURITY,
16    },
17    work::{difficulty::CompactDifficulty, equihash},
18};
19
20use super::*;
21
22/// The chain length for most zebra-chain proptests.
23///
24/// Most generated chains will contain transparent spends at or before this height.
25///
26/// This height was chosen a tradeoff between chains with no spends,
27/// and chains which spend outputs created by previous spends.
28///
29/// The raw probability of having no spends during a test run is:
30/// ```text
31/// shielded_input = shielded_pool_count / pool_count
32/// expected_transactions = expected_inputs = MAX_ARBITRARY_ITEMS/2
33/// shielded_input^(expected_transactions * expected_inputs * (PREVOUTS_CHAIN_HEIGHT - 1))
34/// ```
35///
36/// This probability is approximately 3%. However, proptest generation and
37/// minimisation strategies can create additional chains with no transparent spends.
38///
39/// To increase the proportion of test runs with proptest spends, increase `PREVOUTS_CHAIN_HEIGHT`.
40pub const PREVOUTS_CHAIN_HEIGHT: usize = 4;
41
42/// The chain length for most zebra-state proptests.
43///
44/// Most generated chains will contain transparent spends at or before this height.
45///
46/// This height was chosen as a tradeoff between chains with no transparent spends,
47/// and chains which spend outputs created by previous spends.
48///
49/// See [`block::arbitrary::PREVOUTS_CHAIN_HEIGHT`] for details.
50pub const MAX_PARTIAL_CHAIN_BLOCKS: usize =
51    MIN_TRANSPARENT_COINBASE_MATURITY as usize + PREVOUTS_CHAIN_HEIGHT;
52
53impl Arbitrary for Height {
54    type Parameters = ();
55
56    fn arbitrary_with(_args: ()) -> Self::Strategy {
57        (Height::MIN.0..=Height::MAX.0).prop_map(Height).boxed()
58    }
59
60    type Strategy = BoxedStrategy<Self>;
61}
62
63#[derive(Debug, Clone)]
64#[non_exhaustive]
65/// The configuration data for proptest when generating arbitrary chains
66pub struct LedgerState {
67    /// The height of the generated block, or the start height of the generated chain.
68    ///
69    /// To get the network upgrade, use the `network_upgrade` method.
70    ///
71    /// If `network_upgrade_override` is not set, the network upgrade is derived
72    /// from the `height` and `network`.
73    pub height: Height,
74
75    /// The network to generate fake blocks for.
76    pub network: Network,
77
78    /// Overrides the network upgrade calculated from `height` and `network`.
79    ///
80    /// To get the network upgrade, use the `network_upgrade` method.
81    network_upgrade_override: Option<NetworkUpgrade>,
82
83    /// Overrides the previous block hashes in blocks generated by this ledger.
84    previous_block_hash_override: Option<block::Hash>,
85
86    /// Regardless of tip height and network, every transaction is this version.
87    transaction_version_override: Option<u32>,
88
89    /// Every V5 and later transaction has a valid `network_upgrade` field.
90    ///
91    /// If `false`, zero or more transactions may have invalid network upgrades.
92    transaction_has_valid_network_upgrade: bool,
93
94    /// Generate coinbase transactions.
95    ///
96    /// In a block or transaction vector, make the first transaction a coinbase
97    /// transaction.
98    ///
99    /// For an individual transaction, make the transaction a coinbase
100    /// transaction.
101    pub(crate) has_coinbase: bool,
102}
103
104/// Overrides for arbitrary [`LedgerState`]s.
105#[derive(Debug, Clone, Copy)]
106pub struct LedgerStateOverride {
107    /// Every chain starts at this block. Single blocks have this height.
108    pub height_override: Option<Height>,
109
110    /// Every chain starts with a block with this previous block hash.
111    /// Single blocks have this previous block hash.
112    pub previous_block_hash_override: Option<block::Hash>,
113
114    /// Regardless of tip height and network, every block has features from this
115    /// network upgrade.
116    pub network_upgrade_override: Option<NetworkUpgrade>,
117
118    /// Regardless of tip height and network, every transaction is this version.
119    pub transaction_version_override: Option<u32>,
120
121    /// Every V5 and later transaction has a valid `network_upgrade` field.
122    ///
123    /// If `false`, zero or more transactions may have invalid network upgrades.
124    pub transaction_has_valid_network_upgrade: bool,
125
126    /// Every block has exactly one coinbase transaction.
127    /// Transactions are always coinbase transactions.
128    pub always_has_coinbase: bool,
129}
130
131impl LedgerState {
132    /// Returns the default strategy for creating arbitrary `LedgerState`s.
133    pub fn default_strategy() -> BoxedStrategy<Self> {
134        Self::arbitrary_with(LedgerStateOverride::default())
135    }
136
137    /// Returns a strategy for creating arbitrary `LedgerState`s, without any
138    /// overrides.
139    pub fn no_override_strategy() -> BoxedStrategy<Self> {
140        Self::arbitrary_with(LedgerStateOverride {
141            height_override: None,
142            previous_block_hash_override: None,
143            network_upgrade_override: None,
144            transaction_version_override: None,
145            transaction_has_valid_network_upgrade: false,
146            always_has_coinbase: false,
147        })
148    }
149
150    /// Returns a strategy for creating `LedgerState`s with features from
151    /// `network_upgrade_override`.
152    ///
153    /// These features ignore the actual tip height and network.
154    pub fn network_upgrade_strategy(
155        network_upgrade_override: NetworkUpgrade,
156        transaction_version_override: impl Into<Option<u32>>,
157        transaction_has_valid_network_upgrade: bool,
158    ) -> BoxedStrategy<Self> {
159        Self::arbitrary_with(LedgerStateOverride {
160            height_override: None,
161            previous_block_hash_override: None,
162            network_upgrade_override: Some(network_upgrade_override),
163            transaction_version_override: transaction_version_override.into(),
164            transaction_has_valid_network_upgrade,
165            always_has_coinbase: false,
166        })
167    }
168
169    /// Returns a strategy for creating `LedgerState`s that always have coinbase
170    /// transactions.
171    ///
172    /// Also applies `network_upgrade_override`, if present.
173    pub fn coinbase_strategy(
174        network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
175        transaction_version_override: impl Into<Option<u32>>,
176        transaction_has_valid_network_upgrade: bool,
177    ) -> BoxedStrategy<Self> {
178        Self::arbitrary_with(LedgerStateOverride {
179            height_override: None,
180            previous_block_hash_override: None,
181            network_upgrade_override: network_upgrade_override.into(),
182            transaction_version_override: transaction_version_override.into(),
183            transaction_has_valid_network_upgrade,
184            always_has_coinbase: true,
185        })
186    }
187
188    /// Returns a strategy for creating `LedgerState`s that start with a genesis
189    /// block.
190    ///
191    /// These strategies also have coinbase transactions, and an optional network
192    /// upgrade override.
193    ///
194    /// Use the `Genesis` network upgrade to get a random genesis block, with
195    /// Zcash genesis features.
196    pub fn genesis_strategy(
197        network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
198        transaction_version_override: impl Into<Option<u32>>,
199        transaction_has_valid_network_upgrade: bool,
200    ) -> BoxedStrategy<Self> {
201        Self::arbitrary_with(LedgerStateOverride {
202            height_override: Some(Height(0)),
203            previous_block_hash_override: Some(GENESIS_PREVIOUS_BLOCK_HASH),
204            network_upgrade_override: network_upgrade_override.into(),
205            transaction_version_override: transaction_version_override.into(),
206            transaction_has_valid_network_upgrade,
207            always_has_coinbase: true,
208        })
209    }
210
211    /// Returns a strategy for creating `LedgerState`s that start at `height`.
212    ///
213    /// These strategies also have coinbase transactions, and an optional network
214    /// upgrade override.
215    pub fn height_strategy(
216        height: Height,
217        network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
218        transaction_version_override: impl Into<Option<u32>>,
219        transaction_has_valid_network_upgrade: bool,
220    ) -> BoxedStrategy<Self> {
221        Self::arbitrary_with(LedgerStateOverride {
222            height_override: Some(height),
223            previous_block_hash_override: None,
224            network_upgrade_override: network_upgrade_override.into(),
225            transaction_version_override: transaction_version_override.into(),
226            transaction_has_valid_network_upgrade,
227            always_has_coinbase: true,
228        })
229    }
230
231    /// Returns the network upgrade for this ledger state.
232    ///
233    /// If `network_upgrade_override` is set, it replaces the upgrade calculated
234    /// using `height` and `network`.
235    pub fn network_upgrade(&self) -> NetworkUpgrade {
236        if let Some(network_upgrade_override) = self.network_upgrade_override {
237            network_upgrade_override
238        } else {
239            NetworkUpgrade::current(&self.network, self.height)
240        }
241    }
242
243    /// Returns the transaction version override.
244    pub fn transaction_version_override(&self) -> Option<u32> {
245        self.transaction_version_override
246    }
247
248    /// Returns `true` if all transactions have valid network upgrade fields.
249    ///
250    /// If `false`, some transactions have invalid network upgrades.
251    pub fn transaction_has_valid_network_upgrade(&self) -> bool {
252        self.transaction_has_valid_network_upgrade
253    }
254}
255
256impl Default for LedgerState {
257    fn default() -> Self {
258        // TODO: stop having a default network
259        let default_network = Network::default();
260        let default_override = LedgerStateOverride::default();
261
262        let most_recent_nu = NetworkUpgrade::current(&default_network, Height::MAX);
263        let most_recent_activation_height =
264            most_recent_nu.activation_height(&default_network).unwrap();
265
266        LedgerState {
267            height: most_recent_activation_height,
268            network: default_network,
269            network_upgrade_override: default_override.network_upgrade_override,
270            previous_block_hash_override: default_override.previous_block_hash_override,
271            transaction_version_override: default_override.transaction_version_override,
272            transaction_has_valid_network_upgrade: default_override
273                .transaction_has_valid_network_upgrade,
274            has_coinbase: default_override.always_has_coinbase,
275        }
276    }
277}
278
279impl Default for LedgerStateOverride {
280    fn default() -> Self {
281        let default_network = Network::default();
282
283        // TODO: dynamically select any future network upgrade (#1974)
284        let nu5_activation_height = Nu5.activation_height(&default_network);
285        let nu5_override = if nu5_activation_height.is_some() {
286            None
287        } else {
288            Some(Nu5)
289        };
290
291        LedgerStateOverride {
292            height_override: None,
293            previous_block_hash_override: None,
294            network_upgrade_override: nu5_override,
295            transaction_version_override: None,
296            transaction_has_valid_network_upgrade: false,
297            always_has_coinbase: true,
298        }
299    }
300}
301
302impl Arbitrary for LedgerState {
303    type Parameters = LedgerStateOverride;
304
305    /// Generate an arbitrary [`LedgerState`].
306    ///
307    /// The default strategy arbitrarily skips some coinbase transactions, and
308    /// has an arbitrary start height. To override, use a specific [`LedgerState`]
309    /// strategy method.
310    fn arbitrary_with(ledger_override: Self::Parameters) -> Self::Strategy {
311        (
312            any::<Height>(),
313            any::<Network>(),
314            any::<bool>(),
315            any::<bool>(),
316        )
317            .prop_map(
318                move |(height, network, transaction_has_valid_network_upgrade, has_coinbase)| {
319                    LedgerState {
320                        height: ledger_override.height_override.unwrap_or(height),
321                        network,
322                        network_upgrade_override: ledger_override.network_upgrade_override,
323                        previous_block_hash_override: ledger_override.previous_block_hash_override,
324                        transaction_version_override: ledger_override.transaction_version_override,
325                        transaction_has_valid_network_upgrade: ledger_override
326                            .transaction_has_valid_network_upgrade
327                            || transaction_has_valid_network_upgrade,
328                        has_coinbase: ledger_override.always_has_coinbase || has_coinbase,
329                    }
330                },
331            )
332            .boxed()
333    }
334
335    type Strategy = BoxedStrategy<Self>;
336}
337
338impl Arbitrary for Block {
339    type Parameters = LedgerState;
340
341    fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
342        let transactions_strategy = {
343            let ledger_state = ledger_state.clone();
344            // Generate a random number transactions. A coinbase tx is always generated, so if
345            // `transaction_count` is zero, the block will contain only the coinbase tx.
346            (0..MAX_ARBITRARY_ITEMS).prop_flat_map(move |transaction_count| {
347                Transaction::vec_strategy(ledger_state.clone(), transaction_count)
348            })
349        };
350
351        // TODO: if needed, fixup:
352        // - history and authorizing data commitments
353        // - the transaction merkle root
354
355        (Header::arbitrary_with(ledger_state), transactions_strategy)
356            .prop_map(move |(header, transactions)| Self {
357                header: header.into(),
358                transactions,
359            })
360            .boxed()
361    }
362
363    type Strategy = BoxedStrategy<Self>;
364}
365
366/// Skip checking transparent coinbase spends in [`Block::partial_chain_strategy`].
367#[allow(clippy::result_unit_err)]
368pub fn allow_all_transparent_coinbase_spends(
369    _: transparent::OutPoint,
370    _: transparent::CoinbaseSpendRestriction,
371    _: &transparent::Utxo,
372) -> Result<(), ()> {
373    Ok(())
374}
375
376impl Block {
377    /// Returns a strategy for creating vectors of blocks with increasing height.
378    ///
379    /// Each vector is `count` blocks long.
380    ///
381    /// `check_transparent_coinbase_spend` is used to check if
382    /// transparent coinbase UTXOs are valid, before using them in blocks.
383    /// Use [`allow_all_transparent_coinbase_spends`] to disable this check.
384    ///
385    /// `generate_valid_commitments` specifies if the generated blocks
386    /// should have valid commitments. This makes it much slower so it's better
387    /// to enable only when needed.
388    pub fn partial_chain_strategy<F, E>(
389        mut current: LedgerState,
390        count: usize,
391        check_transparent_coinbase_spend: F,
392        generate_valid_commitments: bool,
393    ) -> BoxedStrategy<SummaryDebug<Vec<Arc<Self>>>>
394    where
395        F: Fn(
396                transparent::OutPoint,
397                transparent::CoinbaseSpendRestriction,
398                &transparent::Utxo,
399            ) -> Result<(), E>
400            + Copy
401            + 'static,
402    {
403        let mut vec = Vec::with_capacity(count);
404
405        // generate block strategies with the correct heights
406        for _ in 0..count {
407            vec.push((Just(current.height), Block::arbitrary_with(current.clone())));
408            current.height.0 += 1;
409        }
410
411        // after the vec strategy generates blocks, fixup invalid parts of the blocks
412        vec.prop_map(move |mut vec| {
413            let mut previous_block_hash = None;
414            let mut utxos = HashMap::new();
415            let mut chain_value_pools = ValueBalance::zero();
416            let mut sapling_tree = sapling::tree::NoteCommitmentTree::default();
417            let mut orchard_tree = orchard::tree::NoteCommitmentTree::default();
418            // The history tree usually takes care of "creating itself". But this
419            // only works when blocks are pushed into it starting from genesis
420            // (or at least pre-Heartwood, where the tree is not required).
421            // However, this strategy can generate blocks from an arbitrary height,
422            // so we must wait for the first block to create the history tree from it.
423            // This is why `Option` is used here.
424            let mut history_tree: Option<HistoryTree> = None;
425
426            for (height, block) in vec.iter_mut() {
427                // fixup the previous block hash
428                if let Some(previous_block_hash) = previous_block_hash {
429                    Arc::make_mut(&mut block.header).previous_block_hash = previous_block_hash;
430                }
431
432                let mut new_transactions = Vec::new();
433                for (tx_index_in_block, transaction) in block.transactions.drain(..).enumerate() {
434                    if let Some(transaction) = fix_generated_transaction(
435                        (*transaction).clone(),
436                        tx_index_in_block,
437                        *height,
438                        &mut chain_value_pools,
439                        &mut utxos,
440                        check_transparent_coinbase_spend,
441                    ) {
442                        // The FinalizedState does not update the note commitment trees with the genesis block,
443                        // because it doesn't need to (the trees are not used at that point) and updating them
444                        // would be awkward since the genesis block is handled separately there.
445                        // This forces us to skip the genesis block here too in order to able to use
446                        // this to test the finalized state.
447                        //
448                        // TODO: run note commitment tree updates in parallel rayon threads,
449                        //       using `NoteCommitmentTrees::update_trees_parallel()`
450                        if generate_valid_commitments && *height != Height(0) {
451                            for sapling_note_commitment in transaction.sapling_note_commitments() {
452                                sapling_tree.append(*sapling_note_commitment).unwrap();
453                            }
454                            for orchard_note_commitment in transaction.orchard_note_commitments() {
455                                orchard_tree.append(*orchard_note_commitment).unwrap();
456                            }
457                        }
458                        new_transactions.push(Arc::new(transaction));
459                    }
460                }
461
462                // delete invalid transactions
463                block.transactions = new_transactions;
464
465                // fix commitment (must be done after finishing changing the block)
466                if generate_valid_commitments {
467                    let current_height = block.coinbase_height().unwrap();
468                    let heartwood_height = NetworkUpgrade::Heartwood
469                        .activation_height(&current.network)
470                        .unwrap();
471                    let nu5_height = NetworkUpgrade::Nu5.activation_height(&current.network);
472
473                    match current_height.cmp(&heartwood_height) {
474                        std::cmp::Ordering::Less => {
475                            // In pre-Heartwood blocks this is the Sapling note commitment tree root.
476                            // We don't validate it since we checkpoint on Canopy, but it
477                            // needs to be well-formed, i.e. smaller than 𝑞_J, so we
478                            // arbitrarily set it to 1.
479                            let block_header = Arc::make_mut(&mut block.header);
480                            block_header.commitment_bytes = [0u8; 32].into();
481                            block_header.commitment_bytes[0] = 1;
482                        }
483                        std::cmp::Ordering::Equal => {
484                            // The Heartwood activation block has a hardcoded all-zeroes commitment.
485                            let block_header = Arc::make_mut(&mut block.header);
486                            block_header.commitment_bytes = [0u8; 32].into();
487                        }
488                        std::cmp::Ordering::Greater => {
489                            // Set the correct commitment bytes according to the network upgrade.
490                            let history_tree_root = match &history_tree {
491                                Some(tree) => tree.hash().unwrap_or_else(|| [0u8; 32].into()),
492                                None => [0u8; 32].into(),
493                            };
494                            if nu5_height.is_some() && current_height >= nu5_height.unwrap() {
495                                // From zebra-state/src/service/check.rs
496                                let auth_data_root = block.auth_data_root();
497                                let hash_block_commitments =
498                                    ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
499                                        &history_tree_root,
500                                        &auth_data_root,
501                                    );
502                                let block_header = Arc::make_mut(&mut block.header);
503                                block_header.commitment_bytes =
504                                    hash_block_commitments.bytes_in_serialized_order().into();
505                            } else {
506                                let block_header = Arc::make_mut(&mut block.header);
507                                block_header.commitment_bytes =
508                                    history_tree_root.bytes_in_serialized_order().into();
509                            }
510                        }
511                    }
512                    // update history tree for the next block
513                    if let Some(history_tree) = history_tree.as_mut() {
514                        history_tree
515                            .push(
516                                &current.network,
517                                Arc::new(block.clone()),
518                                &sapling_tree.root(),
519                                &orchard_tree.root(),
520                            )
521                            .unwrap();
522                    } else {
523                        history_tree = Some(
524                            HistoryTree::from_block(
525                                &current.network,
526                                Arc::new(block.clone()),
527                                &sapling_tree.root(),
528                                &orchard_tree.root(),
529                            )
530                            .unwrap(),
531                        );
532                    }
533                }
534
535                // now that we've made all the changes, calculate our block hash,
536                // so the next block can use it
537                previous_block_hash = Some(block.hash());
538            }
539            SummaryDebug(
540                vec.into_iter()
541                    .map(|(_height, block)| Arc::new(block))
542                    .collect(),
543            )
544        })
545        .boxed()
546    }
547}
548
549/// Fix `transaction` so it obeys more consensus rules.
550///
551/// Spends [`transparent::OutPoint`]s from `utxos`, and adds newly created outputs.
552///
553/// If the transaction can't be fixed, returns `None`.
554pub fn fix_generated_transaction<F, E>(
555    mut transaction: Transaction,
556    tx_index_in_block: usize,
557    height: Height,
558    chain_value_pools: &mut ValueBalance<NonNegative>,
559    utxos: &mut HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
560    check_transparent_coinbase_spend: F,
561) -> Option<Transaction>
562where
563    F: Fn(
564            transparent::OutPoint,
565            transparent::CoinbaseSpendRestriction,
566            &transparent::Utxo,
567        ) -> Result<(), E>
568        + Copy
569        + 'static,
570{
571    let mut spend_restriction = transaction.coinbase_spend_restriction(&Network::Mainnet, height);
572    let mut new_inputs = Vec::new();
573    let mut spent_outputs = HashMap::new();
574
575    // fixup the transparent spends
576    let original_inputs = transaction.inputs().to_vec();
577    for mut input in original_inputs.into_iter() {
578        if input.outpoint().is_some() {
579            // the transparent chain value pool is the sum of unspent UTXOs,
580            // so we don't need to check it separately, because we only spend unspent UTXOs
581            if let Some(selected_outpoint) = find_valid_utxo_for_spend(
582                &mut transaction,
583                &mut spend_restriction,
584                height,
585                utxos,
586                check_transparent_coinbase_spend,
587            ) {
588                input.set_outpoint(selected_outpoint);
589                new_inputs.push(input);
590
591                let spent_utxo = utxos
592                    .remove(&selected_outpoint)
593                    .expect("selected outpoint must have a UTXO");
594                spent_outputs.insert(selected_outpoint, spent_utxo.utxo.output);
595            }
596            // otherwise, drop the invalid input, because it has no valid UTXOs to spend
597        } else {
598            // preserve coinbase inputs
599            new_inputs.push(input.clone());
600        }
601    }
602
603    // delete invalid inputs
604    *transaction.inputs_mut() = new_inputs;
605
606    let (_remaining_transaction_value, new_chain_value_pools) = transaction
607        .fix_chain_value_pools(*chain_value_pools, &spent_outputs)
608        .expect("value fixes produce valid chain value pools and remaining transaction values");
609
610    // TODO: if needed, check output count here as well
611    if transaction.has_transparent_or_shielded_inputs() {
612        // consensus rule: skip genesis created UTXOs
613        // Zebra implementation: also skip shielded chain value pool changes
614        if height > Height(0) {
615            *chain_value_pools = new_chain_value_pools;
616
617            utxos.extend(new_transaction_ordered_outputs(
618                &transaction,
619                transaction.hash(),
620                tx_index_in_block,
621                height,
622            ));
623        }
624
625        Some(transaction)
626    } else {
627        None
628    }
629}
630
631/// Find a valid [`transparent::OutPoint`] in `utxos` to spend in `transaction`.
632///
633/// Modifies `transaction` and updates `spend_restriction` if needed.
634///
635/// If there is no valid output, or many search attempts have failed, returns `None`.
636pub fn find_valid_utxo_for_spend<F, E>(
637    transaction: &mut Transaction,
638    spend_restriction: &mut CoinbaseSpendRestriction,
639    spend_height: Height,
640    utxos: &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
641    check_transparent_coinbase_spend: F,
642) -> Option<transparent::OutPoint>
643where
644    F: Fn(
645            transparent::OutPoint,
646            transparent::CoinbaseSpendRestriction,
647            &transparent::Utxo,
648        ) -> Result<(), E>
649        + Copy
650        + 'static,
651{
652    let has_shielded_outputs = transaction.has_shielded_outputs();
653    let delete_transparent_outputs =
654        CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height };
655    let mut attempts: usize = 0;
656
657    // choose an arbitrary spendable UTXO, in hash set order
658    while let Some((candidate_outpoint, candidate_utxo)) = utxos.iter().next() {
659        attempts += 1;
660
661        // Avoid O(n^2) algorithmic complexity by giving up early,
662        // rather than exhaustively checking the entire UTXO set
663        if attempts > 100 {
664            return None;
665        }
666
667        // try the utxo as-is, then try it with deleted transparent outputs
668        if check_transparent_coinbase_spend(
669            *candidate_outpoint,
670            *spend_restriction,
671            candidate_utxo.as_ref(),
672        )
673        .is_ok()
674        {
675            return Some(*candidate_outpoint);
676        } else if has_shielded_outputs
677            && check_transparent_coinbase_spend(
678                *candidate_outpoint,
679                delete_transparent_outputs,
680                candidate_utxo.as_ref(),
681            )
682            .is_ok()
683        {
684            *transaction.outputs_mut() = Vec::new();
685            *spend_restriction = delete_transparent_outputs;
686
687            return Some(*candidate_outpoint);
688        }
689    }
690
691    None
692}
693
694impl Arbitrary for Commitment {
695    type Parameters = ();
696
697    fn arbitrary_with(_args: ()) -> Self::Strategy {
698        (any::<[u8; 32]>(), any::<Network>(), any::<Height>())
699            .prop_map(|(commitment_bytes, network, block_height)| {
700                if block_height == Heartwood.activation_height(&network).unwrap() {
701                    Commitment::ChainHistoryActivationReserved
702                } else {
703                    Commitment::from_bytes(commitment_bytes, &network, block_height)
704                        .expect("unexpected failure in from_bytes parsing")
705                }
706            })
707            .boxed()
708    }
709
710    type Strategy = BoxedStrategy<Self>;
711}
712
713impl Arbitrary for Header {
714    type Parameters = LedgerState;
715
716    fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
717        (
718            // version is interpreted as i32 in the spec, so we are limited to i32::MAX here
719            (4u32..(i32::MAX as u32)),
720            any::<Hash>(),
721            any::<merkle::Root>(),
722            any::<HexDebug<[u8; 32]>>(),
723            serialization::arbitrary::datetime_u32(),
724            any::<CompactDifficulty>(),
725            any::<HexDebug<[u8; 32]>>(),
726            any::<equihash::Solution>(),
727        )
728            .prop_map(
729                move |(
730                    version,
731                    mut previous_block_hash,
732                    merkle_root,
733                    commitment_bytes,
734                    time,
735                    difficulty_threshold,
736                    nonce,
737                    solution,
738                )| {
739                    if let Some(previous_block_hash_override) =
740                        ledger_state.previous_block_hash_override
741                    {
742                        previous_block_hash = previous_block_hash_override;
743                    } else if ledger_state.height == Height(0) {
744                        previous_block_hash = GENESIS_PREVIOUS_BLOCK_HASH;
745                    }
746
747                    Header {
748                        version,
749                        previous_block_hash,
750                        merkle_root,
751                        commitment_bytes,
752                        time,
753                        difficulty_threshold,
754                        nonce,
755                        solution,
756                    }
757                },
758            )
759            .boxed()
760    }
761
762    type Strategy = BoxedStrategy<Self>;
763}