reth_transaction_pool/test_utils/
tx_gen.rs

1use crate::{EthPooledTransaction, PoolTransaction};
2use alloy_consensus::{SignableTransaction, TxEip1559, TxEip4844, TxLegacy};
3use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718, eip2930::AccessList};
4use alloy_primitives::{Address, Bytes, TxKind, B256, U256};
5use rand::{Rng, RngCore};
6use reth_chainspec::MAINNET;
7use reth_ethereum_primitives::{Transaction, TransactionSigned};
8use reth_primitives_traits::{crypto::secp256k1::sign_message, SignedTransaction};
9
10/// A generator for transactions for testing purposes.
11#[derive(Debug)]
12pub struct TransactionGenerator<R> {
13    /// The random number generator used for generating keys and selecting signers.
14    pub rng: R,
15    /// The set of signer keys available for transaction generation.
16    pub signer_keys: Vec<B256>,
17    /// The base fee for transactions.
18    pub base_fee: u128,
19    /// The gas limit for transactions.
20    pub gas_limit: u64,
21}
22
23impl<R: RngCore> TransactionGenerator<R> {
24    /// Initializes the generator with 10 random signers
25    pub fn new(rng: R) -> Self {
26        Self::with_num_signers(rng, 10)
27    }
28
29    /// Generates random signers
30    pub fn with_num_signers(rng: R, num_signers: usize) -> Self {
31        Self {
32            rng,
33            signer_keys: (0..num_signers).map(|_| B256::random()).collect(),
34            base_fee: MIN_PROTOCOL_BASE_FEE as u128,
35            gas_limit: 300_000,
36        }
37    }
38
39    /// Adds a new signer to the set
40    pub fn push_signer(&mut self, signer: B256) -> &mut Self {
41        self.signer_keys.push(signer);
42        self
43    }
44
45    /// Sets the default gas limit for all generated transactions
46    pub const fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self {
47        self.gas_limit = gas_limit;
48        self
49    }
50
51    /// Sets the default gas limit for all generated transactions
52    pub const fn with_gas_limit(mut self, gas_limit: u64) -> Self {
53        self.gas_limit = gas_limit;
54        self
55    }
56
57    /// Sets the base fee for the generated transactions
58    pub const fn set_base_fee(&mut self, base_fee: u64) -> &mut Self {
59        self.base_fee = base_fee as u128;
60        self
61    }
62
63    /// Sets the base fee for the generated transactions
64    pub const fn with_base_fee(mut self, base_fee: u64) -> Self {
65        self.base_fee = base_fee as u128;
66        self
67    }
68
69    /// Adds the given signers to the set.
70    pub fn extend_signers(&mut self, signers: impl IntoIterator<Item = B256>) -> &mut Self {
71        self.signer_keys.extend(signers);
72        self
73    }
74
75    /// Returns a random signer from the set
76    fn rng_signer(&mut self) -> B256 {
77        let idx = self.rng.random_range(0..self.signer_keys.len());
78        self.signer_keys[idx]
79    }
80
81    /// Creates a new transaction with a random signer
82    pub fn transaction(&mut self) -> TransactionBuilder {
83        TransactionBuilder::default()
84            .signer(self.rng_signer())
85            .max_fee_per_gas(self.base_fee)
86            .max_priority_fee_per_gas(self.base_fee)
87            .gas_limit(self.gas_limit)
88    }
89
90    /// Creates a new transaction with a random signer
91    pub fn gen_eip1559(&mut self) -> TransactionSigned {
92        self.transaction().into_eip1559()
93    }
94
95    /// Creates a new transaction with a random signer
96    pub fn gen_eip4844(&mut self) -> TransactionSigned {
97        self.transaction().into_eip4844()
98    }
99
100    /// Generates and returns a pooled EIP-1559 transaction with a random signer.
101    pub fn gen_eip1559_pooled(&mut self) -> EthPooledTransaction {
102        EthPooledTransaction::try_from_consensus(
103            SignedTransaction::try_into_recovered(self.gen_eip1559()).unwrap(),
104        )
105        .unwrap()
106    }
107
108    /// Generates and returns a pooled EIP-4844 transaction with a random signer.
109    pub fn gen_eip4844_pooled(&mut self) -> EthPooledTransaction {
110        let tx = self.gen_eip4844().try_into_recovered().unwrap();
111        let encoded_length = tx.encode_2718_len();
112        EthPooledTransaction::new(tx, encoded_length)
113    }
114}
115
116/// A Builder type to configure and create a transaction.
117#[derive(Debug)]
118pub struct TransactionBuilder {
119    /// The signer used to sign the transaction.
120    pub signer: B256,
121    /// The chain ID on which the transaction will be executed.
122    pub chain_id: u64,
123    /// The nonce value for the transaction to prevent replay attacks.
124    pub nonce: u64,
125    /// The maximum amount of gas units that the transaction can consume.
126    pub gas_limit: u64,
127    /// The maximum fee per gas unit that the sender is willing to pay.
128    pub max_fee_per_gas: u128,
129    /// The maximum priority fee per gas unit that the sender is willing to pay for faster
130    /// processing.
131    pub max_priority_fee_per_gas: u128,
132    /// The recipient or contract address of the transaction.
133    pub to: TxKind,
134    /// The value to be transferred in the transaction.
135    pub value: U256,
136    /// The list of addresses and storage keys that the transaction can access.
137    pub access_list: AccessList,
138    /// The input data for the transaction, typically containing function parameters for contract
139    /// calls.
140    pub input: Bytes,
141}
142
143impl TransactionBuilder {
144    /// Converts the transaction builder into a legacy transaction format.
145    pub fn into_legacy(self) -> TransactionSigned {
146        Self::signed(
147            TxLegacy {
148                chain_id: Some(self.chain_id),
149                nonce: self.nonce,
150                gas_limit: self.gas_limit,
151                gas_price: self.max_fee_per_gas,
152                to: self.to,
153                value: self.value,
154                input: self.input,
155            }
156            .into(),
157            self.signer,
158        )
159    }
160
161    /// Converts the transaction builder into a transaction format using EIP-1559.
162    pub fn into_eip1559(self) -> TransactionSigned {
163        Self::signed(
164            TxEip1559 {
165                chain_id: self.chain_id,
166                nonce: self.nonce,
167                gas_limit: self.gas_limit,
168                max_fee_per_gas: self.max_fee_per_gas,
169                max_priority_fee_per_gas: self.max_priority_fee_per_gas,
170                to: self.to,
171                value: self.value,
172                access_list: self.access_list,
173                input: self.input,
174            }
175            .into(),
176            self.signer,
177        )
178    }
179    /// Converts the transaction builder into a transaction format using EIP-4844.
180    pub fn into_eip4844(self) -> TransactionSigned {
181        Self::signed(
182            TxEip4844 {
183                chain_id: self.chain_id,
184                nonce: self.nonce,
185                gas_limit: self.gas_limit,
186                max_fee_per_gas: self.max_fee_per_gas,
187                max_priority_fee_per_gas: self.max_priority_fee_per_gas,
188                to: match self.to {
189                    TxKind::Call(to) => to,
190                    TxKind::Create => Address::default(),
191                },
192                value: self.value,
193                access_list: self.access_list,
194                input: self.input,
195                blob_versioned_hashes: Default::default(),
196                max_fee_per_blob_gas: Default::default(),
197            }
198            .into(),
199            self.signer,
200        )
201    }
202
203    /// Signs the provided transaction using the specified signer and returns a signed transaction.
204    fn signed(transaction: Transaction, signer: B256) -> TransactionSigned {
205        let signature = sign_message(signer, transaction.signature_hash()).unwrap();
206        TransactionSigned::new_unhashed(transaction, signature)
207    }
208
209    /// Sets the signer for the transaction builder.
210    pub const fn signer(mut self, signer: B256) -> Self {
211        self.signer = signer;
212        self
213    }
214
215    /// Sets the gas limit for the transaction builder.
216    pub const fn gas_limit(mut self, gas_limit: u64) -> Self {
217        self.gas_limit = gas_limit;
218        self
219    }
220
221    /// Sets the nonce for the transaction builder.
222    pub const fn nonce(mut self, nonce: u64) -> Self {
223        self.nonce = nonce;
224        self
225    }
226
227    /// Increments the nonce value of the transaction builder by 1.
228    pub const fn inc_nonce(mut self) -> Self {
229        self.nonce += 1;
230        self
231    }
232
233    /// Decrements the nonce value of the transaction builder by 1, avoiding underflow.
234    pub const fn decr_nonce(mut self) -> Self {
235        self.nonce = self.nonce.saturating_sub(1);
236        self
237    }
238
239    /// Sets the maximum fee per gas for the transaction builder.
240    pub const fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
241        self.max_fee_per_gas = max_fee_per_gas;
242        self
243    }
244
245    /// Sets the maximum priority fee per gas for the transaction builder.
246    pub const fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
247        self.max_priority_fee_per_gas = max_priority_fee_per_gas;
248        self
249    }
250
251    /// Sets the recipient or contract address for the transaction builder.
252    pub const fn to(mut self, to: Address) -> Self {
253        self.to = TxKind::Call(to);
254        self
255    }
256
257    /// Sets the value to be transferred in the transaction.
258    pub fn value(mut self, value: u128) -> Self {
259        self.value = U256::from(value);
260        self
261    }
262
263    /// Sets the access list for the transaction builder.
264    pub fn access_list(mut self, access_list: AccessList) -> Self {
265        self.access_list = access_list;
266        self
267    }
268
269    /// Sets the transaction input data.
270    pub fn input(mut self, input: impl Into<Bytes>) -> Self {
271        self.input = input.into();
272        self
273    }
274
275    /// Sets the chain ID for the transaction.
276    pub const fn chain_id(mut self, chain_id: u64) -> Self {
277        self.chain_id = chain_id;
278        self
279    }
280
281    /// Sets the chain ID for the transaction, mutable reference version.
282    pub const fn set_chain_id(&mut self, chain_id: u64) -> &mut Self {
283        self.chain_id = chain_id;
284        self
285    }
286
287    /// Sets the nonce for the transaction, mutable reference version.
288    pub const fn set_nonce(&mut self, nonce: u64) -> &mut Self {
289        self.nonce = nonce;
290        self
291    }
292
293    /// Sets the gas limit for the transaction, mutable reference version.
294    pub const fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self {
295        self.gas_limit = gas_limit;
296        self
297    }
298
299    /// Sets the maximum fee per gas for the transaction, mutable reference version.
300    pub const fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) -> &mut Self {
301        self.max_fee_per_gas = max_fee_per_gas;
302        self
303    }
304
305    /// Sets the maximum priority fee per gas for the transaction, mutable reference version.
306    pub const fn set_max_priority_fee_per_gas(
307        &mut self,
308        max_priority_fee_per_gas: u128,
309    ) -> &mut Self {
310        self.max_priority_fee_per_gas = max_priority_fee_per_gas;
311        self
312    }
313
314    /// Sets the recipient or contract address for the transaction, mutable reference version.
315    pub fn set_to(&mut self, to: Address) -> &mut Self {
316        self.to = to.into();
317        self
318    }
319
320    /// Sets the value to be transferred in the transaction, mutable reference version.
321    pub fn set_value(&mut self, value: u128) -> &mut Self {
322        self.value = U256::from(value);
323        self
324    }
325
326    /// Sets the access list for the transaction, mutable reference version.
327    pub fn set_access_list(&mut self, access_list: AccessList) -> &mut Self {
328        self.access_list = access_list;
329        self
330    }
331
332    /// Sets the signer for the transaction, mutable reference version.
333    pub const fn set_signer(&mut self, signer: B256) -> &mut Self {
334        self.signer = signer;
335        self
336    }
337
338    /// Sets the transaction input data, mutable reference version.
339    pub fn set_input(&mut self, input: impl Into<Bytes>) -> &mut Self {
340        self.input = input.into();
341        self
342    }
343}
344
345impl Default for TransactionBuilder {
346    fn default() -> Self {
347        Self {
348            signer: B256::random(),
349            chain_id: MAINNET.chain.id(),
350            nonce: 0,
351            gas_limit: 0,
352            max_fee_per_gas: 0,
353            max_priority_fee_per_gas: 0,
354            to: Default::default(),
355            value: Default::default(),
356            access_list: Default::default(),
357            input: Default::default(),
358        }
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365    use rand::rng;
366
367    #[test]
368    fn test_generate_transaction() {
369        let rng = rng();
370        let mut tx_gen = TransactionGenerator::new(rng);
371        let _tx = tx_gen.transaction().into_legacy();
372        let _tx = tx_gen.transaction().into_eip1559();
373    }
374}