Skip to content

Commit 2a292ef

Browse files
committed
Initial commit
0 parents  commit 2a292ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+6940
-0
lines changed

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
debug/
4+
target/
5+
6+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7+
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
8+
Cargo.lock
9+
10+
# These are backup files generated by rustfmt
11+
**/*.rs.bk
12+
13+
# MSVC Windows builds of rustc generate these, which store debugging information
14+
*.pdb

.vscode/settings.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"rust-analyzer.rustfmt.extraArgs": [
3+
"+nightly"
4+
]
5+
}

Cargo.toml

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
[workspace]
2+
members = [
3+
"bin/commit-boost",
4+
5+
"crates/cli",
6+
"crates/common",
7+
"crates/crypto",
8+
"crates/pbs",
9+
10+
"tests",
11+
"examples",
12+
]
13+
resolver = "2"
14+
15+
[workspace.package]
16+
version = "0.0.1"
17+
rust-version = "1.76"
18+
edition = "2021"
19+
20+
[workspace.dependencies]
21+
cb-cli = { path = "crates/cli" }
22+
cb-common = { path = "crates/common" }
23+
cb-crypto = { path = "crates/crypto" }
24+
cb-pbs = { path = "crates/pbs" }
25+
26+
# ethereum
27+
ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus", rev = "cf3c404043230559660810bc0c9d6d5a8498d819" }
28+
alloy-primitives = { version = "0.7.4", features = ["ssz"] }
29+
alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "64feb9bc51c8021ea08535694c44de84222f474e" }
30+
31+
ethereum_ssz = "0.5"
32+
ethereum_ssz_derive = "0.5.3"
33+
ssz_types = "0.5"
34+
ethereum_serde_utils = "0.5.2"
35+
ethereum-types = "0.14.1"
36+
37+
# networking
38+
axum = "0.7.5"
39+
axum-extra = { version = "0.9.3", features = ["typed-header"] }
40+
reqwest = "0.12.4"
41+
headers = "0.4.0"
42+
43+
# async
44+
tokio = { version = "1.37.0", features = ["full"] }
45+
futures = "0.3.30"
46+
async-trait = "0.1.80"
47+
48+
# serialization
49+
toml = "0.8.13"
50+
serde = { version = "1.0.202", features = ["derive"] }
51+
serde_json = "1.0.117"
52+
53+
# logging
54+
tracing = "0.1.40"
55+
tracing-subscriber = "0.3.18"
56+
57+
# sync
58+
dashmap = "5.5.3"
59+
60+
# crypto
61+
blst = "0.3.11"
62+
tree_hash = "0.5"
63+
tree_hash_derive = "0.5"
64+
65+
# misc
66+
clap = { version = "4.5.4", features = ["derive", "env"] }
67+
thiserror = "1.0.61"
68+
eyre = "0.6.12"
69+
url = "2.5.0"
70+
uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] }
71+
typenum = "1.17.0"
72+
rand = "0.8.5"

README.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Commit-Boost
2+
### Note
3+
- Commit-Boost is still in alpha development, all APIs are subject to change
4+
- The code is unaudited and NOT ready for production
5+
6+
## What is Commit-Boost?
7+
- Open source public good that is backwards compatible to help standardize the communication mechanism for the last mile of proposers’ commitments.
8+
- The goal is to develop and then sustain a standard software that will limit fragmentation, reduce complexity for core devs, and decrease risks for proposers making commitments–but, still allow for open innovation / not limiting designs developed by proposer commitment protocols
9+
- Specifically, Commit-Boost is a new Ethereum validator sidecar focused on standardizing the last mile of communication between validators and third parties. It has been designed with modularity at its core, with the goal of supporting a broad range of different use cases and protocols.
10+
- Read more [here](https://ethresear.ch/t/based-proposer-commitments-ethereum-s-marketplace-for-proposer-commitments/19517)
11+
12+
## Goals
13+
- **Open Source**: Developed in the open and under open-source licensing
14+
- **Optionality**: Ensure that the final design does not limit innovation or ossify certain downstream stakeholders / proposer commitments
15+
- **Safety**: Thoroughly tested and audited, with full backwards compatibility with previous clients
16+
- **Modularity**: Allow developers and protocol teams to easily test, iterate, and deploy new protocols and software for proposer commitments without needing to implement everything from scratch
17+
- **Observability**: Allow node operators to collect and quickly analyze detailed telemetry about sidecar services
18+
- **Transparency**: Provide open access and good documentations to allow maximal verifiability and ease of integration
19+
20+
## Developing
21+
With Commit-Boost you can:
22+
- Spin up a Commit Module, requesting arbitrary signatures from the proposer (`examples/da_commit.rs`)
23+
- Extend or replace the default BuilderApi implementation (`examples/custom_boost.rs`)
24+
- Subscribe to BuilderApi events and trigger arbitrary pipelines (`examples/alert_hook.rs`)
25+
26+
### High-level architecture
27+
By default, Commit-Boost will start a [MEV-boost](https://github.com/flashbots/mev-boost) compatible service. If any commit module is registered, then a signing manager is also started. The signing manager abstracts away the different
28+
29+
## Roadmap
30+
- [ ] Detailed telemetry and logging
31+
- [ ] Support for additional key managers (web3, ERC-2335, keystores, proxy)
32+
- [ ] Increased modularity of services, including in-process monitoring and extensive configurability
33+
34+
## Acknowledgements
35+
- MEV boost
36+
- Reth
37+
- Lighthouse

bin/commit-boost/Cargo.toml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "commit-boost"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
7+
[dependencies]
8+
cb-cli.workspace = true
9+
cb-common.workspace = true
10+
cb-pbs.workspace = true
11+
12+
13+
tokio.workspace = true
14+
async-trait.workspace = true
15+
16+
serde_json.workspace = true
17+
18+
tracing.workspace = true
19+
tracing-subscriber.workspace = true
20+
21+
clap.workspace = true

bin/commit-boost/src/main.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use cb_cli::runner::Runner;
2+
use cb_common::utils::initialize_tracing_log;
3+
use cb_pbs::{BuilderState, DefaultBuilderApi};
4+
use clap::Parser;
5+
6+
#[tokio::main]
7+
async fn main() {
8+
// set default backtrace unless provided
9+
if std::env::var_os("RUST_BACKTRACE").is_none() {
10+
std::env::set_var("RUST_BACKTRACE", "1");
11+
}
12+
13+
initialize_tracing_log();
14+
15+
let (chain, config) = cb_cli::Args::parse().to_config();
16+
17+
let state = BuilderState::new(chain, config);
18+
let runner = Runner::<(), DefaultBuilderApi>::new(state);
19+
20+
if let Err(err) = runner.run().await {
21+
eprintln!("Error: {err}");
22+
std::process::exit(1)
23+
};
24+
}

crates/cli/Cargo.toml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "cb-cli"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
7+
[dependencies]
8+
cb-common.workspace = true
9+
cb-pbs.workspace = true
10+
cb-crypto.workspace = true
11+
12+
alloy-primitives.workspace = true
13+
alloy-rpc-types-beacon.workspace = true
14+
15+
tokio.workspace = true
16+
17+
serde_json.workspace = true
18+
19+
tracing.workspace = true
20+
clap.workspace = true
21+
22+
eyre.workspace = true
23+
24+
tree_hash.workspace = true
25+
tree_hash_derive.workspace = true

crates/cli/src/lib.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::net::SocketAddr;
2+
3+
use cb_common::{config::BuilderConfig, pbs::RelayEntry, types::Chain, utils::eth_to_wei};
4+
use clap::{Parser, Subcommand};
5+
6+
pub mod runner;
7+
8+
#[derive(Parser, Debug)]
9+
#[command(version, about)]
10+
pub struct Args {
11+
#[command(subcommand)]
12+
pub cmd: Command,
13+
14+
/// Start with Holesky spec
15+
#[arg(long, global = true)]
16+
pub holesky: bool,
17+
}
18+
19+
#[derive(Debug, Subcommand)]
20+
pub enum Command {
21+
/// Pbs module configs, try to keep compatibility with MEV boost cli configs
22+
Boost {
23+
/// Address to start for boost server on
24+
#[arg(short, long, default_value = "127.0.0.1:18550", env = "BOOST_LISTEN_ADDR")]
25+
listen_address: SocketAddr,
26+
/// Add a single relay (can be repeated or comma separated). Format is scheme://pubkey@host
27+
#[arg(short, long, visible_alias = "relays", env = "RELAYS", num_args = 1.., required = true, value_delimiter = ',')]
28+
relay: Vec<String>,
29+
/// Check relay status on startup and getStatus calls
30+
#[arg(long, env = "RELAY_STARTUP_CHECK")]
31+
relay_check: bool,
32+
/// Timeout in ms for calling getHeader to relays
33+
#[arg(long, default_value_t = 950, env = "RELAY_TIMEOUT_MS_GETHEADER")]
34+
timeout_get_header_ms: u64,
35+
/// Timeout in ms for calling getPayload to relays
36+
#[arg(long, default_value_t = 4000, env = "RELAY_TIMEOUT_MS_GETPAYLOAD")]
37+
timeout_get_payload_ms: u64,
38+
/// Timeout in ms for calling registerValidator to relays
39+
#[arg(long, default_value_t = 3000, env = "RELAY_TIMEOUT_MS_REGVAL")]
40+
timeout_register_validator_ms: u64,
41+
/// Skip signature verification for relay headers
42+
#[arg(long)]
43+
skip_sigverify: bool,
44+
/// Minimum bid to accept from relays in ETH
45+
#[arg(long, default_value_t = 0.0, env = "MIN_BID_ETH")]
46+
min_bid_eth: f64,
47+
},
48+
}
49+
50+
impl Args {
51+
pub fn to_config(self) -> (Chain, BuilderConfig) {
52+
let chain = if self.holesky { Chain::Holesky } else { Chain::Mainnet };
53+
54+
match self.cmd {
55+
Command::Boost {
56+
listen_address: address,
57+
relay,
58+
relay_check,
59+
timeout_get_header_ms,
60+
timeout_get_payload_ms,
61+
timeout_register_validator_ms,
62+
skip_sigverify,
63+
min_bid_eth,
64+
} => {
65+
let config = BuilderConfig {
66+
address,
67+
relays: deser_relay_vec(relay),
68+
relay_check,
69+
timeout_get_header_ms,
70+
timeout_get_payload_ms,
71+
timeout_register_validator_ms,
72+
skip_sigverify,
73+
min_bid_wei: eth_to_wei(min_bid_eth),
74+
};
75+
println!("{}", serde_json::to_string_pretty(&config).unwrap());
76+
(chain, config)
77+
}
78+
}
79+
}
80+
}
81+
82+
fn deser_relay_vec(relays: Vec<String>) -> Vec<RelayEntry> {
83+
relays
84+
.into_iter()
85+
.map(|s| {
86+
serde_json::from_str::<RelayEntry>(&format!("\"{}\"", s.trim()))
87+
.expect("invalid relay format, should be scheme://pubkey@host")
88+
})
89+
.collect()
90+
}

crates/cli/src/runner.rs

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use std::{collections::HashSet, future::Future, marker::PhantomData};
2+
3+
use alloy_rpc_types_beacon::BlsPublicKey;
4+
use cb_crypto::{
5+
manager::{Signer, SigningManager},
6+
service::SigningService,
7+
types::SignRequest,
8+
};
9+
use cb_pbs::{
10+
BuilderApi, BuilderApiState, BuilderEvent, BuilderState, DefaultBuilderApi, PbsService,
11+
};
12+
use tokio::sync::{
13+
broadcast,
14+
mpsc::{self, unbounded_channel, UnboundedSender},
15+
};
16+
17+
pub type SignRequestSender = mpsc::UnboundedSender<SignRequest>;
18+
19+
pub struct Runner<S: BuilderApiState = (), T: BuilderApi<S> = DefaultBuilderApi> {
20+
state: BuilderState<S>,
21+
commit_ids: HashSet<String>,
22+
hooks_ids: HashSet<String>,
23+
sign_manager: SigningManager,
24+
25+
notif_tx: SignRequestSender,
26+
notif_rx: mpsc::UnboundedReceiver<SignRequest>,
27+
_marker: PhantomData<T>,
28+
}
29+
30+
impl<S: BuilderApiState, T: BuilderApi<S>> Runner<S, T> {
31+
pub fn new(state: BuilderState<S>) -> Self {
32+
let (notif_tx, notif_rx) = unbounded_channel();
33+
34+
// TODO: move this in run + spawn only if needed
35+
let mut sign_manager = SigningManager::new(state.chain);
36+
sign_manager.add_consensus_signer(Signer::new_random());
37+
38+
Self {
39+
state,
40+
commit_ids: HashSet::new(),
41+
hooks_ids: HashSet::new(),
42+
sign_manager,
43+
notif_tx,
44+
notif_rx,
45+
_marker: PhantomData,
46+
}
47+
}
48+
49+
pub fn add_commitment<F, R>(&mut self, commit_id: impl Into<String>, commitment: F)
50+
where
51+
F: FnOnce(UnboundedSender<SignRequest>, Vec<BlsPublicKey>) -> R + 'static,
52+
R: Future<Output = eyre::Result<()>> + Send + 'static,
53+
{
54+
let id = commit_id.into();
55+
56+
if !self.commit_ids.insert(id.clone()) {
57+
eprintln!("Commitments ids need to be unique, found duplicate: {id}");
58+
std::process::exit(1);
59+
}
60+
61+
// move to vector and spawn after signing service
62+
tokio::spawn(commitment(self.notif_tx.clone(), self.sign_manager.consensus_pubkeys()));
63+
}
64+
65+
pub fn add_boost_hook<F, R>(&mut self, hook_id: impl Into<String>, hook: F)
66+
where
67+
F: FnOnce(broadcast::Receiver<BuilderEvent>) -> R + 'static,
68+
R: Future<Output = eyre::Result<()>> + Send + 'static,
69+
{
70+
let id = hook_id.into();
71+
72+
if !self.hooks_ids.insert(id.clone()) {
73+
eprintln!("Hook ids need to be unique, found duplicate: {id}");
74+
std::process::exit(1);
75+
}
76+
77+
// move to vector and spawn after signing service
78+
tokio::spawn(hook(self.state.subscribe_events()));
79+
}
80+
81+
pub async fn run(self) -> eyre::Result<()> {
82+
// start signature service
83+
if !self.commit_ids.is_empty() {
84+
let sign_service = SigningService::new(self.sign_manager, self.notif_rx);
85+
tokio::spawn(sign_service.run());
86+
}
87+
88+
// TODO: start commitments and hooks here
89+
90+
// start boost service
91+
PbsService::run::<S, T>(self.state).await;
92+
93+
Ok(())
94+
}
95+
}

0 commit comments

Comments
 (0)