Skip to main content

reth_e2e_test_utils/
node.rs

1use crate::{
2    engine::EngineTestContext,
3    network::NetworkTestContext,
4    payload::PayloadTestContext,
5    receipt::ensure_successful,
6    rpc::RpcTestContext,
7    wait::{poll_until, POLL_INTERVAL, WAIT_TIMEOUT},
8    TmpDB,
9};
10use alloy_consensus::BlockHeader;
11use alloy_eips::{eip2718::Encodable2718, BlockId};
12use alloy_network::{Ethereum, IntoWallet, ReceiptResponse};
13use alloy_primitives::{BlockHash, BlockNumber, Bytes, Sealable, B256};
14use alloy_provider::{
15    fillers::{FillProvider, RecommendedFillers, TxFiller},
16    Provider, ProviderBuilder, RootProvider,
17};
18use alloy_rpc_types_engine::{
19    ForkchoiceState, ForkchoiceUpdateError, ForkchoiceUpdated, PayloadStatus, PayloadStatusEnum,
20};
21use alloy_rpc_types_eth::BlockNumberOrTag;
22use eyre::{bail, ensure, eyre, Ok, OptionExt, WrapErr};
23use futures_util::{
24    future::{select, BoxFuture, Either},
25    Future,
26};
27use jsonrpsee::{core::client::ClientT, http_client::HttpClient};
28use reth_chainspec::EthereumHardforks;
29use reth_db::{mdbx::DatabaseArguments, open_db_read_only};
30use reth_engine_primitives::BeaconForkChoiceUpdateError;
31use reth_network_api::test_utils::PeersHandleProvider;
32use reth_node_api::{
33    Block, BlockBody, BlockTy, FullNodeComponents, NodePrimitives, PayloadTypes, PrimitivesTy,
34};
35use reth_node_builder::{rpc::RethRpcAddOns, FullNode, NodeTypes};
36use reth_node_core::{
37    dirs::{ChainPath, DataDirPath},
38    exit::NodeExitFuture,
39};
40use reth_payload_primitives::BuiltPayload;
41use reth_primitives_traits::SealedBlock;
42use reth_provider::{
43    providers::RocksDBProvider, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader,
44    BlockReaderIdExt, CanonStateNotificationStream, CanonStateSubscriptions, Chain,
45    DatabaseProviderFactory, HeaderProvider, MetadataProvider, PruneCheckpointReader, SnapAttempt,
46    StageCheckpointReader,
47};
48use reth_prune_types::PruneSegment;
49use reth_rpc_api::{TestingBuildBlockRequestV1, TestingBuildBlockResponseV1};
50use reth_rpc_builder::auth::AuthServerHandle;
51use reth_rpc_eth_api::{
52    helpers::{EthApiSpec, EthTransactions, LoadReceipt, TraceExt},
53    EthApiTypes, RpcReceipt,
54};
55use reth_stages_types::StageId;
56use reth_transaction_pool::TransactionPool;
57use std::{
58    pin::{pin, Pin},
59    sync::{Arc, Mutex, PoisonError},
60    time::{Duration, Instant},
61};
62use tokio_stream::StreamExt;
63use url::Url;
64
65/// Interval at which the alloy providers of [`NodeTestContext`] poll the node, e.g. for receipts
66/// of pending transactions.
67///
68/// Much shorter than alloy's default for local nodes, since test nodes build blocks on demand or
69/// with short dev block times.
70pub const RPC_PROVIDER_POLL_INTERVAL: Duration = Duration::from_millis(10);
71
72/// Maximum time [`NodeTestContext::stop`] waits for the database of a node to be released after
73/// the node shut down.
74///
75/// The node itself releases it within milliseconds, so running into this timeout means that
76/// something else still holds a handle of the database.
77pub const DATABASE_RELEASE_TIMEOUT: Duration = Duration::from_secs(10);
78
79/// A helper struct to handle node actions
80#[expect(missing_debug_implementations)]
81pub struct NodeTestContext<Node, AddOns>
82where
83    Node: FullNodeComponents,
84    AddOns: RethRpcAddOns<Node>,
85{
86    /// The core structure representing the full node.
87    pub inner: FullNode<Node, AddOns>,
88    /// Context for testing payload-related features.
89    pub payload: PayloadTestContext<<Node::Types as NodeTypes>::Payload>,
90    /// Context for testing network functionalities.
91    pub network: NetworkTestContext<Node::Network>,
92    /// Context for testing RPC features.
93    pub rpc: RpcTestContext<Node, AddOns::EthApi>,
94    /// Context for sending payloads and forkchoice updates to the engine independent of the
95    /// Engine API version.
96    pub engine: EngineTestContext<<Node::Types as NodeTypes>::Payload>,
97    /// Canonical state events.
98    pub canonical_stream: CanonStateNotificationStream<PrimitivesTy<Node::Types>>,
99    /// Which blocks [`Self::import_payload`] marks as safe and finalized.
100    finality: Finality,
101    /// What [`Self::stop`] needs to stop the node and launch it again, only set for restartable
102    /// nodes of [`E2ETestSetupBuilder`](crate::E2ETestSetupBuilder).
103    pub(crate) restart: Option<NodeRestart<Self>>,
104    /// Resolves when the consensus engine of the node exits, see [`Self::take_exit_future`].
105    ///
106    /// The future is not `Sync`, but the context must be, so that helpers borrowing it return
107    /// `Send` futures.
108    pub(crate) exit_future: Mutex<Option<NodeExitFuture>>,
109}
110
111impl<Node, Payload, AddOns> NodeTestContext<Node, AddOns>
112where
113    Payload: PayloadTypes,
114    Node: FullNodeComponents,
115    Node::Types: NodeTypes<ChainSpec: EthereumHardforks, Payload = Payload>,
116    Node::Network: PeersHandleProvider,
117    AddOns: RethRpcAddOns<Node>,
118{
119    /// Creates a new test node
120    ///
121    /// Payload timestamps start after the default Cancun timestamp of the payload context, or after
122    /// the timestamp of the latest block if it is newer, e.g. for a custom genesis or an imported
123    /// chain.
124    pub async fn new(
125        node: FullNode<Node, AddOns>,
126        attributes_generator: impl Fn(u64) -> Payload::PayloadAttributes + Send + Sync + 'static,
127    ) -> eyre::Result<Self> {
128        let mut payload =
129            PayloadTestContext::new(node.payload_builder_handle.clone(), attributes_generator)
130                .await?;
131        if let Some(latest) =
132            node.provider.sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)?
133        {
134            payload.timestamp = payload.timestamp.max(latest.timestamp());
135        }
136        Ok(Self {
137            inner: node.clone(),
138            payload,
139            network: NetworkTestContext::new(node.network.clone()),
140            rpc: RpcTestContext { inner: node.add_ons_handle.rpc_registry },
141            engine: EngineTestContext::new(node.add_ons_handle.beacon_engine_handle),
142            canonical_stream: node.provider.canonical_state_stream(),
143            finality: Finality::default(),
144            restart: None,
145            exit_future: Mutex::new(None),
146        })
147    }
148
149    /// Establish a connection to the node
150    pub async fn connect(&mut self, node: &mut Self) {
151        self.network.add_peer(node.network.record()).await;
152        node.network.next_session_established().await;
153        self.network.next_session_established().await;
154    }
155
156    /// Advances the chain `length` blocks.
157    ///
158    /// Returns the added chain as a Vec of block hashes.
159    pub async fn advance(
160        &mut self,
161        length: u64,
162        tx_generator: impl Fn(u64) -> Pin<Box<dyn Future<Output = Bytes>>>,
163    ) -> eyre::Result<Vec<Payload::BuiltPayload>>
164    where
165        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
166            + EthTransactions
167            + TraceExt,
168    {
169        let mut chain = Vec::with_capacity(length as usize);
170        for i in 0..length {
171            let (_, payload) = self.inject_and_advance(tx_generator(i).await).await?;
172            chain.push(payload);
173        }
174        Ok(chain)
175    }
176
177    /// Injects the raw transaction into the pool, advances the node one block and waits until the
178    /// block is committed, returning the transaction hash and the built payload.
179    ///
180    /// Returns an error if the transaction is not included in the block, see
181    /// [`Self::assert_new_block`].
182    pub fn inject_and_advance(
183        &mut self,
184        raw_tx: Bytes,
185    ) -> BoxFuture<'_, eyre::Result<(B256, Payload::BuiltPayload)>>
186    where
187        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
188            + EthTransactions
189            + TraceExt,
190    {
191        Box::pin(async move {
192            let tx_hash = self.rpc.inject_tx(raw_tx).await?;
193            let payload = self.advance_block().await?;
194            self.assert_new_block(tx_hash, payload.block().hash(), payload.block().number())
195                .await?;
196            Ok((tx_hash, payload))
197        })
198    }
199
200    /// Returns the current forkchoice state of the node.
201    pub fn current_forkchoice_state(&self) -> eyre::Result<ForkchoiceState> {
202        let latest_header = self
203            .inner
204            .provider
205            .sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)?
206            .ok_or_else(|| eyre!("latest block not found"))?;
207
208        if latest_header.number() == 0 {
209            return Ok(ForkchoiceState::same_hash(latest_header.hash()));
210        }
211
212        Ok(ForkchoiceState {
213            head_block_hash: latest_header.hash(),
214            safe_block_hash: self
215                .inner
216                .provider
217                .sealed_header_by_number_or_tag(BlockNumberOrTag::Safe)?
218                .ok_or_else(|| eyre!("safe block not found"))?
219                .hash(),
220            finalized_block_hash: self
221                .inner
222                .provider
223                .sealed_header_by_number_or_tag(BlockNumberOrTag::Finalized)?
224                .ok_or_else(|| eyre!("finalized block not found"))?
225                .hash(),
226        })
227    }
228
229    /// Sets the timestamp of the next payload built by [`Self::new_payload`] and the methods
230    /// built on it, such as [`Self::advance_block`].
231    ///
232    /// Later payloads continue from this timestamp, one second apart. The attributes generator is
233    /// called with the new timestamp, so fork-dependent attributes follow it.
234    ///
235    /// Returns an error if the timestamp is not greater than the timestamp of the current latest
236    /// block, which is the parent of the next payload.
237    pub fn set_next_payload_timestamp(&mut self, timestamp: u64) -> eyre::Result<()> {
238        let latest = self
239            .inner
240            .provider
241            .sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)?
242            .ok_or_else(|| eyre!("latest block not found"))?;
243        ensure!(
244            timestamp > latest.timestamp(),
245            "next payload timestamp {timestamp} must be greater than the latest block timestamp {}",
246            latest.timestamp()
247        );
248        // The payload context increments its timestamp before generating the next attributes.
249        self.payload.timestamp = timestamp - 1;
250        Ok(())
251    }
252
253    /// Builds a payload on top of the latest block and returns it without submitting it to the
254    /// engine.
255    ///
256    /// Sends a forkchoice update with the next payload attributes, see
257    /// [`Self::set_next_payload_timestamp`], and resolves the payload job it starts with
258    /// [`PayloadKind::WaitForPending`], which waits for the build in progress instead of returning
259    /// an empty payload. The payload can still contain no transactions, e.g. if the pool had none
260    /// when the build started. Payload jobs started by others, e.g. by forkchoice updates with
261    /// payload attributes sent by the test, do not affect this.
262    ///
263    /// Returns an error if the forkchoice update fails or starts no payload job, e.g. because the
264    /// engine is syncing, or if the payload job fails to build a payload.
265    ///
266    /// [`PayloadKind::WaitForPending`]: reth_payload_primitives::PayloadKind::WaitForPending
267    pub async fn new_payload(&mut self) -> eyre::Result<Payload::BuiltPayload> {
268        self.build_payload(self.current_forkchoice_state()?).await
269    }
270
271    /// Builds a payload on the head of `state` with the next payload attributes and returns it
272    /// without submitting it.
273    ///
274    /// Returns an error if the engine does not report the forkchoice update valid, see
275    /// [`Self::build_payload_on`].
276    async fn build_payload(
277        &mut self,
278        state: ForkchoiceState,
279    ) -> eyre::Result<Payload::BuiltPayload> {
280        let attributes = self.payload.next_attributes();
281        let updated = self.send_forkchoice_updated(state, Some(attributes)).await?;
282        let payload_id = updated.payload_id.ok_or_else(|| {
283            eyre!(
284                "forkchoice update to block {} with payload attributes started no payload job: {}",
285                state.head_block_hash,
286                updated.payload_status.status
287            )
288        })?;
289        self.payload.resolve_payload(payload_id).await
290    }
291
292    /// Triggers payload building job and submits it to the engine.
293    ///
294    /// Returns an error if the engine reports the payload invalid, see [`Self::submit_payload`].
295    pub async fn build_and_submit_payload(&mut self) -> eyre::Result<Payload::BuiltPayload> {
296        let payload = self.new_payload().await?;
297
298        self.submit_payload(payload.clone()).await?;
299
300        Ok(payload)
301    }
302
303    /// Advances the node forward one block by building a payload and importing it with
304    /// [`Self::import_payload`].
305    ///
306    /// The block becomes the head, the safe and finalized blocks follow the [`Finality`] policy of
307    /// the context.
308    pub async fn advance_block(&mut self) -> eyre::Result<Payload::BuiltPayload> {
309        let payload = self.new_payload().await?;
310
311        self.import_payload(payload.clone()).await?;
312
313        Ok(payload)
314    }
315
316    /// Advances the node forward one block like [`Self::advance_block`] and waits until the
317    /// transaction pool processed the new block, see [`Self::wait_for_pool_head`].
318    ///
319    /// This only waits for the canonical state update of the pool itself, which removes the
320    /// transactions mined in the block and updates sender nonces and pending fees. Nodes that run
321    /// additional pool maintenance tasks on new blocks have to wait for the expected contents of
322    /// the pool instead, e.g. with [`Self::wait_for_pool_removal`] for the mined transactions.
323    pub async fn advance_block_synced(&mut self) -> eyre::Result<Payload::BuiltPayload> {
324        let payload = self.advance_block().await?;
325        self.wait_for_pool_head(payload.block().hash()).await?;
326        Ok(payload)
327    }
328
329    /// Advances the chain `length` blocks, see [`Self::advance_block`].
330    ///
331    /// Unlike [`Self::advance`], this does not inject transactions, so the blocks include the
332    /// pending transactions of the pool, if any.
333    ///
334    /// Returns the built payloads.
335    pub async fn advance_blocks(
336        &mut self,
337        length: u64,
338    ) -> eyre::Result<Vec<Payload::BuiltPayload>> {
339        let mut chain = Vec::with_capacity(length as usize);
340        for _ in 0..length {
341            chain.push(self.advance_block().await?);
342        }
343        Ok(chain)
344    }
345
346    /// Builds a payload on the block with the given hash with the next payload attributes and
347    /// returns it without submitting it, like [`Self::new_payload`] does for the head.
348    ///
349    /// The payload job is started with a forkchoice update to `parent` that keeps the safe and
350    /// finalized blocks of the node. Its effect depends on `parent`:
351    /// - The head, or a canonical ancestor of it that is not below the finalized block: the head
352    ///   stays, the engine builds on `parent` without changing the canonical chain. The exception
353    ///   is a tree config with both `always_process_payload_attributes_on_canonical_head` and
354    ///   `unwind_canonical_header`, under which the engine moves the head back to an ancestor.
355    /// - A block on a side chain: the engine first makes the side chain canonical, so `parent`
356    ///   becomes the head, reorging the chain.
357    /// - A canonical block below the finalized block, or a side chain block that does not descend
358    ///   from the finalized block: the engine rejects the update and this returns an error, the
359    ///   head stays. Blocks imported under [`Finality::Head`] are finalized, import them under
360    ///   [`Finality::Keep`] or [`Finality::Lag`] to build on them later, see
361    ///   [`Self::set_finality`].
362    /// - A block the node does not know: the engine starts no payload job and requests the block
363    ///   from its peers, and this returns an error.
364    ///
365    /// The payload includes transactions of the pool, which tracks the head: transactions mined in
366    /// the blocks above `parent` are not in the pool anymore, so the payload does not include them,
367    /// and pool transactions that are invalid on `parent` are skipped.
368    pub async fn build_payload_on(&mut self, parent: B256) -> eyre::Result<Payload::BuiltPayload> {
369        self.build_payload(ForkchoiceState {
370            head_block_hash: parent,
371            ..self.current_forkchoice_state()?
372        })
373        .await
374    }
375
376    /// Builds a block on the block with the given hash and imports it with
377    /// [`Self::import_payload`], so it becomes the head.
378    ///
379    /// If `parent` is not the head, this reorgs the chain, so `parent` must be the finalized block
380    /// or descend from it, see [`Self::build_payload_on`]. The safe and finalized blocks follow
381    /// the [`Finality`] policy of the context. The transaction pool processes the reorg in the
382    /// background, see [`Self::reorg_to`].
383    pub async fn advance_block_on(&mut self, parent: B256) -> eyre::Result<Payload::BuiltPayload> {
384        let payload = self.build_payload_on(parent).await?;
385
386        self.import_payload(payload.clone()).await?;
387
388        Ok(payload)
389    }
390
391    /// Builds a chain of `length` blocks on the block with the given hash and makes its tip the
392    /// head, see [`Self::advance_block_on`].
393    ///
394    /// The first block is built on `parent` and each further block on the previous one, so this
395    /// reorgs the chain if `parent` is not the head. Returns the built payloads, does nothing for
396    /// a length of 0.
397    pub async fn advance_fork(
398        &mut self,
399        parent: B256,
400        length: u64,
401    ) -> eyre::Result<Vec<Payload::BuiltPayload>> {
402        let mut chain = Vec::with_capacity(length as usize);
403        let mut parent = parent;
404        for _ in 0..length {
405            let payload = self.advance_block_on(parent).await?;
406            parent = payload.block().hash();
407            chain.push(payload);
408        }
409        Ok(chain)
410    }
411
412    /// Advances the chain one block at a time until the transaction with the given hash is
413    /// included in a canonical block, returning its receipt.
414    ///
415    /// Returns the receipt without advancing if the transaction is already included. Returns an
416    /// error if the transaction is not included within [`WAIT_TIMEOUT`].
417    pub fn advance_until_receipt(
418        &mut self,
419        hash: B256,
420    ) -> BoxFuture<'_, eyre::Result<RpcReceipt<<AddOns::EthApi as EthApiTypes>::NetworkTypes>>>
421    where
422        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
423            + EthTransactions
424            + TraceExt
425            + LoadReceipt
426            + 'static,
427    {
428        Box::pin(async move {
429            let wait = async {
430                loop {
431                    if let Some(receipt) = self.rpc.transaction_receipt(hash).await? {
432                        return Ok(receipt)
433                    }
434                    self.advance_block().await?;
435                }
436            };
437            tokio::time::timeout(WAIT_TIMEOUT, wait)
438                .await
439                .map_err(|_| eyre!("timed out waiting for the receipt of transaction {hash}"))?
440        })
441    }
442
443    /// Advances the chain one block at a time until the transaction pool has no pending
444    /// transactions left, returning the built payloads.
445    ///
446    /// Pending transactions are the ones the pool considers ready for the next block, which the
447    /// payload builder picks from. This does not wait for the other transactions of the pool,
448    /// which stay in it: queued transactions, e.g. behind a nonce gap, and transactions whose fee
449    /// cap is below the base fee or blob fee of the next block. Transactions that become pending
450    /// once a built block is processed, e.g. because it lowered the base fee, are included in the
451    /// following blocks. The pool decides when to stop, not the content of the blocks, since
452    /// payload builders can add transactions of their own that are not pool transactions.
453    ///
454    /// The pool processes new blocks in the background, so this waits until it processed the
455    /// current head before it looks at the pool, see [`Self::wait_for_pool_head`], and advances
456    /// with [`Self::advance_block_synced`]. No block is built once the pool has no pending
457    /// transactions: the last payload is the block after which none were left, not an extra empty
458    /// block, and no payload is returned if the pool has none to begin with.
459    ///
460    /// Returns an error if the pool does not process the current head within [`WAIT_TIMEOUT`],
461    /// e.g. because it was synced by backfill, or, listing them, if pending transactions are left
462    /// after [`WAIT_TIMEOUT`], e.g. because the payload builder skips them.
463    pub async fn advance_until_pool_drained(&mut self) -> eyre::Result<Vec<Payload::BuiltPayload>> {
464        let head = self
465            .inner
466            .provider
467            .sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)?
468            .ok_or_else(|| eyre!("latest block not found"))?;
469        self.wait_for_pool_head(head.hash()).await?;
470
471        let wait = async {
472            let mut chain = Vec::new();
473            while self.inner.pool.pool_size().pending > 0 {
474                chain.push(self.advance_block_synced().await?);
475            }
476            Ok(chain)
477        };
478        tokio::time::timeout(WAIT_TIMEOUT, wait).await.map_err(|_| {
479            eyre!(
480                "timed out advancing the chain until the transaction pool has no pending \
481                 transactions, {}",
482                describe_pending_transactions(&self.inner.pool)
483            )
484        })?
485    }
486
487    /// Drives `fut` to completion while advancing the chain one block every [`POLL_INTERVAL`],
488    /// returning its output.
489    ///
490    /// This is useful for futures that only complete once their transaction is mined, e.g. an
491    /// `eth_sendRawTransactionSync` request. `fut` is also polled while a block is built, but the
492    /// block is always built to completion, so the chain may advance one more block after `fut`
493    /// completed.
494    ///
495    /// Returns an error if `fut` does not complete within [`WAIT_TIMEOUT`].
496    pub async fn advance_while<F: Future>(&mut self, fut: F) -> eyre::Result<F::Output> {
497        let wait = async {
498            let mut fut = pin!(fut);
499            loop {
500                if let Result::Ok(output) = tokio::time::timeout(POLL_INTERVAL, fut.as_mut()).await
501                {
502                    return Ok(output)
503                }
504                // The engine can still import a cancelled block and make it canonical after this
505                // returns, so finish it even if `fut` completes first.
506                let mut advance = pin!(self.advance_block());
507                match select(advance.as_mut(), fut.as_mut()).await {
508                    Either::Left((payload, _)) => {
509                        payload?;
510                    }
511                    Either::Right((output, _)) => {
512                        advance.await?;
513                        return Ok(output)
514                    }
515                }
516            }
517        };
518        tokio::time::timeout(WAIT_TIMEOUT, wait)
519            .await
520            .map_err(|_| eyre!("timed out advancing the chain until the future completed"))?
521    }
522
523    /// Injects the raw transactions into the pool in the given order, advances the node one block
524    /// and returns the mined block with the receipts of the transactions.
525    ///
526    /// The block must include all given transactions, in any order, and no other transaction of the
527    /// pool, so a transaction that is dropped or that rides along fails instead of going unnoticed.
528    /// An empty `txs` mines a block without pool transactions. Transactions that the payload
529    /// builder adds by itself, e.g. from the payload attributes, are not pool transactions and may
530    /// be included. Use [`Self::mine_including`] if other pool transactions may be included too.
531    ///
532    /// The receipts are in the order of `txs` and can report reverted transactions, see
533    /// [`MinedBlock::ensure_success`]. The committed chain segment is taken from
534    /// [`Self::canonical_stream`], whose notifications up to the one of the mined block are
535    /// consumed, so the stream does not need to be aligned with the mined blocks before.
536    ///
537    /// The block is imported with [`Self::import_payload`], so the transaction pool processes it in
538    /// the background, see [`Self::advance_block_synced`].
539    ///
540    /// Returns an error if the pool rejects a transaction, or, without importing the block, if the
541    /// block does not include the expected transactions, listing the missing and the unexpected
542    /// ones.
543    ///
544    /// Use [`Self::mine_pooled`] for transactions that are already in the pool, e.g. sent through
545    /// [`Self::rpc_provider_with_wallet`] or added with the pool API, and [`Self::mine_signed`]
546    /// for signed transactions that should all succeed.
547    ///
548    /// The helpers that drive the eth API return boxed futures: with the new trait solver,
549    /// awaiting the unboxed future makes every caller borrow-check its whole state machine, which
550    /// dominates compile time for nodes with large eth API impls.
551    pub fn mine(
552        &mut self,
553        txs: impl IntoIterator<Item = Bytes>,
554    ) -> MinedBlockFuture<
555        '_,
556        Payload::BuiltPayload,
557        RpcReceipt<<AddOns::EthApi as EthApiTypes>::NetworkTypes>,
558    >
559    where
560        // Implied by `NodeTypes`, but not derived by the compiler through the `Payload` parameter.
561        Payload::BuiltPayload: BuiltPayload<Primitives = PrimitivesTy<Node::Types>>,
562        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
563            + EthTransactions
564            + TraceExt
565            + LoadReceipt
566            + 'static,
567    {
568        let txs: Vec<Bytes> = txs.into_iter().collect();
569        Box::pin(async move { self.mine_txs(txs, true).await })
570    }
571
572    /// Mines the raw transactions like [`Self::mine`], but the block may also include other
573    /// transactions of the pool, e.g. transactions that peers gossiped to the node.
574    ///
575    /// Only the receipts of the given transactions are returned.
576    pub fn mine_including(
577        &mut self,
578        txs: impl IntoIterator<Item = Bytes>,
579    ) -> MinedBlockFuture<
580        '_,
581        Payload::BuiltPayload,
582        RpcReceipt<<AddOns::EthApi as EthApiTypes>::NetworkTypes>,
583    >
584    where
585        Payload::BuiltPayload: BuiltPayload<Primitives = PrimitivesTy<Node::Types>>,
586        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
587            + EthTransactions
588            + TraceExt
589            + LoadReceipt
590            + 'static,
591    {
592        let txs: Vec<Bytes> = txs.into_iter().collect();
593        Box::pin(async move { self.mine_txs(txs, false).await })
594    }
595
596    /// Mines the signed transactions like [`Self::mine`] and returns an error if one of them
597    /// reverted, see [`MinedBlock::ensure_success`].
598    ///
599    /// The transactions are EIP-2718 encoded before they are sent to the pool, so signed
600    /// transactions or envelopes, e.g. of a custom transaction type, can be passed without
601    /// encoding them first.
602    pub fn mine_signed<T: Encodable2718>(
603        &mut self,
604        txs: impl IntoIterator<Item = T>,
605    ) -> MinedBlockFuture<
606        '_,
607        Payload::BuiltPayload,
608        RpcReceipt<<AddOns::EthApi as EthApiTypes>::NetworkTypes>,
609    >
610    where
611        Payload::BuiltPayload: BuiltPayload<Primitives = PrimitivesTy<Node::Types>>,
612        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
613            + EthTransactions
614            + TraceExt
615            + LoadReceipt
616            + 'static,
617    {
618        // Encoded up front, so the returned future does not hold `T`.
619        let txs: Vec<Bytes> = txs.into_iter().map(|tx| tx.encoded_2718().into()).collect();
620        Box::pin(async move { self.mine(txs).await?.ensure_success() })
621    }
622
623    /// Mines the transactions with the given hashes like [`Self::mine`], for transactions that are
624    /// already in the pool, e.g. sent through [`Self::rpc_provider_with_wallet`] or added with the
625    /// pool API.
626    ///
627    /// The receipts are in the order of `hashes`.
628    ///
629    /// Before building a block, returns an error naming the first hash that is not in the pool,
630    /// e.g. of a transaction that was already mined or replaced. Transactions in any sub-pool pass
631    /// this check, not only pending ones, since the pool sorts transactions for its own view of the
632    /// next block, which can lag behind the block that is built. A transaction the block can not
633    /// include, e.g. one queued behind a nonce gap, is reported as missing like with
634    /// [`Self::mine`].
635    pub fn mine_pooled(
636        &mut self,
637        hashes: impl IntoIterator<Item = B256>,
638    ) -> MinedBlockFuture<
639        '_,
640        Payload::BuiltPayload,
641        RpcReceipt<<AddOns::EthApi as EthApiTypes>::NetworkTypes>,
642    >
643    where
644        Payload::BuiltPayload: BuiltPayload<Primitives = PrimitivesTy<Node::Types>>,
645        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
646            + EthTransactions
647            + TraceExt
648            + LoadReceipt
649            + 'static,
650    {
651        let hashes: Vec<B256> = hashes.into_iter().collect();
652        Box::pin(async move { self.mine_pooled_txs(hashes, true).await })
653    }
654
655    /// Mines the raw transactions, see [`Self::mine`]. Unless `exclusive` is set, the block may
656    /// include other pool transactions.
657    async fn mine_txs(
658        &mut self,
659        txs: impl IntoIterator<Item = Bytes>,
660        exclusive: bool,
661    ) -> eyre::Result<
662        MinedBlock<
663            Payload::BuiltPayload,
664            RpcReceipt<<AddOns::EthApi as EthApiTypes>::NetworkTypes>,
665        >,
666    >
667    where
668        Payload::BuiltPayload: BuiltPayload<Primitives = PrimitivesTy<Node::Types>>,
669        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
670            + EthTransactions
671            + TraceExt
672            + LoadReceipt
673            + 'static,
674    {
675        let mut hashes = Vec::new();
676        for (index, raw_tx) in txs.into_iter().enumerate() {
677            let hash = self
678                .rpc
679                .inject_tx(raw_tx)
680                .await
681                .wrap_err_with(|| format!("failed to inject transaction {index}"))?;
682            hashes.push(hash);
683        }
684
685        self.mine_pooled_txs(hashes, exclusive).await
686    }
687
688    /// Mines the pool transactions with the given hashes, see [`Self::mine_pooled`]. Unless
689    /// `exclusive` is set, the block may include other pool transactions.
690    async fn mine_pooled_txs(
691        &mut self,
692        hashes: Vec<B256>,
693        exclusive: bool,
694    ) -> eyre::Result<
695        MinedBlock<
696            Payload::BuiltPayload,
697            RpcReceipt<<AddOns::EthApi as EthApiTypes>::NetworkTypes>,
698        >,
699    >
700    where
701        Payload::BuiltPayload: BuiltPayload<Primitives = PrimitivesTy<Node::Types>>,
702        AddOns::EthApi: EthApiSpec<Provider: BlockReader<Block = BlockTy<Node::Types>>>
703            + EthTransactions
704            + TraceExt
705            + LoadReceipt
706            + 'static,
707    {
708        for hash in &hashes {
709            ensure!(self.inner.pool.contains(hash), "transaction {hash} is not in the pool");
710        }
711
712        let payload = self.new_payload().await?;
713        let (number, block_hash) = (payload.block().number(), payload.block().hash());
714        let body = payload.block().body();
715        let missing: Vec<_> =
716            hashes.iter().filter(|hash| !body.contains_transaction(hash)).collect();
717        // The pool only removes the transactions of a block once it is canonical, so all pool
718        // transactions of the block are still in the pool before the import.
719        let unexpected: Vec<_> = body
720            .transaction_hashes_iter()
721            .filter(|hash| exclusive && !hashes.contains(hash) && self.inner.pool.contains(hash))
722            .collect();
723        ensure!(
724            missing.is_empty() && unexpected.is_empty(),
725            "block {number} does not include the expected transactions: missing {missing:?}, \
726             unexpected pool transactions {unexpected:?}"
727        );
728
729        self.import_payload(payload.clone()).await?;
730        let chain =
731            wait_for_committed_chain(&mut self.canonical_stream, block_hash, number).await?;
732
733        let mut receipts = Vec::with_capacity(hashes.len());
734        for hash in hashes {
735            let receipt = self.rpc.transaction_receipt(hash).await?.ok_or_else(|| {
736                eyre!("receipt of transaction {hash} in block {number} not found")
737            })?;
738            receipts.push(receipt);
739        }
740
741        Ok(MinedBlock { payload, receipts, chain })
742    }
743
744    /// Waits for block to be available on node.
745    ///
746    /// Returns an error if the block is not available within [`WAIT_TIMEOUT`].
747    pub async fn wait_block(
748        &self,
749        number: BlockNumber,
750        expected_block_hash: BlockHash,
751        wait_finish_checkpoint: bool,
752    ) -> eyre::Result<()> {
753        let provider = &self.inner.provider;
754        poll_until(format!("block {number}"), move || async move {
755            if wait_finish_checkpoint &&
756                provider
757                    .get_stage_checkpoint(StageId::Finish)?
758                    .is_none_or(|checkpoint| checkpoint.block_number < number)
759            {
760                return Ok(None)
761            }
762            let Some(header) = provider.header_by_number(number)? else {
763                ensure!(
764                    !wait_finish_checkpoint,
765                    "Finish checkpoint matches, but could not fetch block {number}"
766                );
767                return Ok(None)
768            };
769            let hash = header.hash_slow();
770            ensure!(
771                hash == expected_block_hash,
772                "block {number} is {hash}, expected {expected_block_hash}"
773            );
774            Ok(Some(()))
775        })
776        .await
777    }
778
779    /// Waits for the node to unwind to the given block number.
780    ///
781    /// Returns an error if the node does not unwind within [`WAIT_TIMEOUT`].
782    pub async fn wait_unwind(&self, number: BlockNumber) -> eyre::Result<()> {
783        let provider = &self.inner.provider;
784        poll_until(format!("unwind to block {number}"), move || async move {
785            let checkpoint = provider.get_stage_checkpoint(StageId::Headers)?;
786            Ok(checkpoint.is_some_and(|checkpoint| checkpoint.block_number == number).then_some(()))
787        })
788        .await
789    }
790
791    /// Waits until `condition` holds for the transaction pool of the node.
792    ///
793    /// Returns an error if the condition does not hold within [`WAIT_TIMEOUT`].
794    pub async fn wait_for_pool(
795        &self,
796        mut condition: impl FnMut(&Node::Pool) -> bool,
797    ) -> eyre::Result<()> {
798        let pool = &self.inner.pool;
799        poll_until("transaction pool condition", move || {
800            let ready = condition(pool);
801            async move { Ok(ready.then_some(())) }
802        })
803        .await
804    }
805
806    /// Waits until the transaction pool of the node processed the canonical state update that made
807    /// the block with the given hash its head.
808    ///
809    /// The pool maintenance task processes new heads in the background, so right after e.g. a
810    /// forkchoice update the pool can still contain the transactions mined in the new block or use
811    /// outdated sender nonces and pending fees. The pool updates its last seen block together with
812    /// these, so they are up to date once this returns. Exceptions are reorgs, after which the
813    /// maintenance task re-injects the transactions of the old chain only afterwards, so wait for
814    /// them with [`Self::wait_for_pooled`], and commits deeper than the maximum update depth of the
815    /// maintenance task, e.g. after a long sync, which only update the last seen block.
816    ///
817    /// This only covers the canonical state update of the pool itself. Nodes can run additional
818    /// pool maintenance tasks on new blocks, e.g. to remove mined transactions from a separate
819    /// sub-pool or to evict transactions by their own rules, and the pool can report the new head
820    /// before these processed the block. Tests of such nodes have to wait for the expected
821    /// contents of the pool instead, with [`Self::wait_for_pool_removal`],
822    /// [`Self::wait_for_pooled`] or [`Self::wait_for_pool`].
823    ///
824    /// Returns an error if the pool does not process the block within [`WAIT_TIMEOUT`], e.g.
825    /// because the block is not the canonical head, the pool already processed a newer head, or
826    /// the block was synced by backfill, which does not notify the pool.
827    pub async fn wait_for_pool_head(&self, hash: B256) -> eyre::Result<()> {
828        let pool = &self.inner.pool;
829        poll_until(format!("transaction pool to process block {hash}"), move || {
830            let ready = pool.block_info().last_seen_block_hash == hash;
831            async move { Ok(ready.then_some(())) }
832        })
833        .await
834    }
835
836    /// Waits until all transactions with the given hashes are in the transaction pool of the
837    /// node.
838    ///
839    /// The transactions must be in the pool at the same time, so this keeps waiting if one of them
840    /// leaves the pool again, e.g. because it is mined or replaced, before the others entered.
841    ///
842    /// Returns an error listing the transactions that are still missing if they are not all in
843    /// the pool within [`WAIT_TIMEOUT`].
844    pub async fn wait_for_pooled(
845        &self,
846        hashes: impl IntoIterator<Item = B256>,
847    ) -> eyre::Result<()> {
848        wait_for_pool_transactions(&self.inner.pool, hashes, true).await
849    }
850
851    /// Waits until none of the transactions with the given hashes is in the transaction pool of
852    /// the node, regardless of why they left, e.g. because they were mined, replaced or evicted.
853    ///
854    /// Unlike [`Self::wait_for_pool_head`], this does not depend on which task removes the
855    /// transactions, so it also covers nodes with additional pool maintenance tasks. It returns
856    /// right away for transactions that never entered the pool, so wait for transactions that
857    /// arrive in the background, e.g. from peers, with [`Self::wait_for_pooled`] first.
858    ///
859    /// Returns an error listing the transactions that are still pooled if they do not all leave
860    /// the pool within [`WAIT_TIMEOUT`].
861    pub async fn wait_for_pool_removal(
862        &self,
863        hashes: impl IntoIterator<Item = B256>,
864    ) -> eyre::Result<()> {
865        wait_for_pool_transactions(&self.inner.pool, hashes, false).await
866    }
867
868    /// Waits until the node has persisted at least the block with the given number to disk.
869    ///
870    /// The engine keeps the most recent blocks in memory, so tests that inspect the database
871    /// directly, e.g. via [`assert_trie_consistency`], must first advance the chain far enough
872    /// and wait for the persistence service to catch up. This checks the `Finish` stage checkpoint
873    /// of the database, which is committed together with the saved blocks, so all blocks up to
874    /// `number` are readable from disk once this returns. Their state and trie are committed in
875    /// the same transaction, unless the engine keeps the state of the most recently persisted
876    /// blocks masked by its in-memory suffix (`TreeConfig::num_state_masking_blocks`, disabled by a
877    /// persistence threshold of 0), in which case the persisted state can lag behind `number`. The
878    /// pruner runs after the save in a separate commit, so pruning of the saved blocks can still be
879    /// pending, wait for it with [`Self::wait_for_prune_checkpoint`].
880    ///
881    /// Unlike [`Self::wait_block`], this does not check the block hash, so it also returns if the
882    /// persisted block at `number` is not canonical anymore.
883    ///
884    /// Returns an error if the block is not persisted within [`WAIT_TIMEOUT`].
885    ///
886    /// [`assert_trie_consistency`]: crate::trie::assert_trie_consistency
887    pub async fn wait_for_persisted_block(&self, number: BlockNumber) -> eyre::Result<()> {
888        let provider = &self.inner.provider;
889        poll_until(format!("block {number} to be persisted"), move || async move {
890            let persisted = provider.database_provider_ro()?.best_block_number()?;
891            Ok((persisted >= number).then_some(()))
892        })
893        .await
894    }
895
896    /// Waits until the pruner has pruned `segment` of the node up to at least the block with the
897    /// given number.
898    ///
899    /// The persistence service acknowledges a save before it runs the pruner for the new database
900    /// tip in a separate commit, so a block being persisted, e.g. awaited with
901    /// [`Self::wait_for_persisted_block`], does not mean the pruner has run for it yet. The
902    /// pruner saves the checkpoint of a segment in the same provider commit as the pruned data,
903    /// which commits static files and `RocksDB` before the database transaction, so the data of
904    /// `segment` up to `block` is pruned once this returns.
905    ///
906    /// Returns an error if the segment is not pruned within [`WAIT_TIMEOUT`], e.g. because the
907    /// node is not configured to prune it or `block` is within its retention window.
908    pub async fn wait_for_prune_checkpoint(
909        &self,
910        segment: PruneSegment,
911        block: BlockNumber,
912    ) -> eyre::Result<()> {
913        let provider = &self.inner.provider;
914        poll_until(format!("{segment} to be pruned up to block {block}"), move || async move {
915            let checkpoint = provider.get_prune_checkpoint(segment)?;
916            Ok(checkpoint
917                .and_then(|checkpoint| checkpoint.block_number)
918                .is_some_and(|pruned| pruned >= block)
919                .then_some(()))
920        })
921        .await
922    }
923
924    /// Waits until the block with the given hash is the latest block of the node, i.e. its
925    /// canonical head.
926    ///
927    /// This only observes the node and sends nothing to the engine, so the block has to become the
928    /// head otherwise, e.g. through a forkchoice update the test sent itself, which may start a
929    /// backfill sync to the block. [`Self::sync_to`] instead keeps sending forkchoice updates that
930    /// make the block the head, safe and finalized block until it is the head, and then waits
931    /// briefly for the transaction pool, which this does not, see [`Self::wait_for_pool_head`].
932    /// [`Self::wait_block`] waits for the header at a block number, which a canonical block that is
933    /// not the head also satisfies, and after a backfill sync it can return before the engine made
934    /// the synced block its head, even when it waits for the `Finish` checkpoint of the pipeline.
935    ///
936    /// Returns an error naming the head of the node if the block is not the head within
937    /// [`WAIT_TIMEOUT`], e.g. because the node made another block its head.
938    pub async fn wait_for_head(&self, hash: BlockHash) -> eyre::Result<()> {
939        let provider = &self.inner.provider;
940        let latest = || provider.sealed_header_by_number_or_tag(BlockNumberOrTag::Latest);
941        let wait = async {
942            while latest()?.is_none_or(|head| head.hash() != hash) {
943                tokio::time::sleep(POLL_INTERVAL).await;
944            }
945            Ok(())
946        };
947        tokio::time::timeout(WAIT_TIMEOUT, wait).await.map_err(|_| {
948            let head = latest().ok().flatten().map_or_else(
949                || "unknown".to_string(),
950                |head| format!("block {} ({})", head.number(), head.hash()),
951            );
952            eyre!("timed out waiting for block {hash} to become the head, the head is {head}")
953        })?
954    }
955
956    /// Asserts that a new block has been added to the blockchain and the tx has been included in
957    /// the block, at any position.
958    ///
959    /// Does NOT work for pipeline since there's no stream notification! Returns an error if the
960    /// block is not committed within [`WAIT_TIMEOUT`].
961    pub async fn assert_new_block(
962        &mut self,
963        tip_tx_hash: B256,
964        block_hash: B256,
965        block_number: BlockNumber,
966    ) -> eyre::Result<()> {
967        let chain =
968            wait_for_committed_chain(&mut self.canonical_stream, block_hash, block_number).await?;
969        ensure!(
970            chain
971                .blocks()
972                .get(&block_number)
973                .is_some_and(|block| block.body().contains_transaction(&tip_tx_hash)),
974            "transaction {tip_tx_hash} is not included in block {block_number}"
975        );
976
977        // Wait for the block to commit and make sure the block hash we submitted via FCU engine
978        // api is the new latest block.
979        let provider = &self.inner.provider;
980        poll_until(format!("block {block_number} to be committed"), move || async move {
981            let Some(latest) = provider.block_by_number_or_tag(BlockNumberOrTag::Latest)? else {
982                return Ok(None)
983            };
984            if latest.header().number() != block_number {
985                return Ok(None)
986            }
987            let hash = latest.header().hash_slow();
988            ensure!(
989                hash == block_hash,
990                "latest block {block_number} is {hash}, expected {block_hash}"
991            );
992            Ok(Some(()))
993        })
994        .await
995    }
996
997    /// Gets block hash by number.
998    pub fn block_hash(&self, number: u64) -> BlockHash {
999        self.inner
1000            .provider
1001            .sealed_header_by_number_or_tag(BlockNumberOrTag::Number(number))
1002            .unwrap()
1003            .unwrap()
1004            .hash()
1005    }
1006
1007    /// Returns the snap attempt the node persisted.
1008    pub fn snap_attempt(&self) -> eyre::Result<SnapAttempt>
1009    where
1010        <Node::Provider as DatabaseProviderFactory>::Provider: MetadataProvider,
1011    {
1012        self.inner.provider.database_provider_ro()?.snap_attempt()?.ok_or_eyre("no snap attempt")
1013    }
1014
1015    /// Sends FCU and waits for the node to sync to the given block.
1016    ///
1017    /// The forkchoice update makes the block the head, safe and finalized block, regardless of the
1018    /// [`Finality`] policy of the context. Returns an error if the node does not sync within
1019    /// [`WAIT_TIMEOUT`].
1020    pub async fn sync_to(&self, block: BlockHash) -> eyre::Result<()> {
1021        self.sync_to_forkchoice(ForkchoiceState::same_hash(block)).await
1022    }
1023
1024    /// Sends the forkchoice update `state` and waits for the node to sync to its head, like
1025    /// [`Self::sync_to`], but with the safe and finalized blocks of `state`.
1026    ///
1027    /// A finalized block below the head, e.g. one the node does not know yet, lets a backfill
1028    /// anchor to it, as a snap sync anchors its pivot to the finalized block. Returns an error if
1029    /// the node does not sync within [`WAIT_TIMEOUT`].
1030    pub async fn sync_to_forkchoice(&self, state: ForkchoiceState) -> eyre::Result<()> {
1031        let block = state.head_block_hash;
1032        let sync = async {
1033            while self
1034                .inner
1035                .provider
1036                .sealed_header_by_id(BlockId::latest())?
1037                .is_none_or(|h| h.hash() != block)
1038            {
1039                tokio::time::sleep(Duration::from_millis(100)).await;
1040                self.inner
1041                    .add_ons_handle
1042                    .beacon_engine_handle
1043                    .fork_choice_updated(state, None)
1044                    .await?;
1045            }
1046            Ok(())
1047        };
1048        tokio::time::timeout(WAIT_TIMEOUT, sync)
1049            .await
1050            .map_err(|_| eyre!("timed out syncing to block {block}"))??;
1051
1052        // The transaction pool processes the canonical state update in the background, and
1053        // advancing the chain before it did can fail with e.g. "nonce too low" errors. Blocks
1054        // synced by backfill don't notify the pool, so wait for at most a second.
1055        let _ = tokio::time::timeout(Duration::from_secs(1), self.wait_for_pool_head(block)).await;
1056
1057        Ok(())
1058    }
1059
1060    /// Sends a forkchoice update message to the engine and returns its response.
1061    ///
1062    /// The update makes `new_head` the head and `current_head` the safe and finalized block,
1063    /// regardless of the [`Finality`] policy of the context.
1064    pub async fn update_forkchoice(
1065        &self,
1066        current_head: B256,
1067        new_head: B256,
1068    ) -> eyre::Result<ForkchoiceUpdated> {
1069        Ok(self
1070            .inner
1071            .add_ons_handle
1072            .beacon_engine_handle
1073            .fork_choice_updated(
1074                ForkchoiceState {
1075                    head_block_hash: new_head,
1076                    safe_block_hash: current_head,
1077                    finalized_block_hash: current_head,
1078                },
1079                None,
1080            )
1081            .await?)
1082    }
1083
1084    /// Sends forkchoice update to the engine api with a zero finalized hash and returns its
1085    /// response.
1086    pub async fn update_optimistic_forkchoice(
1087        &self,
1088        hash: B256,
1089    ) -> eyre::Result<ForkchoiceUpdated> {
1090        self.update_forkchoice(B256::ZERO, hash).await
1091    }
1092
1093    /// Submits a payload to the engine with `newPayload` and returns its block hash.
1094    ///
1095    /// This only inserts the block, it does not make it canonical, see [`Self::import_payload`].
1096    /// Returns the block hash if the engine reports the payload:
1097    /// - `VALID`: the engine executed the block on top of its parent, or knew it as valid already.
1098    /// - `SYNCING` or `ACCEPTED`: the engine did not execute the block, e.g. because its parent is
1099    ///   unknown, so the block is not known to be valid yet, see
1100    ///   [`Self::submit_payload_with_status`].
1101    ///
1102    /// Returns an error with the validation error and the latest valid hash if the engine reports
1103    /// the payload `INVALID`. Use [`Self::submit_payload_with_status`] to inspect the status
1104    /// instead, e.g. to test that a payload is rejected.
1105    pub async fn submit_payload(&self, payload: Payload::BuiltPayload) -> eyre::Result<B256> {
1106        let block_hash = payload.block().hash();
1107        let status = self.submit_payload_with_status(payload).await?;
1108        if let PayloadStatusEnum::Invalid { validation_error } = status.status {
1109            let latest_valid_hash = status
1110                .latest_valid_hash
1111                .map_or_else(|| "none".to_string(), |hash| hash.to_string());
1112            bail!(
1113                "payload {block_hash} is invalid (latest valid hash: {latest_valid_hash}): \
1114                 {validation_error}"
1115            )
1116        }
1117
1118        Ok(block_hash)
1119    }
1120
1121    /// Submits a payload to the engine with `newPayload` and returns the payload status the engine
1122    /// reports.
1123    ///
1124    /// Unlike [`Self::submit_payload`], this returns `INVALID` statuses instead of an error. This
1125    /// only inserts the block, it does not make it canonical. The engine reports:
1126    /// - `VALID` if it executed the block on top of its parent, or knew it as valid already. The
1127    ///   latest valid hash is the block hash.
1128    /// - `INVALID` if the block failed validation, or descends from a block that did. The latest
1129    ///   valid hash is the hash of the most recent valid ancestor, or `None` if the engine can't
1130    ///   determine it, e.g. because the block hash does not match the payload.
1131    /// - `SYNCING` if it did not execute the block because the parent of the block is unknown, in
1132    ///   which case it buffers the block and executes it once the parent is inserted, or because
1133    ///   the node is syncing.
1134    /// - `ACCEPTED` if it accepted the block without executing it. The reth engine tree does not
1135    ///   report this status.
1136    ///
1137    /// Returns an error if the engine fails to process the payload, e.g. because it shut down.
1138    pub async fn submit_payload_with_status(
1139        &self,
1140        payload: Payload::BuiltPayload,
1141    ) -> eyre::Result<PayloadStatus> {
1142        Ok(self.inner.add_ons_handle.beacon_engine_handle.new_payload(payload.into()).await?)
1143    }
1144
1145    /// Sets which blocks [`Self::import_payload`] and the block producing helpers built on it, such
1146    /// as [`Self::advance_block`], mark as safe and finalized, see [`Finality`].
1147    ///
1148    /// The policy applies from the next imported block on, the current safe and finalized blocks
1149    /// are not changed. Defaults to [`Finality::Head`].
1150    pub const fn set_finality(&mut self, finality: Finality) {
1151        self.finality = finality;
1152    }
1153
1154    /// Submits a payload to the engine and makes its block the canonical head, returning the block
1155    /// hash.
1156    ///
1157    /// The forkchoice update marks the safe and finalized blocks according to the [`Finality`]
1158    /// policy of the context, see [`Self::set_finality`]. Under the default [`Finality::Head`],
1159    /// the block also becomes the safe and finalized block, so only a forkchoice update that moves
1160    /// the finalized block to another chain can reorg it. The engine only reports the forkchoice
1161    /// update valid after making the head canonical, so the block is the latest block of the node
1162    /// once this returns. The transaction pool processes the new block in the background, see
1163    /// [`Self::wait_for_pool`].
1164    ///
1165    /// The parent of the payload must be known to the node, e.g. to import a payload built by
1166    /// another node into its peers. Returns an error if the engine reports the payload invalid, see
1167    /// [`Self::submit_payload`], or does not report the forkchoice update valid, e.g. because the
1168    /// parent is unknown, or because the block does not descend from the safe and finalized blocks
1169    /// that [`Finality::Keep`] and [`Finality::Lag`] keep.
1170    pub async fn import_payload(&self, payload: Payload::BuiltPayload) -> eyre::Result<B256> {
1171        let block_number = payload.block().number();
1172        let block_hash = self.submit_payload(payload).await?;
1173        let state = match self.finality {
1174            Finality::Head => ForkchoiceState::same_hash(block_hash),
1175            Finality::Keep | Finality::Lag(_) => {
1176                ForkchoiceState { head_block_hash: block_hash, ..self.current_forkchoice_state()? }
1177            }
1178        };
1179        self.send_forkchoice_updated(state, None).await?;
1180
1181        // Look up the block at the lagging height only now that the block is canonical, so it is an
1182        // ancestor of the block even if the block reorged the chain.
1183        if let Finality::Lag(lag) = self.finality &&
1184            let Some(number) = block_number.checked_sub(lag) &&
1185            self.inner
1186                .provider
1187                .finalized_block_number()?
1188                .is_none_or(|finalized| number > finalized) &&
1189            let Some(finalized) = self.inner.provider.block_hash(number)?
1190        {
1191            self.send_forkchoice_updated(
1192                ForkchoiceState {
1193                    head_block_hash: block_hash,
1194                    safe_block_hash: finalized,
1195                    finalized_block_hash: finalized,
1196                },
1197                None,
1198            )
1199            .await?;
1200        }
1201
1202        Ok(block_hash)
1203    }
1204
1205    /// Makes the known block with the given hash the head of the node, keeping its safe and
1206    /// finalized blocks, and returns once the block is the latest block.
1207    ///
1208    /// The engine makes a block on a side chain canonical, which reorgs the chain, e.g. back to a
1209    /// chain the node left with [`Self::advance_fork`]. This requires the block to descend from
1210    /// the finalized block, so blocks imported under [`Finality::Head`] can't be reorged away
1211    /// from, import them under [`Finality::Keep`] or [`Finality::Lag`], see
1212    /// [`Self::set_finality`]. The engine does not move the head back to a canonical ancestor, so
1213    /// this returns an error for those, build a block on the ancestor with
1214    /// [`Self::advance_block_on`] instead.
1215    ///
1216    /// The transaction pool processes the reorg in the background: once
1217    /// [`Self::wait_for_pool_head`] returns for the block, the pool has removed the transactions
1218    /// mined in the new chain and updated sender nonces and fees to it, while the maintenance task
1219    /// re-injects the transactions of the reorged blocks only afterwards, wait for them with
1220    /// [`Self::wait_for_pool`].
1221    ///
1222    /// Returns an error if the node does not know the block or one of its ancestors, if the block
1223    /// is invalid, or if it does not descend from the safe and finalized blocks.
1224    pub async fn reorg_to(&self, hash: B256) -> eyre::Result<()> {
1225        let state = ForkchoiceState { head_block_hash: hash, ..self.current_forkchoice_state()? };
1226        self.send_forkchoice_updated(state, None).await?;
1227
1228        let latest = self
1229            .inner
1230            .provider
1231            .sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)?
1232            .ok_or_else(|| eyre!("latest block not found"))?;
1233        ensure!(
1234            latest.hash() == hash,
1235            "block {hash} is a canonical ancestor of the head {}, which the engine does not move \
1236             the head back to, build a block on it with `advance_block_on` instead",
1237            latest.hash()
1238        );
1239
1240        Ok(())
1241    }
1242
1243    /// Sends the forkchoice state to the engine and returns its response, or an error that
1244    /// explains why the engine did not report it valid.
1245    async fn send_forkchoice_updated(
1246        &self,
1247        state: ForkchoiceState,
1248        attributes: Option<Payload::PayloadAttributes>,
1249    ) -> eyre::Result<ForkchoiceUpdated> {
1250        let head = state.head_block_hash;
1251        let updated = self
1252            .inner
1253            .add_ons_handle
1254            .beacon_engine_handle
1255            .fork_choice_updated(state, attributes)
1256            .await
1257            .map_err(|err| self.explain_forkchoice_error(err, head))?;
1258        ensure!(
1259            !updated.is_syncing(),
1260            "the node does not know block {head} or one of its ancestors, or is syncing, submit \
1261             the blocks first, e.g. with `submit_payload`"
1262        );
1263        ensure!(
1264            updated.is_valid(),
1265            "forkchoice update to head {head}, safe {}, finalized {} is not valid: {}",
1266            state.safe_block_hash,
1267            state.finalized_block_hash,
1268            updated.payload_status.status
1269        );
1270        Ok(updated)
1271    }
1272
1273    /// Converts an error of a forkchoice update to `head` into a report, explaining how to avoid it
1274    /// if the engine rejected the update because `head` does not descend from the finalized block.
1275    fn explain_forkchoice_error(
1276        &self,
1277        err: BeaconForkChoiceUpdateError,
1278        head: B256,
1279    ) -> eyre::Report {
1280        if !matches!(
1281            err,
1282            BeaconForkChoiceUpdateError::ForkchoiceUpdateError(
1283                ForkchoiceUpdateError::TooDeepReorg | ForkchoiceUpdateError::InvalidState
1284            )
1285        ) {
1286            return err.into()
1287        }
1288        let finalized = match self.inner.provider.finalized_block_num_hash() {
1289            Result::Ok(Some(finalized)) => format!("{} ({})", finalized.number, finalized.hash),
1290            _ => "unknown".to_string(),
1291        };
1292        eyre::Report::new(err).wrap_err(format!(
1293            "the node can't reorg to block {head}: it does not descend from the safe and finalized \
1294             blocks of the node (finalized: {finalized}). Only blocks above the finalized block \
1295             can be reorged, import them under `Finality::Keep` or `Finality::Lag`, see \
1296             `NodeTestContext::set_finality`"
1297        ))
1298    }
1299
1300    /// Returns the RPC URL.
1301    pub fn rpc_url(&self) -> Url {
1302        let addr = self.inner.rpc_server_handle().http_local_addr().unwrap();
1303        format!("http://{addr}").parse().unwrap()
1304    }
1305
1306    /// Returns an RPC client.
1307    pub fn rpc_client(&self) -> Option<HttpClient> {
1308        self.inner.rpc_server_handle().http_client()
1309    }
1310
1311    /// Returns an alloy provider with the recommended fillers connected to the HTTP RPC server.
1312    ///
1313    /// # Panics
1314    ///
1315    /// If the HTTP RPC server is disabled.
1316    pub fn rpc_provider(
1317        &self,
1318    ) -> FillProvider<impl TxFiller<Ethereum> + use<Node, Payload, AddOns>, RootProvider> {
1319        self.rpc_provider_for::<Ethereum>()
1320    }
1321
1322    /// Returns an alloy provider with the recommended fillers and the given wallet connected to
1323    /// the HTTP RPC server.
1324    ///
1325    /// Transactions sent via the provider are signed by the wallet, e.g. a
1326    /// [`PrivateKeySigner`](alloy_signer_local::PrivateKeySigner) of the test
1327    /// [`Wallet`](crate::wallet::Wallet).
1328    ///
1329    /// # Panics
1330    ///
1331    /// If the HTTP RPC server is disabled.
1332    pub fn rpc_provider_with_wallet<W>(
1333        &self,
1334        wallet: W,
1335    ) -> FillProvider<impl TxFiller<Ethereum> + use<W, Node, Payload, AddOns>, RootProvider>
1336    where
1337        W: IntoWallet<Ethereum, NetworkWallet: Clone>,
1338    {
1339        self.rpc_provider_with_wallet_for::<Ethereum, W>(wallet)
1340    }
1341
1342    /// Returns an alloy provider for the network `Net` with its recommended fillers connected to
1343    /// the HTTP RPC server, polling every [`RPC_PROVIDER_POLL_INTERVAL`].
1344    ///
1345    /// This is [`Self::rpc_provider`] for nodes whose RPC types differ from Ethereum's.
1346    ///
1347    /// # Panics
1348    ///
1349    /// If the HTTP RPC server is disabled.
1350    pub fn rpc_provider_for<Net: RecommendedFillers>(
1351        &self,
1352    ) -> FillProvider<impl TxFiller<Net> + use<Net, Node, Payload, AddOns>, RootProvider<Net>, Net>
1353    {
1354        let provider = ProviderBuilder::new_with_network::<Net>().connect_http(self.rpc_url());
1355        provider.client().set_poll_interval(RPC_PROVIDER_POLL_INTERVAL);
1356        provider
1357    }
1358
1359    /// Returns an alloy provider for the network `Net` with its recommended fillers and the given
1360    /// wallet connected to the HTTP RPC server, polling every [`RPC_PROVIDER_POLL_INTERVAL`].
1361    ///
1362    /// This is [`Self::rpc_provider_with_wallet`] for nodes whose RPC types differ from
1363    /// Ethereum's.
1364    ///
1365    /// # Panics
1366    ///
1367    /// If the HTTP RPC server is disabled.
1368    pub fn rpc_provider_with_wallet_for<Net, W>(
1369        &self,
1370        wallet: W,
1371    ) -> FillProvider<impl TxFiller<Net> + use<Net, W, Node, Payload, AddOns>, RootProvider<Net>, Net>
1372    where
1373        Net: RecommendedFillers,
1374        W: IntoWallet<Net, NetworkWallet: Clone>,
1375    {
1376        let provider =
1377            ProviderBuilder::new_with_network::<Net>().wallet(wallet).connect_http(self.rpc_url());
1378        provider.client().set_poll_interval(RPC_PROVIDER_POLL_INTERVAL);
1379        provider
1380    }
1381
1382    /// Returns an Engine API client.
1383    pub fn auth_server_handle(&self) -> AuthServerHandle {
1384        self.inner.auth_server_handle().clone()
1385    }
1386
1387    /// Creates a [`crate::testsuite::NodeClient`] from this test context.
1388    ///
1389    /// This helper method extracts the necessary handles and creates a client
1390    /// that can interact with both the regular RPC and Engine API endpoints.
1391    /// It automatically includes the beacon engine handle for direct consensus engine interaction
1392    /// and read-only access to the node's database.
1393    pub fn to_node_client(&self) -> eyre::Result<crate::testsuite::NodeClient<Payload>> {
1394        let rpc = self
1395            .rpc_client()
1396            .ok_or_else(|| eyre::eyre!("Failed to create HTTP RPC client for node"))?;
1397        let auth = self.auth_server_handle();
1398        let url = self.rpc_url();
1399        let beacon_handle = self.inner.add_ons_handle.beacon_engine_handle.clone();
1400
1401        let mut client =
1402            crate::testsuite::NodeClient::new_with_beacon_engine(rpc, auth, url, beacon_handle);
1403        client.payload_builder = Some(self.inner.payload_builder_handle.clone());
1404        let provider = self.inner.provider.clone();
1405        client.database = Some(Arc::new(move || {
1406            provider.database_provider_ro().map(|db| Box::new(db) as Box<dyn BlockNumReader>)
1407        }));
1408        Ok(client)
1409    }
1410
1411    /// Calls the `testing_buildBlockV1` RPC on this node.
1412    ///
1413    /// This endpoint builds a block using the provided parent, payload attributes, and
1414    /// transactions. Requires the `Testing` RPC module to be enabled.
1415    pub async fn testing_build_block_v1(
1416        &self,
1417        request: TestingBuildBlockRequestV1,
1418    ) -> eyre::Result<TestingBuildBlockResponseV1> {
1419        let client =
1420            self.rpc_client().ok_or_else(|| eyre::eyre!("HTTP RPC client not available"))?;
1421
1422        let res: TestingBuildBlockResponseV1 =
1423            client.request("testing_buildBlockV1", request.into_params()).await?;
1424        eyre::Ok(res)
1425    }
1426
1427    /// Stops the node and closes its database, keeping its datadir to launch the node again with
1428    /// [`StoppedNode::start`].
1429    ///
1430    /// The node shuts down like a node that receives `SIGTERM`: the engine persists every block of
1431    /// its canonical chain together with its state, and the tasks of the node exit. Blocks that
1432    /// are not canonical, e.g. payloads that were only submitted with [`Self::submit_payload`],
1433    /// are lost. Local transactions of the pool are saved to the datadir and reinserted when the
1434    /// node starts again.
1435    ///
1436    /// Returns once the node released its database, so the database, static files and `RocksDB`
1437    /// are closed and nothing writes to the datadir anymore, including the pruner that runs after
1438    /// the last save. Peers of the node notice the disconnect in the background.
1439    ///
1440    /// Only restartable nodes, see
1441    /// [`E2ETestSetupBuilder::with_restartable_nodes`](crate::E2ETestSetupBuilder::with_restartable_nodes),
1442    /// can be stopped. Returns an error for other nodes, if the node does not shut down within
1443    /// [`WAIT_TIMEOUT`], or if the test keeps the database open:
1444    /// - a handle of the node that holds its database, e.g. a clone of `inner.provider` or a
1445    ///   [`NodeClient`](crate::testsuite::NodeClient), makes this fail once the database is still
1446    ///   in use [`DATABASE_RELEASE_TIMEOUT`] after the node shut down,
1447    /// - an open database transaction, e.g. a provider of `database_provider_ro`, makes this fail
1448    ///   right away,
1449    /// - a handle of `RocksDB`, e.g. of `inner.provider.rocksdb_provider()`, makes this fail once
1450    ///   `RocksDB` is still open [`DATABASE_RELEASE_TIMEOUT`] after the node shut down.
1451    ///
1452    /// Drop such handles before stopping the node.
1453    pub async fn stop(self) -> eyre::Result<StoppedNode<Node, AddOns>> {
1454        self.stop_with_release_timeout(DATABASE_RELEASE_TIMEOUT).await
1455    }
1456
1457    /// Stops the node like [`Self::stop`], but waits at most `release_timeout` instead of
1458    /// [`DATABASE_RELEASE_TIMEOUT`] for the database and `RocksDB` to be released.
1459    pub async fn stop_with_release_timeout(
1460        mut self,
1461        release_timeout: Duration,
1462    ) -> eyre::Result<StoppedNode<Node, AddOns>> {
1463        let Some(NodeRestart { database, relaunch }) = self.restart.take() else {
1464            bail!(
1465                "the node is not restartable, launch it with \
1466                 `E2ETestSetupBuilder::with_restartable_nodes` to stop it"
1467            )
1468        };
1469        let data_dir = self.inner.data_dir.clone();
1470        let runtime = self.inner.task_executor.clone();
1471        // Stop the RPC servers even if the test kept handles to them, which would otherwise keep
1472        // the database open.
1473        let _ = self.inner.rpc_server_handle().clone().stop();
1474        let _ = self.inner.auth_server_handle().clone().stop();
1475        drop(self);
1476
1477        // The consensus engine is a graceful task that persists the canonical chain before it
1478        // exits. Waiting for graceful tasks blocks the thread.
1479        let shut_down = tokio::task::spawn_blocking(move || {
1480            runtime.graceful_shutdown_with_timeout(WAIT_TIMEOUT)
1481        })
1482        .await?;
1483        ensure!(shut_down, "the node did not shut down within {WAIT_TIMEOUT:?}");
1484
1485        close_database(database, &data_dir, release_timeout).await?;
1486        Ok(StoppedNode { data_dir, relaunch, remove_data_dir: true })
1487    }
1488
1489    /// Stops the node with [`Self::stop`] and starts it again with [`StoppedNode::start`],
1490    /// returning its new test context.
1491    pub async fn restart(self) -> eyre::Result<Self> {
1492        self.stop().await?.start().await
1493    }
1494
1495    /// Takes the future that resolves when the consensus engine of the node exits.
1496    ///
1497    /// It resolves with an error if the engine exits because of a fatal error, and with `Ok` once
1498    /// the node is stopped, so a test can use it to notice a node that died. Returns `None` if the
1499    /// future was already taken or the node was not launched by
1500    /// [`E2ETestSetupBuilder`](crate::E2ETestSetupBuilder).
1501    pub fn take_exit_future(&mut self) -> Option<NodeExitFuture> {
1502        self.exit_future.get_mut().unwrap_or_else(PoisonError::into_inner).take()
1503    }
1504}
1505
1506/// Skips canonical state notifications until the one that commits the block with the given hash
1507/// and number, returning the chain segment it commits.
1508///
1509/// The stream buffers every canonical notification since it was created, e.g. of blocks mined with
1510/// [`NodeTestContext::advance_block`], so it does not need to be aligned with the block.
1511///
1512/// Returns an error if the block is not committed within [`WAIT_TIMEOUT`].
1513async fn wait_for_committed_chain<N: NodePrimitives>(
1514    stream: &mut CanonStateNotificationStream<N>,
1515    block_hash: B256,
1516    block_number: BlockNumber,
1517) -> eyre::Result<Arc<Chain<N>>> {
1518    let wait = async {
1519        loop {
1520            let notification =
1521                stream.next().await.ok_or_else(|| eyre!("canonical state stream closed"))?;
1522            let committed = notification.committed();
1523            if committed.blocks().get(&block_number).is_some_and(|block| block.hash() == block_hash)
1524            {
1525                return Ok(committed)
1526            }
1527        }
1528    };
1529    tokio::time::timeout(WAIT_TIMEOUT, wait)
1530        .await
1531        .map_err(|_| eyre!("timed out waiting for block {block_number}"))?
1532}
1533
1534/// Waits until `database` is the last handle of the database of a stopped node, closes the
1535/// database without removing its datadir, and waits until `RocksDB` is closed.
1536///
1537/// Returns an error if the database or `RocksDB` is still open afterwards.
1538async fn close_database(
1539    database: TmpDB,
1540    data_dir: &ChainPath<DataDirPath>,
1541    release_timeout: Duration,
1542) -> eyre::Result<()> {
1543    let released = tokio::time::timeout(release_timeout, async {
1544        while Arc::strong_count(&database) > 1 {
1545            tokio::time::sleep(POLL_INTERVAL).await;
1546        }
1547    })
1548    .await;
1549    if released.is_err() {
1550        bail!(
1551            "the database of the stopped node is still in use {release_timeout:?} after the node \
1552             shut down: drop all handles that hold a provider of the node, e.g. a clone of \
1553             `inner.provider`, before stopping it"
1554        )
1555    }
1556    let database = Arc::into_inner(database)
1557        .ok_or_else(|| eyre!("the database of the stopped node is still in use"))?;
1558    // Unlike dropping the temporary database, this keeps the datadir.
1559    drop(database.into_inner_db());
1560
1561    // A database transaction keeps the database open without a handle of it. MDBX does not open
1562    // a database twice in a process, so opening it fails until the transaction is dropped.
1563    open_db_read_only(data_dir.db(), DatabaseArguments::test()).wrap_err(
1564        "the database of the stopped node is still open: drop all database transactions of the \
1565         node, e.g. providers of `database_provider_ro`, before stopping it",
1566    )?;
1567
1568    // Providers drop their database handle before their `RocksDB` handle, whose drop flushes and
1569    // closes `RocksDB`, so the last provider can still be closing it. `RocksDB` does not open a
1570    // database twice in a process either, so wait until opening it succeeds.
1571    let deadline = Instant::now() + release_timeout;
1572    loop {
1573        let path = data_dir.rocksdb();
1574        let opened = tokio::task::spawn_blocking(move || {
1575            RocksDBProvider::builder(path).with_default_tables().build().map(drop)
1576        })
1577        .await?;
1578        match opened {
1579            Result::Ok(()) => return Ok(()),
1580            Err(err) if Instant::now() >= deadline => {
1581                return Err(err).wrap_err(format!(
1582                    "the RocksDB of the stopped node is still open {release_timeout:?} after \
1583                     the node shut down: drop all handles of it, e.g. of \
1584                     `inner.provider.rocksdb_provider()`, before stopping the node"
1585                ))
1586            }
1587            Err(_) => tokio::time::sleep(POLL_INTERVAL).await,
1588        }
1589    }
1590}
1591
1592/// Waits until all `hashes` are in `pool` if `pooled` is true, or none of them otherwise.
1593///
1594/// On timeout, the error lists the transactions that are still missing or still pooled.
1595async fn wait_for_pool_transactions<P: TransactionPool>(
1596    pool: &P,
1597    hashes: impl IntoIterator<Item = B256>,
1598    pooled: bool,
1599) -> eyre::Result<()> {
1600    let hashes = hashes.into_iter().collect::<Vec<_>>();
1601    let (what, pending_kind) = if pooled {
1602        ("transactions to enter the pool", "still missing")
1603    } else {
1604        ("transactions to leave the pool", "still pooled")
1605    };
1606    let mut pending = Vec::new();
1607    poll_until(what, || {
1608        pending = hashes.iter().copied().filter(|hash| pool.contains(hash) != pooled).collect();
1609        let done = pending.is_empty();
1610        async move { Ok(done.then_some(())) }
1611    })
1612    .await
1613    .map_err(|err| eyre!("{err}, {pending_kind}: {pending:?}"))
1614}
1615
1616/// Describes the pending transactions of `pool` for an error message, listing at most ten.
1617fn describe_pending_transactions<P: TransactionPool>(pool: &P) -> String {
1618    const MAX_LISTED: usize = 10;
1619
1620    let pending = pool.pending_transactions();
1621    let mut listed = pending
1622        .iter()
1623        .take(MAX_LISTED)
1624        .map(|tx| format!("{} (sender {}, nonce {})", tx.hash(), tx.sender(), tx.nonce()))
1625        .collect::<Vec<_>>();
1626    if pending.len() > MAX_LISTED {
1627        listed.push(format!("and {} more", pending.len() - MAX_LISTED));
1628    }
1629    format!(
1630        "the pool still has {} at block {}: {}",
1631        pending.len(),
1632        pool.block_info().last_seen_block_number,
1633        listed.join(", ")
1634    )
1635}
1636
1637/// The future returned by [`NodeTestContext::mine`] and the related mining helpers.
1638pub type MinedBlockFuture<'a, Payload, Receipt> =
1639    BoxFuture<'a, eyre::Result<MinedBlock<Payload, Receipt>>>;
1640
1641/// A block mined by [`NodeTestContext::mine`], [`NodeTestContext::mine_including`],
1642/// [`NodeTestContext::mine_signed`] or [`NodeTestContext::mine_pooled`].
1643#[derive(Debug)]
1644pub struct MinedBlock<Payload: BuiltPayload, Receipt> {
1645    /// The built payload of the block.
1646    pub payload: Payload,
1647    /// The RPC receipts of the mined transactions, in the order they were passed.
1648    pub receipts: Vec<Receipt>,
1649    /// The chain segment committed by the canonical state notification of the block.
1650    ///
1651    /// The block is built on the canonical head, so the segment consists of this block only and
1652    /// its execution outcome, e.g. the bundle state, is the outcome of the block.
1653    pub chain: Arc<Chain<Payload::Primitives>>,
1654}
1655
1656impl<Payload: BuiltPayload, Receipt: ReceiptResponse> MinedBlock<Payload, Receipt> {
1657    /// Returns the mined block.
1658    pub fn block(&self) -> &SealedBlock<<Payload::Primitives as NodePrimitives>::Block> {
1659        self.payload.block()
1660    }
1661
1662    /// Returns the mined block if none of the mined transactions reverted.
1663    ///
1664    /// Returns an error naming the first reverted transaction otherwise.
1665    pub fn ensure_success(self) -> eyre::Result<Self> {
1666        for receipt in &self.receipts {
1667            ensure_successful(receipt)?;
1668        }
1669        Ok(self)
1670    }
1671}
1672
1673/// Which blocks [`NodeTestContext::import_payload`] marks as safe and finalized when it makes a
1674/// block the head.
1675///
1676/// The policy applies to [`NodeTestContext::import_payload`] and the block producing helpers built
1677/// on it, e.g. [`NodeTestContext::advance_block`], [`NodeTestContext::advance`] and
1678/// [`NodeTestContext::advance_while`]. [`NodeTestContext::update_forkchoice`] and
1679/// [`NodeTestContext::sync_to`] send the forkchoice state given by their arguments, and
1680/// [`NodeTestContext::new_payload`] keeps the safe and finalized blocks the node reports.
1681///
1682/// The engine accepts a forkchoice update only if its safe and finalized blocks are ancestors of
1683/// its head, and rejects moving the head back to a canonical block below the finalized block. So
1684/// while the finalized block stays, e.g. under [`Finality::Keep`], only blocks above it can be
1685/// reorged.
1686///
1687/// Finality does not hold back persistence: the engine persists canonical blocks and evicts them
1688/// from memory once more of them than the persistence threshold are in memory, and the pruner runs
1689/// on the persisted tip, so [`NodeTestContext::wait_for_persisted_block`] and
1690/// [`NodeTestContext::wait_for_prune_checkpoint`] behave the same under every policy. The engine
1691/// only evicts a side chain from memory once the finalized block passes its fork point, and the
1692/// transaction pool keeps the blob sidecars of mined transactions until their block is finalized,
1693/// so under [`Finality::Keep`] both remain available for reorgs.
1694#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1695pub enum Finality {
1696    /// The imported block becomes the safe and finalized block.
1697    #[default]
1698    Head,
1699    /// The safe and finalized blocks stay what the node reports, e.g. genesis after the setup, so
1700    /// the imported blocks can be reorged.
1701    Keep,
1702    /// The safe and finalized blocks trail the imported block by the given number of blocks.
1703    ///
1704    /// After importing the block at height `h`, its ancestor at height `h - n` becomes the safe
1705    /// and finalized block if it is above the current finalized block. Otherwise they stay, so
1706    /// they never move backwards, e.g. after switching from [`Finality::Head`], and stay at
1707    /// genesis until the chain is `n` blocks past it. The latest `n` blocks can be reorged.
1708    Lag(u64),
1709}
1710
1711/// A test node that was stopped with [`NodeTestContext::stop`].
1712///
1713/// The datadir of the node is kept until the node is launched again with [`Self::start`], and
1714/// removed when this is dropped.
1715pub struct StoppedNode<Node, AddOns>
1716where
1717    Node: FullNodeComponents,
1718    AddOns: RethRpcAddOns<Node>,
1719{
1720    data_dir: ChainPath<DataDirPath>,
1721    relaunch: Relaunch<NodeTestContext<Node, AddOns>>,
1722    /// Whether the datadir is removed on drop, which is unset once a launched node owns it.
1723    remove_data_dir: bool,
1724}
1725
1726impl<Node, AddOns> StoppedNode<Node, AddOns>
1727where
1728    Node: FullNodeComponents,
1729    AddOns: RethRpcAddOns<Node>,
1730{
1731    /// Returns the datadir of the node.
1732    pub const fn data_dir(&self) -> &ChainPath<DataDirPath> {
1733        &self.data_dir
1734    }
1735
1736    /// Launches the node again on its datadir and returns its new test context.
1737    ///
1738    /// The node is launched like by
1739    /// [`E2ETestSetupBuilder::build`](crate::E2ETestSetupBuilder::build), with the same node
1740    /// factory, node builder modifiers, node and tree configuration and attributes generator,
1741    /// on a new runtime. It opens the database, static files and `RocksDB` in its datadir
1742    /// again, so it recovers from disk like a node restarted from the command line.
1743    ///
1744    /// Its head is the canonical head it had when it was stopped, and its safe and finalized blocks
1745    /// are the ones it persisted, which are the head after [`NodeTestContext::advance_block`].
1746    /// Unless the node mines in dev mode, it receives a forkchoice update that restates these
1747    /// blocks instead of the forkchoice update to genesis of a new node, and
1748    /// [`NodeTestContext::advance_block`] builds on its head right away.
1749    ///
1750    /// The node keeps its peer identity, which is stored in its datadir, but listens on new ports
1751    /// and does not reconnect to its peers. Connect it with `node.connect(&mut peer)` once `peer`
1752    /// noticed that the stopped node disconnected, e.g. once its number of connected peers
1753    /// dropped. `peer.connect(&mut node)` panics instead, because the network events of `peer`
1754    /// still contain the disconnect of the stopped node.
1755    ///
1756    /// Returns an error if the node can not be launched, or if the forkchoice update is not valid.
1757    pub async fn start(mut self) -> eyre::Result<NodeTestContext<Node, AddOns>> {
1758        // The database of the launched node removes the datadir once it is dropped.
1759        self.remove_data_dir = false;
1760        (self.relaunch)().await
1761    }
1762}
1763
1764impl<Node, AddOns> std::fmt::Debug for StoppedNode<Node, AddOns>
1765where
1766    Node: FullNodeComponents,
1767    AddOns: RethRpcAddOns<Node>,
1768{
1769    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1770        f.debug_struct("StoppedNode").field("data_dir", &self.data_dir).finish_non_exhaustive()
1771    }
1772}
1773
1774impl<Node, AddOns> Drop for StoppedNode<Node, AddOns>
1775where
1776    Node: FullNodeComponents,
1777    AddOns: RethRpcAddOns<Node>,
1778{
1779    fn drop(&mut self) {
1780        if self.remove_data_dir {
1781            let _ = reth_fs_util::remove_dir_all(self.data_dir.data_dir());
1782        }
1783    }
1784}
1785
1786/// What a restartable node of [`E2ETestSetupBuilder`](crate::E2ETestSetupBuilder) needs to be
1787/// stopped and launched again.
1788pub(crate) struct NodeRestart<Ctx> {
1789    /// A handle of the database of the node, to wait until the stopped node released it.
1790    pub(crate) database: TmpDB,
1791    /// Launches the node again on its datadir.
1792    pub(crate) relaunch: Relaunch<Ctx>,
1793}
1794
1795/// Closure that launches a stopped node again, returning its new test context.
1796pub(crate) type Relaunch<Ctx> =
1797    Arc<dyn Fn() -> BoxFuture<'static, eyre::Result<Ctx>> + Send + Sync>;
1798
1799#[cfg(test)]
1800mod tests {
1801    use super::*;
1802    use crate::{
1803        snap::SnapControl,
1804        wait::{assert_holds_for, poll_until_with, PollOpts},
1805        NodeHelperType,
1806    };
1807    use alloy_consensus::TxEnvelope;
1808    use reth_node_ethereum::{EthEngineTypes, EthereumNode};
1809    use reth_transaction_pool::test_utils::{testing_pool, MockTransaction};
1810
1811    fn assert_send<T: Send>(_: T) {}
1812
1813    /// Tests of downstream nodes await these helpers in spawned tasks, so their futures must be
1814    /// `Send`.
1815    #[expect(dead_code)]
1816    fn test_helper_futures_are_send(
1817        node: &mut NodeHelperType<EthereumNode>,
1818        payload: <EthEngineTypes as PayloadTypes>::BuiltPayload,
1819    ) {
1820        assert_send(node.new_payload());
1821        assert_send(node.advance_block());
1822        assert_send(node.advance_block_synced());
1823        assert_send(node.inject_and_advance(Bytes::new()));
1824        assert_send(node.advance_blocks(0));
1825        assert_send(node.build_payload_on(B256::ZERO));
1826        assert_send(node.advance_block_on(B256::ZERO));
1827        assert_send(node.advance_fork(B256::ZERO, 0));
1828        assert_send(node.reorg_to(B256::ZERO));
1829        assert_send(node.advance_until_receipt(B256::ZERO));
1830        assert_send(node.advance_until_pool_drained());
1831        assert_send(node.advance_while(async {}));
1832        assert_send(node.engine.new_payload(payload.clone()));
1833        assert_send(node.engine.new_payload_from_block(payload.block().clone(), None));
1834        assert_send(node.engine.forkchoice_updated(ForkchoiceState::default()));
1835        assert_send(node.engine.forkchoice_updated_with_attributes(
1836            ForkchoiceState::default(),
1837            <EthEngineTypes as PayloadTypes>::PayloadAttributes::default(),
1838        ));
1839        assert_send(node.mine([Bytes::new()]));
1840        assert_send(node.mine_including(Vec::new()));
1841        assert_send(node.mine_signed(Vec::<TxEnvelope>::new()));
1842        assert_send(node.mine_pooled([B256::ZERO]));
1843        assert_send(node.wait_block(0, B256::ZERO, false));
1844        assert_send(node.wait_unwind(0));
1845        assert_send(node.wait_for_pool(|_| true));
1846        assert_send(node.wait_for_pool_head(B256::ZERO));
1847        assert_send(node.wait_for_pooled([B256::ZERO]));
1848        assert_send(node.wait_for_pool_removal([B256::ZERO]));
1849        assert_send(node.wait_for_persisted_block(0));
1850        assert_send(node.wait_for_prune_checkpoint(PruneSegment::SenderRecovery, 0));
1851        assert_send(node.wait_for_head(B256::ZERO));
1852        assert_send(node.assert_new_block(B256::ZERO, B256::ZERO, 0));
1853        assert_send(node.sync_to(B256::ZERO));
1854        assert_send(node.sync_to_forkchoice(ForkchoiceState::default()));
1855        assert_send(node.submit_payload(payload.clone()));
1856        assert_send(node.submit_payload_with_status(payload.clone()));
1857        assert_send(node.import_payload(payload));
1858        assert_send(poll_until_with(PollOpts::default(), "", || async { Ok(Some(())) }));
1859        assert_send(assert_holds_for(Duration::ZERO, "", || async { Ok(true) }));
1860        let gate = SnapControl::default().pause_on(|_| true);
1861        assert_send(gate.reached());
1862    }
1863
1864    /// Like [`test_helper_futures_are_send`], for the helpers that stop and restart a node.
1865    #[expect(dead_code)]
1866    fn test_restart_futures_are_send(
1867        node: NodeHelperType<EthereumNode>,
1868        other: NodeHelperType<EthereumNode>,
1869    ) {
1870        assert_send(node.stop());
1871        assert_send(other.restart());
1872    }
1873
1874    // The paused clock lets the waits run into their timeout without waiting for it.
1875    #[tokio::test(start_paused = true)]
1876    async fn pool_transaction_waits_list_pending_transactions_on_timeout() {
1877        let pool = testing_pool();
1878        let tx = MockTransaction::eip1559();
1879        let pooled = *tx.get_hash();
1880        pool.add_external_transaction(tx).await.unwrap();
1881        let absent = B256::repeat_byte(1);
1882
1883        let err = wait_for_pool_transactions(&pool, [pooled, absent], true).await.unwrap_err();
1884        assert_eq!(
1885            err.to_string(),
1886            format!(
1887                "timed out after 60s waiting for transactions to enter the pool, still missing: \
1888                 [{absent}]"
1889            )
1890        );
1891
1892        let err = wait_for_pool_transactions(&pool, [pooled, absent], false).await.unwrap_err();
1893        assert_eq!(
1894            err.to_string(),
1895            format!(
1896                "timed out after 60s waiting for transactions to leave the pool, still pooled: \
1897                 [{pooled}]"
1898            )
1899        );
1900    }
1901}