reth_evm_ethereum/
execute.rs

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