1//! Loads a pending block from database. Helper trait for `eth_` block, transaction, call and trace
2//! RPC methods.
34use super::SpawnBlocking;
5use crate::{types::RpcTypes, EthApiTypes, FromEthApiError, FromEvmError, RpcNodeCore};
6use alloy_consensus::{BlockHeader, Transaction};
7use alloy_eips::eip4844::MAX_DATA_GAS_PER_BLOCK;
8use alloy_rpc_types_eth::BlockNumberOrTag;
9use futures::Future;
10use reth_chainspec::{EthChainSpec, EthereumHardforks};
11use reth_errors::{BlockExecutionError, BlockValidationError, RethError};
12use reth_evm::{
13 execute::{BlockBuilder, BlockBuilderOutcome},
14 ConfigureEvm, Evm, SpecFor,
15};
16use reth_node_api::NodePrimitives;
17use reth_primitives_traits::{
18 transaction::error::InvalidTransactionError, Receipt, RecoveredBlock, SealedHeader,
19};
20use reth_provider::{
21 BlockReader, BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderError, ProviderHeader,
22 ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory,
23};
24use reth_revm::{database::StateProviderDatabase, db::State};
25use reth_rpc_eth_types::{EthApiError, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin};
26use reth_transaction_pool::{
27 error::InvalidPoolTransactionError, BestTransactionsAttributes, PoolTransaction,
28 TransactionPool,
29};
30use revm::context_interface::Block;
31use std::time::{Duration, Instant};
32use tokio::sync::Mutex;
33use tracing::debug;
3435/// Loads a pending block from database.
36///
37/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
38pub trait LoadPendingBlock:
39EthApiTypes<
40 NetworkTypes: RpcTypes<
41 Header = alloy_rpc_types_eth::Header<ProviderHeader<Self::Provider>>,
42 >,
43 Error: FromEvmError<Self::Evm>,
44 > + RpcNodeCore<
45 Provider: BlockReaderIdExt<Receipt: Receipt>
46 + ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
47 + StateProviderFactory,
48 Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
49 Evm: ConfigureEvm<
50 Primitives: NodePrimitives<
51 BlockHeader = ProviderHeader<Self::Provider>,
52 SignedTx = ProviderTx<Self::Provider>,
53 Receipt = ProviderReceipt<Self::Provider>,
54 Block = ProviderBlock<Self::Provider>,
55 >,
56 >,
57 >
58{
59/// Returns a handle to the pending block.
60 ///
61 /// Data access in default (L1) trait method implementations.
62#[expect(clippy::type_complexity)]
63fn pending_block(
64&self,
65 ) -> &Mutex<Option<PendingBlock<ProviderBlock<Self::Provider>, ProviderReceipt<Self::Provider>>>>;
6667/// Configures the [`PendingBlockEnv`] for the pending block
68 ///
69 /// If no pending block is available, this will derive it from the `latest` block
70#[expect(clippy::type_complexity)]
71fn pending_block_env_and_cfg(
72&self,
73 ) -> Result<
74 PendingBlockEnv<
75 ProviderBlock<Self::Provider>,
76 ProviderReceipt<Self::Provider>,
77 SpecFor<Self::Evm>,
78 >,
79Self::Error,
80 > {
81if let Some(block) =
82self.provider().pending_block_with_senders().map_err(Self::Error::from_eth_err)?
83{
84if let Some(receipts) = self85.provider()
86 .receipts_by_block(block.hash().into())
87 .map_err(Self::Error::from_eth_err)?
88{
89// Note: for the PENDING block we assume it is past the known merge block and
90 // thus this will not fail when looking up the total
91 // difficulty value for the blockenv.
92let evm_env = self.evm_config().evm_env(block.header());
9394return Ok(PendingBlockEnv::new(
95evm_env,
96 PendingBlockEnvOrigin::ActualPending(block, receipts),
97 ));
98 }
99 }
100101// no pending block from the CL yet, so we use the latest block and modify the env
102 // values that we can
103let latest = self104.provider()
105 .latest_header()
106 .map_err(Self::Error::from_eth_err)?
107.ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?;
108109let evm_env = self110.evm_config()
111 .next_evm_env(&latest, &self.next_env_attributes(&latest)?)
112 .map_err(RethError::other)
113 .map_err(Self::Error::from_eth_err)?;
114115Ok(PendingBlockEnv::new(evm_env, PendingBlockEnvOrigin::DerivedFromLatest(latest)))
116 }
117118/// Returns [`ConfigureEvm::NextBlockEnvCtx`] for building a local pending block.
119fn next_env_attributes(
120&self,
121 parent: &SealedHeader<ProviderHeader<Self::Provider>>,
122 ) -> Result<<Self::Evm as ConfigureEvm>::NextBlockEnvCtx, Self::Error>;
123124/// Returns the locally built pending block
125#[expect(clippy::type_complexity)]
126fn local_pending_block(
127&self,
128 ) -> impl Future<
129 Output = Result<
130Option<(
131 RecoveredBlock<<Self::Provider as BlockReader>::Block>,
132Vec<ProviderReceipt<Self::Provider>>,
133 )>,
134Self::Error,
135 >,
136 > + Send137where
138Self: SpawnBlocking,
139 {
140async move {
141let pending = self.pending_block_env_and_cfg()?;
142let parent = match pending.origin {
143 PendingBlockEnvOrigin::ActualPending(block, receipts) => {
144return Ok(Some((block, receipts)));
145 }
146 PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent,
147 };
148149// we couldn't find the real pending block, so we need to build it ourselves
150let mut lock = self.pending_block().lock().await;
151152let now = Instant::now();
153154// check if the block is still good
155if let Some(pending_block) = lock.as_ref() {
156// this is guaranteed to be the `latest` header
157if pending.evm_env.block_env.number == pending_block.block.number() &&
158 parent.hash() == pending_block.block.parent_hash() &&
159 now <= pending_block.expires_at
160 {
161return Ok(Some((pending_block.block.clone(), pending_block.receipts.clone())));
162 }
163 }
164165// no pending block from the CL yet, so we need to build it ourselves via txpool
166let (sealed_block, receipts) = match self167.spawn_blocking_io(move |this| {
168// we rebuild the block
169this.build_block(&parent)
170 })
171 .await
172{
173Ok(block) => block,
174Err(err) => {
175debug!(target: "rpc", "Failed to build pending block: {:?}", err);
176return Ok(None)
177 }
178 };
179180let now = Instant::now();
181*lock = Some(PendingBlock::new(
182now + Duration::from_secs(1),
183sealed_block.clone(),
184receipts.clone(),
185 ));
186187Ok(Some((sealed_block, receipts)))
188 }
189 }
190191/// Builds a pending block using the configured provider and pool.
192 ///
193 /// If the origin is the actual pending block, the block is built with withdrawals.
194 ///
195 /// After Cancun, if the origin is the actual pending block, the block includes the EIP-4788 pre
196 /// block contract call using the parent beacon block root received from the CL.
197#[expect(clippy::type_complexity)]
198fn build_block(
199&self,
200 parent: &SealedHeader<ProviderHeader<Self::Provider>>,
201 ) -> Result<
202 (RecoveredBlock<ProviderBlock<Self::Provider>>, Vec<ProviderReceipt<Self::Provider>>),
203Self::Error,
204 >
205where
206EthApiError: From<ProviderError>,
207 {
208let state_provider = self209.provider()
210 .history_by_block_hash(parent.hash())
211 .map_err(Self::Error::from_eth_err)?;
212let state = StateProviderDatabase::new(&state_provider);
213let mut db = State::builder().with_database(state).with_bundle_update().build();
214215let mut builder = self216.evm_config()
217 .builder_for_next_block(&mut db, parent, self.next_env_attributes(parent)?)
218 .map_err(RethError::other)
219 .map_err(Self::Error::from_eth_err)?;
220221builder.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
222223let block_env = builder.evm_mut().block().clone();
224225let mut cumulative_gas_used = 0;
226let mut sum_blob_gas_used = 0;
227let block_gas_limit: u64 = block_env.gas_limit;
228229let mut best_txs =
230self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new(
231block_env.basefee,
232block_env.blob_gasprice().map(|gasprice| gaspriceas u64),
233 ));
234235while let Some(pool_tx) = best_txs.next() {
236// ensure we still have capacity for this transaction
237if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
238// we can't fit this transaction into the block, so we need to mark it as invalid
239 // which also removes all dependent transaction from the iterator before we can
240 // continue
241best_txs.mark_invalid(
242&pool_tx,
243 InvalidPoolTransactionError::ExceedsGasLimit(
244 pool_tx.gas_limit(),
245 block_gas_limit,
246 ),
247 );
248continue
249}
250251if pool_tx.origin.is_private() {
252// we don't want to leak any state changes made by private transactions, so we mark
253 // them as invalid here which removes all dependent transactions from the iterator
254 // before we can continue
255best_txs.mark_invalid(
256&pool_tx,
257 InvalidPoolTransactionError::Consensus(
258 InvalidTransactionError::TxTypeNotSupported,
259 ),
260 );
261continue
262}
263264// convert tx to a signed transaction
265let tx = pool_tx.to_consensus();
266267// There's only limited amount of blob space available per block, so we need to check if
268 // the EIP-4844 can still fit in the block
269if let Some(tx_blob_gas) = tx.blob_gas_used() {
270if sum_blob_gas_used + tx_blob_gas > MAX_DATA_GAS_PER_BLOCK {
271// we can't fit this _blob_ transaction into the block, so we mark it as
272 // invalid, which removes its dependent transactions from
273 // the iterator. This is similar to the gas limit condition
274 // for regular transactions above.
275best_txs.mark_invalid(
276&pool_tx,
277 InvalidPoolTransactionError::ExceedsGasLimit(
278 tx_blob_gas,
279 MAX_DATA_GAS_PER_BLOCK,
280 ),
281 );
282continue
283}
284 }
285286let gas_used = match builder.execute_transaction(tx.clone()) {
287Ok(gas_used) => gas_used,
288Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
289 error,
290 ..
291 })) => {
292if error.is_nonce_too_low() {
293// if the nonce is too low, we can skip this transaction
294} else {
295// if the transaction is invalid, we can skip it and all of its
296 // descendants
297best_txs.mark_invalid(
298&pool_tx,
299 InvalidPoolTransactionError::Consensus(
300 InvalidTransactionError::TxTypeNotSupported,
301 ),
302 );
303 }
304continue
305}
306// this is an error that we should treat as fatal for this attempt
307Err(err) => return Err(Self::Error::from_eth_err(err)),
308 };
309310// add to the total blob gas used if the transaction successfully executed
311if let Some(tx_blob_gas) = tx.blob_gas_used() {
312 sum_blob_gas_used += tx_blob_gas;
313314// if we've reached the max data gas per block, we can skip blob txs entirely
315if sum_blob_gas_used == MAX_DATA_GAS_PER_BLOCK {
316 best_txs.skip_blobs();
317 }
318 }
319320// add gas used by the transaction to cumulative gas used, before creating the receipt
321cumulative_gas_used += gas_used;
322 }
323324let BlockBuilderOutcome { execution_result, block, .. } =
325builder.finish(&state_provider).map_err(Self::Error::from_eth_err)?;
326327Ok((block, execution_result.receipts))
328 }
329}