reth_rpc/
testing.rs

1//! Implementation of the `testing` namespace.
2//!
3//! This exposes `testing_buildBlockV1`, intended for non-production/debug use.
4
5use 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/// Testing API handler.
25#[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    /// Create a new testing API handler.
33    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    /// Handles `testing_buildBlockV1` by gating concurrency via a semaphore and offloading heavy
120    /// work to the blocking pool to avoid stalling the async runtime.
121    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}