1use alloy_consensus::{Header, Transaction};
6use alloy_evm::Evm;
7use alloy_primitives::U256;
8use alloy_rpc_types_engine::ExecutionPayloadEnvelopeV5;
9use async_trait::async_trait;
10use jsonrpsee::core::RpcResult;
11use reth_errors::RethError;
12use reth_ethereum_engine_primitives::EthBuiltPayload;
13use reth_ethereum_primitives::EthPrimitives;
14use reth_evm::{execute::BlockBuilder, ConfigureEvm, NextBlockEnvAttributes};
15use reth_primitives_traits::{AlloyBlockHeader as BlockTrait, Recovered, TxTy};
16use reth_revm::{database::StateProviderDatabase, db::State};
17use reth_rpc_api::{TestingApiServer, TestingBuildBlockRequestV1};
18use reth_rpc_eth_api::{helpers::Call, FromEthApiError};
19use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError};
20use reth_storage_api::{BlockReader, HeaderProvider};
21use revm::context::Block;
22use std::sync::Arc;
23
24#[derive(Debug, Clone)]
26pub struct TestingApi<Eth, Evm> {
27 eth_api: Eth,
28 evm_config: Evm,
29}
30
31impl<Eth, Evm> TestingApi<Eth, Evm> {
32 pub const fn new(eth_api: Eth, evm_config: Evm) -> Self {
34 Self { eth_api, evm_config }
35 }
36}
37
38impl<Eth, Evm> TestingApi<Eth, Evm>
39where
40 Eth: Call<Provider: BlockReader<Header = Header>>,
41 Evm: ConfigureEvm<NextBlockEnvCtx = NextBlockEnvAttributes, Primitives = EthPrimitives>
42 + 'static,
43{
44 async fn build_block_v1(
45 &self,
46 request: TestingBuildBlockRequestV1,
47 ) -> Result<ExecutionPayloadEnvelopeV5, Eth::Error> {
48 let evm_config = self.evm_config.clone();
49 self.eth_api
50 .spawn_with_state_at_block(request.parent_block_hash, move |eth_api, state| {
51 let state = state.database.0;
52 let mut db = State::builder()
53 .with_bundle_update()
54 .with_database(StateProviderDatabase::new(&state))
55 .build();
56 let parent = eth_api
57 .provider()
58 .sealed_header_by_hash(request.parent_block_hash)?
59 .ok_or_else(|| {
60 EthApiError::HeaderNotFound(request.parent_block_hash.into())
61 })?;
62
63 let env_attrs = NextBlockEnvAttributes {
64 timestamp: request.payload_attributes.timestamp,
65 suggested_fee_recipient: request.payload_attributes.suggested_fee_recipient,
66 prev_randao: request.payload_attributes.prev_randao,
67 gas_limit: parent.gas_limit(),
68 parent_beacon_block_root: request.payload_attributes.parent_beacon_block_root,
69 withdrawals: request.payload_attributes.withdrawals.map(Into::into),
70 extra_data: request.extra_data.unwrap_or_default(),
71 };
72
73 let mut builder = evm_config
74 .builder_for_next_block(&mut db, &parent, env_attrs)
75 .map_err(RethError::other)
76 .map_err(Eth::Error::from_eth_err)?;
77 builder.apply_pre_execution_changes().map_err(Eth::Error::from_eth_err)?;
78
79 let mut total_fees = U256::ZERO;
80 let base_fee = builder.evm_mut().block().basefee();
81
82 for tx in request.transactions {
83 let tx: Recovered<TxTy<Evm::Primitives>> = recover_raw_transaction(&tx)?;
84 let tip = tx.effective_tip_per_gas(base_fee).unwrap_or_default();
85 let gas_used =
86 builder.execute_transaction(tx).map_err(Eth::Error::from_eth_err)?;
87
88 total_fees += U256::from(tip) * U256::from(gas_used);
89 }
90 let outcome = builder.finish(&state).map_err(Eth::Error::from_eth_err)?;
91
92 let requests = outcome
93 .block
94 .requests_hash()
95 .is_some()
96 .then_some(outcome.execution_result.requests);
97
98 EthBuiltPayload::new(
99 alloy_rpc_types_engine::PayloadId::default(),
100 Arc::new(outcome.block.into_sealed_block()),
101 total_fees,
102 requests,
103 )
104 .try_into_v5()
105 .map_err(RethError::other)
106 .map_err(Eth::Error::from_eth_err)
107 })
108 .await
109 }
110}
111
112#[async_trait]
113impl<Eth, Evm> TestingApiServer for TestingApi<Eth, Evm>
114where
115 Eth: Call<Provider: BlockReader<Header = Header>>,
116 Evm: ConfigureEvm<NextBlockEnvCtx = NextBlockEnvAttributes, Primitives = EthPrimitives>
117 + 'static,
118{
119 async fn build_block_v1(
122 &self,
123 request: TestingBuildBlockRequestV1,
124 ) -> RpcResult<ExecutionPayloadEnvelopeV5> {
125 self.build_block_v1(request).await.map_err(Into::into)
126 }
127}