use rand::{thread_rng, Rng}; use reth_primitives::{ proofs, Address, BlockLocked, Bytes, Header, SealedHeader, Signature, Transaction, TransactionKind, TransactionSigned, TxLegacy, H256, U256, }; use secp256k1::{KeyPair, Message as SecpMessage, Secp256k1, SecretKey}; // TODO(onbjerg): Maybe we should split this off to its own crate, or move the helpers to the // relevant crates? /// Generates a range of random [SealedHeader]s. /// /// The parent hash of the first header /// in the result will be equal to `head`. /// /// The headers are assumed to not be correct if validated. pub fn random_header_range(rng: std::ops::Range, head: H256) -> Vec { let mut headers = Vec::with_capacity(rng.end.saturating_sub(rng.start) as usize); for idx in rng { headers.push(random_header( idx, Some(headers.last().map(|h: &SealedHeader| h.hash()).unwrap_or(head)), )); } headers } /// Generate a random [SealedHeader]. /// /// The header is assumed to not be correct if validated. pub fn random_header(number: u64, parent: Option) -> SealedHeader { let header = reth_primitives::Header { number, nonce: rand::random(), difficulty: U256::from(rand::random::()), parent_hash: parent.unwrap_or_default(), ..Default::default() }; header.seal() } /// Generates a random legacy [Transaction]. /// /// Every field is random, except: /// /// - The chain ID, which is always 1 /// - The input, which is always nothing pub fn random_tx() -> Transaction { Transaction::Legacy(TxLegacy { chain_id: Some(1), nonce: rand::random::().into(), gas_price: rand::random::().into(), gas_limit: rand::random::().into(), to: TransactionKind::Call(Address::random()), value: rand::random::().into(), input: Bytes::default(), }) } /// Generates a random legacy [Transaction] that is signed. /// /// On top of the considerations of [gen_random_tx], these apply as well: /// /// - There is no guarantee that the nonce is not used twice for the same account pub fn random_signed_tx() -> TransactionSigned { let secp = Secp256k1::new(); let key_pair = KeyPair::new(&secp, &mut rand::thread_rng()); let tx = random_tx(); let signature = sign_message(H256::from_slice(&key_pair.secret_bytes()[..]), tx.signature_hash()).unwrap(); TransactionSigned::from_transaction_and_signature(tx, signature) } /// Signs message with the given secret key. /// Returns the corresponding signature. pub fn sign_message(secret: H256, message: H256) -> Result { let secp = Secp256k1::new(); let sec = SecretKey::from_slice(secret.as_ref())?; let s = secp.sign_ecdsa_recoverable(&SecpMessage::from_slice(&message[..])?, &sec); let (rec_id, data) = s.serialize_compact(); Ok(Signature { r: U256::from_big_endian(&data[..32]), s: U256::from_big_endian(&data[32..64]), odd_y_parity: rec_id.to_i32() != 0, }) } /// Generate a random block filled with a random number of signed transactions (generated using /// [random_signed_tx]). /// /// All fields use the default values (and are assumed to be invalid) except for: /// /// - `parent_hash` /// - `transactions_root` /// - `ommers_hash` /// /// Additionally, `gas_used` and `gas_limit` always exactly match the total `gas_limit` of all /// transactions in the block. /// /// The ommer headers are not assumed to be valid. pub fn random_block(number: u64, parent: Option) -> BlockLocked { let mut rng = thread_rng(); // Generate transactions let transactions: Vec = (0..rand::random::()).into_iter().map(|_| random_signed_tx()).collect(); let total_gas = transactions.iter().fold(0, |sum, tx| sum + tx.transaction.gas_limit()); // Generate ommers let mut ommers = Vec::new(); for _ in 0..rng.gen_range(0..2) { ommers.push(random_header(number, parent).unseal()); } // Calculate roots let transactions_root = proofs::calculate_transaction_root(transactions.iter()); let ommers_hash = proofs::calculate_ommers_root(ommers.iter()); BlockLocked { header: Header { parent_hash: parent.unwrap_or_default(), number, gas_used: total_gas, gas_limit: total_gas, transactions_root, ommers_hash, ..Default::default() } .seal(), body: transactions, ommers: ommers.into_iter().map(|ommer| ommer.seal()).collect(), } } /// Generate a range of random blocks. /// /// The parent hash of the first block /// in the result will be equal to `head`. /// /// See [random_block] for considerations when validating the generated blocks. pub fn random_block_range(rng: std::ops::Range, head: H256) -> Vec { let mut blocks = Vec::with_capacity(rng.end.saturating_sub(rng.start) as usize); for idx in rng { blocks.push(random_block( idx, Some(blocks.last().map(|block: &BlockLocked| block.header.hash()).unwrap_or(head)), )); } blocks } #[cfg(test)] mod test { use super::*; use hex_literal::hex; use reth_primitives::{keccak256, AccessList, Address, TransactionKind, TxEip1559}; use secp256k1::KeyPair; #[test] fn test_sign_message() { let secp = Secp256k1::new(); let tx = Transaction::Eip1559(TxEip1559 { chain_id: 1, nonce: 0x42, gas_limit: 44386, to: TransactionKind::Call(hex!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into()), value: 0_u128, input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(), max_fee_per_gas: 0x4a817c800, max_priority_fee_per_gas: 0x3b9aca00, access_list: AccessList::default(), }); let signature_hash = tx.signature_hash(); for _ in 0..100 { let key_pair = KeyPair::new(&secp, &mut rand::thread_rng()); let signature = sign_message(H256::from_slice(&key_pair.secret_bytes()[..]), signature_hash) .unwrap(); let signed = TransactionSigned::from_transaction_and_signature(tx.clone(), signature); let recovered = signed.recover_signer().unwrap(); let public_key_hash = keccak256(&key_pair.public_key().serialize_uncompressed()[1..]); let expected = Address::from_slice(&public_key_hash[12..]); assert_eq!(recovered, expected); } } }