mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-07 22:54:17 -05:00
Add Gloas execution payload header processing and state integration Implement Gloas fork support in consensus-types/blocks Add Gloas state fields to beacon state implementation
257 lines
9.1 KiB
Go
257 lines
9.1 KiB
Go
package gloas
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
|
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
|
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
|
)
|
|
|
|
// ProcessExecutionPayloadBid processes a signed execution payload bid in the Gloas fork.
|
|
// Spec v1.6.1 (pseudocode):
|
|
// process_execution_payload_bid(state: BeaconState, block: BeaconBlock):
|
|
//
|
|
// signed_bid = block.body.signed_execution_payload_bid
|
|
// bid = signed_bid.message
|
|
// builder_index = bid.builder_index
|
|
// builder = state.validators[builder_index]
|
|
// amount = bid.value
|
|
// if builder_index == block.proposer_index:
|
|
// assert amount == 0
|
|
// assert signed_bid.signature == G2_POINT_AT_INFINITY
|
|
// else:
|
|
// assert has_builder_withdrawal_credential(builder)
|
|
// assert verify_execution_payload_bid_signature(state, signed_bid)
|
|
// assert is_active_validator(builder, get_current_epoch(state))
|
|
// assert not builder.slashed
|
|
// assert amount == 0 or state.balances[builder_index] >= amount + pending_payments + pending_withdrawals + MIN_ACTIVATION_BALANCE
|
|
// assert bid.slot == block.slot
|
|
// assert bid.parent_block_hash == state.latest_block_hash
|
|
// assert bid.parent_block_root == block.parent_root
|
|
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
|
// if amount > 0:
|
|
// state.builder_pending_payments[...] = BuilderPendingPayment(weight=0, withdrawal=BuilderPendingWithdrawal(fee_recipient=bid.fee_recipient, amount=amount, builder_index=builder_index, withdrawable_epoch=FAR_FUTURE_EPOCH))
|
|
// state.latest_execution_payload_bid = bid
|
|
func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyBeaconBlock) error {
|
|
signedBid, err := block.Body().SignedExecutionPayloadBid()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get signed execution payload bid: %w", err)
|
|
}
|
|
|
|
wrappedBid, err := blocks.WrappedROSignedExecutionPayloadBid(signedBid)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to wrap signed bid: %w", err)
|
|
}
|
|
|
|
bid, err := wrappedBid.Bid()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get bid from wrapped bid: %w", err)
|
|
}
|
|
|
|
builderIndex := bid.BuilderIndex()
|
|
proposerIndex := block.ProposerIndex()
|
|
amount := bid.Value()
|
|
|
|
builder, err := st.ValidatorAtIndex(builderIndex)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get builder validator: %w", err)
|
|
}
|
|
|
|
if builderIndex == proposerIndex {
|
|
if amount != 0 {
|
|
return fmt.Errorf("self-build amount must be zero, got %d", amount)
|
|
}
|
|
if wrappedBid.Signature() != common.InfiniteSignature {
|
|
return errors.New("self-build signature must be point at infinity")
|
|
}
|
|
} else {
|
|
if err := validateBuilderWithdrawalCredential(builder); err != nil {
|
|
return fmt.Errorf("builder withdrawal credential validation failed: %w", err)
|
|
}
|
|
if err := validatePayloadBidSignature(st, wrappedBid); err != nil {
|
|
return fmt.Errorf("bid signature validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := validateBuilderStatus(st, builder); err != nil {
|
|
return fmt.Errorf("builder validation failed: %w", err)
|
|
}
|
|
|
|
if err := validateBuilderHasEnoughBalance(st, builderIndex, amount); err != nil {
|
|
return fmt.Errorf("builder financial capacity validation failed: %w", err)
|
|
}
|
|
|
|
if err := validateBidConsistency(st, bid, block); err != nil {
|
|
return fmt.Errorf("bid consistency validation failed: %w", err)
|
|
}
|
|
|
|
if amount > 0 {
|
|
feeRecipient := bid.FeeRecipient()
|
|
pendingPayment := ðpb.BuilderPendingPayment{
|
|
Weight: 0,
|
|
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
|
FeeRecipient: feeRecipient[:],
|
|
Amount: amount,
|
|
BuilderIndex: builderIndex,
|
|
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
|
},
|
|
}
|
|
slotIndex := params.BeaconConfig().SlotsPerEpoch + (bid.Slot() % params.BeaconConfig().SlotsPerEpoch)
|
|
if err := st.SetBuilderPendingPayment(slotIndex, pendingPayment); err != nil {
|
|
return fmt.Errorf("failed to set pending payment: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := st.SetExecutionPayloadBid(bid); err != nil {
|
|
return fmt.Errorf("failed to cache execution payload bid: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateBuilderStatus checks if the builder is active and not slashed.
|
|
func validateBuilderStatus(st state.BeaconState, builder *ethpb.Validator) error {
|
|
currentEpoch := slots.ToEpoch(st.Slot())
|
|
if !helpers.IsActiveValidator(builder, currentEpoch) {
|
|
return fmt.Errorf("builder is not active in epoch %d", currentEpoch)
|
|
}
|
|
|
|
if builder.Slashed {
|
|
return errors.New("builder is slashed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateBidConsistency checks that the bid is consistent with the current beacon state.
|
|
func validateBidConsistency(st state.BeaconState, bid interfaces.ROExecutionPayloadBid, block interfaces.ReadOnlyBeaconBlock) error {
|
|
if bid.Slot() != block.Slot() {
|
|
return fmt.Errorf("bid slot %d does not match block slot %d", bid.Slot(), block.Slot())
|
|
}
|
|
|
|
latestBlockHash, err := st.LatestBlockHash()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get latest block hash: %w", err)
|
|
}
|
|
if bid.ParentBlockHash() != latestBlockHash {
|
|
return fmt.Errorf("bid parent block hash mismatch: got %x, expected %x",
|
|
bid.ParentBlockHash(), latestBlockHash)
|
|
}
|
|
|
|
if bid.ParentBlockRoot() != block.ParentRoot() {
|
|
return fmt.Errorf("bid parent block root mismatch: got %x, expected %x",
|
|
bid.ParentBlockRoot(), block.ParentRoot())
|
|
}
|
|
|
|
randaoMix, err := helpers.RandaoMix(st, slots.ToEpoch(st.Slot()))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get randao mix: %w", err)
|
|
}
|
|
if bid.PrevRandao() != [32]byte(randaoMix) {
|
|
return fmt.Errorf("bid prev randao mismatch: got %x, expected %x", bid.PrevRandao(), randaoMix)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateBuilderWithdrawalCredential checks if the builder has the correct withdrawal credential prefix.
|
|
func validateBuilderWithdrawalCredential(validator *ethpb.Validator) error {
|
|
if len(validator.WithdrawalCredentials) != 32 {
|
|
return fmt.Errorf("invalid withdrawal credential length: %d", len(validator.WithdrawalCredentials))
|
|
}
|
|
|
|
if validator.WithdrawalCredentials[0] != params.BeaconConfig().BuilderWithdrawalPrefixByte {
|
|
return fmt.Errorf("builder must have withdrawal credential prefix 0x%02x, got 0x%02x",
|
|
params.BeaconConfig().BuilderWithdrawalPrefixByte,
|
|
validator.WithdrawalCredentials[0])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateBuilderHasEnoughBalance checks if the builder has sufficient funds for the bid.
|
|
func validateBuilderHasEnoughBalance(st state.BeaconState, builderIndex primitives.ValidatorIndex, amount primitives.Gwei) error {
|
|
if amount == 0 {
|
|
return nil
|
|
}
|
|
|
|
builderBalance, err := st.BalanceAtIndex(builderIndex)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get builder balance: %w", err)
|
|
}
|
|
|
|
pendingPayments, err := st.PendingPaymentSum(builderIndex)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to calculate pending payments: %w", err)
|
|
}
|
|
|
|
pendingWithdrawals, err := st.PendingWithdrawalSum(builderIndex)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to calculate pending withdrawals: %w", err)
|
|
}
|
|
|
|
minActivationBalance := params.BeaconConfig().MinActivationBalance
|
|
requiredBalance := uint64(amount) + pendingPayments + pendingWithdrawals + minActivationBalance
|
|
|
|
if builderBalance < requiredBalance {
|
|
return fmt.Errorf("builder %d has insufficient balance: has %d, needs %d (amount=%d, pending_payments=%d, pending_withdrawals=%d, min_activation=%d)",
|
|
builderIndex, builderBalance, requiredBalance, amount, pendingPayments, pendingWithdrawals, minActivationBalance)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validatePayloadBidSignature verifies the BLS signature on a signed execution payload bid.
|
|
// It validates that the signature was created by the builder specified in the bid
|
|
// using the appropriate domain for the beacon builder.
|
|
func validatePayloadBidSignature(st state.ReadOnlyBeaconState, signedBid interfaces.ROSignedExecutionPayloadBid) error {
|
|
bid, err := signedBid.Bid()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get bid: %w", err)
|
|
}
|
|
|
|
builderPubkey := st.PubkeyAtIndex(bid.BuilderIndex())
|
|
publicKey, err := bls.PublicKeyFromBytes(builderPubkey[:])
|
|
if err != nil {
|
|
return fmt.Errorf("invalid builder public key: %w", err)
|
|
}
|
|
|
|
signatureBytes := signedBid.Signature()
|
|
signature, err := bls.SignatureFromBytes(signatureBytes[:])
|
|
if err != nil {
|
|
return fmt.Errorf("invalid signature format: %w", err)
|
|
}
|
|
|
|
currentEpoch := slots.ToEpoch(bid.Slot())
|
|
domain, err := signing.Domain(
|
|
st.Fork(),
|
|
currentEpoch,
|
|
params.BeaconConfig().DomainBeaconBuilder,
|
|
st.GenesisValidatorsRoot(),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to compute signing domain: %w", err)
|
|
}
|
|
|
|
signingRoot, err := signedBid.SigningRoot(domain)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to compute signing root: %w", err)
|
|
}
|
|
|
|
if !signature.Verify(publicKey, signingRoot[:]) {
|
|
return fmt.Errorf("signature verification failed: %w", signing.ErrSigFailedToVerify)
|
|
}
|
|
|
|
return nil
|
|
}
|