Skip to main content

reth_rpc_eth_api/helpers/
pending_block.rs

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