From f74d3a3e7e71b65102320e5141604097dd64c94c Mon Sep 17 00:00:00 2001 From: James Date: Fri, 13 Mar 2026 17:19:26 -0400 Subject: [PATCH] fix(node-tests): adapt test infra to HostNotifier trait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace ExExContext-based test setup with a channel-backed TestHostNotifier, allowing signet-node-tests to compile against the new HostNotifier-generic SignetNodeBuilder API. - Add TestHostNotifier wrapping an unbounded channel - Convert ExExNotification → HostNotification directly (drop reth conversion path in convert.rs) - Supply blob_cacher, ServeConfig, StorageRpcConfig to the builder - Remove reth-exex and reth-node-api deps; keep reth for pool access Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/node-tests/Cargo.toml | 7 +- crates/node-tests/src/context.rs | 104 ++++++++++++++++---- crates/node-tests/src/convert.rs | 163 ++++--------------------------- crates/node-tests/src/lib.rs | 6 +- crates/node-tests/tests/db.rs | 31 +++++- 5 files changed, 144 insertions(+), 167 deletions(-) diff --git a/crates/node-tests/Cargo.toml b/crates/node-tests/Cargo.toml index a6060f0..16b6544 100644 --- a/crates/node-tests/Cargo.toml +++ b/crates/node-tests/Cargo.toml @@ -9,12 +9,13 @@ homepage.workspace = true repository.workspace = true [dependencies] +signet-blobber = { workspace = true, features = ["test-utils"] } signet-node.workspace = true signet-node-config = { workspace = true, features = ["test_utils"] } +signet-node-types.workspace = true signet-cold = { workspace = true, features = ["in-memory"] } signet-constants.workspace = true -signet-evm.workspace = true signet-genesis.workspace = true signet-hot = { workspace = true, features = ["in-memory"] } signet-storage.workspace = true @@ -26,11 +27,11 @@ signet-zenith.workspace = true alloy.workspace = true reth.workspace = true -reth-exex.workspace = true reth-exex-test-utils.workspace = true -reth-node-api.workspace = true eyre.workspace = true +reqwest.workspace = true +signet-rpc.workspace = true tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true diff --git a/crates/node-tests/src/context.rs b/crates/node-tests/src/context.rs index 190ac9f..bea65af 100644 --- a/crates/node-tests/src/context.rs +++ b/crates/node-tests/src/context.rs @@ -1,6 +1,6 @@ use crate::{ HostBlockSpec, NotificationSpec, NotificationWithSidecars, RuBlockSpec, - convert::ToRethPrimitive, + convert::to_host_notification, types::{CtxProvider, Log, TestCounterInstance, TestErc20Instance, TestLogInstance}, }; use alloy::{ @@ -14,9 +14,11 @@ use alloy::{ }, rpc::types::eth::{TransactionReceipt, TransactionRequest}, }; -use reth::transaction_pool::{TransactionOrigin, TransactionPool, test_utils::MockTransaction}; -use reth_exex_test_utils::{Adapter, TestExExHandle}; -use reth_node_api::FullNodeComponents; +use reth::{ + api::FullNodeComponents, + transaction_pool::{TransactionOrigin, TransactionPool, test_utils::MockTransaction}, +}; +use reth_exex_test_utils::Adapter; use signet_cold::{ColdStorageReadHandle, mem::MemColdBackend}; use signet_hot::{ db::{HotDbRead, UnsafeDbWrite}, @@ -24,26 +26,67 @@ use signet_hot::{ }; use signet_node::{NodeStatus, SignetNodeBuilder}; use signet_node_config::test_utils::test_config; +use signet_node_types::{HostNotification, HostNotifier}; +use signet_rpc::{ServeConfig, StorageRpcConfig}; use signet_storage::{CancellationToken, HistoryRead, HistoryWrite, HotKv, UnifiedStorage}; use signet_storage_types::{Account, BlockNumberList, DbSignetEvent, RecoveredTx, SealedHeader}; -use signet_test_utils::contracts::counter::COUNTER_DEPLOY_CODE; +use signet_test_utils::{chain::Chain, contracts::counter::COUNTER_DEPLOY_CODE}; use signet_types::constants::{HostPermitted, RollupPermitted, SignetSystemConstants}; use signet_zenith::{HostOrders::OrdersInstance, RollupPassage::RollupPassageInstance}; use std::sync::{ Arc, Mutex, atomic::{AtomicU64, Ordering}, }; -use tokio::{sync::watch, task::JoinHandle}; +use tokio::sync::{mpsc, watch}; +use tokio::task::JoinHandle; use tracing::instrument; +/// A channel-backed [`HostNotifier`] for integration tests. +/// +/// Receives [`HostNotification`]s from the test harness and yields them +/// to the signet node's main loop. +pub struct TestHostNotifier { + receiver: mpsc::UnboundedReceiver>, +} + +impl TestHostNotifier { + /// Create a new test notifier from a receiver. + pub const fn new(receiver: mpsc::UnboundedReceiver>) -> Self { + Self { receiver } + } +} + +impl core::fmt::Debug for TestHostNotifier { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("TestHostNotifier").finish_non_exhaustive() + } +} + +impl HostNotifier for TestHostNotifier { + type Chain = Chain; + type Error = core::convert::Infallible; + + async fn next_notification( + &mut self, + ) -> Option, Self::Error>> { + self.receiver.recv().await.map(Ok) + } + + fn set_head(&mut self, _block_number: u64) {} + + fn set_backfill_thresholds(&mut self, _max_blocks: Option) {} + + fn send_finished_height(&self, _block_number: u64) -> Result<(), Self::Error> { + Ok(()) + } +} + /// Signet Node test context /// /// This contains the following: /// -/// - Reth/ExEx context info -/// - The test exex handle, which is used to send notifications to the signet -/// instance. -/// - The components for the Signet Node instance +/// - A channel sender for host notifications +/// - The reth test components (for pool access) /// - A receiver for the node status (latest block processed) /// - Unified storage backed by in-memory hot and cold storage /// - An alloy provider connected to the Signet Node RPC, @@ -52,10 +95,10 @@ use tracing::instrument; /// - A set of addresses for testing, each of which has a pre-existing balance /// on the Signet Node chain. pub struct SignetTestContext { - /// The test exex handle - pub handle: TestExExHandle, + /// The notification sender for the test host notifier. + pub sender: mpsc::UnboundedSender>, - /// The components for the Signet Node instance + /// The reth test components (for pool access). pub components: Adapter, /// The Signet Node status receiver @@ -101,7 +144,7 @@ impl SignetTestContext { #[instrument] pub async fn new() -> (Self, JoinHandle>) { let cfg = test_config(); - let (ctx, handle) = reth_exex_test_utils::test_exex_context().await.unwrap(); + let (ctx, _handle) = reth_exex_test_utils::test_exex_context().await.unwrap(); let components = ctx.components.clone(); // set up Signet Node storage @@ -148,10 +191,36 @@ impl SignetTestContext { let alias_oracle: Arc>> = Arc::new(Mutex::new(HashSet::default())); + // Create the test host notifier channel + let (sender, receiver) = mpsc::unbounded_channel(); + let notifier = TestHostNotifier { receiver }; + + // Build the blob cacher from the reth test pool + let blob_cacher = signet_blobber::BlobFetcher::builder() + .with_config(cfg.block_extractor()) + .unwrap() + .with_pool(components.pool().clone()) + .with_client(reqwest::Client::new()) + .build_cache() + .unwrap() + .spawn(); + + // Build ServeConfig from the test config's IPC endpoint + let serve_config = ServeConfig { + http: vec![], + http_cors: None, + ws: vec![], + ws_cors: None, + ipc: cfg.ipc_endpoint().map(ToOwned::to_owned), + }; + let (node, mut node_status) = SignetNodeBuilder::new(cfg.clone()) - .with_ctx(ctx) + .with_notifier(notifier) .with_storage(Arc::clone(&storage)) .with_alias_oracle(Arc::clone(&alias_oracle)) + .with_blob_cacher(blob_cacher) + .with_serve_config(serve_config) + .with_rpc_config(StorageRpcConfig::default()) .build() .await .unwrap(); @@ -179,7 +248,7 @@ impl SignetTestContext { .unwrap(); let this = Self { - handle, + sender, components, node_status, storage, @@ -284,7 +353,8 @@ impl SignetTestContext { assert!(pool.get_blob(tx_hash).unwrap().is_some(), "Missing blob we just inserted"); } - self.handle.notifications_tx.send(notification.notification.to_reth()).await.unwrap(); + let host_notification = to_host_notification(¬ification.notification); + self.sender.send(host_notification).unwrap(); } /// Send a notification to the Signet Node instance and wait for it to be diff --git a/crates/node-tests/src/convert.rs b/crates/node-tests/src/convert.rs index d08dbef..abf627e 100644 --- a/crates/node-tests/src/convert.rs +++ b/crates/node-tests/src/convert.rs @@ -1,147 +1,26 @@ -use alloy::consensus::ReceiptEnvelope; -use signet_evm::ExecutionOutcome; -use signet_types::primitives::{RecoveredBlock, SealedBlock}; +use signet_node_types::{HostNotification, HostNotificationKind}; +use signet_test_utils::chain::Chain; use std::sync::Arc; -/// Utility trait to convert a type to a Reth primitive type. -/// This is used mainly where we need to convert to a reth primitive type -/// because reth does not support the alloy equivalents. -pub trait ToRethPrimitive { - /// The Reth primitive type that the type can be converted to. - type RethPrimitive; - - /// Convert the type to a Reth primitive type. - fn to_reth(self) -> Self::RethPrimitive; -} - -// Reth does not preserve envelope status for receipts, so -// the DB model will not support envelopes. -impl ToRethPrimitive for ReceiptEnvelope { - type RethPrimitive = reth::primitives::Receipt; - - fn to_reth(self) -> Self::RethPrimitive { - let success = self.is_success(); - let cumulative_gas_used = self.cumulative_gas_used(); - let tx_type = match self.tx_type() { - alloy::consensus::TxType::Legacy => reth::primitives::TxType::Legacy, - alloy::consensus::TxType::Eip2930 => reth::primitives::TxType::Eip2930, - alloy::consensus::TxType::Eip1559 => reth::primitives::TxType::Eip1559, - alloy::consensus::TxType::Eip4844 => reth::primitives::TxType::Eip4844, - alloy::consensus::TxType::Eip7702 => reth::primitives::TxType::Eip7702, - }; - - let r = match self { - ReceiptEnvelope::Legacy(r) - | ReceiptEnvelope::Eip2930(r) - | ReceiptEnvelope::Eip1559(r) - | ReceiptEnvelope::Eip4844(r) => r, - _ => panic!("unsupported receipt type"), - }; - - reth::primitives::Receipt { tx_type, success, cumulative_gas_used, logs: r.receipt.logs } - } -} - -impl ToRethPrimitive for SealedBlock { - type RethPrimitive = reth::primitives::SealedBlock; - - fn to_reth(self) -> Self::RethPrimitive { - let hash = self.header.hash(); - let header = self.header.into_inner(); - let body = reth::primitives::BlockBody { - transactions: self.transactions, - ommers: vec![], - withdrawals: None, - }; - reth::primitives::SealedBlock::new_unchecked( - reth::primitives::Block::new(header, body), - hash, - ) - } -} - -impl ToRethPrimitive for RecoveredBlock { - type RethPrimitive = reth::primitives::RecoveredBlock; - - fn to_reth(self) -> Self::RethPrimitive { - let hash = self.header.hash(); - let senders: Vec<_> = self.senders().collect(); - let header = self.header.into_inner(); - let transactions = self.transactions.into_iter().map(|r| r.into_inner()).collect(); - let body = reth::primitives::BlockBody { transactions, ommers: vec![], withdrawals: None }; - let block = reth::primitives::Block::new(header, body); - reth::primitives::RecoveredBlock::new(block, senders, hash) - } -} - -impl ToRethPrimitive for signet_test_utils::chain::Chain { - type RethPrimitive = reth::providers::Chain; - - fn to_reth(self) -> Self::RethPrimitive { - reth::providers::Chain::new( - self.blocks.to_reth(), - self.execution_outcome.to_reth(), - Default::default(), - ) - } -} - -impl ToRethPrimitive for Vec -where - T: ToRethPrimitive, -{ - type RethPrimitive = Vec; - - fn to_reth(self) -> Self::RethPrimitive { - self.into_iter().map(ToRethPrimitive::to_reth).collect() - } -} - -impl ToRethPrimitive for ExecutionOutcome { - type RethPrimitive = reth::providers::ExecutionOutcome; - - fn to_reth(self) -> Self::RethPrimitive { - let (bundle, receipts, first_block) = self.into_parts(); - - reth::providers::ExecutionOutcome { - bundle, - receipts: receipts.into_iter().map(ToRethPrimitive::to_reth).collect(), - first_block, - requests: vec![], +/// Convert a test [`ExExNotification`] into a [`HostNotification`]. +/// +/// Safe and finalized block numbers are set to `None` since the test +/// harness does not exercise block tag logic. +/// +/// [`ExExNotification`]: signet_test_utils::specs::ExExNotification +pub fn to_host_notification( + notif: &signet_test_utils::specs::ExExNotification, +) -> HostNotification { + let kind = match notif { + signet_test_utils::specs::ExExNotification::Committed { new } => { + HostNotificationKind::ChainCommitted { new: Arc::clone(new) } } - } -} - -impl ToRethPrimitive for signet_test_utils::specs::ExExNotification { - type RethPrimitive = reth_exex::ExExNotification; - - fn to_reth(self) -> Self::RethPrimitive { - match self { - signet_test_utils::specs::ExExNotification::Committed { new } => { - reth_exex::ExExNotification::ChainCommitted { new: new.to_reth() } - } - signet_test_utils::specs::ExExNotification::Reorged { old, new } => { - reth_exex::ExExNotification::ChainReorged { old: old.to_reth(), new: new.to_reth() } - } - signet_test_utils::specs::ExExNotification::Reverted { old } => { - reth_exex::ExExNotification::ChainReverted { old: old.to_reth() } - } + signet_test_utils::specs::ExExNotification::Reorged { old, new } => { + HostNotificationKind::ChainReorged { old: Arc::clone(old), new: Arc::clone(new) } } - } -} - -impl ToRethPrimitive for Arc { - type RethPrimitive = Arc; - - fn to_reth(self) -> Self::RethPrimitive { - Arc::new(ToRethPrimitive::to_reth((*self).clone())) - } -} - -impl ToRethPrimitive for &T { - type RethPrimitive = T::RethPrimitive; - - fn to_reth(self) -> Self::RethPrimitive { - ToRethPrimitive::to_reth(self.clone()) - } + signet_test_utils::specs::ExExNotification::Reverted { old } => { + HostNotificationKind::ChainReverted { old: Arc::clone(old) } + } + }; + HostNotification { kind, safe_block_number: None, finalized_block_number: None } } diff --git a/crates/node-tests/src/lib.rs b/crates/node-tests/src/lib.rs index 10a13f8..38d8fc5 100644 --- a/crates/node-tests/src/lib.rs +++ b/crates/node-tests/src/lib.rs @@ -16,9 +16,9 @@ pub mod constants; /// Test context. mod context; -pub use context::{BalanceChecks, NonceChecks, SignetTestContext}; +pub use context::{BalanceChecks, NonceChecks, SignetTestContext, TestHostNotifier}; -/// Bespoke conversion utilities for converting between alloy and reth types. +/// Conversion utilities for test notifications. pub mod convert; /// Signet node RPC server utilities and helpers. @@ -32,7 +32,7 @@ pub mod types; pub mod utils; pub use utils::run_test; -pub use reth_exex_test_utils::{Adapter, TestExExContext}; +pub use reth_exex_test_utils::Adapter; pub use signet_test_utils::specs::{ HostBlockSpec, NotificationSpec, NotificationWithSidecars, RuBlockSpec, }; diff --git a/crates/node-tests/tests/db.rs b/crates/node-tests/tests/db.rs index 9788342..41e7576 100644 --- a/crates/node-tests/tests/db.rs +++ b/crates/node-tests/tests/db.rs @@ -6,15 +6,17 @@ use signet_hot::{ }; use signet_node::SignetNodeBuilder; use signet_node_config::test_utils::test_config; +use signet_node_tests::TestHostNotifier; +use signet_rpc::{ServeConfig, StorageRpcConfig}; use signet_storage::{CancellationToken, HistoryRead, HistoryWrite, HotKv, UnifiedStorage}; use std::sync::Arc; +use tokio::sync::mpsc; #[serial] #[tokio::test] async fn test_genesis() { let cfg = test_config(); let consts = cfg.constants(); - let (ctx, _) = reth_exex_test_utils::test_exex_context().await.unwrap(); let chain_spec: Arc<_> = cfg.chain_spec().clone(); assert_eq!(chain_spec.genesis().config.chain_id, consts.unwrap().ru_chain_id()); @@ -30,9 +32,34 @@ async fn test_genesis() { let storage = Arc::new(UnifiedStorage::spawn(hot, MemColdBackend::new(), cancel_token.clone())); + // Create a dummy notifier (not used, we only check genesis loading) + let (_sender, receiver) = mpsc::unbounded_channel(); + let notifier = TestHostNotifier::new(receiver); + + // Build a dummy blob cacher + let blob_cacher = signet_blobber::BlobFetcher::builder() + .with_test_pool() + .with_explorer_url("https://example.com") + .with_client(reqwest::Client::new()) + .build_cache() + .unwrap() + .spawn(); + let (_, _) = SignetNodeBuilder::new(cfg.clone()) - .with_ctx(ctx) + .with_notifier(notifier) .with_storage(Arc::clone(&storage)) + .with_alias_oracle(Arc::new(std::sync::Mutex::new(alloy::primitives::map::HashSet::< + alloy::primitives::Address, + >::default()))) + .with_blob_cacher(blob_cacher) + .with_serve_config(ServeConfig { + http: vec![], + http_cors: None, + ws: vec![], + ws_cors: None, + ipc: cfg.ipc_endpoint().map(ToOwned::to_owned), + }) + .with_rpc_config(StorageRpcConfig::default()) .build() .await .unwrap();