Skip to content
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
689 changes: 390 additions & 299 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 15 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ alloy = { version = "^1.0.35", features = [
"rpc-types-beacon",
"serde",
"signer-local",
"ssz",
] }
alloy-primitives = "^1.3.1"
arbitrary = "1.4"
assert_cmd = "2.1.2"
async-trait = "0.1.80"
axum = { version = "0.8.1", features = ["macros"] }
Expand Down Expand Up @@ -54,10 +54,10 @@ indexmap = "2.2.6"
jsonwebtoken = { version = "9.3.1", default-features = false }
lazy_static = "1.5.0"
mediatype = "0.20.0"
lh_eth2 = { package = "eth2", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3", features = ["events"] }
lh_eth2_keystore = { package = "eth2_keystore", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3" }
lh_bls = { package = "bls", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3" }
lh_types = { package = "types", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3" }
lh_eth2 = { package = "eth2", git = "https://github.com/sigp/lighthouse", rev = "31d8cfd40d228c2dd902a67b5142ff6abf00c058", features = ["events"] }
lh_eth2_keystore = { package = "eth2_keystore", git = "https://github.com/sigp/lighthouse", rev = "31d8cfd40d228c2dd902a67b5142ff6abf00c058" }
lh_bls = { package = "bls", git = "https://github.com/sigp/lighthouse", rev = "31d8cfd40d228c2dd902a67b5142ff6abf00c058", features = ["arbitrary"] }
lh_types = { package = "types", git = "https://github.com/sigp/lighthouse", rev = "31d8cfd40d228c2dd902a67b5142ff6abf00c058", features = ["arbitrary"] }
notify = "8.2.0"
parking_lot = "0.12.3"
pbkdf2 = "0.12.2"
Expand All @@ -74,7 +74,7 @@ serde = { version = "1.0.202", features = ["derive"] }
serde_json = "1.0.117"
serde_yaml = "0.9.33"
sha2 = "0.10.8"
ssz_types = "0.11"
ssz_types = "0.14.1"
subtle = "2.5"
tempfile = "3.20.0"
thiserror = "2.0.12"
Expand All @@ -97,4 +97,12 @@ uuid = { version = "1.8.0", features = ["fast-rng", "serde", "v4"] }
webpki-roots = "1.0"

[patch.crates-io]
blstrs_plus = { git = "https://github.com/Commit-Boost/blstrs" }
blstrs_plus = { git = "https://github.com/Commit-Boost/blstrs", rev = "c4ea6b21193886ee9849867397a62e9c243c1fbb" }
# Progressive-container ssz stack required by lighthouse-unstable (EIP-7495/7916).
# A consumer does NOT inherit a dependency's [patch]; mirror lighthouse's here.
ssz_types = { git = "https://github.com/sigp/ssz_types", rev = "9203d56ad2d7bc5f12133d043e085843f81edcd6" }
milhouse = { git = "https://github.com/sigp/milhouse", rev = "c70f128976ac0d60ea65a978dabd117a921c36ee" }
ethereum_ssz = { git = "https://github.com/sigp/ethereum_ssz", rev = "2059c21ba52cd3a7e39a8ad537012b761812a393" }
ethereum_ssz_derive = { git = "https://github.com/sigp/ethereum_ssz", rev = "2059c21ba52cd3a7e39a8ad537012b761812a393" }
tree_hash = { git = "https://github.com/sigp/tree_hash", rev = "03d9fa474586c125f306dd7a00cf46284575a01f" }
tree_hash_derive = { git = "https://github.com/sigp/tree_hash", rev = "03d9fa474586c125f306dd7a00cf46284575a01f" }
5 changes: 4 additions & 1 deletion config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ wait_all_registrations = true
# this should be lower than that, leaving some margin for overhead
# OPTIONAL, DEFAULT: 950
timeout_get_header_ms = 950
# (unreleased, from v0.12.0-rc1) ePBS bids only: ms kept back from the beacon node's X-Timeout-Ms deadline, must be under one slot, 12000 on mainnet (https://commit-boost.github.io/commit-boost-client/get_started/epbs#timing)
# OPTIONAL, DEFAULT: 50
# proposer_deadline_buffer_ms = 50
# Timeout in milliseconds for the `submit_blinded_block` call to relays.
# OPTIONAL, DEFAULT: 4000
timeout_get_payload_ms = 4000
Expand Down Expand Up @@ -139,7 +142,7 @@ frequency_get_header_ms = 300
# Configuration for the PBS multiplexers, which enable different configs to be used for get header requests, depending on validator pubkey
# Note that:
# - multiple sets of keys can be defined by adding multiple [[mux]] sections. The validator pubkey sets need to be disjoint
# - the mux is only used for get header requests
# - the mux is only used for get header requests, and (unreleased, from v0.12.0-rc1) ePBS bid and builder preferences requests
# - if any value is missing from the mux config, the default value from the main config will be used
[[mux]]
# Unique ID for the mux config
Expand Down
2 changes: 2 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ testing-flags = []
[dependencies]
aes.workspace = true
alloy.workspace = true
arbitrary.workspace = true
async-trait.workspace = true
axum.workspace = true
base64.workspace = true
Expand Down Expand Up @@ -53,6 +54,7 @@ tracing-appender.workspace = true
tracing-subscriber.workspace = true
tree_hash.workspace = true
tree_hash_derive.workspace = true
typenum.workspace = true
unicode-normalization.workspace = true
url.workspace = true
uuid.workspace = true
Expand Down
18 changes: 16 additions & 2 deletions crates/common/src/config/pbs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::{
},
pbs::{
DEFAULT_PBS_PORT, DEFAULT_REGISTRY_REFRESH_SECONDS, DefaultTimeout, LATE_IN_SLOT_TIME_MS,
REGISTER_VALIDATOR_RETRY_LIMIT, RelayClient, RelayEntry,
PROPOSER_DEADLINE_BUFFER_MS, REGISTER_VALIDATOR_RETRY_LIMIT, RelayClient, RelayEntry,
},
types::{BlsPublicKey, Chain, Jwt, ModuleId},
utils::{
Expand Down Expand Up @@ -202,9 +202,14 @@ pub struct PbsConfig {
/// Minimum bid that will be accepted from get_header
#[serde(rename = "min_bid_eth", with = "as_eth_str", default = "default_u256")]
pub min_bid_wei: U256,
/// How late in the slot we consider to be "late"
/// How late in the slot we consider to be "late" (legacy get_header path)
#[serde(default = "default_u64::<LATE_IN_SLOT_TIME_MS>")]
pub late_in_slot_time_ms: u64,
/// ePBS bid path only: ms reserved before the proposer's declared deadline
/// (Date-Milliseconds + X-Timeout-Ms) for the winning bid's return trip. CB
/// asks the builder for `deadline - this`.
#[serde(default = "default_u64::<PROPOSER_DEADLINE_BUFFER_MS>")]
pub proposer_deadline_buffer_ms: u64,
/// Enable extra validation of get_header responses
#[serde(default = "default_bool::<false>")]
pub extra_validation_enabled: bool,
Expand Down Expand Up @@ -245,6 +250,15 @@ impl PbsConfig {
);
ensure!(self.late_in_slot_time_ms > 0, "late_in_slot_time_ms must be greater than 0");

// The buffer comes out of the proposer's deadline, which is clamped to
// one slot, so a buffer of a slot or more leaves no time for the bid
// request. 0 is allowed (no reserve).
let slot_time_ms = chain.slot_time_sec().saturating_mul(1000);
ensure!(
self.proposer_deadline_buffer_ms < slot_time_ms,
"proposer_deadline_buffer_ms must be less than one slot ({slot_time_ms} ms)"
);

ensure!(
self.timeout_get_header_ms < self.late_in_slot_time_ms,
"timeout_get_header_ms must be less than late_in_slot_time_ms"
Expand Down
1 change: 1 addition & 0 deletions crates/common/src/config/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ mod tests {
skip_sigverify: false,
min_bid_wei: Uint::<256, 4>::from(0),
late_in_slot_time_ms: 0,
proposer_deadline_buffer_ms: 0,
extra_validation_enabled: false,
rpc_url: None,
http_timeout_seconds: 30,
Expand Down
7 changes: 4 additions & 3 deletions crates/common/src/config/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ pub(crate) mod test_env {
#[cfg(test)]
mod tests {
use super::{test_env::with_env, *};
use crate::utils::TestRandomSeed;

// Minimal TOML-deserializable type used by load_from_file / load_file_from_env
// tests.
Expand Down Expand Up @@ -140,8 +139,10 @@ mod tests {

#[test]
fn test_remove_duplicate_keys() {
let key1 = BlsPublicKey::test_random();
let key2 = BlsPublicKey::test_random();
// Real, distinct keys: `arbitrary` for a validated point falls back to
// the (invalid) all-zeros pubkey, so derive from random secret keys.
let key1 = crate::types::BlsSecretKey::random().public_key();
let key2 = crate::types::BlsSecretKey::random().public_key();
let keys = vec![key1.clone(), key2.clone(), key1.clone()];

let unique_keys = remove_duplicate_keys(keys);
Expand Down
11 changes: 11 additions & 0 deletions crates/common/src/pbs/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ pub const REGISTER_VALIDATOR_PATH: &str = "/validators";
pub const SUBMIT_BLOCK_PATH: &str = "/blinded_blocks";
pub const RELOAD_PATH: &str = "/reload";

pub const GET_EXECUTION_PAYLOAD_BID_PATH: &str =
"/execution_payload_bid/{slot}/{parent_hash}/{parent_root}/{proposer_pubkey}";
pub const SUBMIT_BUILDER_PREFERENCES_PATH: &str = "/builder_preferences/{proposer_pubkey}";
pub const SUBMIT_SIGNED_BEACON_BLOCK_PATH: &str = "/beacon_blocks";

// https://ethereum.github.io/builder-specs/#/Builder

// Currently unused to enable a stateless default PBS module
Expand All @@ -36,6 +41,12 @@ impl DefaultTimeout {

pub const LATE_IN_SLOT_TIME_MS: u64 = 2000;

/// ePBS bid path: ms reserved before the proposer's own deadline
/// (Date-Milliseconds + X-Timeout-Ms) for the winning bid's return trip to the
/// beacon node and the beacon node's own selection/assembly. CB asks the
/// builder for `proposer_deadline - this`.
pub const PROPOSER_DEADLINE_BUFFER_MS: u64 = 50;

// Maximum number of retries for validator registration request per relay
pub const REGISTER_VALIDATOR_RETRY_LIMIT: u32 = 3;

Expand Down
2 changes: 1 addition & 1 deletion crates/common/src/pbs/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl PbsError {
}

/// Extract the HTTP status code from relay-originated errors.
fn relay_status_code(&self) -> Option<u16> {
pub fn relay_status_code(&self) -> Option<u16> {
match self {
PbsError::RelayResponse { code, .. } => Some(*code),
PbsError::ReadResponse(ResponseReadError::NonSuccess { status_code, .. }) => {
Expand Down
84 changes: 84 additions & 0 deletions crates/common/src/pbs/relay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use super::{
HEADER_VERSION_KEY, HEADER_VERSION_VALUE,
constants::{
GET_HEADER_STREAM_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, SUBMIT_BLOCK_PATH,
SUBMIT_SIGNED_BEACON_BLOCK_PATH,
},
error::PbsError,
};
Expand Down Expand Up @@ -219,6 +220,39 @@ impl RelayClient {
pub fn submit_block_url(&self, api_version: BuilderApiVersion) -> Result<Url, PbsError> {
self.builder_api_url(SUBMIT_BLOCK_PATH, api_version)
}

/// builder-API: POST /eth/v1/builder/execution_payload_bid/{slot}/
/// {parent_hash}/{parent_root}/{proposer_pubkey}
pub fn get_execution_payload_bid_url(
&self,
slot: u64,
parent_hash: &B256,
parent_root: &B256,
validator_pubkey: &BlsPublicKey,
) -> Result<Url, PbsError> {
self.builder_api_url(
&format!(
"/execution_payload_bid/{slot}/{parent_hash}/{parent_root}/{validator_pubkey}"
),
BuilderApiVersion::V1,
)
}

/// builder-API: POST /eth/v1/builder/builder_preferences/{proposer_pubkey}
pub fn submit_builder_preferences_url(
&self,
validator_pubkey: &BlsPublicKey,
) -> Result<Url, PbsError> {
self.builder_api_url(
&format!("/builder_preferences/{validator_pubkey}"),
BuilderApiVersion::V1,
)
}

/// builder-API: POST /eth/v1/builder/beacon_blocks
pub fn submit_signed_beacon_block_url(&self) -> Result<Url, PbsError> {
self.builder_api_url(SUBMIT_SIGNED_BEACON_BLOCK_PATH, BuilderApiVersion::V1)
}
}

/// First 4 bytes of the value's SHA-256: enough to see a rotation
Expand Down Expand Up @@ -468,4 +502,54 @@ mod tests {
let config = serde_json::from_str::<RelayConfig>(relay_config).unwrap();
assert_eq!(config.get_header, GetHeaderTransport::Http);
}

#[test]
fn test_relay_url_get_execution_payload() {
let slot = 0;
let parent_hash = B256::ZERO;
let parent_root = B256::ZERO;
let validator_pubkey = bls_pubkey_from_hex_unchecked(
"0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae",
);
let expected = format!(
"http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz/eth/v1/builder/execution_payload_bid/{slot}/{parent_hash}/{parent_root}/{validator_pubkey}"
);

let relay_config = r#"
{
"url": "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz"
}"#;

let config = serde_json::from_str::<RelayConfig>(relay_config).unwrap();
let relay = RelayClient::new(config).unwrap();

assert_eq!(
relay
.get_execution_payload_bid_url(slot, &parent_hash, &parent_root, &validator_pubkey)
.unwrap()
.to_string(),
expected
);

let relay_config = r#"
{
"url": "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz//"
}"#;

let config = serde_json::from_str::<RelayConfig>(relay_config).unwrap();
let relay = RelayClient::new(config).unwrap();

assert_eq!(
relay
.get_execution_payload_bid_url(slot, &parent_hash, &parent_root, &validator_pubkey)
.unwrap()
.to_string(),
expected
);

assert_eq!(
relay.submit_builder_preferences_url(&validator_pubkey).unwrap().to_string(),
"http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz/eth/v1/builder/builder_preferences/0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae"
);
}
}
Loading
Loading