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, 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            // we couldn't find the real pending block, so we need to build it ourselves
149            let mut lock = self.pending_block().lock().await;
150
151            let now = Instant::now();
152
153            // Is the pending block cached?
154            if let Some(pending_block) = lock.as_ref() {
155                // Is the cached block not expired and latest is its parent?
156                if pending.evm_env.block_env.number() == U256::from(pending_block.block().number()) &&
157                    parent.hash() == pending_block.block().parent_hash() &&
158                    now <= pending_block.expires_at
159                {
160                    return Ok(Some(pending_block.clone()));
161                }
162            }
163
164            let executed_block = match self
165                .spawn_blocking_io(move |this| {
166                    // we rebuild the block
167                    this.build_block(&parent)
168                })
169                .await
170            {
171                Ok(block) => block,
172                Err(err) => {
173                    debug!(target: "rpc", "Failed to build pending block: {:?}", err);
174                    return Ok(None)
175                }
176            };
177
178            let pending = PendingBlock::with_executed_block(
179                Instant::now() + Duration::from_secs(1),
180                executed_block,
181            );
182
183            *lock = Some(pending.clone());
184
185            Ok(Some(pending))
186        }
187    }
188
189    /// Returns the locally built pending block
190    fn local_pending_block(
191        &self,
192    ) -> impl Future<Output = Result<Option<BlockAndReceipts<Self::Primitives>>, Self::Error>> + Send
193    where
194        Self: SpawnBlocking,
195        Self::Pool:
196            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
197    {
198        async move {
199            if self.pending_block_kind().is_none() {
200                return Ok(None);
201            }
202
203            let pending = self.pending_block_env_and_cfg()?;
204
205            Ok(match pending.origin {
206                PendingBlockEnvOrigin::ActualPending(block, receipts) => {
207                    Some(BlockAndReceipts { block, receipts })
208                }
209                PendingBlockEnvOrigin::DerivedFromLatest(..) => {
210                    self.pool_pending_block().await?.map(PendingBlock::into_block_and_receipts)
211                }
212            })
213        }
214    }
215
216    /// Builds a locally derived pending block using the configured provider and pool.
217    ///
218    /// This is used when no execution-layer pending block is available and a pending block is
219    /// derived from the latest canonical header, using the provided parent.
220    ///
221    /// Withdrawals and any fork-specific behavior (such as EIP-4788 pre-block contract calls) are
222    /// determined by the EVM environment and chain specification used during construction.
223    fn build_block(
224        &self,
225        parent: &SealedHeader<ProviderHeader<Self::Provider>>,
226    ) -> Result<ExecutedBlock<Self::Primitives>, Self::Error>
227    where
228        Self::Pool:
229            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
230        EthApiError: From<ProviderError>,
231    {
232        let state_provider = self
233            .provider()
234            .history_by_block_hash(parent.hash())
235            .map_err(Self::Error::from_eth_err)?;
236        let state = StateProviderDatabase::new(state_provider);
237        let mut db = State::builder().with_database(state).with_bundle_update().build();
238
239        let mut builder = self
240            .evm_config()
241            .builder_for_next_block(&mut db, parent, self.next_env_attributes(parent)?)
242            .map_err(RethError::other)
243            .map_err(Self::Error::from_eth_err)?;
244
245        builder.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
246
247        let block_env = builder.evm_mut().block().clone();
248
249        let blob_params = self
250            .provider()
251            .chain_spec()
252            .blob_params_at_timestamp(parent.timestamp())
253            .unwrap_or_else(BlobParams::cancun);
254        let mut cumulative_gas_used = 0;
255        let mut sum_blob_gas_used = 0;
256        let block_gas_limit: u64 = block_env.gas_limit();
257
258        // Only include transactions if not configured as Empty
259        if !self.pending_block_kind().is_empty() {
260            let mut best_txs = self
261                .pool()
262                .best_transactions_with_attributes(BestTransactionsAttributes::new(
263                    block_env.basefee(),
264                    block_env.blob_gasprice().map(|gasprice| gasprice as u64),
265                ))
266                // freeze to get a block as fast as possible
267                .without_updates();
268
269            while let Some(pool_tx) = best_txs.next() {
270                // ensure we still have capacity for this transaction
271                if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
272                    // we can't fit this transaction into the block, so we need to mark it as
273                    // invalid which also removes all dependent transaction from
274                    // the iterator before we can continue
275                    best_txs.mark_invalid(
276                        &pool_tx,
277                        &InvalidPoolTransactionError::ExceedsGasLimit(
278                            pool_tx.gas_limit(),
279                            block_gas_limit,
280                        ),
281                    );
282                    continue
283                }
284
285                if pool_tx.origin.is_private() {
286                    // we don't want to leak any state changes made by private transactions, so we
287                    // mark them as invalid here which removes all dependent
288                    // transactions from the iteratorbefore we can continue
289                    best_txs.mark_invalid(
290                        &pool_tx,
291                        &InvalidPoolTransactionError::Consensus(
292                            InvalidTransactionError::TxTypeNotSupported,
293                        ),
294                    );
295                    continue
296                }
297
298                // convert tx to a signed transaction
299                let tx = pool_tx.to_consensus();
300
301                // There's only limited amount of blob space available per block, so we need to
302                // check if the EIP-4844 can still fit in the block
303                if let Some(tx_blob_gas) = tx.blob_gas_used() &&
304                    sum_blob_gas_used + tx_blob_gas > blob_params.max_blob_gas_per_block()
305                {
306                    // we can't fit this _blob_ transaction into the block, so we mark it as
307                    // invalid, which removes its dependent transactions from
308                    // the iterator. This is similar to the gas limit condition
309                    // for regular transactions above.
310                    best_txs.mark_invalid(
311                        &pool_tx,
312                        &InvalidPoolTransactionError::ExceedsGasLimit(
313                            tx_blob_gas,
314                            blob_params.max_blob_gas_per_block(),
315                        ),
316                    );
317                    continue
318                }
319
320                let gas_used = match builder.execute_transaction(tx.clone()) {
321                    Ok(gas_used) => gas_used,
322                    Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
323                        error,
324                        ..
325                    })) => {
326                        if error.is_nonce_too_low() {
327                            // if the nonce is too low, we can skip this transaction
328                        } else {
329                            // if the transaction is invalid, we can skip it and all of its
330                            // descendants
331                            best_txs.mark_invalid(
332                                &pool_tx,
333                                &InvalidPoolTransactionError::Consensus(
334                                    InvalidTransactionError::TxTypeNotSupported,
335                                ),
336                            );
337                        }
338                        continue
339                    }
340                    // this is an error that we should treat as fatal for this attempt
341                    Err(err) => return Err(Self::Error::from_eth_err(err)),
342                };
343
344                // add to the total blob gas used if the transaction successfully executed
345                if let Some(tx_blob_gas) = tx.blob_gas_used() {
346                    sum_blob_gas_used += tx_blob_gas;
347
348                    // if we've reached the max data gas per block, we can skip blob txs entirely
349                    if sum_blob_gas_used == blob_params.max_blob_gas_per_block() {
350                        best_txs.skip_blobs();
351                    }
352                }
353
354                // add gas used by the transaction to cumulative gas used, before creating the
355                // receipt
356                cumulative_gas_used += gas_used;
357            }
358        }
359
360        let BlockBuilderOutcome { execution_result, block, hashed_state, trie_updates } =
361            builder.finish(NoopProvider::default()).map_err(Self::Error::from_eth_err)?;
362
363        let execution_outcome =
364            BlockExecutionOutput { state: db.take_bundle(), result: execution_result };
365
366        Ok(ExecutedBlock::new(
367            block.into(),
368            Arc::new(execution_outcome),
369            ComputedTrieData::without_trie_input(
370                Arc::new(hashed_state.into_sorted()),
371                Arc::new(trie_updates.into_sorted()),
372            ),
373        ))
374    }
375}
376
377/// A type that knows how to build a [`ConfigureEvm::NextBlockEnvCtx`] for a pending block.
378pub trait PendingEnvBuilder<Evm: ConfigureEvm>: Send + Sync + Unpin + 'static {
379    /// Builds a [`ConfigureEvm::NextBlockEnvCtx`] for pending block.
380    fn pending_env_attributes(
381        &self,
382        parent: &SealedHeader<HeaderTy<Evm::Primitives>>,
383    ) -> Result<Evm::NextBlockEnvCtx, EthApiError>;
384}
385
386/// Trait that should be implemented on [`ConfigureEvm::NextBlockEnvCtx`] to provide a way for it to
387/// build an environment for pending block.
388///
389/// This assumes that next environment building doesn't require any additional context, for more
390/// complex implementations one should implement [`PendingEnvBuilder`] on their custom type.
391pub trait BuildPendingEnv<Header> {
392    /// Builds a [`ConfigureEvm::NextBlockEnvCtx`] for pending block.
393    fn build_pending_env(parent: &SealedHeader<Header>) -> Self;
394}
395
396impl<Evm> PendingEnvBuilder<Evm> for ()
397where
398    Evm: ConfigureEvm<NextBlockEnvCtx: BuildPendingEnv<HeaderTy<Evm::Primitives>>>,
399{
400    fn pending_env_attributes(
401        &self,
402        parent: &SealedHeader<HeaderTy<Evm::Primitives>>,
403    ) -> Result<Evm::NextBlockEnvCtx, EthApiError> {
404        Ok(Evm::NextBlockEnvCtx::build_pending_env(parent))
405    }
406}
407
408impl<H: BlockHeader> BuildPendingEnv<H> for NextBlockEnvAttributes {
409    fn build_pending_env(parent: &SealedHeader<H>) -> Self {
410        Self {
411            timestamp: parent.timestamp().saturating_add(12),
412            suggested_fee_recipient: parent.beneficiary(),
413            prev_randao: B256::random(),
414            gas_limit: parent.gas_limit(),
415            parent_beacon_block_root: parent.parent_beacon_block_root(),
416            withdrawals: parent.withdrawals_root().map(|_| Default::default()),
417            extra_data: parent.extra_data().clone(),
418        }
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425    use alloy_consensus::Header;
426    use alloy_primitives::B256;
427    use reth_primitives_traits::SealedHeader;
428
429    #[test]
430    fn pending_env_keeps_parent_beacon_root() {
431        let mut header = Header::default();
432        let beacon_root = B256::repeat_byte(0x42);
433        header.parent_beacon_block_root = Some(beacon_root);
434        let sealed = SealedHeader::new(header, B256::ZERO);
435
436        let attrs = NextBlockEnvAttributes::build_pending_env(&sealed);
437
438        assert_eq!(attrs.parent_beacon_block_root, Some(beacon_root));
439    }
440}