reth_transaction_pool/test_utils/
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;
6use reth_chainspec::MAINNET;
7use reth_ethereum_primitives::{Transaction, TransactionSigned};
8use reth_primitives_traits::transaction::signed::SignedTransaction;
9
10use reth_primitives_traits::crypto::secp256k1::sign_message;
11
12#[derive(Debug)]
14pub struct TransactionGenerator<R> {
15 pub rng: R,
17 pub signer_keys: Vec<B256>,
19 pub base_fee: u128,
21 pub gas_limit: u64,
23}
24
25impl<R: Rng> TransactionGenerator<R> {
26 pub fn new(rng: R) -> Self {
28 Self::with_num_signers(rng, 10)
29 }
30
31 pub fn with_num_signers(rng: R, num_signers: usize) -> Self {
33 Self {
34 rng,
35 signer_keys: (0..num_signers).map(|_| B256::random()).collect(),
36 base_fee: MIN_PROTOCOL_BASE_FEE as u128,
37 gas_limit: 300_000,
38 }
39 }
40
41 pub fn push_signer(&mut self, signer: B256) -> &mut Self {
43 self.signer_keys.push(signer);
44 self
45 }
46
47 pub fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self {
49 self.gas_limit = gas_limit;
50 self
51 }
52
53 pub const fn with_gas_limit(mut self, gas_limit: u64) -> Self {
55 self.gas_limit = gas_limit;
56 self
57 }
58
59 pub fn set_base_fee(&mut self, base_fee: u64) -> &mut Self {
61 self.base_fee = base_fee as u128;
62 self
63 }
64
65 pub const fn with_base_fee(mut self, base_fee: u64) -> Self {
67 self.base_fee = base_fee as u128;
68 self
69 }
70
71 pub fn extend_signers(&mut self, signers: impl IntoIterator<Item = B256>) -> &mut Self {
73 self.signer_keys.extend(signers);
74 self
75 }
76
77 fn rng_signer(&mut self) -> B256 {
79 let idx = self.rng.gen_range(0..self.signer_keys.len());
80 self.signer_keys[idx]
81 }
82
83 pub fn transaction(&mut self) -> TransactionBuilder {
85 TransactionBuilder::default()
86 .signer(self.rng_signer())
87 .max_fee_per_gas(self.base_fee)
88 .max_priority_fee_per_gas(self.base_fee)
89 .gas_limit(self.gas_limit)
90 }
91
92 pub fn gen_eip1559(&mut self) -> TransactionSigned {
94 self.transaction().into_eip1559()
95 }
96
97 pub fn gen_eip4844(&mut self) -> TransactionSigned {
99 self.transaction().into_eip4844()
100 }
101
102 pub fn gen_eip1559_pooled(&mut self) -> EthPooledTransaction {
104 EthPooledTransaction::try_from_consensus(self.gen_eip1559().try_into_recovered().unwrap())
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 fn set_chain_id(&mut self, chain_id: u64) -> &mut Self {
283 self.chain_id = chain_id;
284 self
285 }
286
287 pub fn set_nonce(&mut self, nonce: u64) -> &mut Self {
289 self.nonce = nonce;
290 self
291 }
292
293 pub fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self {
295 self.gas_limit = gas_limit;
296 self
297 }
298
299 pub 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 fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) -> &mut Self {
307 self.max_priority_fee_per_gas = max_priority_fee_per_gas;
308 self
309 }
310
311 pub fn set_to(&mut self, to: Address) -> &mut Self {
313 self.to = to.into();
314 self
315 }
316
317 pub fn set_value(&mut self, value: u128) -> &mut Self {
319 self.value = U256::from(value);
320 self
321 }
322
323 pub fn set_access_list(&mut self, access_list: AccessList) -> &mut Self {
325 self.access_list = access_list;
326 self
327 }
328
329 pub fn set_signer(&mut self, signer: B256) -> &mut Self {
331 self.signer = signer;
332 self
333 }
334
335 pub fn set_input(&mut self, input: impl Into<Bytes>) -> &mut Self {
337 self.input = input.into();
338 self
339 }
340}
341
342impl Default for TransactionBuilder {
343 fn default() -> Self {
344 Self {
345 signer: B256::random(),
346 chain_id: MAINNET.chain.id(),
347 nonce: 0,
348 gas_limit: 0,
349 max_fee_per_gas: 0,
350 max_priority_fee_per_gas: 0,
351 to: Default::default(),
352 value: Default::default(),
353 access_list: Default::default(),
354 input: Default::default(),
355 }
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use rand::thread_rng;
363
364 #[test]
365 fn test_generate_transaction() {
366 let rng = thread_rng();
367 let mut gen = TransactionGenerator::new(rng);
368 let _tx = gen.transaction().into_legacy();
369 let _tx = gen.transaction().into_eip1559();
370 }
371}