Skip to content

Commit 12a3246

Browse files
authored
feat(admin rpc): add command to enable/disable automatic sequencing (#289)
1 parent d6d8d97 commit 12a3246

File tree

9 files changed

+252
-2
lines changed

9 files changed

+252
-2
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/manager/src/manager/command.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@ pub enum RollupManagerCommand<N: FullNetwork<Primitives = ScrollNetworkPrimitive
2020
NetworkHandle(oneshot::Sender<ScrollNetworkHandle<N>>),
2121
/// Update the head of the fcs in the engine driver.
2222
UpdateFcsHead(BlockInfo),
23+
/// Enable automatic sequencing.
24+
EnableAutomaticSequencing(oneshot::Sender<bool>),
25+
/// Disable automatic sequencing.
26+
DisableAutomaticSequencing(oneshot::Sender<bool>),
2327
}

crates/manager/src/manager/handle.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,18 @@ impl<N: FullNetwork<Primitives = ScrollNetworkPrimitives>> RollupManagerHandle<N
5252
pub async fn update_fcs_head(&self, head: BlockInfo) {
5353
self.send_command(RollupManagerCommand::UpdateFcsHead(head)).await;
5454
}
55+
56+
/// Sends a command to the rollup manager to enable automatic sequencing.
57+
pub async fn enable_automatic_sequencing(&self) -> Result<bool, oneshot::error::RecvError> {
58+
let (tx, rx) = oneshot::channel();
59+
self.send_command(RollupManagerCommand::EnableAutomaticSequencing(tx)).await;
60+
rx.await
61+
}
62+
63+
/// Sends a command to the rollup manager to disable automatic sequencing.
64+
pub async fn disable_automatic_sequencing(&self) -> Result<bool, oneshot::error::RecvError> {
65+
let (tx, rx) = oneshot::channel();
66+
self.send_command(RollupManagerCommand::DisableAutomaticSequencing(tx)).await;
67+
rx.await
68+
}
5569
}

crates/manager/src/manager/mod.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use tokio::{
3838
time::Interval,
3939
};
4040
use tokio_stream::wrappers::ReceiverStream;
41-
use tracing::{error, trace, warn};
41+
use tracing::{error, info, trace, warn};
4242

4343
use rollup_node_providers::{L1MessageProvider, L1Provider};
4444
use scroll_db::{Database, DatabaseError};
@@ -112,6 +112,8 @@ pub struct RollupNodeManager<
112112
signer: Option<SignerHandle>,
113113
/// The trigger for the block building process.
114114
block_building_trigger: Option<Interval>,
115+
/// The original block time configuration for restoring automatic sequencing.
116+
block_time_config: Option<u64>,
115117
}
116118

117119
/// The current status of the rollup manager.
@@ -145,6 +147,7 @@ impl<
145147
.field("event_sender", &self.event_sender)
146148
.field("sequencer", &self.sequencer)
147149
.field("block_building_trigger", &self.block_building_trigger)
150+
.field("block_time_config", &self.block_time_config)
148151
.finish()
149152
}
150153
}
@@ -189,6 +192,7 @@ where
189192
sequencer,
190193
signer,
191194
block_building_trigger: block_time.map(delayed_interval),
195+
block_time_config: block_time,
192196
};
193197
(rnm, RollupManagerHandle::new(handle_tx))
194198
}
@@ -512,6 +516,27 @@ where
512516
tx.send(network_handle.clone())
513517
.expect("Failed to send network handle to handle");
514518
}
519+
RollupManagerCommand::EnableAutomaticSequencing(tx) => {
520+
let success = if let Some(block_time) = this.block_time_config {
521+
if this.block_building_trigger.is_none() {
522+
this.block_building_trigger = Some(delayed_interval(block_time));
523+
info!(target: "scroll::node::manager", "Enabled automatic sequencing with interval {}ms", block_time);
524+
} else {
525+
info!(target: "scroll::node::manager", "Automatic sequencing already enabled");
526+
}
527+
true
528+
} else {
529+
warn!(target: "scroll::node::manager", "Cannot enable automatic sequencing: sequencer and block time not configured");
530+
false
531+
};
532+
tx.send(success).expect("Failed to send enable automatic sequencing response");
533+
}
534+
RollupManagerCommand::DisableAutomaticSequencing(tx) => {
535+
let was_enabled = this.block_building_trigger.is_some();
536+
this.block_building_trigger = None;
537+
info!(target: "scroll::node::manager", "Disabled automatic sequencing (was enabled: {})", was_enabled);
538+
tx.send(true).expect("Failed to send disable automatic sequencing response");
539+
}
515540
}
516541
}
517542

crates/node/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ path = "src/main.rs"
1414
workspace = true
1515

1616
[dependencies]
17+
# async trait support
18+
async-trait.workspace = true
19+
1720
# alloy
1821
alloy-chains.workspace = true
1922
alloy-primitives.workspace = true
@@ -88,6 +91,7 @@ scroll-network.workspace = true
8891
auto_impl.workspace = true
8992
clap = { workspace = true, features = ["derive", "env"] }
9093
eyre.workspace = true
94+
jsonrpsee = { version = "0.25.1", features = ["server", "client", "macros"] }
9195
reqwest.workspace = true
9296
tokio.workspace = true
9397
tracing.workspace = true

crates/node/src/add_ons/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ use scroll_wire::ScrollWireEvent;
3131
mod handle;
3232
pub use handle::ScrollAddOnsHandle;
3333

34+
mod rpc;
35+
pub use rpc::{RollupNodeExtApiClient, RollupNodeExtApiServer, RollupNodeRpcExt};
36+
3437
mod rollup;
3538
pub use rollup::IsDevChain;
3639
use rollup::RollupManagerAddOn;
@@ -123,9 +126,21 @@ where
123126
rpc_add_ons.eth_api_builder.with_propagate_local_transactions(
124127
!ctx.config.txpool.no_local_transactions_propagation,
125128
);
129+
130+
let (tx, rx) = tokio::sync::oneshot::channel();
131+
let rollup_node_rpc_ext = RollupNodeRpcExt::<N::Network>::new(rx);
132+
rpc_add_ons = rpc_add_ons.extend_rpc_modules(move |ctx| {
133+
ctx.modules.merge_configured(rollup_node_rpc_ext.into_rpc())?;
134+
Ok(())
135+
});
136+
126137
let rpc_handle = rpc_add_ons.launch_add_ons_with(ctx.clone(), |_| Ok(())).await?;
127138
let (rollup_manager_handle, l1_watcher_tx) =
128139
rollup_node_manager_addon.launch(ctx.clone(), rpc_handle.clone()).await?;
140+
141+
tx.send(rollup_manager_handle.clone())
142+
.map_err(|_| eyre::eyre!("failed to send rollup manager handle"))?;
143+
129144
Ok(ScrollAddOnsHandle {
130145
rollup_manager_handle,
131146
rpc_handle,

crates/node/src/add_ons/rpc.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use async_trait::async_trait;
2+
use jsonrpsee::{
3+
core::RpcResult,
4+
proc_macros::rpc,
5+
types::{error, ErrorObjectOwned},
6+
};
7+
use reth_network_api::FullNetwork;
8+
use reth_scroll_node::ScrollNetworkPrimitives;
9+
use rollup_node_manager::RollupManagerHandle;
10+
use tokio::sync::{oneshot, Mutex, OnceCell};
11+
12+
/// RPC extension for rollup node management operations.
13+
///
14+
/// This struct provides a custom JSON-RPC namespace (`rollupNode`) that exposes
15+
/// rollup management functionality to RPC clients. It manages a connection to the
16+
/// rollup manager through a handle that is initialized lazily via a oneshot channel.
17+
#[derive(Debug)]
18+
pub struct RollupNodeRpcExt<N>
19+
where
20+
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
21+
{
22+
/// Cached rollup manager handle, initialized lazily via `OnceCell`
23+
handle: tokio::sync::OnceCell<RollupManagerHandle<N>>,
24+
/// Oneshot channel receiver for obtaining the rollup manager handle during initialization
25+
rx: Mutex<Option<oneshot::Receiver<RollupManagerHandle<N>>>>,
26+
}
27+
28+
impl<N> RollupNodeRpcExt<N>
29+
where
30+
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
31+
{
32+
/// Creates a new RPC extension with a receiver for the rollup manager handle.
33+
pub fn new(rx: oneshot::Receiver<RollupManagerHandle<N>>) -> Self {
34+
Self { rx: Mutex::new(Some(rx)), handle: OnceCell::new() }
35+
}
36+
37+
/// Gets or initializes the rollup manager handle.
38+
///
39+
/// This method lazily initializes the rollup manager handle by consuming the oneshot
40+
/// receiver. Subsequent calls will return the cached handle.
41+
async fn rollup_manager_handle(&self) -> eyre::Result<&RollupManagerHandle<N>> {
42+
self.handle
43+
.get_or_try_init(|| async {
44+
let rx = {
45+
let mut g = self.rx.lock().await;
46+
g.take().ok_or_else(|| eyre::eyre!("receiver already consumed"))?
47+
};
48+
rx.await.map_err(|e| eyre::eyre!("failed to receive handle: {e}"))
49+
})
50+
.await
51+
}
52+
}
53+
54+
/// Defines the `rollupNode` JSON-RPC namespace for rollup management operations.
55+
///
56+
/// This trait provides a custom RPC namespace that exposes rollup node management
57+
/// functionality to external clients. The namespace is exposed as `rollupNode` and
58+
/// provides methods for controlling automatic sequencing behavior.
59+
///
60+
/// # Usage
61+
/// These methods can be called via JSON-RPC using the `rollupNode` namespace:
62+
/// ```json
63+
/// {"jsonrpc": "2.0", "method": "rollupNode_enableAutomaticSequencing", "params": [], "id": 1}
64+
/// ```
65+
/// or using cast:
66+
/// ```bash
67+
/// cast rpc rollupNode_enableAutomaticSequencing
68+
/// ```
69+
#[rpc(server, client, namespace = "rollupNode")]
70+
pub trait RollupNodeExtApi {
71+
/// Enables automatic sequencing in the rollup node.
72+
#[method(name = "enableAutomaticSequencing")]
73+
async fn enable_automatic_sequencing(&self) -> RpcResult<bool>;
74+
75+
/// Disables automatic sequencing in the rollup node.
76+
#[method(name = "disableAutomaticSequencing")]
77+
async fn disable_automatic_sequencing(&self) -> RpcResult<bool>;
78+
}
79+
80+
#[async_trait]
81+
impl<N> RollupNodeExtApiServer for RollupNodeRpcExt<N>
82+
where
83+
N: FullNetwork<Primitives = ScrollNetworkPrimitives>,
84+
{
85+
async fn enable_automatic_sequencing(&self) -> RpcResult<bool> {
86+
let handle = self.rollup_manager_handle().await.map_err(|e| {
87+
ErrorObjectOwned::owned(
88+
error::INTERNAL_ERROR_CODE,
89+
format!("Failed to get rollup manager handle: {}", e),
90+
None::<()>,
91+
)
92+
})?;
93+
94+
handle.enable_automatic_sequencing().await.map_err(|e| {
95+
ErrorObjectOwned::owned(
96+
error::INTERNAL_ERROR_CODE,
97+
format!("Failed to enable automatic sequencing: {}", e),
98+
None::<()>,
99+
)
100+
})
101+
}
102+
103+
async fn disable_automatic_sequencing(&self) -> RpcResult<bool> {
104+
let handle = self.rollup_manager_handle().await.map_err(|e| {
105+
ErrorObjectOwned::owned(
106+
error::INTERNAL_ERROR_CODE,
107+
format!("Failed to get rollup manager handle: {}", e),
108+
None::<()>,
109+
)
110+
})?;
111+
112+
handle.disable_automatic_sequencing().await.map_err(|e| {
113+
ErrorObjectOwned::owned(
114+
error::INTERNAL_ERROR_CODE,
115+
format!("Failed to disable automatic sequencing: {}", e),
116+
None::<()>,
117+
)
118+
})
119+
}
120+
}

crates/node/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod node;
88
#[cfg(feature = "test-utils")]
99
pub mod test_utils;
1010

11+
pub use add_ons::*;
1112
pub use args::*;
1213
pub use context::RollupNodeContext;
1314
pub use node::ScrollRollupNode;

crates/node/tests/e2e.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ use rollup_node::{
2626
},
2727
BeaconProviderArgs, ChainOrchestratorArgs, ConsensusAlgorithm, ConsensusArgs, DatabaseArgs,
2828
EngineDriverArgs, GasPriceOracleArgs, L1ProviderArgs, NetworkArgs as ScrollNetworkArgs,
29-
RollupNodeContext, ScrollRollupNode, ScrollRollupNodeConfig, SequencerArgs,
29+
RollupNodeContext, RollupNodeExtApiClient, ScrollRollupNode, ScrollRollupNodeConfig,
30+
SequencerArgs,
3031
};
3132
use rollup_node_chain_orchestrator::ChainOrchestratorEvent;
3233
use rollup_node_manager::{RollupManagerCommand, RollupManagerEvent};
@@ -1274,6 +1275,70 @@ async fn can_handle_l1_message_reorg() -> eyre::Result<()> {
12741275
Ok(())
12751276
}
12761277

1278+
#[tokio::test]
1279+
async fn can_rpc_enable_disable_sequencing() -> eyre::Result<()> {
1280+
reth_tracing::init_test_tracing();
1281+
color_eyre::install()?;
1282+
let chain_spec = (*SCROLL_DEV).clone();
1283+
1284+
// Launch sequencer node with automatic sequencing enabled.
1285+
let mut config = default_sequencer_test_scroll_rollup_node_config();
1286+
config.sequencer_args.block_time = 40; // Enable automatic block production
1287+
1288+
let (mut nodes, _tasks, _) = setup_engine(config, 2, chain_spec.clone(), false, false).await?;
1289+
let node0 = nodes.remove(0);
1290+
let node1 = nodes.remove(0);
1291+
1292+
// Get handles
1293+
let node0_rnm_handle = node0.inner.add_ons_handle.rollup_manager_handle.clone();
1294+
let mut node0_rnm_events = node0_rnm_handle.get_event_listener().await?;
1295+
1296+
let node1_rnm_handle = node1.inner.add_ons_handle.rollup_manager_handle.clone();
1297+
let mut node1_rnm_events = node1_rnm_handle.get_event_listener().await?;
1298+
1299+
// Create RPC client
1300+
let client0 = node0.rpc_client().expect("RPC client should be available");
1301+
1302+
// Test that sequencing is initially enabled (blocks produced automatically)
1303+
tokio::time::sleep(Duration::from_millis(100)).await;
1304+
assert_ne!(latest_block(&node0).await?.header.number, 0, "Should produce blocks");
1305+
1306+
// Disable automatic sequencing via RPC
1307+
let result = RollupNodeExtApiClient::disable_automatic_sequencing(&client0).await?;
1308+
assert!(result, "Disable automatic sequencing should return true");
1309+
1310+
// Wait a bit and verify no more blocks are produced automatically.
1311+
// +1 blocks is okay due to still being processed
1312+
let block_num_before_wait = latest_block(&node0).await?.header.number;
1313+
tokio::time::sleep(Duration::from_millis(300)).await;
1314+
let block_num_after_wait = latest_block(&node0).await?.header.number;
1315+
assert!(
1316+
(block_num_before_wait..=block_num_before_wait + 1).contains(&block_num_after_wait),
1317+
"No blocks should be produced automatically after disabling"
1318+
);
1319+
1320+
// Make sure follower is at same block
1321+
wait_for_block_imported_5s(&mut node1_rnm_events, block_num_after_wait).await?;
1322+
assert_eq!(block_num_after_wait, latest_block(&node1).await?.header.number);
1323+
1324+
// Verify manual block building still works
1325+
node0_rnm_handle.build_block().await;
1326+
wait_for_block_sequenced_5s(&mut node0_rnm_events, block_num_after_wait + 1).await?;
1327+
1328+
// Wait for the follower to import the block
1329+
wait_for_block_imported_5s(&mut node1_rnm_events, block_num_after_wait + 1).await?;
1330+
1331+
// Enable sequencing again
1332+
let result = RollupNodeExtApiClient::enable_automatic_sequencing(&client0).await?;
1333+
assert!(result, "Enable automatic sequencing should return true");
1334+
1335+
// Make sure automatic sequencing resumes
1336+
wait_for_block_sequenced_5s(&mut node0_rnm_events, block_num_after_wait + 2).await?;
1337+
wait_for_block_imported_5s(&mut node1_rnm_events, block_num_after_wait + 2).await?;
1338+
1339+
Ok(())
1340+
}
1341+
12771342
/// Tests that a follower node correctly rejects L2 blocks containing L1 messages it hasn't received
12781343
/// yet.
12791344
///

0 commit comments

Comments
 (0)