Skip to content
Open
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
114 changes: 7 additions & 107 deletions crates/rbuilder-primitives/src/mev_boost/adjustment.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,5 @@
use alloy_primitives::{Address, Bloom, Bytes, B256};

/// The type representing UltraSound bid adjustments.
#[derive(
PartialEq,
Eq,
Clone,
Debug,
serde::Serialize,
serde::Deserialize,
ssz_derive::Encode,
ssz_derive::Decode,
)]
pub struct BidAdjustmentDataV1 {
/// State root of the payload.
pub state_root: B256,
/// Transactions root of the payload.
pub transactions_root: B256,
/// Receipts root of the payload.
pub receipts_root: B256,
/// The usual builder address that pays the proposer in the last transaction of the block.
/// When we adjust a bid, this transaction is overwritten by a transaction from the collateral
/// account `fee_payer_address`. If we don't adjust the bid, `builder_address` pays the
/// proposer as per usual.
pub builder_address: Address,
/// The state proof for the builder account.
pub builder_proof: Vec<Bytes>,
/// The proposer's fee recipient.
pub fee_recipient_address: Address,
/// The state proof for the fee recipient account.
pub fee_recipient_proof: Vec<Bytes>,
/// The fee payer address that is custodied by the relay.
pub fee_payer_address: Address,
/// The state proof for the fee payer account.
pub fee_payer_proof: Vec<Bytes>,
/// The merkle proof for the last transaction in the block, which will be overwritten with a
/// payment from `fee_payer` to `fee_recipient` if we adjust the bid.
pub placeholder_transaction_proof: Vec<Bytes>,
/// The merkle proof for the receipt of the placeholder transaction. It's required for
/// adjusting payments to contract addresses.
pub placeholder_receipt_proof: Vec<Bytes>,
}

/// The type for bid adjustments in optimistic v3.
/// Ref: <https://github.com/ultrasoundmoney/docs/blob/main/optimistic-v3.md#optimistic-v3>
#[derive(
Expand All @@ -53,7 +12,7 @@ pub struct BidAdjustmentDataV1 {
ssz_derive::Encode,
ssz_derive::Decode,
)]
pub struct BidAdjustmentDataV2 {
pub struct BidAdjustmentDataV3 {
/// Transactions root of the payload.
pub el_transactions_root: B256,
/// Withdrawals root of the payload.
Expand All @@ -76,75 +35,16 @@ pub struct BidAdjustmentDataV2 {
/// The merkle proof for the last transaction in the block, which will be overwritten with a
/// payment from `fee_payer` to `fee_recipient` if we adjust the bid.
pub el_placeholder_transaction_proof: Vec<Bytes>,
/// New in V2: SSZ merkle proof for last transaction
pub cl_placeholder_transaction_proof: Vec<B256>,
/// The merkle proof for the receipt of the placeholder transaction. It's required for
/// adjusting payments to contract addresses.
pub placeholder_receipt_proof: Vec<Bytes>,
/// New in V2: Logs bloom accrued until but not including the last (payment) transaction.
pub pre_payment_logs_bloom: Bloom,
}

/// Common bid adjustment information that can be used for creating bid adjustment data.
#[derive(Clone, Debug)]
pub struct BidAdjustmentData {
/// State root of the payload.
pub state_root: B256,
/// Transactions root of the payload.
pub el_transactions_root: B256,
/// Withdrawals root of the payload.
pub el_withdrawals_root: B256,
/// Receipts root of the payload.
pub receipts_root: B256,
/// The merkle proof for the last transaction in the block, which will be overwritten with a
/// payment from `fee_payer` to `fee_recipient` if we adjust the bid.
pub el_placeholder_transaction_proof: Vec<Bytes>,
/// New in V2: SSZ merkle proof for last transaction
/// SSZ merkle proof for last transaction
pub cl_placeholder_transaction_proof: Vec<B256>,
/// The merkle proof for the receipt of the placeholder transaction. It's required for
/// adjusting payments to contract addresses.
pub placeholder_receipt_proof: Vec<Bytes>,
/// New in V2: Logs bloom accrued until but not including the last (payment) transaction.
pub el_placeholder_receipt_proof: Vec<Bytes>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field naming change

The field was renamed from placeholder_receipt_proof to el_placeholder_receipt_proof. While this naming is more consistent with the EL/CL prefix convention used elsewhere in V3, ensure this matches the exact field name expected by the relay API. The UltraSound docs should be the source of truth for field names.

The el_ prefix makes sense as this is an execution layer proof (vs consensus layer), similar to el_transactions_root and cl_placeholder_transaction_proof.

/// Logs bloom accrued until but not including the last (payment) transaction.
pub pre_payment_logs_bloom: Bloom,
/// State proofs.
pub state_proofs: BidAdjustmentStateProofs,
}

impl BidAdjustmentData {
/// Convert bid adjustment data into [`BidAdjustmentDataV1`].
pub fn into_v1(self) -> BidAdjustmentDataV1 {
BidAdjustmentDataV1 {
state_root: self.state_root,
transactions_root: self.el_transactions_root,
receipts_root: self.receipts_root,
builder_address: self.state_proofs.builder_address,
builder_proof: self.state_proofs.builder_proof,
fee_recipient_address: self.state_proofs.fee_recipient_address,
fee_recipient_proof: self.state_proofs.fee_recipient_proof,
fee_payer_address: self.state_proofs.fee_payer_address,
fee_payer_proof: self.state_proofs.fee_payer_proof,
placeholder_transaction_proof: self.el_placeholder_transaction_proof,
placeholder_receipt_proof: self.placeholder_receipt_proof,
}
}

/// Convert bid adjustment data into [`BidAdjustmentDataV2`].
pub fn into_v2(self) -> BidAdjustmentDataV2 {
BidAdjustmentDataV2 {
el_transactions_root: self.el_transactions_root,
el_withdrawals_root: self.el_withdrawals_root,
builder_address: self.state_proofs.builder_address,
builder_proof: self.state_proofs.builder_proof,
fee_recipient_address: self.state_proofs.fee_recipient_address,
fee_recipient_proof: self.state_proofs.fee_recipient_proof,
fee_payer_address: self.state_proofs.fee_payer_address,
fee_payer_proof: self.state_proofs.fee_payer_proof,
el_placeholder_transaction_proof: self.el_placeholder_transaction_proof,
cl_placeholder_transaction_proof: self.cl_placeholder_transaction_proof,
placeholder_receipt_proof: self.placeholder_receipt_proof,
pre_payment_logs_bloom: self.pre_payment_logs_bloom,
}
}
/// Gas used by the placeholder (payout) transaction. Required for V3 to relax the
/// gas_limit == gas_used requirement.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New V3 field - verify relay compatibility

The placeholder_gas_used field is the key addition for V3 format. This is correctly documented as relaxing the gas_limit == gas_used requirement.

Please verify:

  1. This field name matches exactly what the relay expects
  2. The relay version you're targeting supports this field (ensure relay is updated to accept V3 format if not already)

pub placeholder_gas_used: u64,
}

/// Bid adjustment state proofs.
Expand Down
18 changes: 9 additions & 9 deletions crates/rbuilder-primitives/src/mev_boost/submit_block.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::mev_boost::BidAdjustmentDataV1;
use crate::mev_boost::BidAdjustmentDataV3;
use alloy_rpc_types_beacon::{
relay::{
BidTrace, SignedBidSubmissionV2, SignedBidSubmissionV3, SignedBidSubmissionV4,
Expand All @@ -22,7 +22,7 @@ pub struct SubmitBlockRequest {
pub request: Arc<AlloySubmitBlockRequest>,
/// Bid adjustment data if present.
#[serde(skip_serializing_if = "Option::is_none")]
pub adjustment_data: Option<BidAdjustmentDataV1>,
pub adjustment_data: Option<BidAdjustmentDataV3>,
}

impl ssz::Encode for SubmitBlockRequest {
Expand Down Expand Up @@ -56,7 +56,7 @@ impl ssz::Encode for SubmitBlockRequest {
};
// Add adjustment data offset if present.
if self.adjustment_data.is_some() {
offset += <BidAdjustmentDataV1 as ssz::Encode>::ssz_fixed_len();
offset += <BidAdjustmentDataV3 as ssz::Encode>::ssz_fixed_len();
}

let mut encoder = ssz::SszEncoder::container(buf, offset);
Expand Down Expand Up @@ -122,7 +122,7 @@ impl ssz::Encode for SubmitBlockRequest {
fn ssz_bytes_len(&self) -> usize {
let mut len = <AlloySubmitBlockRequest as ssz::Encode>::ssz_bytes_len(&self.request);
if let Some(adjustment) = &self.adjustment_data {
len += <BidAdjustmentDataV1 as ssz::Encode>::ssz_bytes_len(adjustment);
len += <BidAdjustmentDataV3 as ssz::Encode>::ssz_bytes_len(adjustment);
}
len
}
Expand Down Expand Up @@ -216,7 +216,7 @@ impl SubmitBlockRequest {
}

/// Set the bid adjustment data on the request.
pub fn set_adjustment_data(&mut self, data: BidAdjustmentDataV1) {
pub fn set_adjustment_data(&mut self, data: BidAdjustmentDataV3) {
self.adjustment_data = Some(data);
}

Expand Down Expand Up @@ -313,7 +313,7 @@ mod ssz_helpers {
message: BidTrace,
execution_payload: ExecutionPayloadV2,
signature: BlsSignature,
adjustment_data: BidAdjustmentDataV1,
adjustment_data: BidAdjustmentDataV3,
}

impl From<CapellaSubmitBlockRequestSszHelper> for SubmitBlockRequest {
Expand Down Expand Up @@ -341,7 +341,7 @@ mod ssz_helpers {
execution_payload: ExecutionPayloadV3,
blobs_bundle: BlobsBundleV1,
signature: BlsSignature,
adjustment_data: BidAdjustmentDataV1,
adjustment_data: BidAdjustmentDataV3,
}

impl From<DenebSubmitBlockRequestSszHelper> for SubmitBlockRequest {
Expand Down Expand Up @@ -372,7 +372,7 @@ mod ssz_helpers {
blobs_bundle: BlobsBundleV1,
execution_requests: ExecutionRequestsV4,
signature: BlsSignature,
adjustment_data: BidAdjustmentDataV1,
adjustment_data: BidAdjustmentDataV3,
}

impl From<ElectraSubmitBlockRequestSszHelper> for SubmitBlockRequest {
Expand Down Expand Up @@ -405,7 +405,7 @@ mod ssz_helpers {
blobs_bundle: BlobsBundleV2,
execution_requests: ExecutionRequestsV4,
signature: BlsSignature,
adjustment_data: BidAdjustmentDataV1,
adjustment_data: BidAdjustmentDataV3,
}

impl From<FuluSubmitBlockRequestSszHelper> for SubmitBlockRequest {
Expand Down
14 changes: 7 additions & 7 deletions crates/rbuilder-primitives/src/mev_boost/submit_header.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::mev_boost::{
adjustment::BidAdjustmentDataV2,
adjustment::BidAdjustmentDataV3,
ssz_roots::{calculate_transactions_root_ssz, calculate_withdrawals_root_ssz},
BidMetadata,
};
Expand Down Expand Up @@ -35,7 +35,7 @@ pub struct SubmitHeaderRequest {
/// The number of transactions in the block.
pub tx_count: u32,
/// The signed header data. This is the same structure used by
/// the Optimistic V2 'SignedHeaderSubmission'.
/// the Optimistic V3 'SignedHeaderSubmission'.
pub submission: SignedHeaderSubmission,
}

Expand Down Expand Up @@ -84,8 +84,8 @@ pub struct HeaderSubmissionElectra {
pub execution_requests: ExecutionRequestsV4,
/// Blob KZG commitments.
pub commitments: Vec<alloy_consensus::Bytes48>,
/// Bid adjustment data V2.
pub adjustment_data: Option<BidAdjustmentDataV2>,
/// Bid adjustment data V3.
pub adjustment_data: Option<BidAdjustmentDataV3>,
}

impl ssz::Encode for HeaderSubmissionElectra {
Expand All @@ -99,7 +99,7 @@ impl ssz::Encode for HeaderSubmissionElectra {
+ <ExecutionRequestsV4 as ssz::Encode>::ssz_fixed_len()
+ <Vec<alloy_consensus::Bytes48> as ssz::Encode>::ssz_fixed_len();
if self.adjustment_data.is_some() {
offset += <BidAdjustmentDataV2 as ssz::Encode>::ssz_fixed_len();
offset += <BidAdjustmentDataV3 as ssz::Encode>::ssz_fixed_len();
}

let mut encoder = ssz::SszEncoder::container(buf, offset);
Expand All @@ -123,7 +123,7 @@ impl ssz::Encode for HeaderSubmissionElectra {
+ <ExecutionRequestsV4 as ssz::Encode>::ssz_bytes_len(&self.execution_requests)
+ <Vec<alloy_consensus::Bytes48> as ssz::Encode>::ssz_bytes_len(&self.commitments);
if let Some(adjustment) = &self.adjustment_data {
len += <BidAdjustmentDataV2 as ssz::Encode>::ssz_bytes_len(adjustment);
len += <BidAdjustmentDataV3 as ssz::Encode>::ssz_bytes_len(adjustment);
}
len
}
Expand All @@ -141,7 +141,7 @@ impl ssz::Decode for HeaderSubmissionElectra {
execution_payload_header: ExecutionPayloadHeaderElectra,
execution_requests: ExecutionRequestsV4,
commitments: Vec<alloy_consensus::Bytes48>,
adjustment_data: BidAdjustmentDataV2,
adjustment_data: BidAdjustmentDataV3,
}

#[derive(ssz_derive::Decode)]
Expand Down
4 changes: 2 additions & 2 deletions crates/rbuilder/src/building/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
use ahash::HashSet;
use alloy_eips::eip7594::BlobTransactionSidecarVariant;
use alloy_primitives::{Address, Bytes};
use rbuilder_primitives::{mev_boost::BidAdjustmentData, AccountNonce, OrderId, SimulatedOrder};
use rbuilder_primitives::{mev_boost::BidAdjustmentDataV3, AccountNonce, OrderId, SimulatedOrder};
use reth::primitives::SealedBlock;
use std::{
collections::HashMap,
Expand Down Expand Up @@ -49,7 +49,7 @@ pub struct Block {
/// The Pectra execution requests for this bid.
pub execution_requests: Vec<Bytes>,
/// Bid adjustment data by fee payer address.
pub bid_adjustments: HashMap<Address, BidAdjustmentData>,
pub bid_adjustments: HashMap<Address, BidAdjustmentDataV3>,
}

/// Id to uniquely identify every block built (unique even among different algorithms).
Expand Down
23 changes: 16 additions & 7 deletions crates/rbuilder/src/building/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use evm::EthCachedEvmFactory;
use jsonrpsee::core::Serialize;
use parking_lot::Mutex;
use rbuilder_primitives::{
mev_boost::BidAdjustmentData, BlockSpace, Order, SimValue, SimulatedOrder,
mev_boost::BidAdjustmentDataV3, BlockSpace, Order, SimValue, SimulatedOrder,
TransactionSignedEcRecoveredWithBlobs,
};
use reth::{
Expand Down Expand Up @@ -613,7 +613,7 @@ pub struct FinalizeResult {
/// The Pectra execution requests for this bid.
pub execution_requests: Vec<Bytes>,
/// Bid adjustment data.
pub bid_adjustments: HashMap<Address, BidAdjustmentData>,
pub bid_adjustments: HashMap<Address, BidAdjustmentDataV3>,
/// Duration of root hash calculation.
pub root_hash_time: Duration,
}
Expand Down Expand Up @@ -1141,21 +1141,30 @@ impl<Tracer: SimulationTracer, PartialBlockExecutionTracerType: PartialBlockExec
&local_ctx.tx_ssz_leaf_root_cache,
)
});
let placeholder_gas_used = self
.executed_tx_infos
.last()
.map(|tx_info| tx_info.space_used.gas)
.expect("payout transaction must exist");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential panic in edge cases

This expect() is now evaluated unconditionally, whereas previously the bid adjustment data was constructed lazily inside the iterator. If executed_tx_infos is ever empty (e.g., in testing or edge cases), this will panic.

Consider deferring this calculation to only when bid_adjustment_state_proofs is non-empty, or using unwrap_or(0) if a default is acceptable:

Suggested change
.expect("payout transaction must exist");
.expect("payout transaction must exist");

Alternatively, you could compute this lazily:

let placeholder_gas_used = LazyCell::new(|| {
    self.executed_tx_infos
        .last()
        .map(|tx_info| tx_info.space_used.gas)
        .expect("payout transaction must exist")
});

This preserves the original behavior where the expect would only be evaluated when bid adjustments are actually created.

let bid_adjustments = bid_adjustment_state_proofs
.into_iter()
.map(|(fee_payer, state_proofs)| {
(
fee_payer,
BidAdjustmentData {
state_root: block.header.state_root,
BidAdjustmentDataV3 {
el_transactions_root: block.header.transactions_root,
el_withdrawals_root: block.header.withdrawals_root.unwrap_or_default(),
receipts_root: block.header.receipts_root,
builder_address: state_proofs.builder_address,
builder_proof: state_proofs.builder_proof,
fee_recipient_address: state_proofs.fee_recipient_address,
fee_recipient_proof: state_proofs.fee_recipient_proof,
fee_payer_address: state_proofs.fee_payer_address,
fee_payer_proof: state_proofs.fee_payer_proof,
el_placeholder_transaction_proof: el_placeholder_transaction_proof.clone(),
cl_placeholder_transaction_proof: cl_placeholder_transaction_proof.clone(),
placeholder_receipt_proof: placeholder_receipt_proof.clone(),
el_placeholder_receipt_proof: placeholder_receipt_proof.clone(),
pre_payment_logs_bloom,
state_proofs,
placeholder_gas_used,
},
)
})
Expand Down
Loading
Loading