Skip to content
Merged
4 changes: 2 additions & 2 deletions execution/engineapi/engine_api_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (e *EngineServer) GetPayloadV3(ctx context.Context, payloadID hexutil.Bytes
return e.getPayload(ctx, decodedPayloadId, clparams.DenebVersion)
}

// Same as [GetPayloadV3], but returning ExecutionPayloadV4 (= ExecutionPayloadV3 + requests)
// Same as [GetPayloadV3], with executionRequests added to the response.
// See https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#engine_getpayloadv4
func (e *EngineServer) GetPayloadV4(ctx context.Context, payloadID hexutil.Bytes) (*engine_types.GetPayloadResponse, error) {
decodedPayloadId, err := decodePayloadID(payloadID)
Expand All @@ -152,7 +152,7 @@ func (e *EngineServer) GetPayloadV5(ctx context.Context, payloadID hexutil.Bytes
return e.getPayload(ctx, decodedPayloadId, clparams.FuluVersion)
}

// Same as [GetPayloadV5], but returning ExecutionPayloadV6
// Same as [GetPayloadV5], but returning ExecutionPayloadV4.
// See https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_getpayloadv6
func (e *EngineServer) GetPayloadV6(ctx context.Context, payloadID hexutil.Bytes) (*engine_types.GetPayloadResponse, error) {
decodedPayloadId, err := decodePayloadID(payloadID)
Expand Down
18 changes: 9 additions & 9 deletions execution/engineapi/engine_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,10 @@ func (s *EngineServer) getPayload(ctx context.Context, payloadId uint64, version
}

ts := header.Time
if (!s.config.IsCancun(ts) && version >= clparams.DenebVersion) ||
// Unlike later forks, Shanghai does not require an exact version match:
// engine_getPayloadV2 serves both Paris and Shanghai payloads.
if (s.config.IsShanghai(ts) && version < clparams.CapellaVersion) ||
(!s.config.IsCancun(ts) && version >= clparams.DenebVersion) ||
(s.config.IsCancun(ts) && version < clparams.DenebVersion) ||
(!s.config.IsPrague(ts) && version >= clparams.ElectraVersion) ||
(s.config.IsPrague(ts) && version < clparams.ElectraVersion) ||
Expand All @@ -715,15 +718,12 @@ func (s *EngineServer) getPayload(ctx context.Context, payloadId uint64, version
return nil, err
}

if version == clparams.FuluVersion {
if payload.BlobsBundle == nil {
payload.BlobsBundle = &engine_types.BlobsBundle{
Commitments: make([]hexutil.Bytes, 0),
Blobs: make([]hexutil.Bytes, 0),
Proofs: make([]hexutil.Bytes, 0),
}
if version >= clparams.DenebVersion {
proofsPerBlob := 1
if version >= clparams.FuluVersion {
proofsPerBlob = int(params.CellsPerExtBlob)
}
if len(payload.BlobsBundle.Commitments) != len(payload.BlobsBundle.Blobs) || len(payload.BlobsBundle.Proofs) != len(payload.BlobsBundle.Blobs)*int(params.CellsPerExtBlob) {
if len(payload.BlobsBundle.Commitments) != len(payload.BlobsBundle.Blobs) || len(payload.BlobsBundle.Proofs) != len(payload.BlobsBundle.Blobs)*proofsPerBlob {
return nil, fmt.Errorf("built invalid blobsBundle len(blobs)=%d len(commitments)=%d len(proofs)=%d", len(payload.BlobsBundle.Blobs), len(payload.BlobsBundle.Commitments), len(payload.BlobsBundle.Proofs))
}
}
Expand Down
203 changes: 194 additions & 9 deletions execution/engineapi/engine_server_getpayload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ import (
"github.com/erigontech/erigon/common/hexutil"
"github.com/erigontech/erigon/common/log/v3"
"github.com/erigontech/erigon/execution/builder"
"github.com/erigontech/erigon/execution/chain"
"github.com/erigontech/erigon/execution/execmodule"
"github.com/erigontech/erigon/execution/types"
"github.com/erigontech/erigon/rpc"
)

func TestGetPayloadV4RejectsNilRequests(t *testing.T) {
Expand All @@ -42,7 +44,7 @@ func TestGetPayloadV4RejectsNilRequests(t *testing.T) {
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: minimalPragueBlock(1, nil /* nil Requests */),
Block: minimalPayloadBlock(1, nil /* nil Requests */),
}, nil
},
}
Expand All @@ -62,7 +64,7 @@ func TestGetPayloadV4AcceptsEmptyRequestsBundle(t *testing.T) {
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: minimalPragueBlock(1, make(types.FlatRequests, 0)),
Block: minimalPayloadBlock(1, make(types.FlatRequests, 0)),
}, nil
},
}
Expand All @@ -76,6 +78,161 @@ func TestGetPayloadV4AcceptsEmptyRequestsBundle(t *testing.T) {
require.Len(t, resp.ExecutionRequests, 0)
}

func TestGetPayloadV4AcceptsMatchingBlobsBundleCounts(t *testing.T) {
t.Parallel()

const payloadID uint64 = 48
cfg := preOsakaChainConfig()
stub := &getPayloadStubModule{
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: blobPayloadBlock(cfg.ChainID, 0 /* wrapperVersion */, 1 /* commitments */, 1 /* blobs */, 1 /* proofs */),
BlockValue: uint256.NewInt(0),
}, nil
},
}
srv := newProposingEngineServerWithConfig(cfg, stub)

resp, err := srv.GetPayloadV4(context.Background(), payloadIDBytes(payloadID))

require.NoError(t, err)
require.NotNil(t, resp)
require.Len(t, resp.BlobsBundle.Blobs, 1)
require.Len(t, resp.BlobsBundle.Proofs, 1)
}

func TestGetPayloadV3RejectsMismatchedBlobsBundleCounts(t *testing.T) {
t.Parallel()

const payloadID uint64 = 47
cfg := prePragueChainConfig()
stub := &getPayloadStubModule{
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: blobPayloadBlock(cfg.ChainID, 0 /* wrapperVersion */, 1 /* commitments */, 1 /* blobs */, 2 /* proofs */),
BlockValue: uint256.NewInt(0),
}, nil
},
}
srv := newProposingEngineServerWithConfig(cfg, stub)

resp, err := srv.GetPayloadV3(context.Background(), payloadIDBytes(payloadID))

require.ErrorContains(t, err, "built invalid blobsBundle")
require.Nil(t, resp)
}

func TestGetPayloadV6RejectsMismatchedBlobsBundleCounts(t *testing.T) {
t.Parallel()

const payloadID uint64 = 44
cfg := allForksChainConfig()
stub := &getPayloadStubModule{
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: blobPayloadBlock(cfg.ChainID, 1 /* wrapperVersion */, 1 /* commitments */, 1 /* blobs */, 1 /* proofs */),
BlockValue: uint256.NewInt(0),
}, nil
},
}
srv := newProposingEngineServerWithConfig(cfg, stub)

resp, err := srv.GetPayloadV6(context.Background(), payloadIDBytes(payloadID))

require.ErrorContains(t, err, "built invalid blobsBundle")
require.Nil(t, resp)
}

func TestGetPayloadV1AcceptsParisPayload(t *testing.T) {
t.Parallel()

const payloadID uint64 = 49
stub := &getPayloadStubModule{
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: minimalPayloadBlock(99, nil),
BlockValue: uint256.NewInt(0),
}, nil
},
}
srv := newProposingEngineServerWithConfig(parisShanghaiChainConfig(), stub)

resp, err := srv.GetPayloadV1(context.Background(), payloadIDBytes(payloadID))

require.NoError(t, err)
require.NotNil(t, resp)
require.Nil(t, resp.Withdrawals)
}

func TestGetPayloadV1RejectsShanghaiPayload(t *testing.T) {
t.Parallel()

const payloadID uint64 = 44
stub := &getPayloadStubModule{
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: minimalPayloadBlock(100, nil),
BlockValue: uint256.NewInt(0),
}, nil
},
}
srv := newProposingEngineServerWithConfig(parisShanghaiChainConfig(), stub)

resp, err := srv.GetPayloadV1(context.Background(), payloadIDBytes(payloadID))

require.Nil(t, resp)
var unsupported *rpc.UnsupportedForkError
require.ErrorAs(t, err, &unsupported)
}

func TestGetPayloadV2AcceptsParisPayload(t *testing.T) {
t.Parallel()

const payloadID uint64 = 45
stub := &getPayloadStubModule{
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: minimalPayloadBlock(99, nil),
BlockValue: uint256.NewInt(0),
}, nil
},
}
srv := newProposingEngineServerWithConfig(parisShanghaiChainConfig(), stub)

resp, err := srv.GetPayloadV2(context.Background(), payloadIDBytes(payloadID))

require.NoError(t, err)
require.NotNil(t, resp)
require.Nil(t, resp.ExecutionPayload.Withdrawals)
}

func TestGetPayloadV2AcceptsShanghaiPayload(t *testing.T) {
t.Parallel()

const payloadID uint64 = 46
stub := &getPayloadStubModule{
getAssembledBlockFunc: func(_ context.Context, id uint64) (execmodule.AssembledBlockResult, error) {
require.Equal(t, payloadID, id)
return execmodule.AssembledBlockResult{
Block: minimalPayloadBlock(100, nil),
BlockValue: uint256.NewInt(0),
}, nil
},
}
srv := newProposingEngineServerWithConfig(parisShanghaiChainConfig(), stub)

resp, err := srv.GetPayloadV2(context.Background(), payloadIDBytes(payloadID))

require.NoError(t, err)
require.NotNil(t, resp)
}

func TestAssembledBlockToPayloadResponseIncludesCanonicalEmptyBAL(t *testing.T) {
t.Parallel()

Expand All @@ -100,12 +257,13 @@ func TestAssembledBlockToPayloadResponseIncludesCanonicalEmptyBAL(t *testing.T)
require.Equal(t, hexutil.Bytes(emptyBAL), *resp.ExecutionPayload.BlockAccessList)
}

// newProposingEngineServerForGetPayloadTests returns a server on a Prague-window
// config: GetPayloadV4 is valid on Prague but invalid once Osaka activates.
func newProposingEngineServerForGetPayloadTests(stub execmodule.ExecutionModule) *EngineServer {
cfg := allForksChainConfig()
// GetPayloadV4 is valid on Prague but invalid once Osaka activates.
cfg.OsakaTime = nil
cfg.AmsterdamTime = nil
return newProposingEngineServerWithConfig(preOsakaChainConfig(), stub)
}

func newProposingEngineServerWithConfig(cfg *chain.Config, stub execmodule.ExecutionModule) *EngineServer {
return NewEngineServer(
log.New(),
cfg,
Expand All @@ -122,9 +280,13 @@ func newProposingEngineServerForGetPayloadTests(stub execmodule.ExecutionModule)
)
}

// minimalPragueBlock builds the smallest possible BlockWithReceipts for Prague
// (timestamp=1, BaseFee set, no transactions) with the given requests slice.
func minimalPragueBlock(timestamp uint64, requests types.FlatRequests) *types.BlockWithReceipts {
func parisShanghaiChainConfig() *chain.Config {
cfg := preCancunChainConfig()
cfg.ShanghaiTime = common.NewUint64(100)
return cfg
}

func minimalPayloadBlock(timestamp uint64, requests types.FlatRequests) *types.BlockWithReceipts {
baseFee := uint256.NewInt(1_000_000_000)
header := &types.Header{
Number: *uint256.NewInt(101),
Expand All @@ -139,6 +301,29 @@ func minimalPragueBlock(timestamp uint64, requests types.FlatRequests) *types.Bl
}
}

func blobPayloadBlock(chainID *uint256.Int, wrapperVersion byte, commitments, blobs, proofs int) *types.BlockWithReceipts {
to := common.Address{0x01}
wrappedTxn := &types.BlobTxWrapper{WrapperVersion: wrapperVersion}
wrappedTxn.Tx.To = &to
wrappedTxn.Tx.ChainID = *chainID
wrappedTxn.Tx.BlobVersionedHashes = []common.Hash{{0x01}}
wrappedTxn.Commitments = make(types.BlobKzgs, commitments)
wrappedTxn.Blobs = make(types.Blobs, blobs)
wrappedTxn.Proofs = make(types.KZGProofs, proofs)

header := &types.Header{
Number: *uint256.NewInt(101),
Time: 1,
BaseFee: uint256.NewInt(1_000_000_000),
GasLimit: 30_000_000,
}
block := types.NewBlock(header, []types.Transaction{wrappedTxn}, nil, nil, nil)
return &types.BlockWithReceipts{
Block: block,
Requests: make(types.FlatRequests, 0),
}
}

func payloadIDBytes(payloadID uint64) hexutil.Bytes {
payloadBytes := make(hexutil.Bytes, 8)
binary.BigEndian.PutUint64(payloadBytes, payloadID)
Expand Down
19 changes: 19 additions & 0 deletions execution/engineapi/testing_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,25 @@ func preCancunChainConfig() *chain.Config {
return cfg
}

// prePragueChainConfig returns a chain config where Cancun is active but
// Prague and later forks are NOT activated.
func prePragueChainConfig() *chain.Config {
cfg := allForksChainConfig()
cfg.PragueTime = nil
cfg.OsakaTime = nil
cfg.AmsterdamTime = nil
return cfg
}

// preOsakaChainConfig returns a chain config where Prague is active but
// Osaka and later forks are NOT activated.
func preOsakaChainConfig() *chain.Config {
cfg := allForksChainConfig()
cfg.OsakaTime = nil
cfg.AmsterdamTime = nil
return cfg
}

// preAmsterdamChainConfig returns a chain config where Osaka is active but
// Amsterdam (Glamsterdam) is NOT activated.
func preAmsterdamChainConfig() *chain.Config {
Expand Down
Loading