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