Skip to content
Merged
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/manager/src/manager/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ pub enum RollupManagerCommand<N: FullNetwork<Primitives = ScrollNetworkPrimitive
NetworkHandle(oneshot::Sender<ScrollNetworkHandle<N>>),
/// Update the head of the fcs in the engine driver.
UpdateFcsHead(BlockInfo),
/// Enable automatic sequencing.
EnableAutomaticSequencing(oneshot::Sender<bool>),
/// Disable automatic sequencing.
DisableAutomaticSequencing(oneshot::Sender<bool>),
}
14 changes: 14 additions & 0 deletions crates/manager/src/manager/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,18 @@ impl<N: FullNetwork<Primitives = ScrollNetworkPrimitives>> RollupManagerHandle<N
pub async fn update_fcs_head(&self, head: BlockInfo) {
self.send_command(RollupManagerCommand::UpdateFcsHead(head)).await;
}

/// Sends a command to the rollup manager to enable automatic sequencing.
pub async fn enable_automatic_sequencing(&self) -> Result<bool, oneshot::error::RecvError> {
let (tx, rx) = oneshot::channel();
self.send_command(RollupManagerCommand::EnableAutomaticSequencing(tx)).await;
rx.await
}

/// Sends a command to the rollup manager to disable automatic sequencing.
pub async fn disable_automatic_sequencing(&self) -> Result<bool, oneshot::error::RecvError> {
let (tx, rx) = oneshot::channel();
self.send_command(RollupManagerCommand::DisableAutomaticSequencing(tx)).await;
rx.await
}
}
27 changes: 26 additions & 1 deletion crates/manager/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use tokio::{
time::Interval,
};
use tokio_stream::wrappers::ReceiverStream;
use tracing::{error, trace, warn};
use tracing::{error, info, trace, warn};

use rollup_node_providers::{L1MessageProvider, L1Provider};
use scroll_db::{Database, DatabaseError};
Expand Down Expand Up @@ -112,6 +112,8 @@ pub struct RollupNodeManager<
signer: Option<SignerHandle>,
/// The trigger for the block building process.
block_building_trigger: Option<Interval>,
/// The original block time configuration for restoring automatic sequencing.
block_time_config: Option<u64>,
}

/// The current status of the rollup manager.
Expand Down Expand Up @@ -145,6 +147,7 @@ impl<
.field("event_sender", &self.event_sender)
.field("sequencer", &self.sequencer)
.field("block_building_trigger", &self.block_building_trigger)
.field("block_time_config", &self.block_time_config)
.finish()
}
}
Expand Down Expand Up @@ -189,6 +192,7 @@ where
sequencer,
signer,
block_building_trigger: block_time.map(delayed_interval),
block_time_config: block_time,
};
(rnm, RollupManagerHandle::new(handle_tx))
}
Expand Down Expand Up @@ -512,6 +516,27 @@ where
tx.send(network_handle.clone())
.expect("Failed to send network handle to handle");
}
RollupManagerCommand::EnableAutomaticSequencing(tx) => {
let success = if let Some(block_time) = this.block_time_config {
if this.block_building_trigger.is_none() {
this.block_building_trigger = Some(delayed_interval(block_time));
info!(target: "scroll::node::manager", "Enabled automatic sequencing with interval {}ms", block_time);
} else {
info!(target: "scroll::node::manager", "Automatic sequencing already enabled");
}
true
} else {
warn!(target: "scroll::node::manager", "Cannot enable automatic sequencing: sequencer and block time not configured");
false
};
tx.send(success).expect("Failed to send enable automatic sequencing response");
}
RollupManagerCommand::DisableAutomaticSequencing(tx) => {
let was_enabled = this.block_building_trigger.is_some();
this.block_building_trigger = None;
info!(target: "scroll::node::manager", "Disabled automatic sequencing (was enabled: {})", was_enabled);
tx.send(true).expect("Failed to send disable automatic sequencing response");
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ path = "src/main.rs"
workspace = true

[dependencies]
# async trait support
async-trait.workspace = true

# alloy
alloy-chains.workspace = true
alloy-primitives.workspace = true
Expand Down Expand Up @@ -88,6 +91,7 @@ scroll-network.workspace = true
auto_impl.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
eyre.workspace = true
jsonrpsee = { version = "0.25.1", features = ["server", "macros"] }
reqwest.workspace = true
tokio.workspace = true
tracing.workspace = true
Expand Down
15 changes: 15 additions & 0 deletions crates/node/src/add_ons/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ use scroll_wire::ScrollWireEvent;
mod handle;
pub use handle::ScrollAddOnsHandle;

mod rpc;
pub use rpc::{RollupNodeExtApiServer, RollupNodeRpcExt};

mod rollup;
pub use rollup::IsDevChain;
use rollup::RollupManagerAddOn;
Expand Down Expand Up @@ -123,9 +126,21 @@ where
rpc_add_ons.eth_api_builder.with_propagate_local_transactions(
!ctx.config.txpool.no_local_transactions_propagation,
);

let (tx, rx) = tokio::sync::oneshot::channel();
let rollup_node_rpc_ext = RollupNodeRpcExt::<N::Network>::new(rx);
rpc_add_ons = rpc_add_ons.extend_rpc_modules(move |ctx| {
ctx.modules.merge_configured(rollup_node_rpc_ext.into_rpc())?;
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"))?;

Ok(ScrollAddOnsHandle {
rollup_manager_handle,
rpc_handle,
Expand Down
121 changes: 121 additions & 0 deletions crates/node/src/add_ons/rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use async_trait::async_trait;
use jsonrpsee::{
core::RpcResult,
proc_macros::rpc,
types::{error, ErrorObjectOwned},
};
use reth_network_api::FullNetwork;
use reth_scroll_node::ScrollNetworkPrimitives;
use rollup_node_manager::RollupManagerHandle;
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
/// rollup manager through a handle that is initialized lazily via a oneshot channel.///
#[derive(Debug)]
pub struct RollupNodeRpcExt<N>
where
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
{
/// Cached rollup manager handle, initialized lazily via `OnceCell`
handle: tokio::sync::OnceCell<RollupManagerHandle<N>>,
/// Oneshot channel receiver for obtaining the rollup manager handle during initialization
rx: Mutex<Option<oneshot::Receiver<RollupManagerHandle<N>>>>,
}

impl<N> RollupNodeRpcExt<N>
where
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
{
/// Creates a new RPC extension with a receiver for the rollup manager handle.
pub fn new(rx: oneshot::Receiver<RollupManagerHandle<N>>) -> Self {
Self { rx: Mutex::new(Some(rx)), handle: OnceCell::new() }
}

/// Gets or initializes the rollup manager handle.
///
/// This method lazily initializes the rollup manager handle by consuming the oneshot
/// receiver. Subsequent calls will return the cached handle.
async fn rollup_manager_handle(&self) -> eyre::Result<&RollupManagerHandle<N>> {
self.handle
.get_or_try_init(|| async {
let rx = {
let mut g = self.rx.lock().await;
g.take().ok_or_else(|| eyre::eyre!("receiver already consumed"))?
};
rx.await.map_err(|e| eyre::eyre!("failed to receive handle: {e}"))
})
.await
}
}

/// Defines the `rollupNode` JSON-RPC namespace for rollup management 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.
///
/// # Usage
/// These methods can be called via JSON-RPC using the `rollupNode` namespace:
/// ```json
/// {"jsonrpc": "2.0", "method": "rollupNode_enableAutomaticSequencing", "params": [], "id": 1}
/// ```
/// or using cast:
/// ```bash
/// cast rpc rollupNode_enableAutomaticSequencing
/// ```
#[cfg_attr(not(test), rpc(server, namespace = "rollupNode"))]
#[cfg_attr(test, 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>;
}

#[async_trait]
impl<N> RollupNodeExtApiServer 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,
format!("Failed to get rollup manager handle: {}", e),
None::<()>,
)
})?;

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

async fn disable_automatic_sequencing(&self) -> RpcResult<bool> {
let handle = self.rollup_manager_handle().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
format!("Failed to get rollup manager handle: {}", e),
None::<()>,
)
})?;

handle.disable_automatic_sequencing().await.map_err(|e| {
ErrorObjectOwned::owned(
error::INTERNAL_ERROR_CODE,
format!("Failed to disable automatic sequencing: {}", e),
None::<()>,
)
})
}
}
Loading