reth_transaction_pool/test_utils/
tx_gen.rs1use 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#[derive(Debug)]
12pub struct TransactionGenerator<R> {
13 pub rng: R,
15 pub signer_keys: Vec<B256>,
17 pub base_fee: u128,
19 pub gas_limit: u64,
21}
22
23impl<R: RngCore> TransactionGenerator<R> {
24 pub fn new(rng: R) -> Self {
26 Self::with_num_signers(rng, 10)
27 }
28
29 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 pub fn push_signer(&mut self, signer: B256) -> &mut Self {
41 self.signer_keys.push(signer);
42 self
43 }
44
45 pub const fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self {
47 self.gas_limit = gas_limit;
48 self
49 }
50
51 pub const fn with_gas_limit(mut self, gas_limit: u64) -> Self {
53 self.gas_limit = gas_limit;
54 self
55 }
56
57 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 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 pub fn extend_signers(&mut self, signers: impl IntoIterator<Item = B256>) -> &mut Self {
71 self.signer_keys.extend(signers);
72 self
73 }
74
75 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 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 pub fn gen_eip1559(&mut self) -> TransactionSigned {
92 self.transaction().into_eip1559()
93 }
94
95 pub fn gen_eip4844(&mut self) -> TransactionSigned {
97 self.transaction().into_eip4844()
98 }
99
100 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 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#[derive(Debug)]
118pub struct TransactionBuilder {
119 pub signer: B256,
121 pub chain_id: u64,
123 pub nonce: u64,
125 pub gas_limit: u64,
127 pub max_fee_per_gas: u128,
129 pub max_priority_fee_per_gas: u128,
132 pub to: TxKind,
134 pub value: U256,
136 pub access_list: AccessList,
138 pub input: Bytes,
141}
142
143impl TransactionBuilder {
144 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 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 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 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 pub const fn signer(mut self, signer: B256) -> Self {
211 self.signer = signer;
212 self
213 }
214
215 pub const fn gas_limit(mut self, gas_limit: u64) -> Self {
217 self.gas_limit = gas_limit;
218 self
219 }
220
221 pub const fn nonce(mut self, nonce: u64) -> Self {
223 self.nonce = nonce;
224 self
225 }
226
227 pub const fn inc_nonce(mut self) -> Self {
229 self.nonce += 1;
230 self
231 }
232
233 pub const fn decr_nonce(mut self) -> Self {
235 self.nonce = self.nonce.saturating_sub(1);
236 self
237 }
238
239 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 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 pub const fn to(mut self, to: Address) -> Self {
253 self.to = TxKind::Call(to);
254 self
255 }
256
257 pub fn value(mut self, value: u128) -> Self {
259 self.value = U256::from(value);
260 self
261 }
262
263 pub fn access_list(mut self, access_list: AccessList) -> Self {
265 self.access_list = access_list;
266 self
267 }
268
269 pub fn input(mut self, input: impl Into<Bytes>) -> Self {
271 self.input = input.into();
272 self
273 }
274
275 pub const fn chain_id(mut self, chain_id: u64) -> Self {
277 self.chain_id = chain_id;
278 self
279 }
280
281 pub const fn set_chain_id(&mut self, chain_id: u64) -> &mut Self {
283 self.chain_id = chain_id;
284 self
285 }
286
287 pub const fn set_nonce(&mut self, nonce: u64) -> &mut Self {
289 self.nonce = nonce;
290 self
291 }
292
293 pub const fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self {
295 self.gas_limit = gas_limit;
296 self
297 }
298
299 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 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 pub fn set_to(&mut self, to: Address) -> &mut Self {
316 self.to = to.into();
317 self
318 }
319
320 pub fn set_value(&mut self, value: u128) -> &mut Self {
322 self.value = U256::from(value);
323 self
324 }
325
326 pub fn set_access_list(&mut self, access_list: AccessList) -> &mut Self {
328 self.access_list = access_list;
329 self
330 }
331
332 pub const fn set_signer(&mut self, signer: B256) -> &mut Self {
334 self.signer = signer;
335 self
336 }
337
338 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}