1use crate::{
2 in_memory::ExecutedBlock, CanonStateNotification, CanonStateNotifications,
3 CanonStateSubscriptions, ComputedTrieData,
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;
24use revm_database::BundleState;
25use revm_state::AccountInfo;
26use std::{
27 ops::Range,
28 sync::{Arc, Mutex},
29};
30use tokio::sync::broadcast::{self, Sender};
31
32const TEST_STORAGE_ADDRESS: Address = Address::new([0xAA; 20]);
34
35const TEST_STORAGE_SLOT: U256 = U256::from_limbs([1, 0, 0, 0]);
37
38#[derive(Debug)]
41pub struct TestBlockBuilder<N: NodePrimitives = EthPrimitives> {
42 pub signer: Address,
44 pub signer_pk: PrivateKeySigner,
46 pub signer_execute_account_info: AccountInfo,
49 pub signer_build_account_info: AccountInfo,
52 pub chain_spec: ChainSpec,
54 pub post_block_state: B256HashMap<(AccountInfo, U256)>,
57 pub with_state: bool,
60 _prims: PhantomData<N>,
61}
62
63impl<N: NodePrimitives> Default for TestBlockBuilder<N> {
64 fn default() -> Self {
65 let initial_account_info = AccountInfo::from_balance(U256::from(10).pow(U256::from(18)));
66 let signer_pk = PrivateKeySigner::random();
67 let signer = signer_pk.address();
68 Self {
69 chain_spec: ChainSpec::default(),
70 signer,
71 signer_pk,
72 signer_execute_account_info: initial_account_info.clone(),
73 signer_build_account_info: initial_account_info,
74 post_block_state: B256HashMap::default(),
75 with_state: false,
76 _prims: PhantomData,
77 }
78 }
79}
80
81impl<N: NodePrimitives> TestBlockBuilder<N> {
82 pub fn with_signer_pk(mut self, signer_pk: PrivateKeySigner) -> Self {
84 self.signer = signer_pk.address();
85 self.signer_pk = signer_pk;
86
87 self
88 }
89
90 pub fn with_chain_spec(mut self, chain_spec: ChainSpec) -> Self {
92 self.chain_spec = chain_spec;
93 self
94 }
95
96 pub const fn with_state(mut self) -> Self {
99 self.with_state = true;
100 self
101 }
102
103 pub fn single_tx_cost() -> U256 {
105 U256::from(INITIAL_BASE_FEE * MIN_TRANSACTION_GAS)
106 }
107
108 pub fn generate_random_block(
110 &mut self,
111 number: BlockNumber,
112 parent_hash: B256,
113 ) -> SealedBlock<reth_ethereum_primitives::Block> {
114 let mut rng = rand::rng();
115
116 let mock_tx = |nonce: u64| -> Recovered<_> {
117 let tx = Transaction::Eip1559(TxEip1559 {
118 chain_id: self.chain_spec.chain.id(),
119 nonce,
120 gas_limit: MIN_TRANSACTION_GAS,
121 to: Address::random().into(),
122 max_fee_per_gas: INITIAL_BASE_FEE as u128,
123 max_priority_fee_per_gas: 1,
124 ..Default::default()
125 });
126 let signature_hash = tx.signature_hash();
127 let signature = self.signer_pk.sign_hash_sync(&signature_hash).unwrap();
128
129 TransactionSigned::new_unhashed(tx, signature).with_signer(self.signer)
130 };
131
132 let num_txs = rng.random_range(0..5);
133 let signer_balance_decrease = Self::single_tx_cost() * U256::from(num_txs);
134 let transactions: Vec<Recovered<_>> = (0..num_txs)
135 .map(|_| {
136 let tx = mock_tx(self.signer_build_account_info.nonce);
137 self.signer_build_account_info.nonce += 1;
138 self.signer_build_account_info.balance -= Self::single_tx_cost();
139 tx
140 })
141 .collect();
142
143 let receipts = transactions
144 .iter()
145 .enumerate()
146 .map(|(idx, tx)| {
147 Receipt {
148 tx_type: tx.tx_type(),
149 success: true,
150 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
151 ..Default::default()
152 }
153 .into_with_bloom()
154 })
155 .collect::<Vec<_>>();
156
157 let initial_signer_balance = U256::from(10).pow(U256::from(18));
158
159 let header = Header {
160 number,
161 parent_hash,
162 gas_used: transactions.len() as u64 * MIN_TRANSACTION_GAS,
163 mix_hash: B256::random(),
164 gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
165 base_fee_per_gas: Some(INITIAL_BASE_FEE),
166 transactions_root: calculate_transaction_root(&transactions),
167 receipts_root: calculate_receipt_root(&receipts),
168 beneficiary: Address::random(),
169 state_root: state_root_unhashed([(
170 self.signer,
171 Account {
172 balance: initial_signer_balance - signer_balance_decrease,
173 nonce: num_txs,
174 ..Default::default()
175 }
176 .into_trie_account(EMPTY_ROOT_HASH),
177 )]),
178 timestamp: number +
180 EthereumHardfork::Cancun.activation_timestamp(self.chain_spec.chain).unwrap(),
181 withdrawals_root: Some(calculate_withdrawals_root(&[])),
182 blob_gas_used: Some(0),
183 excess_blob_gas: Some(0),
184 parent_beacon_block_root: Some(B256::random()),
185 ..Default::default()
186 };
187
188 SealedBlock::from_sealed_parts(
189 SealedHeader::seal_slow(header),
190 BlockBody {
191 transactions: transactions.into_iter().map(|tx| tx.into_inner()).collect(),
192 ommers: Vec::new(),
193 withdrawals: Some(vec![].into()),
194 },
195 )
196 }
197
198 pub fn create_fork(
200 &mut self,
201 base_block: &SealedBlock<Block>,
202 length: u64,
203 ) -> Vec<RecoveredBlock<Block>> {
204 let mut fork = Vec::with_capacity(length as usize);
205 let mut parent = base_block.clone();
206
207 for _ in 0..length {
208 let block = self.generate_random_block(parent.number + 1, parent.hash());
209 parent = block.clone();
210 let senders = vec![self.signer; block.body().transactions.len()];
211 let block = block.with_senders(senders);
212 fork.push(block);
213 }
214
215 fork
216 }
217
218 fn get_executed_block(
224 &mut self,
225 block_number: BlockNumber,
226 mut receipts: Vec<Vec<Receipt>>,
227 parent_hash: B256,
228 ) -> ExecutedBlock {
229 let block = self.generate_random_block(block_number, parent_hash);
230 let senders = vec![self.signer; block.body().transactions.len()];
231 let recovered = RecoveredBlock::new_sealed(block, senders);
232
233 if !self.with_state {
234 let executed = ExecutedBlock::new(
235 Arc::new(recovered),
236 Arc::new(BlockExecutionOutput {
237 result: BlockExecutionResult {
238 receipts: receipts.pop().unwrap_or_default(),
239 requests: Default::default(),
240 gas_used: 0,
241 blob_gas_used: 0,
242 },
243 state: BundleState::default(),
244 }),
245 ComputedTrieData::default(),
246 );
247 return executed;
248 }
249
250 let initial_info = AccountInfo::from_balance(U256::from(10).pow(U256::from(18)));
251 let num_txs = recovered.body().transactions.len() as u64;
252 let single_cost = Self::single_tx_cost();
253
254 let (pre_info, old_slot_value) = self
256 .post_block_state
257 .get(&parent_hash)
258 .cloned()
259 .unwrap_or_else(|| (initial_info.clone(), U256::ZERO));
260
261 let mut final_balance = pre_info.balance;
262 for _ in 0..num_txs {
263 final_balance -= single_cost;
264 }
265 let final_nonce = pre_info.nonce + num_txs;
266 let post_info =
267 AccountInfo { nonce: final_nonce, balance: final_balance, ..Default::default() };
268
269 let account_revert = if pre_info.balance == initial_info.balance && pre_info.nonce == 0 {
270 Some(None)
271 } else {
272 Some(Some(pre_info))
273 };
274
275 let new_slot_value = U256::from(block_number).wrapping_add(U256::from(1));
276
277 let bundle = BundleState::builder(block_number..=block_number)
278 .state_present_account_info(self.signer, post_info.clone())
279 .revert_account_info(block_number, self.signer, account_revert)
280 .state_storage(
281 TEST_STORAGE_ADDRESS,
282 alloy_primitives::map::HashMap::from_iter([(
283 TEST_STORAGE_SLOT,
284 (old_slot_value, new_slot_value),
285 )]),
286 )
287 .revert_storage(
288 block_number,
289 TEST_STORAGE_ADDRESS,
290 vec![(TEST_STORAGE_SLOT, old_slot_value)],
291 )
292 .build();
293
294 let hashed_state = reth_trie::HashedPostState::from_bundle_state::<
295 reth_trie::KeccakKeyHasher,
296 >(bundle.state.iter())
297 .into_sorted();
298
299 let block_receipts = if receipts.is_empty() {
300 recovered
301 .body()
302 .transactions
303 .iter()
304 .enumerate()
305 .map(|(idx, tx)| Receipt {
306 tx_type: tx.tx_type(),
307 success: true,
308 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
309 ..Default::default()
310 })
311 .collect()
312 } else {
313 receipts.into_iter().flatten().collect()
314 };
315
316 let trie_data =
317 ComputedTrieData { hashed_state: Arc::new(hashed_state), ..Default::default() };
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}