reth_testing_utils/
generators.rs

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