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