Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions crates/node-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
104 changes: 87 additions & 17 deletions crates/node-tests/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
HostBlockSpec, NotificationSpec, NotificationWithSidecars, RuBlockSpec,
convert::ToRethPrimitive,
convert::to_host_notification,
types::{CtxProvider, Log, TestCounterInstance, TestErc20Instance, TestLogInstance},
};
use alloy::{
Expand All @@ -14,36 +14,79 @@ 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},
mem::MemKv,
};
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<HostNotification<Chain>>,
}

impl TestHostNotifier {
/// Create a new test notifier from a receiver.
pub const fn new(receiver: mpsc::UnboundedReceiver<HostNotification<Chain>>) -> 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<Result<HostNotification<Self::Chain>, 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<u64>) {}

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,
Expand All @@ -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<HostNotification<Chain>>,

/// The components for the Signet Node instance
/// The reth test components (for pool access).
pub components: Adapter,

/// The Signet Node status receiver
Expand Down Expand Up @@ -101,7 +144,7 @@ impl SignetTestContext {
#[instrument]
pub async fn new() -> (Self, JoinHandle<eyre::Result<()>>) {
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
Expand Down Expand Up @@ -148,10 +191,36 @@ impl SignetTestContext {

let alias_oracle: Arc<Mutex<HashSet<Address>>> = 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();
Expand Down Expand Up @@ -179,7 +248,7 @@ impl SignetTestContext {
.unwrap();

let this = Self {
handle,
sender,
components,
node_status,
storage,
Expand Down Expand Up @@ -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(&notification.notification);
self.sender.send(host_notification).unwrap();
}

/// Send a notification to the Signet Node instance and wait for it to be
Expand Down
163 changes: 21 additions & 142 deletions crates/node-tests/src/convert.rs
Original file line number Diff line number Diff line change
@@ -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<reth::primitives::Block>;

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<reth::primitives::Block>;

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<T> ToRethPrimitive for Vec<T>
where
T: ToRethPrimitive,
{
type RethPrimitive = Vec<T::RethPrimitive>;

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<Chain> {
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<T: ToRethPrimitive + Clone> ToRethPrimitive for Arc<T> {
type RethPrimitive = Arc<T::RethPrimitive>;

fn to_reth(self) -> Self::RethPrimitive {
Arc::new(ToRethPrimitive::to_reth((*self).clone()))
}
}

impl<T: Clone + ToRethPrimitive> 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 }
}
6 changes: 3 additions & 3 deletions crates/node-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
};
Loading