From affdfb0d13a33e279d57e180d227240392336658 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sun, 21 Jun 2026 17:57:16 +0300 Subject: [PATCH] Payload builder version --- .../gossip_verified_bid.rs | 10 +++++++++- .../src/payload_bid_verification/mod.rs | 2 ++ .../src/payload_bid_verification/tests.rs | 3 +++ .../network_beacon_processor/gossip_methods.rs | 3 ++- .../state_processing/src/per_block_processing.rs | 15 ++++++++++++++- .../src/per_block_processing/errors.rs | 2 ++ .../per_block_processing/process_operations.rs | 11 ++++++++++- consensus/state_processing/src/upgrade/gloas.rs | 4 +++- consensus/types/src/core/consts.rs | 3 +++ consensus/types/src/state/beacon_state.rs | 5 +---- 10 files changed, 49 insertions(+), 9 deletions(-) diff --git a/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs b/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs index 2e69a21853..2a0c49aff3 100644 --- a/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs +++ b/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs @@ -14,7 +14,7 @@ use state_processing::signature_sets::{ use tracing::debug; use types::{ BeaconState, ChainSpec, EthSpec, ExecutionPayloadBid, SignedExecutionPayloadBid, - SignedProposerPreferences, Slot, + SignedProposerPreferences, Slot, consts::gloas::PAYLOAD_BUILDER_VERSION, }; /// Verify that an execution payload bid is consistent with the current chain state @@ -64,6 +64,14 @@ pub(crate) fn verify_bid_consistency( return Err(PayloadBidError::InvalidBuilder { builder_index }); } + let builder_version = head_state.get_builder(builder_index)?.version; + if builder_version != PAYLOAD_BUILDER_VERSION { + return Err(PayloadBidError::InvalidBuilderVersion { + builder_index, + version: builder_version, + }); + } + if !head_state.can_builder_cover_bid(builder_index, bid.value, spec)? { return Err(PayloadBidError::BuilderCantCoverBid { builder_index, diff --git a/beacon_node/beacon_chain/src/payload_bid_verification/mod.rs b/beacon_node/beacon_chain/src/payload_bid_verification/mod.rs index b590d9200f..a5453d0c5b 100644 --- a/beacon_node/beacon_chain/src/payload_bid_verification/mod.rs +++ b/beacon_node/beacon_chain/src/payload_bid_verification/mod.rs @@ -30,6 +30,8 @@ pub enum PayloadBidError { BuilderAlreadySeen { builder_index: u64, slot: Slot }, /// Builder is not valid/active for the given epoch InvalidBuilder { builder_index: u64 }, + /// The builder's version is not `PAYLOAD_BUILDER_VERSION`. + InvalidBuilderVersion { builder_index: u64, version: u8 }, /// The bid value is lower than the currently cached bid. BidValueBelowCached { cached_value: u64, diff --git a/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs b/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs index e764f0beb5..a11d466db3 100644 --- a/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs +++ b/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs @@ -16,6 +16,7 @@ use types::{ Address, ChainSpec, Checkpoint, Domain, Epoch, EthSpec, ExecutionBlockHash, ExecutionPayloadBid, Hash256, MinimalEthSpec, ProposerPreferences, SignedBeaconBlock, SignedExecutionPayloadBid, SignedProposerPreferences, SignedRoot, Slot, + consts::gloas::PAYLOAD_BUILDER_VERSION, }; use proto_array::{Block as ProtoBlock, ExecutionStatus, PayloadStatus}; @@ -86,6 +87,7 @@ impl TestContext { state .add_builder_to_registry( PublicKeyBytes::from(keypair.pk.clone()), + PAYLOAD_BUILDER_VERSION, creds, BUILDER_BALANCE, Slot::new(0), @@ -117,6 +119,7 @@ impl TestContext { let inactive_builder_index = state .add_builder_to_registry( PublicKeyBytes::from(inactive_keypair.pk.clone()), + PAYLOAD_BUILDER_VERSION, inactive_creds, BUILDER_BALANCE, Slot::new(E::slots_per_epoch()), diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 5a7d104b4d..7853b4f6af 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -4021,10 +4021,11 @@ impl NetworkBeaconProcessor { Err( PayloadBidError::BadSignature | PayloadBidError::InvalidBuilder { .. } + | PayloadBidError::InvalidBuilderVersion { .. } | PayloadBidError::InvalidFeeRecipient | PayloadBidError::ExecutionPaymentNonZero { .. } | PayloadBidError::InvalidBlobKzgCommitments { .. } - | PayloadBidError::BidNotDescendantOfParent { .. }, + | PayloadBidError::BidNotDescendantOfParent { .. } | PayloadBidError::InvalidPrevRandao { .. }, ) => { self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index f13f2a339b..61748cdc02 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -11,7 +11,10 @@ use signature_sets::{ use std::borrow::Cow; use tree_hash::TreeHash; use typenum::Unsigned; -use types::{consts::gloas::BUILDER_INDEX_SELF_BUILD, *}; +use types::{ + consts::gloas::{BUILDER_INDEX_SELF_BUILD, PAYLOAD_BUILDER_VERSION}, + *, +}; pub use self::verify_attester_slashing::{ get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing, @@ -698,6 +701,16 @@ pub fn process_execution_payload_bid ExecutionPayloadBidInvalid::BuilderNotActive(builder_index).into() ); + // Verify that the builder is a payload builder + block_verify!( + builder.version == PAYLOAD_BUILDER_VERSION, + ExecutionPayloadBidInvalid::InvalidBuilderVersion { + builder_index, + version: builder.version, + } + .into() + ); + // Verify that the builder has funds to cover the bid block_verify!( state.can_builder_cover_bid(builder_index, amount, spec)?, diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index 93d668c8c9..34908449b9 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -527,6 +527,8 @@ pub enum ExecutionPayloadBidInvalid { BadSignature, /// The builder is not active. BuilderNotActive(u64), + /// The builder's version is not `PAYLOAD_BUILDER_VERSION`. + InvalidBuilderVersion { builder_index: u64, version: u8 }, /// The builder has insufficient balance to cover the bid InsufficientBalance { builder_index: u64, diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index cb305d6127..86ad13079c 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -970,11 +970,18 @@ pub fn process_deposit_request_post_gloas( && !is_validator && !is_pending_validator(state.pending_deposits()?, &deposit_request.pubkey, spec)) { - // Apply builder deposits immediately + // Apply builder deposits immediately. The builder version is taken from the + // withdrawal credentials prefix byte (spec: `process_builder_deposit_request`). + let version = *deposit_request + .withdrawal_credentials + .as_slice() + .first() + .ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?; apply_deposit_for_builder( state, builder_index, deposit_request.pubkey, + version, deposit_request.withdrawal_credentials, deposit_request.amount, deposit_request.signature.clone(), @@ -1002,6 +1009,7 @@ pub fn apply_deposit_for_builder( state: &mut BeaconState, builder_index_opt: Option, pubkey: PublicKeyBytes, + version: u8, withdrawal_credentials: Hash256, amount: u64, signature: SignatureBytes, @@ -1020,6 +1028,7 @@ pub fn apply_deposit_for_builder( if is_valid_deposit_signature(&deposit_data, spec).is_ok() { let builder_index = state.add_builder_to_registry( pubkey, + version, withdrawal_credentials, amount, slot, diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index c26547e304..e23eb94a64 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -10,7 +10,8 @@ use tree_hash::TreeHash; use typenum::Unsigned; use types::{ BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec, - EthSpec, ExecutionPayloadBid, ExecutionRequests, Fork, is_builder_withdrawal_credential, + EthSpec, ExecutionPayloadBid, ExecutionRequests, Fork, consts::gloas::PAYLOAD_BUILDER_VERSION, + is_builder_withdrawal_credential, }; /// Transform a `Fulu` state into a `Gloas` state. @@ -208,6 +209,7 @@ fn onboard_builders_from_pending_deposits( state, builder_index, deposit.pubkey, + PAYLOAD_BUILDER_VERSION, deposit.withdrawal_credentials, deposit.amount, deposit.signature.clone(), diff --git a/consensus/types/src/core/consts.rs b/consensus/types/src/core/consts.rs index 049094da76..66de387b01 100644 --- a/consensus/types/src/core/consts.rs +++ b/consensus/types/src/core/consts.rs @@ -29,6 +29,9 @@ pub mod gloas { pub const BUILDER_INDEX_SELF_BUILD: u64 = u64::MAX; pub const BUILDER_INDEX_FLAG: u64 = 1 << 40; + /// Version for an execution payload builder. + pub const PAYLOAD_BUILDER_VERSION: u8 = 0; + // Fork choice constants pub type PayloadStatus = u8; pub const PAYLOAD_STATUS_EMPTY: PayloadStatus = 0; diff --git a/consensus/types/src/state/beacon_state.rs b/consensus/types/src/state/beacon_state.rs index 26f28eda45..d25d3fc150 100644 --- a/consensus/types/src/state/beacon_state.rs +++ b/consensus/types/src/state/beacon_state.rs @@ -2061,6 +2061,7 @@ impl BeaconState { pub fn add_builder_to_registry( &mut self, pubkey: PublicKeyBytes, + version: u8, withdrawal_credentials: Hash256, amount: u64, slot: Slot, @@ -2072,10 +2073,6 @@ impl BeaconState { let builder_index = self.get_index_for_new_builder()?; let builders = self.builders_mut()?; - let version = *withdrawal_credentials - .as_slice() - .first() - .ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?; let execution_address = withdrawal_credentials .as_slice() .get(12..)