Skip to content

Commit

Permalink
Merge pull request #301 from chainbound/nico/chore/commitment-key
Browse files Browse the repository at this point in the history
feat(sidecar): add commitment signing key + holesky deployment scripts
  • Loading branch information
thedevbirb authored Oct 18, 2024
2 parents b390910 + 13de577 commit e919dc2
Show file tree
Hide file tree
Showing 18 changed files with 250 additions and 115 deletions.
17 changes: 11 additions & 6 deletions bolt-sidecar/.env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@

# node + PBS URLs
# Ethereum node connections
BOLT_SIDECAR_EXECUTION_API_URL=http://localhost:4485
BOLT_SIDECAR_BEACON_API_URL=http://localhost:4400
BOLT_SIDECAR_ENGINE_API_URL=http://localhost:4451
BOLT_SIDECAR_ENGINE_JWT_HEX=

# Constraint URL: should point to the constraint API sidecar.
# Usually this corresponds to `mev-boost` or `bolt-boost`
BOLT_SIDECAR_CONSTRAINTS_URL=http://localhost:19550

# Commit-boost specific options (optional)
BOLT_SIDECAR_CB_SIGNER_URL=http://localhost:19551
BOLT_SIDECAR_CB_JWT_HEX=

# server ports
BOLT_SIDECAR_PORT=8000
Expand All @@ -15,14 +21,13 @@ BOLT_SIDECAR_MAX_COMMITMENTS=128
BOLT_SIDECAR_MAX_COMMITTED_GAS=10000000

# chain configs
BOLT_SIDECAR_CHAIN=helder
BOLT_SIDECAR_CHAIN=holesky
BOLT_SIDECAR_COMMITMENT_DEADLINE=8000
BOLT_SIDECAR_SLOT_TIME=12

# sidecar security configs
BOLT_SIDECAR_VALIDATOR_INDEXES=
BOLT_SIDECAR_JWT_HEX=
BOLT_SIDECAR_CB_JWT_HEX=
BOLT_SIDECAR_FEE_RECIPIENT=
BOLT_SIDECAR_BUILDER_PRIVATE_KEY=
BOLT_SIDECAR_PRIVATE_KEY=
BOLT_SIDECAR_CONSTRAINT_PRIVATE_KEY=
BOLT_SIDECAR_COMMITMENT_PRIVATE_KEY=
3 changes: 2 additions & 1 deletion bolt-sidecar/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target/
.env
.env.dev
.env.*
!.env.example
20 changes: 17 additions & 3 deletions bolt-sidecar/Config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,29 @@ metrics_port = 3300
execution_api_url = "http://localhost:8545"
beacon_api_url = "http://localhost:5052"
engine_api_url = "http://localhost:8551"
engine_jwt_hex = "0x573300d0cd9a8e253429998a3ceecf358aa4868d96772d1344a80144d3b7b593"

# constraints options
constraints_url = "http://localhost:3030"
constraints_api_url = "http://localhost:3030"
constraints_proxy_port = 18551

validator_indexes = "0..64"
fee_recipient = "0x0155ef0C0fE550C297c1216585e0DE1478EA30e4"

builder_private_key = "0x359c156600e1f5715a58c9e09f17c8d04e7ee3a9f88b39f6e489ffca0148fabe"
commitment_private_key = "0x359c156600e1f5715a58c9e09f17c8d04e7ee3a9f88b39f6e489ffca0148fabe"

# chain options
chain = "kurtosis"
[chain]
chain = "Kurtosis"
slot_time = 2
commitment_deadline = 8000

[telemetry]
metrics_port = 3300
disable_metrics = false

# signing options
private_key = "0x359c156600e1f5715a58c9e09f17c8d04e7ee3a9f88b39f6e489ffca0148fabe"
[constraint_signing]
constraint_private_key = "0x359c156600e1f5715a58c9e09f17c8d04e7ee3a9f88b39f6e489ffca0148fabe"
delegations_path = "./delegations.json"
4 changes: 2 additions & 2 deletions bolt-sidecar/bin/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ async fn main() -> Result<()> {

info!(chain = opts.chain.name(), "Starting Bolt sidecar");

if opts.signing.private_key.is_some() {
if opts.constraint_signing.constraint_private_key.is_some() {
match SidecarDriver::with_local_signer(&opts).await {
Ok(driver) => driver.run_forever().await,
Err(err) => {
bail!("Failed to initialize the sidecar driver with local signer: {:?}", err)
}
}
} else if opts.signing.commit_boost_jwt_hex.is_some() {
} else if opts.constraint_signing.commit_boost_signer_url.is_some() {
match SidecarDriver::with_commit_boost_signer(&opts).await {
Ok(driver) => driver.run_forever().await,
Err(err) => {
Expand Down
2 changes: 1 addition & 1 deletion bolt-sidecar/src/builder/payload_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl FallbackPayloadBuilder {
pub fn new(config: &Opts, beacon_api_client: BeaconClient, genesis_time: u64) -> Self {
let engine_hinter = EngineHinter {
client: reqwest::Client::new(),
jwt_hex: config.jwt_hex.to_string(),
jwt_hex: config.engine_jwt_hex.to_string(),
engine_rpc_url: config.engine_api_url.clone(),
};

Expand Down
46 changes: 45 additions & 1 deletion bolt-sidecar/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
path::Path,
};

use alloy::primitives::U256;
use alloy::{primitives::U256, signers::k256::ecdsa::SigningKey};
use blst::min_pk::SecretKey;
use rand::{Rng, RngCore};
use reth_primitives::PooledTransactionsElement;
Expand Down Expand Up @@ -139,6 +139,50 @@ impl fmt::Display for BlsSecretKeyWrapper {
}
}

#[derive(Clone, Debug)]
pub struct EcdsaSecretKeyWrapper(pub SigningKey);

impl EcdsaSecretKeyWrapper {
/// Generate a new random ECDSA secret key.
#[allow(dead_code)]
pub fn random() -> Self {
Self(SigningKey::random(&mut rand::thread_rng()))
}
}

impl<'de> Deserialize<'de> for EcdsaSecretKeyWrapper {
fn deserialize<D>(deserializer: D) -> Result<EcdsaSecretKeyWrapper, D::Error>
where
D: Deserializer<'de>,
{
let sk = String::deserialize(deserializer)?;
Ok(EcdsaSecretKeyWrapper::from(sk.as_str()))
}
}

impl From<&str> for EcdsaSecretKeyWrapper {
fn from(sk: &str) -> Self {
let hex_sk = sk.strip_prefix("0x").unwrap_or(sk);
let bytes = hex::decode(hex_sk).expect("valid hex");
let sk = SigningKey::from_slice(&bytes).expect("valid sk");
EcdsaSecretKeyWrapper(sk)
}
}

impl Display for EcdsaSecretKeyWrapper {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{}", hex::encode(self.0.to_bytes()))
}
}

impl Deref for EcdsaSecretKeyWrapper {
type Target = SigningKey;

fn deref(&self) -> &Self::Target {
&self.0
}
}

#[derive(Debug, Clone)]
pub struct JwtSecretConfig(pub String);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
use std::{fmt, net::SocketAddr, path::PathBuf};
use std::{fmt, path::PathBuf};

use clap::{ArgGroup, Args};
use lighthouse_account_utils::ZeroizeString;
use reqwest::Url;
use serde::Deserialize;

use crate::common::{BlsSecretKeyWrapper, JwtSecretConfig};

/// Command-line options for signing
/// Command-line options for signing constraint messages
#[derive(Args, Deserialize)]
#[clap(
group = ArgGroup::new("signing-opts").required(true)
.args(&["private_key", "commit_boost_address", "keystore_password", "keystore_secrets_path"])
.args(&["constraint_private_key", "commit_boost_signer_url", "keystore_password", "keystore_secrets_path"])
)]
pub struct SigningOpts {
/// Private key to use for signing preconfirmation requests
#[clap(long, env = "BOLT_SIDECAR_PRIVATE_KEY")]
pub private_key: Option<BlsSecretKeyWrapper>,
/// Socket address for the commit-boost sidecar
pub struct ConstraintSigningOpts {
/// Private key to use for signing constraint messages
#[clap(long, env = "BOLT_SIDECAR_CONSTRAINT_PRIVATE_KEY")]
pub constraint_private_key: Option<BlsSecretKeyWrapper>,
/// URL for the commit-boost sidecar
#[clap(long, env = "BOLT_SIDECAR_CB_SIGNER_URL", requires("commit_boost_jwt_hex"))]
pub commit_boost_address: Option<SocketAddr>,
pub commit_boost_signer_url: Option<Url>,
/// JWT in hexadecimal format for authenticating with the commit-boost service
#[clap(long, env = "BOLT_SIDECAR_CB_JWT_HEX", requires("commit_boost_address"))]
#[clap(long, env = "BOLT_SIDECAR_CB_JWT_HEX", requires("commit_boost_signer_url"))]
pub commit_boost_jwt_hex: Option<JwtSecretConfig>,
/// The password for the ERC-2335 keystore.
/// Reference: https://eips.ethereum.org/EIPS/eip-2335
Expand All @@ -39,11 +40,11 @@ pub struct SigningOpts {
}

// Implement Debug manually to hide the keystore_password field
impl fmt::Debug for SigningOpts {
impl fmt::Debug for ConstraintSigningOpts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SigningOpts")
.field("private_key", &self.private_key)
.field("commit_boost_address", &self.commit_boost_address)
.field("constraint_private_key", &"********") // Hides the actual private key
.field("commit_boost_signer_url", &self.commit_boost_signer_url)
.field("commit_boost_jwt_hex", &self.commit_boost_jwt_hex)
.field("keystore_password", &"********") // Hides the actual password
.field("keystore_path", &self.keystore_path)
Expand Down
76 changes: 46 additions & 30 deletions bolt-sidecar/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{fs::File, io::Read};
use std::fs;

use alloy::primitives::Address;
use clap::Parser;
Expand All @@ -12,16 +12,16 @@ pub use validator_indexes::ValidatorIndexes;
pub mod chain;
pub use chain::ChainConfig;

pub mod signing;
pub use signing::SigningOpts;
pub mod constraint_signing;
pub use constraint_signing::ConstraintSigningOpts;

pub mod telemetry;
use telemetry::TelemetryOpts;

pub mod limits;
use limits::LimitsOpts;

use crate::common::{BlsSecretKeyWrapper, JwtSecretConfig};
use crate::common::{BlsSecretKeyWrapper, EcdsaSecretKeyWrapper, JwtSecretConfig};

/// Default port for the JSON-RPC server exposed by the sidecar.
pub const DEFAULT_RPC_PORT: u16 = 8000;
Expand All @@ -45,11 +45,21 @@ pub struct Opts {
/// Execution client Engine API URL
#[clap(long, env = "BOLT_SIDECAR_ENGINE_API_URL", default_value = "http://localhost:8551")]
pub engine_api_url: Url,
/// URL for the Constraint sidecar client to use
#[clap(long, env = "BOLT_SIDECAR_CONSTRAINTS_URL", default_value = "http://localhost:3030")]
pub constraints_url: Url,
/// Constraint proxy server port to use
#[clap(long, env = "BOLT_SIDECAR_CONSTRAINTS_PROXY_PORT", default_value_t = DEFAULT_CONSTRAINTS_PROXY_PORT)]
/// URL to forward the constraints produced by the Bolt sidecar to a server supporting the
/// Constraints API, such as an MEV-Boost fork.
#[clap(
long,
env = "BOLT_SIDECAR_CONSTRAINTS_API_URL",
default_value = "http://localhost:3030"
)]
pub constraints_api_url: Url,
/// The port from which the Bolt sidecar will receive Builder-API requests from the
/// Beacon client
#[clap(
long,
env = "BOLT_SIDECAR_CONSTRAINTS_PROXY_PORT",
default_value_t = DEFAULT_CONSTRAINTS_PROXY_PORT
)]
pub constraints_proxy_port: u16,
/// Validator indexes of connected validators that the sidecar
/// should accept commitments on behalf of. Accepted values:
Expand All @@ -62,41 +72,43 @@ pub struct Opts {
///
/// It can either be a hex-encoded string or a file path to a file
/// containing the hex-encoded secret.
#[clap(long, env = "BOLT_SIDECAR_JWT_HEX", default_value_t)]
pub jwt_hex: JwtSecretConfig,
#[clap(long, env = "BOLT_SIDECAR_ENGINE_JWT_HEX")]
pub engine_jwt_hex: JwtSecretConfig,
/// The fee recipient address for fallback blocks
#[clap(long, env = "BOLT_SIDECAR_FEE_RECIPIENT", default_value_t = Address::ZERO)]
#[clap(long, env = "BOLT_SIDECAR_FEE_RECIPIENT")]
pub fee_recipient: Address,
/// Secret BLS key to sign fallback payloads with (If not provided, a random key will be used)
#[clap(long, env = "BOLT_SIDECAR_BUILDER_PRIVATE_KEY", default_value_t = BlsSecretKeyWrapper::random())]
#[clap(long, env = "BOLT_SIDECAR_BUILDER_PRIVATE_KEY")]
pub builder_private_key: BlsSecretKeyWrapper,
/// Secret ECDSA key to sign commitment messages with
#[clap(long, env = "BOLT_SIDECAR_COMMITMENT_PRIVATE_KEY")]
pub commitment_private_key: EcdsaSecretKeyWrapper,
/// Operating limits for the sidecar
#[clap(flatten)]
#[serde(default)]
pub limits: LimitsOpts,
/// Chain config for the chain on which the sidecar is running
#[clap(flatten)]
pub chain: ChainConfig,
/// Commitment signing options.
/// Constraint signing options
#[clap(flatten)]
pub signing: SigningOpts,
pub constraint_signing: ConstraintSigningOpts,
/// Telemetry options
#[clap(flatten)]
pub telemetry: TelemetryOpts,

/// Additional unrecognized arguments. Useful for CI and testing
/// to avoid issues on potential extra flags provided (e.g. "--exact" from cargo nextest).
#[cfg(test)]
#[clap(allow_hyphen_values = true)]
#[serde(default)]
pub extra_args: Vec<String>,
}

impl Opts {
/// Parse the configuration from a TOML file.
pub fn parse_from_toml(file_path: &str) -> eyre::Result<Self> {
let mut file = File::open(file_path).wrap_err("Unable to open file")?;

let mut contents = String::new();
file.read_to_string(&mut contents).wrap_err("Unable to read file")?;

let contents = fs::read_to_string(file_path).wrap_err("Unable to read file")?;
toml::from_str(&contents).wrap_err("Error parsing the TOML file")
}
}
Expand All @@ -105,6 +117,12 @@ impl Opts {
mod tests {
use super::*;

#[test]
fn test_validate_cli_flags() {
use clap::CommandFactory;
Opts::command().debug_assert();
}

#[test]
fn test_parse_url() {
let url = "http://0.0.0.0:3030";
Expand All @@ -116,15 +134,13 @@ mod tests {

#[test]
fn test_parse_config_from_toml() {
let path = env!("CARGO_MANIFEST_DIR").to_string() + "Config.toml";

if let Ok(config_file) = std::fs::read_to_string(path) {
let config = Opts::parse_from_toml(&config_file).expect("Failed to parse config");
assert_eq!(config.execution_api_url, Url::parse("http://localhost:8545").unwrap());
assert_eq!(config.beacon_api_url, Url::parse("http://localhost:5052").unwrap());
assert_eq!(config.engine_api_url, Url::parse("http://localhost:8551").unwrap());
assert_eq!(config.constraints_url, Url::parse("http://localhost:3030").unwrap());
assert_eq!(config.constraints_proxy_port, 18551);
}
let path = env!("CARGO_MANIFEST_DIR").to_string() + "/Config.example.toml";

let config = Opts::parse_from_toml(&path).expect("Failed to parse config from TOML");
assert_eq!(config.execution_api_url, Url::parse("http://localhost:8545").unwrap());
assert_eq!(config.beacon_api_url, Url::parse("http://localhost:5052").unwrap());
assert_eq!(config.engine_api_url, Url::parse("http://localhost:8551").unwrap());
assert_eq!(config.constraints_api_url, Url::parse("http://localhost:3030").unwrap());
assert_eq!(config.constraints_proxy_port, 18551);
}
}
Loading

0 comments on commit e919dc2

Please sign in to comment.