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}