Skip to main content

reth_rpc_eth_api/helpers/
pending_block.rs

1//! Loads provider pending blocks or builds them from the transaction pool for `eth_` block,
2//! transaction, call and trace RPC methods.
3
4use super::SpawnBlocking;
5use crate::{EthApiTypes, FromEthApiError, FromEvmError, RpcNodeCore};
6use alloy_consensus::{BlockHeader, Transaction};
7use alloy_eips::{eip7840::BlobParams, BlockId};
8use alloy_primitives::{B256, U256};
9use alloy_rpc_types_eth::BlockOverrides;
10use futures::Future;
11use reth_chain_state::ExecutedBlock;
12use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
13use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError, RethError};
14use reth_evm::{
15    block::TxResult,
16    execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutionOutput},
17    ConfigureEvm, Evm, EvmEnvFor, NextBlockEnvAttributes,
18};
19use reth_primitives_traits::{
20    transaction::error::InvalidTransactionError, HeaderTy, NodePrimitives, SealedHeader,
21};
22use reth_revm::{database::StateProviderDatabase, db::State};
23use reth_rpc_convert::RpcConvert;
24use reth_rpc_eth_types::{
25    block::BlockAndReceipts, builder::config::PendingBlockKind, EthApiError, PendingBlock,
26    PendingBlockEnv, PendingBlockEnvOrigin,
27};
28use reth_storage_api::{
29    noop::NoopProvider, BlockReader, BlockReaderIdExt, ProviderHeader, ProviderTx, StateProvider,
30    StateProviderBox, StateProviderFactory,
31};
32use reth_transaction_pool::{
33    error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes,
34    PoolTransaction, TransactionPool,
35};
36use reth_trie_common::SortedTrieData;
37use revm::context_interface::{Block, Cfg as _};
38use std::{
39    sync::Arc,
40    time::{Duration, Instant},
41};
42use tokio::sync::Mutex;
43use tracing::debug;
44
45/// Loads a provider pending block or builds one from the transaction pool.
46///
47/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
48pub trait LoadPendingBlock:
49    EthApiTypes<
50        Error: FromEvmError<Self::Evm>,
51        RpcConvert: RpcConvert<Network = Self::NetworkTypes>,
52    > + RpcNodeCore
53{
54    /// Returns a handle to the pending block.
55    ///
56    /// Data access in default (L1) trait method implementations.
57    fn pending_block(&self) -> &Mutex<Option<PendingBlock<Self::Primitives>>>;
58
59    /// Returns a [`PendingEnvBuilder`] for the pending block.
60    fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<Self::Evm>;
61
62    /// Returns the pending block kind.
63    fn pending_block_kind(&self) -> PendingBlockKind {
64        self.eth_api_settings().pending_block_kind
65    }
66
67    /// Returns the pending block environment and its origin.
68    ///
69    /// Uses the provider's pending block when available; otherwise derives the next block
70    /// environment from the latest header. This method performs synchronous provider reads.
71    fn pending_block_env_and_cfg(&self) -> Result<PendingBlockEnv<Self::Evm>, Self::Error> {
72        if let Some(pending) =
73            self.provider().pending_block_and_receipts().map_err(Self::Error::from_eth_err)?
74        {
75            let (block, output) = pending.into_parts();
76
77            // Note: for the PENDING block we assume it is past the known merge block and
78            // thus this will not fail when looking up the total
79            // difficulty value for the blockenv.
80            let evm_env = self
81                .evm_config()
82                .evm_env(block.header())
83                .map_err(RethError::other)
84                .map_err(Self::Error::from_eth_err)?;
85
86            return Ok(PendingBlockEnv::new(
87                evm_env,
88                PendingBlockEnvOrigin::ActualPending(block, output.into()),
89            ));
90        }
91
92        // no pending block from the CL yet, so we use the latest block and modify the env
93        // values that we can
94        let latest = self
95            .provider()
96            .latest_header()
97            .map_err(Self::Error::from_eth_err)?
98            .ok_or(EthApiError::HeaderNotFound(BlockId::latest()))?;
99
100        let evm_env = self
101            .evm_config()
102            .next_evm_env(&latest, &self.next_env_attributes(&latest)?)
103            .map_err(RethError::other)
104            .map_err(Self::Error::from_eth_err)?;
105
106        Ok(PendingBlockEnv::new(evm_env, PendingBlockEnvOrigin::DerivedFromLatest(latest)))
107    }
108
109    /// Returns [`ConfigureEvm::NextBlockEnvCtx`] for building a local pending block.
110    fn next_env_attributes(
111        &self,
112        parent: &SealedHeader<ProviderHeader<Self::Provider>>,
113    ) -> Result<<Self::Evm as ConfigureEvm>::NextBlockEnvCtx, Self::Error> {
114        Ok(self.pending_env_builder().pending_env_attributes(parent, None)?)
115    }
116
117    /// Returns a [`StateProviderBox`] on a mem-pool built pending block overlaying latest.
118    ///
119    /// This is used by `state_at_block_id` and, by default, by
120    /// [`Self::local_pending_block_or_state`]. This method's default opens the provider state on a
121    /// blocking task after building the pending block.
122    fn local_pending_state(
123        &self,
124    ) -> impl Future<Output = Result<Option<StateProviderBox>, Self::Error>> + Send
125    where
126        Self: SpawnBlocking,
127    {
128        async move {
129            let Some(pending_block) = self.pool_pending_block().await? else {
130                return Ok(None);
131            };
132
133            self.spawn_blocking_io(move |this| {
134                Ok(Some(
135                    this.provider()
136                        .state_with_block_appended(
137                            pending_block.block().parent_hash(),
138                            pending_block.executed_block,
139                        )
140                        .map_err(Self::Error::from_eth_err)?,
141                ))
142            })
143            .await
144        }
145    }
146
147    /// Selects the local pending source for state RPCs.
148    ///
149    /// By default, delegates to [`Self::local_pending_state`] so existing chain-specific overrides
150    /// continue to apply. Implementations can return [`PendingStateSource::Block`] to defer opening
151    /// the block's state provider until the state RPC's blocking task. Returning `None` falls back
152    /// to the provider's pending state.
153    fn local_pending_block_or_state(
154        &self,
155    ) -> impl Future<Output = Result<Option<PendingStateSource<Self::Primitives>>, Self::Error>> + Send
156    where
157        Self: SpawnBlocking,
158    {
159        async move { Ok(self.local_pending_state().await?.map(PendingStateSource::State)) }
160    }
161
162    /// Returns a cached or newly built pending block from the transaction pool.
163    ///
164    /// Returns `None` if local pending blocks are disabled or the provider has an actual pending
165    /// block. Resolves the environment before awaiting the cache lock, then may spawn a blocking
166    /// task to build the block. Callers must await this outside a blocking task.
167    fn pool_pending_block(
168        &self,
169    ) -> impl Future<Output = Result<Option<PendingBlock<Self::Primitives>>, Self::Error>> + Send
170    where
171        Self: SpawnBlocking,
172    {
173        async move {
174            if self.pending_block_kind().is_none() {
175                return Ok(None);
176            }
177            let pending = self.pending_block_env_and_cfg()?;
178            let parent = match pending.origin {
179                PendingBlockEnvOrigin::ActualPending(..) => return Ok(None),
180                PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent,
181            };
182
183            self.build_pool_pending_block(parent, pending.evm_env).await
184        }
185    }
186
187    /// Returns a cached or newly built pending block for the given parent and environment.
188    ///
189    /// Holds the async cache lock while rebuilding, which may spawn blocking work. The resolved
190    /// parent and environment let callers reuse the result of their provider lookup.
191    fn build_pool_pending_block(
192        &self,
193        parent: SealedHeader<ProviderHeader<Self::Provider>>,
194        evm_env: EvmEnvFor<Self::Evm>,
195    ) -> impl Future<Output = Result<Option<PendingBlock<Self::Primitives>>, Self::Error>> + Send
196    where
197        Self: SpawnBlocking,
198    {
199        async move {
200            // we couldn't find the real pending block, so we need to build it ourselves
201            let mut lock = self.pending_block().lock().await;
202
203            let now = Instant::now();
204
205            // Is the pending block cached?
206            if let Some(pending_block) = lock.as_ref() {
207                // Is the cached block not expired and latest is its parent?
208                if evm_env.block_env.number() == U256::from(pending_block.block().number()) &&
209                    parent.hash() == pending_block.block().parent_hash() &&
210                    now <= pending_block.expires_at
211                {
212                    return Ok(Some(pending_block.clone()));
213                }
214            }
215
216            let executed_block = match self
217                .spawn_blocking_io(move |this| {
218                    // we rebuild the block
219                    this.build_block(&parent)
220                })
221                .await
222            {
223                Ok(block) => block,
224                Err(err) => {
225                    debug!(target: "rpc", "Failed to build pending block: {:?}", err);
226                    return Ok(None)
227                }
228            };
229
230            let pending = PendingBlock::with_executed_block(
231                Instant::now() + Duration::from_secs(1),
232                executed_block,
233            );
234
235            *lock = Some(pending.clone());
236
237            Ok(Some(pending))
238        }
239    }
240
241    /// When enabled, returns the provider's pending block or builds one from the pool.
242    fn local_pending_block(
243        &self,
244    ) -> impl Future<Output = Result<Option<BlockAndReceipts<Self::Primitives>>, Self::Error>> + Send
245    where
246        Self: SpawnBlocking,
247        Self::Pool:
248            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
249    {
250        async move {
251            if self.pending_block_kind().is_none() {
252                return Ok(None);
253            }
254
255            let pending = self.pending_block_env_and_cfg()?;
256
257            Ok(match pending.origin {
258                PendingBlockEnvOrigin::ActualPending(block, receipts) => {
259                    Some(BlockAndReceipts { block, receipts })
260                }
261                PendingBlockEnvOrigin::DerivedFromLatest(parent) => self
262                    .build_pool_pending_block(parent, pending.evm_env)
263                    .await?
264                    .map(PendingBlock::into_block_and_receipts),
265            })
266        }
267    }
268
269    /// Builds a locally derived pending block using the configured provider and pool.
270    ///
271    /// This is used when no execution-layer pending block is available and a pending block is
272    /// derived from the latest canonical header, using the provided parent.
273    ///
274    /// Withdrawals and any fork-specific behavior (such as EIP-4788 pre-block contract calls) are
275    /// determined by the EVM environment and chain specification used during construction.
276    fn build_block(
277        &self,
278        parent: &SealedHeader<ProviderHeader<Self::Provider>>,
279    ) -> Result<ExecutedBlock<Self::Primitives>, Self::Error>
280    where
281        Self::Pool:
282            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
283        EthApiError: From<ProviderError>,
284    {
285        let state_provider = self
286            .provider()
287            .history_by_block_hash(parent.hash())
288            .map_err(Self::Error::from_eth_err)?;
289        let state = StateProviderDatabase::new(state_provider.into_evm_state_provider());
290        let mut db = State::builder().with_database(state).with_bundle_update().build();
291
292        let mut builder = self
293            .evm_config()
294            .builder_for_next_block(&mut db, parent, self.next_env_attributes(parent)?)
295            .map_err(RethError::other)
296            .map_err(Self::Error::from_eth_err)?;
297
298        builder.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
299
300        let block_gas_limit: u64 = builder.evm().block().gas_limit();
301        let is_amsterdam = self
302            .provider()
303            .chain_spec()
304            .is_amsterdam_active_at_timestamp(builder.evm().block().timestamp().saturating_to());
305        let basefee = builder.evm().block().basefee();
306        let blob_gasprice = builder.evm().block().blob_gasprice().map(|p| p as u64);
307
308        let blob_params = self
309            .provider()
310            .chain_spec()
311            .blob_params_at_timestamp(parent.timestamp())
312            .unwrap_or_else(BlobParams::cancun);
313        let mut cumulative_tx_gas_used = 0;
314        let mut block_regular_gas_used = 0;
315        let mut block_state_gas_used = 0;
316        let mut sum_blob_gas_used = 0;
317        let tx_gas_limit_cap = builder.evm().cfg_env().tx_gas_limit_cap();
318
319        // Only include transactions if not configured as Empty
320        if !self.pending_block_kind().is_empty() {
321            let mut best_txs = self
322                .pool()
323                .best_transactions_with_attributes(BestTransactionsAttributes::new(
324                    basefee,
325                    blob_gasprice,
326                ))
327                // freeze to get a block as fast as possible
328                .without_updates();
329
330            while let Some(pool_tx) = best_txs.next() {
331                // ensure we still have capacity for this transaction
332                let exceeds_gas_limit = if is_amsterdam {
333                    let regular_available_gas =
334                        block_gas_limit.saturating_sub(block_regular_gas_used);
335                    let state_available_gas = block_gas_limit.saturating_sub(block_state_gas_used);
336                    let regular_tx_gas_limit = pool_tx.gas_limit().min(tx_gas_limit_cap);
337
338                    if regular_tx_gas_limit > regular_available_gas {
339                        Some((regular_tx_gas_limit, regular_available_gas))
340                    } else if pool_tx.gas_limit() > state_available_gas {
341                        Some((pool_tx.gas_limit(), state_available_gas))
342                    } else {
343                        None
344                    }
345                } else {
346                    let block_available_gas =
347                        block_gas_limit.saturating_sub(cumulative_tx_gas_used);
348                    (pool_tx.gas_limit() > block_available_gas)
349                        .then_some((pool_tx.gas_limit(), block_available_gas))
350                };
351
352                if let Some((transaction_gas_limit, block_available_gas)) = exceeds_gas_limit {
353                    // we can't fit this transaction into the block, so we need to mark it as
354                    // invalid which also removes all dependent transaction from
355                    // the iterator before we can continue
356                    best_txs.mark_invalid(
357                        &pool_tx,
358                        InvalidPoolTransactionError::ExceedsGasLimit(
359                            transaction_gas_limit,
360                            block_available_gas,
361                        ),
362                    );
363                    continue
364                }
365
366                if pool_tx.origin.is_private() {
367                    // we don't want to leak any state changes made by private transactions, so we
368                    // mark them as invalid here which removes all dependent
369                    // transactions from the iteratorbefore we can continue
370                    best_txs.mark_invalid(
371                        &pool_tx,
372                        InvalidPoolTransactionError::Consensus(
373                            InvalidTransactionError::TxTypeNotSupported,
374                        ),
375                    );
376                    continue
377                }
378
379                // convert tx to a signed transaction
380                let tx = pool_tx.to_consensus();
381
382                // There's only limited amount of blob space available per block, so we need to
383                // check if the EIP-4844 can still fit in the block
384                let tx_blob_gas = tx.blob_gas_used();
385                if let Some(tx_blob_gas) = tx_blob_gas &&
386                    sum_blob_gas_used + tx_blob_gas > blob_params.max_blob_gas_per_block()
387                {
388                    // we can't fit this _blob_ transaction into the block, so we mark it as
389                    // invalid, which removes its dependent transactions from
390                    // the iterator. This is similar to the gas limit condition
391                    // for regular transactions above.
392                    best_txs.mark_invalid(
393                        &pool_tx,
394                        InvalidPoolTransactionError::ExceedsGasLimit(
395                            tx_blob_gas,
396                            blob_params.max_blob_gas_per_block(),
397                        ),
398                    );
399                    continue
400                }
401
402                let mut tx_regular_gas_used = 0;
403                let gas_output =
404                    match builder.execute_transaction_with_result_closure(tx, |result| {
405                        tx_regular_gas_used = result.result().result.gas().block_regular_gas_used();
406                    }) {
407                        Ok(gas_output) => gas_output,
408                        Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
409                            error,
410                            ..
411                        })) => {
412                            if error.is_nonce_too_low() {
413                                // if the nonce is too low, we can skip this transaction
414                            } else {
415                                // if the transaction is invalid, we can skip it and all of its
416                                // descendants
417                                best_txs.mark_invalid(
418                                    &pool_tx,
419                                    InvalidPoolTransactionError::Consensus(
420                                        InvalidTransactionError::TxTypeNotSupported,
421                                    ),
422                                );
423                            }
424                            continue
425                        }
426                        Err(BlockExecutionError::Validation(
427                            BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
428                                transaction_gas_limit,
429                                block_available_gas,
430                            },
431                        )) => {
432                            best_txs.mark_invalid(
433                                &pool_tx,
434                                InvalidPoolTransactionError::ExceedsGasLimit(
435                                    transaction_gas_limit,
436                                    block_available_gas,
437                                ),
438                            );
439                            continue
440                        }
441                        // this is an error that we should treat as fatal for this attempt
442                        Err(err) => return Err(Self::Error::from_eth_err(err)),
443                    };
444
445                // add to the total blob gas used if the transaction successfully executed
446                if let Some(tx_blob_gas) = tx_blob_gas {
447                    sum_blob_gas_used += tx_blob_gas;
448
449                    // if we've reached the max data gas per block, we can skip blob txs entirely
450                    if sum_blob_gas_used == blob_params.max_blob_gas_per_block() {
451                        best_txs.skip_blobs();
452                    }
453                }
454
455                // Track receipt gas and the Amsterdam block-capacity counter separately.
456                let gas_used = gas_output.tx_gas_used();
457                cumulative_tx_gas_used += gas_used;
458                block_regular_gas_used += tx_regular_gas_used;
459                block_state_gas_used += gas_output.state_gas_used();
460            }
461        }
462
463        let BlockBuilderOutcome { execution_result, block, hashed_state, trie_updates, .. } =
464            builder.finish(NoopProvider::default(), None).map_err(Self::Error::from_eth_err)?;
465
466        let execution_outcome =
467            BlockExecutionOutput { state: db.take_bundle(), result: execution_result };
468
469        Ok(ExecutedBlock::new(
470            block.into(),
471            Arc::new(execution_outcome),
472            SortedTrieData::new(
473                Arc::new(hashed_state.into_sorted()),
474                Arc::new(trie_updates.into_sorted()),
475            ),
476        ))
477    }
478}
479
480/// Local pending state selected for a state RPC.
481pub enum PendingStateSource<N: NodePrimitives> {
482    /// A block whose state provider must be opened on the state RPC's blocking task.
483    Block(PendingBlock<N>),
484    /// A chain-specific state provider that is already open.
485    State(StateProviderBox),
486}
487
488impl<N: NodePrimitives> std::fmt::Debug for PendingStateSource<N> {
489    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490        match self {
491            Self::Block(_) => f.write_str("PendingStateSource::Block(..)"),
492            Self::State(_) => f.write_str("PendingStateSource::State(..)"),
493        }
494    }
495}
496
497/// A type that knows how to build a [`ConfigureEvm::NextBlockEnvCtx`] for a pending block.
498pub trait PendingEnvBuilder<Evm: ConfigureEvm>: Send + Sync + Unpin + 'static {
499    /// Builds a [`ConfigureEvm::NextBlockEnvCtx`] for a pending block.
500    ///
501    /// `block_overrides` can be used for values that need to be part of the next block context
502    /// before the EVM environment is constructed. Other block overrides are applied directly to the
503    /// EVM environment after construction.
504    fn pending_env_attributes(
505        &self,
506        parent: &SealedHeader<HeaderTy<Evm::Primitives>>,
507        block_overrides: Option<&BlockOverrides>,
508    ) -> Result<Evm::NextBlockEnvCtx, EthApiError>;
509}
510
511/// Trait that should be implemented on [`ConfigureEvm::NextBlockEnvCtx`] to provide a way for it to
512/// build an environment for pending block.
513///
514/// This assumes that next environment building doesn't require any additional context, for more
515/// complex implementations one should implement [`PendingEnvBuilder`] on their custom type.
516pub trait BuildPendingEnv<Header> {
517    /// Builds a [`ConfigureEvm::NextBlockEnvCtx`] for a pending block.
518    ///
519    /// `block_overrides` can be used for values that need to be part of the next block context
520    /// before the EVM environment is constructed. Other block overrides are applied directly to the
521    /// EVM environment after construction.
522    fn build_pending_env(
523        parent: &SealedHeader<Header>,
524        block_overrides: Option<&BlockOverrides>,
525    ) -> Self;
526}
527
528impl<Evm> PendingEnvBuilder<Evm> for ()
529where
530    Evm: ConfigureEvm<NextBlockEnvCtx: BuildPendingEnv<HeaderTy<Evm::Primitives>>>,
531{
532    fn pending_env_attributes(
533        &self,
534        parent: &SealedHeader<HeaderTy<Evm::Primitives>>,
535        block_overrides: Option<&BlockOverrides>,
536    ) -> Result<Evm::NextBlockEnvCtx, EthApiError> {
537        Ok(Evm::NextBlockEnvCtx::build_pending_env(parent, block_overrides))
538    }
539}
540
541impl<H: BlockHeader> BuildPendingEnv<H> for NextBlockEnvAttributes {
542    fn build_pending_env(
543        parent: &SealedHeader<H>,
544        block_overrides: Option<&BlockOverrides>,
545    ) -> Self {
546        let mut attributes = Self {
547            timestamp: parent.timestamp().saturating_add(12),
548            suggested_fee_recipient: parent.beneficiary(),
549            prev_randao: B256::random(),
550            gas_limit: parent.gas_limit(),
551            parent_beacon_block_root: parent.parent_beacon_block_root().map(|_| B256::ZERO),
552            withdrawals: parent.withdrawals_root().map(|_| Default::default()),
553            extra_data: parent.extra_data().clone(),
554            slot_number: parent.slot_number().map(|slot| slot.saturating_add(1)),
555        };
556
557        if attributes.parent_beacon_block_root.is_some() &&
558            let Some(beacon_root) = block_overrides.and_then(|overrides| overrides.beacon_root)
559        {
560            attributes.parent_beacon_block_root = Some(beacon_root);
561        }
562
563        attributes
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570    use alloy_consensus::Header;
571    use alloy_primitives::B256;
572    use reth_primitives_traits::SealedHeader;
573
574    #[test]
575    fn pending_env_defaults_parent_beacon_root() {
576        let mut header = Header::default();
577        let beacon_root = B256::repeat_byte(0x42);
578        header.parent_beacon_block_root = Some(beacon_root);
579        let sealed = SealedHeader::new(header, B256::ZERO);
580
581        let attrs = NextBlockEnvAttributes::build_pending_env(&sealed, None);
582
583        assert_eq!(attrs.parent_beacon_block_root, Some(B256::ZERO));
584    }
585
586    #[test]
587    fn pending_env_applies_parent_beacon_root_override() {
588        let header = Header { parent_beacon_block_root: Some(B256::ZERO), ..Default::default() };
589        let sealed = SealedHeader::new(header, B256::ZERO);
590        let beacon_root = B256::repeat_byte(0x42);
591        let block_overrides =
592            BlockOverrides { beacon_root: Some(beacon_root), ..Default::default() };
593
594        let attrs = NextBlockEnvAttributes::build_pending_env(&sealed, Some(&block_overrides));
595
596        assert_eq!(attrs.parent_beacon_block_root, Some(beacon_root));
597    }
598
599    #[test]
600    fn pending_env_ignores_parent_beacon_root_override_before_fork() {
601        let sealed = SealedHeader::new(Header::default(), B256::ZERO);
602        let beacon_root = B256::repeat_byte(0x42);
603        let block_overrides =
604            BlockOverrides { beacon_root: Some(beacon_root), ..Default::default() };
605
606        let attrs = NextBlockEnvAttributes::build_pending_env(&sealed, Some(&block_overrides));
607
608        assert_eq!(attrs.parent_beacon_block_root, None);
609    }
610
611    #[test]
612    fn pending_env_increments_parent_slot_number() {
613        let header = Header { slot_number: Some(7), ..Default::default() };
614        let sealed = SealedHeader::new(header, B256::ZERO);
615
616        let attrs = NextBlockEnvAttributes::build_pending_env(&sealed, None);
617
618        assert_eq!(attrs.slot_number, Some(8));
619    }
620}