Skip to content

Commit

Permalink
Merge pull request #361 from chainbound/nico/feat/solve-outstanding-t…
Browse files Browse the repository at this point in the history
…odos

feat: solve outstanding TODOs
  • Loading branch information
thedevbirb authored Nov 8, 2024
2 parents 1f310d9 + 5b8c71a commit 43f3b17
Show file tree
Hide file tree
Showing 52 changed files with 500 additions and 760 deletions.
2 changes: 1 addition & 1 deletion .github/.linkspector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ useGitIgnore: true

ignorePatterns:
- pattern: "^https://.*etherscan.io/.*$"

- pattern: "^https://.*docs.eigenlayer.xyz/.*$"
4 changes: 4 additions & 0 deletions .github/workflows/contracts_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ on:
paths:
- "bolt-contracts/**"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
check:
name: Foundry project
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linkspector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ jobs:
reporter: github-pr-review
config_file: .github/.linkspector.yml
fail_on_error: true
filter_mode: nofilter
filter_mode: nofilter
7 changes: 0 additions & 7 deletions bolt-sidecar/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ BOLT_SIDECAR_CONSTRAINTS_PROXY_PORT=18550
# URL to forward the constraints produced by the Bolt sidecar to a server
# supporting the Constraints API, such as an MEV-Boost fork
BOLT_SIDECAR_CONSTRAINTS_API_URL="http://localhost:18551"
# Validator indexes of connected validators that the sidecar should accept
# commitments on behalf of.
# Accepted values:
# - a comma-separated list of indexes (e.g. "1,2,3,4")
# - a contiguous range of indexes (e.g. "1..4")
# - a mix of the above (e.g. "1,2..4,6..8")
BOLT_SIDECAR_VALIDATOR_INDEXES=
# The JWT secret token to authenticate calls to the engine API. It can be
# either be a hex-encoded string or a file path to a file containing the
# hex-encoded secret.
Expand Down
12 changes: 12 additions & 0 deletions bolt-sidecar/Cargo.lock

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

1 change: 1 addition & 0 deletions bolt-sidecar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ tower-http = { version = "0.5.2", features = ["timeout"] }
axum-extra = "0.9.3"
warp = "0.3.7"
futures = "0.3"
tokio-retry = "0.3.0"

# crypto
blst = "0.3.12"
Expand Down
10 changes: 0 additions & 10 deletions bolt-sidecar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,6 @@ Options:
[env: BOLT_SIDECAR_CONSTRAINTS_PROXY_PORT=]
[default: 18551]
--validator-indexes <VALIDATOR_INDEXES>
Validator indexes of connected validators that the sidecar should accept commitments on behalf of.
Accepted values:
- a comma-separated list of indexes (e.g. "1,2,3,4")
- a contiguous range of indexes (e.g. "1..4")
- a mix of the above (e.g. "1,2..4,6..8")
[env: BOLT_SIDECAR_VALIDATOR_INDEXES=]
[default: ]
--jwt-hex <JWT_HEX>
The JWT secret token to authenticate calls to the engine API.
Expand Down
2 changes: 1 addition & 1 deletion bolt-sidecar/bin/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use clap::Parser;
use eyre::{bail, Result};
use tracing::info;

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

const BOLT: &str = r#"
██████╗ ██████╗ ██╗ ████████╗
Expand Down
12 changes: 10 additions & 2 deletions bolt-sidecar/src/api/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use super::spec::{
STATUS_PATH,
};
use crate::{
builder::payload_fetcher::PayloadFetcher,
client::constraints_client::ConstraintsClient,
builder::PayloadFetcher,
client::ConstraintsClient,
primitives::{GetPayloadResponse, SignedBuilderBid},
telemetry::ApiMetrics,
};
Expand All @@ -39,6 +39,7 @@ const GET_HEADER_WITH_PROOFS_TIMEOUT: Duration = Duration::from_millis(500);

/// A proxy server for the builder API.
/// Forwards all requests to the target after interception.
#[derive(Debug)]
pub struct BuilderProxyServer<T, P> {
proxy_target: T,
/// INVARIANT: This will be `Some` IFF we have signed a local header for the latest slot.
Expand All @@ -47,7 +48,9 @@ pub struct BuilderProxyServer<T, P> {
payload_fetcher: P,
}

/// Parameters for the get_header request.
#[derive(Debug, Deserialize)]
#[allow(missing_docs)]
pub struct GetHeaderParams {
pub slot: u64,
pub parent_hash: Hash32,
Expand All @@ -60,6 +63,7 @@ where
T: ConstraintsApi,
P: PayloadFetcher + Send + Sync,
{
/// Create a new builder proxy server.
pub fn new(proxy_target: T, payload_fetcher: P) -> Self {
Self { proxy_target, local_payload: Mutex::new(None), payload_fetcher }
}
Expand Down Expand Up @@ -164,6 +168,8 @@ where
Ok(Json(versioned_bid))
}

/// Gets the payload. If we have a locally built payload, we return it.
/// Otherwise, we forward the request to the constraints client.
pub async fn get_payload(
State(server): State<Arc<BuilderProxyServer<T, P>>>,
req: Request<Body>,
Expand Down Expand Up @@ -259,7 +265,9 @@ async fn index() -> Html<&'static str> {
Html("Hello")
}

/// Errors that can occur when checking the integrity of a locally built payload.
#[derive(Error, Debug, Clone)]
#[allow(missing_docs)]
pub enum LocalPayloadIntegrityError {
#[error(
"Locally built payload does not match signed header.
Expand Down
30 changes: 17 additions & 13 deletions bolt-sidecar/src/api/commitments/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,29 @@ use serde_json::Value;
use tracing::{debug, error, info, instrument};

use crate::{
commitments::headers::auth_from_headers,
api::commitments::headers::auth_from_headers,
common::CARGO_PKG_VERSION,
primitives::{commitment::SignatureError, InclusionRequest},
};

use super::{
jsonrpc::{JsonPayload, JsonResponse},
server::CommitmentsApiInner,
spec::{CommitmentsApi, Error, RejectionError, GET_VERSION_METHOD, REQUEST_INCLUSION_METHOD},
spec::{
CommitmentError, CommitmentsApi, RejectionError, GET_VERSION_METHOD,
REQUEST_INCLUSION_METHOD,
},
};

/// Handler function for the root JSON-RPC path.
#[instrument(skip_all, name = "POST /rpc", fields(method = %payload.method))]
pub async fn rpc_entrypoint(
headers: HeaderMap,
State(api): State<Arc<CommitmentsApiInner>>,
WithRejection(Json(payload), _): WithRejection<Json<JsonPayload>, Error>,
) -> Result<Json<JsonResponse>, Error> {
WithRejection(Json(payload), _): WithRejection<Json<JsonPayload>, CommitmentError>,
) -> Result<Json<JsonResponse>, CommitmentError> {
debug!("Received new request");

let (signer, signature) = auth_from_headers(&headers).inspect_err(|e| {
error!("Failed to extract signature from headers: {:?}", e);
})?;

match payload.method.as_str() {
GET_VERSION_METHOD => {
let version_string = format!("bolt-sidecar-v{CARGO_PKG_VERSION}");
Expand All @@ -47,6 +46,11 @@ pub async fn rpc_entrypoint(
}

REQUEST_INCLUSION_METHOD => {
// Validate the authentication header and extract the signer and signature
let (signer, signature) = auth_from_headers(&headers).inspect_err(|e| {
error!("Failed to extract signature from headers: {:?}", e);
})?;

let Some(request_json) = payload.params.first().cloned() else {
return Err(RejectionError::ValidationFailed("Bad params".to_string()).into());
};
Expand All @@ -66,12 +70,12 @@ pub async fn rpc_entrypoint(

if recovered_signer != signer {
error!(
?recovered_signer,
?signer,
%recovered_signer,
%signer,
"Recovered signer does not match the provided signer"
);

return Err(Error::InvalidSignature(SignatureError));
return Err(CommitmentError::InvalidSignature(SignatureError));
}

// Set the request signer
Expand All @@ -83,15 +87,15 @@ pub async fn rpc_entrypoint(
// Create the JSON-RPC response
let response = JsonResponse {
id: payload.id,
result: serde_json::to_value(inclusion_commitment).unwrap(),
result: serde_json::to_value(inclusion_commitment).expect("infallible"),
..Default::default()
};

Ok(Json(response))
}
other => {
error!("Unknown method: {}", other);
Err(Error::UnknownMethod)
Err(CommitmentError::UnknownMethod)
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions bolt-sidecar/src/api/commitments/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ use axum::http::HeaderMap;

use crate::primitives::commitment::SignatureError;

use super::spec::{Error, SIGNATURE_HEADER};
use super::spec::{CommitmentError, SIGNATURE_HEADER};

/// Extracts the signature ([SIGNATURE_HEADER]) from the HTTP headers.
#[inline]
pub fn auth_from_headers(headers: &HeaderMap) -> Result<(Address, Signature), Error> {
let auth = headers.get(SIGNATURE_HEADER).ok_or(Error::NoSignature)?;
pub fn auth_from_headers(headers: &HeaderMap) -> Result<(Address, Signature), CommitmentError> {
let auth = headers.get(SIGNATURE_HEADER).ok_or(CommitmentError::NoSignature)?;

// Remove the "0x" prefix
let auth = auth.to_str().map_err(|_| Error::MalformedHeader)?;
let auth = auth.to_str().map_err(|_| CommitmentError::MalformedHeader)?;

let mut split = auth.split(':');

let address = split.next().ok_or(Error::MalformedHeader)?;
let address = Address::from_str(address).map_err(|_| Error::MalformedHeader)?;
let address = split.next().ok_or(CommitmentError::MalformedHeader)?;
let address = Address::from_str(address).map_err(|_| CommitmentError::MalformedHeader)?;

let sig = split.next().ok_or(Error::MalformedHeader)?;
let sig = Signature::from_str(sig).map_err(|_| Error::InvalidSignature(SignatureError))?;
let sig = split.next().ok_or(CommitmentError::MalformedHeader)?;
let sig = Signature::from_str(sig).map_err(|_| CommitmentError::InvalidSignature(SignatureError))?;

Ok((address, sig))
}
Expand Down
24 changes: 12 additions & 12 deletions bolt-sidecar/src/api/commitments/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use tower_http::timeout::TimeoutLayer;
use tracing::{error, info};

use crate::{
commitments::handlers,
api::commitments::handlers,
primitives::{
commitment::{InclusionCommitment, SignedCommitment},
CommitmentRequest, InclusionRequest,
Expand All @@ -31,32 +31,32 @@ use crate::{
use super::{
middleware::track_server_metrics,
spec,
spec::{CommitmentsApi, Error},
spec::{CommitmentError, CommitmentsApi},
};

/// Event type emitted by the commitments API.
#[derive(Debug)]
pub struct Event {
pub struct CommitmentEvent {
/// The request to process.
pub request: CommitmentRequest,
/// The response channel.
pub response: oneshot::Sender<Result<SignedCommitment, Error>>,
pub response: oneshot::Sender<Result<SignedCommitment, CommitmentError>>,
}

/// The inner commitments-API handler that implements the [CommitmentsApi] spec.
/// Should be wrapped by a [CommitmentsApiServer] JSON-RPC server to handle requests.
#[derive(Debug)]
pub struct CommitmentsApiInner {
/// Event notification channel
events: mpsc::Sender<Event>,
events: mpsc::Sender<CommitmentEvent>,
/// Optional whitelist of ECDSA public keys
#[allow(unused)]
whitelist: Option<HashSet<Address>>,
}

impl CommitmentsApiInner {
/// Create a new API server with an optional whitelist of ECDSA public keys.
pub fn new(events: mpsc::Sender<Event>) -> Self {
pub fn new(events: mpsc::Sender<CommitmentEvent>) -> Self {
Self { events, whitelist: None }
}
}
Expand All @@ -66,17 +66,17 @@ impl CommitmentsApi for CommitmentsApiInner {
async fn request_inclusion(
&self,
inclusion_request: InclusionRequest,
) -> Result<InclusionCommitment, Error> {
) -> Result<InclusionCommitment, CommitmentError> {
let (response_tx, response_rx) = oneshot::channel();

let event = Event {
let event = CommitmentEvent {
request: CommitmentRequest::Inclusion(inclusion_request),
response: response_tx,
};

self.events.send(event).await.unwrap();

response_rx.await.map_err(|_| Error::Internal)?.map(|c| c.into())
response_rx.await.map_err(|_| CommitmentError::Internal)?.map(|c| c.into())
}
}

Expand Down Expand Up @@ -119,7 +119,7 @@ impl CommitmentsApiServer {
}

/// Runs the JSON-RPC server, sending events to the provided channel.
pub async fn run(&mut self, events_tx: mpsc::Sender<Event>) {
pub async fn run(&mut self, events_tx: mpsc::Sender<CommitmentEvent>) {
let api = Arc::new(CommitmentsApiInner::new(events_tx));

let router = make_router(api);
Expand Down Expand Up @@ -169,7 +169,7 @@ fn make_router(state: Arc<CommitmentsApiInner>) -> Router {

#[cfg(test)]
mod test {
use crate::commitments::{jsonrpc::JsonResponse, spec::SIGNATURE_HEADER};
use crate::api::commitments::{jsonrpc::JsonResponse, spec::SIGNATURE_HEADER};
use alloy::signers::{k256::SecretKey, local::PrivateKeySigner};
use serde_json::json;

Expand Down Expand Up @@ -271,7 +271,7 @@ mod test {
let _ = tx.send(());
});

let Event { request, response } = events.recv().await.unwrap();
let CommitmentEvent { request, response } = events.recv().await.unwrap();

let commitment_signer = PrivateKeySigner::random();

Expand Down
Loading

0 comments on commit 43f3b17

Please sign in to comment.