reth_testing_utils/
generators.rs

1//! Generators for different data structures like block headers, block bodies and ranges of those.
2
3// TODO(rand): update ::random calls after rand_09 migration
4
5use alloy_consensus::{Header, SignableTransaction, Transaction as _, TxLegacy};
6use alloy_eips::{
7    eip1898::BlockWithParent,
8    eip4895::{Withdrawal, Withdrawals},
9    NumHash,
10};
11use alloy_primitives::{Address, BlockNumber, Bytes, TxKind, B256, B64, U256};
12pub use rand::Rng;
13use rand::{distr::uniform::SampleRange, rngs::StdRng, SeedableRng};
14use reth_ethereum_primitives::{Block, BlockBody, Receipt, Transaction, TransactionSigned};
15use reth_primitives_traits::{
16    crypto::secp256k1::sign_message, proofs, Account, Block as _, Log, SealedBlock, SealedHeader,
17    StorageEntry,
18};
19use secp256k1::{Keypair, Secp256k1};
20use std::{
21    cmp::{max, min},
22    collections::BTreeMap,
23    ops::{Range, RangeInclusive},
24};
25
26/// Used to pass arguments for random block generation function in tests
27#[derive(Debug, Default)]
28pub struct BlockParams {
29    /// The parent hash of the block.
30    pub parent: Option<B256>,
31    /// The number of transactions in the block.
32    pub tx_count: Option<u8>,
33    /// The number of ommers (uncles) in the block.
34    pub ommers_count: Option<u8>,
35    /// The number of requests in the block.
36    pub requests_count: Option<u8>,
37    /// The number of withdrawals in the block.
38    pub withdrawals_count: Option<u8>,
39}
40
41/// Used to pass arguments for random block generation function in tests
42#[derive(Debug)]
43pub struct BlockRangeParams {
44    /// The parent hash of the block.
45    pub parent: Option<B256>,
46    /// The range of transactions in the block.
47    /// If set, a random count between the range will be used.
48    /// If not set, a random number of transactions will be used.
49    pub tx_count: Range<u8>,
50    /// The number of requests in the block.
51    pub requests_count: Option<Range<u8>>,
52    /// The number of withdrawals in the block.
53    pub withdrawals_count: Option<Range<u8>>,
54}
55
56impl Default for BlockRangeParams {
57    fn default() -> Self {
58        Self {
59            parent: None,
60            tx_count: 0..u8::MAX / 2,
61            requests_count: None,
62            withdrawals_count: None,
63        }
64    }
65}
66
67/// Returns a random number generator that can be seeded using the `SEED` environment variable.
68///
69/// If `SEED` is not set, a random seed is used.
70pub fn rng() -> StdRng {
71    if let Ok(seed) = std::env::var("SEED") {
72        rng_with_seed(seed.as_bytes())
73    } else {
74        StdRng::from_rng(&mut rand::rng())
75    }
76}
77
78/// Returns a random number generator from a specific seed, as bytes.
79pub fn rng_with_seed(seed: &[u8]) -> StdRng {
80    let mut seed_bytes = [0u8; 32];
81    seed_bytes[..seed.len().min(32)].copy_from_slice(seed);
82    StdRng::from_seed(seed_bytes)
83}
84
85/// Generates a range of random [`SealedHeader`]s.
86///
87/// The parent hash of the first header
88/// in the result will be equal to `head`.
89///
90/// The headers are assumed to not be correct if validated.
91pub fn random_header_range<R: Rng>(
92    rng: &mut R,
93    range: Range<u64>,
94    head: B256,
95) -> Vec<SealedHeader> {
96    let mut headers = Vec::with_capacity(range.end.saturating_sub(range.start) as usize);
97    for idx in range {
98        headers.push(random_header(
99            rng,
100            idx,
101            Some(headers.last().map(|h: &SealedHeader| h.hash()).unwrap_or(head)),
102        ));
103    }
104    headers
105}
106
107/// Generate a random [`BlockWithParent`].
108pub fn random_block_with_parent<R: Rng>(
109    rng: &mut R,
110    number: u64,
111    parent: Option<B256>,
112) -> BlockWithParent {
113    BlockWithParent {
114        parent: parent.unwrap_or_default(),
115        block: NumHash::new(number, rng.random()),
116    }
117}
118
119/// Generate a random [`SealedHeader`].
120///
121/// The header is assumed to not be correct if validated.
122pub fn random_header<R: Rng>(rng: &mut R, number: u64, parent: Option<B256>) -> SealedHeader {
123    let header = alloy_consensus::Header {
124        number,
125        nonce: B64::random(),
126        difficulty: U256::from(rng.random::<u32>()),
127        parent_hash: parent.unwrap_or_default(),
128        ..Default::default()
129    };
130    SealedHeader::seal_slow(header)
131}
132
133/// Generates a random legacy [Transaction].
134///
135/// Every field is random, except:
136///
137/// - The chain ID, which is always 1
138/// - The input, which is always nothing
139pub fn random_tx<R: Rng>(rng: &mut R) -> Transaction {
140    Transaction::Legacy(TxLegacy {
141        chain_id: Some(1),
142        nonce: rng.random::<u16>().into(),
143        gas_price: rng.random::<u16>().into(),
144        gas_limit: rng.random::<u16>().into(),
145        to: TxKind::Call(Address::random()),
146        value: U256::from(rng.random::<u16>()),
147        input: Bytes::default(),
148    })
149}
150
151/// Generates a random legacy [Transaction] that is signed.
152///
153/// On top of the considerations of [`random_tx`], these apply as well:
154///
155/// - There is no guarantee that the nonce is not used twice for the same account
156pub fn random_signed_tx<R: Rng>(rng: &mut R) -> TransactionSigned {
157    let tx = random_tx(rng);
158    sign_tx_with_random_key_pair(rng, tx)
159}
160
161/// Signs the [Transaction] with a random key pair.
162pub fn sign_tx_with_random_key_pair<R: Rng>(_rng: &mut R, tx: Transaction) -> TransactionSigned {
163    let secp = Secp256k1::new();
164    // TODO: rand08
165    let key_pair = Keypair::new(&secp, &mut rand_08::thread_rng());
166    sign_tx_with_key_pair(key_pair, tx)
167}
168
169/// Signs the [Transaction] with the given key pair.
170pub fn sign_tx_with_key_pair(key_pair: Keypair, tx: Transaction) -> TransactionSigned {
171    let signature =
172        sign_message(B256::from_slice(&key_pair.secret_bytes()[..]), tx.signature_hash()).unwrap();
173
174    tx.into_signed(signature).into()
175}
176
177/// Generates a new random [Keypair].
178pub fn generate_key<R: Rng>(_rng: &mut R) -> Keypair {
179    let secp = Secp256k1::new();
180    Keypair::new(&secp, &mut rand_08::thread_rng())
181}
182
183/// Generates a set of [Keypair]s based on the desired count.
184pub fn generate_keys<R: Rng>(_rng: &mut R, count: usize) -> Vec<Keypair> {
185    let secp = Secp256k1::new();
186    // TODO: rand08
187    (0..count).map(|_| Keypair::new(&secp, &mut rand_08::thread_rng())).collect()
188}
189
190/// Generate a random block filled with signed transactions (generated using
191/// [`random_signed_tx`]). If no transaction count is provided, the number of transactions
192/// will be random, otherwise the provided count will be used.
193///
194/// All fields use the default values (and are assumed to be invalid) except for:
195///
196/// - `parent_hash`
197/// - `transactions_root`
198/// - `ommers_hash`
199///
200/// Additionally, `gas_used` and `gas_limit` always exactly match the total `gas_limit` of all
201/// transactions in the block.
202///
203/// The ommer headers are not assumed to be valid.
204pub fn random_block<R: Rng>(
205    rng: &mut R,
206    number: u64,
207    block_params: BlockParams,
208) -> SealedBlock<Block> {
209    // Generate transactions
210    let tx_count = block_params.tx_count.unwrap_or_else(|| rng.random::<u8>());
211    let transactions: Vec<TransactionSigned> =
212        (0..tx_count).map(|_| random_signed_tx(rng)).collect();
213    let total_gas = transactions.iter().fold(0, |sum, tx| sum + tx.gas_limit());
214
215    // Generate ommers
216    let ommers_count = block_params.ommers_count.unwrap_or_else(|| rng.random_range(0..2));
217    let ommers = (0..ommers_count)
218        .map(|_| random_header(rng, number, block_params.parent).unseal())
219        .collect::<Vec<_>>();
220
221    // Calculate roots
222    let transactions_root = proofs::calculate_transaction_root(&transactions);
223    let ommers_hash = proofs::calculate_ommers_root(&ommers);
224
225    let withdrawals = block_params.withdrawals_count.map(|count| {
226        (0..count)
227            .map(|i| Withdrawal {
228                amount: rng.random(),
229                index: i.into(),
230                validator_index: i.into(),
231                address: Address::random(),
232            })
233            .collect::<Vec<_>>()
234    });
235    let withdrawals_root = withdrawals.as_ref().map(|w| proofs::calculate_withdrawals_root(w));
236
237    let header = Header {
238        parent_hash: block_params.parent.unwrap_or_default(),
239        number,
240        gas_used: total_gas,
241        gas_limit: total_gas,
242        transactions_root,
243        ommers_hash,
244        base_fee_per_gas: Some(rng.random()),
245        // TODO(onbjerg): Proper EIP-7685 request support
246        requests_hash: None,
247        withdrawals_root,
248        ..Default::default()
249    };
250
251    Block {
252        header,
253        body: BlockBody { transactions, ommers, withdrawals: withdrawals.map(Withdrawals::new) },
254    }
255    .seal_slow()
256}
257
258/// Generate a range of random blocks.
259///
260/// The parent hash of the first block
261/// in the result will be equal to `head`.
262///
263/// See [`random_block`] for considerations when validating the generated blocks.
264pub fn random_block_range<R: Rng>(
265    rng: &mut R,
266    block_numbers: RangeInclusive<BlockNumber>,
267    block_range_params: BlockRangeParams,
268) -> Vec<SealedBlock<Block>> {
269    let mut blocks =
270        Vec::with_capacity(block_numbers.end().saturating_sub(*block_numbers.start()) as usize);
271    for idx in block_numbers {
272        let tx_count = block_range_params.tx_count.clone().sample_single(rng).unwrap();
273        let requests_count =
274            block_range_params.requests_count.clone().map(|r| r.sample_single(rng).unwrap());
275        let withdrawals_count =
276            block_range_params.withdrawals_count.clone().map(|r| r.sample_single(rng).unwrap());
277        let parent = block_range_params.parent.unwrap_or_default();
278        blocks.push(random_block(
279            rng,
280            idx,
281            BlockParams {
282                parent: Some(
283                    blocks.last().map(|block: &SealedBlock<Block>| block.hash()).unwrap_or(parent),
284                ),
285                tx_count: Some(tx_count),
286                ommers_count: None,
287                requests_count,
288                withdrawals_count,
289            },
290        ));
291    }
292    blocks
293}
294
295/// Collection of account and storage entry changes
296pub type ChangeSet = Vec<(Address, Account, Vec<StorageEntry>)>;
297type AccountState = (Account, Vec<StorageEntry>);
298
299/// Generate a range of changesets for given blocks and accounts.
300///
301/// Returns a Vec of account and storage changes for each block,
302/// along with the final state of all accounts and storages.
303pub fn random_changeset_range<'a, R: Rng, IBlk, IAcc>(
304    rng: &mut R,
305    blocks: IBlk,
306    accounts: IAcc,
307    n_storage_changes: Range<u64>,
308    key_range: Range<u64>,
309) -> (Vec<ChangeSet>, BTreeMap<Address, AccountState>)
310where
311    IBlk: IntoIterator<Item = &'a SealedBlock<Block>>,
312    IAcc: IntoIterator<Item = (Address, (Account, Vec<StorageEntry>))>,
313{
314    let mut state: BTreeMap<_, _> = accounts
315        .into_iter()
316        .map(|(addr, (acc, st))| {
317            (
318                addr,
319                (
320                    acc,
321                    st.into_iter()
322                        .map(|e| (e.key, (e.value, e.is_private)))
323                        .collect::<BTreeMap<_, _>>(),
324                ),
325            )
326        })
327        .collect();
328
329    let valid_addresses = state.keys().copied().collect::<Vec<_>>();
330
331    let mut changesets = Vec::new();
332
333    for _block in blocks {
334        let mut changeset = Vec::new();
335        let (from, to, mut transfer, new_entries) = random_account_change(
336            rng,
337            &valid_addresses,
338            n_storage_changes.clone(),
339            key_range.clone(),
340        );
341
342        // extract from sending account
343        let (prev_from, _) = state.get_mut(&from).unwrap();
344        changeset.push((from, *prev_from, Vec::new()));
345
346        transfer = max(min(transfer, prev_from.balance), U256::from(1));
347        prev_from.balance = prev_from.balance.wrapping_sub(transfer);
348
349        // deposit in receiving account and update storage
350        let (prev_to, storage): &mut (Account, BTreeMap<B256, (U256, bool)>) =
351            state.get_mut(&to).unwrap();
352
353        let mut old_entries: Vec<_> = new_entries
354            .into_iter()
355            .filter_map(|entry| {
356                let old = if entry.value.is_zero() {
357                    let old = storage.remove(&entry.key);
358                    if matches!(old, Some((U256::ZERO, false))) {
359                        return None;
360                    }
361                    old
362                } else {
363                    storage.insert(entry.key, (entry.value, entry.is_private))
364                };
365                match old {
366                    Some((old_value, old_is_private)) => {
367                        return Some(StorageEntry {
368                            value: old_value,
369                            is_private: old_is_private,
370                            ..entry
371                        });
372                    }
373                    None => {
374                        return Some(StorageEntry { value: U256::ZERO, is_private: false, ..entry });
375                    }
376                }
377            })
378            .collect();
379        old_entries.sort_by_key(|entry| entry.key);
380
381        changeset.push((to, *prev_to, old_entries));
382
383        changeset.sort_by_key(|(address, _, _)| *address);
384
385        prev_to.balance = prev_to.balance.wrapping_add(transfer);
386
387        changesets.push(changeset);
388    }
389
390    let final_state = state
391        .into_iter()
392        .map(|(addr, (acc, storage))| {
393            (addr, (acc, storage.into_iter().map(|v| v.into()).collect()))
394        })
395        .collect();
396    (changesets, final_state)
397}
398
399/// Generate a random account change.
400///
401/// Returns two addresses, a `balance_change`, and a Vec of new storage entries.
402pub fn random_account_change<R: Rng>(
403    rng: &mut R,
404    valid_addresses: &[Address],
405    n_storage_changes: Range<u64>,
406    key_range: Range<u64>,
407) -> (Address, Address, U256, Vec<StorageEntry>) {
408    use rand::prelude::IndexedRandom;
409    let mut addresses = valid_addresses.choose_multiple(rng, 2).copied();
410
411    let addr_from = addresses.next().unwrap_or_else(Address::random);
412    let addr_to = addresses.next().unwrap_or_else(Address::random);
413
414    let balance_change = U256::from(rng.random::<u64>());
415
416    let storage_changes = if n_storage_changes.is_empty() {
417        Vec::new()
418    } else {
419        (0..n_storage_changes.sample_single(rng).unwrap())
420            .map(|_| random_storage_entry(rng, key_range.clone()))
421            .collect()
422    };
423
424    (addr_from, addr_to, balance_change, storage_changes)
425}
426
427/// Generate a random storage change.
428pub fn random_storage_entry<R: Rng>(rng: &mut R, key_range: Range<u64>) -> StorageEntry {
429    let key = B256::new({
430        let n = key_range.sample_single(rng).unwrap();
431        let mut m = [0u8; 32];
432        m[24..32].copy_from_slice(&n.to_be_bytes());
433        m
434    });
435    let value = U256::from(rng.random::<u64>());
436
437    StorageEntry { key, value, ..Default::default() }
438}
439
440/// Generate random Externally Owned Account (EOA account without contract).
441pub fn random_eoa_account<R: Rng>(rng: &mut R) -> (Address, Account) {
442    let nonce: u64 = rng.random();
443    let balance = U256::from(rng.random::<u32>());
444    let addr = Address::random();
445
446    (addr, Account { nonce, balance, bytecode_hash: None })
447}
448
449/// Generate random Externally Owned Accounts
450pub fn random_eoa_accounts<R: Rng>(rng: &mut R, accounts_num: usize) -> Vec<(Address, Account)> {
451    let mut accounts = Vec::with_capacity(accounts_num);
452    for _ in 0..accounts_num {
453        accounts.push(random_eoa_account(rng))
454    }
455    accounts
456}
457
458/// Generate random Contract Accounts
459pub fn random_contract_account_range<R: Rng>(
460    rng: &mut R,
461    acc_range: &mut Range<u64>,
462) -> Vec<(Address, Account)> {
463    let mut accounts = Vec::with_capacity(acc_range.end.saturating_sub(acc_range.start) as usize);
464    for _ in acc_range {
465        let (address, eoa_account) = random_eoa_account(rng);
466        // todo: can a non-eoa account have a nonce > 0?
467        let account = Account { bytecode_hash: Some(B256::random()), ..eoa_account };
468        accounts.push((address, account))
469    }
470    accounts
471}
472
473/// Generate random receipt for transaction
474pub fn random_receipt<R: Rng>(
475    rng: &mut R,
476    transaction: &TransactionSigned,
477    logs_count: Option<u8>,
478) -> Receipt {
479    let success = rng.random::<bool>();
480    let logs_count = logs_count.unwrap_or_else(|| rng.random::<u8>());
481    #[expect(clippy::needless_update)] // side-effect of optimism fields
482    Receipt {
483        tx_type: transaction.tx_type(),
484        success,
485        cumulative_gas_used: rng.random_range(0..=transaction.gas_limit()),
486        logs: if success {
487            (0..logs_count).map(|_| random_log(rng, None, None)).collect()
488        } else {
489            vec![]
490        },
491        ..Default::default()
492    }
493}
494
495/// Generate random log
496pub fn random_log<R: Rng>(rng: &mut R, address: Option<Address>, topics_count: Option<u8>) -> Log {
497    let data_byte_count = rng.random::<u8>() as usize;
498    let topics_count = topics_count.unwrap_or_else(|| rng.random()) as usize;
499    Log::new_unchecked(
500        address.unwrap_or_else(|| Address::random()),
501        std::iter::repeat_with(|| B256::random()).take(topics_count).collect(),
502        std::iter::repeat_with(|| rng.random()).take(data_byte_count).collect::<Vec<_>>().into(),
503    )
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509    use alloy_consensus::TxEip1559;
510    use alloy_eips::eip2930::AccessList;
511    use alloy_primitives::{hex, Signature};
512    use reth_primitives_traits::{
513        crypto::secp256k1::{public_key_to_address, sign_message},
514        SignerRecoverable,
515    };
516    use std::str::FromStr;
517
518    #[test]
519    fn test_sign_message() {
520        let secp = Secp256k1::new();
521
522        let tx = Transaction::Eip1559(TxEip1559 {
523            chain_id: 1,
524            nonce: 0x42,
525            gas_limit: 44386,
526            to: TxKind::Call(hex!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into()),
527            value: U256::from(0_u64),
528            input:  hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
529            max_fee_per_gas: 0x4a817c800,
530            max_priority_fee_per_gas: 0x3b9aca00,
531            access_list: AccessList::default(),
532        });
533        let signature_hash = tx.signature_hash();
534
535        for _ in 0..100 {
536            let key_pair = Keypair::new(&secp, &mut rand_08::thread_rng());
537
538            let signature =
539                sign_message(B256::from_slice(&key_pair.secret_bytes()[..]), signature_hash)
540                    .unwrap();
541
542            let signed: TransactionSigned = tx.clone().into_signed(signature).into();
543            let recovered = signed.recover_signer().unwrap();
544
545            let expected = public_key_to_address(key_pair.public_key());
546            assert_eq!(recovered, expected);
547        }
548    }
549
550    #[test]
551    fn test_sign_eip_155() {
552        // reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#example
553        let transaction = Transaction::Legacy(TxLegacy {
554            chain_id: Some(1),
555            nonce: 9,
556            gas_price: 20 * 10_u128.pow(9),
557            gas_limit: 21000,
558            to: TxKind::Call(hex!("3535353535353535353535353535353535353535").into()),
559            value: U256::from(10_u128.pow(18)),
560            input: Bytes::default(),
561        });
562
563        // TODO resolve dependency issue
564        // let expected =
565        // hex!("ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080");
566        // assert_eq!(expected, &alloy_rlp::encode(transaction));
567
568        let hash = transaction.signature_hash();
569        let expected =
570            B256::from_str("daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
571                .unwrap();
572        assert_eq!(expected, hash);
573
574        let secret =
575            B256::from_str("4646464646464646464646464646464646464646464646464646464646464646")
576                .unwrap();
577        let signature = sign_message(secret, hash).unwrap();
578
579        let expected = Signature::new(
580            U256::from_str(
581                "18515461264373351373200002665853028612451056578545711640558177340181847433846",
582            )
583            .unwrap(),
584            U256::from_str(
585                "46948507304638947509940763649030358759909902576025900602547168820602576006531",
586            )
587            .unwrap(),
588            false,
589        );
590        assert_eq!(expected, signature);
591    }
592}