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

feat: solve outstanding TODOs #361

Merged
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
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
Loading