Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
38 changes: 28 additions & 10 deletions crates/node/src/add_ons/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ use reth_scroll_primitives::ScrollPrimitives;
use reth_scroll_rpc::{eth::ScrollEthApiBuilder, ScrollEthApiError};
use scroll_alloy_evm::ScrollTransactionIntoTxEnv;
use scroll_wire::ScrollWireEvent;
use std::sync::Arc;

mod handle;
pub use handle::ScrollAddOnsHandle;

mod rpc;
pub use rpc::{RollupNodeExtApiClient, RollupNodeExtApiServer, RollupNodeRpcExt};
pub use rpc::{
RollupNodeAdminApiClient, RollupNodeAdminApiServer, RollupNodeApiClient, RollupNodeApiServer,
RollupNodeRpcExt,
};

mod rollup;
pub use rollup::IsDevChain;
Expand Down Expand Up @@ -128,20 +132,34 @@ where
);

let (tx, rx) = tokio::sync::oneshot::channel();
let rollup_node_rpc_ext = RollupNodeRpcExt::<N::Network>::new(rx);
if rollup_node_manager_addon.config().rpc_args.enabled {
rpc_add_ons = rpc_add_ons.extend_rpc_modules(move |ctx| {
ctx.modules.merge_configured(rollup_node_rpc_ext.into_rpc())?;
Ok(())
});
}
let rpc_config = rollup_node_manager_addon.config().rpc_args.clone();

// Register rollupNode API and rollupNodeAdmin API if enabled
let rollup_node_rpc_ext = Arc::new(RollupNodeRpcExt::<N::Network>::new(rx));

rpc_add_ons = rpc_add_ons.extend_rpc_modules(move |ctx| {
// Always register rollupNode API (read-only operations)
if rpc_config.basic_enabled {
ctx.modules
.merge_configured(RollupNodeApiServer::into_rpc(rollup_node_rpc_ext.clone()))?;
}
// Only register rollupNodeAdmin API if enabled (administrative operations)
if rpc_config.admin_enabled {
ctx.modules
.merge_configured(RollupNodeAdminApiServer::into_rpc(rollup_node_rpc_ext))?;
}
Ok(())
});

let rpc_handle = rpc_add_ons.launch_add_ons_with(ctx.clone(), |_| Ok(())).await?;
let (rollup_manager_handle, l1_watcher_tx) =
rollup_node_manager_addon.launch(ctx.clone(), rpc_handle.clone()).await?;

tx.send(rollup_manager_handle.clone())
.map_err(|_| eyre::eyre!("failed to send rollup manager handle"))?;
// Only send handle if RPC is enabled
if rpc_config.basic_enabled || rpc_config.admin_enabled {
tx.send(rollup_manager_handle.clone())
.map_err(|_| eyre::eyre!("failed to send rollup manager handle"))?;
}

Ok(ScrollAddOnsHandle {
rollup_manager_handle,
Expand Down
138 changes: 102 additions & 36 deletions crates/node/src/add_ons/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ use tokio::sync::{oneshot, Mutex, OnceCell};

/// RPC extension for rollup node management operations.
///
/// This struct provides a custom JSON-RPC namespace (`rollupNode`) that exposes
/// rollup management functionality to RPC clients. It manages a connection to the
/// This struct provides custom JSON-RPC namespaces (`rollupNode` and `rollupNodeAdmin`)
/// that expose rollup management functionality to RPC clients. It manages a connection to the
/// rollup manager through a handle that is initialized lazily via a oneshot channel.
///
/// Both `RollupNodeApiServer` and `RollupNodeAdminApiServer` traits are implemented.
/// You can control which APIs to expose by selectively registering them in the RPC modules.
#[derive(Debug)]
pub struct RollupNodeRpcExt<N>
where
Expand All @@ -32,6 +35,9 @@ where
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
{
/// Creates a new RPC extension with a receiver for the rollup manager handle.
///
/// This struct implements both `RollupNodeApiServer` and `RollupNodeAdminApiServer`.
/// Control which APIs are exposed by selectively registering them when extending RPC modules.
pub fn new(rx: oneshot::Receiver<ChainOrchestratorHandle<N>>) -> Self {
Self { rx: Mutex::new(Some(rx)), handle: OnceCell::new() }
}
Expand All @@ -53,31 +59,22 @@ where
}
}

/// Defines the `rollupNode` JSON-RPC namespace for rollup management operations.
/// Defines the `rollupNode` JSON-RPC namespace for basic operations.
///
/// This trait provides a custom RPC namespace that exposes rollup node management
/// functionality to external clients. The namespace is exposed as `rollupNode` and
/// provides methods for controlling automatic sequencing behavior.
/// This trait provides a custom RPC namespace that exposes rollup node status
/// and query functionality to external clients. The namespace is exposed as `rollupNode`.
///
/// # Usage
/// These methods can be called via JSON-RPC using the `rollupNode` namespace:
/// ```json
/// {"jsonrpc": "2.0", "method": "rollupNode_enableAutomaticSequencing", "params": [], "id": 1}
/// {"jsonrpc": "2.0", "method": "rollupNode_status", "params": [], "id": 1}
/// ```
/// or using cast:
/// ```bash
/// cast rpc rollupNode_enableAutomaticSequencing
/// cast rpc rollupNode_status
/// ```
#[rpc(server, client, namespace = "rollupNode")]
pub trait RollupNodeExtApi {
/// Enables automatic sequencing in the rollup node.
#[method(name = "enableAutomaticSequencing")]
async fn enable_automatic_sequencing(&self) -> RpcResult<bool>;

/// Disables automatic sequencing in the rollup node.
#[method(name = "disableAutomaticSequencing")]
async fn disable_automatic_sequencing(&self) -> RpcResult<bool>;

pub trait RollupNodeApi {
/// Returns the current status of the rollup node.
#[method(name = "status")]
async fn status(&self) -> RpcResult<ChainOrchestratorStatus>;
Expand All @@ -94,12 +91,38 @@ pub trait RollupNodeExtApi {
) -> RpcResult<Option<L1MessageEnvelope>>;
}

/// Defines the `rollupNodeAdmin` JSON-RPC namespace for administrative operations.
///
/// This trait provides a custom RPC namespace that exposes rollup node administrative
/// functionality to external clients. The namespace is exposed as `rollupNodeAdmin` and
/// requires the `--rpc.rollup-node-admin` flag to be enabled.
///
/// # Usage
/// These methods can be called via JSON-RPC using the `rollupNodeAdmin` namespace:
/// ```json
/// {"jsonrpc": "2.0", "method": "rollupNodeAdmin_enableAutomaticSequencing", "params": [], "id": 1}
/// ```
/// or using cast:
/// ```bash
/// cast rpc rollupNodeAdmin_enableAutomaticSequencing
/// ```
#[rpc(server, client, namespace = "rollupNodeAdmin")]
pub trait RollupNodeAdminApi {
/// Enables automatic sequencing in the rollup node.
#[method(name = "enableAutomaticSequencing")]
async fn enable_automatic_sequencing(&self) -> RpcResult<bool>;

/// Disables automatic sequencing in the rollup node.
#[method(name = "disableAutomaticSequencing")]
async fn disable_automatic_sequencing(&self) -> RpcResult<bool>;
}

#[async_trait]
impl<N> RollupNodeExtApiServer for RollupNodeRpcExt<N>
impl<N> RollupNodeApiServer for RollupNodeRpcExt<N>
where
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
{
async fn enable_automatic_sequencing(&self) -> RpcResult<bool> {
async fn status(&self) -> RpcResult<ChainOrchestratorStatus> {
let handle = self.rollup_manager_handle().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
Expand All @@ -108,16 +131,16 @@ where
)
})?;

handle.enable_automatic_sequencing().await.map_err(|e| {
handle.status().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
format!("Failed to enable automatic sequencing: {}", e),
format!("Failed to get rollup node status: {}", e),
None::<()>,
)
})
}

async fn disable_automatic_sequencing(&self) -> RpcResult<bool> {
async fn get_l1_message_by_index(&self, index: u64) -> RpcResult<Option<L1MessageEnvelope>> {
let handle = self.rollup_manager_handle().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
Expand All @@ -126,16 +149,19 @@ where
)
})?;

handle.disable_automatic_sequencing().await.map_err(|e| {
handle.get_l1_message_by_key(L1MessageKey::from_queue_index(index)).await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
format!("Failed to disable automatic sequencing: {}", e),
format!("Failed to get L1 message by index: {}", e),
None::<()>,
)
})
}

async fn status(&self) -> RpcResult<ChainOrchestratorStatus> {
async fn get_l1_message_by_key(
&self,
l1_message_key: L1MessageKey,
) -> RpcResult<Option<L1MessageEnvelope>> {
let handle = self.rollup_manager_handle().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
Expand All @@ -144,16 +170,22 @@ where
)
})?;

handle.status().await.map_err(|e| {
handle.get_l1_message_by_key(l1_message_key).await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
format!("Failed to get rollup node status: {}", e),
format!("Failed to get L1 message by key: {}", e),
None::<()>,
)
})
}
}

async fn get_l1_message_by_index(&self, index: u64) -> RpcResult<Option<L1MessageEnvelope>> {
#[async_trait]
impl<N> RollupNodeAdminApiServer for RollupNodeRpcExt<N>
where
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
{
async fn enable_automatic_sequencing(&self) -> RpcResult<bool> {
let handle = self.rollup_manager_handle().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
Expand All @@ -162,19 +194,16 @@ where
)
})?;

handle.get_l1_message_by_key(L1MessageKey::from_queue_index(index)).await.map_err(|e| {
handle.enable_automatic_sequencing().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
format!("Failed to get L1 message by index: {}", e),
format!("Failed to enable automatic sequencing: {}", e),
None::<()>,
)
})
}

async fn get_l1_message_by_key(
&self,
l1_message_key: L1MessageKey,
) -> RpcResult<Option<L1MessageEnvelope>> {
async fn disable_automatic_sequencing(&self) -> RpcResult<bool> {
let handle = self.rollup_manager_handle().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
Expand All @@ -183,12 +212,49 @@ where
)
})?;

handle.get_l1_message_by_key(l1_message_key).await.map_err(|e| {
handle.disable_automatic_sequencing().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
format!("Failed to get L1 message by key: {}", e),
format!("Failed to disable automatic sequencing: {}", e),
None::<()>,
)
})
}
}

// Implement RollupNodeApiServer for Arc<RollupNodeRpcExt<N>> to allow shared ownership
#[async_trait]
impl<N> RollupNodeApiServer for std::sync::Arc<RollupNodeRpcExt<N>>
where
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
{
async fn status(&self) -> RpcResult<ChainOrchestratorStatus> {
(**self).status().await
}

async fn get_l1_message_by_index(&self, index: u64) -> RpcResult<Option<L1MessageEnvelope>> {
(**self).get_l1_message_by_index(index).await
}

async fn get_l1_message_by_key(
&self,
l1_message_key: L1MessageKey,
) -> RpcResult<Option<L1MessageEnvelope>> {
(**self).get_l1_message_by_key(l1_message_key).await
}
}

// Implement RollupNodeAdminApiServer for Arc<RollupNodeRpcExt<N>> to allow shared ownership
#[async_trait]
impl<N> RollupNodeAdminApiServer for std::sync::Arc<RollupNodeRpcExt<N>>
where
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
{
async fn enable_automatic_sequencing(&self) -> RpcResult<bool> {
(**self).enable_automatic_sequencing().await
}

async fn disable_automatic_sequencing(&self) -> RpcResult<bool> {
(**self).disable_automatic_sequencing().await
}
}
9 changes: 6 additions & 3 deletions crates/node/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,9 +751,12 @@ pub struct SignerArgs {
/// The arguments for the rpc.
#[derive(Debug, Default, Clone, clap::Args)]
pub struct RpcArgs {
/// A boolean to represent if the rollup node rpc should be enabled.
#[arg(long = "rpc.rollup-node", help = "Enable the rollup node RPC namespace")]
pub enabled: bool,
/// A boolean to represent if the rollup node basic rpc should be enabled.
#[arg(long = "rpc.rollup-node", num_args=0..=1, default_value_t = true, help = "Enable the rollup node basic RPC namespace(default: true)")]
pub basic_enabled: bool,
/// A boolean to represent if the rollup node admin rpc should be enabled.
#[arg(long = "rpc.rollup-node-admin", help = "Enable the rollup node admin RPC namespace")]
pub admin_enabled: bool,
}

impl SignerArgs {
Expand Down
2 changes: 1 addition & 1 deletion crates/node/src/test_utils/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ impl TestFixtureBuilder {
gas_price_oracle_args: RollupNodeGasPriceOracleArgs::default(),
consensus_args: ConsensusArgs::noop(),
database: None,
rpc_args: RpcArgs { enabled: true },
rpc_args: RpcArgs { basic_enabled: true, admin_enabled: true },
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/node/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ pub fn default_test_scroll_rollup_node_config() -> ScrollRollupNodeConfig {
gas_price_oracle_args: crate::RollupNodeGasPriceOracleArgs::default(),
consensus_args: ConsensusArgs::noop(),
database: None,
rpc_args: RpcArgs { enabled: true },
rpc_args: RpcArgs { basic_enabled: true, admin_enabled: true },
}
}

Expand Down Expand Up @@ -279,6 +279,6 @@ pub fn default_sequencer_test_scroll_rollup_node_config() -> ScrollRollupNodeCon
gas_price_oracle_args: crate::RollupNodeGasPriceOracleArgs::default(),
consensus_args: ConsensusArgs::noop(),
database: None,
rpc_args: RpcArgs { enabled: true },
rpc_args: RpcArgs { basic_enabled: true, admin_enabled: true },
}
}
6 changes: 3 additions & 3 deletions crates/node/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use rollup_node::{
generate_tx, setup_engine, EventAssertions, NetworkHelperProvider, ReputationChecks,
TestFixture,
},
RollupNodeContext, RollupNodeExtApiClient,
RollupNodeAdminApiClient, RollupNodeContext,
};
use rollup_node_chain_orchestrator::ChainOrchestratorEvent;
use rollup_node_primitives::{sig_encode_hash, BatchCommitData, BlockInfo};
Expand Down Expand Up @@ -1504,7 +1504,7 @@ async fn can_rpc_enable_disable_sequencing() -> eyre::Result<()> {

// Disable automatic sequencing via RPC
let client = fixture.sequencer().node.rpc_client().expect("Should have rpc client");
let result = RollupNodeExtApiClient::disable_automatic_sequencing(&client).await?;
let result = RollupNodeAdminApiClient::disable_automatic_sequencing(&client).await?;
assert!(result, "Disable automatic sequencing should return true");

// Wait a bit and verify no more blocks are produced automatically.
Expand Down Expand Up @@ -1532,7 +1532,7 @@ async fn can_rpc_enable_disable_sequencing() -> eyre::Result<()> {
fixture.expect_event_on(1).chain_extended(block_num_after_wait + 1).await?;

// Enable sequencing again
let result = RollupNodeExtApiClient::enable_automatic_sequencing(&client).await?;
let result = RollupNodeAdminApiClient::enable_automatic_sequencing(&client).await?;
assert!(result, "Enable automatic sequencing should return true");

// Make sure automatic sequencing resumes
Expand Down
1 change: 1 addition & 0 deletions tests/launch_rollup_node_sequencer.bash
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ exec rollup-node node --chain /l2reth/l2reth-genesis-e2e.json --datadir=/l2reth
--http --http.addr=0.0.0.0 --http.port=8545 --http.corsdomain "*" --http.api admin,debug,eth,net,trace,txpool,web3,rpc,reth,ots,flashbots,miner,mev \
--ws --ws.addr=0.0.0.0 --ws.port=8546 --ws.api admin,debug,eth,net,trace,txpool,web3,rpc,reth,ots,flashbots,miner,mev \
--rpc.rollup-node \
--rpc.rollup-node-admin \
--log.stdout.format log-fmt -vvv \
--sequencer.enabled \
--sequencer.allow-empty-blocks \
Expand Down
Loading