From bd21309780b7677c797935d63e085073b490dd38 Mon Sep 17 00:00:00 2001 From: File Large Date: Mon, 3 Nov 2025 22:35:21 +0100 Subject: [PATCH 1/3] perf: arc block submission to avoid redundant clones --- Cargo.lock | 1 + .../bid-scraper/src/bloxroute_ws_publisher.rs | 4 +- .../rbuilder-operator/src/blocks_processor.rs | 19 +- .../rbuilder-operator/src/flashbots_config.rs | 4 +- .../best_true_value_observer.rs | 4 +- crates/rbuilder-primitives/src/built_block.rs | 61 +- .../rbuilder-primitives/src/mev_boost/mod.rs | 88 +- .../src/mev_boost/submit_block.rs | 762 +++++++----------- .../rbuilder/benches/benchmarks/mev_boost.rs | 4 +- .../rbuilder/src/bin/debug-bench-machine.rs | 11 +- .../block_output/bidding_service_interface.rs | 6 +- .../live_builder/block_output/relay_submit.rs | 103 ++- .../rbuilder/src/mev_boost/bloxroute_grpc.rs | 214 ++--- crates/rbuilder/src/mev_boost/mod.rs | 9 +- .../rbuilder/src/mev_boost/optimistic_v3.rs | 13 +- crates/rbuilder/src/mev_boost/rpc.rs | 20 +- crates/test-relay/Cargo.toml | 1 + .../test-relay/src/validation_api_client.rs | 14 +- 18 files changed, 532 insertions(+), 806 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f355c3ec..14c65d9f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14582,6 +14582,7 @@ dependencies = [ "alloy-json-rpc", "alloy-primitives 1.4.1", "alloy-provider", + "alloy-rpc-types-beacon", "clap", "clap_builder", "ctor", diff --git a/crates/bid-scraper/src/bloxroute_ws_publisher.rs b/crates/bid-scraper/src/bloxroute_ws_publisher.rs index 8473d8bf6..96e5a9ca1 100644 --- a/crates/bid-scraper/src/bloxroute_ws_publisher.rs +++ b/crates/bid-scraper/src/bloxroute_ws_publisher.rs @@ -22,6 +22,8 @@ use tokio_tungstenite::{ }; use tracing::{debug, error, info}; +pub type BloxrouteWsPublisher = Service; + #[derive(Debug, Clone, Deserialize, PartialEq)] pub struct BloxrouteWsPublisherConfig { /// Url to connect to. Example: "wss://mev-eth.blxrbdn.com/ws" @@ -189,5 +191,3 @@ mod tests { assert!(serde_json::from_str::(raw).is_ok()); } } - -pub type BloxrouteWsPublisher = Service; diff --git a/crates/rbuilder-operator/src/blocks_processor.rs b/crates/rbuilder-operator/src/blocks_processor.rs index 4c2804464..4579846b3 100644 --- a/crates/rbuilder-operator/src/blocks_processor.rs +++ b/crates/rbuilder-operator/src/blocks_processor.rs @@ -1,4 +1,5 @@ use alloy_primitives::{Address, BlockHash, B256, U256}; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use exponential_backoff::Backoff; use jsonrpsee::core::{client::ClientT, traits::ToRpcParams}; use rbuilder::{ @@ -9,7 +10,6 @@ use rbuilder::{ utils::error_storage::store_error_event, }; use rbuilder_primitives::{ - mev_boost::SubmitBlockRequest, serialize::{RawBundle, RawShareBundle}, Bundle, Order, OrderId, }; @@ -135,12 +135,23 @@ impl BlocksProcessorClient { } pub async fn submit_built_block( &self, - submit_block_request: &SubmitBlockRequest, + submit_block_request: &AlloySubmitBlockRequest, built_block_trace: &BuiltBlockTrace, builder_name: String, best_bid_value: U256, ) -> eyre::Result<()> { - let execution_payload_v1 = submit_block_request.execution_payload_v1(); + let execution_payload_v1 = match submit_block_request { + AlloySubmitBlockRequest::Capella(request) => &request.execution_payload.payload_inner, + AlloySubmitBlockRequest::Deneb(request) => { + &request.execution_payload.payload_inner.payload_inner + } + AlloySubmitBlockRequest::Electra(request) => { + &request.execution_payload.payload_inner.payload_inner + } + AlloySubmitBlockRequest::Fulu(request) => { + &request.execution_payload.payload_inner.payload_inner + } + }; let header = BlocksProcessorHeader { hash: execution_payload_v1.block_hash, gas_limit: U256::from(execution_payload_v1.gas_limit), @@ -345,7 +356,7 @@ impl fn block_submitted( &self, _slot_data: &MevBoostSlotData, - submit_block_request: &SubmitBlockRequest, + submit_block_request: &AlloySubmitBlockRequest, built_block_trace: &BuiltBlockTrace, builder_name: String, best_bid_value: U256, diff --git a/crates/rbuilder-operator/src/flashbots_config.rs b/crates/rbuilder-operator/src/flashbots_config.rs index 6363fa399..85fb7a131 100644 --- a/crates/rbuilder-operator/src/flashbots_config.rs +++ b/crates/rbuilder-operator/src/flashbots_config.rs @@ -3,6 +3,7 @@ //! @Pending make this copy/paste generic code on the library use alloy_primitives::U256; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use alloy_signer_local::PrivateKeySigner; use derivative::Derivative; use eyre::Context; @@ -29,7 +30,6 @@ use rbuilder::{ utils::build_info::Version, }; use rbuilder_config::EnvOrValue; -use rbuilder_primitives::mev_boost::SubmitBlockRequest; use serde::Deserialize; use serde_with::serde_as; use tokio_util::sync::CancellationToken; @@ -431,7 +431,7 @@ impl BidObserver for RbuilderOperatorBidObserver { fn block_submitted( &self, slot_data: &MevBoostSlotData, - submit_block_request: &SubmitBlockRequest, + submit_block_request: &AlloySubmitBlockRequest, built_block_trace: &BuiltBlockTrace, builder_name: String, best_bid_value: U256, diff --git a/crates/rbuilder-operator/src/true_block_value_push/best_true_value_observer.rs b/crates/rbuilder-operator/src/true_block_value_push/best_true_value_observer.rs index 705f383a4..fd76d3c62 100644 --- a/crates/rbuilder-operator/src/true_block_value_push/best_true_value_observer.rs +++ b/crates/rbuilder-operator/src/true_block_value_push/best_true_value_observer.rs @@ -1,3 +1,4 @@ +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use alloy_signer_local::PrivateKeySigner; use rbuilder::{ building::BuiltBlockTrace, @@ -5,7 +6,6 @@ use rbuilder::{ block_output::bidding_service_interface::BidObserver, payload_events::MevBoostSlotData, }, }; -use rbuilder_primitives::mev_boost::SubmitBlockRequest; use redis::RedisError; use tokio_util::sync::CancellationToken; @@ -73,7 +73,7 @@ impl BidObserver for BestTrueValueObserver { fn block_submitted( &self, slot_data: &MevBoostSlotData, - _submit_block_request: &SubmitBlockRequest, + _submit_block_request: &AlloySubmitBlockRequest, built_block_trace: &BuiltBlockTrace, builder_name: String, _best_bid_value: alloy_primitives::U256, diff --git a/crates/rbuilder-primitives/src/built_block.rs b/crates/rbuilder-primitives/src/built_block.rs index eff29f8b9..5bcf8390f 100644 --- a/crates/rbuilder-primitives/src/built_block.rs +++ b/crates/rbuilder-primitives/src/built_block.rs @@ -1,7 +1,3 @@ -use crate::mev_boost::{ - BidAdjustmentDataV1, CapellaSubmitBlockRequest, DenebSubmitBlockRequest, - ElectraSubmitBlockRequest, FuluSubmitBlockRequest, SubmitBlockRequest, -}; use alloy_eips::{ eip2718::Encodable2718, eip4844::BlobTransactionSidecar, @@ -10,6 +6,7 @@ use alloy_eips::{ }; use alloy_primitives::{Bytes, U256}; use alloy_rpc_types::Withdrawals; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use alloy_rpc_types_beacon::{ events::PayloadAttributesData, relay::{ @@ -37,42 +34,18 @@ pub struct SignedBuiltBlock { } impl SignedBuiltBlock { - /// Convert the signed block into [`SubmitBlockRequest`]. - /// NOTE: This does not set bid adjustment data. Use [`Self::into_request_with_adjustment_data`] instead. - pub fn into_request(self, chain_spec: &ChainSpec) -> eyre::Result { - self.into_request_inner(chain_spec, None) - } - - /// Convert the signed block into [`SubmitBlockRequest`] with ad - pub fn into_request_with_adjustment_data( - self, - chain_spec: &ChainSpec, - adjustment_data: Option, - ) -> eyre::Result { - self.into_request_inner(chain_spec, adjustment_data) - } - - /// Convert the signed block into [`SubmitBlockRequest`]. - /// NOTE: - fn into_request_inner( - self, - chain_spec: &ChainSpec, - adjustment_data: Option, - ) -> eyre::Result { + /// Convert the signed block into [`SubmitBlockRequest`](`alloy_rpc_types_beacon::relay::SubmitBlockRequest`). + pub fn into_request(self, chain_spec: &ChainSpec) -> eyre::Result { match self.execution_payload { ExecutionPayload::V1(_v1) => { eyre::bail!("v1 payloads are not supported"); } ExecutionPayload::V2(v2) => { - let submission = SignedBidSubmissionV2 { + Ok(AlloySubmitBlockRequest::Capella(SignedBidSubmissionV2 { message: self.message, execution_payload: v2, signature: self.signature, - }; - Ok(SubmitBlockRequest::capella(CapellaSubmitBlockRequest::new( - submission, - adjustment_data, - ))) + })) } ExecutionPayload::V3(v3) => { if chain_spec.is_osaka_active_at_timestamp(v3.timestamp()) { @@ -80,17 +53,13 @@ impl SignedBuiltBlock { self.execution_requests.to_vec(), ))?; let blobs_bundle_v2 = marshall_txs_blobs_sidecars_v2(&self.blob_sidecars); - let submission = SignedBidSubmissionV5 { + return Ok(AlloySubmitBlockRequest::Fulu(SignedBidSubmissionV5 { message: self.message, execution_payload: v3, blobs_bundle: blobs_bundle_v2, signature: self.signature, execution_requests, - }; - return Ok(SubmitBlockRequest::fulu(FuluSubmitBlockRequest::new( - submission, - adjustment_data, - ))); + })); } let blobs_bundle = marshal_txs_blobs_sidecars(&self.blob_sidecars); @@ -98,29 +67,21 @@ impl SignedBuiltBlock { let execution_requests = ExecutionRequestsV4::try_from(Requests::new( self.execution_requests.to_vec(), ))?; - let submission = SignedBidSubmissionV4 { + return Ok(AlloySubmitBlockRequest::Electra(SignedBidSubmissionV4 { message: self.message, execution_payload: v3, blobs_bundle, signature: self.signature, execution_requests, - }; - return Ok(SubmitBlockRequest::electra(ElectraSubmitBlockRequest::new( - submission, - adjustment_data, - ))); + })); } - let submission = SignedBidSubmissionV3 { + Ok(AlloySubmitBlockRequest::Deneb(SignedBidSubmissionV3 { message: self.message, execution_payload: v3, blobs_bundle, signature: self.signature, - }; - Ok(SubmitBlockRequest::deneb(DenebSubmitBlockRequest::new( - submission, - adjustment_data, - ))) + })) } } } diff --git a/crates/rbuilder-primitives/src/mev_boost/mod.rs b/crates/rbuilder-primitives/src/mev_boost/mod.rs index 073a9f6ca..cd6d83275 100644 --- a/crates/rbuilder-primitives/src/mev_boost/mod.rs +++ b/crates/rbuilder-primitives/src/mev_boost/mod.rs @@ -1,13 +1,10 @@ use crate::OrderId; use alloy_primitives::{Address, Bytes, B256, U256}; -use alloy_rpc_types_beacon::{ - relay::BidTrace, requests::ExecutionRequestsV4, BlsPublicKey, BlsSignature, -}; -use alloy_rpc_types_engine::{BlobsBundleV1, BlobsBundleV2, ExecutionPayloadV3}; +use alloy_rpc_types_beacon::BlsPublicKey; use reqwest::Url; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; -use std::{sync::Arc, time::Duration}; +use std::time::Duration; mod submit_block; pub use submit_block::*; @@ -216,85 +213,6 @@ pub struct BidValueMetadata { #[derive(Clone, Debug)] pub struct SubmitBlockRequestWithMetadata { - pub submission: Arc, + pub submission: SubmitBlockRequest, pub metadata: BidMetadata, } - -/// Signed bid submission that is serialized without blobs bundle. -#[derive(Debug)] -pub struct SubmitBlockRequestNoBlobs<'a>(pub &'a SubmitBlockRequest); - -impl serde::Serialize for SubmitBlockRequestNoBlobs<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self.0 { - SubmitBlockRequest::Capella(v2) => v2.serialize(serializer), - SubmitBlockRequest::Deneb(v3) => { - #[derive(serde::Serialize)] - struct SignedBidSubmissionV3Ref<'a> { - message: &'a BidTrace, - #[serde(with = "alloy_rpc_types_beacon::payload::beacon_payload_v3")] - execution_payload: &'a ExecutionPayloadV3, - blobs_bundle: &'a BlobsBundleV1, - signature: &'a BlsSignature, - #[serde(skip_serializing_if = "Option::is_none")] - adjustment_data: &'a Option, - } - - SignedBidSubmissionV3Ref { - message: &v3.message, - execution_payload: &v3.execution_payload, - blobs_bundle: &BlobsBundleV1::new([]), // override blobs bundle with empty one - signature: &v3.signature, - adjustment_data: &v3.adjustment_data, - } - .serialize(serializer) - } - SubmitBlockRequest::Electra(v4) => { - #[derive(serde::Serialize)] - struct SignedBidSubmissionV4Ref<'a> { - message: &'a BidTrace, - #[serde(with = "alloy_rpc_types_beacon::payload::beacon_payload_v3")] - execution_payload: &'a ExecutionPayloadV3, - blobs_bundle: &'a BlobsBundleV1, - execution_requests: &'a ExecutionRequestsV4, - signature: &'a BlsSignature, - #[serde(skip_serializing_if = "Option::is_none")] - adjustment_data: &'a Option, - } - - SignedBidSubmissionV4Ref { - message: &v4.message, - execution_payload: &v4.execution_payload, - blobs_bundle: &BlobsBundleV1::new([]), // override blobs bundle with empty one - signature: &v4.signature, - execution_requests: &v4.execution_requests, - adjustment_data: &v4.adjustment_data, - } - .serialize(serializer) - } - SubmitBlockRequest::Fulu(v5) => { - #[derive(serde::Serialize)] - struct SignedBidSubmissionV5Ref<'a> { - message: &'a BidTrace, - #[serde(with = "alloy_rpc_types_beacon::payload::beacon_payload_v3")] - execution_payload: &'a ExecutionPayloadV3, - blobs_bundle: &'a BlobsBundleV2, - execution_requests: &'a ExecutionRequestsV4, - signature: &'a BlsSignature, - } - - SignedBidSubmissionV5Ref { - message: &v5.message, - execution_payload: &v5.execution_payload, - blobs_bundle: &BlobsBundleV2::new([]), // override blobs bundle with empty one - signature: &v5.signature, - execution_requests: &v5.execution_requests, - } - .serialize(serializer) - } - } - } -} diff --git a/crates/rbuilder-primitives/src/mev_boost/submit_block.rs b/crates/rbuilder-primitives/src/mev_boost/submit_block.rs index 423372023..af47d9fe8 100644 --- a/crates/rbuilder-primitives/src/mev_boost/submit_block.rs +++ b/crates/rbuilder-primitives/src/mev_boost/submit_block.rs @@ -2,7 +2,7 @@ use crate::mev_boost::BidAdjustmentDataV1; use alloy_rpc_types_beacon::{ relay::{ BidTrace, SignedBidSubmissionV2, SignedBidSubmissionV3, SignedBidSubmissionV4, - SignedBidSubmissionV5, + SignedBidSubmissionV5, SubmitBlockRequest as AlloySubmitBlockRequest, }, requests::ExecutionRequestsV4, BlsSignature, @@ -12,217 +12,84 @@ use alloy_rpc_types_engine::{ }; use derive_more::Deref; use serde::{Deserialize, Serialize}; +use std::sync::Arc; -#[derive(Debug, Clone, Serialize, Deserialize, ssz_derive::Encode)] -#[ssz(enum_behaviour = "transparent")] -#[serde(untagged)] -pub enum SubmitBlockRequest { - Fulu(FuluSubmitBlockRequest), - Capella(CapellaSubmitBlockRequest), - Deneb(DenebSubmitBlockRequest), - Electra(ElectraSubmitBlockRequest), -} - -impl SubmitBlockRequest { - #[inline] - pub fn capella(request: CapellaSubmitBlockRequest) -> Self { - Self::Capella(request) - } - - #[inline] - pub fn deneb(request: DenebSubmitBlockRequest) -> Self { - Self::Deneb(request) - } - - #[inline] - pub fn electra(request: ElectraSubmitBlockRequest) -> Self { - Self::Electra(request) - } - - #[inline] - pub fn fulu(request: FuluSubmitBlockRequest) -> Self { - Self::Fulu(request) - } - - pub fn bid_trace(&self) -> &BidTrace { - match self { - SubmitBlockRequest::Capella(req) => &req.message, - SubmitBlockRequest::Deneb(req) => &req.message, - SubmitBlockRequest::Electra(req) => &req.message, - SubmitBlockRequest::Fulu(req) => &req.message, - } - } - - pub fn signature(&self) -> BlsSignature { - match self { - SubmitBlockRequest::Capella(req) => req.signature, - SubmitBlockRequest::Deneb(req) => req.signature, - SubmitBlockRequest::Electra(req) => req.signature, - SubmitBlockRequest::Fulu(req) => req.signature, - } - } - - pub fn execution_payload_v1(&self) -> &ExecutionPayloadV1 { - match self { - SubmitBlockRequest::Capella(req) => &req.execution_payload.payload_inner, - SubmitBlockRequest::Deneb(req) => &req.execution_payload.payload_inner.payload_inner, - SubmitBlockRequest::Electra(req) => &req.execution_payload.payload_inner.payload_inner, - SubmitBlockRequest::Fulu(req) => &req.execution_payload.payload_inner.payload_inner, - } - } - - pub fn execution_payload_v2(&self) -> &ExecutionPayloadV2 { - match self { - SubmitBlockRequest::Capella(req) => &req.execution_payload, - SubmitBlockRequest::Deneb(req) => &req.execution_payload.payload_inner, - SubmitBlockRequest::Electra(req) => &req.execution_payload.payload_inner, - SubmitBlockRequest::Fulu(req) => &req.execution_payload.payload_inner, - } - } - - pub fn execution_payload_v3(&self) -> Option<&ExecutionPayloadV3> { - match self { - SubmitBlockRequest::Capella(_) => None, - SubmitBlockRequest::Deneb(req) => Some(&req.execution_payload), - SubmitBlockRequest::Electra(req) => Some(&req.execution_payload), - SubmitBlockRequest::Fulu(req) => Some(&req.execution_payload), - } - } - - /// Returns `true` if block has adjustment data. - pub fn has_adjustment_data(&self) -> bool { - let maybe_adjustment_data = match self { - SubmitBlockRequest::Capella(req) => &req.adjustment_data, - SubmitBlockRequest::Deneb(req) => &req.adjustment_data, - SubmitBlockRequest::Electra(req) => &req.adjustment_data, - SubmitBlockRequest::Fulu(req) => &req.adjustment_data, - }; - maybe_adjustment_data.is_some() - } - - /// Return mutable reference to bid adjustment data. - fn adjustment_data_mut(&mut self) -> &mut Option { - match self { - Self::Capella(CapellaSubmitBlockRequest { - adjustment_data, .. - }) - | Self::Deneb(DenebSubmitBlockRequest { - adjustment_data, .. - }) - | Self::Electra(ElectraSubmitBlockRequest { - adjustment_data, .. - }) - | Self::Fulu(FuluSubmitBlockRequest { - adjustment_data, .. - }) => adjustment_data, - } - } - - /// Set the bid adjustment data on the request. - pub fn set_adjustment_data(&mut self, data: BidAdjustmentDataV1) { - *self.adjustment_data_mut() = Some(data); - } - - /// Remove adjustment data from the bid. - pub fn remove_adjustment_data(&mut self) { - self.adjustment_data_mut().take(); - } - - /// Remove adjustment data from the bid and return it. - pub fn without_adjustment_data(mut self) -> Self { - self.remove_adjustment_data(); - self - } -} - -impl ssz::Decode for SubmitBlockRequest { - fn is_ssz_fixed_len() -> bool { - false - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - if let Ok(result) = FuluSubmitBlockRequest::from_ssz_bytes(bytes) { - return Ok(Self::fulu(result)); - } - if let Ok(result) = ElectraSubmitBlockRequest::from_ssz_bytes(bytes) { - return Ok(Self::electra(result)); - } - if let Ok(result) = DenebSubmitBlockRequest::from_ssz_bytes(bytes) { - return Ok(Self::deneb(result)); - } - - let result = CapellaSubmitBlockRequest::from_ssz_bytes(bytes)?; - Ok(Self::capella(result)) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Deref)] -pub struct FuluSubmitBlockRequest { - #[deref] - #[serde(flatten)] - pub submission: SignedBidSubmissionV5, - #[serde(skip_serializing_if = "Option::is_none")] - pub adjustment_data: Option, -} - -impl FuluSubmitBlockRequest { - pub fn new( - submission: SignedBidSubmissionV5, - adjustment_data: Option, - ) -> Self { - Self { - submission, - adjustment_data, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Deref)] -pub struct ElectraSubmitBlockRequest { - /// Inner bid submission. +#[derive(Debug, Clone, Serialize, Deserialize, Deref)] +pub struct SubmitBlockRequest { + /// Inner submit block request. #[deref] #[serde(flatten)] - pub submission: SignedBidSubmissionV4, + pub request: Arc, /// Bid adjustment data if present. #[serde(skip_serializing_if = "Option::is_none")] pub adjustment_data: Option, } -impl ElectraSubmitBlockRequest { - /// Create new Electra submit block request. - pub fn new( - submission: SignedBidSubmissionV4, - adjustment_data: Option, - ) -> Self { - Self { - submission, - adjustment_data, - } - } -} - -impl ssz::Encode for FuluSubmitBlockRequest { +impl ssz::Encode for SubmitBlockRequest { fn is_ssz_fixed_len() -> bool { false } fn ssz_append(&self, buf: &mut Vec) { - let mut offset = ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len(); + let mut offset = ::ssz_fixed_len(); if self.adjustment_data.is_some() { offset += ::ssz_fixed_len(); } let mut encoder = ssz::SszEncoder::container(buf, offset); - encoder.append(&self.message); - encoder.append(&self.execution_payload); - encoder.append(&self.blobs_bundle); - encoder.append(&self.execution_requests); - encoder.append(&self.signature); + match self.request.as_ref() { + AlloySubmitBlockRequest::Fulu(request) => { + let SignedBidSubmissionV5 { + message, + execution_payload, + blobs_bundle, + execution_requests, + signature, + } = request; + encoder.append(&message); + encoder.append(&execution_payload); + encoder.append(&blobs_bundle); + encoder.append(&execution_requests); + encoder.append(&signature); + } + AlloySubmitBlockRequest::Electra(request) => { + let SignedBidSubmissionV4 { + message, + execution_payload, + blobs_bundle, + execution_requests, + signature, + } = request; + encoder.append(&message); + encoder.append(&execution_payload); + encoder.append(&blobs_bundle); + encoder.append(&execution_requests); + encoder.append(&signature); + } + AlloySubmitBlockRequest::Deneb(request) => { + let SignedBidSubmissionV3 { + message, + execution_payload, + blobs_bundle, + signature, + } = request; + encoder.append(&message); + encoder.append(&execution_payload); + encoder.append(&blobs_bundle); + encoder.append(&signature); + } + AlloySubmitBlockRequest::Capella(request) => { + let SignedBidSubmissionV2 { + message, + execution_payload, + signature, + } = request; + encoder.append(&message); + encoder.append(&execution_payload); + encoder.append(&signature); + } + }; if let Some(adjustment) = &self.adjustment_data { encoder.append(&adjustment); } @@ -231,11 +98,7 @@ impl ssz::Encode for FuluSubmitBlockRequest { } fn ssz_bytes_len(&self) -> usize { - let mut len = ::ssz_bytes_len(&self.message) - + ::ssz_bytes_len(&self.execution_payload) - + ::ssz_bytes_len(&self.blobs_bundle) - + ::ssz_bytes_len(&self.execution_requests) - + ::ssz_bytes_len(&self.signature); + let mut len = ::ssz_bytes_len(&self.request); if let Some(adjustment) = &self.adjustment_data { len += ::ssz_bytes_len(adjustment); } @@ -243,317 +106,306 @@ impl ssz::Encode for FuluSubmitBlockRequest { } } -impl ssz::Decode for FuluSubmitBlockRequest { +impl ssz::Decode for SubmitBlockRequest { fn is_ssz_fixed_len() -> bool { false } + // A naive implementation of decoding where we attempt to decode each variant with or without adjustments. + // Optimize this if it becomes latency sensitive. fn from_ssz_bytes(bytes: &[u8]) -> Result { - #[derive(ssz_derive::Decode)] - struct FuluSubmitBlockRequestSszHelper { - message: BidTrace, - execution_payload: ExecutionPayloadV3, - blobs_bundle: BlobsBundleV2, - execution_requests: ExecutionRequestsV4, - signature: BlsSignature, - adjustment_data: BidAdjustmentDataV1, + // Fulu (with adjustments) + if let Ok(request) = ssz_helpers::FuluSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) { + return Ok(request.into()); } - if let Ok(request) = FuluSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) { - let FuluSubmitBlockRequestSszHelper { - message, - execution_payload, - blobs_bundle, - execution_requests, - signature, - adjustment_data, - } = request; - let submission = SignedBidSubmissionV5 { - message, - execution_payload, - blobs_bundle, - execution_requests, - signature, - }; - Ok(Self::new(submission, Some(adjustment_data))) - } else { - let submission = SignedBidSubmissionV5::from_ssz_bytes(bytes)?; - Ok(Self::new(submission, None)) + // Electra (with adjustments) + if let Ok(request) = ssz_helpers::ElectraSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) + { + return Ok(request.into()); } - } -} - -impl ssz::Encode for ElectraSubmitBlockRequest { - fn is_ssz_fixed_len() -> bool { - false - } - fn ssz_append(&self, buf: &mut Vec) { - let mut offset = ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len(); - if self.adjustment_data.is_some() { - offset += ::ssz_fixed_len(); + // Deneb (with adjustments) + if let Ok(request) = ssz_helpers::DenebSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) { + return Ok(request.into()); } - let mut encoder = ssz::SszEncoder::container(buf, offset); - - encoder.append(&self.message); - encoder.append(&self.execution_payload); - encoder.append(&self.blobs_bundle); - encoder.append(&self.execution_requests); - encoder.append(&self.signature); - if let Some(adjustment) = &self.adjustment_data { - encoder.append(&adjustment); + // Capella (with adjustments) + if let Ok(request) = ssz_helpers::CapellaSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) + { + return Ok(request.into()); } - encoder.finalize(); - } - - fn ssz_bytes_len(&self) -> usize { - let mut len = ::ssz_bytes_len(&self.message) - + ::ssz_bytes_len(&self.execution_payload) - + ::ssz_bytes_len(&self.blobs_bundle) - + ::ssz_bytes_len(&self.execution_requests) - + ::ssz_bytes_len(&self.signature); - if let Some(adjustment) = &self.adjustment_data { - len += ::ssz_bytes_len(adjustment); - } - len + // Any (no adjustments) + let request = Arc::new(AlloySubmitBlockRequest::from_ssz_bytes(bytes)?); + Ok(Self { + request, + adjustment_data: None, + }) } } -impl ssz::Decode for ElectraSubmitBlockRequest { - fn is_ssz_fixed_len() -> bool { - false - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - #[derive(ssz_derive::Decode)] - struct ElectraSubmitBlockRequestSszHelper { - message: BidTrace, - execution_payload: ExecutionPayloadV3, - blobs_bundle: BlobsBundleV1, - execution_requests: ExecutionRequestsV4, - signature: BlsSignature, - adjustment_data: BidAdjustmentDataV1, - } - - if let Ok(request) = ElectraSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) { - let ElectraSubmitBlockRequestSszHelper { - message, - execution_payload, - blobs_bundle, - execution_requests, - signature, - adjustment_data, - } = request; - let submission = SignedBidSubmissionV4 { - message, - execution_payload, - blobs_bundle, - execution_requests, - signature, - }; - Ok(Self::new(submission, Some(adjustment_data))) - } else { - let submission = SignedBidSubmissionV4::from_ssz_bytes(bytes)?; - Ok(Self::new(submission, None)) +impl SubmitBlockRequest { + pub fn signature(&self) -> BlsSignature { + match self.request.as_ref() { + AlloySubmitBlockRequest::Capella(req) => req.signature, + AlloySubmitBlockRequest::Deneb(req) => req.signature, + AlloySubmitBlockRequest::Electra(req) => req.signature, + AlloySubmitBlockRequest::Fulu(req) => req.signature, } } -} -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Deref)] -pub struct DenebSubmitBlockRequest { - /// Inner bid submission. - #[deref] - #[serde(flatten)] - pub submission: SignedBidSubmissionV3, - /// Bid adjustment data if present. - #[serde(skip_serializing_if = "Option::is_none")] - pub adjustment_data: Option, -} - -impl DenebSubmitBlockRequest { - /// Create new Deneb submit block request. - pub fn new( - submission: SignedBidSubmissionV3, - adjustment_data: Option, - ) -> Self { - Self { - submission, - adjustment_data, + pub fn execution_payload_v1(&self) -> &ExecutionPayloadV1 { + match self.request.as_ref() { + AlloySubmitBlockRequest::Capella(req) => &req.execution_payload.payload_inner, + AlloySubmitBlockRequest::Deneb(req) => { + &req.execution_payload.payload_inner.payload_inner + } + AlloySubmitBlockRequest::Electra(req) => { + &req.execution_payload.payload_inner.payload_inner + } + AlloySubmitBlockRequest::Fulu(req) => { + &req.execution_payload.payload_inner.payload_inner + } } } -} -impl ssz::Encode for DenebSubmitBlockRequest { - fn is_ssz_fixed_len() -> bool { - false - } - - fn ssz_append(&self, buf: &mut Vec) { - let mut offset = ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len(); - if self.adjustment_data.is_some() { - offset += ::ssz_fixed_len(); - } - - let mut encoder = ssz::SszEncoder::container(buf, offset); - - encoder.append(&self.message); - encoder.append(&self.execution_payload); - encoder.append(&self.blobs_bundle); - encoder.append(&self.signature); - if let Some(adjustment) = &self.adjustment_data { - encoder.append(&adjustment); + pub fn execution_payload_v2(&self) -> &ExecutionPayloadV2 { + match self.request.as_ref() { + AlloySubmitBlockRequest::Capella(req) => &req.execution_payload, + AlloySubmitBlockRequest::Deneb(req) => &req.execution_payload.payload_inner, + AlloySubmitBlockRequest::Electra(req) => &req.execution_payload.payload_inner, + AlloySubmitBlockRequest::Fulu(req) => &req.execution_payload.payload_inner, } - - encoder.finalize(); } - fn ssz_bytes_len(&self) -> usize { - let mut len = ::ssz_bytes_len(&self.message) - + ::ssz_bytes_len(&self.execution_payload) - + ::ssz_bytes_len(&self.blobs_bundle) - + ::ssz_bytes_len(&self.signature); - if let Some(adjustment) = &self.adjustment_data { - len += ::ssz_bytes_len(adjustment); + pub fn execution_payload_v3(&self) -> Option<&ExecutionPayloadV3> { + match self.request.as_ref() { + AlloySubmitBlockRequest::Capella(_) => None, + AlloySubmitBlockRequest::Deneb(req) => Some(&req.execution_payload), + AlloySubmitBlockRequest::Electra(req) => Some(&req.execution_payload), + AlloySubmitBlockRequest::Fulu(req) => Some(&req.execution_payload), } - len } -} -impl ssz::Decode for DenebSubmitBlockRequest { - fn is_ssz_fixed_len() -> bool { - false + /// Returns `true` if block has adjustment data. + pub fn has_adjustment_data(&self) -> bool { + self.adjustment_data.is_some() } - fn from_ssz_bytes(bytes: &[u8]) -> Result { - #[derive(ssz_derive::Decode)] - struct DenebSubmitBlockRequestSszHelper { - message: BidTrace, - execution_payload: ExecutionPayloadV3, - blobs_bundle: BlobsBundleV1, - signature: BlsSignature, - adjustment_data: BidAdjustmentDataV1, - } + /// Set the bid adjustment data on the request. + pub fn set_adjustment_data(&mut self, data: BidAdjustmentDataV1) { + self.adjustment_data = Some(data); + } - if let Ok(request) = DenebSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) { - let DenebSubmitBlockRequestSszHelper { - message, - execution_payload, - blobs_bundle, - signature, - adjustment_data, - } = request; - let submission = SignedBidSubmissionV3 { - message, - execution_payload, - blobs_bundle, - signature, - }; - Ok(Self::new(submission, Some(adjustment_data))) - } else { - let submission = SignedBidSubmissionV3::from_ssz_bytes(bytes)?; - Ok(Self::new(submission, None)) - } + /// Remove adjustment data from the bid. + pub fn remove_adjustment_data(&mut self) { + self.adjustment_data.take(); } -} -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Deref)] -pub struct CapellaSubmitBlockRequest { - /// Inner bid submission. - #[deref] - #[serde(flatten)] - pub submission: SignedBidSubmissionV2, - /// Bid adjustment data if present. - #[serde(skip_serializing_if = "Option::is_none")] - pub adjustment_data: Option, + /// Remove adjustment data from the bid and return it. + pub fn without_adjustment_data(mut self) -> Self { + self.remove_adjustment_data(); + self + } } -impl CapellaSubmitBlockRequest { - /// Create new Capella submit block request. - pub fn new( - submission: SignedBidSubmissionV2, - adjustment_data: Option, - ) -> Self { - Self { - submission, - adjustment_data, +/// Signed bid submission that is serialized without blobs bundle. +#[derive(Debug)] +pub struct SubmitBlockRequestNoBlobs<'a>(pub &'a SubmitBlockRequest); + +impl serde::Serialize for SubmitBlockRequestNoBlobs<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self.0.request.as_ref() { + AlloySubmitBlockRequest::Capella(v2) => v2.serialize(serializer), + AlloySubmitBlockRequest::Deneb(v3) => { + #[derive(serde::Serialize)] + struct SignedBidSubmissionV3Ref<'a> { + message: &'a BidTrace, + #[serde(with = "alloy_rpc_types_beacon::payload::beacon_payload_v3")] + execution_payload: &'a ExecutionPayloadV3, + blobs_bundle: &'a BlobsBundleV1, + signature: &'a BlsSignature, + } + + SignedBidSubmissionV3Ref { + message: &v3.message, + execution_payload: &v3.execution_payload, + blobs_bundle: &BlobsBundleV1::new([]), // override blobs bundle with empty one + signature: &v3.signature, + } + .serialize(serializer) + } + AlloySubmitBlockRequest::Electra(v4) => { + #[derive(serde::Serialize)] + struct SignedBidSubmissionV4Ref<'a> { + message: &'a BidTrace, + #[serde(with = "alloy_rpc_types_beacon::payload::beacon_payload_v3")] + execution_payload: &'a ExecutionPayloadV3, + blobs_bundle: &'a BlobsBundleV1, + execution_requests: &'a ExecutionRequestsV4, + signature: &'a BlsSignature, + } + + SignedBidSubmissionV4Ref { + message: &v4.message, + execution_payload: &v4.execution_payload, + blobs_bundle: &BlobsBundleV1::new([]), // override blobs bundle with empty one + signature: &v4.signature, + execution_requests: &v4.execution_requests, + } + .serialize(serializer) + } + AlloySubmitBlockRequest::Fulu(v5) => { + #[derive(serde::Serialize)] + struct SignedBidSubmissionV5Ref<'a> { + message: &'a BidTrace, + #[serde(with = "alloy_rpc_types_beacon::payload::beacon_payload_v3")] + execution_payload: &'a ExecutionPayloadV3, + blobs_bundle: &'a BlobsBundleV2, + execution_requests: &'a ExecutionRequestsV4, + signature: &'a BlsSignature, + } + + SignedBidSubmissionV5Ref { + message: &v5.message, + execution_payload: &v5.execution_payload, + blobs_bundle: &BlobsBundleV2::new([]), // override blobs bundle with empty one + signature: &v5.signature, + execution_requests: &v5.execution_requests, + } + .serialize(serializer) + } } } } -impl ssz::Encode for CapellaSubmitBlockRequest { - fn is_ssz_fixed_len() -> bool { - false - } - - fn ssz_append(&self, buf: &mut Vec) { - let mut offset = ::ssz_fixed_len() - + ::ssz_fixed_len() - + ::ssz_fixed_len(); - if self.adjustment_data.is_some() { - offset += ::ssz_fixed_len(); - } - - let mut encoder = ssz::SszEncoder::container(buf, offset); - encoder.append(&self.message); - encoder.append(&self.execution_payload); - encoder.append(&self.signature); - if let Some(adjustment) = &self.adjustment_data { - encoder.append(&adjustment); - } +mod ssz_helpers { + use super::*; - encoder.finalize(); + #[derive(ssz_derive::Decode)] + pub(crate) struct CapellaSubmitBlockRequestSszHelper { + message: BidTrace, + execution_payload: ExecutionPayloadV2, + signature: BlsSignature, + adjustment_data: BidAdjustmentDataV1, } - fn ssz_bytes_len(&self) -> usize { - let mut len = ::ssz_bytes_len(&self.message) - + ::ssz_bytes_len(&self.execution_payload) - + ::ssz_bytes_len(&self.signature); - if let Some(adjustment) = &self.adjustment_data { - len += ::ssz_bytes_len(adjustment); + impl From for SubmitBlockRequest { + fn from(value: CapellaSubmitBlockRequestSszHelper) -> Self { + let CapellaSubmitBlockRequestSszHelper { + message, + execution_payload, + signature, + adjustment_data, + } = value; + Self { + request: Arc::new(AlloySubmitBlockRequest::Capella(SignedBidSubmissionV2 { + message, + execution_payload, + signature, + })), + adjustment_data: Some(adjustment_data), + } } - len } -} -impl ssz::Decode for CapellaSubmitBlockRequest { - fn is_ssz_fixed_len() -> bool { - false + #[derive(ssz_derive::Decode)] + pub(crate) struct DenebSubmitBlockRequestSszHelper { + message: BidTrace, + execution_payload: ExecutionPayloadV3, + blobs_bundle: BlobsBundleV1, + signature: BlsSignature, + adjustment_data: BidAdjustmentDataV1, } - fn from_ssz_bytes(bytes: &[u8]) -> Result { - #[derive(ssz_derive::Decode)] - struct CapellaSubmitBlockRequestSszHelper { - message: BidTrace, - execution_payload: ExecutionPayloadV2, - signature: BlsSignature, - adjustment_data: BidAdjustmentDataV1, - } - - if let Ok(request) = CapellaSubmitBlockRequestSszHelper::from_ssz_bytes(bytes) { - let CapellaSubmitBlockRequestSszHelper { + impl From for SubmitBlockRequest { + fn from(value: DenebSubmitBlockRequestSszHelper) -> Self { + let DenebSubmitBlockRequestSszHelper { message, execution_payload, + blobs_bundle, signature, adjustment_data, - } = request; - let submission = SignedBidSubmissionV2 { + } = value; + Self { + request: Arc::new(AlloySubmitBlockRequest::Deneb(SignedBidSubmissionV3 { + message, + execution_payload, + blobs_bundle, + signature, + })), + adjustment_data: Some(adjustment_data), + } + } + } + + #[derive(ssz_derive::Decode)] + pub(crate) struct ElectraSubmitBlockRequestSszHelper { + message: BidTrace, + execution_payload: ExecutionPayloadV3, + blobs_bundle: BlobsBundleV1, + execution_requests: ExecutionRequestsV4, + signature: BlsSignature, + adjustment_data: BidAdjustmentDataV1, + } + + impl From for SubmitBlockRequest { + fn from(value: ElectraSubmitBlockRequestSszHelper) -> Self { + let ElectraSubmitBlockRequestSszHelper { message, execution_payload, + blobs_bundle, + execution_requests, signature, - }; - Ok(Self::new(submission, Some(adjustment_data))) - } else { - let submission = SignedBidSubmissionV2::from_ssz_bytes(bytes)?; - Ok(Self::new(submission, None)) + adjustment_data, + } = value; + Self { + request: Arc::new(AlloySubmitBlockRequest::Electra(SignedBidSubmissionV4 { + message, + execution_payload, + blobs_bundle, + execution_requests, + signature, + })), + adjustment_data: Some(adjustment_data), + } + } + } + + #[derive(ssz_derive::Decode)] + pub(crate) struct FuluSubmitBlockRequestSszHelper { + message: BidTrace, + execution_payload: ExecutionPayloadV3, + blobs_bundle: BlobsBundleV2, + execution_requests: ExecutionRequestsV4, + signature: BlsSignature, + adjustment_data: BidAdjustmentDataV1, + } + + impl From for SubmitBlockRequest { + fn from(value: FuluSubmitBlockRequestSszHelper) -> Self { + let FuluSubmitBlockRequestSszHelper { + message, + execution_payload, + blobs_bundle, + execution_requests, + signature, + adjustment_data, + } = value; + Self { + request: Arc::new(AlloySubmitBlockRequest::Fulu(SignedBidSubmissionV5 { + message, + execution_payload, + blobs_bundle, + execution_requests, + signature, + })), + adjustment_data: Some(adjustment_data), + } } } } diff --git a/crates/rbuilder/benches/benchmarks/mev_boost.rs b/crates/rbuilder/benches/benchmarks/mev_boost.rs index ec6266fcd..9a0f93848 100644 --- a/crates/rbuilder/benches/benchmarks/mev_boost.rs +++ b/crates/rbuilder/benches/benchmarks/mev_boost.rs @@ -1,16 +1,16 @@ use alloy_consensus::{Block, Header}; use alloy_eips::{eip4844::BlobTransactionSidecar, eip7594::BlobTransactionSidecarVariant}; use alloy_primitives::U256; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use alloy_rpc_types_beacon::BlsPublicKey; use criterion::{criterion_group, Criterion}; use rbuilder::mev_boost::{rpc::TestDataGenerator, sign_block_for_relay, BLSBlockSigner}; -use rbuilder_primitives::mev_boost::DenebSubmitBlockRequest; use reth::primitives::SealedBlock; use reth_primitives::kzg::Blob; use ssz::Encode; use std::{fs, path::PathBuf, sync::Arc}; -fn mev_boost_serialize_submit_block(data: DenebSubmitBlockRequest) { +fn mev_boost_serialize_submit_block(data: AlloySubmitBlockRequest) { data.as_ssz_bytes(); } diff --git a/crates/rbuilder/src/bin/debug-bench-machine.rs b/crates/rbuilder/src/bin/debug-bench-machine.rs index 979a2a738..2268fa3d0 100644 --- a/crates/rbuilder/src/bin/debug-bench-machine.rs +++ b/crates/rbuilder/src/bin/debug-bench-machine.rs @@ -5,6 +5,7 @@ use alloy_consensus::TxEnvelope; use alloy_eips::Decodable2718; use alloy_primitives::address; use alloy_provider::Provider; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use clap::Parser; use eyre::Context; use itertools::Itertools; @@ -180,11 +181,11 @@ async fn main() -> eyre::Result<()> { fn read_execution_payload_from_json(path: PathBuf) -> eyre::Result { let req = std::fs::read_to_string(&path)?; let req: SubmitBlockRequest = serde_json::from_str(&req)?; - let block_raw = match req { - SubmitBlockRequest::Capella(req) => req.execution_payload.clone().into_block_raw()?, - SubmitBlockRequest::Fulu(req) => req.execution_payload.clone().into_block_raw()?, - SubmitBlockRequest::Deneb(req) => req.execution_payload.clone().into_block_raw()?, - SubmitBlockRequest::Electra(req) => req.execution_payload.clone().into_block_raw()?, + let block_raw = match req.request.as_ref() { + AlloySubmitBlockRequest::Capella(req) => req.execution_payload.clone().into_block_raw()?, + AlloySubmitBlockRequest::Fulu(req) => req.execution_payload.clone().into_block_raw()?, + AlloySubmitBlockRequest::Deneb(req) => req.execution_payload.clone().into_block_raw()?, + AlloySubmitBlockRequest::Electra(req) => req.execution_payload.clone().into_block_raw()?, }; let rpc_block = alloy_rpc_types::Block::from_consensus(block_raw, None); let rpc_block = rpc_block.try_map_transactions(|bytes| -> eyre::Result<_> { diff --git a/crates/rbuilder/src/live_builder/block_output/bidding_service_interface.rs b/crates/rbuilder/src/live_builder/block_output/bidding_service_interface.rs index b074d55c2..f559e46f7 100644 --- a/crates/rbuilder/src/live_builder/block_output/bidding_service_interface.rs +++ b/crates/rbuilder/src/live_builder/block_output/bidding_service_interface.rs @@ -1,13 +1,13 @@ use std::sync::Arc; use alloy_primitives::{BlockHash, BlockNumber, I256, U256}; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use bid_scraper::{ bid_sender::{BidSender, BidSenderError}, types::ScrapedRelayBlockBid, }; use derivative::Derivative; use mockall::automock; -use rbuilder_primitives::mev_boost::SubmitBlockRequest; use time::OffsetDateTime; use tokio_util::sync::CancellationToken; @@ -27,7 +27,7 @@ pub trait BidObserver: std::fmt::Debug { fn block_submitted( &self, slot_data: &MevBoostSlotData, - submit_block_request: &SubmitBlockRequest, + submit_block_request: &AlloySubmitBlockRequest, built_block_trace: &BuiltBlockTrace, builder_name: String, best_bid_value: U256, @@ -41,7 +41,7 @@ impl BidObserver for NullBidObserver { fn block_submitted( &self, _slot_data: &MevBoostSlotData, - _submit_block_request: &SubmitBlockRequest, + _submit_block_request: &AlloySubmitBlockRequest, _built_block_trace: &BuiltBlockTrace, _builder_name: String, _best_bid_value: U256, diff --git a/crates/rbuilder/src/live_builder/block_output/relay_submit.rs b/crates/rbuilder/src/live_builder/block_output/relay_submit.rs index 9a52f3676..72c5bdb53 100644 --- a/crates/rbuilder/src/live_builder/block_output/relay_submit.rs +++ b/crates/rbuilder/src/live_builder/block_output/relay_submit.rs @@ -15,6 +15,7 @@ use crate::{ }; use ahash::HashMap; use alloy_primitives::{utils::format_ether, Address, U256}; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use alloy_rpc_types_engine::ExecutionPayload; use futures::FutureExt as _; use mockall::automock; @@ -114,7 +115,7 @@ pub struct OptimisticV3Config { /// The URL where the relay can call to retrieve the block. pub builder_url: Vec, /// Sender for Optimistic V3 blocks. - pub block_sender: broadcast::Sender>, + pub block_sender: broadcast::Sender>, } /// Values from [`BuiltBlockTrace`] @@ -147,7 +148,7 @@ async fn run_submit_to_relays_job( 'submit: loop { tokio::select! { _ = cancel.cancelled() => { - info!( block = slot_data.block(), "run_submit_to_relays_job cancelled"); + info!(block = slot_data.block(), "run_submit_to_relays_job cancelled"); break 'submit res; }, _ = pending_bid.wait_for_change() => {} @@ -289,7 +290,7 @@ async fn run_submit_to_relays_job( mark_submission_start_time(block.trace.orders_sealed_at); if let Some(request) = ®ular_request { submit_block_to_relays( - request, + request.clone(), &bid_metadata, &block.bid_adjustments, ®ular_relays, @@ -307,7 +308,7 @@ async fn run_submit_to_relays_job( .or(regular_request.map(|req| (req, false))); if let Some((request, optimistic)) = optimistic_request { submit_block_to_relays( - &request, + request.clone(), &bid_metadata, &block.bid_adjustments, &optimistic_relays, @@ -339,7 +340,7 @@ fn create_submit_block_request( slot_data: &MevBoostSlotData, block: &Block, execution_payload: &ExecutionPayload, -) -> eyre::Result { +) -> eyre::Result> { let (message, signature) = sign_block_for_relay( signer, &block.sealed_block, @@ -355,11 +356,12 @@ fn create_submit_block_request( execution_requests: block.execution_requests.clone(), } .into_request(chain_spec) + .map(Arc::new) } fn create_optimistic_v3_request( builder_url: &[u8], - request: &SubmitBlockRequest, + request: &AlloySubmitBlockRequest, maybe_adjustment_data: Option<&BidAdjustmentData>, adjustment_data_required: bool, ) -> eyre::Result { @@ -368,44 +370,62 @@ fn create_optimistic_v3_request( eyre::bail!("adjustment data is required") } - let header_submission = match request { - SubmitBlockRequest::Electra(request) => { - let header = ExecutionPayloadHeaderElectra::from(&request.execution_payload); - HeaderSubmission::Electra(HeaderSubmissionElectra { - bid_trace: request.message.clone(), - execution_payload_header: header, - execution_requests: request.execution_requests.clone(), - commitments: request.blobs_bundle.commitments.clone(), - adjustment_data: maybe_adjustment_data_v2, - }) + let (submission, tx_count) = match &request { + AlloySubmitBlockRequest::Electra(request) => { + let tx_count = request + .execution_payload + .payload_inner + .payload_inner + .transactions + .len(); + let submission = SignedHeaderSubmission { + message: HeaderSubmission::Electra(HeaderSubmissionElectra { + bid_trace: request.message.clone(), + execution_payload_header: ExecutionPayloadHeaderElectra::from( + &request.execution_payload, + ), + execution_requests: request.execution_requests.clone(), + commitments: request.blobs_bundle.commitments.clone(), + adjustment_data: maybe_adjustment_data_v2, + }), + signature: request.signature, + }; + (submission, tx_count) } - SubmitBlockRequest::Fulu(request) => { - let header = ExecutionPayloadHeaderElectra::from(&request.execution_payload); - HeaderSubmission::Fulu(HeaderSubmissionElectra { - bid_trace: request.message.clone(), - execution_payload_header: header, - execution_requests: request.execution_requests.clone(), - commitments: request.blobs_bundle.commitments.clone(), - adjustment_data: maybe_adjustment_data_v2, - }) + AlloySubmitBlockRequest::Fulu(request) => { + let tx_count = request + .execution_payload + .payload_inner + .payload_inner + .transactions + .len(); + let submission = SignedHeaderSubmission { + message: HeaderSubmission::Fulu(HeaderSubmissionElectra { + bid_trace: request.message.clone(), + execution_payload_header: ExecutionPayloadHeaderElectra::from( + &request.execution_payload, + ), + execution_requests: request.execution_requests.clone(), + commitments: request.blobs_bundle.commitments.clone(), + adjustment_data: maybe_adjustment_data_v2, + }), + signature: request.signature, + }; + (submission, tx_count) } _ => eyre::bail!("optimistic v3 submission is not supported for this fork"), }; - let tx_count = request.execution_payload_v1().transactions.len(); Ok(HeaderSubmissionOptimisticV3 { url: builder_url.to_vec(), tx_count: tx_count as u32, - submission: SignedHeaderSubmission { - message: header_submission, - signature: request.signature(), - }, + submission, }) } #[allow(clippy::too_many_arguments)] fn submit_block_to_relays( - request: &SubmitBlockRequest, + request: Arc, bid_metadata: &BidMetadata, bid_adjustments: &std::collections::HashMap, relays: &Vec, @@ -440,7 +460,7 @@ fn submit_block_to_relays( if let Some(config) = optimistic_v3_config { optimistic_v3 = create_optimistic_v3_request( &config.builder_url, - request, + request.as_ref(), maybe_adjustment_data, relay.optimistic_v3_bid_adjustment_required(), ) @@ -452,16 +472,15 @@ fn submit_block_to_relays( } } - let mut request = request.clone(); - - // We only set adjustment data on non optimistic v3 submissions. - // For optimistic v3, it is already included in the header submission. - if let Some(adjustment_data) = maybe_adjustment_data.filter(|_| optimistic_v3.is_none()) { - request.set_adjustment_data(adjustment_data.clone().into_v1()); - } - let submission = SubmitBlockRequestWithMetadata { - submission: Arc::new(request), + submission: SubmitBlockRequest { + request: request.clone(), + // We only set adjustment data on non optimistic v3 submissions. + // For optimistic v3, it is already included in the header submission. + adjustment_data: maybe_adjustment_data + .filter(|_| optimistic_v3.is_none()) + .map(|adjustment_data| adjustment_data.clone().into_v1()), + }, metadata: bid_metadata.clone(), }; @@ -505,7 +524,7 @@ async fn submit_bid_to_the_relay( // Send the block to be saved in cache let _ = config .block_sender - .send(submit_block_request.submission.clone()); + .send(submit_block_request.submission.request.clone()); relay .submit_optimistic_v3(request, registration) .left_future() diff --git a/crates/rbuilder/src/mev_boost/bloxroute_grpc.rs b/crates/rbuilder/src/mev_boost/bloxroute_grpc.rs index a746980a8..a1a238ede 100644 --- a/crates/rbuilder/src/mev_boost/bloxroute_grpc.rs +++ b/crates/rbuilder/src/mev_boost/bloxroute_grpc.rs @@ -5,15 +5,12 @@ use alloy_eips::{ use alloy_rpc_types_beacon::{ relay::{ BidTrace, SignedBidSubmissionV2, SignedBidSubmissionV3, SignedBidSubmissionV4, - SignedBidSubmissionV5, + SignedBidSubmissionV5, SubmitBlockRequest as AlloySubmitBlockRequest, }, requests::ExecutionRequestsV4, }; use alloy_rpc_types_engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}; -use rbuilder_primitives::mev_boost::{ - CapellaSubmitBlockRequest, DenebSubmitBlockRequest, ElectraSubmitBlockRequest, - FuluSubmitBlockRequest, SubmitBlockRequest, -}; +use rbuilder_primitives::mev_boost::SubmitBlockRequest; use std::sync::Arc; /// Bloxroute gRPC types. @@ -43,131 +40,98 @@ pub type GrpcRelayClient = impl From<&SubmitBlockRequest> for types::SubmitBlockRequest { fn from(value: &SubmitBlockRequest) -> Self { - let ( - version, - execution_payload, - bid_trace, - signature, - blobs_bundle, - execution_requests, - adjustment_data, - ) = match value { - SubmitBlockRequest::Capella(request) => { - let CapellaSubmitBlockRequest { - submission: - SignedBidSubmissionV2 { - execution_payload, - message, - signature, - }, - adjustment_data, - } = request; + let (version, execution_payload, bid_trace, signature, blobs_bundle, execution_requests) = + match value.request.as_ref() { + AlloySubmitBlockRequest::Capella(request) => { + let SignedBidSubmissionV2 { + execution_payload, + message, + signature, + } = request; - let version = DataVersion::Capella; - let execution_payload = types::ExecutionPayload::from_v2(execution_payload); - let bid_trace = types::BidTrace::new(message, None, None); - ( - version, - execution_payload, - bid_trace, - signature, - None, - None, - adjustment_data, - ) - } - SubmitBlockRequest::Deneb(request) => { - let DenebSubmitBlockRequest { - submission: - SignedBidSubmissionV3 { - execution_payload, - message, - signature, - blobs_bundle, - }, - adjustment_data, - } = request; + let version = DataVersion::Capella; + let execution_payload = types::ExecutionPayload::from_v2(execution_payload); + let bid_trace = types::BidTrace::new(message, None, None); + (version, execution_payload, bid_trace, signature, None, None) + } + AlloySubmitBlockRequest::Deneb(request) => { + let SignedBidSubmissionV3 { + execution_payload, + message, + signature, + blobs_bundle, + } = request; - let version = DataVersion::Deneb; - let execution_payload = types::ExecutionPayload::from_v3(execution_payload); - let bid_trace = types::BidTrace::new( - message, - Some(execution_payload.blob_gas_used), - Some(execution_payload.excess_blob_gas), - ); - ( - version, - execution_payload, - bid_trace, - signature, - Some(types::BlobsBundle::from(blobs_bundle)), - None, - adjustment_data, - ) - } - SubmitBlockRequest::Electra(request) => { - let ElectraSubmitBlockRequest { - submission: - SignedBidSubmissionV4 { - execution_payload, - message, - signature, - blobs_bundle, - execution_requests, - }, - adjustment_data, - } = request; + let version = DataVersion::Deneb; + let execution_payload = types::ExecutionPayload::from_v3(execution_payload); + let bid_trace = types::BidTrace::new( + message, + Some(execution_payload.blob_gas_used), + Some(execution_payload.excess_blob_gas), + ); + ( + version, + execution_payload, + bid_trace, + signature, + Some(types::BlobsBundle::from(blobs_bundle)), + None, + ) + } + AlloySubmitBlockRequest::Electra(request) => { + let SignedBidSubmissionV4 { + execution_payload, + message, + signature, + blobs_bundle, + execution_requests, + } = request; - let version = DataVersion::Electra; - let execution_payload = types::ExecutionPayload::from_v3(execution_payload); - let bid_trace = types::BidTrace::new( - message, - Some(execution_payload.blob_gas_used), - Some(execution_payload.excess_blob_gas), - ); - ( - version, - execution_payload, - bid_trace, - signature, - Some(types::BlobsBundle::from(blobs_bundle)), - Some(execution_requests), - adjustment_data, - ) - } - SubmitBlockRequest::Fulu(request) => { - let FuluSubmitBlockRequest { - submission: - SignedBidSubmissionV5 { - execution_payload, - message, - signature, - blobs_bundle, - execution_requests, - }, - adjustment_data, - } = request; + let version = DataVersion::Electra; + let execution_payload = types::ExecutionPayload::from_v3(execution_payload); + let bid_trace = types::BidTrace::new( + message, + Some(execution_payload.blob_gas_used), + Some(execution_payload.excess_blob_gas), + ); + ( + version, + execution_payload, + bid_trace, + signature, + Some(types::BlobsBundle::from(blobs_bundle)), + Some(execution_requests), + ) + } + AlloySubmitBlockRequest::Fulu(request) => { + let SignedBidSubmissionV5 { + execution_payload, + message, + signature, + blobs_bundle, + execution_requests, + } = request; - let version = DataVersion::Electra; - let execution_payload = types::ExecutionPayload::from_v3(execution_payload); - let bid_trace = types::BidTrace::new( - message, - Some(execution_payload.blob_gas_used), - Some(execution_payload.excess_blob_gas), - ); - ( - version, - execution_payload, - bid_trace, - signature, - Some(types::BlobsBundle::from(blobs_bundle)), - Some(execution_requests), - adjustment_data, - ) - } - }; + let version = DataVersion::Electra; + let execution_payload = types::ExecutionPayload::from_v3(execution_payload); + let bid_trace = types::BidTrace::new( + message, + Some(execution_payload.blob_gas_used), + Some(execution_payload.excess_blob_gas), + ); + ( + version, + execution_payload, + bid_trace, + signature, + Some(types::BlobsBundle::from(blobs_bundle)), + Some(execution_requests), + ) + } + }; let execution_requests = execution_requests.map(types::ExecutionRequests::from); - let adjustment_data = adjustment_data + let adjustment_data = value + .adjustment_data .as_ref() .map(ssz::Encode::as_ssz_bytes) .unwrap_or_default(); diff --git a/crates/rbuilder/src/mev_boost/mod.rs b/crates/rbuilder/src/mev_boost/mod.rs index 0b418bc41..fe4daf053 100644 --- a/crates/rbuilder/src/mev_boost/mod.rs +++ b/crates/rbuilder/src/mev_boost/mod.rs @@ -798,7 +798,7 @@ impl RelayClient { submission: &SubmitBlockRequestWithMetadata, ) -> Result { let mut request = tonic::Request::new(bloxroute_grpc::types::SubmitBlockRequest::from( - submission.submission.as_ref(), + &submission.submission, )); request.set_timeout(Duration::from_secs(2)); request.metadata_mut().insert( @@ -1256,9 +1256,10 @@ mod tests { let relay_url = Url::from_str(&srv.endpoint()).unwrap(); let relay = RelayClient::from_url(relay_url, None, None, None, false, Vec::new(), false, false); - let submission = Arc::new(SubmitBlockRequest::Deneb( - generator.create_deneb_submit_block_request(), - )); + let submission = Arc::new(SubmitBlockRequest { + request: generator.create_deneb_submit_block_request(), + adjustment_data: None, + }); let sub_relay = SubmitBlockRequestWithMetadata { submission, metadata: BidMetadata { diff --git a/crates/rbuilder/src/mev_boost/optimistic_v3.rs b/crates/rbuilder/src/mev_boost/optimistic_v3.rs index 53f391b7b..06607e52c 100644 --- a/crates/rbuilder/src/mev_boost/optimistic_v3.rs +++ b/crates/rbuilder/src/mev_boost/optimistic_v3.rs @@ -3,6 +3,7 @@ use crate::{ utils, }; use alloy_primitives::{bytes::Bytes, B256}; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use alloy_rpc_types_beacon::BlsPublicKey; use ctor::ctor; use futures::StreamExt as _; @@ -10,9 +11,7 @@ use lazy_static::lazy_static; use metrics_macros::register_metrics; use parking_lot::Mutex; use prometheus::{HistogramOpts, HistogramVec, IntCounter}; -use rbuilder_primitives::mev_boost::{ - verify_signed_relay_request, SignedGetPayloadV3, SubmitBlockRequest, -}; +use rbuilder_primitives::mev_boost::{verify_signed_relay_request, SignedGetPayloadV3}; use schnellru::{ByLength, LruMap}; use ssz::{Decode as _, Encode}; use std::{ @@ -65,7 +64,7 @@ pub fn spawn_server( address: impl Into, domain: B256, relay_pubkeys: HashSet, - bid_stream: BroadcastStream>, + bid_stream: BroadcastStream>, ) -> eyre::Result<()> { let blocks = Arc::new(Mutex::new(LruMap::new(ByLength::new( OPTIMISTIC_V3_CACHE_SIZE_DEFAULT, @@ -103,7 +102,7 @@ pub fn spawn_server( struct Handler { domain: B256, relay_pubkeys: HashSet, - blocks: Arc>>>, + blocks: Arc>>>, } impl Handler { @@ -199,8 +198,8 @@ impl Handler { } async fn maintain_block_cache( - mut bid_stream: BroadcastStream>, - blocks: Arc>>>, + mut bid_stream: BroadcastStream>, + blocks: Arc>>>, ) { loop { match bid_stream.next().await { diff --git a/crates/rbuilder/src/mev_boost/rpc.rs b/crates/rbuilder/src/mev_boost/rpc.rs index c61c2271c..c4cbcde84 100644 --- a/crates/rbuilder/src/mev_boost/rpc.rs +++ b/crates/rbuilder/src/mev_boost/rpc.rs @@ -1,5 +1,6 @@ use alloy_consensus::{Blob, Bytes48}; use alloy_primitives::{Address, Bloom, Bytes, B256, U256}; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use alloy_rpc_types_beacon::{ events::PayloadAttributesData, relay::{BidTrace, SignedBidSubmissionV3}, @@ -9,8 +10,8 @@ use alloy_rpc_types_engine::{ BlobsBundleV1, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, }; use alloy_rpc_types_eth::Withdrawal; -use rbuilder_primitives::mev_boost::DenebSubmitBlockRequest; use reth::rpc::types::engine::PayloadAttributes; + /// TestDataGenerator allows you to create unique test objects with unique content, it tries to use different numbers for every field it sets #[derive(Default)] pub struct TestDataGenerator { @@ -18,16 +19,13 @@ pub struct TestDataGenerator { } impl TestDataGenerator { - pub fn create_deneb_submit_block_request(&mut self) -> DenebSubmitBlockRequest { - DenebSubmitBlockRequest { - submission: SignedBidSubmissionV3 { - message: self.create_bid_trace(), - execution_payload: self.create_deneb_payload(), - blobs_bundle: self.create_txs_blobs_sidecars(), - signature: self.create_signature(), - }, - adjustment_data: None, - } + pub fn create_deneb_submit_block_request(&mut self) -> AlloySubmitBlockRequest { + AlloySubmitBlockRequest::Deneb(SignedBidSubmissionV3 { + message: self.create_bid_trace(), + execution_payload: self.create_deneb_payload(), + blobs_bundle: self.create_txs_blobs_sidecars(), + signature: self.create_signature(), + }) } pub fn create_txs_blobs_sidecars(&mut self) -> BlobsBundleV1 { diff --git a/crates/test-relay/Cargo.toml b/crates/test-relay/Cargo.toml index 3b63166a5..07373fee3 100644 --- a/crates/test-relay/Cargo.toml +++ b/crates/test-relay/Cargo.toml @@ -22,6 +22,7 @@ alloy-json-rpc.workspace = true alloy-primitives.workspace = true alloy-provider.workspace = true alloy-consensus.workspace = true +alloy-rpc-types-beacon.workspace = true serde.workspace = true serde_with.workspace = true serde_json.workspace = true diff --git a/crates/test-relay/src/validation_api_client.rs b/crates/test-relay/src/validation_api_client.rs index 43b6372b1..2950886a3 100644 --- a/crates/test-relay/src/validation_api_client.rs +++ b/crates/test-relay/src/validation_api_client.rs @@ -1,12 +1,12 @@ use alloy_json_rpc::{ErrorPayload, RpcError}; -use std::{fmt::Debug, sync::Arc}; - use alloy_primitives::B256; use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_types_beacon::relay::SubmitBlockRequest as AlloySubmitBlockRequest; use rbuilder::utils::http_provider; use rbuilder_primitives::mev_boost::SubmitBlockRequest; use serde::Serialize; use serde_with::{serde_as, DisplayFromStr}; +use std::{fmt::Debug, sync::Arc}; use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; use tracing::{error, info_span, warn}; @@ -69,11 +69,11 @@ impl ValidationAPIClient { return Err(ValidationError::NoValidationNodes); } - let method = match req { - SubmitBlockRequest::Capella(_) => "flashbots_validateBuilderSubmissionV2", - SubmitBlockRequest::Deneb(_) => "flashbots_validateBuilderSubmissionV3", - SubmitBlockRequest::Electra(_) => "flashbots_validateBuilderSubmissionV4", - SubmitBlockRequest::Fulu(_) => "flashbots_validateBuilderSubmissionV5", + let method = match req.request.as_ref() { + AlloySubmitBlockRequest::Capella(_) => "flashbots_validateBuilderSubmissionV2", + AlloySubmitBlockRequest::Deneb(_) => "flashbots_validateBuilderSubmissionV3", + AlloySubmitBlockRequest::Electra(_) => "flashbots_validateBuilderSubmissionV4", + AlloySubmitBlockRequest::Fulu(_) => "flashbots_validateBuilderSubmissionV5", }; let request = ValidRequest { req: req.clone(), From 2c445928d0c68c96482fddd1c5c3a186dc155211 Mon Sep 17 00:00:00 2001 From: File Large Date: Mon, 3 Nov 2025 22:38:06 +0100 Subject: [PATCH 2/3] fix test --- crates/rbuilder/src/mev_boost/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rbuilder/src/mev_boost/mod.rs b/crates/rbuilder/src/mev_boost/mod.rs index fe4daf053..27e083770 100644 --- a/crates/rbuilder/src/mev_boost/mod.rs +++ b/crates/rbuilder/src/mev_boost/mod.rs @@ -1256,10 +1256,10 @@ mod tests { let relay_url = Url::from_str(&srv.endpoint()).unwrap(); let relay = RelayClient::from_url(relay_url, None, None, None, false, Vec::new(), false, false); - let submission = Arc::new(SubmitBlockRequest { - request: generator.create_deneb_submit_block_request(), + let submission = SubmitBlockRequest { + request: Arc::new(generator.create_deneb_submit_block_request()), adjustment_data: None, - }); + }; let sub_relay = SubmitBlockRequestWithMetadata { submission, metadata: BidMetadata { From 8e4335fcfc7a45e753c154a5216a91b48bb0a12b Mon Sep 17 00:00:00 2001 From: File Large Date: Tue, 4 Nov 2025 15:13:43 +0100 Subject: [PATCH 3/3] fix offset calculation --- .../src/mev_boost/submit_block.rs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/rbuilder-primitives/src/mev_boost/submit_block.rs b/crates/rbuilder-primitives/src/mev_boost/submit_block.rs index af47d9fe8..dabc76eaa 100644 --- a/crates/rbuilder-primitives/src/mev_boost/submit_block.rs +++ b/crates/rbuilder-primitives/src/mev_boost/submit_block.rs @@ -31,13 +31,35 @@ impl ssz::Encode for SubmitBlockRequest { } fn ssz_append(&self, buf: &mut Vec) { - let mut offset = ::ssz_fixed_len(); + // Every request contains bid trace and signature. + let mut offset = ::ssz_fixed_len() + + ::ssz_fixed_len(); + // Amend offset with fork specific fields. + offset += match &self.request.as_ref() { + AlloySubmitBlockRequest::Fulu(_) => { + ::ssz_fixed_len() + + ::ssz_fixed_len() + + ::ssz_fixed_len() + } + AlloySubmitBlockRequest::Electra(_) => { + ::ssz_fixed_len() + + ::ssz_fixed_len() + + ::ssz_fixed_len() + } + AlloySubmitBlockRequest::Deneb(_) => { + ::ssz_fixed_len() + + ::ssz_fixed_len() + } + AlloySubmitBlockRequest::Capella(_) => { + ::ssz_fixed_len() + } + }; + // Add adjustment data offset if present. if self.adjustment_data.is_some() { offset += ::ssz_fixed_len(); } let mut encoder = ssz::SszEncoder::container(buf, offset); - match self.request.as_ref() { AlloySubmitBlockRequest::Fulu(request) => { let SignedBidSubmissionV5 {