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