Skip to content

Commit 4dfc31c

Browse files
authored
Gloas process execution payload bid (#8355)
* add proces_execution_bid * add has_builder_withdrawal_credential * process_execution_payload_bid signature is infinity check for self-build * process_execution_payload_bid updates per consensus spec v1.6.0-beta.1 release * process_execution_bid to avoid expensive lookups for 0 amount bids * verify builder not slashed even for self-building
1 parent 4ab5a77 commit 4dfc31c

File tree

9 files changed

+316
-36
lines changed

9 files changed

+316
-36
lines changed

beacon_node/operation_pool/src/bls_to_execution_changes.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,18 @@ impl<E: EthSpec> BlsToExecutionChanges<E> {
113113
.validators()
114114
.get(validator_index as usize)
115115
.is_none_or(|validator| {
116-
let prune = validator.has_execution_withdrawal_credential(spec)
117-
&& head_block
118-
.message()
119-
.body()
120-
.bls_to_execution_changes()
121-
.map_or(true, |recent_changes| {
122-
!recent_changes
123-
.iter()
124-
.any(|c| c.message.validator_index == validator_index)
125-
});
116+
let prune = validator.has_execution_withdrawal_credential(
117+
spec,
118+
head_state.fork_name_unchecked(),
119+
) && head_block
120+
.message()
121+
.body()
122+
.bls_to_execution_changes()
123+
.map_or(true, |recent_changes| {
124+
!recent_changes
125+
.iter()
126+
.any(|c| c.message.validator_index == validator_index)
127+
});
126128
if prune {
127129
validator_indices_pruned.push(validator_index);
128130
}

beacon_node/operation_pool/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,12 @@ impl<E: EthSpec> OperationPool<E> {
582582
address_change.signature_is_still_valid(&state.fork())
583583
&& state
584584
.get_validator(address_change.as_inner().message.validator_index as usize)
585-
.is_ok_and(|validator| !validator.has_execution_withdrawal_credential(spec))
585+
.is_ok_and(|validator| {
586+
!validator.has_execution_withdrawal_credential(
587+
spec,
588+
state.fork_name_unchecked(),
589+
)
590+
})
586591
},
587592
|address_change| address_change.as_inner().clone(),
588593
E::MaxBlsToExecutionChanges::to_usize(),

consensus/state_processing/src/per_block_processing.rs

Lines changed: 165 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
use self::errors::ExecutionPayloadBidInvalid;
12
use crate::consensus_context::ConsensusContext;
23
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
34
use rayon::prelude::*;
45
use safe_arith::{ArithError, SafeArith, SafeArithIter};
5-
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
6+
use signature_sets::{
7+
block_proposal_signature_set, execution_payload_bid_signature_set, get_pubkey_from_state,
8+
randao_signature_set,
9+
};
610
use std::borrow::Cow;
711
use tree_hash::TreeHash;
812
use types::*;
@@ -173,9 +177,7 @@ pub fn per_block_processing<E: EthSpec, Payload: AbstractExecPayload<E>>(
173177
let body = block.body();
174178
if state.fork_name_unchecked().gloas_enabled() {
175179
process_withdrawals::gloas::process_withdrawals::<E>(state, spec)?;
176-
177-
// TODO(EIP-7732): build out process_execution_bid
178-
// process_execution_bid(state, block, verify_signatures, spec)?;
180+
process_execution_payload_bid(state, block, verify_signatures, spec)?;
179181
} else {
180182
process_withdrawals::capella::process_withdrawals::<E, Payload>(
181183
state,
@@ -625,7 +627,7 @@ pub fn get_expected_withdrawals<E: EthSpec>(
625627
index: withdrawal_index,
626628
validator_index: withdrawal.validator_index,
627629
address: validator
628-
.get_execution_withdrawal_address(spec)
630+
.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
629631
.ok_or(BeaconStateError::NonExecutionAddressWithdrawalCredential)?,
630632
amount: withdrawable_balance,
631633
});
@@ -662,7 +664,7 @@ pub fn get_expected_withdrawals<E: EthSpec>(
662664
index: withdrawal_index,
663665
validator_index,
664666
address: validator
665-
.get_execution_withdrawal_address(spec)
667+
.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
666668
.ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?,
667669
amount: balance,
668670
});
@@ -672,7 +674,7 @@ pub fn get_expected_withdrawals<E: EthSpec>(
672674
index: withdrawal_index,
673675
validator_index,
674676
address: validator
675-
.get_execution_withdrawal_address(spec)
677+
.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
676678
.ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?,
677679
amount: balance.safe_sub(validator.get_max_effective_balance(spec, fork_name))?,
678680
});
@@ -692,3 +694,159 @@ pub fn get_expected_withdrawals<E: EthSpec>(
692694
processed_partial_withdrawals_count,
693695
))
694696
}
697+
698+
pub fn process_execution_payload_bid<E: EthSpec, Payload: AbstractExecPayload<E>>(
699+
state: &mut BeaconState<E>,
700+
block: BeaconBlockRef<'_, E, Payload>,
701+
verify_signatures: VerifySignatures,
702+
spec: &ChainSpec,
703+
) -> Result<(), BlockProcessingError> {
704+
// Verify the bid signature
705+
let signed_bid = block.body().signed_execution_payload_bid()?;
706+
707+
let bid = &signed_bid.message;
708+
let amount = bid.value;
709+
let builder_index = bid.builder_index;
710+
let builder = state.get_validator(builder_index as usize)?;
711+
712+
// For self-builds, amount must be zero regardless of withdrawal credential prefix
713+
if builder_index == block.proposer_index() {
714+
block_verify!(amount == 0, ExecutionPayloadBidInvalid::BadAmount.into());
715+
// TODO(EIP-7732): check with team if we should use ExecutionPayloadBidInvalid::BadSignature or a new error variant for this, like BadSelfBuildSignature
716+
block_verify!(
717+
signed_bid.signature.is_infinity(),
718+
ExecutionPayloadBidInvalid::BadSignature.into()
719+
);
720+
} else {
721+
// Non-self builds require builder withdrawal credential
722+
block_verify!(
723+
builder.has_builder_withdrawal_credential(spec),
724+
ExecutionPayloadBidInvalid::BadWithdrawalCredentials.into()
725+
);
726+
if verify_signatures.is_true() {
727+
block_verify!(
728+
execution_payload_bid_signature_set(
729+
state,
730+
|i| get_pubkey_from_state(state, i),
731+
signed_bid,
732+
spec
733+
)?
734+
.verify(),
735+
ExecutionPayloadBidInvalid::BadSignature.into()
736+
);
737+
}
738+
}
739+
740+
// Verify builder is active and not slashed
741+
block_verify!(
742+
builder.is_active_at(state.current_epoch()),
743+
ExecutionPayloadBidInvalid::BuilderNotActive(builder_index).into()
744+
);
745+
block_verify!(
746+
!builder.slashed,
747+
ExecutionPayloadBidInvalid::BuilderSlashed(builder_index).into()
748+
);
749+
750+
// Only perform payment related checks if amount > 0
751+
if amount > 0 {
752+
// Check that the builder has funds to cover the bid
753+
let pending_payments = state
754+
.builder_pending_payments()?
755+
.iter()
756+
.filter_map(|payment| {
757+
if payment.withdrawal.builder_index == builder_index {
758+
Some(payment.withdrawal.amount)
759+
} else {
760+
None
761+
}
762+
})
763+
.safe_sum()?;
764+
765+
let pending_withdrawals = state
766+
.builder_pending_withdrawals()?
767+
.iter()
768+
.filter_map(|withdrawal| {
769+
if withdrawal.builder_index == builder_index {
770+
Some(withdrawal.amount)
771+
} else {
772+
None
773+
}
774+
})
775+
.safe_sum()?;
776+
777+
let builder_balance = state.get_balance(builder_index as usize)?;
778+
779+
block_verify!(
780+
builder_balance
781+
>= amount
782+
.safe_add(pending_payments)?
783+
.safe_add(pending_withdrawals)?
784+
.safe_add(spec.min_activation_balance)?,
785+
ExecutionPayloadBidInvalid::InsufficientBalance {
786+
builder_index,
787+
builder_balance,
788+
bid_value: amount,
789+
}
790+
.into()
791+
);
792+
}
793+
794+
// Verify that the bid is for the current slot
795+
block_verify!(
796+
bid.slot == block.slot(),
797+
ExecutionPayloadBidInvalid::SlotMismatch {
798+
state_slot: block.slot(),
799+
bid_slot: bid.slot,
800+
}
801+
.into()
802+
);
803+
804+
// Verify that the bid is for the right parent block
805+
let latest_block_hash = state.latest_block_hash()?;
806+
block_verify!(
807+
bid.parent_block_hash == *latest_block_hash,
808+
ExecutionPayloadBidInvalid::ParentBlockHashMismatch {
809+
state_block_hash: *latest_block_hash,
810+
bid_parent_hash: bid.parent_block_hash,
811+
}
812+
.into()
813+
);
814+
815+
block_verify!(
816+
bid.parent_block_root == block.parent_root(),
817+
ExecutionPayloadBidInvalid::ParentBlockRootMismatch {
818+
block_parent_root: block.parent_root(),
819+
bid_parent_root: bid.parent_block_root,
820+
}
821+
.into()
822+
);
823+
824+
// Record the pending payment if there is some payment
825+
if amount > 0 {
826+
let pending_payment = BuilderPendingPayment {
827+
weight: 0,
828+
withdrawal: BuilderPendingWithdrawal {
829+
fee_recipient: bid.fee_recipient,
830+
amount,
831+
builder_index,
832+
withdrawable_epoch: spec.far_future_epoch,
833+
},
834+
};
835+
836+
let payment_index = (E::slots_per_epoch()
837+
.safe_add(bid.slot.as_u64().safe_rem(E::slots_per_epoch())?)?)
838+
as usize;
839+
840+
*state
841+
.builder_pending_payments_mut()?
842+
.get_mut(payment_index)
843+
.ok_or(BlockProcessingError::BeaconStateError(
844+
BeaconStateError::BuilderPendingPaymentsIndexNotSupported(payment_index),
845+
))? = pending_payment;
846+
}
847+
848+
// Cache the execution bid
849+
*state.latest_execution_payload_bid_mut()? = bid.clone();
850+
851+
Ok(())
852+
}

consensus/state_processing/src/per_block_processing/errors.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ pub enum BlockProcessingError {
9191
},
9292
WithdrawalCredentialsInvalid,
9393
PendingAttestationInElectra,
94+
ExecutionPayloadBidInvalid {
95+
reason: ExecutionPayloadBidInvalid,
96+
},
9497
}
9598

9699
impl From<BeaconStateError> for BlockProcessingError {
@@ -147,6 +150,12 @@ impl From<milhouse::Error> for BlockProcessingError {
147150
}
148151
}
149152

153+
impl From<ExecutionPayloadBidInvalid> for BlockProcessingError {
154+
fn from(reason: ExecutionPayloadBidInvalid) -> Self {
155+
Self::ExecutionPayloadBidInvalid { reason }
156+
}
157+
}
158+
150159
impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
151160
fn from(e: BlockOperationError<HeaderInvalid>) -> BlockProcessingError {
152161
match e {
@@ -440,6 +449,38 @@ pub enum ExitInvalid {
440449
PendingWithdrawalInQueue(u64),
441450
}
442451

452+
#[derive(Debug, PartialEq, Clone)]
453+
pub enum ExecutionPayloadBidInvalid {
454+
/// The builder sent a 0 amount
455+
BadAmount,
456+
/// The signature is invalid.
457+
BadSignature,
458+
/// The builder's withdrawal credential is invalid
459+
BadWithdrawalCredentials,
460+
/// The builder is not an active validator.
461+
BuilderNotActive(u64),
462+
/// The builder is slashed
463+
BuilderSlashed(u64),
464+
/// The builder has insufficient balance to cover the bid
465+
InsufficientBalance {
466+
builder_index: u64,
467+
builder_balance: u64,
468+
bid_value: u64,
469+
},
470+
/// Bid slot doesn't match state slot
471+
SlotMismatch { state_slot: Slot, bid_slot: Slot },
472+
/// The bid's parent block hash doesn't match the state's latest block hash
473+
ParentBlockHashMismatch {
474+
state_block_hash: ExecutionBlockHash,
475+
bid_parent_hash: ExecutionBlockHash,
476+
},
477+
/// The bid's parent block root doesn't match the block's parent root
478+
ParentBlockRootMismatch {
479+
block_parent_root: Hash256,
480+
bid_parent_root: Hash256,
481+
},
482+
}
483+
443484
#[derive(Debug, PartialEq, Clone)]
444485
pub enum BlsExecutionChangeInvalid {
445486
/// The specified validator is not in the state's validator registry.

consensus/state_processing/src/per_block_processing/process_operations.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,10 @@ pub fn process_withdrawal_requests<E: EthSpec>(
513513

514514
let validator = state.get_validator(validator_index)?;
515515
// Verify withdrawal credentials
516-
let has_correct_credential = validator.has_execution_withdrawal_credential(spec);
516+
let has_correct_credential =
517+
validator.has_execution_withdrawal_credential(spec, state.fork_name_unchecked());
517518
let is_correct_source_address = validator
518-
.get_execution_withdrawal_address(spec)
519+
.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
519520
.map(|addr| addr == request.source_address)
520521
.unwrap_or(false);
521522

@@ -560,7 +561,7 @@ pub fn process_withdrawal_requests<E: EthSpec>(
560561
.safe_add(pending_balance_to_withdraw)?;
561562

562563
// Only allow partial withdrawals with compounding withdrawal credentials
563-
if validator.has_compounding_withdrawal_credential(spec)
564+
if validator.has_compounding_withdrawal_credential(spec, state.fork_name_unchecked())
564565
&& has_sufficient_effective_balance
565566
&& has_excess_balance
566567
{
@@ -729,7 +730,9 @@ pub fn process_consolidation_request<E: EthSpec>(
729730

730731
let source_validator = state.get_validator(source_index)?;
731732
// Verify the source withdrawal credentials
732-
if let Some(withdrawal_address) = source_validator.get_execution_withdrawal_address(spec) {
733+
if let Some(withdrawal_address) =
734+
source_validator.get_execution_withdrawal_address(spec, state.fork_name_unchecked())
735+
{
733736
if withdrawal_address != consolidation_request.source_address {
734737
return Ok(());
735738
}
@@ -740,7 +743,7 @@ pub fn process_consolidation_request<E: EthSpec>(
740743

741744
let target_validator = state.get_validator(target_index)?;
742745
// Verify the target has compounding withdrawal credentials
743-
if !target_validator.has_compounding_withdrawal_credential(spec) {
746+
if !target_validator.has_compounding_withdrawal_credential(spec, state.fork_name_unchecked()) {
744747
return Ok(());
745748
}
746749

0 commit comments

Comments
 (0)