mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Add bundle v2 support for submit blind block (#15503)
* Add bundle v2 support for submit blind block * add tests --------- Co-authored-by: terence tsao <terence@prysmaticlabs.com>
This commit is contained in:
@@ -50,6 +50,7 @@ go_test(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
|
||||
@@ -101,7 +101,7 @@ type BuilderClient interface {
|
||||
NodeURL() string
|
||||
GetHeader(ctx context.Context, slot primitives.Slot, parentHash [32]byte, pubkey [48]byte) (SignedBid, error)
|
||||
RegisterValidator(ctx context.Context, svr []*ethpb.SignedValidatorRegistrationV1) error
|
||||
SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, *v1.BlobsBundle, error)
|
||||
SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error)
|
||||
Status(ctx context.Context) error
|
||||
}
|
||||
|
||||
@@ -446,6 +446,9 @@ func sszValidatorRegisterRequest(svr []*ethpb.SignedValidatorRegistrationV1) ([]
|
||||
var errResponseVersionMismatch = errors.New("builder API response uses a different version than requested in " + api.VersionHeader + " header")
|
||||
|
||||
func getVersionsBlockToPayload(blockVersion int) (int, error) {
|
||||
if blockVersion >= version.Fulu {
|
||||
return version.Fulu, nil
|
||||
}
|
||||
if blockVersion >= version.Deneb {
|
||||
return version.Deneb, nil
|
||||
}
|
||||
@@ -460,7 +463,7 @@ func getVersionsBlockToPayload(blockVersion int) (int, error) {
|
||||
|
||||
// SubmitBlindedBlock calls the builder API endpoint that binds the validator to the builder and submits the block.
|
||||
// The response is the full execution payload used to create the blinded block.
|
||||
func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, *v1.BlobsBundle, error) {
|
||||
func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error) {
|
||||
body, postOpts, err := c.buildBlindedBlockRequest(sb)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -558,7 +561,7 @@ func (c *Client) buildBlindedBlockRequest(sb interfaces.ReadOnlySignedBeaconBloc
|
||||
func (c *Client) parseBlindedBlockResponse(
|
||||
respBytes []byte,
|
||||
forkVersion int,
|
||||
) (interfaces.ExecutionData, *v1.BlobsBundle, error) {
|
||||
) (interfaces.ExecutionData, v1.BlobsBundler, error) {
|
||||
if c.sszEnabled {
|
||||
return c.parseBlindedBlockResponseSSZ(respBytes, forkVersion)
|
||||
}
|
||||
@@ -568,8 +571,18 @@ func (c *Client) parseBlindedBlockResponse(
|
||||
func (c *Client) parseBlindedBlockResponseSSZ(
|
||||
respBytes []byte,
|
||||
forkVersion int,
|
||||
) (interfaces.ExecutionData, *v1.BlobsBundle, error) {
|
||||
if forkVersion >= version.Deneb {
|
||||
) (interfaces.ExecutionData, v1.BlobsBundler, error) {
|
||||
if forkVersion >= version.Fulu {
|
||||
payloadAndBlobs := &v1.ExecutionPayloadDenebAndBlobsBundleV2{}
|
||||
if err := payloadAndBlobs.UnmarshalSSZ(respBytes); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unable to unmarshal ExecutionPayloadDenebAndBlobsBundleV2 SSZ")
|
||||
}
|
||||
ed, err := blocks.NewWrappedExecutionData(payloadAndBlobs.Payload)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "unable to wrap execution data for %s", version.String(forkVersion))
|
||||
}
|
||||
return ed, payloadAndBlobs.BlobsBundle, nil
|
||||
} else if forkVersion >= version.Deneb {
|
||||
payloadAndBlobs := &v1.ExecutionPayloadDenebAndBlobsBundle{}
|
||||
if err := payloadAndBlobs.UnmarshalSSZ(respBytes); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unable to unmarshal ExecutionPayloadDenebAndBlobsBundle SSZ")
|
||||
|
||||
@@ -2,6 +2,7 @@ package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v6/api/server/structs"
|
||||
"github.com/OffchainLabs/prysm/v6/config/params"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
|
||||
v1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1"
|
||||
@@ -1573,3 +1575,166 @@ func TestRequestLogger(t *testing.T) {
|
||||
err = c.Status(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetVersionsBlockToPayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
blockVersion int
|
||||
expectedVersion int
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Fulu version",
|
||||
blockVersion: 6, // version.Fulu
|
||||
expectedVersion: 6,
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Deneb version",
|
||||
blockVersion: 4, // version.Deneb
|
||||
expectedVersion: 4,
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Capella version",
|
||||
blockVersion: 3, // version.Capella
|
||||
expectedVersion: 3,
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Bellatrix version",
|
||||
blockVersion: 2, // version.Bellatrix
|
||||
expectedVersion: 2,
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Unsupported version",
|
||||
blockVersion: 0,
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
version, err := getVersionsBlockToPayload(tt.blockVersion)
|
||||
if tt.expectedError {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedVersion, version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBlindedBlockResponseSSZ_WithBlobsBundleV2(t *testing.T) {
|
||||
c := &Client{sszEnabled: true}
|
||||
|
||||
// Create test payload
|
||||
payload := &v1.ExecutionPayloadDeneb{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
BlockNumber: 123456,
|
||||
GasLimit: 30000000,
|
||||
GasUsed: 21000,
|
||||
Timestamp: 1234567890,
|
||||
ExtraData: []byte("test-extra-data"),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
Transactions: [][]byte{},
|
||||
Withdrawals: []*v1.Withdrawal{},
|
||||
BlobGasUsed: 1024,
|
||||
ExcessBlobGas: 2048,
|
||||
}
|
||||
|
||||
// Create test BlobsBundleV2
|
||||
bundleV2 := &v1.BlobsBundleV2{
|
||||
KzgCommitments: [][]byte{make([]byte, 48), make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48), make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072), make([]byte, 131072)},
|
||||
}
|
||||
|
||||
// Test Fulu version (should use ExecutionPayloadDenebAndBlobsBundleV2)
|
||||
t.Run("Fulu version with BlobsBundleV2", func(t *testing.T) {
|
||||
payloadAndBlobsV2 := &v1.ExecutionPayloadDenebAndBlobsBundleV2{
|
||||
Payload: payload,
|
||||
BlobsBundle: bundleV2,
|
||||
}
|
||||
|
||||
respBytes, err := payloadAndBlobsV2.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
ed, bundle, err := c.parseBlindedBlockResponseSSZ(respBytes, 6) // version.Fulu
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ed)
|
||||
require.NotNil(t, bundle)
|
||||
|
||||
// Verify the bundle is BlobsBundleV2
|
||||
bundleV2Result, ok := bundle.(*v1.BlobsBundleV2)
|
||||
assert.Equal(t, true, ok, "Expected BlobsBundleV2 type")
|
||||
require.Equal(t, len(bundleV2.KzgCommitments), len(bundleV2Result.KzgCommitments))
|
||||
require.Equal(t, len(bundleV2.Proofs), len(bundleV2Result.Proofs))
|
||||
require.Equal(t, len(bundleV2.Blobs), len(bundleV2Result.Blobs))
|
||||
})
|
||||
|
||||
// Test Deneb version (should use regular BlobsBundle)
|
||||
t.Run("Deneb version with regular BlobsBundle", func(t *testing.T) {
|
||||
regularBundle := &v1.BlobsBundle{
|
||||
KzgCommitments: bundleV2.KzgCommitments,
|
||||
Proofs: bundleV2.Proofs,
|
||||
Blobs: bundleV2.Blobs,
|
||||
}
|
||||
|
||||
payloadAndBlobs := &v1.ExecutionPayloadDenebAndBlobsBundle{
|
||||
Payload: payload,
|
||||
BlobsBundle: regularBundle,
|
||||
}
|
||||
|
||||
respBytes, err := payloadAndBlobs.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
ed, bundle, err := c.parseBlindedBlockResponseSSZ(respBytes, 4) // version.Deneb
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ed)
|
||||
require.NotNil(t, bundle)
|
||||
|
||||
// Verify the bundle is regular BlobsBundle
|
||||
regularBundleResult, ok := bundle.(*v1.BlobsBundle)
|
||||
assert.Equal(t, true, ok, "Expected BlobsBundle type")
|
||||
require.Equal(t, len(regularBundle.KzgCommitments), len(regularBundleResult.KzgCommitments))
|
||||
})
|
||||
|
||||
// Test invalid SSZ data
|
||||
t.Run("Invalid SSZ data", func(t *testing.T) {
|
||||
invalidBytes := []byte("invalid-ssz-data")
|
||||
|
||||
ed, bundle, err := c.parseBlindedBlockResponseSSZ(invalidBytes, 6)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, true, ed == nil)
|
||||
assert.Equal(t, true, bundle == nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSubmitBlindedBlock_BlobsBundlerInterface(t *testing.T) {
|
||||
// Note: The full integration test is complex due to version detection logic
|
||||
// The key functionality is tested in the parseBlindedBlockResponseSSZ tests above
|
||||
// and in the mock service tests which verify the interface changes work correctly
|
||||
|
||||
t.Run("Interface signature verification", func(t *testing.T) {
|
||||
// This test verifies that the SubmitBlindedBlock method signature
|
||||
// has been updated to return BlobsBundler interface
|
||||
|
||||
client := &Client{}
|
||||
|
||||
// Verify the method exists with the correct signature
|
||||
// by using reflection or by checking it compiles with the interface
|
||||
var _ func(ctx context.Context, sb interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error) = client.SubmitBlindedBlock
|
||||
|
||||
// This test passes if the signature is correct
|
||||
assert.Equal(t, true, true)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (m MockClient) RegisterValidator(_ context.Context, svr []*ethpb.SignedVali
|
||||
}
|
||||
|
||||
// SubmitBlindedBlock --
|
||||
func (MockClient) SubmitBlindedBlock(_ context.Context, _ interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, *v1.BlobsBundle, error) {
|
||||
func (MockClient) SubmitBlindedBlock(_ context.Context, _ interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ var ErrNoBuilder = errors.New("builder endpoint not configured")
|
||||
|
||||
// BlockBuilder defines the interface for interacting with the block builder
|
||||
type BlockBuilder interface {
|
||||
SubmitBlindedBlock(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, *v1.BlobsBundle, error)
|
||||
SubmitBlindedBlock(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error)
|
||||
GetHeader(ctx context.Context, slot primitives.Slot, parentHash [32]byte, pubKey [48]byte) (builder.SignedBid, error)
|
||||
RegisterValidator(ctx context.Context, reg []*ethpb.SignedValidatorRegistrationV1) error
|
||||
RegistrationByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error)
|
||||
@@ -87,7 +87,7 @@ func (s *Service) Stop() error {
|
||||
}
|
||||
|
||||
// SubmitBlindedBlock submits a blinded block to the builder relay network.
|
||||
func (s *Service) SubmitBlindedBlock(ctx context.Context, b interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, *v1.BlobsBundle, error) {
|
||||
func (s *Service) SubmitBlindedBlock(ctx context.Context, b interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "builder.SubmitBlindedBlock")
|
||||
defer span.End()
|
||||
start := time.Now()
|
||||
|
||||
@@ -29,6 +29,7 @@ type MockBuilderService struct {
|
||||
PayloadCapella *v1.ExecutionPayloadCapella
|
||||
PayloadDeneb *v1.ExecutionPayloadDeneb
|
||||
BlobBundle *v1.BlobsBundle
|
||||
BlobBundleV2 *v1.BlobsBundleV2
|
||||
ErrSubmitBlindedBlock error
|
||||
Bid *ethpb.SignedBuilderBid
|
||||
BidCapella *ethpb.SignedBuilderBidCapella
|
||||
@@ -46,7 +47,7 @@ func (s *MockBuilderService) Configured() bool {
|
||||
}
|
||||
|
||||
// SubmitBlindedBlock for mocking.
|
||||
func (s *MockBuilderService) SubmitBlindedBlock(_ context.Context, b interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, *v1.BlobsBundle, error) {
|
||||
func (s *MockBuilderService) SubmitBlindedBlock(_ context.Context, b interfaces.ReadOnlySignedBeaconBlock) (interfaces.ExecutionData, v1.BlobsBundler, error) {
|
||||
switch b.Version() {
|
||||
case version.Bellatrix:
|
||||
w, err := blocks.WrappedExecutionPayload(s.Payload)
|
||||
@@ -66,6 +67,16 @@ func (s *MockBuilderService) SubmitBlindedBlock(_ context.Context, b interfaces.
|
||||
return nil, nil, errors.Wrap(err, "could not wrap deneb payload")
|
||||
}
|
||||
return w, s.BlobBundle, s.ErrSubmitBlindedBlock
|
||||
case version.Fulu:
|
||||
w, err := blocks.WrappedExecutionPayloadDeneb(s.PayloadDeneb)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not wrap deneb payload for fulu")
|
||||
}
|
||||
// For Fulu, return BlobsBundleV2 if available, otherwise regular BlobsBundle
|
||||
if s.BlobBundleV2 != nil {
|
||||
return w, s.BlobBundleV2, s.ErrSubmitBlindedBlock
|
||||
}
|
||||
return w, s.BlobBundle, s.ErrSubmitBlindedBlock
|
||||
default:
|
||||
return nil, nil, errors.New("unknown block version for mocking")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func unblindBlobsSidecars(block interfaces.SignedBeaconBlock, bundle *enginev1.BlobsBundle) ([]*ethpb.BlobSidecar, error) {
|
||||
func unblindBlobsSidecars(block interfaces.SignedBeaconBlock, bundle enginev1.BlobsBundler) ([]*ethpb.BlobSidecar, error) {
|
||||
if block.Version() < version.Deneb {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -33,26 +33,30 @@ func unblindBlobsSidecars(block interfaces.SignedBeaconBlock, bundle *enginev1.B
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kzgCommitments := bundle.GetKzgCommitments()
|
||||
blobs := bundle.GetBlobs()
|
||||
proofs := bundle.GetProofs()
|
||||
|
||||
// Ensure there are equal counts of blobs/commitments/proofs.
|
||||
if len(bundle.KzgCommitments) != len(bundle.Blobs) {
|
||||
if len(kzgCommitments) != len(blobs) {
|
||||
return nil, errors.New("mismatch commitments count")
|
||||
}
|
||||
if len(bundle.Proofs) != len(bundle.Blobs) {
|
||||
if len(proofs) != len(blobs) {
|
||||
return nil, errors.New("mismatch proofs count")
|
||||
}
|
||||
|
||||
// Verify that commitments in the bundle match the block.
|
||||
if len(bundle.KzgCommitments) != len(blockCommitments) {
|
||||
if len(kzgCommitments) != len(blockCommitments) {
|
||||
return nil, errors.New("commitment count doesn't match block")
|
||||
}
|
||||
for i, commitment := range blockCommitments {
|
||||
if !bytes.Equal(bundle.KzgCommitments[i], commitment) {
|
||||
if !bytes.Equal(kzgCommitments[i], commitment) {
|
||||
return nil, errors.New("commitment value doesn't match block")
|
||||
}
|
||||
}
|
||||
|
||||
sidecars := make([]*ethpb.BlobSidecar, len(bundle.Blobs))
|
||||
for i, b := range bundle.Blobs {
|
||||
sidecars := make([]*ethpb.BlobSidecar, len(blobs))
|
||||
for i, b := range blobs {
|
||||
proof, err := consensusblocks.MerkleProofKZGCommitment(body, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -60,8 +64,8 @@ func unblindBlobsSidecars(block interfaces.SignedBeaconBlock, bundle *enginev1.B
|
||||
sidecars[i] = ðpb.BlobSidecar{
|
||||
Index: uint64(i),
|
||||
Blob: bytesutil.SafeCopyBytes(b),
|
||||
KzgCommitment: bytesutil.SafeCopyBytes(bundle.KzgCommitments[i]),
|
||||
KzgProof: bytesutil.SafeCopyBytes(bundle.Proofs[i]),
|
||||
KzgCommitment: bytesutil.SafeCopyBytes(kzgCommitments[i]),
|
||||
KzgProof: bytesutil.SafeCopyBytes(proofs[i]),
|
||||
SignedBlockHeader: header,
|
||||
CommitmentInclusionProof: proof,
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"testing"
|
||||
|
||||
consensusblocks "github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v6/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
)
|
||||
|
||||
func TestUnblinder_UnblindBlobSidecars_InvalidBundle(t *testing.T) {
|
||||
@@ -30,5 +32,122 @@ func TestUnblinder_UnblindBlobSidecars_InvalidBundle(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
_, err = unblindBlobsSidecars(wBlock, nil)
|
||||
assert.ErrorContains(t, "no valid bundle provided", err)
|
||||
|
||||
}
|
||||
|
||||
func TestUnblindBlobsSidecars_WithBlobsBundler(t *testing.T) {
|
||||
// Test that the function accepts BlobsBundler interface
|
||||
// This test focuses on the interface change rather than full integration
|
||||
|
||||
t.Run("Interface compatibility with BlobsBundle", func(t *testing.T) {
|
||||
// Create a simple pre-Deneb block that will return nil (no processing needed)
|
||||
wBlock, err := consensusblocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockCapella{
|
||||
Block: ðpb.BeaconBlockCapella{
|
||||
Body: ðpb.BeaconBlockBodyCapella{},
|
||||
},
|
||||
Signature: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with regular BlobsBundle
|
||||
bundle := &enginev1.BlobsBundle{
|
||||
KzgCommitments: [][]byte{make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072)},
|
||||
}
|
||||
|
||||
// This should work without error (returns nil for pre-Deneb)
|
||||
sidecars, err := unblindBlobsSidecars(wBlock, bundle)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, sidecars == nil)
|
||||
})
|
||||
|
||||
t.Run("Interface compatibility with BlobsBundleV2", func(t *testing.T) {
|
||||
// Create a simple pre-Deneb block that will return nil (no processing needed)
|
||||
wBlock, err := consensusblocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockCapella{
|
||||
Block: ðpb.BeaconBlockCapella{
|
||||
Body: ðpb.BeaconBlockBodyCapella{},
|
||||
},
|
||||
Signature: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with BlobsBundleV2 - this is the key test for the interface change
|
||||
bundleV2 := &enginev1.BlobsBundleV2{
|
||||
KzgCommitments: [][]byte{make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072)},
|
||||
}
|
||||
|
||||
// This should work without error (returns nil for pre-Deneb)
|
||||
sidecars, err := unblindBlobsSidecars(wBlock, bundleV2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, sidecars == nil)
|
||||
})
|
||||
|
||||
t.Run("Function signature accepts BlobsBundler interface", func(t *testing.T) {
|
||||
// This test verifies that the function signature has been updated to accept BlobsBundler
|
||||
// We test this by verifying the code compiles with both types
|
||||
|
||||
// Create a simple pre-Deneb block for the interface test
|
||||
wBlock, err := consensusblocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockCapella{
|
||||
Block: ðpb.BeaconBlockCapella{
|
||||
Body: ðpb.BeaconBlockBodyCapella{},
|
||||
},
|
||||
Signature: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify function accepts BlobsBundle through the interface
|
||||
var regularBundle enginev1.BlobsBundler = &enginev1.BlobsBundle{
|
||||
KzgCommitments: [][]byte{make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072)},
|
||||
}
|
||||
_, err = unblindBlobsSidecars(wBlock, regularBundle)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify function accepts BlobsBundleV2 through the interface
|
||||
var bundleV2 enginev1.BlobsBundler = &enginev1.BlobsBundleV2{
|
||||
KzgCommitments: [][]byte{make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072)},
|
||||
}
|
||||
_, err = unblindBlobsSidecars(wBlock, bundleV2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// If we get here, the interface change is working correctly
|
||||
assert.Equal(t, true, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnblindBlobsSidecars_PreDenebBlock(t *testing.T) {
|
||||
// Test with pre-Deneb block (should return nil sidecars)
|
||||
wBlock, err := consensusblocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockCapella{
|
||||
Block: ðpb.BeaconBlockCapella{
|
||||
Body: ðpb.BeaconBlockBodyCapella{},
|
||||
},
|
||||
Signature: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
bundle := &enginev1.BlobsBundle{
|
||||
KzgCommitments: [][]byte{make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072)},
|
||||
}
|
||||
|
||||
sidecars, err := unblindBlobsSidecars(wBlock, bundle)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, sidecars == nil)
|
||||
|
||||
// Also test with BlobsBundleV2
|
||||
bundleV2 := &enginev1.BlobsBundleV2{
|
||||
KzgCommitments: [][]byte{make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072)},
|
||||
}
|
||||
|
||||
sidecars, err = unblindBlobsSidecars(wBlock, bundleV2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, sidecars == nil)
|
||||
}
|
||||
|
||||
2
changelog/manu-peerdas-builder.md
Normal file
2
changelog/manu-peerdas-builder.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Added
|
||||
- Add support for parsing and handling `ExecutionPayloadAndBlobsBundleV2`.
|
||||
@@ -42,6 +42,7 @@ ssz_gen_marshal(
|
||||
"ExecutionPayloadHeaderDeneb",
|
||||
"ExecutionPayloadDeneb",
|
||||
"ExecutionPayloadDenebAndBlobsBundle",
|
||||
"ExecutionPayloadDenebAndBlobsBundleV2",
|
||||
"BlindedBlobsBundle",
|
||||
"BlobsBundle",
|
||||
"BlobsBundleV2",
|
||||
|
||||
@@ -1919,6 +1919,134 @@ func (e *ExecutionPayloadDenebAndBlobsBundle) HashTreeRootWith(hh *ssz.Hasher) (
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalSSZ ssz marshals the ExecutionPayloadDenebAndBlobsBundleV2 object
|
||||
func (e *ExecutionPayloadDenebAndBlobsBundleV2) MarshalSSZ() ([]byte, error) {
|
||||
return ssz.MarshalSSZ(e)
|
||||
}
|
||||
|
||||
// MarshalSSZTo ssz marshals the ExecutionPayloadDenebAndBlobsBundleV2 object to a target array
|
||||
func (e *ExecutionPayloadDenebAndBlobsBundleV2) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
offset := int(8)
|
||||
|
||||
// Offset (0) 'Payload'
|
||||
dst = ssz.WriteOffset(dst, offset)
|
||||
if e.Payload == nil {
|
||||
e.Payload = new(ExecutionPayloadDeneb)
|
||||
}
|
||||
offset += e.Payload.SizeSSZ()
|
||||
|
||||
// Offset (1) 'BlobsBundle'
|
||||
dst = ssz.WriteOffset(dst, offset)
|
||||
if e.BlobsBundle == nil {
|
||||
e.BlobsBundle = new(BlobsBundleV2)
|
||||
}
|
||||
offset += e.BlobsBundle.SizeSSZ()
|
||||
|
||||
// Field (0) 'Payload'
|
||||
if dst, err = e.Payload.MarshalSSZTo(dst); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Field (1) 'BlobsBundle'
|
||||
if dst, err = e.BlobsBundle.MarshalSSZTo(dst); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalSSZ ssz unmarshals the ExecutionPayloadDenebAndBlobsBundleV2 object
|
||||
func (e *ExecutionPayloadDenebAndBlobsBundleV2) UnmarshalSSZ(buf []byte) error {
|
||||
var err error
|
||||
size := uint64(len(buf))
|
||||
if size < 8 {
|
||||
return ssz.ErrSize
|
||||
}
|
||||
|
||||
tail := buf
|
||||
var o0, o1 uint64
|
||||
|
||||
// Offset (0) 'Payload'
|
||||
if o0 = ssz.ReadOffset(buf[0:4]); o0 > size {
|
||||
return ssz.ErrOffset
|
||||
}
|
||||
|
||||
if o0 != 8 {
|
||||
return ssz.ErrInvalidVariableOffset
|
||||
}
|
||||
|
||||
// Offset (1) 'BlobsBundle'
|
||||
if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 {
|
||||
return ssz.ErrOffset
|
||||
}
|
||||
|
||||
// Field (0) 'Payload'
|
||||
{
|
||||
buf = tail[o0:o1]
|
||||
if e.Payload == nil {
|
||||
e.Payload = new(ExecutionPayloadDeneb)
|
||||
}
|
||||
if err = e.Payload.UnmarshalSSZ(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Field (1) 'BlobsBundle'
|
||||
{
|
||||
buf = tail[o1:]
|
||||
if e.BlobsBundle == nil {
|
||||
e.BlobsBundle = new(BlobsBundleV2)
|
||||
}
|
||||
if err = e.BlobsBundle.UnmarshalSSZ(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SizeSSZ returns the ssz encoded size in bytes for the ExecutionPayloadDenebAndBlobsBundleV2 object
|
||||
func (e *ExecutionPayloadDenebAndBlobsBundleV2) SizeSSZ() (size int) {
|
||||
size = 8
|
||||
|
||||
// Field (0) 'Payload'
|
||||
if e.Payload == nil {
|
||||
e.Payload = new(ExecutionPayloadDeneb)
|
||||
}
|
||||
size += e.Payload.SizeSSZ()
|
||||
|
||||
// Field (1) 'BlobsBundle'
|
||||
if e.BlobsBundle == nil {
|
||||
e.BlobsBundle = new(BlobsBundleV2)
|
||||
}
|
||||
size += e.BlobsBundle.SizeSSZ()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// HashTreeRoot ssz hashes the ExecutionPayloadDenebAndBlobsBundleV2 object
|
||||
func (e *ExecutionPayloadDenebAndBlobsBundleV2) HashTreeRoot() ([32]byte, error) {
|
||||
return ssz.HashWithDefaultHasher(e)
|
||||
}
|
||||
|
||||
// HashTreeRootWith ssz hashes the ExecutionPayloadDenebAndBlobsBundleV2 object with a hasher
|
||||
func (e *ExecutionPayloadDenebAndBlobsBundleV2) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
indx := hh.Index()
|
||||
|
||||
// Field (0) 'Payload'
|
||||
if err = e.Payload.HashTreeRootWith(hh); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Field (1) 'BlobsBundle'
|
||||
if err = e.BlobsBundle.HashTreeRootWith(hh); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hh.Merkleize(indx)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalSSZ ssz marshals the ExecutionPayloadHeader object
|
||||
func (e *ExecutionPayloadHeader) MarshalSSZ() ([]byte, error) {
|
||||
return ssz.MarshalSSZ(e)
|
||||
|
||||
715
proto/engine/v1/execution_engine.pb.go
generated
715
proto/engine/v1/execution_engine.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -102,6 +102,11 @@ message ExecutionPayloadDenebAndBlobsBundle {
|
||||
BlobsBundle blobs_bundle = 2;
|
||||
}
|
||||
|
||||
message ExecutionPayloadDenebAndBlobsBundleV2 {
|
||||
ExecutionPayloadDeneb payload = 1;
|
||||
BlobsBundleV2 blobs_bundle = 2;
|
||||
}
|
||||
|
||||
message ExecutionPayloadDenebWithValueAndBlobsBundle {
|
||||
ExecutionPayloadDeneb payload = 1;
|
||||
bytes value = 2;
|
||||
|
||||
@@ -610,6 +610,85 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
|
||||
t.Run("execution bundle electra with deneb payload, blob data, and execution requests", func(t *testing.T) {
|
||||
// TODO #14351: update this test when geth updates
|
||||
})
|
||||
|
||||
t.Run("ExecutionPayloadDenebAndBlobsBundleV2 SSZ marshaling", func(t *testing.T) {
|
||||
payload := &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
BlockNumber: 123,
|
||||
GasLimit: 456,
|
||||
GasUsed: 789,
|
||||
Timestamp: 1000,
|
||||
ExtraData: []byte("extra"),
|
||||
BaseFeePerGas: bytesutil.PadTo(big.NewInt(1000000000).Bytes(), 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
Transactions: [][]byte{},
|
||||
Withdrawals: []*enginev1.Withdrawal{},
|
||||
BlobGasUsed: 1024,
|
||||
ExcessBlobGas: 2048,
|
||||
}
|
||||
|
||||
bundleV2 := &enginev1.BlobsBundleV2{
|
||||
KzgCommitments: [][]byte{make([]byte, 48), make([]byte, 48)},
|
||||
Proofs: [][]byte{make([]byte, 48), make([]byte, 48)},
|
||||
Blobs: [][]byte{make([]byte, 131072), make([]byte, 131072)},
|
||||
}
|
||||
|
||||
bundle := &enginev1.ExecutionPayloadDenebAndBlobsBundleV2{
|
||||
Payload: payload,
|
||||
BlobsBundle: bundleV2,
|
||||
}
|
||||
|
||||
sszBytes, err := bundle.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
unmarshaled := &enginev1.ExecutionPayloadDenebAndBlobsBundleV2{}
|
||||
err = unmarshaled.UnmarshalSSZ(sszBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.DeepEqual(t, bundle.Payload.BlockNumber, unmarshaled.Payload.BlockNumber)
|
||||
require.DeepEqual(t, bundle.Payload.GasLimit, unmarshaled.Payload.GasLimit)
|
||||
require.DeepEqual(t, bundle.BlobsBundle.KzgCommitments, unmarshaled.BlobsBundle.KzgCommitments)
|
||||
require.DeepEqual(t, bundle.BlobsBundle.Proofs, unmarshaled.BlobsBundle.Proofs)
|
||||
})
|
||||
|
||||
t.Run("BlobsBundleV2 SSZ marshaling", func(t *testing.T) {
|
||||
bundle := &enginev1.BlobsBundleV2{
|
||||
KzgCommitments: [][]byte{
|
||||
make([]byte, 48),
|
||||
make([]byte, 48),
|
||||
make([]byte, 48),
|
||||
},
|
||||
Proofs: [][]byte{
|
||||
make([]byte, 48),
|
||||
make([]byte, 48),
|
||||
make([]byte, 48),
|
||||
},
|
||||
Blobs: [][]byte{
|
||||
make([]byte, 131072),
|
||||
make([]byte, 131072),
|
||||
make([]byte, 131072),
|
||||
},
|
||||
}
|
||||
|
||||
sszBytes, err := bundle.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
unmarshaled := &enginev1.BlobsBundleV2{}
|
||||
err = unmarshaled.UnmarshalSSZ(sszBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(bundle.KzgCommitments), len(unmarshaled.KzgCommitments))
|
||||
require.Equal(t, len(bundle.Proofs), len(unmarshaled.Proofs))
|
||||
require.Equal(t, len(bundle.Blobs), len(unmarshaled.Blobs))
|
||||
require.DeepEqual(t, bundle.KzgCommitments, unmarshaled.KzgCommitments)
|
||||
require.DeepEqual(t, bundle.Proofs, unmarshaled.Proofs)
|
||||
require.DeepEqual(t, bundle.Blobs, unmarshaled.Blobs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPayloadIDBytes_MarshalUnmarshalJSON(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user