diff --git a/bolt-sidecar/.env.example b/bolt-sidecar/.env.example index a7ed8591..cb234a59 100644 --- a/bolt-sidecar/.env.example +++ b/bolt-sidecar/.env.example @@ -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 @@ -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= diff --git a/bolt-sidecar/.gitignore b/bolt-sidecar/.gitignore index 8f74f83d..55509a80 100644 --- a/bolt-sidecar/.gitignore +++ b/bolt-sidecar/.gitignore @@ -1,3 +1,4 @@ target/ .env -.env.dev +.env.* +!.env.example diff --git a/bolt-sidecar/Cargo.lock b/bolt-sidecar/Cargo.lock index 3c0f5a3e..249f061b 100644 --- a/bolt-sidecar/Cargo.lock +++ b/bolt-sidecar/Cargo.lock @@ -1675,7 +1675,6 @@ dependencies = [ "serde", "serde_json", "ssz_rs 0.9.0 (git+https://github.com/ralexstokes/ssz-rs)", - "tempfile", "thiserror", "tokio", "toml 0.5.11", @@ -1974,9 +1973,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -1984,9 +1983,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -1997,9 +1996,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -7653,12 +7652,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/bolt-sidecar/Cargo.toml b/bolt-sidecar/Cargo.toml index e4f82c5c..44205ad0 100644 --- a/bolt-sidecar/Cargo.toml +++ b/bolt-sidecar/Cargo.toml @@ -6,7 +6,7 @@ default-run = "bolt-sidecar" [dependencies] # core -clap = { version = "4.5.4", features = ["derive", "env"] } +clap = { version = "4.5.20", features = ["derive", "env"] } tokio = { version = "1", features = ["full"] } axum = { version = "0.7", features = ["macros"] } tower-http = { version = "0.5.2", features = ["timeout"] } @@ -79,7 +79,6 @@ cb-common = { git = "https://github.com/Commit-Boost/commit-boost-client", rev = [dev-dependencies] alloy-node-bindings = "0.2.0" -tempfile = "3.13.0" [[bin]] diff --git a/bolt-sidecar/Config.example.toml b/bolt-sidecar/Config.example.toml index 05f97692..b73e925d 100644 --- a/bolt-sidecar/Config.example.toml +++ b/bolt-sidecar/Config.example.toml @@ -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" diff --git a/bolt-sidecar/bin/sidecar.rs b/bolt-sidecar/bin/sidecar.rs index 53d687ad..c7fbf60c 100644 --- a/bolt-sidecar/bin/sidecar.rs +++ b/bolt-sidecar/bin/sidecar.rs @@ -18,25 +18,25 @@ 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.keystore_password.is_some() { - match SidecarDriver::with_keystore_signer(&opts).await { + } 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) => { - bail!("Failed to initialize the sidecar driver with keystore signer: {:?}", err) + bail!("Failed to initialize the sidecar driver with commit boost: {:?}", err) } } } else { - match SidecarDriver::with_commit_boost_signer(&opts).await { + match SidecarDriver::with_keystore_signer(&opts).await { Ok(driver) => driver.run_forever().await, Err(err) => { - bail!("Failed to initialize the sidecar driver with commit boost: {:?}", err) + bail!("Failed to initialize the sidecar driver with keystore signer: {:?}", err) } } } diff --git a/bolt-sidecar/keys/.gitignore b/bolt-sidecar/keys/.gitignore deleted file mode 100644 index 7c9d611b..00000000 --- a/bolt-sidecar/keys/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!README.md diff --git a/bolt-sidecar/src/builder/payload_builder.rs b/bolt-sidecar/src/builder/payload_builder.rs index 1dfe2e9f..c8338262 100644 --- a/bolt-sidecar/src/builder/payload_builder.rs +++ b/bolt-sidecar/src/builder/payload_builder.rs @@ -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(), }; diff --git a/bolt-sidecar/src/common.rs b/bolt-sidecar/src/common.rs index b8d6e80c..439816cf 100644 --- a/bolt-sidecar/src/common.rs +++ b/bolt-sidecar/src/common.rs @@ -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; @@ -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(deserializer: D) -> Result + 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); diff --git a/bolt-sidecar/src/config/signing.rs b/bolt-sidecar/src/config/constraint_signing.rs similarity index 52% rename from bolt-sidecar/src/config/signing.rs rename to bolt-sidecar/src/config/constraint_signing.rs index beb8f2fe..50356f69 100644 --- a/bolt-sidecar/src/config/signing.rs +++ b/bolt-sidecar/src/config/constraint_signing.rs @@ -1,49 +1,54 @@ -use std::{fmt, net::SocketAddr}; +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"]) + .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, - /// 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, + /// URL for the commit-boost sidecar #[clap(long, env = "BOLT_SIDECAR_CB_SIGNER_URL", requires("commit_boost_jwt_hex"))] - pub commit_boost_address: Option, + pub commit_boost_signer_url: Option, /// 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, /// The password for the ERC-2335 keystore. /// Reference: https://eips.ethereum.org/EIPS/eip-2335 #[clap(long, env = "BOLT_SIDECAR_KEYSTORE_PASSWORD")] pub keystore_password: Option, + /// The path to the ERC-2335 keystore secret passwords + /// Reference: https://eips.ethereum.org/EIPS/eip-2335 + #[clap(long, env = "BOLT_SIDECAR_KEYSTORE_SECRETS_PATH", conflicts_with("keystore_password"))] + pub keystore_secrets_path: Option, /// Path to the keystores folder. If not provided, the default path is used. - #[clap(long, env = "BOLT_SIDECAR_KEYSTORE_PATH", requires("keystore_password"))] - pub keystore_path: Option, + #[clap(long, env = "BOLT_SIDECAR_KEYSTORE_PATH")] + pub keystore_path: Option, /// Path to the delegations file. If not provided, the default path is used. #[clap(long, env = "BOLT_SIDECAR_DELEGATIONS_PATH")] - pub delegations_path: Option, + pub delegations_path: Option, } // 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_url", &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) - .field("delegations_path", &self.delegations_path) + .field("keystore_secrets_path", &self.keystore_secrets_path) .finish() } } diff --git a/bolt-sidecar/src/config/mod.rs b/bolt-sidecar/src/config/mod.rs index 93dfa069..af325600 100644 --- a/bolt-sidecar/src/config/mod.rs +++ b/bolt-sidecar/src/config/mod.rs @@ -1,4 +1,4 @@ -use std::{fs::File, io::Read}; +use std::fs; use alloy::primitives::Address; use clap::Parser; @@ -12,8 +12,8 @@ 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; @@ -21,7 +21,7 @@ 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; @@ -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: @@ -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, } impl Opts { /// Parse the configuration from a TOML file. pub fn parse_from_toml(file_path: &str) -> eyre::Result { - 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") } } @@ -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"; @@ -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); } } diff --git a/bolt-sidecar/src/driver.rs b/bolt-sidecar/src/driver.rs index 77dcb671..2c433eed 100644 --- a/bolt-sidecar/src/driver.rs +++ b/bolt-sidecar/src/driver.rs @@ -90,13 +90,17 @@ impl SidecarDriver { // Constraints are signed with a BLS private key let constraint_signer = SignerBLS::Local(LocalSigner::new( - opts.signing.private_key.clone().expect("local signer").0, + opts.constraint_signing + .constraint_private_key + .clone() + .expect("local constraint signing key") + .0, opts.chain, )); // Commitment responses are signed with a regular Ethereum wallet private key. - // This is now generated randomly because slashing is not yet implemented. - let commitment_signer = PrivateKeySigner::random(); + let commitment_key = opts.commitment_private_key.0.clone(); + let commitment_signer = PrivateKeySigner::from_signing_key(commitment_key); Self::from_components(opts, constraint_signer, commitment_signer, state_client).await } @@ -108,15 +112,25 @@ impl SidecarDriver { // The default state client simply uses the execution API URL to fetch state updates. let state_client = StateClient::new(opts.execution_api_url.clone()); - let keystore_signer = SignerBLS::Keystore(KeystoreSigner::new( - opts.signing.keystore_path.as_deref(), - opts.signing.keystore_password.as_ref().expect("keystore password").as_ref(), - opts.chain, - )?); + let keystore = if let Some(psw) = opts.constraint_signing.keystore_password.as_ref() { + KeystoreSigner::from_password( + opts.constraint_signing.keystore_path.as_ref().expect("keystore path"), + psw.as_ref(), + opts.chain, + )? + } else { + KeystoreSigner::from_secrets_directory( + opts.constraint_signing.keystore_path.as_ref().expect("keystore path"), + opts.constraint_signing.keystore_secrets_path.as_ref().expect("keystore secrets"), + opts.chain, + )? + }; + + let keystore_signer = SignerBLS::Keystore(keystore); // Commitment responses are signed with a regular Ethereum wallet private key. - // This is now generated randomly because slashing is not yet implemented. - let commitment_signer = PrivateKeySigner::random(); + let commitment_key = opts.commitment_private_key.0.clone(); + let commitment_signer = PrivateKeySigner::from_signing_key(commitment_key); Self::from_components(opts, keystore_signer, commitment_signer, state_client).await } @@ -129,8 +143,8 @@ impl SidecarDriver { let state_client = StateClient::new(opts.execution_api_url.clone()); let commit_boost_signer = CommitBoostSigner::new( - opts.signing.commit_boost_address.expect("CommitBoost URL").to_string(), - &opts.signing.commit_boost_jwt_hex.clone().expect("CommitBoost JWT"), + opts.constraint_signing.commit_boost_signer_url.clone().expect("CommitBoost URL"), + &opts.constraint_signing.commit_boost_jwt_hex.clone().expect("CommitBoost JWT"), )?; let cb_bls_signer = SignerBLS::CommitBoost(commit_boost_signer.clone()); @@ -166,7 +180,7 @@ impl SidecarDriver { let (payload_requests_tx, payload_requests_rx) = mpsc::channel(16); let builder_proxy_cfg = BuilderProxyConfig { - constraints_url: opts.constraints_url.clone(), + constraints_url: opts.constraints_api_url.clone(), server_port: opts.constraints_proxy_port, }; @@ -183,10 +197,10 @@ impl SidecarDriver { let (api_events_tx, api_events_rx) = mpsc::channel(1024); CommitmentsApiServer::new(api_addr).run(api_events_tx).await; - let mut constraints_client = ConstraintsClient::new(opts.constraints_url.clone()); + let mut constraints_client = ConstraintsClient::new(opts.constraints_api_url.clone()); // read the delegaitons from disk if they exist and add them to the constraints client - if let Some(delegations_file_path) = opts.signing.delegations_path.as_ref() { + if let Some(delegations_file_path) = opts.constraint_signing.delegations_path.as_ref() { let delegations = read_signed_delegations_from_file(delegations_file_path)?; constraints_client.add_delegations(delegations); } diff --git a/bolt-sidecar/src/primitives/delegation.rs b/bolt-sidecar/src/primitives/delegation.rs index 99a3b726..20b689bb 100644 --- a/bolt-sidecar/src/primitives/delegation.rs +++ b/bolt-sidecar/src/primitives/delegation.rs @@ -1,4 +1,4 @@ -use std::fs; +use std::{fs, path::PathBuf}; use alloy::signers::k256::sha2::{Digest, Sha256}; use ethereum_consensus::crypto::{PublicKey as BlsPublicKey, Signature as BlsSignature}; @@ -48,7 +48,9 @@ impl SignableBLS for DelegationMessage { } /// read the delegaitons from disk if they exist and add them to the constraints client -pub fn read_signed_delegations_from_file(file_path: &str) -> eyre::Result> { +pub fn read_signed_delegations_from_file( + file_path: &PathBuf, +) -> eyre::Result> { match fs::read_to_string(file_path) { Ok(contents) => match serde_json::from_str::>(&contents) { Ok(delegations) => Ok(delegations), @@ -91,11 +93,14 @@ impl SignableBLS for RevocationMessage { #[cfg(test)] mod tests { + use std::path::PathBuf; + #[test] fn test_read_signed_delegations_from_file() { - let file = env!("CARGO_MANIFEST_DIR").to_string() + "/test_data/delegations.json"; + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("test_data/delegations.json"); - let delegations = super::read_signed_delegations_from_file(&file) + let delegations = super::read_signed_delegations_from_file(&path) .expect("Failed to read delegations from file"); assert_eq!(delegations.len(), 1); diff --git a/bolt-sidecar/src/signer/commit_boost.rs b/bolt-sidecar/src/signer/commit_boost.rs index d3c81a16..e15aec08 100644 --- a/bolt-sidecar/src/signer/commit_boost.rs +++ b/bolt-sidecar/src/signer/commit_boost.rs @@ -8,6 +8,7 @@ use cb_common::{ use commit_boost::prelude::SignProxyRequest; use ethereum_consensus::crypto::bls::PublicKey as BlsPublicKey; use parking_lot::RwLock; +use reqwest::Url; use thiserror::Error; use tracing::{debug, error, info}; @@ -34,15 +35,15 @@ pub enum CommitBoostError { #[error("failed to create signer client: {0}")] SignerClientError(#[from] SignerClientError), #[error("error in commit boost signer: {0}")] - Other(#[from] eyre::Report), + Other(eyre::Report), } #[allow(unused)] impl CommitBoostSigner { /// Create a new [CommitBoostSigner] instance - pub fn new(signer_server_address: String, jwt: &str) -> SignerResult { - let signer_client = - SignerClient::new(signer_server_address, jwt).map_err(CommitBoostError::Other)?; + pub fn new(signer_url: Url, jwt: &str) -> SignerResult { + let socket_addr = parse_address_from_url(signer_url).map_err(CommitBoostError::Other)?; + let signer_client = SignerClient::new(socket_addr, jwt).map_err(CommitBoostError::Other)?; let client = Self { signer_client, @@ -158,12 +159,34 @@ impl SignerECDSA for CommitBoostSigner { } } +fn parse_address_from_url(url: Url) -> eyre::Result { + let str = url.as_str(); + + // take the host out of the URL, e.g. "http://localhost:425" -> localhost:425 + // and also "remotehost:2425" -> remotehost:2425 + let without_base = url.as_str().split("://").last().unwrap_or(str); + let hostname = without_base.split(':').next().unwrap_or(without_base); + let port = without_base.split(':').last().ok_or_else(|| eyre::eyre!("No port found"))?; + let port = port.trim_end_matches('/'); + + Ok(format!("{}:{}", hostname, port)) +} + #[cfg(test)] mod test { use super::*; use rand::Rng; use tracing::warn; + #[test] + fn test_url_parse_address() { + let url = Url::parse("http://localhost:8080").unwrap(); + assert_eq!(parse_address_from_url(url).unwrap(), "localhost:8080"); + + let url = Url::parse("remotehost:2425").unwrap(); + assert_eq!(parse_address_from_url(url).unwrap(), "remotehost:2425"); + } + #[tokio::test] async fn test_bls_commit_boost_signer() -> eyre::Result<()> { let _ = dotenvy::dotenv(); @@ -178,7 +201,7 @@ mod test { return Ok(()); } }; - let signer = CommitBoostSigner::new(signer_server_address, &jwt_hex).unwrap(); + let signer = CommitBoostSigner::new(signer_server_address.parse()?, &jwt_hex).unwrap(); // Generate random data for the test let mut rng = rand::thread_rng(); @@ -208,7 +231,7 @@ mod test { return Ok(()); } }; - let signer = CommitBoostSigner::new(signer_server_address, &jwt_hex).unwrap(); + let signer = CommitBoostSigner::new(signer_server_address.parse()?, &jwt_hex).unwrap(); let pubkey = signer.get_proxy_ecdsa_pubkey(); // Generate random data for the test diff --git a/bolt-sidecar/src/signer/keystore.rs b/bolt-sidecar/src/signer/keystore.rs index efe87f5f..999e1b4e 100644 --- a/bolt-sidecar/src/signer/keystore.rs +++ b/bolt-sidecar/src/signer/keystore.rs @@ -20,14 +20,14 @@ use crate::{builder::signature::compute_signing_root, crypto::bls::BLSSig, Chain use super::SignerResult; -pub const KEYSTORES_DEFAULT_PATH: &str = "keys"; - #[derive(Debug, thiserror::Error)] pub enum KeystoreError { #[error("failed to read keystore directory: {0}")] ReadFromDirectory(#[from] std::io::Error), #[error("failed to read keystore from JSON file {0}: {1}")] ReadFromJSON(PathBuf, String), + #[error("failed to read keystore secret from file: {0}")] + ReadFromSecretFile(String), #[error("failed to decrypt keypair from JSON file {0} with the provided password: {1}")] KeypairDecryption(PathBuf, String), #[error("could not find private key associated to public key {0}")] @@ -44,14 +44,19 @@ pub struct KeystoreSigner { impl KeystoreSigner { /// Creates a new `KeystoreSigner` from the keystore files in the `keys_path` directory. - pub fn new(keys_path: Option<&str>, password: &[u8], chain: ChainConfig) -> SignerResult { - let keystores_paths = keystore_paths(keys_path)?; + pub fn from_password( + keys_path: &PathBuf, + password: &[u8], + chain: ChainConfig, + ) -> SignerResult { + // Create the path to the keystore directory, starting from the root of the project + let keystores_paths = find_json_keystores(keys_path)?; let mut keypairs = Vec::with_capacity(keystores_paths.len()); for path in keystores_paths { - let keypair = Keystore::from_json_file(path.clone()); - let keypair = keypair - .map_err(|e| KeystoreError::ReadFromJSON(path.clone(), format!("{e:?}")))? + let keystore = Keystore::from_json_file(path.clone()) + .map_err(|e| KeystoreError::ReadFromJSON(path.clone(), format!("{e:?}")))?; + let keypair = keystore .decrypt_keypair(password) .map_err(|e| KeystoreError::KeypairDecryption(path.clone(), format!("{e:?}")))?; keypairs.push(keypair); @@ -60,6 +65,36 @@ impl KeystoreSigner { Ok(Self { keypairs, chain }) } + pub fn from_secrets_directory( + keys_path: &PathBuf, + secrets_path: &Path, + chain: ChainConfig, + ) -> SignerResult { + let keystores_paths = find_json_keystores(keys_path)?; + + let mut keypairs = Vec::with_capacity(keystores_paths.len()); + + for path in keystores_paths { + let keystore = Keystore::from_json_file(path.clone()) + .map_err(|e| KeystoreError::ReadFromJSON(path.clone(), format!("{e:?}")))?; + + let pubkey = format!("0x{}", keystore.pubkey()); + + let mut secret_path = secrets_path.to_path_buf(); + secret_path.push(pubkey); + + let password = fs::read_to_string(secret_path) + .map_err(|e| KeystoreError::ReadFromSecretFile(format!("{e:?}")))?; + + let keypair = keystore + .decrypt_keypair(password.as_bytes()) + .map_err(|e| KeystoreError::KeypairDecryption(path.clone(), format!("{e:?}")))?; + keypairs.push(keypair); + } + + Ok(Self { keypairs, chain }) + } + /// Returns the public keys of the keypairs in the keystore. pub fn pubkeys(&self) -> HashSet { self.keypairs @@ -105,7 +140,7 @@ impl KeystoreSigner { impl Debug for KeystoreSigner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Signer") + f.debug_struct("KeystoreSigner") .field( "pubkeys", &self.keypairs.iter().map(|kp| kp.pk.as_hex_string()).collect::>(), @@ -121,15 +156,7 @@ impl Debug for KeystoreSigner { /// -- 0x1234.../validator.json /// -- 0x5678.../validator.json /// -- ... -fn keystore_paths(keys_path: Option<&str>) -> SignerResult> { - // Create the path to the keystore directory, starting from the root of the project - let keys_path = if let Some(keys_path) = keys_path { - Path::new(&keys_path).to_path_buf() - } else { - let project_root = env!("CARGO_MANIFEST_DIR"); - Path::new(project_root).join(keys_path.unwrap_or(KEYSTORES_DEFAULT_PATH)) - }; - +fn find_json_keystores(keys_path: &PathBuf) -> SignerResult> { let json_extension = OsString::from("json"); let mut keystores_paths = vec![]; @@ -137,7 +164,7 @@ fn keystore_paths(keys_path: Option<&str>) -> SignerResult> { for entry in read_dir(keys_path)? { let path = read_path(entry)?; if path.is_dir() { - for entry in read_dir(path)? { + for entry in read_dir(&path)? { let path = read_path(entry)?; if path.is_file() && path.extension() == Some(&json_extension) { keystores_paths.push(path); @@ -149,7 +176,7 @@ fn keystore_paths(keys_path: Option<&str>) -> SignerResult> { Ok(keystores_paths) } -fn read_dir(path: PathBuf) -> SignerResult { +fn read_dir(path: &PathBuf) -> SignerResult { Ok(fs::read_dir(path).map_err(KeystoreError::ReadFromDirectory)?) } @@ -159,16 +186,30 @@ fn read_path(entry: std::result::Result) -> SignerResult PathBuf { + let project_root = env!("CARGO_MANIFEST_DIR"); + Path::new(project_root).join(relative_path) + } + #[test] fn test_keystore_signer() { // 0. Test data setup @@ -245,74 +286,85 @@ mod tests { ]; // Reference: https://eips.ethereum.org/EIPS/eip-2335#test-cases - let keystore_password = r#"𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑"#; - let keystore_public_key = "0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07"; - let keystore_public_key_bytes: [u8; 48] = [ + let password = r#"𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑"#; + let public_key = "0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07"; + let public_key_bytes: [u8; 48] = [ 0x96, 0x12, 0xd7, 0xa7, 0x27, 0xc9, 0xd0, 0xa2, 0x2e, 0x18, 0x5a, 0x1c, 0x76, 0x84, 0x78, 0xdf, 0xe9, 0x19, 0xca, 0xda, 0x92, 0x66, 0x98, 0x8c, 0xb3, 0x23, 0x59, 0xc1, 0x1f, 0x2b, 0x7b, 0x27, 0xf4, 0xae, 0x40, 0x40, 0x90, 0x23, 0x82, 0xae, 0x29, 0x10, 0xc1, 0x5e, 0x2b, 0x42, 0x0d, 0x07, ]; - let keystore_secret_key = - "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; + let secret_key = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; let chain_config = ChainConfig::mainnet(); - // 1. Create a temp directory with the keystore and create a signer from it - - let path_str = format!("{}/{}", CARGO_MANIFEST_DIR, KEYSTORES_DEFAULT_PATH); + let keystore_path = + format!("{}/{}/{}", CARGO_MANIFEST_DIR, KEYSTORES_DEFAULT_PATH_TEST, public_key); + let keystore_path = PathBuf::from(keystore_path); for test_keystore_json in tests_keystore_json { - let tmp_dir = tempfile::TempDir::with_prefix_in( - "0xdeadbeefdeadbeefdeadbeefdeadbeef", - path_str.clone(), - ) - .expect("to create temp dir"); - - // NOTE: it is sufficient to create a temp dir, then we can create a file as usual and - // it will be dropped correctly - let mut tmp_file = File::create_new(tmp_dir.path().join("voting-keystore.json")) - .expect("to create new file"); - - tmp_file.write_all(test_keystore_json.as_bytes()).expect("to write to temp file"); - - for entry in tmp_dir.path().read_dir().expect("to read tmp dir") { - let mut path = entry.expect("to read entry").path(); - println!("inside loop: {:?}", path); - let extenstion = path - .extension() - .expect("to get extension") - .to_str() - .expect("to convert to str"); - - if extenstion.contains("tmp") { - path.set_extension("json"); - println!("path: {:?}", path); - break; - } - } + // 1. Write the keystore in a `test-voting-keystore.json` file so we test both scrypt and PBDKF2 - let keystore_signer = - KeystoreSigner::new(None, keystore_password.as_bytes(), chain_config) - .expect("to create keystore signer"); + let mut tmp_keystore_file = + File::create(keystore_path.join("test-voting-keystore.json")) + .expect("to create new keystore file"); + + tmp_keystore_file + .write_all(test_keystore_json.as_bytes()) + .expect("to write to temp file"); + + // Create a file for the secret, we are going to test it as well + let keystores_secrets_path = make_path(KEYSTORES_SECRETS_DEFAULT_PATH_TEST); + let mut tmp_secret_file = File::create(keystores_secrets_path.join(public_key)) + .expect("to create secret file"); + + tmp_secret_file.write_all(password.as_bytes()).expect("to write to temp file"); + + let keys_path = make_path(KEYSTORES_DEFAULT_PATH_TEST); + let keystore_signer_from_password = + KeystoreSigner::from_password(&keys_path, password.as_bytes(), chain_config) + .expect("to create keystore signer from password"); + + assert_eq!(keystore_signer_from_password.keypairs.len(), 3); + assert_eq!( + keystore_signer_from_password + .keypairs + .first() + .expect("to get keypair") + .pk + .to_string(), + public_key + ); + + let keystore_signer_from_directory = KeystoreSigner::from_secrets_directory( + &keys_path, + &keystores_secrets_path, + chain_config, + ) + .expect("to create keystore signer from secrets dir"); - assert_eq!(keystore_signer.keypairs.len(), 1); + assert_eq!(keystore_signer_from_directory.keypairs.len(), 3); assert_eq!( - keystore_signer.keypairs.first().expect("to get keypair").pk.to_string(), - keystore_public_key + keystore_signer_from_directory + .keypairs + .first() + .expect("to get keypair") + .pk + .to_string(), + public_key ); // 2. Sign a message with the signer and check the signature let keystore_sk_bls = SecretKey::from_bytes( - hex::decode(keystore_secret_key).expect("to decode secret key").as_slice(), + hex::decode(secret_key).expect("to decode secret key").as_slice(), ) .expect("to create secret key"); let local_signer = LocalSigner::new(keystore_sk_bls, chain_config); let sig_local = local_signer.sign_commit_boost_root([0; 32]).expect("to sign message"); - let sig_keystore = keystore_signer - .sign_commit_boost_root([0; 32], keystore_public_key_bytes) + let sig_keystore = keystore_signer_from_password + .sign_commit_boost_root([0; 32], public_key_bytes) .expect("to sign message"); assert_eq!(sig_local, sig_keystore); } diff --git a/bolt-sidecar/src/signer/local.rs b/bolt-sidecar/src/signer/local.rs index 2ce97059..f7481b9d 100644 --- a/bolt-sidecar/src/signer/local.rs +++ b/bolt-sidecar/src/signer/local.rs @@ -28,7 +28,7 @@ pub struct LocalSigner { impl Debug for LocalSigner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Signer") + f.debug_struct("LocalSigner") .field("pubkey", &self.pubkey()) .field("chain", &self.chain.name()) .finish() diff --git a/bolt-sidecar/src/test_util.rs b/bolt-sidecar/src/test_util.rs index b4dbf40c..6eace374 100644 --- a/bolt-sidecar/src/test_util.rs +++ b/bolt-sidecar/src/test_util.rs @@ -1,3 +1,5 @@ +use std::env; + use alloy::{ eips::eip2718::Encodable2718, network::{EthereumWallet, TransactionBuilder}, @@ -18,7 +20,7 @@ use secp256k1::Message; use tracing::warn; use crate::{ - common::{BlsSecretKeyWrapper, JwtSecretConfig}, + common::{BlsSecretKeyWrapper, EcdsaSecretKeyWrapper, JwtSecretConfig}, crypto::{ecdsa::SignableECDSA, SignableBLS}, primitives::{ CommitmentRequest, ConstraintsMessage, DelegationMessage, FullTransaction, @@ -79,13 +81,21 @@ pub(crate) async fn try_get_beacon_api_url() -> Option<&'static str> { /// /// If any of the above values can't be found, the function will return `None`. pub(crate) async fn get_test_config() -> Option { - std::env::set_var("BOLT_SIDECAR_PRIVATE_KEY", BlsSecretKeyWrapper::random().to_string()); + env::set_var("BOLT_SIDECAR_PRIVATE_KEY", BlsSecretKeyWrapper::random().to_string()); + env::set_var("BOLT_SIDECAR_ENGINE_JWT_HEX", JwtSecretConfig::default().to_string()); + env::set_var("BOLT_SIDECAR_FEE_RECIPIENT", Address::ZERO.to_string()); + env::set_var("BOLT_SIDECAR_BUILDER_PRIVATE_KEY", BlsSecretKeyWrapper::random().to_string()); + env::set_var("BOLT_SIDECAR_CONSTRAINT_PRIVATE_KEY", BlsSecretKeyWrapper::random().to_string()); + env::set_var( + "BOLT_SIDECAR_COMMITMENT_PRIVATE_KEY", + EcdsaSecretKeyWrapper::random().to_string(), + ); let _ = dotenvy::dotenv(); let mut opts = Opts::parse(); - let Some(jwt) = std::env::var("ENGINE_JWT").ok() else { + let Some(jwt) = env::var("ENGINE_JWT").ok() else { warn!("ENGINE_JWT not found in environment variables"); return None; }; @@ -99,7 +109,7 @@ pub(crate) async fn get_test_config() -> Option { if let Some(url) = try_get_engine_api_url().await { opts.engine_api_url = url.parse().expect("valid URL"); } - opts.jwt_hex = JwtSecretConfig(jwt); + opts.engine_jwt_hex = JwtSecretConfig(jwt); Some(opts) } diff --git a/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/.gitignore b/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/.gitignore new file mode 100644 index 00000000..4ce30e9a --- /dev/null +++ b/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/.gitignore @@ -0,0 +1 @@ +test-voting-keystore.json diff --git a/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/voting-keystore-pbkdf2.json b/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/voting-keystore-pbkdf2.json new file mode 100644 index 00000000..f2618d39 --- /dev/null +++ b/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/voting-keystore-pbkdf2.json @@ -0,0 +1,31 @@ +{ + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad" + } + }, + "description": "This is a test keystore that uses PBKDF2 to secure the secret.", + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 +} diff --git a/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/voting-keystore-scrypt.json b/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/voting-keystore-scrypt.json new file mode 100644 index 00000000..2e6c7915 --- /dev/null +++ b/bolt-sidecar/test_data/keys/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07/voting-keystore-scrypt.json @@ -0,0 +1,33 @@ +{ + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f" + } + }, + "description": "This is a test keystore that uses scrypt to secure the secret.", + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/3141592653/589793238", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "version": 4 +} + diff --git a/bolt-sidecar/keys/README.md b/bolt-sidecar/test_data/keys/README.md similarity index 100% rename from bolt-sidecar/keys/README.md rename to bolt-sidecar/test_data/keys/README.md diff --git a/bolt-sidecar/test_data/secrets/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07 b/bolt-sidecar/test_data/secrets/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07 new file mode 100644 index 00000000..883de65c --- /dev/null +++ b/bolt-sidecar/test_data/secrets/0x9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07 @@ -0,0 +1 @@ +𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑 \ No newline at end of file diff --git a/bolt-sidecar/test_data/secrets/README.md b/bolt-sidecar/test_data/secrets/README.md new file mode 100644 index 00000000..a0af6490 --- /dev/null +++ b/bolt-sidecar/test_data/secrets/README.md @@ -0,0 +1,7 @@ +### About + +This directory is intended to be used as the folder to place ERC-2335 keystores +secrets. + +It is assumed that each secrets file is named after the public key it +corresponds to. and contains the password in plain text. diff --git a/justfile b/justfile index 54827ebc..974344ec 100644 --- a/justfile +++ b/justfile @@ -172,20 +172,20 @@ _build-bolt-boost: cd bolt-boost && docker build -t ghcr.io/chainbound/bolt-boost:0.1.0 . --load # deploy the bolt sidecar to the dev server -deploy-sidecar-dev: - chmod +x ./scripts/deploy_bolt_sidecar.sh && ./scripts/deploy_bolt_sidecar.sh +deploy-sidecar-dev chain: + chmod +x ./scripts/deploy_bolt_sidecar.sh && ./scripts/deploy_bolt_sidecar.sh {{chain}} # Check the status of the sidecar service on the dev server -status-sidecar-dev: - ssh shared@remotebeast "sudo systemctl status bolt_sidecar" | less +status-sidecar-dev chain: + ssh shared@remotebeast "sudo systemctl status bolt_sidecar_{{chain}}" | less # Tail the logs of the service on the dev server -logs-sidecar-dev: - ssh shared@remotebeast "journalctl -qu bolt_sidecar -f" +logs-sidecar-dev chain: + ssh shared@remotebeast "journalctl -qu bolt_sidecar_{{chain}} -f" # Stop the service on the dev server -stop-sidecar-dev: - ssh shared@remotebeast "sudo systemctl stop bolt_sidecar" +stop-sidecar-dev chain: + ssh shared@remotebeast "sudo systemctl stop bolt_sidecar_{{chain}}" # build and push the docker images to the github container registry with the provided tag diff --git a/scripts/bolt_sidecar.service b/scripts/bolt_sidecar.service deleted file mode 100644 index c8b1dedc..00000000 --- a/scripts/bolt_sidecar.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Bolt Sidecar Development Service -After=network.target - -[Service] -User=shared -ExecStart=/usr/local/bin/bolt-sidecar -Restart=on-failure -EnvironmentFile=/home/shared/bolt_sidecar/.env.dev - -[Install] -WantedBy=multi-user.target diff --git a/scripts/bolt_sidecar_helder.service b/scripts/bolt_sidecar_helder.service new file mode 100644 index 00000000..1048f32b --- /dev/null +++ b/scripts/bolt_sidecar_helder.service @@ -0,0 +1,12 @@ +[Unit] +Description=Bolt Sidecar Development Service for Helder +After=network.target + +[Service] +User=shared +ExecStart=/usr/local/bin/bolt-sidecar-helder +Restart=on-failure +EnvironmentFile=/home/shared/helder/bolt_sidecar/.env.helder.dev + +[Install] +WantedBy=multi-user.target diff --git a/scripts/bolt_sidecar_holesky.service b/scripts/bolt_sidecar_holesky.service new file mode 100644 index 00000000..eedbceac --- /dev/null +++ b/scripts/bolt_sidecar_holesky.service @@ -0,0 +1,12 @@ +[Unit] +Description=Bolt Sidecar Development Service for Holesky +After=network.target + +[Service] +User=shared +ExecStart=/usr/local/bin/bolt-sidecar-holesky +Restart=on-failure +EnvironmentFile=/home/shared/holesky/bolt_sidecar/.env.holesky.dev + +[Install] +WantedBy=multi-user.target diff --git a/scripts/deploy_bolt_sidecar.sh b/scripts/deploy_bolt_sidecar.sh index 27f7105e..7baca2cd 100755 --- a/scripts/deploy_bolt_sidecar.sh +++ b/scripts/deploy_bolt_sidecar.sh @@ -4,21 +4,28 @@ # our remote dev server. Requirements: # - Access to Chainbound's Tailnet dev server "remotebeast" # - A .env.dev file in the bolt_sidecar directory, filled with the necessary vars +# and configured with the right chain configuration set -e -# check if ".env.dev" exists. if not, exit with error -test -f ./bolt-sidecar/.env.dev || (echo "No .env.dev file found. Exiting." && exit 1) +# check that the first argument is one of the supported chains: "helder" & "holesky" +if [ "$1" != "helder" ] && [ "$1" != "holesky" ]; then + echo "Invalid chain argument. Supported chains: helder, holesky" + exit 1 +fi + +# check if ".env.$1.dev" exists. if not, exit with error +test -f "./bolt-sidecar/.env.$1.dev" || (echo "No .env.$1.dev file found. Exiting." && exit 1) # copy the files to the remote dev server -rsync -av --exclude target --exclude .git ./bolt-sidecar/ shared@remotebeast:/home/shared/bolt_sidecar -rsync -av ./scripts/bolt_sidecar.service shared@remotebeast:/home/shared/bolt_sidecar/bolt_sidecar.service +rsync -av --exclude target --exclude .git ./bolt-sidecar/ shared@remotebeast:/home/shared/$1/bolt_sidecar +rsync -av ./scripts/bolt_sidecar_$1.service shared@remotebeast:/home/shared/$1/bolt_sidecar/bolt_sidecar_$1.service # build the project on the remote dev server -ssh shared@remotebeast "cd ~/bolt_sidecar && CC=clang ~/.cargo/bin/cargo build --release" -ssh shared@remotebeast "mv ~/bolt_sidecar/target/release/bolt-sidecar /usr/local/bin/bolt-sidecar || true" -ssh shared@remotebeast "cp -f ~/bolt_sidecar/bolt_sidecar.service /etc/systemd/system/bolt_sidecar.service" -ssh shared@remotebeast "sudo systemctl daemon-reload && sudo systemctl enable bolt_sidecar" -ssh shared@remotebeast "sudo systemctl restart bolt_sidecar" +ssh shared@remotebeast "cd ~/$1/bolt_sidecar && CC=clang ~/.cargo/bin/cargo build --release" +ssh shared@remotebeast "mv ~/$1/bolt_sidecar/target/release/bolt-sidecar /usr/local/bin/bolt-sidecar-$1 || true" +ssh shared@remotebeast "cp -f ~/$1/bolt_sidecar/bolt_sidecar_$1.service /etc/systemd/system/bolt_sidecar_$1.service" +ssh shared@remotebeast "sudo systemctl daemon-reload && sudo systemctl enable bolt_sidecar_$1" +ssh shared@remotebeast "sudo systemctl restart bolt_sidecar_$1" -echo "Deployed bolt_sidecar successfully" +echo "Deployed bolt_sidecar_$1 successfully"