1use crate::{
2 in_memory::ExecutedBlockWithTrieUpdates, CanonStateNotification, CanonStateNotifications,
3 CanonStateSubscriptions,
4};
5use alloy_consensus::{
6 Header, SignableTransaction, Transaction as _, TxEip1559, TxReceipt, EMPTY_ROOT_HASH,
7};
8use alloy_eips::{
9 eip1559::{ETHEREUM_BLOCK_GAS_LIMIT_30M, INITIAL_BASE_FEE},
10 eip7685::Requests,
11};
12use alloy_primitives::{Address, BlockNumber, B256, U256};
13use alloy_signer::SignerSync;
14use alloy_signer_local::PrivateKeySigner;
15use core::marker::PhantomData;
16use rand::Rng;
17use reth_chainspec::{ChainSpec, EthereumHardfork, MIN_TRANSACTION_GAS};
18use reth_ethereum_primitives::{
19 Block, BlockBody, EthPrimitives, Receipt, Transaction, TransactionSigned,
20};
21use reth_execution_types::{Chain, ExecutionOutcome};
22use reth_primitives_traits::{
23 proofs::{calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root},
24 Account, NodePrimitives, Recovered, RecoveredBlock, SealedBlock, SealedHeader,
25 SignedTransaction,
26};
27use reth_storage_api::NodePrimitivesProvider;
28use reth_trie::{root::state_root_unhashed, updates::TrieUpdates, HashedPostState};
29use revm_database::BundleState;
30use revm_state::AccountInfo;
31use std::{
32 collections::HashMap,
33 ops::Range,
34 sync::{Arc, Mutex},
35};
36use tokio::sync::broadcast::{self, Sender};
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 _prims: PhantomData<N>,
55}
56
57impl<N: NodePrimitives> Default for TestBlockBuilder<N> {
58 fn default() -> Self {
59 let initial_account_info = AccountInfo::from_balance(U256::from(10).pow(U256::from(18)));
60 let signer_pk = PrivateKeySigner::random();
61 let signer = signer_pk.address();
62 Self {
63 chain_spec: ChainSpec::default(),
64 signer,
65 signer_pk,
66 signer_execute_account_info: initial_account_info.clone(),
67 signer_build_account_info: initial_account_info,
68 _prims: PhantomData,
69 }
70 }
71}
72
73impl<N: NodePrimitives> TestBlockBuilder<N> {
74 pub fn with_signer_pk(mut self, signer_pk: PrivateKeySigner) -> Self {
76 self.signer = signer_pk.address();
77 self.signer_pk = signer_pk;
78
79 self
80 }
81
82 pub fn with_chain_spec(mut self, chain_spec: ChainSpec) -> Self {
84 self.chain_spec = chain_spec;
85 self
86 }
87
88 pub fn single_tx_cost() -> U256 {
90 U256::from(INITIAL_BASE_FEE * MIN_TRANSACTION_GAS)
91 }
92
93 pub fn generate_random_block(
95 &mut self,
96 number: BlockNumber,
97 parent_hash: B256,
98 ) -> RecoveredBlock<reth_ethereum_primitives::Block> {
99 let mut rng = rand::rng();
100
101 let mock_tx = |nonce: u64| -> Recovered<_> {
102 let tx = Transaction::Eip1559(TxEip1559 {
103 chain_id: self.chain_spec.chain.id(),
104 nonce,
105 gas_limit: MIN_TRANSACTION_GAS,
106 to: Address::random().into(),
107 max_fee_per_gas: INITIAL_BASE_FEE as u128,
108 max_priority_fee_per_gas: 1,
109 ..Default::default()
110 });
111 let signature_hash = tx.signature_hash();
112 let signature = self.signer_pk.sign_hash_sync(&signature_hash).unwrap();
113
114 TransactionSigned::new_unhashed(tx, signature).with_signer(self.signer)
115 };
116
117 let num_txs = rng.random_range(0..5);
118 let signer_balance_decrease = Self::single_tx_cost() * U256::from(num_txs);
119 let transactions: Vec<Recovered<_>> = (0..num_txs)
120 .map(|_| {
121 let tx = mock_tx(self.signer_build_account_info.nonce);
122 self.signer_build_account_info.nonce += 1;
123 self.signer_build_account_info.balance -= signer_balance_decrease;
124 tx
125 })
126 .collect();
127
128 let receipts = transactions
129 .iter()
130 .enumerate()
131 .map(|(idx, tx)| {
132 Receipt {
133 tx_type: tx.tx_type(),
134 success: true,
135 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
136 ..Default::default()
137 }
138 .into_with_bloom()
139 })
140 .collect::<Vec<_>>();
141
142 let initial_signer_balance = U256::from(10).pow(U256::from(18));
143
144 let header = Header {
145 number,
146 parent_hash,
147 gas_used: transactions.len() as u64 * MIN_TRANSACTION_GAS,
148 mix_hash: B256::random(),
149 gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
150 base_fee_per_gas: Some(INITIAL_BASE_FEE),
151 transactions_root: calculate_transaction_root(
152 &transactions.clone().into_iter().map(|tx| tx.into_inner()).collect::<Vec<_>>(),
153 ),
154 receipts_root: calculate_receipt_root(&receipts),
155 beneficiary: Address::random(),
156 state_root: state_root_unhashed(HashMap::from([(
157 self.signer,
158 Account {
159 balance: initial_signer_balance - signer_balance_decrease,
160 nonce: num_txs,
161 ..Default::default()
162 }
163 .into_trie_account(EMPTY_ROOT_HASH),
164 )])),
165 timestamp: number +
167 EthereumHardfork::Cancun.activation_timestamp(self.chain_spec.chain).unwrap(),
168 withdrawals_root: Some(calculate_withdrawals_root(&[])),
169 blob_gas_used: Some(0),
170 excess_blob_gas: Some(0),
171 parent_beacon_block_root: Some(B256::random()),
172 ..Default::default()
173 };
174
175 let block = SealedBlock::from_sealed_parts(
176 SealedHeader::seal_slow(header),
177 BlockBody {
178 transactions: transactions.into_iter().map(|tx| tx.into_inner()).collect(),
179 ommers: Vec::new(),
180 withdrawals: Some(vec![].into()),
181 },
182 );
183
184 RecoveredBlock::try_recover_sealed_with_senders(block, vec![self.signer; num_txs as usize])
185 .unwrap()
186 }
187
188 pub fn create_fork(
190 &mut self,
191 base_block: &SealedBlock<Block>,
192 length: u64,
193 ) -> Vec<RecoveredBlock<Block>> {
194 let mut fork = Vec::with_capacity(length as usize);
195 let mut parent = base_block.clone();
196
197 for _ in 0..length {
198 let block = self.generate_random_block(parent.number + 1, parent.hash());
199 parent = block.clone_sealed_block();
200 fork.push(block);
201 }
202
203 fork
204 }
205
206 fn get_executed_block(
208 &mut self,
209 block_number: BlockNumber,
210 receipts: Vec<Vec<Receipt>>,
211 parent_hash: B256,
212 ) -> ExecutedBlockWithTrieUpdates {
213 let block_with_senders = self.generate_random_block(block_number, parent_hash);
214
215 let (block, senders) = block_with_senders.split_sealed();
216 ExecutedBlockWithTrieUpdates::new(
217 Arc::new(RecoveredBlock::new_sealed(block, senders)),
218 Arc::new(ExecutionOutcome::new(
219 BundleState::default(),
220 receipts,
221 block_number,
222 vec![Requests::default()],
223 )),
224 Arc::new(HashedPostState::default()),
225 Arc::new(TrieUpdates::default()),
226 )
227 }
228
229 pub fn get_executed_block_with_receipts(
231 &mut self,
232 receipts: Vec<Vec<Receipt>>,
233 parent_hash: B256,
234 ) -> ExecutedBlockWithTrieUpdates {
235 let number = rand::rng().random::<u64>();
236 self.get_executed_block(number, receipts, parent_hash)
237 }
238
239 pub fn get_executed_block_with_number(
241 &mut self,
242 block_number: BlockNumber,
243 parent_hash: B256,
244 ) -> ExecutedBlockWithTrieUpdates {
245 self.get_executed_block(block_number, vec![vec![]], parent_hash)
246 }
247
248 pub fn get_executed_blocks(
250 &mut self,
251 range: Range<u64>,
252 ) -> impl Iterator<Item = ExecutedBlockWithTrieUpdates> + '_ {
253 let mut parent_hash = B256::default();
254 range.map(move |number| {
255 let current_parent_hash = parent_hash;
256 let block = self.get_executed_block_with_number(number, current_parent_hash);
257 parent_hash = block.recovered_block().hash();
258 block
259 })
260 }
261
262 pub fn get_execution_outcome(
266 &mut self,
267 block: RecoveredBlock<reth_ethereum_primitives::Block>,
268 ) -> ExecutionOutcome {
269 let receipts = block
270 .body()
271 .transactions
272 .iter()
273 .enumerate()
274 .map(|(idx, tx)| Receipt {
275 tx_type: tx.tx_type(),
276 success: true,
277 cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS,
278 ..Default::default()
279 })
280 .collect::<Vec<_>>();
281
282 let mut bundle_state_builder = BundleState::builder(block.number..=block.number);
283
284 for tx in &block.body().transactions {
285 self.signer_execute_account_info.balance -= Self::single_tx_cost();
286 bundle_state_builder = bundle_state_builder.state_present_account_info(
287 self.signer,
288 AccountInfo {
289 nonce: tx.nonce(),
290 balance: self.signer_execute_account_info.balance,
291 ..Default::default()
292 },
293 );
294 }
295
296 let execution_outcome = ExecutionOutcome::new(
297 bundle_state_builder.build(),
298 vec![vec![]],
299 block.number,
300 Vec::new(),
301 );
302
303 execution_outcome.with_receipts(vec![receipts])
304 }
305}
306
307impl TestBlockBuilder {
308 pub fn eth() -> Self {
310 Self::default()
311 }
312}
313#[derive(Clone, Debug, Default)]
315pub struct TestCanonStateSubscriptions<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives>
316{
317 canon_notif_tx: Arc<Mutex<Vec<Sender<CanonStateNotification<N>>>>>,
318}
319
320impl TestCanonStateSubscriptions {
321 pub fn add_next_commit(&self, new: Arc<Chain>) {
324 let event = CanonStateNotification::Commit { new };
325 self.canon_notif_tx.lock().as_mut().unwrap().retain(|tx| tx.send(event.clone()).is_ok())
326 }
327
328 pub fn add_next_reorg(&self, old: Arc<Chain>, new: Arc<Chain>) {
331 let event = CanonStateNotification::Reorg { old, new };
332 self.canon_notif_tx.lock().as_mut().unwrap().retain(|tx| tx.send(event.clone()).is_ok())
333 }
334}
335
336impl NodePrimitivesProvider for TestCanonStateSubscriptions {
337 type Primitives = EthPrimitives;
338}
339
340impl CanonStateSubscriptions for TestCanonStateSubscriptions {
341 fn subscribe_to_canonical_state(&self) -> CanonStateNotifications {
343 let (canon_notif_tx, canon_notif_rx) = broadcast::channel(100);
344 self.canon_notif_tx.lock().as_mut().unwrap().push(canon_notif_tx);
345
346 canon_notif_rx
347 }
348}