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