Skip to content

feat: add private key file configuration for sequencer signing #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
8 changes: 8 additions & 0 deletions Cargo.lock

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

6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,11 @@ To run a sequencer node you should build the binary in release mode using the in
Then, you can run the sequencer node with the following command:

```sh
./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
./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
```

**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.

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.

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

## Contributing
Expand Down
3 changes: 3 additions & 0 deletions crates/manager/src/manager/event.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use reth_scroll_primitives::ScrollBlock;
use rollup_node_primitives::L2BlockInfoWithL1Messages;
use rollup_node_signer::SignerEvent;
use scroll_network::NewBlockWithPeer;

/// An event that can be emitted by the rollup node manager.
Expand All @@ -15,4 +16,6 @@ pub enum RollupManagerEvent {
L1DerivedBlockConsolidated(L2BlockInfoWithL1Messages),
/// An L1 message with the given index has been indexed.
L1MessageIndexed(u64),
/// A new event from the signer.
SignerEvent(SignerEvent),
}
7 changes: 7 additions & 0 deletions crates/manager/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,13 @@ where
match event {
SignerEvent::SignedBlock { block, signature } => {
trace!(target: "scroll::node::manager", ?block, ?signature, "Received signed block from signer, announcing to the network");
// Send SignerEvent for test monitoring
if let Some(event_sender) = this.event_sender.as_ref() {
event_sender.notify(RollupManagerEvent::SignerEvent(
SignerEvent::SignedBlock { block: block.clone(), signature },
));
}

this.network.handle().announce_block(block, signature);
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ reth-tracing.workspace = true
rollup-node = { workspace = true, features = ["test-utils"] }
scroll-alloy-rpc-types-engine.workspace = true
serde_json = { version = "1.0.94", default-features = false, features = ["alloc"] }
tempfile = "3.0"

[features]
test-utils = [
Expand Down
30 changes: 28 additions & 2 deletions crates/node/src/add_ons/rollup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
constants::PROVIDER_BLOB_CACHE_SIZE,
};

use alloy_primitives::hex;
use alloy_provider::ProviderBuilder;
use alloy_rpc_client::RpcClient;
use alloy_signer_local::PrivateKeySigner;
Expand Down Expand Up @@ -32,7 +33,7 @@ use scroll_engine::{EngineDriver, ForkchoiceState};
use scroll_migration::traits::ScrollMigrator;
use scroll_network::ScrollNetworkManager;
use scroll_wire::{ScrollWireConfig, ScrollWireProtocolHandler};
use std::{sync::Arc, time::Duration};
use std::{fs, sync::Arc, time::Duration};
use tokio::sync::mpsc::Sender;

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

// Instantiate the signer
let signer = self.config.test.then_some(Signer::spawn(PrivateKeySigner::random()));
let signer = if self.config.test {
Some(Signer::spawn(PrivateKeySigner::random()))
} else if let Some(key_file_path) = &self.config.signer_args.key_file {
let key_content = fs::read_to_string(key_file_path)
.map_err(|e| {
eyre::eyre!("Failed to read signer key file {}: {}", key_file_path.display(), e)
})?
.trim()
.to_string();

let hex_str = key_content.strip_prefix("0x").unwrap_or(&key_content);
let key_bytes = hex::decode(hex_str).map_err(|e| {
eyre::eyre!(
"Failed to decode hex private key from file {}: {}",
key_file_path.display(),
e
)
})?;

let private_key_signer = PrivateKeySigner::from_slice(&key_bytes)
.map_err(|e| eyre::eyre!("Failed to create signer from key file: {}", e))?;

Some(Signer::spawn(private_key_signer))
} else {
None
};

// Spawn the rollup node manager
let rnm = RollupNodeManager::new(
Expand Down
102 changes: 102 additions & 0 deletions crates/node/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ pub struct ScrollRollupNodeConfig {
/// The network arguments
#[command(flatten)]
pub network_args: NetworkArgs,
/// The signer arguments
#[command(flatten)]
pub signer_args: SignerArgs,
}

impl ScrollRollupNodeConfig {
/// Validate that signer key file is provided when sequencer is enabled
pub fn validate(&self) -> Result<(), String> {
if !self.test &&
self.sequencer_args.sequencer_enabled &&
self.signer_args.key_file.is_none()
{
return Err("Signer key file is required when sequencer is enabled".to_string());
}
Ok(())
}
}

/// The database arguments.
Expand Down Expand Up @@ -122,3 +138,89 @@ pub struct SequencerArgs {
)]
pub l1_message_inclusion_mode: L1MessageInclusionMode,
}

/// The arguments for the signer.
#[derive(Debug, Default, Clone, clap::Args)]
pub struct SignerArgs {
/// Path to the file containing the signer's private key
#[arg(
long = "signer.key-file",
value_name = "FILE_PATH",
help = "Path to the signer's hex-encoded private key file (optional 0x prefix). Required when sequencer is enabled"
)]
pub key_file: Option<PathBuf>,
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;

#[test]
fn test_validate_sequencer_enabled_without_key_file_fails() {
let config = ScrollRollupNodeConfig {
test: false,
sequencer_args: SequencerArgs { sequencer_enabled: true, ..Default::default() },
signer_args: SignerArgs { key_file: None },
database_args: DatabaseArgs::default(),
engine_driver_args: EngineDriverArgs::default(),
l1_provider_args: L1ProviderArgs::default(),
beacon_provider_args: BeaconProviderArgs::default(),
network_args: NetworkArgs::default(),
};

let result = config.validate();
assert!(result.is_err());
assert!(result
.unwrap_err()
.contains("Signer key file is required when sequencer is enabled"));
}

#[test]
fn test_validate_sequencer_enabled_with_key_file_succeeds() {
let config = ScrollRollupNodeConfig {
test: false,
sequencer_args: SequencerArgs { sequencer_enabled: true, ..Default::default() },
signer_args: SignerArgs { key_file: Some(PathBuf::from("/path/to/key")) },
database_args: DatabaseArgs::default(),
engine_driver_args: EngineDriverArgs::default(),
l1_provider_args: L1ProviderArgs::default(),
beacon_provider_args: BeaconProviderArgs::default(),
network_args: NetworkArgs::default(),
};

assert!(config.validate().is_ok());
}

#[test]
fn test_validate_test_mode_without_key_file_succeeds() {
let config = ScrollRollupNodeConfig {
test: true,
sequencer_args: SequencerArgs { sequencer_enabled: true, ..Default::default() },
signer_args: SignerArgs { key_file: None },
database_args: DatabaseArgs::default(),
engine_driver_args: EngineDriverArgs::default(),
l1_provider_args: L1ProviderArgs::default(),
beacon_provider_args: BeaconProviderArgs::default(),
network_args: NetworkArgs::default(),
};

assert!(config.validate().is_ok());
}

#[test]
fn test_validate_sequencer_disabled_without_key_file_succeeds() {
let config = ScrollRollupNodeConfig {
test: false,
sequencer_args: SequencerArgs { sequencer_enabled: false, ..Default::default() },
signer_args: SignerArgs { key_file: None },
database_args: DatabaseArgs::default(),
engine_driver_args: EngineDriverArgs::default(),
l1_provider_args: L1ProviderArgs::default(),
beacon_provider_args: BeaconProviderArgs::default(),
network_args: NetworkArgs::default(),
};

assert!(config.validate().is_ok());
}
}
6 changes: 5 additions & 1 deletion crates/node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ pub struct ScrollRollupNode {

impl ScrollRollupNode {
/// Create a new instance of [`ScrollRollupNode`].
pub const fn new(config: ScrollRollupNodeConfig) -> Self {
pub fn new(config: ScrollRollupNodeConfig) -> Self {
config
.validate()
.map_err(|e| eyre::eyre!("Configuration validation failed: {}", e))
.expect("Configuration validation failed");
Self { config }
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/node/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub fn default_test_scroll_rollup_node_config() -> ScrollRollupNodeConfig {
engine_driver_args: EngineDriverArgs { en_sync_trigger: 100 },
sequencer_args: SequencerArgs::default(),
beacon_provider_args: BeaconProviderArgs::default(),
signer_args: Default::default(),
}
}

Expand All @@ -162,5 +163,6 @@ pub fn default_sequencer_test_scroll_rollup_node_config() -> ScrollRollupNodeCon
l1_message_inclusion_mode: L1MessageInclusionMode::BlockDepth(0),
},
beacon_provider_args: BeaconProviderArgs::default(),
signer_args: Default::default(),
}
}
2 changes: 2 additions & 0 deletions crates/node/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ async fn can_bridge_l1_messages() -> eyre::Result<()> {
..SequencerArgs::default()
},
beacon_provider_args: BeaconProviderArgs::default(),
signer_args: Default::default(),
};
let (mut nodes, _tasks, _wallet) = setup_engine(node_args, 1, chain_spec, false).await?;
let node = nodes.pop().unwrap();
Expand Down Expand Up @@ -106,6 +107,7 @@ async fn can_sequence_and_gossip_blocks() {
..SequencerArgs::default()
},
beacon_provider_args: BeaconProviderArgs::default(),
signer_args: Default::default(),
};

let (nodes, _tasks, wallet) =
Expand Down
6 changes: 6 additions & 0 deletions crates/sequencer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ metrics-derive.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
eyre.workspace = true

[dev-dependencies]
# alloy
alloy-consensus.workspace = true
alloy-primitives.workspace = true

rollup-node-manager.workspace = true
rollup-node-signer.workspace = true

# scroll-alloy
scroll-alloy-consensus.workspace = true

Expand All @@ -59,3 +63,5 @@ scroll-engine.workspace = true

# misc
futures.workspace = true
tempfile = "3.20.0"
alloy-signer-local.workspace = true
Loading