Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(sidecar): cleanup + remove TODOs (continue) #370

Merged
merged 2 commits into from
Nov 11, 2024
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
25 changes: 5 additions & 20 deletions bolt-sidecar/bin/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,22 @@ const BOLT: &str = r#"

#[tokio::main]
async fn main() -> eyre::Result<()> {
println!("{}", BOLT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious -- do you prefer displaying the logo as the first thing or after all startup checks passed successfully?

I like the idea of seeing the logo knowing I've done everything well :D but maybe that's just me

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that, but I didn't like having other logs before the logo, such as:

[info] telemetry initialized!
LOGO
[...] other logs

instead it should be:
LOGO
[...] all logs

Not a blocker ofc, let's just pick one :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, let's merge this! We can modify it at a later time if we wish


read_env_file()?;

let opts = Opts::parse();

init_telemetry_stack(opts.telemetry.metrics_port())?;

println!("{BOLT}");

info!(chain = opts.chain.name(), "Starting Bolt sidecar");

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)
}
}
SidecarDriver::with_local_signer(&opts).await?.run_forever().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 commit boost: {:?}", err)
}
}
SidecarDriver::with_commit_boost_signer(&opts).await?.run_forever().await
} else {
match SidecarDriver::with_keystore_signer(&opts).await {
Ok(driver) => driver.run_forever().await,
Err(err) => {
bail!("Failed to initialize the sidecar driver with keystore signer: {:?}", err)
}
}
SidecarDriver::with_keystore_signer(&opts).await?.run_forever().await
}
}

Expand Down
2 changes: 1 addition & 1 deletion bolt-sidecar/src/api/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ where
{
info!(
port = config.server_port,
target = config.constraints_client.url.to_string(),
target = config.constraints_client.target(),
"Starting builder proxy..."
);

Expand Down
130 changes: 60 additions & 70 deletions bolt-sidecar/src/chain_io/manager.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
use std::str::FromStr;
use tracing::error;

use alloy::{
contract::Error as ContractError,
primitives::{Address, Bytes},
primitives::Address,
providers::{ProviderBuilder, RootProvider},
sol,
sol_types::SolInterface,
transports::{http::Http, TransportError},
transports::http::Http,
};
use ethereum_consensus::primitives::BlsPublicKey;
use eyre::bail;
use eyre::{bail, Context};
use reqwest::{Client, Url};
use serde::Serialize;

Expand All @@ -23,7 +18,8 @@ use crate::config::chain::Chain;

use super::utils::{self, CompressedHash};

const CHUNK_SIZE: usize = 100;
/// Maximum number of keys to fetch from the EL node in a single query.
const MAX_CHUNK_SIZE: usize = 100;

/// A wrapper over a BoltManagerContract that exposes various utility methods.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -58,91 +54,85 @@ impl BoltManager {
) -> eyre::Result<Vec<ProposerStatus>> {
let hashes_with_preimages = utils::pubkey_hashes(keys);
let mut hashes = hashes_with_preimages.keys().cloned().collect::<Vec<_>>();

let total_keys = hashes.len();
let chunk_count = total_keys.div_ceil(MAX_CHUNK_SIZE);

let mut proposers_statuses = Vec::with_capacity(hashes.len());
let mut proposers_statuses = Vec::with_capacity(total_keys);

let mut i = 0;
while !hashes.is_empty() {
i += 1;

// No more than CHUNK_SIZE at a time to avoid EL config limits
//
// TODO: write an unsafe function that splits a vec into owned chunks without
// allocating
let hashes_chunk = hashes.drain(..CHUNK_SIZE.min(hashes.len())).collect::<Vec<_>>();

debug!(
"fetching {} proposer statuses for chunk {} of {}",
hashes_chunk.len(),
i,
total_keys.div_ceil(CHUNK_SIZE)
);

let returndata = self.0.getProposerStatuses(hashes_chunk).call().await;

// TODO: clean this after https://github.com/alloy-rs/alloy/issues/787 is merged
let error = match returndata.map(|data| data.statuses) {
Ok(statuses) => {
for status in &statuses {
if !status.active {
bail!(
"validator with public key {:?} and public key hash {:?} is not active in Bolt",
hashes_with_preimages.get(&status.pubkeyHash),
status.pubkeyHash
);
} else if status.operator != commitment_signer_pubkey {
bail!(generate_operator_keys_mismatch_error(
status.pubkeyHash,
commitment_signer_pubkey,
status.operator
));
}
}

proposers_statuses.extend(statuses);

continue;
// No more than MAX_CHUNK_SIZE at a time to avoid EL config limits
let chunk_size = MAX_CHUNK_SIZE.min(hashes.len());
let hashes_chunk = hashes.drain(..chunk_size).collect::<Vec<_>>();

debug!("fetching proposer statuses for chunk {} of {}", i, chunk_count);

let returndata = match self.0.getProposerStatuses(hashes_chunk).call().await {
Ok(returndata) => returndata,
Err(error) => {
let decoded_error = utils::try_parse_contract_error(error)
.wrap_err("Failed to fetch proposer statuses from EL client")?;

bail!(generate_bolt_manager_error(decoded_error, commitment_signer_pubkey));
}
Err(error) => match error {
ContractError::TransportError(TransportError::ErrorResp(err)) => {
error!("error response from contract: {:?}", err);
let data = err.data.unwrap_or_default();
let data = data.get().trim_matches('"');
let data = Bytes::from_str(data)?;

BoltManagerContractErrors::abi_decode(&data, true)?
}
e => return Err(e)?,
},
};

match error {
BoltManagerContractErrors::ValidatorDoesNotExist(ValidatorDoesNotExist {
pubkeyHash: pubkey_hash,
}) => {
bail!("ValidatorDoesNotExist -- validator with public key {:?} and public key hash {:?} is not registered in Bolt", hashes_with_preimages.get(&pubkey_hash), pubkey_hash);
}
BoltManagerContractErrors::InvalidQuery(_) => {
bail!("InvalidQuery -- invalid zero public key hash");
}
BoltManagerContractErrors::KeyNotFound(_) => {
bail!("KeyNotFound -- operator associated with commitment signer public key {:?} is not registered in Bolt", commitment_signer_pubkey);
for status in &returndata.statuses {
if !status.active {
bail!(
"validator with public key {:?} and public key hash {} is not active in Bolt",
hashes_with_preimages.get(&status.pubkeyHash),
status.pubkeyHash
);
} else if status.operator != commitment_signer_pubkey {
bail!(generate_operator_keys_mismatch_error(
status.pubkeyHash,
commitment_signer_pubkey,
status.operator
));
}
}

proposers_statuses.extend(returndata.statuses);

continue;
}

Ok(proposers_statuses)
}
}

fn generate_bolt_manager_error(
error: BoltManagerContractErrors,
commitment_signer_pubkey: Address,
) -> String {
match error {
BoltManagerContractErrors::ValidatorDoesNotExist(ValidatorDoesNotExist {
pubkeyHash: pubkey_hash,
}) => {
format!("BoltManager::ValidatorDoesNotExist: validator with public key hash {} is not registered in Bolt", pubkey_hash)
}
BoltManagerContractErrors::InvalidQuery(_) => {
"BoltManager::InvalidQuery: invalid zero public key hash".to_string()
}
BoltManagerContractErrors::KeyNotFound(_) => {
format!("BoltManager::KeyNotFound: operator associated with commitment signer public key {} is not registered in Bolt", commitment_signer_pubkey)
}
}
}

fn generate_operator_keys_mismatch_error(
pubkey_hash: CompressedHash,
commitment_signer_pubkey: Address,
operator: Address,
) -> String {
format!(
"mismatch between commitment signer public key and authorized operator address for validator with public key hash {:?} in Bolt.\n - commitment signer public key: {:?}\n - authorized operator address: {:?}",
"Mismatch between commitment signer public key and authorized operator address for validator\nwith public key hash {:?}.
- commitment signer public key: {}
- authorized operator address: {}",
pubkey_hash,
commitment_signer_pubkey,
operator
Expand Down
44 changes: 42 additions & 2 deletions bolt-sidecar/src/chain_io/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use std::collections::HashMap;
use std::{collections::HashMap, str::FromStr};

use alloy::primitives::{FixedBytes, B512};
use alloy::{
contract::Error as ContractError,
primitives::{Bytes, FixedBytes, B512},
sol_types::SolInterface,
transports::TransportError,
};
use ethereum_consensus::primitives::BlsPublicKey;
use reth_primitives::keccak256;

Expand Down Expand Up @@ -39,6 +44,41 @@ fn pubkey_hash_digest(key: &BlsPublicKey) -> B512 {
onchain_pubkey_repr
}

/// Try to decode a contract error into a specific Solidity error interface.
/// If the error cannot be decoded or it is not a contract error, return the original error.
///
/// Example usage:
///
/// ```rust no_run
/// sol! {
/// library ErrorLib {
/// error SomeError(uint256 code);
/// }
/// }
///
/// // call a contract that may return an error with the SomeError interface
/// let returndata = match myContract.call().await {
/// Ok(returndata) => returndata,
/// Err(err) => {
/// let decoded_error = try_decode_contract_error::<ErrorLib::ErrorLibError>(err)?;
/// // handle the decoded error however you want; for example, return it
/// return Err(decoded_error);
/// },
/// }
/// ```
pub fn try_parse_contract_error<T: SolInterface>(error: ContractError) -> Result<T, ContractError> {
match error {
ContractError::TransportError(TransportError::ErrorResp(resp)) => {
let data = resp.data.unwrap_or_default();
let data = data.get().trim_matches('"');
let data = Bytes::from_str(data).unwrap_or_default();

T::abi_decode(&data, true).map_err(Into::into)
}
_ => Err(error),
}
}

#[cfg(test)]
mod tests {
use alloy::hex;
Expand Down
8 changes: 6 additions & 2 deletions bolt-sidecar/src/client/constraints_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ use crate::{
/// A client for interacting with the Constraints client API.
#[derive(Debug, Clone)]
pub struct ConstraintsClient {
/// The URL of the MEV-Boost target supporting the Constraints API.
pub url: Url,
url: Url,
client: reqwest::Client,
delegations: Vec<SignedDelegation>,
}
Expand Down Expand Up @@ -87,6 +86,11 @@ impl ConstraintsClient {
.collect::<HashSet<_>>()
}

/// Returns the URL of the target client.
pub fn target(&self) -> &str {
self.url.as_str()
}

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
5 changes: 0 additions & 5 deletions bolt-sidecar/src/config/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,6 @@ impl ChainConfig {
}
}

/// Get the chain name for the given chain.
pub fn name(&self) -> &'static str {
self.chain.name()
}

/// Get the slot time for the given chain in seconds.
pub fn slot_time(&self) -> u64 {
self.slot_time
Expand Down
Loading