reth_evm_ethereum/
execute.rs

1//! Ethereum block execution strategy.
2
3use crate::EthEvmConfig;
4use alloc::sync::Arc;
5use reth_chainspec::ChainSpec;
6use reth_evm::execute::BasicBlockExecutorProvider;
7
8/// Helper type with backwards compatible methods to obtain Ethereum executor
9/// providers.
10#[derive(Debug)]
11pub struct EthExecutorProvider;
12
13impl EthExecutorProvider {
14    /// Creates a new default ethereum executor provider.
15    pub fn ethereum(chain_spec: Arc<ChainSpec>) -> BasicBlockExecutorProvider<EthEvmConfig> {
16        BasicBlockExecutorProvider::new(EthEvmConfig::new(chain_spec))
17    }
18
19    /// Returns a new provider for the mainnet.
20    pub fn mainnet() -> BasicBlockExecutorProvider<EthEvmConfig> {
21        BasicBlockExecutorProvider::new(EthEvmConfig::mainnet())
22    }
23}
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28    use alloy_consensus::{constants::ETH_TO_WEI, Header, TxLegacy};
29    use alloy_eips::{
30        eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE},
31        eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS},
32        eip4895::Withdrawal,
33        eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE},
34        eip7685::EMPTY_REQUESTS_HASH,
35    };
36    use alloy_evm::block::BlockValidationError;
37    use alloy_primitives::{b256, fixed_bytes, keccak256, Bytes, TxKind, B256, U256};
38    use reth_chainspec::{ChainSpecBuilder, EthereumHardfork, ForkCondition, MAINNET};
39    use reth_ethereum_primitives::{Block, BlockBody, Transaction};
40    use reth_evm::execute::{BasicBlockExecutorProvider, BlockExecutorProvider, Executor};
41    use reth_execution_types::BlockExecutionResult;
42    use reth_primitives_traits::{
43        crypto::secp256k1::public_key_to_address, Block as _, RecoveredBlock,
44    };
45    use reth_testing_utils::generators::{self, sign_tx_with_key_pair};
46    use revm::{
47        database::{CacheDB, EmptyDB, TransitionState},
48        primitives::{address, BLOCKHASH_SERVE_WINDOW},
49        state::{AccountInfo, Bytecode, EvmState},
50        Database,
51    };
52    use secp256k1::{Keypair, Secp256k1};
53    use std::sync::mpsc;
54
55    fn create_database_with_beacon_root_contract() -> CacheDB<EmptyDB> {
56        let mut db = CacheDB::new(Default::default());
57
58        let beacon_root_contract_account = AccountInfo {
59            balance: U256::ZERO,
60            code_hash: keccak256(BEACON_ROOTS_CODE.clone()),
61            nonce: 1,
62            code: Some(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())),
63        };
64
65        db.insert_account_info(BEACON_ROOTS_ADDRESS, beacon_root_contract_account);
66
67        db
68    }
69
70    fn create_database_with_withdrawal_requests_contract() -> CacheDB<EmptyDB> {
71        let mut db = CacheDB::new(Default::default());
72
73        let withdrawal_requests_contract_account = AccountInfo {
74            nonce: 1,
75            balance: U256::ZERO,
76            code_hash: keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()),
77            code: Some(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())),
78        };
79
80        db.insert_account_info(
81            WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
82            withdrawal_requests_contract_account,
83        );
84
85        db
86    }
87
88    fn executor_provider(chain_spec: Arc<ChainSpec>) -> BasicBlockExecutorProvider<EthEvmConfig> {
89        BasicBlockExecutorProvider::new(EthEvmConfig::new(chain_spec))
90    }
91
92    #[test]
93    fn eip_4788_non_genesis_call() {
94        let mut header =
95            Header { timestamp: 1, number: 1, excess_blob_gas: Some(0), ..Header::default() };
96
97        let db = create_database_with_beacon_root_contract();
98
99        let chain_spec = Arc::new(
100            ChainSpecBuilder::from(&*MAINNET)
101                .shanghai_activated()
102                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
103                .build(),
104        );
105
106        let provider = executor_provider(chain_spec);
107
108        let mut executor = provider.executor(db);
109
110        // attempt to execute a block without parent beacon block root, expect err
111        let err = executor
112            .execute_one(&RecoveredBlock::new_unhashed(
113                Block {
114                    header: header.clone(),
115                    body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
116                },
117                vec![],
118            ))
119            .expect_err(
120                "Executing cancun block without parent beacon block root field should fail",
121            );
122
123        assert!(matches!(
124            err.as_validation().unwrap(),
125            BlockValidationError::MissingParentBeaconBlockRoot
126        ));
127
128        // fix header, set a gas limit
129        header.parent_beacon_block_root = Some(B256::with_last_byte(0x69));
130
131        // Now execute a block with the fixed header, ensure that it does not fail
132        executor
133            .execute_one(&RecoveredBlock::new_unhashed(
134                Block {
135                    header: header.clone(),
136                    body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
137                },
138                vec![],
139            ))
140            .unwrap();
141
142        // check the actual storage of the contract - it should be:
143        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be
144        // header.timestamp
145        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH
146        //   // should be parent_beacon_block_root
147        let history_buffer_length = 8191u64;
148        let timestamp_index = header.timestamp % history_buffer_length;
149        let parent_beacon_block_root_index =
150            timestamp_index % history_buffer_length + history_buffer_length;
151
152        let timestamp_storage = executor.with_state_mut(|state| {
153            state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap()
154        });
155        assert_eq!(timestamp_storage, U256::from(header.timestamp));
156
157        // get parent beacon block root storage and compare
158        let parent_beacon_block_root_storage = executor.with_state_mut(|state| {
159            state
160                .storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index))
161                .expect("storage value should exist")
162        });
163        assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
164    }
165
166    #[test]
167    fn eip_4788_no_code_cancun() {
168        // This test ensures that we "silently fail" when cancun is active and there is no code at
169        // // BEACON_ROOTS_ADDRESS
170        let header = Header {
171            timestamp: 1,
172            number: 1,
173            parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
174            excess_blob_gas: Some(0),
175            ..Header::default()
176        };
177
178        let db = CacheDB::new(EmptyDB::default());
179
180        // DON'T deploy the contract at genesis
181        let chain_spec = Arc::new(
182            ChainSpecBuilder::from(&*MAINNET)
183                .shanghai_activated()
184                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
185                .build(),
186        );
187
188        let provider = executor_provider(chain_spec);
189
190        // attempt to execute an empty block with parent beacon block root, this should not fail
191        provider
192            .executor(db)
193            .execute_one(&RecoveredBlock::new_unhashed(
194                Block {
195                    header,
196                    body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
197                },
198                vec![],
199            ))
200            .expect(
201                "Executing a block with no transactions while cancun is active should not fail",
202            );
203    }
204
205    #[test]
206    fn eip_4788_empty_account_call() {
207        // This test ensures that we do not increment the nonce of an empty SYSTEM_ADDRESS account
208        // // during the pre-block call
209
210        let mut db = create_database_with_beacon_root_contract();
211
212        // insert an empty SYSTEM_ADDRESS
213        db.insert_account_info(SYSTEM_ADDRESS, Default::default());
214
215        let chain_spec = Arc::new(
216            ChainSpecBuilder::from(&*MAINNET)
217                .shanghai_activated()
218                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
219                .build(),
220        );
221
222        let provider = executor_provider(chain_spec);
223
224        // construct the header for block one
225        let header = Header {
226            timestamp: 1,
227            number: 1,
228            parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
229            excess_blob_gas: Some(0),
230            ..Header::default()
231        };
232
233        let mut executor = provider.executor(db);
234
235        // attempt to execute an empty block with parent beacon block root, this should not fail
236        executor
237            .execute_one(&RecoveredBlock::new_unhashed(
238                Block {
239                    header,
240                    body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
241                },
242                vec![],
243            ))
244            .expect(
245                "Executing a block with no transactions while cancun is active should not fail",
246            );
247
248        // ensure that the nonce of the system address account has not changed
249        let nonce =
250            executor.with_state_mut(|state| state.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce);
251        assert_eq!(nonce, 0);
252    }
253
254    #[test]
255    fn eip_4788_genesis_call() {
256        let db = create_database_with_beacon_root_contract();
257
258        // activate cancun at genesis
259        let chain_spec = Arc::new(
260            ChainSpecBuilder::from(&*MAINNET)
261                .shanghai_activated()
262                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0))
263                .build(),
264        );
265
266        let mut header = chain_spec.genesis_header().clone();
267        let provider = executor_provider(chain_spec);
268        let mut executor = provider.executor(db);
269
270        // attempt to execute the genesis block with non-zero parent beacon block root, expect err
271        header.parent_beacon_block_root = Some(B256::with_last_byte(0x69));
272        let _err = executor
273            .execute_one(&RecoveredBlock::new_unhashed(
274                Block { header: header.clone(), body: Default::default() },
275                vec![],
276            ))
277            .expect_err(
278                "Executing genesis cancun block with non-zero parent beacon block root field
279    should fail",
280            );
281
282        // fix header
283        header.parent_beacon_block_root = Some(B256::ZERO);
284
285        // now try to process the genesis block again, this time ensuring that a system contract
286        // call does not occur
287        executor
288            .execute_one(&RecoveredBlock::new_unhashed(
289                Block { header, body: Default::default() },
290                vec![],
291            ))
292            .unwrap();
293
294        // there is no system contract call so there should be NO STORAGE CHANGES
295        // this means we'll check the transition state
296        let transition_state = executor.with_state_mut(|state| {
297            state
298                .transition_state
299                .take()
300                .expect("the evm should be initialized with bundle updates")
301        });
302
303        // assert that it is the default (empty) transition state
304        assert_eq!(transition_state, TransitionState::default());
305    }
306
307    #[test]
308    fn eip_4788_high_base_fee() {
309        // This test ensures that if we have a base fee, then we don't return an error when the
310        // system contract is called, due to the gas price being less than the base fee.
311        let header = Header {
312            timestamp: 1,
313            number: 1,
314            parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
315            base_fee_per_gas: Some(u64::MAX),
316            excess_blob_gas: Some(0),
317            ..Header::default()
318        };
319
320        let db = create_database_with_beacon_root_contract();
321
322        let chain_spec = Arc::new(
323            ChainSpecBuilder::from(&*MAINNET)
324                .shanghai_activated()
325                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
326                .build(),
327        );
328
329        let provider = executor_provider(chain_spec);
330
331        // execute header
332        let mut executor = provider.executor(db);
333
334        // Now execute a block with the fixed header, ensure that it does not fail
335        executor
336            .execute_one(&RecoveredBlock::new_unhashed(
337                Block { header: header.clone(), body: Default::default() },
338                vec![],
339            ))
340            .unwrap();
341
342        // check the actual storage of the contract - it should be:
343        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be
344        // header.timestamp
345        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH
346        //   // should be parent_beacon_block_root
347        let history_buffer_length = 8191u64;
348        let timestamp_index = header.timestamp % history_buffer_length;
349        let parent_beacon_block_root_index =
350            timestamp_index % history_buffer_length + history_buffer_length;
351
352        // get timestamp storage and compare
353        let timestamp_storage = executor.with_state_mut(|state| {
354            state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap()
355        });
356        assert_eq!(timestamp_storage, U256::from(header.timestamp));
357
358        // get parent beacon block root storage and compare
359        let parent_beacon_block_root_storage = executor.with_state_mut(|state| {
360            state.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)).unwrap()
361        });
362        assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
363    }
364
365    /// Create a state provider with blockhashes and the EIP-2935 system contract.
366    fn create_database_with_block_hashes(latest_block: u64) -> CacheDB<EmptyDB> {
367        let mut db = CacheDB::new(Default::default());
368        for block_number in 0..=latest_block {
369            db.cache
370                .block_hashes
371                .insert(U256::from(block_number), keccak256(block_number.to_string()));
372        }
373
374        let blockhashes_contract_account = AccountInfo {
375            balance: U256::ZERO,
376            code_hash: keccak256(HISTORY_STORAGE_CODE.clone()),
377            code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())),
378            nonce: 1,
379        };
380
381        db.insert_account_info(HISTORY_STORAGE_ADDRESS, blockhashes_contract_account);
382
383        db
384    }
385    #[test]
386    fn eip_2935_pre_fork() {
387        let db = create_database_with_block_hashes(1);
388
389        let chain_spec = Arc::new(
390            ChainSpecBuilder::from(&*MAINNET)
391                .shanghai_activated()
392                .with_fork(EthereumHardfork::Prague, ForkCondition::Never)
393                .build(),
394        );
395
396        let provider = executor_provider(chain_spec);
397        let mut executor = provider.executor(db);
398
399        // construct the header for block one
400        let header = Header { timestamp: 1, number: 1, ..Header::default() };
401
402        // attempt to execute an empty block, this should not fail
403        executor
404            .execute_one(&RecoveredBlock::new_unhashed(
405                Block { header, body: Default::default() },
406                vec![],
407            ))
408            .expect(
409                "Executing a block with no transactions while Prague is active should not fail",
410            );
411
412        // ensure that the block hash was *not* written to storage, since this is before the fork
413        // was activated
414        //
415        // we load the account first, because revm expects it to be
416        // loaded
417        executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap());
418        assert!(executor.with_state_mut(|state| state
419            .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
420            .unwrap()
421            .is_zero()));
422    }
423
424    #[test]
425    fn eip_2935_fork_activation_genesis() {
426        let db = create_database_with_block_hashes(0);
427
428        let chain_spec = Arc::new(
429            ChainSpecBuilder::from(&*MAINNET)
430                .shanghai_activated()
431                .cancun_activated()
432                .prague_activated()
433                .build(),
434        );
435
436        let header = chain_spec.genesis_header().clone();
437        let provider = executor_provider(chain_spec);
438        let mut executor = provider.executor(db);
439
440        // attempt to execute genesis block, this should not fail
441        executor
442            .execute_one(&RecoveredBlock::new_unhashed(
443                Block { header, body: Default::default() },
444                vec![],
445            ))
446            .expect(
447                "Executing a block with no transactions while Prague is active should not fail",
448            );
449
450        // ensure that the block hash was *not* written to storage, since there are no blocks
451        // preceding genesis
452        //
453        // we load the account first, because revm expects it to be
454        // loaded
455        executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap());
456        assert!(executor.with_state_mut(|state| state
457            .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
458            .unwrap()
459            .is_zero()));
460    }
461
462    #[test]
463    fn eip_2935_fork_activation_within_window_bounds() {
464        let fork_activation_block = (BLOCKHASH_SERVE_WINDOW - 10) as u64;
465        let db = create_database_with_block_hashes(fork_activation_block);
466
467        let chain_spec = Arc::new(
468            ChainSpecBuilder::from(&*MAINNET)
469                .shanghai_activated()
470                .cancun_activated()
471                .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1))
472                .build(),
473        );
474
475        let header = Header {
476            parent_hash: B256::random(),
477            timestamp: 1,
478            number: fork_activation_block,
479            requests_hash: Some(EMPTY_REQUESTS_HASH),
480            excess_blob_gas: Some(0),
481            parent_beacon_block_root: Some(B256::random()),
482            ..Header::default()
483        };
484        let provider = executor_provider(chain_spec);
485        let mut executor = provider.executor(db);
486
487        // attempt to execute the fork activation block, this should not fail
488        executor
489            .execute_one(&RecoveredBlock::new_unhashed(
490                Block { header, body: Default::default() },
491                vec![],
492            ))
493            .expect(
494                "Executing a block with no transactions while Prague is active should not fail",
495            );
496
497        // the hash for the ancestor of the fork activation block should be present
498        assert!(executor
499            .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
500        assert_ne!(
501            executor.with_state_mut(|state| state
502                .storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block - 1))
503                .unwrap()),
504            U256::ZERO
505        );
506
507        // the hash of the block itself should not be in storage
508        assert!(executor.with_state_mut(|state| state
509            .storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block))
510            .unwrap()
511            .is_zero()));
512    }
513
514    // <https://github.com/ethereum/EIPs/pull/9144>
515    #[test]
516    fn eip_2935_fork_activation_outside_window_bounds() {
517        let fork_activation_block = (BLOCKHASH_SERVE_WINDOW + 256) as u64;
518        let db = create_database_with_block_hashes(fork_activation_block);
519
520        let chain_spec = Arc::new(
521            ChainSpecBuilder::from(&*MAINNET)
522                .shanghai_activated()
523                .cancun_activated()
524                .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1))
525                .build(),
526        );
527
528        let provider = executor_provider(chain_spec);
529        let mut executor = provider.executor(db);
530
531        let header = Header {
532            parent_hash: B256::random(),
533            timestamp: 1,
534            number: fork_activation_block,
535            requests_hash: Some(EMPTY_REQUESTS_HASH),
536            excess_blob_gas: Some(0),
537            parent_beacon_block_root: Some(B256::random()),
538            ..Header::default()
539        };
540
541        // attempt to execute the fork activation block, this should not fail
542        executor
543            .execute_one(&RecoveredBlock::new_unhashed(
544                Block { header, body: Default::default() },
545                vec![],
546            ))
547            .expect(
548                "Executing a block with no transactions while Prague is active should not fail",
549            );
550
551        // the hash for the ancestor of the fork activation block should be present
552        assert!(executor
553            .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
554    }
555
556    #[test]
557    fn eip_2935_state_transition_inside_fork() {
558        let db = create_database_with_block_hashes(2);
559
560        let chain_spec = Arc::new(
561            ChainSpecBuilder::from(&*MAINNET)
562                .shanghai_activated()
563                .cancun_activated()
564                .prague_activated()
565                .build(),
566        );
567
568        let header = chain_spec.genesis_header().clone();
569        let header_hash = header.hash_slow();
570
571        let provider = executor_provider(chain_spec);
572        let mut executor = provider.executor(db);
573
574        // attempt to execute the genesis block, this should not fail
575        executor
576            .execute_one(&RecoveredBlock::new_unhashed(
577                Block { header, body: Default::default() },
578                vec![],
579            ))
580            .expect(
581                "Executing a block with no transactions while Prague is active should not fail",
582            );
583
584        // nothing should be written as the genesis has no ancestors
585        //
586        // we load the account first, because revm expects it to be
587        // loaded
588        executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap());
589        assert!(executor.with_state_mut(|state| state
590            .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
591            .unwrap()
592            .is_zero()));
593
594        // attempt to execute block 1, this should not fail
595        let header = Header {
596            parent_hash: header_hash,
597            timestamp: 1,
598            number: 1,
599            requests_hash: Some(EMPTY_REQUESTS_HASH),
600            excess_blob_gas: Some(0),
601            parent_beacon_block_root: Some(B256::random()),
602            ..Header::default()
603        };
604        let header_hash = header.hash_slow();
605
606        executor
607            .execute_one(&RecoveredBlock::new_unhashed(
608                Block { header, body: Default::default() },
609                vec![],
610            ))
611            .expect(
612                "Executing a block with no transactions while Prague is active should not fail",
613            );
614
615        // the block hash of genesis should now be in storage, but not block 1
616        assert!(executor
617            .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
618        assert_ne!(
619            executor.with_state_mut(|state| state
620                .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
621                .unwrap()),
622            U256::ZERO
623        );
624        assert!(executor.with_state_mut(|state| state
625            .storage(HISTORY_STORAGE_ADDRESS, U256::from(1))
626            .unwrap()
627            .is_zero()));
628
629        // attempt to execute block 2, this should not fail
630        let header = Header {
631            parent_hash: header_hash,
632            timestamp: 1,
633            number: 2,
634            requests_hash: Some(EMPTY_REQUESTS_HASH),
635            excess_blob_gas: Some(0),
636            parent_beacon_block_root: Some(B256::random()),
637            ..Header::default()
638        };
639
640        executor
641            .execute_one(&RecoveredBlock::new_unhashed(
642                Block { header, body: Default::default() },
643                vec![],
644            ))
645            .expect(
646                "Executing a block with no transactions while Prague is active should not fail",
647            );
648
649        // the block hash of genesis and block 1 should now be in storage, but not block 2
650        assert!(executor
651            .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
652        assert_ne!(
653            executor.with_state_mut(|state| state
654                .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
655                .unwrap()),
656            U256::ZERO
657        );
658        assert_ne!(
659            executor.with_state_mut(|state| state
660                .storage(HISTORY_STORAGE_ADDRESS, U256::from(1))
661                .unwrap()),
662            U256::ZERO
663        );
664        assert!(executor.with_state_mut(|state| state
665            .storage(HISTORY_STORAGE_ADDRESS, U256::from(2))
666            .unwrap()
667            .is_zero()));
668    }
669
670    #[test]
671    fn eip_7002() {
672        let chain_spec = Arc::new(
673            ChainSpecBuilder::from(&*MAINNET)
674                .shanghai_activated()
675                .cancun_activated()
676                .prague_activated()
677                .build(),
678        );
679
680        let mut db = create_database_with_withdrawal_requests_contract();
681
682        let secp = Secp256k1::new();
683        let sender_key_pair = Keypair::new(&secp, &mut generators::rng());
684        let sender_address = public_key_to_address(sender_key_pair.public_key());
685
686        db.insert_account_info(
687            sender_address,
688            AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() },
689        );
690
691        // https://github.com/lightclient/sys-asm/blob/9282bdb9fd64e024e27f60f507486ffb2183cba2/test/Withdrawal.t.sol.in#L36
692        let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
693        let withdrawal_amount = fixed_bytes!("0203040506070809");
694        let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
695        assert_eq!(input.len(), 56);
696
697        let mut header = chain_spec.genesis_header().clone();
698        header.gas_limit = 1_500_000;
699        // measured
700        header.gas_used = 135_856;
701        header.receipts_root =
702            b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3");
703
704        let tx = sign_tx_with_key_pair(
705            sender_key_pair,
706            Transaction::Legacy(TxLegacy {
707                chain_id: Some(chain_spec.chain.id()),
708                nonce: 1,
709                gas_price: header.base_fee_per_gas.unwrap().into(),
710                gas_limit: header.gas_used,
711                to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
712                // `MIN_WITHDRAWAL_REQUEST_FEE`
713                value: U256::from(2),
714                input,
715            }),
716        );
717
718        let provider = executor_provider(chain_spec);
719
720        let mut executor = provider.executor(db);
721
722        let BlockExecutionResult { receipts, requests, .. } = executor
723            .execute_one(
724                &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } }
725                    .try_into_recovered()
726                    .unwrap(),
727            )
728            .unwrap();
729
730        let receipt = receipts.first().unwrap();
731        assert!(receipt.success);
732
733        // There should be exactly one entry with withdrawal requests
734        assert_eq!(requests.len(), 1);
735        assert_eq!(requests[0][0], 1);
736    }
737
738    #[test]
739    fn block_gas_limit_error() {
740        // Create a chain specification with fork conditions set for Prague
741        let chain_spec = Arc::new(
742            ChainSpecBuilder::from(&*MAINNET)
743                .shanghai_activated()
744                .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0))
745                .build(),
746        );
747
748        // Create a state provider with the withdrawal requests contract pre-deployed
749        let mut db = create_database_with_withdrawal_requests_contract();
750
751        // Initialize Secp256k1 for key pair generation
752        let secp = Secp256k1::new();
753        // Generate a new key pair for the sender
754        let sender_key_pair = Keypair::new(&secp, &mut generators::rng());
755        // Get the sender's address from the public key
756        let sender_address = public_key_to_address(sender_key_pair.public_key());
757
758        // Insert the sender account into the state with a nonce of 1 and a balance of 1 ETH in Wei
759        db.insert_account_info(
760            sender_address,
761            AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() },
762        );
763
764        // Define the validator public key and withdrawal amount as fixed bytes
765        let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
766        let withdrawal_amount = fixed_bytes!("2222222222222222");
767        // Concatenate the validator public key and withdrawal amount into a single byte array
768        let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
769        // Ensure the input length is 56 bytes
770        assert_eq!(input.len(), 56);
771
772        // Create a genesis block header with a specified gas limit and gas used
773        let mut header = chain_spec.genesis_header().clone();
774        header.gas_limit = 1_500_000;
775        header.gas_used = 134_807;
776        header.receipts_root =
777            b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3");
778
779        // Create a transaction with a gas limit higher than the block gas limit
780        let tx = sign_tx_with_key_pair(
781            sender_key_pair,
782            Transaction::Legacy(TxLegacy {
783                chain_id: Some(chain_spec.chain.id()),
784                nonce: 1,
785                gas_price: header.base_fee_per_gas.unwrap().into(),
786                gas_limit: 2_500_000, // higher than block gas limit
787                to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
788                value: U256::from(1),
789                input,
790            }),
791        );
792
793        // Create an executor from the state provider
794        let mut executor = executor_provider(chain_spec).executor(db);
795
796        // Execute the block and capture the result
797        let exec_result = executor.execute_one(
798            &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } }
799                .try_into_recovered()
800                .unwrap(),
801        );
802
803        // Check if the execution result is an error and assert the specific error type
804        match exec_result {
805            Ok(_) => panic!("Expected block gas limit error"),
806            Err(err) => assert!(matches!(
807                *err.as_validation().unwrap(),
808                BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
809                    transaction_gas_limit: 2_500_000,
810                    block_available_gas: 1_500_000,
811                }
812            )),
813        }
814    }
815
816    #[test]
817    fn test_balance_increment_not_duplicated() {
818        let chain_spec = Arc::new(
819            ChainSpecBuilder::from(&*MAINNET)
820                .shanghai_activated()
821                .cancun_activated()
822                .prague_activated()
823                .build(),
824        );
825
826        let withdrawal_recipient = address!("0x1000000000000000000000000000000000000000");
827
828        let mut db = CacheDB::new(EmptyDB::default());
829        let initial_balance = 100;
830        db.insert_account_info(
831            withdrawal_recipient,
832            AccountInfo { balance: U256::from(initial_balance), nonce: 1, ..Default::default() },
833        );
834
835        let withdrawal =
836            Withdrawal { index: 0, validator_index: 0, address: withdrawal_recipient, amount: 1 };
837
838        let header = Header {
839            timestamp: 1,
840            number: 1,
841            excess_blob_gas: Some(0),
842            parent_beacon_block_root: Some(B256::random()),
843            ..Header::default()
844        };
845
846        let block = &RecoveredBlock::new_unhashed(
847            Block {
848                header,
849                body: BlockBody {
850                    transactions: vec![],
851                    ommers: vec![],
852                    withdrawals: Some(vec![withdrawal].into()),
853                },
854            },
855            vec![],
856        );
857
858        let provider = executor_provider(chain_spec);
859        let executor = provider.executor(db);
860
861        let (tx, rx) = mpsc::channel();
862        let tx_clone = tx.clone();
863
864        let _output = executor
865            .execute_with_state_hook(block, move |_, state: &EvmState| {
866                if let Some(account) = state.get(&withdrawal_recipient) {
867                    let _ = tx_clone.send(account.info.balance);
868                }
869            })
870            .expect("Block execution should succeed");
871
872        drop(tx);
873        let balance_changes: Vec<U256> = rx.try_iter().collect();
874
875        if let Some(final_balance) = balance_changes.last() {
876            let expected_final_balance = U256::from(initial_balance) + U256::from(1_000_000_000); // initial + 1 Gwei in Wei
877            assert_eq!(
878                *final_balance, expected_final_balance,
879                "Final balance should match expected value after withdrawal"
880            );
881        }
882    }
883}