beacon/engine, eth/catalyst, miner: EIP-4788 CL/EL protocol updates (#27872)

This PR makes EIP-4788 work in the engine API and miner. It also fixes some bugs related to 
EIP-4844 block processing and mining. Changes in detail:

- Header.BeaconRoot has been renamed to ParentBeaconRoot.
- The engine API now implements forkchoiceUpdatedV3
- newPayloadV3 method has been updated with the parentBeaconBlockRoot parameter
- beacon root is now applied to new blocks in miner
- For EIP-4844, block creation now updates the blobGasUsed field of the header
This commit is contained in:
Martin Holst Swende 2023-08-26 04:52:12 +02:00 committed by GitHub
parent cde462c6bf
commit 6aa88ccdd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 300 additions and 125 deletions

View File

@ -80,6 +80,7 @@ var (
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"} InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"} TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"} InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil} STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}

View File

@ -20,12 +20,14 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
} }
var enc PayloadAttributes var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Timestamp = hexutil.Uint64(p.Timestamp)
enc.Random = p.Random enc.Random = p.Random
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -36,6 +38,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
Random *common.Hash `json:"prevRandao" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
} }
var dec PayloadAttributes var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -56,5 +59,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.Withdrawals != nil { if dec.Withdrawals != nil {
p.Withdrawals = dec.Withdrawals p.Withdrawals = dec.Withdrawals
} }
if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot
}
return nil return nil
} }

View File

@ -35,6 +35,7 @@ type PayloadAttributes struct {
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
} }
// JSON type overrides for PayloadAttributes. // JSON type overrides for PayloadAttributes.
@ -171,7 +172,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// and that the blockhash of the constructed block matches the parameters. Nil // and that the blockhash of the constructed block matches the parameters. Nil
// Withdrawals value will propagate through the returned block. Empty // Withdrawals value will propagate through the returned block. Empty
// Withdrawals value must be passed via non-nil, length 0 value in params. // Withdrawals value must be passed via non-nil, length 0 value in params.
func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash) (*types.Block, error) { func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
txs, err := decodeTransactions(params.Transactions) txs, err := decodeTransactions(params.Transactions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -225,7 +226,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash)
WithdrawalsHash: withdrawalsRoot, WithdrawalsHash: withdrawalsRoot,
ExcessBlobGas: params.ExcessBlobGas, ExcessBlobGas: params.ExcessBlobGas,
BlobGasUsed: params.BlobGasUsed, BlobGasUsed: params.BlobGasUsed,
// TODO BeaconRoot ParentBeaconRoot: beaconRoot,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
if block.Hash() != params.BlockHash { if block.Hash() != params.BlockHash {
@ -255,7 +256,6 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
Withdrawals: block.Withdrawals(), Withdrawals: block.Withdrawals(),
BlobGasUsed: block.BlobGasUsed(), BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(), ExcessBlobGas: block.ExcessBlobGas(),
// TODO BeaconRoot
} }
bundle := BlobsBundleV1{ bundle := BlobsBundleV1{
Commitments: make([]hexutil.Bytes, 0), Commitments: make([]hexutil.Bytes, 0),

View File

@ -277,11 +277,11 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas) return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
case header.BlobGasUsed != nil: case header.BlobGasUsed != nil:
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed) return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
case header.BeaconRoot != nil: case header.ParentBeaconRoot != nil:
return fmt.Errorf("invalid beaconRoot, have %#x, expected nil", header.BeaconRoot) return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
} }
} else { } else {
if header.BeaconRoot == nil { if header.ParentBeaconRoot == nil {
return errors.New("header is missing beaconRoot") return errors.New("header is missing beaconRoot")
} }
if err := eip4844.VerifyEIP4844Header(parent, header); err != nil { if err := eip4844.VerifyEIP4844Header(parent, header); err != nil {

View File

@ -411,7 +411,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
excessBlobGas := eip4844.CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed) excessBlobGas := eip4844.CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed)
header.ExcessBlobGas = &excessBlobGas header.ExcessBlobGas = &excessBlobGas
header.BlobGasUsed = new(uint64) header.BlobGasUsed = new(uint64)
header.BeaconRoot = new(common.Hash) header.ParentBeaconRoot = new(common.Hash)
} }
return header return header
} }

View File

@ -483,6 +483,11 @@ func (g *Genesis) ToBlock() *types.Block {
withdrawals = make([]*types.Withdrawal, 0) withdrawals = make([]*types.Withdrawal, 0)
} }
if conf.IsCancun(num, g.Timestamp) { if conf.IsCancun(num, g.Timestamp) {
// EIP-4788: The parentBeaconBlockRoot of the genesis block is always
// the zero hash. This is because the genesis block does not have a parent
// by definition.
head.ParentBeaconRoot = new(common.Hash)
// EIP-4844 fields
head.ExcessBlobGas = g.ExcessBlobGas head.ExcessBlobGas = g.ExcessBlobGas
head.BlobGasUsed = g.BlobGasUsed head.BlobGasUsed = g.BlobGasUsed
if head.ExcessBlobGas == nil { if head.ExcessBlobGas == nil {
@ -491,9 +496,6 @@ func (g *Genesis) ToBlock() *types.Block {
if head.BlobGasUsed == nil { if head.BlobGasUsed == nil {
head.BlobGasUsed = new(uint64) head.BlobGasUsed = new(uint64)
} }
if head.BeaconRoot == nil {
head.BeaconRoot = new(common.Hash)
}
} }
} }
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals) return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals)

View File

@ -135,6 +135,9 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
receipt.TxHash = tx.Hash() receipt.TxHash = tx.Hash()
receipt.GasUsed = result.UsedGas receipt.GasUsed = result.UsedGas
receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob)
receipt.BlobGasPrice = tx.BlobGasFeeCap()
// If the transaction created a contract, store the creation address in the receipt. // If the transaction created a contract, store the creation address in the receipt.
if msg.To == nil { if msg.To == nil {
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())

View File

@ -412,7 +412,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
header.BlobGasUsed = &used header.BlobGasUsed = &used
beaconRoot := common.HexToHash("0xbeac00") beaconRoot := common.HexToHash("0xbeac00")
header.BeaconRoot = &beaconRoot header.ParentBeaconRoot = &beaconRoot
} }
// Assemble and return the final block for sealing // Assemble and return the final block for sealing
if config.IsShanghai(header.Number, header.Time) { if config.IsShanghai(header.Number, header.Time) {

View File

@ -91,8 +91,8 @@ type Header struct {
// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
// BeaconRoot was added by EIP-4788 and is ignored in legacy headers. // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
} }
// field type overrides for gencodec // field type overrides for gencodec
@ -300,9 +300,9 @@ func CopyHeader(h *Header) *Header {
cpy.BlobGasUsed = new(uint64) cpy.BlobGasUsed = new(uint64)
*cpy.BlobGasUsed = *h.BlobGasUsed *cpy.BlobGasUsed = *h.BlobGasUsed
} }
if h.BeaconRoot != nil { if h.ParentBeaconRoot != nil {
cpy.BeaconRoot = new(common.Hash) cpy.ParentBeaconRoot = new(common.Hash)
*cpy.BeaconRoot = *h.BeaconRoot *cpy.ParentBeaconRoot = *h.ParentBeaconRoot
} }
return &cpy return &cpy
} }
@ -383,7 +383,7 @@ func (b *Block) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee) return new(big.Int).Set(b.header.BaseFee)
} }
func (b *Block) BeaconRoot() *common.Hash { return b.header.BeaconRoot } func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
func (b *Block) ExcessBlobGas() *uint64 { func (b *Block) ExcessBlobGas() *uint64 {
var excessBlobGas *uint64 var excessBlobGas *uint64

View File

@ -35,7 +35,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -58,7 +58,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.WithdrawalsHash = h.WithdrawalsHash enc.WithdrawalsHash = h.WithdrawalsHash
enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed) enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.BeaconRoot = h.BeaconRoot enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -85,7 +85,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -160,8 +160,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.ExcessBlobGas != nil { if dec.ExcessBlobGas != nil {
h.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) h.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
} }
if dec.BeaconRoot != nil { if dec.ParentBeaconRoot != nil {
h.BeaconRoot = dec.BeaconRoot h.ParentBeaconRoot = dec.ParentBeaconRoot
} }
return nil return nil
} }

View File

@ -44,7 +44,7 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp2 := obj.WithdrawalsHash != nil _tmp2 := obj.WithdrawalsHash != nil
_tmp3 := obj.BlobGasUsed != nil _tmp3 := obj.BlobGasUsed != nil
_tmp4 := obj.ExcessBlobGas != nil _tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.BeaconRoot != nil _tmp5 := obj.ParentBeaconRoot != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 { if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 {
if obj.BaseFee == nil { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
@ -77,10 +77,10 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
} }
} }
if _tmp5 { if _tmp5 {
if obj.BeaconRoot == nil { if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.BeaconRoot[:]) w.WriteBytes(obj.ParentBeaconRoot[:])
} }
} }
w.ListEnd(_tmp0) w.ListEnd(_tmp0)

View File

@ -78,6 +78,7 @@ const (
var caps = []string{ var caps = []string{
"engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV1",
"engine_forkchoiceUpdatedV2", "engine_forkchoiceUpdatedV2",
"engine_forkchoiceUpdatedV3",
"engine_exchangeTransitionConfigurationV1", "engine_exchangeTransitionConfigurationV1",
"engine_getPayloadV1", "engine_getPayloadV1",
"engine_getPayloadV2", "engine_getPayloadV2",
@ -192,17 +193,36 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
return api.forkchoiceUpdated(update, payloadAttributes) return api.forkchoiceUpdated(update, payloadAttributes)
} }
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root in the payload attributes.
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil {
if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
return engine.STATUS_INVALID, engine.InvalidParams.With(err)
}
}
return api.forkchoiceUpdated(update, payloadAttributes)
}
func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error { func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error {
if !api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, attr.Timestamp) { c := api.eth.BlockChain().Config()
// Reject payload attributes with withdrawals before shanghai
if attr.Withdrawals != nil { // Verify withdrawals attribute for Shanghai.
return errors.New("withdrawals before shanghai") if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, attr.Timestamp); err != nil {
return fmt.Errorf("invalid withdrawals: %w", err)
} }
} else { // Verify beacon root attribute for Cancun.
// Reject payload attributes with nil withdrawals after shanghai if err := checkAttribute(c.IsCancun, attr.BeaconRoot != nil, attr.Timestamp); err != nil {
if attr.Withdrawals == nil { return fmt.Errorf("invalid parent beacon block root: %w", err)
return errors.New("missing withdrawals list")
} }
return nil
}
func checkAttribute(active func(*big.Int, uint64) bool, exists bool, time uint64) error {
if active(common.Big0, time) && !exists {
return errors.New("fork active, missing expected attribute")
}
if !active(common.Big0, time) && exists {
return errors.New("fork inactive, unexpected attribute set")
} }
return nil return nil
} }
@ -350,6 +370,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
FeeRecipient: payloadAttributes.SuggestedFeeRecipient, FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
Random: payloadAttributes.Random, Random: payloadAttributes.Random,
Withdrawals: payloadAttributes.Withdrawals, Withdrawals: payloadAttributes.Withdrawals,
BeaconRoot: payloadAttributes.BeaconRoot,
} }
id := args.Id() id := args.Id()
// If we already are busy generating this work, then we do not need // If we already are busy generating this work, then we do not need
@ -431,7 +452,7 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
if params.Withdrawals != nil { if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
} }
return api.newPayload(params, nil) return api.newPayload(params, nil, nil)
} }
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@ -446,26 +467,32 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) { if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun")) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun"))
} }
return api.newPayload(params, nil) return api.newPayload(params, nil, nil)
} }
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes *[]common.Hash) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV3 called pre-cancun"))
}
if params.ExcessBlobGas == nil { if params.ExcessBlobGas == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
} }
var hashes []common.Hash if params.BlobGasUsed == nil {
if versionedHashes != nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
hashes = *versionedHashes
} }
return api.newPayload(params, hashes) if versionedHashes == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
}
if beaconRoot == nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil parentBeaconBlockRoot post-cancun"))
} }
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash) (engine.PayloadStatusV1, error) { if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun"))
}
return api.newPayload(params, versionedHashes, beaconRoot)
}
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
// The locking here is, strictly, not required. Without these locks, this can happen: // The locking here is, strictly, not required. Without these locks, this can happen:
// //
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to // 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
@ -483,7 +510,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
defer api.newPayloadLock.Unlock() defer api.newPayloadLock.Unlock()
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
block, err := engine.ExecutableDataToBlock(params, versionedHashes) block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot)
if err != nil { if err != nil {
log.Warn("Invalid NewPayload params", "params", params, "error", err) log.Warn("Invalid NewPayload params", "params", params, "error", err)
return engine.PayloadStatusV1{Status: engine.INVALID}, nil return engine.PayloadStatusV1{Status: engine.INVALID}, nil

View File

@ -41,12 +41,14 @@ import (
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/mattn/go-colorable"
) )
var ( var (
@ -69,7 +71,10 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) {
} }
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: &config, Config: &config,
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, Alloc: core.GenesisAlloc{
testAddr: {Balance: testBalance},
params.BeaconRootsStorageAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")},
},
ExtraData: []byte("test genesis"), ExtraData: []byte("test genesis"),
Timestamp: 9000, Timestamp: 9000,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
@ -204,6 +209,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
Timestamp: blockParams.Timestamp, Timestamp: blockParams.Timestamp,
FeeRecipient: blockParams.SuggestedFeeRecipient, FeeRecipient: blockParams.SuggestedFeeRecipient,
Random: blockParams.Random, Random: blockParams.Random,
BeaconRoot: blockParams.BeaconRoot,
}).Id() }).Id()
execData, err := api.GetPayloadV1(payloadID) execData, err := api.GetPayloadV1(payloadID)
if err != nil { if err != nil {
@ -314,7 +320,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create the executable data %v", err) t.Fatalf("Failed to create the executable data %v", err)
} }
block, err := engine.ExecutableDataToBlock(*execData, nil) block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
@ -356,7 +362,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create the executable data %v", err) t.Fatalf("Failed to create the executable data %v", err)
} }
block, err := engine.ExecutableDataToBlock(*execData, nil) block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
@ -667,6 +673,7 @@ func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.Pay
FeeRecipient: params.SuggestedFeeRecipient, FeeRecipient: params.SuggestedFeeRecipient,
Random: params.Random, Random: params.Random,
Withdrawals: params.Withdrawals, Withdrawals: params.Withdrawals,
BeaconRoot: params.BeaconRoot,
} }
payload, err := api.eth.Miner().BuildPayload(args) payload, err := api.eth.Miner().BuildPayload(args)
if err != nil { if err != nil {
@ -988,7 +995,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
t.Fatal(testErr) t.Fatal(testErr)
} }
} }
block, err := engine.ExecutableDataToBlock(*execData, nil) block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
@ -1068,6 +1075,7 @@ func TestWithdrawals(t *testing.T) {
FeeRecipient: blockParams.SuggestedFeeRecipient, FeeRecipient: blockParams.SuggestedFeeRecipient,
Random: blockParams.Random, Random: blockParams.Random,
Withdrawals: blockParams.Withdrawals, Withdrawals: blockParams.Withdrawals,
BeaconRoot: blockParams.BeaconRoot,
}).Id() }).Id()
execData, err := api.GetPayloadV2(payloadID) execData, err := api.GetPayloadV2(payloadID)
if err != nil { if err != nil {
@ -1115,6 +1123,7 @@ func TestWithdrawals(t *testing.T) {
FeeRecipient: blockParams.SuggestedFeeRecipient, FeeRecipient: blockParams.SuggestedFeeRecipient,
Random: blockParams.Random, Random: blockParams.Random,
Withdrawals: blockParams.Withdrawals, Withdrawals: blockParams.Withdrawals,
BeaconRoot: blockParams.BeaconRoot,
}).Id() }).Id()
execData, err = api.GetPayloadV2(payloadID) execData, err = api.GetPayloadV2(payloadID)
if err != nil { if err != nil {
@ -1245,6 +1254,7 @@ func TestNilWithdrawals(t *testing.T) {
Timestamp: test.blockParams.Timestamp, Timestamp: test.blockParams.Timestamp,
FeeRecipient: test.blockParams.SuggestedFeeRecipient, FeeRecipient: test.blockParams.SuggestedFeeRecipient,
Random: test.blockParams.Random, Random: test.blockParams.Random,
BeaconRoot: test.blockParams.BeaconRoot,
}).Id() }).Id()
execData, err := api.GetPayloadV2(payloadID) execData, err := api.GetPayloadV2(payloadID)
if err != nil { if err != nil {
@ -1544,8 +1554,91 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
if got := len(envelope.BlobsBundle.Blobs); got != want { if got := len(envelope.BlobsBundle.Blobs); got != want {
t.Fatalf("invalid number of blobs: got %v, want %v", got, want) t.Fatalf("invalid number of blobs: got %v, want %v", got, want)
} }
_, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1)) _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
} }
// This checks that beaconRoot is applied to the state from the engine API.
func TestParentBeaconBlockRoot(t *testing.T) {
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
genesis, blocks := generateMergeChain(10, true)
// Set cancun time to last block + 5 seconds
time := blocks[len(blocks)-1].Time() + 5
genesis.Config.ShanghaiTime = &time
genesis.Config.CancunTime = &time
n, ethservice := startEthService(t, genesis, blocks)
ethservice.Merger().ReachTTD()
defer n.Close()
api := NewConsensusAPI(ethservice)
// 11: Build Shanghai block with no withdrawals.
parent := ethservice.BlockChain().CurrentHeader()
blockParams := engine.PayloadAttributes{
Timestamp: parent.Time + 5,
Withdrawals: make([]*types.Withdrawal, 0),
BeaconRoot: &common.Hash{42},
}
fcState := engine.ForkchoiceStateV1{
HeadBlockHash: parent.Hash(),
}
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
}
if resp.PayloadStatus.Status != engine.VALID {
t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
}
// 11: verify state root is the same as parent
payloadID := (&miner.BuildPayloadArgs{
Parent: fcState.HeadBlockHash,
Timestamp: blockParams.Timestamp,
FeeRecipient: blockParams.SuggestedFeeRecipient,
Random: blockParams.Random,
Withdrawals: blockParams.Withdrawals,
BeaconRoot: blockParams.BeaconRoot,
}).Id()
execData, err := api.GetPayloadV3(payloadID)
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
// 11: verify locally built block
if status, err := api.NewPayloadV3(*execData.ExecutionPayload, []common.Hash{}, &common.Hash{42}); err != nil {
t.Fatalf("error validating payload: %v", err)
} else if status.Status != engine.VALID {
t.Fatalf("invalid payload")
}
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
resp, err = api.ForkchoiceUpdatedV3(fcState, nil)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
}
if resp.PayloadStatus.Status != engine.VALID {
t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
}
// 11: verify beacon root was processed.
db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
if err != nil {
t.Fatalf("unable to load db: %v", err)
}
var (
timeIdx = common.BigToHash(big.NewInt(int64(execData.ExecutionPayload.Timestamp % 98304)))
rootIdx = common.BigToHash(big.NewInt(int64((execData.ExecutionPayload.Timestamp % 98304) + 98304)))
)
if num := db.GetState(params.BeaconRootsStorageAddress, timeIdx); num != timeIdx {
t.Fatalf("incorrect number stored: want %s, got %s", timeIdx, num)
}
if root := db.GetState(params.BeaconRootsStorageAddress, rootIdx); root != *blockParams.BeaconRoot {
t.Fatalf("incorrect root stored: want %s, got %s", *blockParams.BeaconRoot, root)
}
}

View File

@ -1345,6 +1345,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
if head.ExcessBlobGas != nil { if head.ExcessBlobGas != nil {
result["excessBlobGas"] = hexutil.Uint64(*head.ExcessBlobGas) result["excessBlobGas"] = hexutil.Uint64(*head.ExcessBlobGas)
} }
if head.ParentBeaconRoot != nil {
result["parentBeaconBlockRoot"] = head.ParentBeaconRoot
}
return result return result
} }

View File

@ -40,6 +40,7 @@ type BuildPayloadArgs struct {
FeeRecipient common.Address // The provided recipient address for collecting transaction fee FeeRecipient common.Address // The provided recipient address for collecting transaction fee
Random common.Hash // The provided randomness value Random common.Hash // The provided randomness value
Withdrawals types.Withdrawals // The provided withdrawals Withdrawals types.Withdrawals // The provided withdrawals
BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
} }
// Id computes an 8-byte identifier by hashing the components of the payload arguments. // Id computes an 8-byte identifier by hashing the components of the payload arguments.
@ -51,6 +52,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
hasher.Write(args.Random[:]) hasher.Write(args.Random[:])
hasher.Write(args.FeeRecipient[:]) hasher.Write(args.FeeRecipient[:])
rlp.Encode(hasher, args.Withdrawals) rlp.Encode(hasher, args.Withdrawals)
if args.BeaconRoot != nil {
hasher.Write(args.BeaconRoot[:])
}
var out engine.PayloadID var out engine.PayloadID
copy(out[:], hasher.Sum(nil)[:8]) copy(out[:], hasher.Sum(nil)[:8])
return out return out
@ -182,6 +186,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
coinbase: args.FeeRecipient, coinbase: args.FeeRecipient,
random: args.Random, random: args.Random,
withdrawals: args.Withdrawals, withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
noTxs: true, noTxs: true,
} }
empty := w.getSealingBlock(emptyParams) empty := w.getSealingBlock(emptyParams)
@ -212,6 +217,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
coinbase: args.FeeRecipient, coinbase: args.FeeRecipient,
random: args.Random, random: args.Random,
withdrawals: args.Withdrawals, withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
noTxs: false, noTxs: false,
} }

View File

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -738,34 +739,56 @@ func (w *worker) updateSnapshot(env *environment) {
} }
func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) { func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
var ( if tx.Type() == types.BlobTxType {
snap = env.state.Snapshot() return w.commitBlobTransaction(env, tx)
gp = env.gasPool.Gas() }
)
receipt, err := w.applyTransaction(env, tx)
if err != nil {
return nil, err
}
env.txs = append(env.txs, tx)
env.receipts = append(env.receipts, receipt)
return receipt.Logs, nil
}
func (w *worker) commitBlobTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
sc := tx.BlobTxSidecar()
if sc == nil {
panic("blob transaction without blobs in miner")
}
// Checking against blob gas limit: It's kind of ugly to perform this check here, but there // Checking against blob gas limit: It's kind of ugly to perform this check here, but there
// isn't really a better place right now. The blob gas limit is checked at block validation time // isn't really a better place right now. The blob gas limit is checked at block validation time
// and not during execution. This means core.ApplyTransaction will not return an error if the // and not during execution. This means core.ApplyTransaction will not return an error if the
// tx has too many blobs. So we have to explicitly check it here. // tx has too many blobs. So we have to explicitly check it here.
if (env.blobs+len(tx.BlobHashes()))*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { if (env.blobs+len(sc.Blobs))*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock {
return nil, errors.New("max data blobs reached") return nil, errors.New("max data blobs reached")
} }
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) receipt, err := w.applyTransaction(env, tx)
if err != nil { if err != nil {
env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp)
return nil, err return nil, err
} }
env.txs = append(env.txs, tx.WithoutBlobTxSidecar()) env.txs = append(env.txs, tx.WithoutBlobTxSidecar())
env.receipts = append(env.receipts, receipt) env.receipts = append(env.receipts, receipt)
if sc := tx.BlobTxSidecar(); sc != nil {
env.sidecars = append(env.sidecars, sc) env.sidecars = append(env.sidecars, sc)
env.blobs += len(sc.Blobs) env.blobs += len(sc.Blobs)
*env.header.BlobGasUsed += receipt.BlobGasUsed
return receipt.Logs, nil
} }
return receipt.Logs, nil // applyTransaction runs the transaction. If execution fails, state and gas pool are reverted.
func (w *worker) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) {
var (
snap = env.state.Snapshot()
gp = env.gasPool.Gas()
)
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig())
if err != nil {
env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp)
}
return receipt, err
} }
func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {
@ -860,6 +883,7 @@ type generateParams struct {
coinbase common.Address // The fee recipient address for including transaction coinbase common.Address // The fee recipient address for including transaction
random common.Hash // The randomness generated by beacon chain, empty before the merge random common.Hash // The randomness generated by beacon chain, empty before the merge
withdrawals types.Withdrawals // List of withdrawals to include in block. withdrawals types.Withdrawals // List of withdrawals to include in block.
beaconRoot *common.Hash // The beacon root (cancun field).
noTxs bool // Flag whether an empty block without any transaction is expected noTxs bool // Flag whether an empty block without any transaction is expected
} }
@ -912,6 +936,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil)
} }
} }
// Apply EIP-4844, EIP-4788.
if w.chainConfig.IsCancun(header.Number, header.Time) { if w.chainConfig.IsCancun(header.Number, header.Time) {
var excessBlobGas uint64 var excessBlobGas uint64
if w.chainConfig.IsCancun(parent.Number, parent.Time) { if w.chainConfig.IsCancun(parent.Number, parent.Time) {
@ -920,7 +945,9 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
// For the first post-fork block, both parent.data_gas_used and parent.excess_data_gas are evaluated as 0 // For the first post-fork block, both parent.data_gas_used and parent.excess_data_gas are evaluated as 0
excessBlobGas = eip4844.CalcExcessBlobGas(0, 0) excessBlobGas = eip4844.CalcExcessBlobGas(0, 0)
} }
header.BlobGasUsed = new(uint64)
header.ExcessBlobGas = &excessBlobGas header.ExcessBlobGas = &excessBlobGas
header.ParentBeaconRoot = genParams.beaconRoot
} }
// Run the consensus preparation with the default or customized consensus engine. // Run the consensus preparation with the default or customized consensus engine.
if err := w.engine.Prepare(w.chain, header); err != nil { if err := w.engine.Prepare(w.chain, header); err != nil {
@ -935,6 +962,11 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
log.Error("Failed to create sealing context", "err", err) log.Error("Failed to create sealing context", "err", err)
return nil, err return nil, err
} }
if header.ParentBeaconRoot != nil {
context := core.NewEVMBlockContext(header, w.chain, nil)
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, w.chainConfig, vm.Config{})
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
}
return env, nil return env, nil
} }

View File

@ -458,6 +458,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
coinbase: c.coinbase, coinbase: c.coinbase,
random: c.random, random: c.random,
withdrawals: nil, withdrawals: nil,
beaconRoot: nil,
noTxs: false, noTxs: false,
forceTime: true, forceTime: true,
}) })
@ -482,6 +483,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
coinbase: c.coinbase, coinbase: c.coinbase,
random: c.random, random: c.random,
withdrawals: nil, withdrawals: nil,
beaconRoot: nil,
noTxs: false, noTxs: false,
forceTime: true, forceTime: true,
}) })