Skip to content

Commit 3a8ffd5

Browse files
fjlMariusVanDerWijden
authored andcommitted
beacon/engine, eth/catalyst: EIP-4844 updates for the engine API (ethereum#27736)
This is a spin-out from the EIP-4844 devnet branch, containing just the Engine API modifications and nothing else. The newPayloadV3 endpoint won't really work in this version, but we need the data structures for testing so I'd like to get this in early. Co-authored-by: Marius van der Wijden <[email protected]>
1 parent d84ccdb commit 3a8ffd5

File tree

7 files changed

+146
-38
lines changed

7 files changed

+146
-38
lines changed

beacon/engine/gen_ed.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon/engine/gen_epe.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon/engine/types.go

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/ethereum/go-ethereum/common"
2424
"github.com/ethereum/go-ethereum/common/hexutil"
2525
"github.com/ethereum/go-ethereum/core/types"
26+
"github.com/ethereum/go-ethereum/crypto/kzg4844"
2627
"github.com/ethereum/go-ethereum/trie"
2728
)
2829

@@ -61,6 +62,8 @@ type ExecutableData struct {
6162
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
6263
Transactions [][]byte `json:"transactions" gencodec:"required"`
6364
Withdrawals []*types.Withdrawal `json:"withdrawals"`
65+
DataGasUsed *uint64 `json:"dataGasUsed"`
66+
ExcessDataGas *uint64 `json:"excessDataGas"`
6467
}
6568

6669
// JSON type overrides for executableData.
@@ -73,13 +76,22 @@ type executableDataMarshaling struct {
7376
ExtraData hexutil.Bytes
7477
LogsBloom hexutil.Bytes
7578
Transactions []hexutil.Bytes
79+
DataGasUsed *hexutil.Uint64
80+
ExcessDataGas *hexutil.Uint64
7681
}
7782

7883
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
7984

8085
type ExecutionPayloadEnvelope struct {
8186
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
8287
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
88+
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
89+
}
90+
91+
type BlobsBundleV1 struct {
92+
Commitments []hexutil.Bytes `json:"commitments"`
93+
Proofs []hexutil.Bytes `json:"proofs"`
94+
Blobs []hexutil.Bytes `json:"blobs"`
8395
}
8496

8597
// JSON type overrides for ExecutionPayloadEnvelope.
@@ -152,14 +164,15 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
152164
// ExecutableDataToBlock constructs a block from executable data.
153165
// It verifies that the following fields:
154166
//
155-
// len(extraData) <= 32
156-
// uncleHash = emptyUncleHash
157-
// difficulty = 0
167+
// len(extraData) <= 32
168+
// uncleHash = emptyUncleHash
169+
// difficulty = 0
170+
// if versionedHashes != nil, versionedHashes match to blob transactions
158171
//
159172
// and that the blockhash of the constructed block matches the parameters. Nil
160173
// Withdrawals value will propagate through the returned block. Empty
161174
// Withdrawals value must be passed via non-nil, length 0 value in params.
162-
func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
175+
func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash) (*types.Block, error) {
163176
txs, err := decodeTransactions(params.Transactions)
164177
if err != nil {
165178
return nil, err
@@ -174,6 +187,18 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
174187
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
175188
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
176189
}
190+
var blobHashes []common.Hash
191+
for _, tx := range txs {
192+
blobHashes = append(blobHashes, tx.BlobHashes()...)
193+
}
194+
if len(blobHashes) != len(versionedHashes) {
195+
return nil, fmt.Errorf("invalid number of versionedHashes: %v blobHashes: %v", versionedHashes, blobHashes)
196+
}
197+
for i := 0; i < len(blobHashes); i++ {
198+
if blobHashes[i] != versionedHashes[i] {
199+
return nil, fmt.Errorf("invalid versionedHash at %v: %v blobHashes: %v", i, versionedHashes, blobHashes)
200+
}
201+
}
177202
// Only set withdrawalsRoot if it is non-nil. This allows CLs to use
178203
// ExecutableData before withdrawals are enabled by marshaling
179204
// Withdrawals as the json null value.
@@ -199,6 +224,8 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
199224
Extra: params.ExtraData,
200225
MixDigest: params.Random,
201226
WithdrawalsHash: withdrawalsRoot,
227+
ExcessDataGas: params.ExcessDataGas,
228+
DataGasUsed: params.DataGasUsed,
202229
}
203230
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
204231
if block.Hash() != params.BlockHash {
@@ -209,7 +236,7 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
209236

210237
// BlockToExecutableData constructs the ExecutableData structure by filling the
211238
// fields from the given block. It assumes the given block is post-merge block.
212-
func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadEnvelope {
239+
func BlockToExecutableData(block *types.Block, fees *big.Int, blobs []kzg4844.Blob, commitments []kzg4844.Commitment, proofs []kzg4844.Proof) *ExecutionPayloadEnvelope {
213240
data := &ExecutableData{
214241
BlockHash: block.Hash(),
215242
ParentHash: block.ParentHash(),
@@ -226,8 +253,20 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE
226253
Random: block.MixDigest(),
227254
ExtraData: block.Extra(),
228255
Withdrawals: block.Withdrawals(),
256+
DataGasUsed: block.DataGasUsed(),
257+
ExcessDataGas: block.ExcessDataGas(),
258+
}
259+
blobsBundle := BlobsBundleV1{
260+
Commitments: make([]hexutil.Bytes, 0),
261+
Blobs: make([]hexutil.Bytes, 0),
262+
Proofs: make([]hexutil.Bytes, 0),
263+
}
264+
for i := range blobs {
265+
blobsBundle.Blobs = append(blobsBundle.Blobs, hexutil.Bytes(blobs[i][:]))
266+
blobsBundle.Commitments = append(blobsBundle.Commitments, hexutil.Bytes(commitments[i][:]))
267+
blobsBundle.Proofs = append(blobsBundle.Proofs, hexutil.Bytes(proofs[i][:]))
229268
}
230-
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees}
269+
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &blobsBundle}
231270
}
232271

233272
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1

eth/catalyst/api.go

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ var caps = []string{
8181
"engine_exchangeTransitionConfigurationV1",
8282
"engine_getPayloadV1",
8383
"engine_getPayloadV2",
84+
"engine_getPayloadV3",
8485
"engine_newPayloadV1",
8586
"engine_newPayloadV2",
87+
"engine_newPayloadV3",
8688
"engine_getPayloadBodiesByHashV1",
8789
"engine_getPayloadBodiesByRangeV1",
8890
}
@@ -405,23 +407,13 @@ func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.Execu
405407
return api.getPayload(payloadID)
406408
}
407409

408-
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
409-
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
410-
data := api.localBlocks.get(payloadID, false)
411-
if data == nil {
412-
return nil, engine.UnknownPayload
413-
}
414-
return data, nil
410+
// GetPayloadV3 returns a cached payload by id.
411+
func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
412+
return api.getPayload(payloadID)
415413
}
416414

417-
// getFullPayload returns a cached payload by it. The difference is that this
418-
// function always expects a non-empty payload, but can also return empty one
419-
// if no transaction is executable.
420-
//
421-
// Note, this function is not a part of standard engine API, meant to be used
422-
// by consensus client mock in dev mode.
423-
func (api *ConsensusAPI) getFullPayload(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
424-
log.Trace("Engine API request received", "method", "GetFullPayload", "id", payloadID)
415+
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
416+
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
425417
data := api.localBlocks.get(payloadID, true)
426418
if data == nil {
427419
return nil, engine.UnknownPayload
@@ -434,7 +426,7 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
434426
if params.Withdrawals != nil {
435427
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
436428
}
437-
return api.newPayload(params)
429+
return api.newPayload(params, nil)
438430
}
439431

440432
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@@ -446,10 +438,29 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
446438
} else if params.Withdrawals != nil {
447439
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
448440
}
449-
return api.newPayload(params)
441+
if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
442+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun"))
443+
}
444+
return api.newPayload(params, nil)
450445
}
451446

452-
func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
447+
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
448+
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes *[]common.Hash) (engine.PayloadStatusV1, error) {
449+
if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
450+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV3 called pre-cancun"))
451+
}
452+
453+
if params.ExcessDataGas == nil {
454+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(fmt.Errorf("nil excessDataGas post-cancun"))
455+
}
456+
var hashes []common.Hash
457+
if versionedHashes != nil {
458+
hashes = *versionedHashes
459+
}
460+
return api.newPayload(params, hashes)
461+
}
462+
463+
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash) (engine.PayloadStatusV1, error) {
453464
// The locking here is, strictly, not required. Without these locks, this can happen:
454465
//
455466
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
@@ -467,9 +478,9 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.Payloa
467478
defer api.newPayloadLock.Unlock()
468479

469480
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
470-
block, err := engine.ExecutableDataToBlock(params)
481+
block, err := engine.ExecutableDataToBlock(params, versionedHashes)
471482
if err != nil {
472-
log.Debug("Invalid NewPayload params", "params", params, "error", err)
483+
log.Warn("Invalid NewPayload params", "params", params, "error", err)
473484
return engine.PayloadStatusV1{Status: engine.INVALID}, nil
474485
}
475486
// Stash away the last update to warn the user if the beacon client goes offline
@@ -730,8 +741,8 @@ func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
730741
return caps
731742
}
732743

733-
// GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which
734-
// allows for retrieval of a list of block bodies by the engine api.
744+
// GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
745+
// of block bodies by the engine api.
735746
func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBodyV1 {
736747
var bodies = make([]*engine.ExecutionPayloadBodyV1, len(hashes))
737748
for i, hash := range hashes {
@@ -741,8 +752,8 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin
741752
return bodies
742753
}
743754

744-
// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which
745-
// allows for retrieval of a range of block bodies by the engine api.
755+
// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
756+
// of block bodies by the engine api.
746757
func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV1, error) {
747758
if start == 0 || count == 0 {
748759
return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
@@ -768,19 +779,23 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 {
768779
if block == nil {
769780
return nil
770781
}
782+
771783
var (
772784
body = block.Body()
773785
txs = make([]hexutil.Bytes, len(body.Transactions))
774786
withdrawals = body.Withdrawals
775787
)
788+
776789
for j, tx := range body.Transactions {
777790
data, _ := tx.MarshalBinary()
778791
txs[j] = hexutil.Bytes(data)
779792
}
793+
780794
// Post-shanghai withdrawals MUST be set to empty slice instead of nil
781795
if withdrawals == nil && block.Header().WithdrawalsHash != nil {
782796
withdrawals = make([]*types.Withdrawal, 0)
783797
}
798+
784799
return &engine.ExecutionPayloadBodyV1{
785800
TransactionData: txs,
786801
Withdrawals: withdrawals,

0 commit comments

Comments
 (0)