Skip to content

Commit fcf3b96

Browse files
authored
feat: add private key file configuration for sequencer signing (#161)
* feat: add private key file configuration for sequencer signing * add back -d disable the discovery service in README.md's sequencer command * address comments * refactor unit tests * update README.md * address comments * remove duplicated checks
1 parent b8b4c6b commit fcf3b96

File tree

15 files changed

+344
-10
lines changed

15 files changed

+344
-10
lines changed

Cargo.lock

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

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,11 @@ To run a sequencer node you should build the binary in release mode using the in
123123
Then, you can run the sequencer node with the following command:
124124

125125
```sh
126-
./target/release/rollup-node node --chain dev -d --scroll-sequencer-enabled --http --http.api admin,debug,eth,net,trace,txpool,web3,rpc,reth,ots,flashbots,miner,mev
126+
./target/release/rollup-node node --chain dev -d --sequencer.enabled --signer.key-file /path/to/your/private.key --http --http.api admin,debug,eth,net,trace,txpool,web3,rpc,reth,ots,flashbots,miner,mev
127127
```
128128

129+
**Note**: When running a sequencer, a signer key file is required unless the `--test` flag is specified. Use the `--signer.key-file` option to specify the path to your private key file. Keep your private key file secure and never commit it to version control.
130+
129131
This will start a dev node in sequencer mode with all rpc apis enabled. You can adjust the `--http.api` flag to include or exclude specific APIs as needed.
130132

131133
The chain will be configured with a genesis that funds 20 addresses derived from the mnemonic:
@@ -147,6 +149,8 @@ A list of sequencer specific configuration options can be seen below:
147149
The max L1 messages per block for the sequencer [default: 4]
148150
--fee-recipient <FEE_RECIPIENT>
149151
The fee recipient for the sequencer [default: 0x5300000000000000000000000000000000000005]
152+
--signer.key-file <FILE_PATH>
153+
Path to the signer's hex-encoded private key file (optional 0x prefix). Required when sequencer is enabled
150154
```
151155
152156
## Contributing

crates/manager/src/manager/event.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use reth_scroll_primitives::ScrollBlock;
22
use rollup_node_primitives::L2BlockInfoWithL1Messages;
3+
use rollup_node_signer::SignerEvent;
34
use scroll_network::NewBlockWithPeer;
45

56
/// An event that can be emitted by the rollup node manager.
@@ -15,4 +16,6 @@ pub enum RollupManagerEvent {
1516
L1DerivedBlockConsolidated(L2BlockInfoWithL1Messages),
1617
/// An L1 message with the given index has been indexed.
1718
L1MessageIndexed(u64),
19+
/// A new event from the signer.
20+
SignerEvent(SignerEvent),
1821
}

crates/manager/src/manager/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,13 @@ where
455455
match event {
456456
SignerEvent::SignedBlock { block, signature } => {
457457
trace!(target: "scroll::node::manager", ?block, ?signature, "Received signed block from signer, announcing to the network");
458+
// Send SignerEvent for test monitoring
459+
if let Some(event_sender) = this.event_sender.as_ref() {
460+
event_sender.notify(RollupManagerEvent::SignerEvent(
461+
SignerEvent::SignedBlock { block: block.clone(), signature },
462+
));
463+
}
464+
458465
this.network.handle().announce_block(block, signature);
459466
}
460467
}

crates/node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ reth-tracing.workspace = true
9393
rollup-node = { workspace = true, features = ["test-utils"] }
9494
scroll-alloy-rpc-types-engine.workspace = true
9595
serde_json = { version = "1.0.94", default-features = false, features = ["alloc"] }
96+
tempfile = "3.0"
9697

9798
[features]
9899
test-utils = [

crates/node/src/add_ons/rollup.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{
33
constants::PROVIDER_BLOB_CACHE_SIZE,
44
};
55

6+
use alloy_primitives::hex;
67
use alloy_provider::ProviderBuilder;
78
use alloy_rpc_client::RpcClient;
89
use alloy_signer_local::PrivateKeySigner;
@@ -32,7 +33,7 @@ use scroll_engine::{EngineDriver, ForkchoiceState};
3233
use scroll_migration::traits::ScrollMigrator;
3334
use scroll_network::ScrollNetworkManager;
3435
use scroll_wire::{ScrollWireConfig, ScrollWireProtocolHandler};
35-
use std::{sync::Arc, time::Duration};
36+
use std::{fs, sync::Arc, time::Duration};
3637
use tokio::sync::mpsc::Sender;
3738

3839
/// Implementing the trait allows the type to return whether it is configured for dev chain.
@@ -211,7 +212,32 @@ impl RollupManagerAddOn {
211212
.then_some(ctx.node.network().eth_wire_block_listener().await?);
212213

213214
// Instantiate the signer
214-
let signer = self.config.test.then_some(Signer::spawn(PrivateKeySigner::random()));
215+
let signer = if self.config.test {
216+
Some(Signer::spawn(PrivateKeySigner::random()))
217+
} else if let Some(key_file_path) = &self.config.signer_args.key_file {
218+
let key_content = fs::read_to_string(key_file_path)
219+
.map_err(|e| {
220+
eyre::eyre!("Failed to read signer key file {}: {}", key_file_path.display(), e)
221+
})?
222+
.trim()
223+
.to_string();
224+
225+
let hex_str = key_content.strip_prefix("0x").unwrap_or(&key_content);
226+
let key_bytes = hex::decode(hex_str).map_err(|e| {
227+
eyre::eyre!(
228+
"Failed to decode hex private key from file {}: {}",
229+
key_file_path.display(),
230+
e
231+
)
232+
})?;
233+
234+
let private_key_signer = PrivateKeySigner::from_slice(&key_bytes)
235+
.map_err(|e| eyre::eyre!("Failed to create signer from key file: {}", e))?;
236+
237+
Some(Signer::spawn(private_key_signer))
238+
} else {
239+
None
240+
};
215241

216242
// Spawn the rollup node manager
217243
let rnm = RollupNodeManager::new(

crates/node/src/args.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ pub struct ScrollRollupNodeConfig {
2828
/// The network arguments
2929
#[command(flatten)]
3030
pub network_args: NetworkArgs,
31+
/// The signer arguments
32+
#[command(flatten)]
33+
pub signer_args: SignerArgs,
34+
}
35+
36+
impl ScrollRollupNodeConfig {
37+
/// Validate that signer key file is provided when sequencer is enabled
38+
pub fn validate(&self) -> Result<(), String> {
39+
if !self.test &&
40+
self.sequencer_args.sequencer_enabled &&
41+
self.signer_args.key_file.is_none()
42+
{
43+
return Err("Signer key file is required when sequencer is enabled".to_string());
44+
}
45+
Ok(())
46+
}
3147
}
3248

3349
/// The database arguments.
@@ -122,3 +138,89 @@ pub struct SequencerArgs {
122138
)]
123139
pub l1_message_inclusion_mode: L1MessageInclusionMode,
124140
}
141+
142+
/// The arguments for the signer.
143+
#[derive(Debug, Default, Clone, clap::Args)]
144+
pub struct SignerArgs {
145+
/// Path to the file containing the signer's private key
146+
#[arg(
147+
long = "signer.key-file",
148+
value_name = "FILE_PATH",
149+
help = "Path to the signer's hex-encoded private key file (optional 0x prefix). Required when sequencer is enabled"
150+
)]
151+
pub key_file: Option<PathBuf>,
152+
}
153+
154+
#[cfg(test)]
155+
mod tests {
156+
use super::*;
157+
use std::path::PathBuf;
158+
159+
#[test]
160+
fn test_validate_sequencer_enabled_without_key_file_fails() {
161+
let config = ScrollRollupNodeConfig {
162+
test: false,
163+
sequencer_args: SequencerArgs { sequencer_enabled: true, ..Default::default() },
164+
signer_args: SignerArgs { key_file: None },
165+
database_args: DatabaseArgs::default(),
166+
engine_driver_args: EngineDriverArgs::default(),
167+
l1_provider_args: L1ProviderArgs::default(),
168+
beacon_provider_args: BeaconProviderArgs::default(),
169+
network_args: NetworkArgs::default(),
170+
};
171+
172+
let result = config.validate();
173+
assert!(result.is_err());
174+
assert!(result
175+
.unwrap_err()
176+
.contains("Signer key file is required when sequencer is enabled"));
177+
}
178+
179+
#[test]
180+
fn test_validate_sequencer_enabled_with_key_file_succeeds() {
181+
let config = ScrollRollupNodeConfig {
182+
test: false,
183+
sequencer_args: SequencerArgs { sequencer_enabled: true, ..Default::default() },
184+
signer_args: SignerArgs { key_file: Some(PathBuf::from("/path/to/key")) },
185+
database_args: DatabaseArgs::default(),
186+
engine_driver_args: EngineDriverArgs::default(),
187+
l1_provider_args: L1ProviderArgs::default(),
188+
beacon_provider_args: BeaconProviderArgs::default(),
189+
network_args: NetworkArgs::default(),
190+
};
191+
192+
assert!(config.validate().is_ok());
193+
}
194+
195+
#[test]
196+
fn test_validate_test_mode_without_key_file_succeeds() {
197+
let config = ScrollRollupNodeConfig {
198+
test: true,
199+
sequencer_args: SequencerArgs { sequencer_enabled: true, ..Default::default() },
200+
signer_args: SignerArgs { key_file: None },
201+
database_args: DatabaseArgs::default(),
202+
engine_driver_args: EngineDriverArgs::default(),
203+
l1_provider_args: L1ProviderArgs::default(),
204+
beacon_provider_args: BeaconProviderArgs::default(),
205+
network_args: NetworkArgs::default(),
206+
};
207+
208+
assert!(config.validate().is_ok());
209+
}
210+
211+
#[test]
212+
fn test_validate_sequencer_disabled_without_key_file_succeeds() {
213+
let config = ScrollRollupNodeConfig {
214+
test: false,
215+
sequencer_args: SequencerArgs { sequencer_enabled: false, ..Default::default() },
216+
signer_args: SignerArgs { key_file: None },
217+
database_args: DatabaseArgs::default(),
218+
engine_driver_args: EngineDriverArgs::default(),
219+
l1_provider_args: L1ProviderArgs::default(),
220+
beacon_provider_args: BeaconProviderArgs::default(),
221+
network_args: NetworkArgs::default(),
222+
};
223+
224+
assert!(config.validate().is_ok());
225+
}
226+
}

crates/node/src/node.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ pub struct ScrollRollupNode {
2121

2222
impl ScrollRollupNode {
2323
/// Create a new instance of [`ScrollRollupNode`].
24-
pub const fn new(config: ScrollRollupNodeConfig) -> Self {
24+
pub fn new(config: ScrollRollupNodeConfig) -> Self {
25+
config
26+
.validate()
27+
.map_err(|e| eyre::eyre!("Configuration validation failed: {}", e))
28+
.expect("Configuration validation failed");
2529
Self { config }
2630
}
2731
}

crates/node/src/test_utils.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ pub fn default_test_scroll_rollup_node_config() -> ScrollRollupNodeConfig {
139139
engine_driver_args: EngineDriverArgs { en_sync_trigger: 100 },
140140
sequencer_args: SequencerArgs::default(),
141141
beacon_provider_args: BeaconProviderArgs::default(),
142+
signer_args: Default::default(),
142143
}
143144
}
144145

@@ -162,5 +163,6 @@ pub fn default_sequencer_test_scroll_rollup_node_config() -> ScrollRollupNodeCon
162163
l1_message_inclusion_mode: L1MessageInclusionMode::BlockDepth(0),
163164
},
164165
beacon_provider_args: BeaconProviderArgs::default(),
166+
signer_args: Default::default(),
165167
}
166168
}

crates/node/tests/e2e.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ async fn can_bridge_l1_messages() -> eyre::Result<()> {
4343
..SequencerArgs::default()
4444
},
4545
beacon_provider_args: BeaconProviderArgs::default(),
46+
signer_args: Default::default(),
4647
};
4748
let (mut nodes, _tasks, _wallet) = setup_engine(node_args, 1, chain_spec, false).await?;
4849
let node = nodes.pop().unwrap();
@@ -106,6 +107,7 @@ async fn can_sequence_and_gossip_blocks() {
106107
..SequencerArgs::default()
107108
},
108109
beacon_provider_args: BeaconProviderArgs::default(),
110+
signer_args: Default::default(),
109111
};
110112

111113
let (nodes, _tasks, wallet) =

0 commit comments

Comments
 (0)