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