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, ExecutedBlock};
12use reth_chainspec::{ChainSpecProvider, EthChainSpec};
13use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError, RethError};
14use reth_evm::{
15    execute::{BlockBuilder, BlockBuilderOutcome, ExecutionOutcome},
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, ReceiptProvider,
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) = self.provider().pending_block().map_err(Self::Error::from_eth_err)? &&
66            let Some(receipts) = self
67                .provider()
68                .receipts_by_block(block.hash().into())
69                .map_err(Self::Error::from_eth_err)?
70        {
71            // Note: for the PENDING block we assume it is past the known merge block and
72            // thus this will not fail when looking up the total
73            // difficulty value for the blockenv.
74            let evm_env = self
75                .evm_config()
76                .evm_env(block.header())
77                .map_err(RethError::other)
78                .map_err(Self::Error::from_eth_err)?;
79
80            return Ok(PendingBlockEnv::new(
81                evm_env,
82                PendingBlockEnvOrigin::ActualPending(Arc::new(block), Arc::new(receipts)),
83            ));
84        }
85
86        // no pending block from the CL yet, so we use the latest block and modify the env
87        // values that we can
88        let latest = self
89            .provider()
90            .latest_header()
91            .map_err(Self::Error::from_eth_err)?
92            .ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?;
93
94        let evm_env = self
95            .evm_config()
96            .next_evm_env(&latest, &self.next_env_attributes(&latest)?)
97            .map_err(RethError::other)
98            .map_err(Self::Error::from_eth_err)?;
99
100        Ok(PendingBlockEnv::new(evm_env, PendingBlockEnvOrigin::DerivedFromLatest(latest)))
101    }
102
103    /// Returns [`ConfigureEvm::NextBlockEnvCtx`] for building a local pending block.
104    fn next_env_attributes(
105        &self,
106        parent: &SealedHeader<ProviderHeader<Self::Provider>>,
107    ) -> Result<<Self::Evm as ConfigureEvm>::NextBlockEnvCtx, Self::Error> {
108        Ok(self.pending_env_builder().pending_env_attributes(parent)?)
109    }
110
111    /// Returns a [`StateProviderBox`] on a mem-pool built pending block overlaying latest.
112    fn local_pending_state(
113        &self,
114    ) -> impl Future<Output = Result<Option<StateProviderBox>, Self::Error>> + Send
115    where
116        Self: SpawnBlocking,
117    {
118        async move {
119            let Some(pending_block) = self.pool_pending_block().await? else {
120                return Ok(None);
121            };
122
123            let latest_historical = self
124                .provider()
125                .history_by_block_hash(pending_block.block().parent_hash())
126                .map_err(Self::Error::from_eth_err)?;
127
128            let state = BlockState::from(pending_block);
129
130            Ok(Some(Box::new(state.state_provider(latest_historical)) as StateProviderBox))
131        }
132    }
133
134    /// Returns a mem-pool built pending block.
135    fn pool_pending_block(
136        &self,
137    ) -> impl Future<Output = Result<Option<PendingBlock<Self::Primitives>>, Self::Error>> + Send
138    where
139        Self: SpawnBlocking,
140    {
141        async move {
142            if self.pending_block_kind().is_none() {
143                return Ok(None);
144            }
145            let pending = self.pending_block_env_and_cfg()?;
146            let parent = match pending.origin {
147                PendingBlockEnvOrigin::ActualPending(..) => return Ok(None),
148                PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent,
149            };
150
151            // we couldn't find the real pending block, so we need to build it ourselves
152            let mut lock = self.pending_block().lock().await;
153
154            let now = Instant::now();
155
156            // Is the pending block cached?
157            if let Some(pending_block) = lock.as_ref() {
158                // Is the cached block not expired and latest is its parent?
159                if pending.evm_env.block_env.number() == U256::from(pending_block.block().number()) &&
160                    parent.hash() == pending_block.block().parent_hash() &&
161                    now <= pending_block.expires_at
162                {
163                    return Ok(Some(pending_block.clone()));
164                }
165            }
166
167            let executed_block = match self
168                .spawn_blocking_io(move |this| {
169                    // we rebuild the block
170                    this.build_block(&parent)
171                })
172                .await
173            {
174                Ok(block) => block,
175                Err(err) => {
176                    debug!(target: "rpc", "Failed to build pending block: {:?}", err);
177                    return Ok(None)
178                }
179            };
180
181            let pending = PendingBlock::with_executed_block(
182                Instant::now() + Duration::from_secs(1),
183                executed_block,
184            );
185
186            *lock = Some(pending.clone());
187
188            Ok(Some(pending))
189        }
190    }
191
192    /// Returns the locally built pending block
193    fn local_pending_block(
194        &self,
195    ) -> impl Future<Output = Result<Option<BlockAndReceipts<Self::Primitives>>, Self::Error>> + Send
196    where
197        Self: SpawnBlocking,
198        Self::Pool:
199            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
200    {
201        async move {
202            if self.pending_block_kind().is_none() {
203                return Ok(None);
204            }
205
206            let pending = self.pending_block_env_and_cfg()?;
207
208            Ok(match pending.origin {
209                PendingBlockEnvOrigin::ActualPending(block, receipts) => {
210                    Some(BlockAndReceipts { block, receipts })
211                }
212                PendingBlockEnvOrigin::DerivedFromLatest(..) => {
213                    self.pool_pending_block().await?.map(PendingBlock::into_block_and_receipts)
214                }
215            })
216        }
217    }
218
219    /// Builds a pending block using the configured provider and pool.
220    ///
221    /// If the origin is the actual pending block, the block is built with withdrawals.
222    ///
223    /// After Cancun, if the origin is the actual pending block, the block includes the EIP-4788 pre
224    /// block contract call using the parent beacon block root received from the CL.
225    fn build_block(
226        &self,
227        parent: &SealedHeader<ProviderHeader<Self::Provider>>,
228    ) -> Result<ExecutedBlock<Self::Primitives>, Self::Error>
229    where
230        Self::Pool:
231            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
232        EthApiError: From<ProviderError>,
233    {
234        let state_provider = self
235            .provider()
236            .history_by_block_hash(parent.hash())
237            .map_err(Self::Error::from_eth_err)?;
238        let state = StateProviderDatabase::new(&state_provider);
239        let mut db = State::builder().with_database(state).with_bundle_update().build();
240
241        let mut builder = self
242            .evm_config()
243            .builder_for_next_block(&mut db, parent, self.next_env_attributes(parent)?)
244            .map_err(RethError::other)
245            .map_err(Self::Error::from_eth_err)?;
246
247        builder.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
248
249        let block_env = builder.evm_mut().block().clone();
250
251        let blob_params = self
252            .provider()
253            .chain_spec()
254            .blob_params_at_timestamp(parent.timestamp())
255            .unwrap_or_else(BlobParams::cancun);
256        let mut cumulative_gas_used = 0;
257        let mut sum_blob_gas_used = 0;
258        let block_gas_limit: u64 = block_env.gas_limit();
259
260        // Only include transactions if not configured as Empty
261        if !self.pending_block_kind().is_empty() {
262            let mut best_txs = self
263                .pool()
264                .best_transactions_with_attributes(BestTransactionsAttributes::new(
265                    block_env.basefee(),
266                    block_env.blob_gasprice().map(|gasprice| gasprice as u64),
267                ))
268                // freeze to get a block as fast as possible
269                .without_updates();
270
271            while let Some(pool_tx) = best_txs.next() {
272                // ensure we still have capacity for this transaction
273                if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
274                    // we can't fit this transaction into the block, so we need to mark it as
275                    // invalid which also removes all dependent transaction from
276                    // the iterator before we can continue
277                    best_txs.mark_invalid(
278                        &pool_tx,
279                        InvalidPoolTransactionError::ExceedsGasLimit(
280                            pool_tx.gas_limit(),
281                            block_gas_limit,
282                        ),
283                    );
284                    continue
285                }
286
287                if pool_tx.origin.is_private() {
288                    // we don't want to leak any state changes made by private transactions, so we
289                    // mark them as invalid here which removes all dependent
290                    // transactions from the iteratorbefore we can continue
291                    best_txs.mark_invalid(
292                        &pool_tx,
293                        InvalidPoolTransactionError::Consensus(
294                            InvalidTransactionError::TxTypeNotSupported,
295                        ),
296                    );
297                    continue
298                }
299
300                // convert tx to a signed transaction
301                let tx = pool_tx.to_consensus();
302
303                // There's only limited amount of blob space available per block, so we need to
304                // check if the EIP-4844 can still fit in the block
305                if let Some(tx_blob_gas) = tx.blob_gas_used() &&
306                    sum_blob_gas_used + tx_blob_gas > blob_params.max_blob_gas_per_block()
307                {
308                    // we can't fit this _blob_ transaction into the block, so we mark it as
309                    // invalid, which removes its dependent transactions from
310                    // the iterator. This is similar to the gas limit condition
311                    // for regular transactions above.
312                    best_txs.mark_invalid(
313                        &pool_tx,
314                        InvalidPoolTransactionError::ExceedsGasLimit(
315                            tx_blob_gas,
316                            blob_params.max_blob_gas_per_block(),
317                        ),
318                    );
319                    continue
320                }
321
322                let gas_used = match builder.execute_transaction(tx.clone()) {
323                    Ok(gas_used) => gas_used,
324                    Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
325                        error,
326                        ..
327                    })) => {
328                        if error.is_nonce_too_low() {
329                            // if the nonce is too low, we can skip this transaction
330                        } else {
331                            // if the transaction is invalid, we can skip it and all of its
332                            // descendants
333                            best_txs.mark_invalid(
334                                &pool_tx,
335                                InvalidPoolTransactionError::Consensus(
336                                    InvalidTransactionError::TxTypeNotSupported,
337                                ),
338                            );
339                        }
340                        continue
341                    }
342                    // this is an error that we should treat as fatal for this attempt
343                    Err(err) => return Err(Self::Error::from_eth_err(err)),
344                };
345
346                // add to the total blob gas used if the transaction successfully executed
347                if let Some(tx_blob_gas) = tx.blob_gas_used() {
348                    sum_blob_gas_used += tx_blob_gas;
349
350                    // if we've reached the max data gas per block, we can skip blob txs entirely
351                    if sum_blob_gas_used == blob_params.max_blob_gas_per_block() {
352                        best_txs.skip_blobs();
353                    }
354                }
355
356                // add gas used by the transaction to cumulative gas used, before creating the
357                // receipt
358                cumulative_gas_used += gas_used;
359            }
360        }
361
362        let BlockBuilderOutcome { execution_result, block, hashed_state, trie_updates } =
363            builder.finish(NoopProvider::default()).map_err(Self::Error::from_eth_err)?;
364
365        let execution_outcome = ExecutionOutcome::new(
366            db.take_bundle(),
367            vec![execution_result.receipts],
368            block.number(),
369            vec![execution_result.requests],
370        );
371
372        Ok(ExecutedBlock {
373            recovered_block: block.into(),
374            execution_output: Arc::new(execution_outcome),
375            hashed_state: Arc::new(hashed_state),
376            trie_updates: Arc::new(trie_updates),
377        })
378    }
379}
380
381/// A type that knows how to build a [`ConfigureEvm::NextBlockEnvCtx`] for a pending block.
382pub trait PendingEnvBuilder<Evm: ConfigureEvm>: Send + Sync + Unpin + 'static {
383    /// Builds a [`ConfigureEvm::NextBlockEnvCtx`] for pending block.
384    fn pending_env_attributes(
385        &self,
386        parent: &SealedHeader<HeaderTy<Evm::Primitives>>,
387    ) -> Result<Evm::NextBlockEnvCtx, EthApiError>;
388}
389
390/// Trait that should be implemented on [`ConfigureEvm::NextBlockEnvCtx`] to provide a way for it to
391/// build an environment for pending block.
392///
393/// This assumes that next environment building doesn't require any additional context, for more
394/// complex implementations one should implement [`PendingEnvBuilder`] on their custom type.
395pub trait BuildPendingEnv<Header> {
396    /// Builds a [`ConfigureEvm::NextBlockEnvCtx`] for pending block.
397    fn build_pending_env(parent: &SealedHeader<Header>) -> Self;
398}
399
400impl<Evm> PendingEnvBuilder<Evm> for ()
401where
402    Evm: ConfigureEvm<NextBlockEnvCtx: BuildPendingEnv<HeaderTy<Evm::Primitives>>>,
403{
404    fn pending_env_attributes(
405        &self,
406        parent: &SealedHeader<HeaderTy<Evm::Primitives>>,
407    ) -> Result<Evm::NextBlockEnvCtx, EthApiError> {
408        Ok(Evm::NextBlockEnvCtx::build_pending_env(parent))
409    }
410}
411
412impl<H: BlockHeader> BuildPendingEnv<H> for NextBlockEnvAttributes {
413    fn build_pending_env(parent: &SealedHeader<H>) -> Self {
414        Self {
415            timestamp: parent.timestamp().saturating_add(12),
416            suggested_fee_recipient: parent.beneficiary(),
417            prev_randao: B256::random(),
418            gas_limit: parent.gas_limit(),
419            parent_beacon_block_root: parent.parent_beacon_block_root().map(|_| B256::ZERO),
420            withdrawals: parent.withdrawals_root().map(|_| Default::default()),
421        }
422    }
423}