Skip to main content

reth_rpc_eth_api/helpers/
pending_block.rs

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