@@ -4,7 +4,7 @@ use alloy::{rpc::types::beacon::events::HeadEvent, signers::local::PrivateKeySig
44use beacon_api_client:: mainnet:: Client as BeaconClient ;
55use ethereum_consensus:: {
66 clock:: { self , SlotStream , SystemTimeProvider } ,
7- phase0:: mainnet:: SLOTS_PER_EPOCH ,
7+ phase0:: mainnet:: { BlsPublicKey , SLOTS_PER_EPOCH } ,
88} ;
99use eyre:: Context ;
1010use futures:: StreamExt ;
@@ -27,8 +27,8 @@ use crate::{
2727 config:: Opts ,
2828 crypto:: { SignableBLS , SignerECDSA } ,
2929 primitives:: {
30- read_signed_delegations_from_file , CommitmentRequest , ConstraintsMessage ,
31- FetchPayloadRequest , SignedConstraints , TransactionExt ,
30+ commitment :: SignedCommitment , read_signed_delegations_from_file , CommitmentRequest ,
31+ ConstraintsMessage , FetchPayloadRequest , SignedConstraints , TransactionExt ,
3232 } ,
3333 signer:: { keystore:: KeystoreSigner , local:: LocalSigner , CommitBoostSigner , SignerBLS } ,
3434 state:: { fetcher:: StateFetcher , ConsensusState , ExecutionState , HeadTracker , StateClient } ,
@@ -66,6 +66,8 @@ pub struct SidecarDriver<C, ECDSA> {
6666 payload_requests_rx : mpsc:: Receiver < FetchPayloadRequest > ,
6767 /// Stream of slots made from the consensus clock
6868 slot_stream : SlotStream < SystemTimeProvider > ,
69+ /// Whether to skip consensus checks (should only be used for testing)
70+ unsafe_skip_consensus_checks : bool ,
6971}
7072
7173impl SidecarDriver < StateClient , PrivateKeySigner > {
@@ -224,7 +226,10 @@ impl<C: StateFetcher, ECDSA: SignerECDSA> SidecarDriver<C, ECDSA> {
224226 let ( api_events_tx, api_events_rx) = mpsc:: channel ( 1024 ) ;
225227 CommitmentsApiServer :: new ( api_addr) . run ( api_events_tx) . await ;
226228
229+ let unsafe_skip_consensus_checks = opts. unsafe_disable_consensus_checks ;
230+
227231 Ok ( SidecarDriver {
232+ unsafe_skip_consensus_checks,
228233 head_tracker,
229234 execution,
230235 consensus,
@@ -268,67 +273,83 @@ impl<C: StateFetcher, ECDSA: SignerECDSA> SidecarDriver<C, ECDSA> {
268273
269274 /// Handle an incoming API event, validating the request and responding with a commitment.
270275 async fn handle_incoming_api_event ( & mut self , event : CommitmentEvent ) {
271- let CommitmentEvent { mut request, response } = event;
276+ let CommitmentEvent { request, response } = event;
277+
272278 info ! ( "Received new commitment request: {:?}" , request) ;
273279 ApiMetrics :: increment_inclusion_commitments_received ( ) ;
274280
275281 let start = Instant :: now ( ) ;
276282
277- let validator_pubkey = match self . consensus . validate_request ( & request) {
278- Ok ( pubkey) => pubkey,
279- Err ( err) => {
280- warn ! ( ?err, "Consensus: failed to validate request" ) ;
281- let _ = response. send ( Err ( CommitmentError :: Consensus ( err) ) ) ;
283+ // When we'll add more commitment types, we'll need to match on the request type here.
284+ // For now, we only support inclusion requests so the flow is straightforward.
285+ let CommitmentRequest :: Inclusion ( mut inclusion_request) = request;
286+ let target_slot = inclusion_request. slot ;
287+
288+ let available_pubkeys = self . constraint_signer . available_pubkeys ( ) ;
289+
290+ // Determine the constraint signing public key for this request. Rationale:
291+ // - If we're skipping consensus checks, we can use any available pubkey in the keystore.
292+ // - On regular operation, we need to validate the request against the consensus state to
293+ // determine if the sidecar is the proposer for the given slot. If so, we use the
294+ // validator pubkey or any of its active delegatees to sign constraints.
295+ let signing_pubkey = if self . unsafe_skip_consensus_checks {
296+ // PERF: this is inefficient, but it's only used for testing purposes.
297+ let mut ap = available_pubkeys. iter ( ) . collect :: < Vec < _ > > ( ) ;
298+ ap. sort ( ) ;
299+ ap. first ( ) . cloned ( ) . cloned ( ) . expect ( "at least one available pubkey" )
300+ } else {
301+ let validator_pubkey = match self . consensus . validate_request ( & inclusion_request) {
302+ Ok ( pubkey) => pubkey,
303+ Err ( err) => {
304+ warn ! ( ?err, "Consensus: failed to validate request" ) ;
305+ let _ = response. send ( Err ( CommitmentError :: Consensus ( err) ) ) ;
306+ return ;
307+ }
308+ } ;
309+
310+ // Find a public key to sign new constraints with for this slot.
311+ // This can either be the validator pubkey or a delegatee (if one is available).
312+ let Some ( signing_key) =
313+ self . constraints_client . find_signing_key ( validator_pubkey, available_pubkeys)
314+ else {
315+ error ! ( %target_slot, "No available public key to sign constraints with" ) ;
316+ let _ = response. send ( Err ( CommitmentError :: Internal ) ) ;
282317 return ;
283- }
318+ } ;
319+
320+ signing_key
284321 } ;
285322
286- if let Err ( err) = self . execution . validate_request ( & mut request ) . await {
323+ if let Err ( err) = self . execution . validate_request ( & mut inclusion_request ) . await {
287324 warn ! ( ?err, "Execution: failed to validate request" ) ;
288325 ApiMetrics :: increment_validation_errors ( err. to_tag_str ( ) . to_owned ( ) ) ;
289326 let _ = response. send ( Err ( CommitmentError :: Validation ( err) ) ) ;
290327 return ;
291328 }
292329
293- // When we'll add more commitment types, we'll need to match on the request type here.
294- // For now, we only support inclusion requests so the flow is straightforward.
295- let CommitmentRequest :: Inclusion ( inclusion_request) = request. clone ( ) ;
296- let target_slot = inclusion_request. slot ;
297-
298330 info ! (
299331 target_slot,
300332 elapsed = ?start. elapsed( ) ,
301333 "Validation against execution state passed"
302334 ) ;
303335
304- let available_pubkeys = self . constraint_signer . available_pubkeys ( ) ;
305-
306- // Find a public key to sign new constraints with for this slot.
307- // This can either be the validator pubkey or a delegatee (if one is available).
308- let Some ( signing_pubkey) =
309- self . constraints_client . find_signing_key ( validator_pubkey, available_pubkeys)
310- else {
311- error ! ( %target_slot, "No available public key to sign constraints with" ) ;
312- let _ = response. send ( Err ( CommitmentError :: Internal ) ) ;
313- return ;
314- } ;
315-
316336 // NOTE: we iterate over the transactions in the request and generate a signed constraint
317337 // for each one. This is because the transactions in the commitment request are not supposed
318338 // to be treated as a relative-ordering bundle, but a batch with no ordering guarantees.
319339 //
320340 // For more information, check out the constraints API docs:
321341 // https://docs.boltprotocol.xyz/technical-docs/api/builder#constraints
322- for tx in inclusion_request. txs {
342+ for tx in inclusion_request. txs . iter ( ) {
323343 let tx_type = tx. tx_type ( ) ;
324- let message = ConstraintsMessage :: from_tx ( signing_pubkey. clone ( ) , target_slot, tx) ;
344+ let message =
345+ ConstraintsMessage :: from_tx ( signing_pubkey. clone ( ) , target_slot, tx. clone ( ) ) ;
325346 let digest = message. digest ( ) ;
326347
327348 let signature_result = match & self . constraint_signer {
328349 SignerBLS :: Local ( signer) => signer. sign_commit_boost_root ( digest) ,
329350 SignerBLS :: CommitBoost ( signer) => signer. sign_commit_boost_root ( digest) . await ,
330351 SignerBLS :: Keystore ( signer) => {
331- signer. sign_commit_boost_root ( digest, signing_pubkey. clone ( ) )
352+ signer. sign_commit_boost_root ( digest, & signing_pubkey)
332353 }
333354 } ;
334355
@@ -346,10 +367,10 @@ impl<C: StateFetcher, ECDSA: SignerECDSA> SidecarDriver<C, ECDSA> {
346367 }
347368
348369 // Create a commitment by signing the request
349- match request . commit_and_sign ( & self . commitment_signer ) . await {
370+ match inclusion_request . commit_and_sign ( & self . commitment_signer ) . await {
350371 Ok ( commitment) => {
351372 debug ! ( target_slot, elapsed = ?start. elapsed( ) , "Commitment signed and sent" ) ;
352- response. send ( Ok ( commitment) ) . ok ( )
373+ response. send ( Ok ( SignedCommitment :: Inclusion ( commitment) ) ) . ok ( )
353374 }
354375 Err ( err) => {
355376 error ! ( ?err, "Failed to sign commitment" ) ;
0 commit comments