Builder deposit requests

This commit is contained in:
Eitan Seri-Levi
2026-06-22 12:55:40 +03:00
parent d610407820
commit edf34c5575
33 changed files with 675 additions and 265 deletions

View File

@@ -7,7 +7,7 @@ use keccak_hash::KECCAK_EMPTY_LIST_RLP;
use triehash::ordered_trie_root;
use types::{
EncodableExecutionBlockHeader, EthSpec, ExecutionBlockHash, ExecutionBlockHeader,
ExecutionPayloadRef, ExecutionRequests, Hash256,
ExecutionPayloadRef, ExecutionRequestsRef, Hash256,
};
/// Calculate the block hash of an execution block.
@@ -17,7 +17,7 @@ use types::{
pub fn calculate_execution_block_hash<E: EthSpec>(
payload: ExecutionPayloadRef<E>,
parent_beacon_block_root: Option<Hash256>,
execution_requests: Option<&ExecutionRequests<E>>,
execution_requests: Option<ExecutionRequestsRef<E>>,
) -> (ExecutionBlockHash, Hash256) {
// Calculate the transactions root.
// We're currently using a deprecated Parity library for this. We should move to a

View File

@@ -844,8 +844,7 @@ impl HttpJsonRpc {
),
new_payload_request_electra.versioned_hashes,
new_payload_request_electra.parent_beacon_block_root,
new_payload_request_electra
.execution_requests
types::ExecutionRequestsRef::Electra(new_payload_request_electra.execution_requests)
.get_execution_requests_list(),
]);
@@ -873,8 +872,7 @@ impl HttpJsonRpc {
),
new_payload_request_fulu.versioned_hashes,
new_payload_request_fulu.parent_beacon_block_root,
new_payload_request_fulu
.execution_requests
types::ExecutionRequestsRef::Electra(new_payload_request_fulu.execution_requests)
.get_execution_requests_list(),
]);
@@ -902,8 +900,7 @@ impl HttpJsonRpc {
),
new_payload_request_gloas.versioned_hashes,
new_payload_request_gloas.parent_beacon_block_root,
new_payload_request_gloas
.execution_requests
types::ExecutionRequestsRef::Gloas(new_payload_request_gloas.execution_requests)
.get_execution_requests_list(),
]);

View File

@@ -1,12 +1,15 @@
use super::*;
use alloy_rlp::RlpEncodable;
use serde::{Deserialize, Serialize};
use ssz::{Decode, Encode, TryFromIter};
use ssz::{Decode, TryFromIter};
use ssz_types::{FixedVector, VariableList, typenum::Unsigned};
use strum::EnumString;
use superstruct::superstruct;
use types::data::BlobsList;
use types::execution::{ConsolidationRequests, DepositRequests, RequestType, WithdrawalRequests};
use types::execution::{
BuilderDepositRequests, BuilderExitRequests, ConsolidationRequests, DepositRequests,
ExecutionRequestsElectra, ExecutionRequestsGloas, RequestType, WithdrawalRequests,
};
use types::kzg_ext::KzgCommitments;
use types::{Blob, KzgProof};
@@ -483,28 +486,13 @@ pub struct JsonExecutionRequests(pub Vec<String>);
impl<E: EthSpec> From<ExecutionRequests<E>> for JsonExecutionRequests {
fn from(requests: ExecutionRequests<E>) -> Self {
let mut result = Vec::new();
if !requests.deposits.is_empty() {
result.push(format!(
"0x{:02x}{}",
RequestType::Deposit.to_u8(),
hex::encode(requests.deposits.as_ssz_bytes())
));
}
if !requests.withdrawals.is_empty() {
result.push(format!(
"0x{:02x}{}",
RequestType::Withdrawal.to_u8(),
hex::encode(requests.withdrawals.as_ssz_bytes())
));
}
if !requests.consolidations.is_empty() {
result.push(format!(
"0x{:02x}{}",
RequestType::Consolidation.to_u8(),
hex::encode(requests.consolidations.as_ssz_bytes())
));
}
// Each element is a `RequestType`-prefixed, SSZ-encoded request list (EIP-7685).
// The Gloas variant additionally emits builder deposit/exit requests.
let result = requests
.get_execution_requests_list()
.into_iter()
.map(|bytes| format!("0x{}", hex::encode(bytes)))
.collect();
JsonExecutionRequests(result)
}
}
@@ -513,7 +501,15 @@ impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
type Error = RequestsError;
fn try_from(value: JsonExecutionRequests) -> Result<Self, Self::Error> {
let mut requests = ExecutionRequests::default();
let mut deposits = DepositRequests::<E>::default();
let mut withdrawals = WithdrawalRequests::<E>::default();
let mut consolidations = ConsolidationRequests::<E>::default();
let mut builder_deposits = BuilderDepositRequests::<E>::default();
let mut builder_exits = BuilderExitRequests::<E>::default();
// [New in Gloas:EIP8282] The presence of builder requests determines the variant: the
// EIP-7685 list is fork-agnostic, so we only know it is Gloas-shaped once a builder
// request type appears.
let mut has_builder_requests = false;
let mut prev_prefix: Option<RequestType> = None;
for (i, request) in value.0.into_iter().enumerate() {
// hex string
@@ -540,8 +536,8 @@ impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
match current_prefix {
RequestType::Deposit => {
requests.deposits = DepositRequests::<E>::from_ssz_bytes(request_bytes)
.map_err(|e| {
deposits =
DepositRequests::<E>::from_ssz_bytes(request_bytes).map_err(|e| {
RequestsError::DecodeError(format!(
"Failed to decode DepositRequest from EL: {:?}",
e
@@ -549,26 +545,64 @@ impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
})?;
}
RequestType::Withdrawal => {
requests.withdrawals = WithdrawalRequests::<E>::from_ssz_bytes(request_bytes)
.map_err(|e| {
RequestsError::DecodeError(format!(
"Failed to decode WithdrawalRequest from EL: {:?}",
e
))
})?;
withdrawals =
WithdrawalRequests::<E>::from_ssz_bytes(request_bytes).map_err(|e| {
RequestsError::DecodeError(format!(
"Failed to decode WithdrawalRequest from EL: {:?}",
e
))
})?;
}
RequestType::Consolidation => {
requests.consolidations =
ConsolidationRequests::<E>::from_ssz_bytes(request_bytes).map_err(|e| {
consolidations = ConsolidationRequests::<E>::from_ssz_bytes(request_bytes)
.map_err(|e| {
RequestsError::DecodeError(format!(
"Failed to decode ConsolidationRequest from EL: {:?}",
e
))
})?;
}
RequestType::BuilderDeposit => {
builder_deposits = BuilderDepositRequests::<E>::from_ssz_bytes(request_bytes)
.map_err(|e| {
RequestsError::DecodeError(format!(
"Failed to decode BuilderDepositRequest from EL: {:?}",
e
))
})?;
has_builder_requests = true;
}
RequestType::BuilderExit => {
builder_exits = BuilderExitRequests::<E>::from_ssz_bytes(request_bytes)
.map_err(|e| {
RequestsError::DecodeError(format!(
"Failed to decode BuilderExitRequest from EL: {:?}",
e
))
})?;
has_builder_requests = true;
}
}
}
Ok(requests)
// Without any builder requests the list is indistinguishable from a pre-Gloas one, so we
// produce the Electra-shaped variant. Consumers that require the Gloas variant lift it
// (carrying empty builder lists) at their boundary.
if has_builder_requests {
Ok(ExecutionRequests::Gloas(ExecutionRequestsGloas {
deposits,
withdrawals,
consolidations,
builder_deposits,
builder_exits,
}))
} else {
Ok(ExecutionRequests::Electra(ExecutionRequestsElectra {
deposits,
withdrawals,
consolidations,
}))
}
}
}

View File

@@ -9,7 +9,8 @@ use types::{
};
use types::{
ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb,
ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests,
ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequestsElectra,
ExecutionRequestsGloas, ExecutionRequestsRef,
};
#[superstruct(
@@ -47,8 +48,13 @@ pub struct NewPayloadRequest<'block, E: EthSpec> {
pub versioned_hashes: Vec<VersionedHash>,
#[superstruct(only(Deneb, Electra, Fulu, Gloas))]
pub parent_beacon_block_root: Hash256,
#[superstruct(only(Electra, Fulu, Gloas))]
pub execution_requests: &'block ExecutionRequests<E>,
#[superstruct(
only(Electra, Fulu),
partial_getter(rename = "execution_requests_electra")
)]
pub execution_requests: &'block ExecutionRequestsElectra<E>,
#[superstruct(only(Gloas), partial_getter(rename = "execution_requests_gloas"))]
pub execution_requests: &'block ExecutionRequestsGloas<E>,
}
impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
@@ -123,6 +129,16 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
Ok(())
}
/// Returns the execution requests as a fork-tagged reference, if present.
pub fn execution_requests_ref(&self) -> Option<ExecutionRequestsRef<'block, E>> {
match self {
Self::Bellatrix(_) | Self::Capella(_) | Self::Deneb(_) => None,
Self::Electra(r) => Some(ExecutionRequestsRef::Electra(r.execution_requests)),
Self::Fulu(r) => Some(ExecutionRequestsRef::Electra(r.execution_requests)),
Self::Gloas(r) => Some(ExecutionRequestsRef::Gloas(r.execution_requests)),
}
}
/// Verify the block hash is consistent locally within Lighthouse.
///
/// ## Specification
@@ -143,7 +159,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
let (header_hash, rlp_transactions_root) = calculate_execution_block_hash(
payload,
parent_beacon_block_root,
self.execution_requests().ok().copied(),
self.execution_requests_ref(),
);
if header_hash != self.block_hash() {

View File

@@ -118,14 +118,14 @@ impl<E: EthSpec> TryFrom<BuilderBid<E>> for ProvenancedPayload<BlockProposalCont
block_value: builder_bid.value,
kzg_commitments: builder_bid.blob_kzg_commitments,
blobs_and_proofs: None,
requests: Some(builder_bid.execution_requests),
requests: Some(builder_bid.execution_requests.into()),
},
BuilderBid::Fulu(builder_bid) => BlockProposalContents::PayloadAndBlobs {
payload: ExecutionPayloadHeader::Fulu(builder_bid.header).into(),
block_value: builder_bid.value,
kzg_commitments: builder_bid.blob_kzg_commitments,
blobs_and_proofs: None,
requests: Some(builder_bid.execution_requests),
requests: Some(builder_bid.execution_requests.into()),
},
};
Ok(ProvenancedPayload::Builder(

View File

@@ -438,7 +438,9 @@ pub async fn handle_rpc<E: EthSpec>(
should_override_builder: false,
execution_requests: maybe_execution_requests
.clone()
.unwrap_or_default()
.unwrap_or_else(|| {
types::ExecutionRequests::Electra(Default::default())
})
.into(),
})
.unwrap()
@@ -461,7 +463,9 @@ pub async fn handle_rpc<E: EthSpec>(
should_override_builder: false,
execution_requests: maybe_execution_requests
.clone()
.unwrap_or_default()
.unwrap_or_else(|| {
types::ExecutionRequests::Electra(Default::default())
})
.into(),
})
.unwrap()
@@ -483,7 +487,9 @@ pub async fn handle_rpc<E: EthSpec>(
.into(),
should_override_builder: false,
execution_requests: maybe_execution_requests
.unwrap_or_default()
.unwrap_or_else(|| {
types::ExecutionRequests::Electra(Default::default())
})
.into(),
})
.unwrap()

View File

@@ -35,8 +35,9 @@ use types::builder::{
};
use types::{
Address, BeaconState, ChainSpec, Epoch, EthSpec, ExecPayload, ExecutionPayload,
ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, ForkVersionDecode, Hash256,
SignedBlindedBeaconBlock, SignedRoot, SignedValidatorRegistrationData, Slot, Uint256,
ExecutionPayloadHeaderRefMut, ExecutionRequests, ExecutionRequestsElectra, ForkName,
ForkVersionDecode, Hash256, SignedBlindedBeaconBlock, SignedRoot,
SignedValidatorRegistrationData, Slot, Uint256,
};
use warp::reply::{self, Reply};
use warp::{Filter, Rejection};
@@ -603,7 +604,13 @@ impl<E: EthSpec> MockBuilder<E> {
.unwrap_or_default(),
value: self.get_bid_value(value),
pubkey: self.builder_sk.public_key().compress(),
execution_requests: maybe_requests.unwrap_or_default(),
execution_requests: maybe_requests
.map(|r| ExecutionRequestsElectra {
deposits: r.deposits().clone(),
withdrawals: r.withdrawals().clone(),
consolidations: r.consolidations().clone(),
})
.unwrap_or_default(),
}),
ForkName::Electra => BuilderBid::Electra(BuilderBidElectra {
header: payload
@@ -615,7 +622,13 @@ impl<E: EthSpec> MockBuilder<E> {
.unwrap_or_default(),
value: self.get_bid_value(value),
pubkey: self.builder_sk.public_key().compress(),
execution_requests: maybe_requests.unwrap_or_default(),
execution_requests: maybe_requests
.map(|r| ExecutionRequestsElectra {
deposits: r.deposits().clone(),
withdrawals: r.withdrawals().clone(),
consolidations: r.consolidations().clone(),
})
.unwrap_or_default(),
}),
ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb {
header: payload