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(sidecar): Signature checks (user, proposer) + refactor #42

Merged
merged 12 commits into from
May 31, 2024
430 changes: 223 additions & 207 deletions bolt-sidecar/Cargo.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions bolt-sidecar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ edition = "2021"
# core
clap = { version = "4.5.4", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
secp256k1 = { version = "0.29.0", features = ["hashes", "rand"] }
warp = "0.3.7"
futures = "0.3"

# crypto
secp256k1 = { version = "0.29.0", features = ["hashes", "rand"] }

# alloy
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", features = [
"reqwest",
Expand All @@ -28,7 +30,7 @@ alloy-transport-http = { git = "https://github.com/alloy-rs/alloy" }
alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy" }
alloy-pubsub = { git = "https://github.com/alloy-rs/alloy" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy" }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy" }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", features = ["k256"] }
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy" }
alloy-primitives = "0.7.1"
alloy-rlp = "0.3"
Expand Down
15 changes: 11 additions & 4 deletions bolt-sidecar/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# `bolt-sidecar`

The Bolt sidecar is the main entrypoint for proposers to issue proposer commitments. Proposers should point their `builder-api` to the Bolt sidecar API in order to enable it.

## Functionality

The sidecar is responsible for:

1. Registering the preferences of the proposer
2. Accepting (or rejecting) commitment requests
3. Implementing pricing strategies
Expand All @@ -12,23 +15,27 @@ The sidecar is responsible for:
7. Dealing with PBS failures by falling back to the local template

### Local Block Template

The local block template serves 3 purposes:

1. Building a fallback block in case the PBS pipeline fails
2. Maintaining intermediate state to simulate commitment requests on
3. Syncing with Ethereum state and invalidating stale commitments

*What do we simulate?*
_What do we simulate?_
We only simulate in order to verify the validity of the transaction according to protocol rules. This means:

1. The transaction sender should be able to pay for it: `balance >= value + fee`
2. The transaction nonce should be higher than any previously known nonce
3. The base fee should be able to cover the maximum base fee the target block can have: `max_base_fee = current_base_fee * 1.125^block_diff`

*Building strategy*
_Building strategy_
The block template is built and simulated on in FIFO order.

*Updating state*
_Updating state_
We store a list of commitment addresses along with their account state. For each new block, we should update that state and check if we have to invalidate any commitments. This is critical as we don't want to return an invalid block
in case a fallback block is required.

## Running
- We require Anvil to be installed in the $PATH for running tests

- We require Anvil to be installed in the $PATH for running tests
2 changes: 1 addition & 1 deletion bolt-sidecar/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use alloy::ClientBuilder;
use alloy_eips::BlockNumberOrTag;
use alloy_primitives::{Address, U256, U64};
use alloy_rpc_client as alloy;
use alloy_rpc_types::{AccountInfo, FeeHistory};
use alloy_rpc_types::FeeHistory;
use alloy_transport::TransportResult;
use alloy_transport_http::Http;
use reqwest::{Client, Url};
Expand Down
27 changes: 13 additions & 14 deletions bolt-sidecar/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::str::FromStr;

use clap::Parser;
use secp256k1::{rand, SecretKey};

#[derive(Parser)]
pub(super) struct Opts {
Expand All @@ -17,50 +18,48 @@ pub(super) struct Opts {

pub struct Config {
pub rpc_port: u16,
pub private_key: secp256k1::SecretKey,
pub private_key: SecretKey,
pub limits: Limits,
}

impl Default for Config {
fn default() -> Self {
Self {
rpc_port: 8000,
private_key: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()),
private_key: SecretKey::new(&mut rand::thread_rng()),
limits: Limits::default(),
}
}
}

impl From<Opts> for Config {
fn from(opts: Opts) -> Self {
// Start with default config
impl TryFrom<Opts> for Config {
type Error = eyre::Report;

fn try_from(opts: Opts) -> eyre::Result<Self> {
let mut config = Config::default();

if let Some(port) = opts.port {
config.rpc_port = port;
}

let private_key = secp256k1::SecretKey::from_str(&opts.private_key)
.expect("Invalid secpk256k1 private key");

config.private_key = private_key;

if let Some(max_commitments) = opts.max_commitments {
config.limits.max_commitments_per_block = max_commitments;
config.limits.max_commitments_per_slot = max_commitments;
}

config
config.private_key = SecretKey::from_str(&opts.private_key)?;

Ok(config)
}
}

pub struct Limits {
pub max_commitments_per_block: usize,
pub max_commitments_per_slot: usize,
}

impl Default for Limits {
fn default() -> Self {
Self {
max_commitments_per_block: 6,
max_commitments_per_slot: 6,
}
}
}
53 changes: 53 additions & 0 deletions bolt-sidecar/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use secp256k1::{ecdsa::Signature, Message, PublicKey, SecretKey};

/// Trait for any types that can be signed and verified with ECDSA.
/// This trait is used to abstract over the signing and verification of different types.
pub trait SignableECDSA {
/// Create a digest of the object that can be signed.
/// This API doesn't enforce a specific hash or encoding method.
fn digest(&self) -> Message;

/// Sign the object with the given key. Returns the signature.
///
/// Note: The default implementation should be used where possible.
fn sign(&self, key: &SecretKey) -> Signature {
secp256k1::Secp256k1::new().sign_ecdsa(&self.digest(), key)
}

/// Verify the signature of the object with the given public key.
///
/// Note: The default implementation should be used where possible.
fn verify(&self, signature: &Signature, pubkey: &PublicKey) -> bool {
secp256k1::Secp256k1::new()
.verify_ecdsa(&self.digest(), signature, pubkey)
.is_ok()
}
}

/// A signer that can sign any type that implements `Signable{curve}` trait.
pub struct Signer {
secp256k1_key: SecretKey,
}

impl Signer {
/// Create a new signer with the given SECP256K1 secret key.
pub fn new(secp256k1_key: SecretKey) -> Self {
Self { secp256k1_key }
}

/// Sign the given object with the SECP256K1 key and ECDSA algorithm.
pub fn sign_ecdsa<T: SignableECDSA>(&self, obj: &T) -> Signature {
obj.sign(&self.secp256k1_key)
}

/// Verify the given object with the SECP256K1 key and ECDSA algorithm.
#[allow(dead_code)]
pub fn verify_ecdsa<T: SignableECDSA>(
&self,
obj: &T,
sig: &Signature,
pubkey: &PublicKey,
) -> bool {
obj.verify(sig, pubkey)
}
}
Loading