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