1use crate::{
2 in_memory::ExecutedBlock, CanonStateNotification, CanonStateNotifications,
3 CanonStateSubscriptions,
4};
5use alloy_consensus::{Header, SignableTransaction, TxEip1559, TxReceipt, EMPTY_ROOT_HASH};
6use alloy_eips::eip1559::{ETHEREUM_BLOCK_GAS_LIMIT_30M, INITIAL_BASE_FEE};
7use alloy_primitives::{map::B256HashMap, Address, BlockNumber, B256, U256};
8use alloy_signer::SignerSync;
9use alloy_signer_local::PrivateKeySigner;
10use core::marker::PhantomData;
11use rand::Rng;
12use reth_chainspec::{ChainSpec, EthereumHardfork, MIN_TRANSACTION_GAS};
13use reth_ethereum_primitives::{
14 Block, BlockBody, EthPrimitives, Receipt, Transaction, TransactionSigned,
15};
16use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult, Chain, ExecutionOutcome};
17use reth_primitives_traits::{
18 proofs::{calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root},
19 Account, NodePrimitives, Recovered, RecoveredBlock, SealedBlock, SealedHeader,
20 SignedTransaction,
21};
22use reth_storage_api::NodePrimitivesProvider;
23use reth_trie::{root::state_root_unhashed, ComputedTrieData, SortedTrieData};
24use revm::{database::BundleState, state::AccountInfo};
25use std::{
26 ops::Range,
27 sync::{Arc, Mutex},
28};
29use tokio::sync::broadcast::{self, Sender};
30
31const TEST_STORAGE_ADDRESS: Address = Address::new([0xAA; 20]);
33
34const TEST_STORAGE_SLOT: U256 = U256::from_limbs([1, 0, 0, 0]);
36
37#[derive(Debug)]
40pub struct TestBlockBuilder<N: NodePrimitives = EthPrimitives> {
41 pub signer: Address,
43 pub signer_pk: PrivateKeySigner,
45 pub signer_execute_account_info: AccountInfo,
48 pub signer_build_account_info: AccountInfo,
51 pub chain_spec: ChainSpec,
53 pub post_block_state: B256HashMap<(AccountInfo, U256)>,
56 pub with_state: bool,
59 _prims: PhantomData<N>,
60}
61
62impl<N: NodePrimitives> Default for TestBlockBuilder<N> {
63 fn default() -> Self {
64 let initial_account_info = AccountInfo::from_balance(U256::from(10).pow(U256::from(18)));
65 let signer_pk = PrivateKeySigner::random();
66 let signer = signer_pk.address();
67 Self {
68 chain_spec: ChainSpec::default(),
69 signer,
70 signer_pk,
71 signer_execute_account_info: initial_account_info.clone(),
72 signer_build_account_info: initial_account_info,
73 post_block_state: B256HashMap::default(),
74 with_state: false,
75 _prims: PhantomData,
76 }
77 }
78}
79
80impl<N: NodePrimitives> TestBlockBuilder<N> {
81 pub fn with_signer_pk(mut self, signer_pk: PrivateKeySigner) -> Self {
83 self.signer = signer_pk.address();
84 self.signer_pk = signer_pk;
85
86 self
87 }
88
89 pub fn with_chain_spec(mut self, chain_spec: ChainSpec) -> Self {
91 self.chain_spec = chain_spec;
92 self
93 }
94
95 pub const fn with_state(mut self) -> Self {
98 self.with_state = true;
99 self
100 }
101
102 pub fn single_tx_cost() -> U256 {
104 U256::from(INITIAL_BASE_FEE * MIN_TRANSACTION_GAS)
105 }
106
107 pub fn generate_random_block(
109 &mut self,
110 number: BlockNumber,
111 parent_hash: B256,
112 ) -> SealedBlock<reth_ethereum_primitives::Block> {
113 let mut rng = rand::rng();
114
115 let mock_tx = |nonce: u64| -> Recovered<_> {
116 let tx = Transaction::Eip1559(TxEip1559 {
117 chain_id: self.chain_spec.chain.id(),
118 nonce,
119 gas_limit: MIN_TRANSACTION_GAS,
120 to: Address::random().into(),
121 max_fee_per_gas: INITIAL_BASE_FEE as u128,
122 max_priority_fee_per_gas: 1,
123 ..Default::default()
124 });
125 let signature_hash = tx.signature_hash();
126 let signature = self.signer_pk.sign_hash_sync(&signature_hash).unwrap();
127
128 TransactionSigned::new_unhashed(tx, signature).with_signer(self.signer)
129 };
130
131 let num_txs = rng.random_range(0..5);
132 let signer_balance_decrease = Self::single_tx_cost() * U256::from(num_txs);
133 let transactions: Vec<Recovered<_>> = (0..num_txs)
134 .map(|_| {
135 let tx = mock_tx(self.signer_build_account_info.nonce);
136 self.signer_build_account_info.nonce += 1;
137 self.signer_build_account_info.balance -= Self::single_tx_cost();
138 tx
139 })
140 .collect();
141
142 let receipts = transactions
143 .iter()
144 .enumerate()
145 .map(|(idx, tx)| {
146 Receipt {
147 tx_type: tx.tx_type(),
148 success: true,
149 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
150 ..Default::default()
151 }
152 .into_with_bloom()
153 })
154 .collect::<Vec<_>>();
155
156 let initial_signer_balance = U256::from(10).pow(U256::from(18));
157
158 let header = Header {
159 number,
160 parent_hash,
161 gas_used: transactions.len() as u64 * MIN_TRANSACTION_GAS,
162 mix_hash: B256::random(),
163 gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
164 base_fee_per_gas: Some(INITIAL_BASE_FEE),
165 transactions_root: calculate_transaction_root(&transactions),
166 receipts_root: calculate_receipt_root(&receipts),
167 beneficiary: Address::random(),
168 state_root: state_root_unhashed([(
169 self.signer,
170 Account {
171 balance: initial_signer_balance - signer_balance_decrease,
172 nonce: num_txs,
173 ..Default::default()
174 }
175 .into_trie_account(EMPTY_ROOT_HASH),
176 )]),
177 timestamp: number +
179 EthereumHardfork::Cancun.activation_timestamp(self.chain_spec.chain).unwrap(),
180 withdrawals_root: Some(calculate_withdrawals_root(&[])),
181 blob_gas_used: Some(0),
182 excess_blob_gas: Some(0),
183 parent_beacon_block_root: Some(B256::random()),
184 ..Default::default()
185 };
186
187 SealedBlock::from_sealed_parts(
188 SealedHeader::seal_slow(header),
189 BlockBody {
190 transactions: transactions.into_iter().map(|tx| tx.into_inner()).collect(),
191 ommers: Vec::new(),
192 withdrawals: Some(vec![].into()),
193 },
194 )
195 }
196
197 pub fn create_fork(
199 &mut self,
200 base_block: &SealedBlock<Block>,
201 length: u64,
202 ) -> Vec<RecoveredBlock<Block>> {
203 let mut fork = Vec::with_capacity(length as usize);
204 let mut parent = base_block.clone();
205
206 for _ in 0..length {
207 let block = self.generate_random_block(parent.number + 1, parent.hash());
208 parent = block.clone();
209 let senders = vec![self.signer; block.body().transactions.len()];
210 let block = block.with_senders(senders);
211 fork.push(block);
212 }
213
214 fork
215 }
216
217 fn get_executed_block(
223 &mut self,
224 block_number: BlockNumber,
225 mut receipts: Vec<Vec<Receipt>>,
226 parent_hash: B256,
227 ) -> ExecutedBlock {
228 let block = self.generate_random_block(block_number, parent_hash);
229 let senders = vec![self.signer; block.body().transactions.len()];
230 let recovered = RecoveredBlock::new_sealed(block, senders);
231
232 if !self.with_state {
233 let executed = ExecutedBlock::new(
234 Arc::new(recovered),
235 Arc::new(BlockExecutionOutput {
236 result: BlockExecutionResult {
237 receipts: receipts.pop().unwrap_or_default(),
238 requests: Default::default(),
239 gas_used: 0,
240 blob_gas_used: 0,
241 },
242 state: BundleState::default(),
243 }),
244 ComputedTrieData::default(),
245 );
246 return executed;
247 }
248
249 let initial_info = AccountInfo::from_balance(U256::from(10).pow(U256::from(18)));
250 let num_txs = recovered.body().transactions.len() as u64;
251 let single_cost = Self::single_tx_cost();
252
253 let (pre_info, old_slot_value) = self
255 .post_block_state
256 .get(&parent_hash)
257 .cloned()
258 .unwrap_or_else(|| (initial_info.clone(), U256::ZERO));
259
260 let mut final_balance = pre_info.balance;
261 for _ in 0..num_txs {
262 final_balance -= single_cost;
263 }
264 let final_nonce = pre_info.nonce + num_txs;
265 let post_info =
266 AccountInfo { nonce: final_nonce, balance: final_balance, ..Default::default() };
267
268 let account_revert = if pre_info.balance == initial_info.balance && pre_info.nonce == 0 {
269 Some(None)
270 } else {
271 Some(Some(pre_info))
272 };
273
274 let new_slot_value = U256::from(block_number).wrapping_add(U256::from(1));
275
276 let bundle = BundleState::builder(block_number..=block_number)
277 .state_present_account_info(self.signer, post_info.clone())
278 .revert_account_info(block_number, self.signer, account_revert)
279 .state_storage(
280 TEST_STORAGE_ADDRESS,
281 alloy_primitives::map::HashMap::from_iter([(
282 TEST_STORAGE_SLOT,
283 (old_slot_value, new_slot_value),
284 )]),
285 )
286 .revert_storage(
287 block_number,
288 TEST_STORAGE_ADDRESS,
289 vec![(TEST_STORAGE_SLOT, old_slot_value)],
290 )
291 .build();
292
293 let hashed_state = reth_trie::HashedPostState::from_bundle_state::<
294 reth_trie::KeccakKeyHasher,
295 >(bundle.state.iter())
296 .into_sorted();
297
298 let block_receipts = if receipts.is_empty() {
299 recovered
300 .body()
301 .transactions
302 .iter()
303 .enumerate()
304 .map(|(idx, tx)| Receipt {
305 tx_type: tx.tx_type(),
306 success: true,
307 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
308 ..Default::default()
309 })
310 .collect()
311 } else {
312 receipts.into_iter().flatten().collect()
313 };
314
315 let trie_data = ComputedTrieData {
316 sorted: SortedTrieData { hashed_state: Arc::new(hashed_state), ..Default::default() },
317 };
318
319 let block_hash = recovered.hash();
320 let executed = ExecutedBlock::new(
321 Arc::new(recovered),
322 Arc::new(BlockExecutionOutput {
323 result: BlockExecutionResult {
324 receipts: block_receipts,
325 requests: Default::default(),
326 gas_used: num_txs * MIN_TRANSACTION_GAS,
327 blob_gas_used: 0,
328 },
329 state: bundle,
330 }),
331 trie_data,
332 );
333
334 self.post_block_state.insert(block_hash, (post_info, new_slot_value));
335
336 executed
337 }
338
339 pub fn get_executed_block_with_receipts(
341 &mut self,
342 receipts: Vec<Vec<Receipt>>,
343 parent_hash: B256,
344 ) -> ExecutedBlock {
345 let number = rand::rng().random::<u64>();
346 self.get_executed_block(number, receipts, parent_hash)
347 }
348
349 pub fn get_executed_block_with_number(
351 &mut self,
352 block_number: BlockNumber,
353 parent_hash: B256,
354 ) -> ExecutedBlock {
355 self.get_executed_block(block_number, vec![vec![]], parent_hash)
356 }
357
358 pub fn get_executed_blocks(
360 &mut self,
361 range: Range<u64>,
362 ) -> impl Iterator<Item = ExecutedBlock> + '_ {
363 let mut parent_hash = B256::default();
364 range.map(move |number| {
365 let current_parent_hash = parent_hash;
366 let block = self.get_executed_block_with_number(number, current_parent_hash);
367 parent_hash = block.recovered_block().hash();
368 block
369 })
370 }
371
372 pub fn get_execution_outcome(
376 &mut self,
377 block: RecoveredBlock<reth_ethereum_primitives::Block>,
378 ) -> ExecutionOutcome {
379 let num_txs = block.body().transactions.len() as u64;
380 let single_cost = Self::single_tx_cost();
381
382 let mut final_balance = self.signer_execute_account_info.balance;
383 for _ in 0..num_txs {
384 final_balance -= single_cost;
385 }
386
387 let final_nonce = self.signer_execute_account_info.nonce + num_txs;
388
389 let receipts = block
390 .body()
391 .transactions
392 .iter()
393 .enumerate()
394 .map(|(idx, tx)| Receipt {
395 tx_type: tx.tx_type(),
396 success: true,
397 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
398 ..Default::default()
399 })
400 .collect::<Vec<_>>();
401
402 let bundle_state = BundleState::builder(block.number..=block.number)
403 .state_present_account_info(
404 self.signer,
405 AccountInfo { nonce: final_nonce, balance: final_balance, ..Default::default() },
406 )
407 .build();
408
409 self.signer_execute_account_info.balance = final_balance;
410 self.signer_execute_account_info.nonce = final_nonce;
411
412 let execution_outcome =
413 ExecutionOutcome::new(bundle_state, vec![vec![]], block.number, Vec::new());
414
415 execution_outcome.with_receipts(vec![receipts])
416 }
417}
418
419impl TestBlockBuilder {
420 pub fn eth() -> Self {
422 Self::default()
423 }
424}
425#[derive(Clone, Debug, Default)]
427pub struct TestCanonStateSubscriptions<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives>
428{
429 canon_notif_tx: Arc<Mutex<Vec<Sender<CanonStateNotification<N>>>>>,
430}
431
432impl TestCanonStateSubscriptions {
433 pub fn add_next_commit(&self, new: Arc<Chain>) {
436 let event = CanonStateNotification::Commit { new };
437 self.canon_notif_tx.lock().as_mut().unwrap().retain(|tx| tx.send(event.clone()).is_ok())
438 }
439
440 pub fn add_next_reorg(&self, old: Arc<Chain>, new: Arc<Chain>) {
443 let event = CanonStateNotification::Reorg { old, new };
444 self.canon_notif_tx.lock().as_mut().unwrap().retain(|tx| tx.send(event.clone()).is_ok())
445 }
446}
447
448impl NodePrimitivesProvider for TestCanonStateSubscriptions {
449 type Primitives = EthPrimitives;
450}
451
452impl CanonStateSubscriptions for TestCanonStateSubscriptions {
453 fn subscribe_to_canonical_state(&self) -> CanonStateNotifications {
455 let (canon_notif_tx, canon_notif_rx) = broadcast::channel(100);
456 self.canon_notif_tx.lock().as_mut().unwrap().push(canon_notif_tx);
457
458 canon_notif_rx
459 }
460}