From a28c6c8145333e10f724f71bce9c14e436605f8d Mon Sep 17 00:00:00 2001 From: terence Date: Wed, 18 Feb 2026 12:14:21 -0800 Subject: [PATCH] Add gloas block gossip changes (#16368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This updates beacon-block gossip validation for Gloas to use the signed execution payload bid instead of the execution payload. It removes execution‑payload‑based checks and introduces bid‑based checks, plus stubs for parent‑payload checks pending blockchain package support Reference: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#beacon_block fixes #https://github.com/OffchainLabs/prysm/issues/16372 --- beacon-chain/sync/BUILD.bazel | 2 + .../sync/validate_beacon_block_gloas.go | 61 +++++++++++++++ .../sync/validate_beacon_block_gloas_test.go | 75 +++++++++++++++++++ beacon-chain/sync/validate_beacon_blocks.go | 19 ++++- ...loas-beacon-block-bid-gossip-validation.md | 3 + 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 beacon-chain/sync/validate_beacon_block_gloas.go create mode 100644 beacon-chain/sync/validate_beacon_block_gloas_test.go create mode 100644 changelog/terence_gloas-beacon-block-bid-gossip-validation.md diff --git a/beacon-chain/sync/BUILD.bazel b/beacon-chain/sync/BUILD.bazel index 42b84ad1f7..dc01ca025a 100644 --- a/beacon-chain/sync/BUILD.bazel +++ b/beacon-chain/sync/BUILD.bazel @@ -54,6 +54,7 @@ go_library( "validate_aggregate_proof.go", "validate_attester_slashing.go", "validate_beacon_attestation.go", + "validate_beacon_block_gloas.go", "validate_beacon_blocks.go", "validate_blob.go", "validate_bls_to_execution_change.go", @@ -211,6 +212,7 @@ go_test( "validate_aggregate_proof_test.go", "validate_attester_slashing_test.go", "validate_beacon_attestation_test.go", + "validate_beacon_block_gloas_test.go", "validate_beacon_blocks_test.go", "validate_blob_test.go", "validate_bls_to_execution_change_test.go", diff --git a/beacon-chain/sync/validate_beacon_block_gloas.go b/beacon-chain/sync/validate_beacon_block_gloas.go new file mode 100644 index 0000000000..712e74c49f --- /dev/null +++ b/beacon-chain/sync/validate_beacon_block_gloas.go @@ -0,0 +1,61 @@ +package sync + +import ( + "context" + + "github.com/OffchainLabs/prysm/v7/config/params" + consensusblocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks" + "github.com/OffchainLabs/prysm/v7/consensus-types/interfaces" + "github.com/OffchainLabs/prysm/v7/runtime/version" + "github.com/OffchainLabs/prysm/v7/time/slots" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/pkg/errors" +) + +// validateExecutionPayloadBid validates execution payload bid gossip rules. +// [REJECT] The bid's parent (defined by bid.parent_block_root) equals the block's parent (defined by block.parent_root). +// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the consensus layer -- +// i.e. validate that len(bid.blob_kzg_commitments) <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block +func (s *Service) validateExecutionPayloadBid(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock) (pubsub.ValidationResult, error) { + if blk.Version() < version.Gloas { + return pubsub.ValidationAccept, nil + } + signedBid, err := blk.Body().SignedExecutionPayloadBid() + if err != nil { + return pubsub.ValidationIgnore, errors.Wrap(err, "unable to read bid from block") + } + wrappedBid, err := consensusblocks.WrappedROSignedExecutionPayloadBid(signedBid) + if err != nil { + return pubsub.ValidationIgnore, errors.Wrap(err, "unable to wrap signed execution payload bid") + } + bid, err := wrappedBid.Bid() + if err != nil { + return pubsub.ValidationIgnore, errors.Wrap(err, "unable to read bid from signed execution payload bid") + } + + if bid.ParentBlockRoot() != blk.ParentRoot() { + return pubsub.ValidationReject, errors.New("bid parent block root does not match block parent root") + } + + maxBlobsPerBlock := params.BeaconConfig().MaxBlobsPerBlockAtEpoch(slots.ToEpoch(blk.Slot())) + if bid.BlobKzgCommitmentCount() > uint64(maxBlobsPerBlock) { + return pubsub.ValidationReject, errors.Wrapf(errRejectCommitmentLen, "%d > %d", bid.BlobKzgCommitmentCount(), maxBlobsPerBlock) + } + return pubsub.ValidationAccept, nil +} + +// validateExecutionPayloadBidParentSeen validates parent payload gossip rules. +// [IGNORE] The block's parent execution payload (defined by bid.parent_block_hash) has been seen +// (via gossip or non-gossip sources) (a client MAY queue blocks for processing once the parent payload is retrieved). +func (s *Service) validateExecutionPayloadBidParentSeen(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock) (pubsub.ValidationResult, error) { + // TODO: Requires blockchain service changes to expose parent payload seen status + return pubsub.ValidationAccept, nil +} + +// validateExecutionPayloadBidParentValid validates parent payload verification status. +// If execution_payload verification of block's execution payload parent by an execution node is complete: +// [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation. +func (s *Service) validateExecutionPayloadBidParentValid(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock) (pubsub.ValidationResult, error) { + // TODO: Requires blockchain service changes to expose execution payload parent validation status. + return pubsub.ValidationAccept, nil +} diff --git a/beacon-chain/sync/validate_beacon_block_gloas_test.go b/beacon-chain/sync/validate_beacon_block_gloas_test.go new file mode 100644 index 0000000000..d1be4808ba --- /dev/null +++ b/beacon-chain/sync/validate_beacon_block_gloas_test.go @@ -0,0 +1,75 @@ +package sync + +import ( + "context" + "testing" + + fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams" + "github.com/OffchainLabs/prysm/v7/config/params" + "github.com/OffchainLabs/prysm/v7/consensus-types/blocks" + "github.com/OffchainLabs/prysm/v7/encoding/bytesutil" + "github.com/OffchainLabs/prysm/v7/testing/util" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/stretchr/testify/require" +) + +func TestValidateExecutionPayloadBid_Accept(t *testing.T) { + params.SetupTestConfigCleanup(t) + ctx := context.Background() + + parentRoot := bytesutil.PadTo([]byte{0x01}, fieldparams.RootLength) + block := util.NewBeaconBlockGloas() + block.Block.ParentRoot = parentRoot + block.Block.Body.SignedExecutionPayloadBid.Message.ParentBlockRoot = parentRoot + block.Block.Body.SignedExecutionPayloadBid.Message.BlobKzgCommitments = nil + + wsb, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + s := &Service{} + res, err := s.validateExecutionPayloadBid(ctx, wsb.Block()) + require.NoError(t, err) + require.Equal(t, pubsub.ValidationAccept, res) +} + +func TestValidateExecutionPayloadBid_RejectParentRootMismatch(t *testing.T) { + params.SetupTestConfigCleanup(t) + ctx := context.Background() + + block := util.NewBeaconBlockGloas() + block.Block.ParentRoot = bytesutil.PadTo([]byte{0x01}, fieldparams.RootLength) + block.Block.Body.SignedExecutionPayloadBid.Message.ParentBlockRoot = bytesutil.PadTo([]byte{0x02}, fieldparams.RootLength) + + wsb, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + s := &Service{} + res, err := s.validateExecutionPayloadBid(ctx, wsb.Block()) + require.Error(t, err) + require.Equal(t, pubsub.ValidationReject, res) +} + +func TestValidateExecutionPayloadBid_RejectTooManyCommitments(t *testing.T) { + params.SetupTestConfigCleanup(t) + ctx := context.Background() + + parentRoot := bytesutil.PadTo([]byte{0x01}, fieldparams.RootLength) + block := util.NewBeaconBlockGloas() + block.Block.ParentRoot = parentRoot + block.Block.Body.SignedExecutionPayloadBid.Message.ParentBlockRoot = parentRoot + + maxBlobs := params.BeaconConfig().MaxBlobsPerBlockAtEpoch(0) + commitments := make([][]byte, maxBlobs+1) + for i := range commitments { + commitments[i] = bytesutil.PadTo([]byte{0x02}, fieldparams.BLSPubkeyLength) + } + block.Block.Body.SignedExecutionPayloadBid.Message.BlobKzgCommitments = commitments + + wsb, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + s := &Service{} + res, err := s.validateExecutionPayloadBid(ctx, wsb.Block()) + require.Error(t, err) + require.Equal(t, pubsub.ValidationReject, res) +} diff --git a/beacon-chain/sync/validate_beacon_blocks.go b/beacon-chain/sync/validate_beacon_blocks.go index cd4236a9c3..9baf520b19 100644 --- a/beacon-chain/sync/validate_beacon_blocks.go +++ b/beacon-chain/sync/validate_beacon_blocks.go @@ -127,6 +127,9 @@ func (s *Service) validateBeaconBlockPubSub(ctx context.Context, pid peer.ID, ms log.WithError(err).WithFields(getBlockFields(blk)).Debug("Received block with an invalid parent") return pubsub.ValidationReject, err } + if res, err := s.validateExecutionPayloadBidParentValid(ctx, blk.Block()); err != nil { + return res, err + } s.pendingQueueLock.RLock() if s.seenPendingBlocks[blockRoot] { @@ -198,6 +201,16 @@ func (s *Service) validateBeaconBlockPubSub(ctx context.Context, pid peer.ID, ms log.WithError(err).WithFields(getBlockFields(blk)).Debug("Could not identify parent for block") return pubsub.ValidationIgnore, err } + if res, err := s.validateExecutionPayloadBidParentSeen(ctx, blk.Block()); err != nil { + return res, err + } + + if res, err := s.validateExecutionPayloadBid(ctx, blk.Block()); err != nil { + if res == pubsub.ValidationReject { + s.setBadBlock(ctx, blockRoot) + } + return res, err + } err = s.validateBeaconBlock(ctx, blk, blockRoot) if err != nil { @@ -365,7 +378,7 @@ func (s *Service) blockVerifyingState(ctx context.Context, blk interfaces.ReadOn } func validateDenebBeaconBlock(blk interfaces.ReadOnlyBeaconBlock) error { - if blk.Version() < version.Deneb { + if blk.Version() < version.Deneb || blk.Version() >= version.Gloas { return nil } commits, err := blk.Body().BlobKzgCommitments() @@ -398,6 +411,10 @@ func validateDenebBeaconBlock(blk interfaces.ReadOnlyBeaconBlock) error { // [IGNORE] The block's parent (defined by block.parent_root) passes all validation (including execution // node verification of the block.body.execution_payload). func (s *Service) validateBellatrixBeaconBlock(ctx context.Context, verifyingState state.ReadOnlyBeaconState, blk interfaces.ReadOnlyBeaconBlock) error { + if blk.Version() >= version.Gloas { + return nil + } + // Error if block and state are not the same version if verifyingState.Version() != blk.Version() { return errors.New("block and state are not the same version") diff --git a/changelog/terence_gloas-beacon-block-bid-gossip-validation.md b/changelog/terence_gloas-beacon-block-bid-gossip-validation.md new file mode 100644 index 0000000000..63a6fcca65 --- /dev/null +++ b/changelog/terence_gloas-beacon-block-bid-gossip-validation.md @@ -0,0 +1,3 @@ +### Added + +- Add Gloas beacon block gossip validation for execution payload bids