From 0186a10839740a5c21a55bd9e3e3b8ae16db67f7 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:54:38 +0200 Subject: [PATCH 1/4] chore: slot_time config rename --- bolt-sidecar/src/config/chain.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bolt-sidecar/src/config/chain.rs b/bolt-sidecar/src/config/chain.rs index a58cd559..eda5c9d6 100644 --- a/bolt-sidecar/src/config/chain.rs +++ b/bolt-sidecar/src/config/chain.rs @@ -43,7 +43,7 @@ pub struct ChainConfig { /// The slot time duration in seconds. If provided, /// it overrides the default for the selected [Chain]. #[clap(short = 's', long, default_value_t = DEFAULT_SLOT_TIME_IN_SECONDS)] - slot_time_in_seconds: u64, + slot_time: u64, } impl Default for ChainConfig { @@ -51,7 +51,7 @@ impl Default for ChainConfig { Self { chain: Chain::Mainnet, commitment_deadline: DEFAULT_COMMITMENT_DEADLINE_IN_MILLIS, - slot_time_in_seconds: DEFAULT_SLOT_TIME_IN_SECONDS, + slot_time: DEFAULT_SLOT_TIME_IN_SECONDS, } } } @@ -90,7 +90,7 @@ impl ChainConfig { /// Get the slot time for the given chain in seconds. pub fn slot_time(&self) -> u64 { - self.slot_time_in_seconds + self.slot_time } /// Get the domain for signing messages on the given chain. @@ -145,7 +145,7 @@ impl ChainConfig { pub fn kurtosis(slot_time_in_seconds: u64, commitment_deadline: u64) -> Self { Self { chain: Chain::Kurtosis, - slot_time_in_seconds, + slot_time: slot_time_in_seconds, commitment_deadline, } } From d5f3a31eb902c5cbe035599caf8f7c12c4bbaca6 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:40:54 +0200 Subject: [PATCH 2/4] chore: addressed some review comments --- bolt-sidecar/bin/sidecar.rs | 14 ++-- bolt-sidecar/src/api/builder.rs | 7 +- .../src/builder/call_trace_manager.rs | 3 +- bolt-sidecar/src/builder/mod.rs | 4 +- bolt-sidecar/src/builder/payload_builder.rs | 74 ++++++++++--------- bolt-sidecar/src/builder/state_root.rs | 6 +- bolt-sidecar/src/client/mevboost.rs | 7 +- bolt-sidecar/src/client/mod.rs | 3 + bolt-sidecar/src/client/rpc.rs | 15 ++-- bolt-sidecar/src/config/mod.rs | 62 +++++++++------- bolt-sidecar/src/lib.rs | 2 +- bolt-sidecar/src/state/consensus.rs | 12 +-- bolt-sidecar/src/state/fetcher.rs | 8 +- bolt-sidecar/src/state/head_tracker.rs | 17 +++-- bolt-sidecar/src/state/mod.rs | 11 +-- bolt-sidecar/src/test_util.rs | 11 ++- builder/Dockerfile | 2 +- builder/Dockerfile.alltools | 2 +- 18 files changed, 145 insertions(+), 115 deletions(-) diff --git a/bolt-sidecar/bin/sidecar.rs b/bolt-sidecar/bin/sidecar.rs index e5523b4d..28ac755e 100644 --- a/bolt-sidecar/bin/sidecar.rs +++ b/bolt-sidecar/bin/sidecar.rs @@ -2,7 +2,6 @@ use std::time::Duration; use alloy_rpc_types_beacon::events::HeadEvent; use tokio::sync::mpsc; -use tracing::info; use bolt_sidecar::{ crypto::{bls::Signer, SignableBLS, SignerBLS}, @@ -13,7 +12,7 @@ use bolt_sidecar::{ }, start_builder_proxy_server, start_rpc_server, state::{ConsensusState, ExecutionState, HeadTracker, StateClient}, - BuilderProxyConfig, Config, ConstraintsApi, LocalBuilder, MevBoostClient, + BeaconClient, BuilderProxyConfig, Config, ConstraintsApi, LocalBuilder, MevBoostClient, }; #[tokio::main] @@ -22,21 +21,22 @@ async fn main() -> eyre::Result<()> { let config = Config::parse_from_cli()?; - info!(chain = config.chain.name(), "Starting Bolt sidecar"); + tracing::info!(chain = config.chain.name(), "Starting Bolt sidecar"); // TODO: support external signers // probably it's cleanest to have the Config parser initialize a generic Signer let signer = Signer::new(config.private_key.clone().unwrap()); - let state_client = StateClient::new(&config.execution_api_url); + let state_client = StateClient::new(config.execution_api_url.clone()); let mut execution_state = ExecutionState::new(state_client).await?; - let mevboost_client = MevBoostClient::new(&config.mevboost_url); + let mevboost_client = MevBoostClient::new(config.mevboost_url.clone()); + let beacon_client = BeaconClient::new(config.beacon_api_url.clone()); let (api_events, mut api_events_rx) = mpsc::channel(1024); let shutdown_tx = start_rpc_server(&config, api_events).await?; let mut consensus_state = ConsensusState::new( - &config.beacon_api_url, + beacon_client.clone(), &config.validator_indexes, config.chain.commitment_deadline(), ); @@ -44,7 +44,7 @@ async fn main() -> eyre::Result<()> { // TODO: this can be replaced with ethereum_consensus::clock::from_system_time() // but using beacon node events is easier to work on a custom devnet for now // (as we don't need to specify genesis time and slot duration) - let mut head_tracker = HeadTracker::start(&config.beacon_api_url); + let mut head_tracker = HeadTracker::start(beacon_client); let builder_proxy_config = BuilderProxyConfig { mevboost_url: config.mevboost_url.clone(), diff --git a/bolt-sidecar/src/api/builder.rs b/bolt-sidecar/src/api/builder.rs index 0a0838eb..7a6b93b9 100644 --- a/bolt-sidecar/src/api/builder.rs +++ b/bolt-sidecar/src/api/builder.rs @@ -14,6 +14,7 @@ use ethereum_consensus::{ Fork, }; use parking_lot::Mutex; +use reqwest::Url; use serde::Deserialize; use std::{sync::Arc, time::Duration}; use tokio::net::TcpListener; @@ -240,7 +241,7 @@ where #[derive(Debug, Clone)] pub struct BuilderProxyConfig { /// The URL of the target mev-boost server. - pub mevboost_url: String, + pub mevboost_url: Url, /// The port on which the builder proxy should listen. pub server_port: u16, } @@ -255,11 +256,11 @@ where { tracing::info!( port = config.server_port, - target = config.mevboost_url, + target = config.mevboost_url.to_string(), "Starting builder proxy..." ); - let mev_boost = MevBoostClient::new(&config.mevboost_url); + let mev_boost = MevBoostClient::new(config.mevboost_url); let server = Arc::new(BuilderProxyServer::new(mev_boost, payload_fetcher)); let router = Router::new() diff --git a/bolt-sidecar/src/builder/call_trace_manager.rs b/bolt-sidecar/src/builder/call_trace_manager.rs index 467cb4ce..5a8ba312 100644 --- a/bolt-sidecar/src/builder/call_trace_manager.rs +++ b/bolt-sidecar/src/builder/call_trace_manager.rs @@ -19,6 +19,7 @@ use alloy_rpc_types_trace::geth::{ }; use alloy_transport::TransportResult; use futures::{stream::FuturesOrdered, Future, StreamExt}; +use reqwest::Url; use tokio::{ sync::{mpsc, oneshot}, task::JoinHandle, @@ -131,7 +132,7 @@ impl Future for CallTraceManager { impl CallTraceManager { /// Creates a new [CallTraceManager] instance, which will listen for incoming /// trace requests and process them in the background using the given RPC client. - pub fn new(url: &str) -> (Self, CallTraceHandle) { + pub fn new>(url: U) -> (Self, CallTraceHandle) { let rpc = RpcClient::new(url); let (cmd_tx, cmd_rx) = mpsc::channel(512); diff --git a/bolt-sidecar/src/builder/mod.rs b/bolt-sidecar/src/builder/mod.rs index dfd46292..16d20cf8 100644 --- a/bolt-sidecar/src/builder/mod.rs +++ b/bolt-sidecar/src/builder/mod.rs @@ -58,6 +58,8 @@ pub enum BuilderError { Transport(#[from] alloy_transport::TransportError), #[error("Failed in SSZ merkleization: {0}")] Merkleization(#[from] MerkleizationError), + #[error("Failed while interacting with beacon client: {0}")] + BeaconApi(#[from] beacon_api_client::Error), #[error("Failed to parse hint from engine response: {0}")] InvalidEngineHint(String), #[error("Failed to build payload: {0}")] @@ -66,7 +68,7 @@ pub enum BuilderError { /// Local builder instance that can ingest a sealed header and /// create the corresponding builder bid ready for the Builder API. -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct LocalBuilder { /// BLS credentials for the local builder. We use this to sign the /// payload bid submissions built by the sidecar. diff --git a/bolt-sidecar/src/builder/payload_builder.rs b/bolt-sidecar/src/builder/payload_builder.rs index ea6ad460..827dba05 100644 --- a/bolt-sidecar/src/builder/payload_builder.rs +++ b/bolt-sidecar/src/builder/payload_builder.rs @@ -5,18 +5,18 @@ use alloy_rpc_types_engine::ExecutionPayload as AlloyExecutionPayload; use beacon_api_client::{BlockId, StateId}; use hex::FromHex; use regex::Regex; +use reqwest::Url; use reth_primitives::{ constants::BEACON_NONCE, proofs, BlockBody, Bloom, Header, SealedBlock, TransactionSigned, Withdrawals, EMPTY_OMMER_ROOT_HASH, }; use reth_rpc_layer::{secret_to_bearer_header, JwtSecret}; -use serde_json::Value; use super::{ compat::{to_alloy_execution_payload, to_reth_withdrawal}, BuilderError, }; -use crate::{Config, RpcClient}; +use crate::{BeaconClient, Config, RpcClient}; /// Extra-data payload field used for locally built blocks, decoded in UTF-8. /// @@ -37,11 +37,11 @@ const DEFAULT_EXTRA_DATA: [u8; 20] = [ /// /// Find more information about this process & its reasoning here: /// -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct FallbackPayloadBuilder { extra_data: Bytes, fee_recipient: Address, - beacon_api_url: String, + beacon_api_client: BeaconClient, execution_rpc_client: RpcClient, engine_hinter: EngineHinter, slot_time_in_seconds: u64, @@ -53,15 +53,15 @@ impl FallbackPayloadBuilder { let engine_hinter = EngineHinter { client: reqwest::Client::new(), jwt_hex: config.jwt_hex.to_string(), - engine_rpc_url: config.engine_api_url.to_string(), + engine_rpc_url: config.engine_api_url.clone(), }; Self { engine_hinter, extra_data: DEFAULT_EXTRA_DATA.into(), fee_recipient: config.fee_recipient, - beacon_api_url: config.beacon_api_url.to_string(), - execution_rpc_client: RpcClient::new(&config.execution_api_url), + beacon_api_client: BeaconClient::new(config.beacon_api_url.clone()), + execution_rpc_client: RpcClient::new(config.execution_api_url.clone()), slot_time_in_seconds: config.chain.slot_time(), } } @@ -106,47 +106,49 @@ impl FallbackPayloadBuilder { let latest_block = self.execution_rpc_client.get_block(None, true).await?; tracing::debug!(num = ?latest_block.header.number, "got latest block"); - // TODO: refactor this once ConsensusState (https://github.com/chainbound/bolt/issues/58) is ready - let beacon_api_endpoint = reqwest::Url::parse(&self.beacon_api_url).unwrap(); - let beacon_api = beacon_api_client::mainnet::Client::new(beacon_api_endpoint); - - let withdrawals = beacon_api + let withdrawals = self + .beacon_api_client // Slot: Defaults to the slot after the parent state if not specified. .get_expected_withdrawals(StateId::Head, None) - .await - .unwrap() + .await? .into_iter() .map(to_reth_withdrawal) .collect::>(); tracing::debug!(amount = ?withdrawals.len(), "got withdrawals"); + let prev_randao = self + .beacon_api_client + .get_randao(StateId::Head, None) + .await?; + let prev_randao = B256::from_slice(&prev_randao); + // NOTE: for some reason, this call fails with an ApiResult deserialization error // when using the beacon_api_client crate directly, so we use reqwest temporarily. // this is to be refactored. - let prev_randao = reqwest::Client::new() - .get(format!( - "{}/eth/v1/beacon/states/head/randao", - self.beacon_api_url - )) - .send() - .await - .unwrap() - .json::() - .await - .unwrap(); - let prev_randao = prev_randao - .pointer("/data/randao") - .unwrap() - .as_str() - .unwrap(); - let prev_randao = B256::from_hex(prev_randao).unwrap(); + // let prev_randao = reqwest::Client::new() + // .get(format!( + // "{}/eth/v1/beacon/states/head/randao", + // self.beacon_api_url + // )) + // .send() + // .await + // .unwrap() + // .json::() + // .await + // .unwrap(); + // let prev_randao = prev_randao + // .pointer("/data/randao") + // .unwrap() + // .as_str() + // .unwrap(); + // let prev_randao = B256::from_hex(prev_randao).unwrap(); tracing::debug!("got prev_randao"); - let parent_beacon_block_root = beacon_api + let parent_beacon_block_root = self + .beacon_api_client .get_beacon_block_root(BlockId::Head) - .await - .unwrap(); + .await?; tracing::debug!(parent = ?parent_beacon_block_root, "got parent_beacon_block_root"); let versioned_hashes = transactions @@ -265,7 +267,7 @@ pub(crate) enum EngineApiHint { pub(crate) struct EngineHinter { client: reqwest::Client, jwt_hex: String, - engine_rpc_url: String, + engine_rpc_url: Url, } impl EngineHinter { @@ -287,7 +289,7 @@ impl EngineHinter { let raw_hint = self .client - .post(&self.engine_rpc_url) + .post(self.engine_rpc_url.as_str()) .header("Content-Type", "application/json") .header("Authorization", auth_jwt.clone()) .body(body) diff --git a/bolt-sidecar/src/builder/state_root.rs b/bolt-sidecar/src/builder/state_root.rs index bc855835..73aa92f3 100644 --- a/bolt-sidecar/src/builder/state_root.rs +++ b/bolt-sidecar/src/builder/state_root.rs @@ -8,6 +8,7 @@ mod tests { use alloy_primitives::{keccak256, B256, U256}; use partial_mpt::StateTrie; + use reqwest::Url; use crate::{builder::CallTraceManager, client::rpc::RpcClient}; @@ -19,9 +20,10 @@ mod tests { tracing::info!("Starting test_trace_call"); let rpc_url = std::env::var("RPC_URL").expect("RPC_URL must be set"); - let client = RpcClient::new(&rpc_url); + let rpc_url = Url::parse(&rpc_url).unwrap(); + let client = RpcClient::new(rpc_url.clone()); - let (call_trace_manager, call_trace_handler) = CallTraceManager::new(&rpc_url); + let (call_trace_manager, call_trace_handler) = CallTraceManager::new(rpc_url); tokio::spawn(call_trace_manager); // https://etherscan.io/block/20125606 diff --git a/bolt-sidecar/src/client/mevboost.rs b/bolt-sidecar/src/client/mevboost.rs index 39697bcc..7262ca94 100644 --- a/bolt-sidecar/src/client/mevboost.rs +++ b/bolt-sidecar/src/client/mevboost.rs @@ -7,6 +7,7 @@ use beacon_api_client::VersionedValue; use ethereum_consensus::{ builder::SignedValidatorRegistration, deneb::mainnet::SignedBlindedBeaconBlock, Fork, }; +use reqwest::Url; use crate::{ api::{ @@ -22,15 +23,15 @@ use crate::{ /// A client for interacting with the MEV-Boost API. #[derive(Debug)] pub struct MevBoostClient { - url: String, + url: Url, client: reqwest::Client, } impl MevBoostClient { /// Creates a new MEV-Boost client with the given URL. - pub fn new(url: &str) -> Self { + pub fn new>(url: U) -> Self { Self { - url: url.trim_end_matches('/').to_string(), + url: url.into(), client: reqwest::ClientBuilder::new() .user_agent("bolt-sidecar") .build() diff --git a/bolt-sidecar/src/client/mod.rs b/bolt-sidecar/src/client/mod.rs index acadc88c..6dd49a4d 100644 --- a/bolt-sidecar/src/client/mod.rs +++ b/bolt-sidecar/src/client/mod.rs @@ -2,3 +2,6 @@ pub mod commit_boost; pub mod mevboost; pub mod pubsub; pub mod rpc; + +// Re-export the beacon_api_client +pub use beacon_api_client::mainnet::Client as BeaconClient; diff --git a/bolt-sidecar/src/client/rpc.rs b/bolt-sidecar/src/client/rpc.rs index cbcafaa5..ec796167 100644 --- a/bolt-sidecar/src/client/rpc.rs +++ b/bolt-sidecar/src/client/rpc.rs @@ -6,7 +6,6 @@ use futures::future::join_all; use std::{ collections::HashSet, ops::{Deref, DerefMut}, - str::FromStr, }; use alloy::ClientBuilder; @@ -28,10 +27,8 @@ pub struct RpcClient(alloy::RpcClient>); impl RpcClient { /// Create a new `RpcClient` with the given URL. - pub fn new(url: &str) -> Self { - let url = Url::from_str(url).unwrap(); - - let client = ClientBuilder::default().http(url); + pub fn new>(url: U) -> Self { + let client = ClientBuilder::default().http(url.into()); Self(client) } @@ -178,6 +175,8 @@ impl DerefMut for RpcClient { #[cfg(test)] mod tests { + use std::str::FromStr; + use alloy_consensus::constants::ETH_TO_WEI; use alloy_primitives::{uint, Uint}; use alloy_rpc_types::EIP1186AccountProofResponse; @@ -190,7 +189,8 @@ mod tests { #[tokio::test] async fn test_rpc_client() { let anvil = launch_anvil(); - let client = RpcClient::new(&anvil.endpoint()); + let anvil_url = Url::from_str(&anvil.endpoint()).unwrap(); + let client = RpcClient::new(anvil_url); let addr = anvil.addresses().first().unwrap(); @@ -207,7 +207,8 @@ mod tests { #[tokio::test] async fn test_get_proof() -> eyre::Result<()> { - let rpc_client = RpcClient::new("https://cloudflare-eth.com"); + let rpc_url = Url::parse("https://cloudflare-eth.com")?; + let rpc_client = RpcClient::new(rpc_url); let proof: EIP1186AccountProofResponse = rpc_client .0 diff --git a/bolt-sidecar/src/config/mod.rs b/bolt-sidecar/src/config/mod.rs index 88557e8b..10db04c3 100644 --- a/bolt-sidecar/src/config/mod.rs +++ b/bolt-sidecar/src/config/mod.rs @@ -1,6 +1,9 @@ +use std::{fs::read_to_string, path::Path}; + use alloy_primitives::Address; use blst::min_pk::SecretKey; use clap::Parser; +use reqwest::Url; use crate::crypto::bls::random_bls_secret; @@ -70,29 +73,31 @@ pub struct Opts { pub struct Config { /// Port to listen on for incoming JSON-RPC requests pub rpc_port: u16, + /// The MEV-Boost proxy server port to listen on + pub mevboost_proxy_port: u16, /// URL for the MEV-Boost sidecar client to use - pub mevboost_url: String, - /// URL for the commit-boost sidecar - pub commit_boost_url: Option, + pub mevboost_url: Url, /// URL for the beacon client API URL - pub beacon_api_url: String, - /// Private key to use for signing preconfirmation requests - pub private_key: Option, + pub beacon_api_url: Url, /// The execution API url - pub execution_api_url: String, + pub execution_api_url: Url, /// The engine API url - pub engine_api_url: String, - /// The MEV-Boost proxy server port to use - pub mevboost_proxy_port: u16, + pub engine_api_url: Url, + /// URL for the commit-boost sidecar + pub commit_boost_url: Option, + /// Private key to use for signing preconfirmation requests + pub private_key: Option, /// The jwt.hex secret to authenticate calls to the engine API pub jwt_hex: String, /// The fee recipient address for fallback blocks pub fee_recipient: Address, - /// Limits for the sidecar + /// Operating limits for the sidecar pub limits: Limits, - /// Validator indexes + /// Validator indexes of connected validators that the + /// sidecar should accept commitments on behalf of pub validator_indexes: Vec, - /// Local bulider private key + /// Local bulider private key for signing fallback payloads. + /// If not provided, a random key will be used. pub builder_private_key: SecretKey, /// The chain on which the sidecar is running pub chain: ChainConfig, @@ -102,13 +107,13 @@ impl Default for Config { fn default() -> Self { Self { rpc_port: DEFAULT_RPC_PORT, + mevboost_proxy_port: DEFAULT_MEV_BOOST_PROXY_PORT, commit_boost_url: None, - mevboost_url: "http://localhost:3030".to_string(), - beacon_api_url: "http://localhost:5052".to_string(), - execution_api_url: "http://localhost:8545".to_string(), - engine_api_url: "http://localhost:8551".to_string(), + mevboost_url: "http://localhost:3030".parse().expect("Valid URL"), + beacon_api_url: "http://localhost:5052".parse().expect("Valid URL"), + execution_api_url: "http://localhost:8545".parse().expect("Valid URL"), + engine_api_url: "http://localhost:8551".parse().expect("Valid URL"), private_key: Some(random_bls_secret()), - mevboost_proxy_port: DEFAULT_MEV_BOOST_PROXY_PORT, jwt_hex: String::new(), fee_recipient: Address::ZERO, builder_private_key: random_bls_secret(), @@ -159,7 +164,9 @@ impl TryFrom for Config { config.commit_boost_url = opts .signing .commit_boost_url - .map(|url| url.trim_end_matches('/').to_string()); + .as_ref() + .map(|url| Url::parse(url)) + .transpose()?; config.private_key = if let Some(sk) = opts.signing.private_key { let sk = SecretKey::from_bytes(&hex::decode(sk)?) @@ -177,8 +184,9 @@ impl TryFrom for Config { config.jwt_hex = if opts.jwt_hex.starts_with("0x") { opts.jwt_hex.trim_start_matches("0x").to_string() - } else if std::path::Path::new(&opts.jwt_hex).exists() { - std::fs::read_to_string(opts.jwt_hex)? + } else if Path::new(&opts.jwt_hex).exists() { + read_to_string(opts.jwt_hex) + .map_err(|e| eyre::eyre!("Failed reading JWT secret file: {:?}", e))? .trim_start_matches("0x") .to_string() } else { @@ -187,16 +195,16 @@ impl TryFrom for Config { // Validate the JWT secret if config.jwt_hex.len() != 64 { - eyre::bail!("JWT secret must be a 32 byte hex string"); + eyre::bail!("Engine JWT secret must be a 32 byte hex string"); } else { - tracing::info!("JWT secret loaded successfully"); + tracing::info!("Engine JWT secret loaded successfully"); } config.mevboost_proxy_port = opts.mevboost_proxy_port; - config.engine_api_url = opts.engine_api_url.trim_end_matches('/').to_string(); - config.execution_api_url = opts.execution_api_url.trim_end_matches('/').to_string(); - config.beacon_api_url = opts.beacon_api_url.trim_end_matches('/').to_string(); - config.mevboost_url = opts.mevboost_url.trim_end_matches('/').to_string(); + config.engine_api_url = opts.engine_api_url.parse()?; + config.execution_api_url = opts.execution_api_url.parse()?; + config.beacon_api_url = opts.beacon_api_url.parse()?; + config.mevboost_url = opts.mevboost_url.parse()?; config.validator_indexes = opts.validator_indexes; diff --git a/bolt-sidecar/src/lib.rs b/bolt-sidecar/src/lib.rs index d768e840..a25c2140 100644 --- a/bolt-sidecar/src/lib.rs +++ b/bolt-sidecar/src/lib.rs @@ -11,7 +11,7 @@ pub use api::{ }; mod client; -pub use client::{mevboost::MevBoostClient, rpc::RpcClient}; +pub use client::{mevboost::MevBoostClient, rpc::RpcClient, BeaconClient}; /// Common types and compatibility utilities /// (To be refactored) diff --git a/bolt-sidecar/src/state/consensus.rs b/bolt-sidecar/src/state/consensus.rs index 232290ad..41c2ffec 100644 --- a/bolt-sidecar/src/state/consensus.rs +++ b/bolt-sidecar/src/state/consensus.rs @@ -6,10 +6,12 @@ use std::time::{Duration, Instant}; use beacon_api_client::{mainnet::Client, BlockId, ProposerDuty}; use ethereum_consensus::{deneb::BeaconBlockHeader, phase0::mainnet::SLOTS_PER_EPOCH}; -use reqwest::Url; use super::CommitmentDeadline; -use crate::primitives::{CommitmentRequest, Slot}; +use crate::{ + primitives::{CommitmentRequest, Slot}, + BeaconClient, +}; #[derive(Debug, thiserror::Error)] pub enum ConsensusError { @@ -52,13 +54,10 @@ pub struct ConsensusState { impl ConsensusState { /// Create a new `ConsensusState` with the given configuration. pub fn new( - beacon_api_url: &str, + beacon_api_client: BeaconClient, validator_indexes: &[u64], commitment_deadline_duration: Duration, ) -> Self { - let url = Url::parse(beacon_api_url).expect("valid beacon client URL"); - let beacon_api_client = Client::new(url); - ConsensusState { beacon_api_client, header: BeaconBlockHeader::default(), @@ -158,6 +157,7 @@ impl ConsensusState { mod tests { use super::*; use beacon_api_client::ProposerDuty; + use reqwest::Url; #[tokio::test] async fn test_find_validator_index_for_slot() { diff --git a/bolt-sidecar/src/state/fetcher.rs b/bolt-sidecar/src/state/fetcher.rs index fcf4d17b..58957978 100644 --- a/bolt-sidecar/src/state/fetcher.rs +++ b/bolt-sidecar/src/state/fetcher.rs @@ -8,6 +8,7 @@ use alloy_eips::BlockNumberOrTag; use alloy_primitives::{Address, U256, U64}; use alloy_transport::TransportError; use futures::{stream::FuturesOrdered, StreamExt}; +use reqwest::Url; use crate::{client::rpc::RpcClient, primitives::AccountState}; @@ -48,10 +49,9 @@ pub struct StateClient { impl StateClient { /// Create a new `StateClient` with the given URL and maximum retries. - pub fn new(url: &str) -> Self { - let client = RpcClient::new(url); + pub fn new>(url: U) -> Self { Self { - client, + client: RpcClient::new(url), retry_backoff: Duration::from_millis(RETRY_BACKOFF_MS), } } @@ -187,7 +187,7 @@ mod tests { #[tokio::test] async fn test_state_client() { let anvil = launch_anvil(); - let client = StateClient::new(&anvil.endpoint()); + let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); let address = anvil.addresses().first().unwrap(); let state = client.get_account_state(address, None).await.unwrap(); diff --git a/bolt-sidecar/src/state/head_tracker.rs b/bolt-sidecar/src/state/head_tracker.rs index 70aa40bd..b52df655 100644 --- a/bolt-sidecar/src/state/head_tracker.rs +++ b/bolt-sidecar/src/state/head_tracker.rs @@ -1,11 +1,12 @@ use std::time::Duration; use alloy_rpc_types_beacon::events::HeadEvent; -use beacon_api_client::{mainnet::Client, Topic}; +use beacon_api_client::Topic; use futures::StreamExt; -use reqwest::Url; use tokio::{sync::broadcast, task::AbortHandle}; +use crate::BeaconClient; + /// Simple actor to keep track of the most recent head of the beacon chain /// and broadcast updates to its subscribers. /// @@ -33,8 +34,7 @@ impl Topic for NewHeadsTopic { impl HeadTracker { /// Create a new `HeadTracker` with the given beacon client HTTP URL and /// start listening for new head events in the background - pub fn start(beacon_api_url: &str) -> Self { - let beacon_client = Client::new(Url::parse(beacon_api_url).expect("Valid beacon API url")); + pub fn start(beacon_client: BeaconClient) -> Self { let (new_heads_tx, new_heads_rx) = broadcast::channel(32); let task = tokio::spawn(async move { @@ -95,7 +95,11 @@ impl HeadTracker { #[cfg(test)] mod tests { - use crate::{state::head_tracker::HeadTracker, test_util::try_get_beacon_api_url}; + use reqwest::Url; + + use crate::{ + state::head_tracker::HeadTracker, test_util::try_get_beacon_api_url, BeaconClient, + }; #[tokio::test] async fn test_fetch_next_beacon_head() -> eyre::Result<()> { @@ -106,7 +110,8 @@ mod tests { return Ok(()); }; - let mut tracker = HeadTracker::start(url); + let beacon_client = BeaconClient::new(Url::parse(url).unwrap()); + let mut tracker = HeadTracker::start(beacon_client); let head = tracker.next_head().await?; diff --git a/bolt-sidecar/src/state/mod.rs b/bolt-sidecar/src/state/mod.rs index 4f89c27a..8ffe24ee 100644 --- a/bolt-sidecar/src/state/mod.rs +++ b/bolt-sidecar/src/state/mod.rs @@ -76,6 +76,7 @@ mod tests { use alloy_signer_local::PrivateKeySigner; use execution::{ExecutionState, ValidationError}; use fetcher::StateClient; + use reqwest::Url; use reth_primitives::TransactionSigned; use tracing_subscriber::fmt; @@ -107,7 +108,7 @@ mod tests { // let mut state = State::new(get_client()).await.unwrap(); let anvil = launch_anvil(); - let client = StateClient::new(&anvil.endpoint()); + let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); let mut state = ExecutionState::new(client).await.unwrap(); @@ -141,7 +142,7 @@ mod tests { let _ = fmt::try_init(); let anvil = launch_anvil(); - let client = StateClient::new(&anvil.endpoint()); + let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); let mut state = ExecutionState::new(client).await.unwrap(); @@ -178,7 +179,7 @@ mod tests { let _ = fmt::try_init(); let anvil = launch_anvil(); - let client = StateClient::new(&anvil.endpoint()); + let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); let mut state = ExecutionState::new(client).await.unwrap(); @@ -216,7 +217,7 @@ mod tests { let _ = fmt::try_init(); let anvil = launch_anvil(); - let client = StateClient::new(&anvil.endpoint()); + let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); let mut state = ExecutionState::new(client).await.unwrap(); @@ -255,7 +256,7 @@ mod tests { let _ = fmt::try_init(); let anvil = launch_anvil(); - let client = StateClient::new(&anvil.endpoint()); + let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); let mut state = ExecutionState::new(client).await.unwrap(); diff --git a/bolt-sidecar/src/test_util.rs b/bolt-sidecar/src/test_util.rs index 7307ea2b..9708b24f 100644 --- a/bolt-sidecar/src/test_util.rs +++ b/bolt-sidecar/src/test_util.rs @@ -63,16 +63,19 @@ pub(crate) async fn try_get_beacon_api_url() -> Option<&'static str> { pub(crate) async fn get_test_config() -> Option { let _ = dotenvy::dotenv(); - let jwt = std::env::var("ENGINE_JWT").ok()?; + let Some(jwt) = std::env::var("ENGINE_JWT").ok() else { + tracing::warn!("ENGINE_JWT not found in environment variables"); + return None; + }; let execution = try_get_execution_api_url().await?; let beacon = try_get_beacon_api_url().await?; let engine = try_get_engine_api_url().await?; Some(Config { - execution_api_url: execution.to_string(), - engine_api_url: engine.to_string(), - beacon_api_url: beacon.to_string(), + execution_api_url: execution.parse().ok()?, + engine_api_url: engine.parse().ok()?, + beacon_api_url: beacon.parse().ok()?, jwt_hex: jwt, ..Default::default() }) diff --git a/builder/Dockerfile b/builder/Dockerfile index 522d5360..e11e7b73 100644 --- a/builder/Dockerfile +++ b/builder/Dockerfile @@ -6,7 +6,7 @@ ARG BUILDNUM="" # Build Geth in a stock Go builder container FROM golang:1.21-alpine AS builder -RUN apk add --no-cache gcc musl-dev linux-headers git +RUN apk add --no-cache gcc=10.2.1_pre1-r3 musl-dev=1.2.2-r7 linux-headers=5.10.41-r0 git=2.30.2-r0 # Get dependencies - will also be cached if we won't change go.mod/go.sum COPY go.mod /go-ethereum/ diff --git a/builder/Dockerfile.alltools b/builder/Dockerfile.alltools index 4800421c..4b957a8f 100644 --- a/builder/Dockerfile.alltools +++ b/builder/Dockerfile.alltools @@ -6,7 +6,7 @@ ARG BUILDNUM="" # Build Geth in a stock Go builder container FROM golang:1.21-alpine AS builder -RUN apk add --no-cache gcc musl-dev linux-headers git +RUN apk add --no-cache gcc=10.2.1_pre1-r3 musl-dev=1.2.2-r7 linux-headers=5.10.41-r0 git=2.30.2-r0 # Get dependencies - will also be cached if we won't change go.mod/go.sum COPY go.mod /go-ethereum/ From 3f91e7393632fde14a4dc0b14e50a12a42678be9 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:48:30 +0200 Subject: [PATCH 3/4] chore: restored prev_randao manual fetch --- bolt-sidecar/src/builder/payload_builder.rs | 45 +++++++++++---------- bolt-sidecar/src/state/consensus.rs | 14 ++++--- bolt-sidecar/src/state/mod.rs | 1 + 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/bolt-sidecar/src/builder/payload_builder.rs b/bolt-sidecar/src/builder/payload_builder.rs index 827dba05..a9898841 100644 --- a/bolt-sidecar/src/builder/payload_builder.rs +++ b/bolt-sidecar/src/builder/payload_builder.rs @@ -11,6 +11,7 @@ use reth_primitives::{ Withdrawals, EMPTY_OMMER_ROOT_HASH, }; use reth_rpc_layer::{secret_to_bearer_header, JwtSecret}; +use serde_json::Value; use super::{ compat::{to_alloy_execution_payload, to_reth_withdrawal}, @@ -117,32 +118,32 @@ impl FallbackPayloadBuilder { tracing::debug!(amount = ?withdrawals.len(), "got withdrawals"); - let prev_randao = self - .beacon_api_client - .get_randao(StateId::Head, None) - .await?; - let prev_randao = B256::from_slice(&prev_randao); + // let prev_randao = self + // .beacon_api_client + // .get_randao(StateId::Head, None) + // .await?; + // let prev_randao = B256::from_slice(&prev_randao); // NOTE: for some reason, this call fails with an ApiResult deserialization error // when using the beacon_api_client crate directly, so we use reqwest temporarily. // this is to be refactored. - // let prev_randao = reqwest::Client::new() - // .get(format!( - // "{}/eth/v1/beacon/states/head/randao", - // self.beacon_api_url - // )) - // .send() - // .await - // .unwrap() - // .json::() - // .await - // .unwrap(); - // let prev_randao = prev_randao - // .pointer("/data/randao") - // .unwrap() - // .as_str() - // .unwrap(); - // let prev_randao = B256::from_hex(prev_randao).unwrap(); + let prev_randao = reqwest::Client::new() + .get(format!( + "{}/eth/v1/beacon/states/head/randao", + self.beacon_api_client.endpoint.as_str() + )) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); + let prev_randao = prev_randao + .pointer("/data/randao") + .unwrap() + .as_str() + .unwrap(); + let prev_randao = B256::from_hex(prev_randao).unwrap(); tracing::debug!("got prev_randao"); let parent_beacon_block_root = self diff --git a/bolt-sidecar/src/state/consensus.rs b/bolt-sidecar/src/state/consensus.rs index 41c2ffec..67ecd381 100644 --- a/bolt-sidecar/src/state/consensus.rs +++ b/bolt-sidecar/src/state/consensus.rs @@ -1,7 +1,3 @@ -#![allow(missing_docs)] -#![allow(unused_variables)] -#![allow(missing_debug_implementations)] - use std::time::{Duration, Instant}; use beacon_api_client::{mainnet::Client, BlockId, ProposerDuty}; @@ -13,7 +9,10 @@ use crate::{ BeaconClient, }; +/// Consensus-related errors #[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +#[non_exhaustive] pub enum ConsensusError { #[error("Beacon API error: {0}")] BeaconApiError(#[from] beacon_api_client::Error), @@ -25,13 +24,17 @@ pub enum ConsensusError { ValidatorNotFound, } +/// Represents an epoch in the beacon chain. #[derive(Debug, Default)] +#[allow(missing_docs)] pub struct Epoch { pub value: u64, pub start_slot: Slot, pub proposer_duties: Vec, } +/// Represents the consensus state container for the sidecar. +#[allow(missing_debug_implementations)] pub struct ConsensusState { beacon_api_client: Client, header: BeaconBlockHeader, @@ -48,7 +51,8 @@ pub struct ConsensusState { /// which won't have time to be included by the PBS pipeline. // commitment_deadline: u64, pub commitment_deadline: CommitmentDeadline, - pub commitment_deadline_duration: Duration, + /// The duration of the commitment deadline. + commitment_deadline_duration: Duration, } impl ConsensusState { diff --git a/bolt-sidecar/src/state/mod.rs b/bolt-sidecar/src/state/mod.rs index 8ffe24ee..0e99a73d 100644 --- a/bolt-sidecar/src/state/mod.rs +++ b/bolt-sidecar/src/state/mod.rs @@ -17,6 +17,7 @@ pub use execution::{ExecutionState, ValidationError}; pub mod fetcher; pub use fetcher::StateClient; +/// Module to track the consensus state. pub mod consensus; pub use consensus::ConsensusState; From c177e4608b69e5e872cf0fd1de02b4dccf7263a1 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:50:10 +0200 Subject: [PATCH 4/4] chore: restored apk unpinned versions --- builder/Dockerfile | 2 +- builder/Dockerfile.alltools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/Dockerfile b/builder/Dockerfile index e11e7b73..522d5360 100644 --- a/builder/Dockerfile +++ b/builder/Dockerfile @@ -6,7 +6,7 @@ ARG BUILDNUM="" # Build Geth in a stock Go builder container FROM golang:1.21-alpine AS builder -RUN apk add --no-cache gcc=10.2.1_pre1-r3 musl-dev=1.2.2-r7 linux-headers=5.10.41-r0 git=2.30.2-r0 +RUN apk add --no-cache gcc musl-dev linux-headers git # Get dependencies - will also be cached if we won't change go.mod/go.sum COPY go.mod /go-ethereum/ diff --git a/builder/Dockerfile.alltools b/builder/Dockerfile.alltools index 4b957a8f..4800421c 100644 --- a/builder/Dockerfile.alltools +++ b/builder/Dockerfile.alltools @@ -6,7 +6,7 @@ ARG BUILDNUM="" # Build Geth in a stock Go builder container FROM golang:1.21-alpine AS builder -RUN apk add --no-cache gcc=10.2.1_pre1-r3 musl-dev=1.2.2-r7 linux-headers=5.10.41-r0 git=2.30.2-r0 +RUN apk add --no-cache gcc musl-dev linux-headers git # Get dependencies - will also be cached if we won't change go.mod/go.sum COPY go.mod /go-ethereum/