Skip to content

Commit

Permalink
feat(sidecar): load delegations on startup and send them upon registr…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
merklefruit committed Oct 14, 2024
1 parent 4634ff9 commit 3730d28
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 63 deletions.
1 change: 1 addition & 0 deletions bolt-sidecar/Cargo.lock

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

2 changes: 1 addition & 1 deletion bolt-sidecar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ thiserror = "1.0"
rand = "0.8.5"
dotenvy = "0.15.7"
regex = "1.10.5"
# backtrace = "0.3.74"
toml = "0.5"

# tracing
tracing = "0.1.40"
Expand Down
13 changes: 8 additions & 5 deletions bolt-sidecar/bin/sidecar.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use bolt_sidecar::{telemetry::init_telemetry_stack, Opts, SidecarDriver};
use clap::Parser;
use eyre::{bail, Result};
use tracing::info;

use bolt_sidecar::{telemetry::init_telemetry_stack, Opts, SidecarDriver};

#[tokio::main]
async fn main() -> Result<()> {
let opts = Opts::parse();
let opts = if let Ok(config_path) = std::env::var("BOLT_SIDECAR_CONFIG_PATH") {
Opts::parse_from_toml(config_path.as_str())?
} else {
Opts::parse()
};

let metrics_port =
if !opts.telemetry.disable_metrics { Some(opts.telemetry.metrics_port) } else { None };
if let Err(err) = init_telemetry_stack(metrics_port) {
if let Err(err) = init_telemetry_stack(opts.telemetry.metrics_port()) {
bail!("Failed to initialize telemetry stack: {:?}", err)
}

Expand Down
4 changes: 2 additions & 2 deletions bolt-sidecar/src/api/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ pub trait ConstraintsApi: BuilderApi {
) -> Result<VersionedValue<SignedBuilderBid>, BuilderApiError>;

/// Implements: <https://chainbound.github.io/bolt-docs/api/builder#delegate>
async fn delegate(&self, signed_data: SignedDelegation) -> Result<(), BuilderApiError>;
async fn delegate(&self, signed_data: &[SignedDelegation]) -> Result<(), BuilderApiError>;

/// Implements: <https://chainbound.github.io/bolt-docs/api/builder#revoke>
async fn revoke(&self, signed_data: SignedRevocation) -> Result<(), BuilderApiError>;
async fn revoke(&self, signed_data: &[SignedRevocation]) -> Result<(), BuilderApiError>;
}
22 changes: 18 additions & 4 deletions bolt-sidecar/src/client/constraints_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::{
pub struct ConstraintsClient {
url: Url,
client: reqwest::Client,
delegations: Vec<SignedDelegation>,
}

impl ConstraintsClient {
Expand All @@ -38,9 +39,15 @@ impl ConstraintsClient {
Self {
url: url.into(),
client: reqwest::ClientBuilder::new().user_agent("bolt-sidecar").build().unwrap(),
delegations: Vec::new(),
}
}

/// Adds a list of delegations to the client.
pub fn add_delegations(&mut self, delegations: Vec<SignedDelegation>) {
self.delegations.extend(delegations);
}

fn endpoint(&self, path: &str) -> Url {
self.url.join(path).unwrap_or_else(|e| {
error!(err = ?e, "Failed to join path: {} with url: {}", path, self.url);
Expand Down Expand Up @@ -80,6 +87,13 @@ impl BuilderApi for ConstraintsClient {
return Err(BuilderApiError::FailedRegisteringValidators(error));
}

// If there are any delegations, propagate them to the relay
if self.delegations.is_empty() {
return Ok(());
} else if let Err(err) = self.delegate(&self.delegations).await {
error!(?err, "Failed to propagate delegations during validator registration");
}

Ok(())
}

Expand Down Expand Up @@ -190,12 +204,12 @@ impl ConstraintsApi for ConstraintsClient {
Ok(header)
}

async fn delegate(&self, signed_data: SignedDelegation) -> Result<(), BuilderApiError> {
async fn delegate(&self, signed_data: &[SignedDelegation]) -> Result<(), BuilderApiError> {
let response = self
.client
.post(self.endpoint(DELEGATE_PATH))
.header("content-type", "application/json")
.body(serde_json::to_string(&signed_data)?)
.body(serde_json::to_string(signed_data)?)
.send()
.await?;

Expand All @@ -207,12 +221,12 @@ impl ConstraintsApi for ConstraintsClient {
Ok(())
}

async fn revoke(&self, signed_data: SignedRevocation) -> Result<(), BuilderApiError> {
async fn revoke(&self, signed_data: &[SignedRevocation]) -> Result<(), BuilderApiError> {
let response = self
.client
.post(self.endpoint(REVOKE_PATH))
.header("content-type", "application/json")
.body(serde_json::to_string(&signed_data)?)
.body(serde_json::to_string(signed_data)?)
.send()
.await?;

Expand Down
4 changes: 2 additions & 2 deletions bolt-sidecar/src/client/test_util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ impl ConstraintsApi for MockConstraintsClient {
Ok(bid)
}

async fn delegate(&self, signed_data: SignedDelegation) -> Result<(), BuilderApiError> {
async fn delegate(&self, signed_data: &[SignedDelegation]) -> Result<(), BuilderApiError> {
Err(BuilderApiError::Generic(
"MockConstraintsClient does not support delegating".to_string(),
))
}

async fn revoke(&self, signed_data: SignedRevocation) -> Result<(), BuilderApiError> {
async fn revoke(&self, signed_data: &[SignedRevocation]) -> Result<(), BuilderApiError> {
Err(BuilderApiError::Generic("MockConstraintsClient does not support revoking".to_string()))
}
}
Expand Down
21 changes: 21 additions & 0 deletions bolt-sidecar/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use alloy::primitives::U256;
use blst::min_pk::SecretKey;
use rand::{Rng, RngCore};
use reth_primitives::PooledTransactionsElement;
use serde::{Deserialize, Deserializer};

use crate::{
primitives::{AccountState, TransactionExt},
Expand Down Expand Up @@ -107,6 +108,16 @@ impl BlsSecretKeyWrapper {
}
}

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

impl From<&str> for BlsSecretKeyWrapper {
fn from(sk: &str) -> Self {
let hex_sk = sk.strip_prefix("0x").unwrap_or(sk);
Expand Down Expand Up @@ -158,6 +169,16 @@ impl From<&str> for JwtSecretConfig {
}
}

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

impl Deref for JwtSecretConfig {
type Target = str;
fn deref(&self) -> &Self::Target {
Expand Down
6 changes: 3 additions & 3 deletions bolt-sidecar/src/config/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::time::Duration;

use clap::{Args, ValueEnum};
use ethereum_consensus::deneb::{compute_fork_data_root, Root};
use serde::Deserialize;

/// Default commitment deadline duration.
///
Expand All @@ -21,7 +22,7 @@ pub const COMMIT_BOOST_DOMAIN_MASK: [u8; 4] = [109, 109, 111, 67];

/// Configuration for the chain the sidecar is running on.
/// This allows to customize the slot time for custom Kurtosis devnets.
#[derive(Debug, Clone, Copy, Args)]
#[derive(Debug, Clone, Copy, Args, Deserialize)]
pub struct ChainConfig {
/// Chain on which the sidecar is running
#[clap(long, env = "BOLT_SIDECAR_CHAIN", default_value = "mainnet")]
Expand Down Expand Up @@ -55,9 +56,8 @@ impl Default for ChainConfig {
}

/// Supported chains for the sidecar
#[derive(Debug, Clone, Copy, ValueEnum)]
#[derive(Debug, Clone, Copy, ValueEnum, Deserialize)]
#[clap(rename_all = "kebab_case")]
#[allow(missing_docs)]
pub enum Chain {
Mainnet,
Holesky,
Expand Down
27 changes: 19 additions & 8 deletions bolt-sidecar/src/config/limits.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
use clap::Parser;
use std::num::NonZero;

use clap::Parser;
use serde::Deserialize;

// Default limit values
pub const DEFAULT_MAX_COMMITMENTS: usize = 128;
pub const DEFAULT_MAX_COMMITTED_GAS: u64 = 10_000_000;
pub const DEFAULT_MIN_PRIORITY_FEE: u128 = 1_000_000_000; // 1 Gwei

/// Limits for the sidecar.
#[derive(Debug, Parser, Clone, Copy)]
#[derive(Debug, Parser, Clone, Copy, Deserialize)]
pub struct LimitsOpts {
/// Max number of commitments to accept per block
#[clap(long, env = "BOLT_SIDECAR_MAX_COMMITMENTS",
default_value_t = LimitsOpts::default().max_commitments_per_slot)]
#[clap(
long,
env = "BOLT_SIDECAR_MAX_COMMITMENTS",
default_value_t = LimitsOpts::default().max_commitments_per_slot
)]
pub max_commitments_per_slot: NonZero<usize>,
/// Max committed gas per slot
#[clap(long, env = "BOLT_SIDECAR_MAX_COMMITTED_GAS",
default_value_t = LimitsOpts::default().max_committed_gas_per_slot)]
#[clap(
long,
env = "BOLT_SIDECAR_MAX_COMMITTED_GAS",
default_value_t = LimitsOpts::default().max_committed_gas_per_slot
)]
pub max_committed_gas_per_slot: NonZero<u64>,
/// Min priority fee to accept for a commitment
#[clap(long, env = "BOLT_SIDECAR_MIN_PRIORITY_FEE",
default_value_t = LimitsOpts::default().min_priority_fee)]
#[clap(
long,
env = "BOLT_SIDECAR_MIN_PRIORITY_FEE",
default_value_t = LimitsOpts::default().min_priority_fee
)]
pub min_priority_fee: NonZero<u128>,
}

Expand Down
27 changes: 20 additions & 7 deletions bolt-sidecar/src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::{fs::File, io::Read};

use alloy::primitives::Address;
use clap::Parser;
use eyre::Context;
use reqwest::Url;
use serde::Deserialize;

pub mod validator_indexes;
pub use validator_indexes::ValidatorIndexes;
Expand All @@ -26,7 +30,7 @@ pub const DEFAULT_RPC_PORT: u16 = 8000;
pub const DEFAULT_CONSTRAINTS_PROXY_PORT: u16 = 18551;

/// Command-line options for the Bolt sidecar
#[derive(Debug, Parser)]
#[derive(Debug, Parser, Deserialize)]
#[clap(trailing_var_arg = true)]
pub struct Opts {
/// Port to listen on for incoming JSON-RPC requests
Expand All @@ -46,8 +50,7 @@ pub struct Opts {
#[clap(long, env = "BOLT_SIDECAR_ENGINE_API_URL", default_value = "http://localhost:8551")]
pub engine_api_url: Url,
/// Constraint proxy server port to use
#[clap(long, env = "BOLT_SIDECAR_CONSTRAINTS_PROXY_PORT",
default_value_t = DEFAULT_CONSTRAINTS_PROXY_PORT)]
#[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 @@ -66,10 +69,8 @@ pub struct Opts {
#[clap(long, env = "BOLT_SIDECAR_FEE_RECIPIENT",
default_value_t = Address::ZERO)]
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())]
/// 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())]
pub builder_private_key: BlsSecretKeyWrapper,
/// Operating limits for the sidecar
#[clap(flatten)]
Expand All @@ -90,6 +91,18 @@ pub struct Opts {
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")?;

toml::from_str(&contents).wrap_err("Error parsing the TOML file")
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
28 changes: 9 additions & 19 deletions bolt-sidecar/src/config/signing.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use crate::common::{BlsSecretKeyWrapper, JwtSecretConfig};
use std::{
fmt::{self},
net::SocketAddr,
};
use std::{fmt, net::SocketAddr};

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

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

/// Command-line options for signing
#[derive(Args)]
#[derive(Args, Deserialize)]
#[clap(
group = ArgGroup::new("signing-opts").required(true)
.args(&["private_key", "commit_boost_address", "commit_boost_jwt_hex", "keystore_password"])
.args(&["private_key", "commit_boost_address", "keystore_password"])
)]
pub struct SigningOpts {
/// Private key to use for signing preconfirmation requests
Expand All @@ -30,6 +29,9 @@ pub struct SigningOpts {
/// 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<String>,
/// Path to the delegations file. If not provided, the default path is used.
#[clap(long, env = "BOLT_SIDECAR_DELEGATIONS_PATH")]
pub delegations_path: Option<String>,
}

// Implement Debug manually to hide the keystore_password field
Expand All @@ -43,15 +45,3 @@ impl fmt::Debug for SigningOpts {
.finish()
}
}

impl Default for SigningOpts {
fn default() -> Self {
Self {
private_key: Some(BlsSecretKeyWrapper::random()),
commit_boost_address: None,
commit_boost_jwt_hex: None,
keystore_password: None,
keystore_path: None,
}
}
}
18 changes: 15 additions & 3 deletions bolt-sidecar/src/config/telemetry.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
use clap::Parser;
use serde::Deserialize;

#[derive(Parser, Debug, Clone)]
#[derive(Parser, Debug, Clone, Deserialize)]
pub struct TelemetryOpts {
/// The port on which to expose Prometheus metrics
#[clap(short, long, env = "METRICS_PORT", default_value_t = 3300)]
pub metrics_port: u16,
metrics_port: u16,
#[clap(short, long, env = "DISABLE_METRICS", default_value_t = false)]
pub disable_metrics: bool,
disable_metrics: bool,
}

impl TelemetryOpts {
/// Get the metrics port if metrics are enabled or None if they are disabled.
pub fn metrics_port(&self) -> Option<u16> {
if self.disable_metrics {
None
} else {
Some(self.metrics_port)
}
}
}
Loading

0 comments on commit 3730d28

Please sign in to comment.