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 ..Default::default()
318 };
319
320 let block_hash = recovered.hash();
321 let executed = ExecutedBlock::new(
322 Arc::new(recovered),
323 Arc::new(BlockExecutionOutput {
324 result: BlockExecutionResult {
325 receipts: block_receipts,
326 requests: Default::default(),
327 gas_used: num_txs * MIN_TRANSACTION_GAS,
328 blob_gas_used: 0,
329 },
330 state: bundle,
331 }),
332 trie_data,
333 );
334
335 self.post_block_state.insert(block_hash, (post_info, new_slot_value));
336
337 executed
338 }
339
340 pub fn get_executed_block_with_receipts(
342 &mut self,
343 receipts: Vec<Vec<Receipt>>,
344 parent_hash: B256,
345 ) -> ExecutedBlock {
346 let number = rand::rng().random::<u64>();
347 self.get_executed_block(number, receipts, parent_hash)
348 }
349
350 pub fn get_executed_block_with_number(
352 &mut self,
353 block_number: BlockNumber,
354 parent_hash: B256,
355 ) -> ExecutedBlock {
356 self.get_executed_block(block_number, vec![vec![]], parent_hash)
357 }
358
359 pub fn get_executed_blocks(
361 &mut self,
362 range: Range<u64>,
363 ) -> impl Iterator<Item = ExecutedBlock> + '_ {
364 let mut parent_hash = B256::default();
365 range.map(move |number| {
366 let current_parent_hash = parent_hash;
367 let block = self.get_executed_block_with_number(number, current_parent_hash);
368 parent_hash = block.recovered_block().hash();
369 block
370 })
371 }
372
373 pub fn get_execution_outcome(
377 &mut self,
378 block: RecoveredBlock<reth_ethereum_primitives::Block>,
379 ) -> ExecutionOutcome {
380 let num_txs = block.body().transactions.len() as u64;
381 let single_cost = Self::single_tx_cost();
382
383 let mut final_balance = self.signer_execute_account_info.balance;
384 for _ in 0..num_txs {
385 final_balance -= single_cost;
386 }
387
388 let final_nonce = self.signer_execute_account_info.nonce + num_txs;
389
390 let receipts = block
391 .body()
392 .transactions
393 .iter()
394 .enumerate()
395 .map(|(idx, tx)| Receipt {
396 tx_type: tx.tx_type(),
397 success: true,
398 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
399 ..Default::default()
400 })
401 .collect::<Vec<_>>();
402
403 let bundle_state = BundleState::builder(block.number..=block.number)
404 .state_present_account_info(
405 self.signer,
406 AccountInfo { nonce: final_nonce, balance: final_balance, ..Default::default() },
407 )
408 .build();
409
410 self.signer_execute_account_info.balance = final_balance;
411 self.signer_execute_account_info.nonce = final_nonce;
412
413 let execution_outcome =
414 ExecutionOutcome::new(bundle_state, vec![vec![]], block.number, Vec::new());
415
416 execution_outcome.with_receipts(vec![receipts])
417 }
418}
419
420impl TestBlockBuilder {
421 pub fn eth() -> Self {
423 Self::default()
424 }
425}
426#[derive(Clone, Debug, Default)]
428pub struct TestCanonStateSubscriptions<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives>
429{
430 canon_notif_tx: Arc<Mutex<Vec<Sender<CanonStateNotification<N>>>>>,
431}
432
433impl TestCanonStateSubscriptions {
434 pub fn add_next_commit(&self, new: Arc<Chain>) {
437 let event = CanonStateNotification::Commit { new };
438 self.canon_notif_tx.lock().as_mut().unwrap().retain(|tx| tx.send(event.clone()).is_ok())
439 }
440
441 pub fn add_next_reorg(&self, old: Arc<Chain>, new: Arc<Chain>) {
444 let event = CanonStateNotification::Reorg { old, new };
445 self.canon_notif_tx.lock().as_mut().unwrap().retain(|tx| tx.send(event.clone()).is_ok())
446 }
447}
448
449impl NodePrimitivesProvider for TestCanonStateSubscriptions {
450 type Primitives = EthPrimitives;
451}
452
453impl CanonStateSubscriptions for TestCanonStateSubscriptions {
454 fn subscribe_to_canonical_state(&self) -> CanonStateNotifications {
456 let (canon_notif_tx, canon_notif_rx) = broadcast::channel(100);
457 self.canon_notif_tx.lock().as_mut().unwrap().push(canon_notif_tx);
458
459 canon_notif_rx
460 }
461}