diff --git a/Cargo.toml b/Cargo.toml index 294c861..9266e64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ incremental = false signet-blobber = { version = "0.16.0-rc.7", path = "crates/blobber" } signet-block-processor = { version = "0.16.0-rc.7", path = "crates/block-processor" } signet-genesis = { version = "0.16.0-rc.7", path = "crates/genesis" } +signet-host-reth = { version = "0.16.0-rc.7", path = "crates/host-reth" } signet-node = { version = "0.16.0-rc.7", path = "crates/node" } signet-node-config = { version = "0.16.0-rc.7", path = "crates/node-config" } signet-node-tests = { version = "0.16.0-rc.7", path = "crates/node-tests" } diff --git a/crates/host-reth/Cargo.toml b/crates/host-reth/Cargo.toml new file mode 100644 index 0000000..b9efe47 --- /dev/null +++ b/crates/host-reth/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "signet-host-reth" +description = "Reth ExEx implementation of the `HostNotifier` trait for signet-node." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +signet-node-types.workspace = true +signet-blobber.workspace = true +signet-extract.workspace = true +signet-rpc.workspace = true +signet-block-processor.workspace = true +signet-types.workspace = true + +alloy.workspace = true +reth.workspace = true +reth-exex.workspace = true +reth-node-api.workspace = true +reth-stages-types.workspace = true + +eyre.workspace = true +futures-util.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true diff --git a/crates/host-reth/README.md b/crates/host-reth/README.md new file mode 100644 index 0000000..c381b88 --- /dev/null +++ b/crates/host-reth/README.md @@ -0,0 +1,3 @@ +# signet-host-reth + +Reth ExEx implementation of the `HostNotifier` trait for signet-node. diff --git a/crates/node/src/alias.rs b/crates/host-reth/src/alias.rs similarity index 60% rename from crates/node/src/alias.rs rename to crates/host-reth/src/alias.rs index c682c87..9cbad0a 100644 --- a/crates/node/src/alias.rs +++ b/crates/host-reth/src/alias.rs @@ -46,8 +46,7 @@ impl RethAliasOracle { impl AliasOracle for RethAliasOracle { fn should_alias(&self, address: Address) -> impl Future> + Send { - let result = self.check_alias(address); - future::ready(result) + future::ready(self.check_alias(address)) } } @@ -74,38 +73,15 @@ impl AliasOracleFactory for RethAliasOracleFactory { type Oracle = RethAliasOracle; fn create(&self) -> eyre::Result { - // ## Why `Latest` instead of a pinned host height + // We use `Latest` rather than a pinned host height because pinning + // would require every node to be an archive node, which is impractical. // - // We use `Latest` rather than pinning to a specific host block - // height because pinning would require every node to be an archive - // node in order to sync historical state, which is impractical. - // - // ## Why `Latest` is safe - // - // An EOA cannot become a non-delegation contract without a birthday - // attack (c.f. EIP-3607). CREATE and CREATE2 addresses are - // deterministic and cannot target an existing EOA. EIP-7702 - // delegations are explicitly excluded by the `is_eip7702()` check - // in `should_alias`, so delegated EOAs are never aliased. This - // means the alias status of an address is stable across blocks - // under normal conditions, making `Latest` equivalent to any - // pinned height. - // - // ## The only risk: birthday attacks - // - // A birthday attack could produce a CREATE/CREATE2 collision with - // an existing EOA, causing `should_alias` to return a false - // positive. This is computationally infeasible for the foreseeable - // future (~2^80 work), and if it ever becomes practical we can - // revisit this decision. - // - // ## Over-aliasing vs under-aliasing - // - // Even in the birthday attack scenario, the result is - // over-aliasing (a false positive), which is benign: a transaction - // sender gets an aliased address when it shouldn't. The dangerous - // failure mode — under-aliasing — cannot occur here because - // contract bytecode is never removed once deployed. + // This is safe because alias status is stable across blocks: an EOA + // cannot become a non-delegation contract without a birthday attack + // (c.f. EIP-3607), and EIP-7702 delegations are excluded by + // `is_eip7702()`. Even in the (computationally infeasible ~2^80) + // birthday attack scenario, the result is a benign false-positive + // (over-aliasing), never a dangerous false-negative. self.0 .state_by_block_number_or_tag(alloy::eips::BlockNumberOrTag::Latest) .map(RethAliasOracle) diff --git a/crates/host-reth/src/chain.rs b/crates/host-reth/src/chain.rs new file mode 100644 index 0000000..3f6e5c7 --- /dev/null +++ b/crates/host-reth/src/chain.rs @@ -0,0 +1,90 @@ +use alloy::{consensus::Block, consensus::BlockHeader}; +use reth::primitives::{EthPrimitives, RecoveredBlock}; +use reth::providers::Chain; +use signet_blobber::RecoveredBlockShim; +use signet_extract::{BlockAndReceipts, Extractable}; +use signet_types::primitives::TransactionSigned; +use std::sync::Arc; + +/// Reth's recovered block type, aliased for readability. +type RethRecovered = RecoveredBlock>; + +/// An owning wrapper around reth's [`Chain`] that implements [`Extractable`] +/// with O(1) metadata accessors. +/// +/// # Usage +/// +/// `RethChain` is typically obtained from [`HostNotification`] events, not +/// constructed directly. To extract blocks and receipts: +/// +/// ```ignore +/// # // Requires reth ExEx runtime — shown for API illustration only. +/// use signet_extract::Extractable; +/// +/// fn process(chain: &RethChain) { +/// for bar in chain.blocks_and_receipts() { +/// println!("block receipts: {}", bar.receipts.len()); +/// } +/// } +/// ``` +/// +/// [`HostNotification`]: signet_node_types::HostNotification +#[derive(Debug)] +pub struct RethChain { + inner: Arc>, +} + +impl RethChain { + /// Wrap a reth chain. + pub const fn new(chain: Arc>) -> Self { + Self { inner: chain } + } +} + +impl Extractable for RethChain { + type Block = RecoveredBlockShim; + type Receipt = reth::primitives::Receipt; + + fn blocks_and_receipts( + &self, + ) -> impl Iterator> { + self.inner.blocks_and_receipts().map(|(block, receipts)| { + // Compile-time check: RecoveredBlockShim must have the same + // layout as RethRecovered (guaranteed by #[repr(transparent)] + // on RecoveredBlockShim in signet-blobber/src/shim.rs). + const { + assert!( + size_of::() == size_of::(), + "RecoveredBlockShim layout diverged from RethRecovered" + ); + assert!( + align_of::() == align_of::(), + "RecoveredBlockShim alignment diverged from RethRecovered" + ); + } + // SAFETY: `RecoveredBlockShim` is `#[repr(transparent)]` over + // `RethRecovered`, so these types have identical memory layouts. + // The lifetime of the reference is tied to `self.inner` (the + // `Arc`), which outlives the returned iterator. + let block = + unsafe { std::mem::transmute::<&RethRecovered, &RecoveredBlockShim>(block) }; + BlockAndReceipts { block, receipts } + }) + } + + fn first_number(&self) -> Option { + Some(self.inner.first().number()) + } + + fn tip_number(&self) -> Option { + Some(self.inner.tip().number()) + } + + fn len(&self) -> usize { + self.inner.len() + } + + fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} diff --git a/crates/host-reth/src/config.rs b/crates/host-reth/src/config.rs new file mode 100644 index 0000000..c759819 --- /dev/null +++ b/crates/host-reth/src/config.rs @@ -0,0 +1,89 @@ +use reth::args::RpcServerArgs; +use signet_rpc::{ServeConfig, StorageRpcConfig}; +use std::net::SocketAddr; + +/// Extract [`StorageRpcConfig`] values from reth's host RPC settings. +/// +/// Fields with no reth equivalent retain their defaults. +pub fn rpc_config_from_args(args: &RpcServerArgs) -> StorageRpcConfig { + let gpo = &args.gas_price_oracle; + StorageRpcConfig::builder() + .rpc_gas_cap(args.rpc_gas_cap) + .max_tracing_requests(args.rpc_max_tracing_requests) + .gas_oracle_block_count(u64::from(gpo.blocks)) + .gas_oracle_percentile(f64::from(gpo.percentile)) + .ignore_price(Some(u128::from(gpo.ignore_price))) + .max_price(Some(u128::from(gpo.max_price))) + .build() +} + +/// Convert reth [`RpcServerArgs`] into a reth-free [`ServeConfig`]. +pub fn serve_config_from_args(args: &RpcServerArgs) -> ServeConfig { + let http = + if args.http { vec![SocketAddr::from((args.http_addr, args.http_port))] } else { vec![] }; + let ws = if args.ws { vec![SocketAddr::from((args.ws_addr, args.ws_port))] } else { vec![] }; + let ipc = if !args.ipcdisable { Some(args.ipcpath.clone()) } else { None }; + + ServeConfig { + http, + http_cors: args.http_corsdomain.clone(), + ws, + ws_cors: args.ws_allowed_origins.clone(), + ipc, + } +} + +#[cfg(test)] +mod tests { + use crate::config::{rpc_config_from_args, serve_config_from_args}; + use reth::args::RpcServerArgs; + + #[test] + fn rpc_config_from_default_args() { + let args = RpcServerArgs::default(); + let gpo = &args.gas_price_oracle; + let config = rpc_config_from_args(&args); + + assert_eq!(config.rpc_gas_cap, args.rpc_gas_cap); + assert_eq!(config.max_tracing_requests, args.rpc_max_tracing_requests); + assert_eq!(config.gas_oracle_block_count, u64::from(gpo.blocks)); + assert_eq!(config.gas_oracle_percentile, f64::from(gpo.percentile)); + assert_eq!(config.ignore_price, Some(u128::from(gpo.ignore_price))); + assert_eq!(config.max_price, Some(u128::from(gpo.max_price))); + } + + #[test] + fn serve_config_http_disabled_by_default() { + let args = RpcServerArgs::default(); + let config = serve_config_from_args(&args); + + assert!(config.http.is_empty()); + assert!(config.ws.is_empty()); + } + + #[test] + fn serve_config_http_enabled() { + let args = RpcServerArgs { http: true, ..Default::default() }; + let config = serve_config_from_args(&args); + + assert_eq!(config.http.len(), 1); + assert_eq!(config.http[0].port(), args.http_port); + } + + #[test] + fn serve_config_ws_enabled() { + let args = RpcServerArgs { ws: true, ..Default::default() }; + let config = serve_config_from_args(&args); + + assert_eq!(config.ws.len(), 1); + assert_eq!(config.ws[0].port(), args.ws_port); + } + + #[test] + fn serve_config_ipc_enabled_by_default() { + let args = RpcServerArgs::default(); + let config = serve_config_from_args(&args); + + assert!(config.ipc.is_some()); + } +} diff --git a/crates/host-reth/src/error.rs b/crates/host-reth/src/error.rs new file mode 100644 index 0000000..1391db2 --- /dev/null +++ b/crates/host-reth/src/error.rs @@ -0,0 +1,24 @@ +use reth_exex::ExExEvent; + +/// Errors from the [`RethHostNotifier`](crate::RethHostNotifier). +#[derive(Debug, thiserror::Error)] +pub enum RethHostError { + /// A notification stream error forwarded from reth. + #[error("notification stream error: {0}")] + Notification(#[source] Box), + /// The provider failed to look up a header or block tag. + #[error("provider error: {0}")] + Provider(#[from] reth::providers::ProviderError), + /// Failed to send an ExEx event back to the host. + #[error("failed to send ExEx event")] + EventSend(#[from] tokio::sync::mpsc::error::SendError), + /// A required header was missing from the provider. + #[error("missing header for block {0}")] + MissingHeader(u64), +} + +impl From for RethHostError { + fn from(e: eyre::Report) -> Self { + Self::Notification(e.into()) + } +} diff --git a/crates/host-reth/src/lib.rs b/crates/host-reth/src/lib.rs new file mode 100644 index 0000000..3ff1e46 --- /dev/null +++ b/crates/host-reth/src/lib.rs @@ -0,0 +1,26 @@ +#![doc = include_str!("../README.md")] +#![warn( + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + unreachable_pub, + clippy::missing_const_for_fn, + rustdoc::all +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +mod alias; +pub use alias::{RethAliasOracle, RethAliasOracleFactory}; +mod error; +pub use error::RethHostError; + +mod chain; +pub use chain::RethChain; + +mod config; +pub use config::{rpc_config_from_args, serve_config_from_args}; + +mod notifier; +pub use notifier::{DecomposedContext, RethHostNotifier, decompose_exex_context}; diff --git a/crates/host-reth/src/notifier.rs b/crates/host-reth/src/notifier.rs new file mode 100644 index 0000000..1e96a5a --- /dev/null +++ b/crates/host-reth/src/notifier.rs @@ -0,0 +1,195 @@ +use crate::{ + RethChain, + config::{rpc_config_from_args, serve_config_from_args}, + error::RethHostError, +}; +use alloy::eips::BlockNumHash; +use futures_util::StreamExt; +use reth::{ + chainspec::EthChainSpec, + primitives::EthPrimitives, + providers::{BlockIdReader, HeaderProvider}, +}; +use reth_exex::{ExExContext, ExExEvent, ExExNotifications, ExExNotificationsStream}; +use reth_node_api::{FullNodeComponents, NodeTypes}; +use reth_stages_types::ExecutionStageThresholds; +use signet_node_types::{HostNotification, HostNotificationKind, HostNotifier}; +use signet_rpc::{ServeConfig, StorageRpcConfig}; +use std::sync::Arc; +use tracing::{debug, error}; + +/// Reth ExEx implementation of [`HostNotifier`]. +/// +/// Wraps reth's notification stream, provider, and event sender. All hash +/// resolution happens internally — consumers only work with block numbers. +pub struct RethHostNotifier { + notifications: ExExNotifications, + provider: Host::Provider, + events: tokio::sync::mpsc::UnboundedSender, +} + +impl core::fmt::Debug for RethHostNotifier { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("RethHostNotifier").finish_non_exhaustive() + } +} + +/// The output of [`decompose_exex_context`]. +pub struct DecomposedContext { + /// The host notifier adapter. + pub notifier: RethHostNotifier, + /// Plain RPC serve config. + pub serve_config: ServeConfig, + /// Plain RPC storage config. + pub rpc_config: StorageRpcConfig, + /// The transaction pool, for blob cacher construction. + pub pool: Host::Pool, + /// The chain name, for tracing. + pub chain_name: String, +} + +impl core::fmt::Debug for DecomposedContext { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("DecomposedContext") + .field("chain_name", &self.chain_name) + .finish_non_exhaustive() + } +} + +/// Decompose a reth [`ExExContext`] into a [`RethHostNotifier`] and +/// associated configuration values. +/// +/// This is the primary entry point for integrating with reth's ExEx +/// framework. Typical usage in an ExEx `init` function: +/// +/// ```ignore +/// # // Requires ExExContext — shown for API illustration only. +/// use signet_host_reth::decompose_exex_context; +/// +/// async fn init(ctx: ExExContext) -> eyre::Result<()> { +/// let decomposed = decompose_exex_context(ctx); +/// // decomposed.notifier implements HostNotifier +/// // decomposed.serve_config / rpc_config are reth-free +/// // decomposed.pool is the transaction pool handle +/// Ok(()) +/// } +/// ``` +/// +/// This splits the ExEx context into: +/// - A [`RethHostNotifier`] (implements [`HostNotifier`]) +/// - A [`ServeConfig`] (plain RPC server config) +/// - A [`StorageRpcConfig`] (gas oracle settings) +/// - The transaction pool handle +/// - A chain name for tracing +pub fn decompose_exex_context(ctx: ExExContext) -> DecomposedContext +where + Host: FullNodeComponents, + Host::Types: NodeTypes, +{ + let chain_name = ctx.config.chain.chain().to_string(); + let serve_config = serve_config_from_args(&ctx.config.rpc); + let rpc_config = rpc_config_from_args(&ctx.config.rpc); + let pool = ctx.pool().clone(); + let provider = ctx.provider().clone(); + + let notifier = + RethHostNotifier { notifications: ctx.notifications, provider, events: ctx.events }; + + DecomposedContext { notifier, serve_config, rpc_config, pool, chain_name } +} + +impl HostNotifier for RethHostNotifier +where + Host: FullNodeComponents, + Host::Types: NodeTypes, +{ + type Chain = RethChain; + type Error = RethHostError; + + async fn next_notification( + &mut self, + ) -> Option, Self::Error>> { + let notification = self.notifications.next().await?; + let notification = match notification { + Ok(n) => n, + Err(e) => return Some(Err(e.into())), + }; + + // Read safe/finalized from the provider at notification time. + let safe_block_number = self + .provider + .safe_block_number() + .inspect_err(|e| { + debug!(%e, "failed to read safe block number from provider"); + }) + .ok() + .flatten(); + let finalized_block_number = self + .provider + .finalized_block_number() + .inspect_err(|e| { + debug!(%e, "failed to read finalized block number from provider"); + }) + .ok() + .flatten(); + + let kind = match notification { + reth_exex::ExExNotification::ChainCommitted { new } => { + HostNotificationKind::ChainCommitted { new: Arc::new(RethChain::new(new)) } + } + reth_exex::ExExNotification::ChainReverted { old } => { + HostNotificationKind::ChainReverted { old: Arc::new(RethChain::new(old)) } + } + reth_exex::ExExNotification::ChainReorged { old, new } => { + HostNotificationKind::ChainReorged { + old: Arc::new(RethChain::new(old)), + new: Arc::new(RethChain::new(new)), + } + } + }; + + Some(Ok(HostNotification { kind, safe_block_number, finalized_block_number })) + } + + fn set_head(&mut self, block_number: u64) { + let head = self + .provider + .sealed_header(block_number) + .inspect_err(|e| error!(block_number, %e, "failed to look up header for set_head")) + .expect("failed to look up header for set_head") + .map(|h| BlockNumHash { number: block_number, hash: h.hash() }) + .unwrap_or_else(|| { + debug!(block_number, "header not found for set_head, falling back to genesis"); + let genesis = self + .provider + .sealed_header(0) + .inspect_err(|e| error!(%e, "failed to look up genesis header")) + .expect("failed to look up genesis header") + .expect("genesis header missing"); + BlockNumHash { number: 0, hash: genesis.hash() } + }); + + self.notifications.set_with_head(reth_exex::ExExHead { block: head }); + } + + fn set_backfill_thresholds(&mut self, max_blocks: Option) { + if let Some(max_blocks) = max_blocks { + self.notifications.set_backfill_thresholds(ExecutionStageThresholds { + max_blocks: Some(max_blocks), + ..Default::default() + }); + debug!(max_blocks, "configured backfill thresholds"); + } + } + + fn send_finished_height(&self, block_number: u64) -> Result<(), Self::Error> { + let header = self + .provider + .sealed_header(block_number)? + .ok_or(RethHostError::MissingHeader(block_number))?; + + let hash = header.hash(); + self.events.send(ExExEvent::FinishedHeight(BlockNumHash { number: block_number, hash }))?; + Ok(()) + } +} diff --git a/crates/node-tests/Cargo.toml b/crates/node-tests/Cargo.toml index a6060f0..358984c 100644 --- a/crates/node-tests/Cargo.toml +++ b/crates/node-tests/Cargo.toml @@ -11,11 +11,14 @@ repository.workspace = true [dependencies] signet-node.workspace = true signet-node-config = { workspace = true, features = ["test_utils"] } +signet-rpc.workspace = true +signet-blobber.workspace = true signet-cold = { workspace = true, features = ["in-memory"] } signet-constants.workspace = true signet-evm.workspace = true signet-genesis.workspace = true +signet-host-reth.workspace = true signet-hot = { workspace = true, features = ["in-memory"] } signet-storage.workspace = true signet-storage-types.workspace = true @@ -31,6 +34,7 @@ reth-exex-test-utils.workspace = true reth-node-api.workspace = true eyre.workspace = true +reqwest.workspace = true tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true diff --git a/crates/node-tests/src/blob_test_utils.rs b/crates/node-tests/src/blob_test_utils.rs new file mode 100644 index 0000000..7b435ad --- /dev/null +++ b/crates/node-tests/src/blob_test_utils.rs @@ -0,0 +1,23 @@ +use reth::transaction_pool::TransactionPool; +use signet_blobber::CacheHandle; +use signet_node_config::SignetNodeConfig; + +/// Build a blob [`CacheHandle`] for test use from a config and transaction +/// pool. +/// +/// Uses a fresh `reqwest::Client` and spawns with [`SimpleCoder`]. +/// +/// [`SimpleCoder`]: alloy::consensus::SimpleCoder +pub fn test_blob_cacher(cfg: &SignetNodeConfig, pool: Pool) -> CacheHandle +where + Pool: TransactionPool + 'static, +{ + signet_blobber::BlobFetcher::builder() + .with_config(cfg.block_extractor()) + .unwrap() + .with_pool(pool) + .with_client(reqwest::Client::new()) + .build_cache() + .unwrap() + .spawn::() +} diff --git a/crates/node-tests/src/context.rs b/crates/node-tests/src/context.rs index 190ac9f..e54f85d 100644 --- a/crates/node-tests/src/context.rs +++ b/crates/node-tests/src/context.rs @@ -18,6 +18,7 @@ use reth::transaction_pool::{TransactionOrigin, TransactionPool, test_utils::Moc use reth_exex_test_utils::{Adapter, TestExExHandle}; use reth_node_api::FullNodeComponents; use signet_cold::{ColdStorageReadHandle, mem::MemColdBackend}; +use signet_host_reth::decompose_exex_context; use signet_hot::{ db::{HotDbRead, UnsafeDbWrite}, mem::MemKv, @@ -104,6 +105,9 @@ impl SignetTestContext { let (ctx, handle) = reth_exex_test_utils::test_exex_context().await.unwrap(); let components = ctx.components.clone(); + // Decompose the ExEx context into notifier + configs + let decomposed = decompose_exex_context(ctx); + // set up Signet Node storage let constants = cfg.constants().unwrap(); @@ -148,10 +152,26 @@ impl SignetTestContext { let alias_oracle: Arc>> = Arc::new(Mutex::new(HashSet::default())); + let blob_cacher = crate::test_blob_cacher(&cfg, decomposed.pool); + + // Build serve config from the Signet test config rather than the + // reth defaults (which have IPC/HTTP disabled). + let serve_config = signet_rpc::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(decomposed.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(decomposed.rpc_config) + .with_client(reqwest::Client::new()) .build() .await .unwrap(); diff --git a/crates/node-tests/src/lib.rs b/crates/node-tests/src/lib.rs index 10a13f8..8275716 100644 --- a/crates/node-tests/src/lib.rs +++ b/crates/node-tests/src/lib.rs @@ -14,6 +14,9 @@ /// Test constants. pub mod constants; +/// Blob cacher test utilities. +mod blob_test_utils; +pub use blob_test_utils::test_blob_cacher; /// Test context. mod context; pub use context::{BalanceChecks, NonceChecks, SignetTestContext}; diff --git a/crates/node-tests/tests/db.rs b/crates/node-tests/tests/db.rs index 9788342..040e387 100644 --- a/crates/node-tests/tests/db.rs +++ b/crates/node-tests/tests/db.rs @@ -1,5 +1,7 @@ +use alloy::primitives::map::HashSet; use serial_test::serial; use signet_cold::mem::MemColdBackend; +use signet_host_reth::decompose_exex_context; use signet_hot::{ db::{HotDbRead, UnsafeDbWrite}, mem::MemKv, @@ -7,7 +9,7 @@ use signet_hot::{ use signet_node::SignetNodeBuilder; use signet_node_config::test_utils::test_config; use signet_storage::{CancellationToken, HistoryRead, HistoryWrite, HotKv, UnifiedStorage}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; #[serial] #[tokio::test] @@ -19,6 +21,8 @@ async fn test_genesis() { let chain_spec: Arc<_> = cfg.chain_spec().clone(); assert_eq!(chain_spec.genesis().config.chain_id, consts.unwrap().ru_chain_id()); + let decomposed = decompose_exex_context(ctx); + let cancel_token = CancellationToken::new(); let hot = MemKv::new(); { @@ -30,9 +34,18 @@ async fn test_genesis() { let storage = Arc::new(UnifiedStorage::spawn(hot, MemColdBackend::new(), cancel_token.clone())); + let blob_cacher = signet_node_tests::test_blob_cacher(&cfg, decomposed.pool); + + let alias_oracle: Arc>> = Arc::new(Mutex::new(HashSet::default())); + let (_, _) = SignetNodeBuilder::new(cfg.clone()) - .with_ctx(ctx) + .with_notifier(decomposed.notifier) .with_storage(Arc::clone(&storage)) + .with_alias_oracle(Arc::clone(&alias_oracle)) + .with_blob_cacher(blob_cacher) + .with_serve_config(decomposed.serve_config) + .with_rpc_config(decomposed.rpc_config) + .with_client(reqwest::Client::new()) .build() .await .unwrap();