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:
parent
cde462c6bf
commit
6aa88ccdd2
@ -80,6 +80,7 @@ var (
|
||||
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
|
||||
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
||||
|
||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||
|
@ -20,12 +20,14 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||
}
|
||||
var enc PayloadAttributes
|
||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||
enc.Random = p.Random
|
||||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||
enc.Withdrawals = p.Withdrawals
|
||||
enc.BeaconRoot = p.BeaconRoot
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
@ -36,6 +38,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||
}
|
||||
var dec PayloadAttributes
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
@ -56,5 +59,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
||||
if dec.Withdrawals != nil {
|
||||
p.Withdrawals = dec.Withdrawals
|
||||
}
|
||||
if dec.BeaconRoot != nil {
|
||||
p.BeaconRoot = dec.BeaconRoot
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ type PayloadAttributes struct {
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||
}
|
||||
|
||||
// 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
|
||||
// Withdrawals value will propagate through the returned block. Empty
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -225,7 +226,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash)
|
||||
WithdrawalsHash: withdrawalsRoot,
|
||||
ExcessBlobGas: params.ExcessBlobGas,
|
||||
BlobGasUsed: params.BlobGasUsed,
|
||||
// TODO BeaconRoot
|
||||
ParentBeaconRoot: beaconRoot,
|
||||
}
|
||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
|
||||
if block.Hash() != params.BlockHash {
|
||||
@ -255,7 +256,6 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
|
||||
Withdrawals: block.Withdrawals(),
|
||||
BlobGasUsed: block.BlobGasUsed(),
|
||||
ExcessBlobGas: block.ExcessBlobGas(),
|
||||
// TODO BeaconRoot
|
||||
}
|
||||
bundle := BlobsBundleV1{
|
||||
Commitments: make([]hexutil.Bytes, 0),
|
||||
|
@ -277,11 +277,11 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
|
||||
return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas)
|
||||
case header.BlobGasUsed != nil:
|
||||
return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed)
|
||||
case header.BeaconRoot != nil:
|
||||
return fmt.Errorf("invalid beaconRoot, have %#x, expected nil", header.BeaconRoot)
|
||||
case header.ParentBeaconRoot != nil:
|
||||
return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot)
|
||||
}
|
||||
} else {
|
||||
if header.BeaconRoot == nil {
|
||||
if header.ParentBeaconRoot == nil {
|
||||
return errors.New("header is missing beaconRoot")
|
||||
}
|
||||
if err := eip4844.VerifyEIP4844Header(parent, header); err != nil {
|
||||
|
@ -411,7 +411,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
|
||||
excessBlobGas := eip4844.CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed)
|
||||
header.ExcessBlobGas = &excessBlobGas
|
||||
header.BlobGasUsed = new(uint64)
|
||||
header.BeaconRoot = new(common.Hash)
|
||||
header.ParentBeaconRoot = new(common.Hash)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
@ -483,6 +483,11 @@ func (g *Genesis) ToBlock() *types.Block {
|
||||
withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
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.BlobGasUsed = g.BlobGasUsed
|
||||
if head.ExcessBlobGas == nil {
|
||||
@ -491,9 +496,6 @@ func (g *Genesis) ToBlock() *types.Block {
|
||||
if head.BlobGasUsed == nil {
|
||||
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)
|
||||
|
@ -135,6 +135,9 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
|
||||
receipt.TxHash = tx.Hash()
|
||||
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 msg.To == nil {
|
||||
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
|
||||
|
@ -412,7 +412,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
|
||||
header.BlobGasUsed = &used
|
||||
|
||||
beaconRoot := common.HexToHash("0xbeac00")
|
||||
header.BeaconRoot = &beaconRoot
|
||||
header.ParentBeaconRoot = &beaconRoot
|
||||
}
|
||||
// Assemble and return the final block for sealing
|
||||
if config.IsShanghai(header.Number, header.Time) {
|
||||
|
@ -91,8 +91,8 @@ type Header struct {
|
||||
// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
|
||||
|
||||
// BeaconRoot was added by EIP-4788 and is ignored in legacy headers.
|
||||
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
}
|
||||
|
||||
// field type overrides for gencodec
|
||||
@ -300,9 +300,9 @@ func CopyHeader(h *Header) *Header {
|
||||
cpy.BlobGasUsed = new(uint64)
|
||||
*cpy.BlobGasUsed = *h.BlobGasUsed
|
||||
}
|
||||
if h.BeaconRoot != nil {
|
||||
cpy.BeaconRoot = new(common.Hash)
|
||||
*cpy.BeaconRoot = *h.BeaconRoot
|
||||
if h.ParentBeaconRoot != nil {
|
||||
cpy.ParentBeaconRoot = new(common.Hash)
|
||||
*cpy.ParentBeaconRoot = *h.ParentBeaconRoot
|
||||
}
|
||||
return &cpy
|
||||
}
|
||||
@ -383,7 +383,7 @@ func (b *Block) BaseFee() *big.Int {
|
||||
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 {
|
||||
var excessBlobGas *uint64
|
||||
|
@ -35,7 +35,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" 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"`
|
||||
}
|
||||
var enc Header
|
||||
@ -58,7 +58,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||
enc.WithdrawalsHash = h.WithdrawalsHash
|
||||
enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
|
||||
enc.BeaconRoot = h.BeaconRoot
|
||||
enc.ParentBeaconRoot = h.ParentBeaconRoot
|
||||
enc.Hash = h.Hash()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
@ -85,7 +85,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" 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
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
@ -160,8 +160,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
if dec.ExcessBlobGas != nil {
|
||||
h.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||
}
|
||||
if dec.BeaconRoot != nil {
|
||||
h.BeaconRoot = dec.BeaconRoot
|
||||
if dec.ParentBeaconRoot != nil {
|
||||
h.ParentBeaconRoot = dec.ParentBeaconRoot
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||
_tmp2 := obj.WithdrawalsHash != nil
|
||||
_tmp3 := obj.BlobGasUsed != nil
|
||||
_tmp4 := obj.ExcessBlobGas != nil
|
||||
_tmp5 := obj.BeaconRoot != nil
|
||||
_tmp5 := obj.ParentBeaconRoot != nil
|
||||
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 {
|
||||
if obj.BaseFee == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
@ -77,10 +77,10 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||
}
|
||||
}
|
||||
if _tmp5 {
|
||||
if obj.BeaconRoot == nil {
|
||||
if obj.ParentBeaconRoot == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.BeaconRoot[:])
|
||||
w.WriteBytes(obj.ParentBeaconRoot[:])
|
||||
}
|
||||
}
|
||||
w.ListEnd(_tmp0)
|
||||
|
@ -78,6 +78,7 @@ const (
|
||||
var caps = []string{
|
||||
"engine_forkchoiceUpdatedV1",
|
||||
"engine_forkchoiceUpdatedV2",
|
||||
"engine_forkchoiceUpdatedV3",
|
||||
"engine_exchangeTransitionConfigurationV1",
|
||||
"engine_getPayloadV1",
|
||||
"engine_getPayloadV2",
|
||||
@ -192,17 +193,36 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
|
||||
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 {
|
||||
if !api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, attr.Timestamp) {
|
||||
// Reject payload attributes with withdrawals before shanghai
|
||||
if attr.Withdrawals != nil {
|
||||
return errors.New("withdrawals before shanghai")
|
||||
c := api.eth.BlockChain().Config()
|
||||
|
||||
// Verify withdrawals attribute for Shanghai.
|
||||
if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, attr.Timestamp); err != nil {
|
||||
return fmt.Errorf("invalid withdrawals: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Reject payload attributes with nil withdrawals after shanghai
|
||||
if attr.Withdrawals == nil {
|
||||
return errors.New("missing withdrawals list")
|
||||
// Verify beacon root attribute for Cancun.
|
||||
if err := checkAttribute(c.IsCancun, attr.BeaconRoot != nil, attr.Timestamp); err != nil {
|
||||
return fmt.Errorf("invalid parent beacon block root: %w", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -350,6 +370,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
||||
FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
|
||||
Random: payloadAttributes.Random,
|
||||
Withdrawals: payloadAttributes.Withdrawals,
|
||||
BeaconRoot: payloadAttributes.BeaconRoot,
|
||||
}
|
||||
id := args.Id()
|
||||
// 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 {
|
||||
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.
|
||||
@ -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) {
|
||||
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.
|
||||
func (api *ConsensusAPI) NewPayloadV3(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.InvalidParams.With(errors.New("newPayloadV3 called pre-cancun"))
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
||||
if params.ExcessBlobGas == nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
||||
}
|
||||
var hashes []common.Hash
|
||||
if versionedHashes != nil {
|
||||
hashes = *versionedHashes
|
||||
if params.BlobGasUsed == nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
|
||||
}
|
||||
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"))
|
||||
}
|
||||
|
||||
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) (engine.PayloadStatusV1, error) {
|
||||
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:
|
||||
//
|
||||
// 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()
|
||||
|
||||
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 {
|
||||
log.Warn("Invalid NewPayload params", "params", params, "error", err)
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, nil
|
||||
|
@ -41,12 +41,14 @@ import (
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -69,7 +71,10 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) {
|
||||
}
|
||||
genesis := &core.Genesis{
|
||||
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"),
|
||||
Timestamp: 9000,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
@ -204,6 +209,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
|
||||
Timestamp: blockParams.Timestamp,
|
||||
FeeRecipient: blockParams.SuggestedFeeRecipient,
|
||||
Random: blockParams.Random,
|
||||
BeaconRoot: blockParams.BeaconRoot,
|
||||
}).Id()
|
||||
execData, err := api.GetPayloadV1(payloadID)
|
||||
if err != nil {
|
||||
@ -314,7 +320,7 @@ func TestEth2NewBlock(t *testing.T) {
|
||||
if err != nil {
|
||||
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 {
|
||||
t.Fatalf("Failed to convert executable data to block %v", err)
|
||||
}
|
||||
@ -356,7 +362,7 @@ func TestEth2NewBlock(t *testing.T) {
|
||||
if err != nil {
|
||||
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 {
|
||||
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,
|
||||
Random: params.Random,
|
||||
Withdrawals: params.Withdrawals,
|
||||
BeaconRoot: params.BeaconRoot,
|
||||
}
|
||||
payload, err := api.eth.Miner().BuildPayload(args)
|
||||
if err != nil {
|
||||
@ -988,7 +995,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
|
||||
t.Fatal(testErr)
|
||||
}
|
||||
}
|
||||
block, err := engine.ExecutableDataToBlock(*execData, nil)
|
||||
block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert executable data to block %v", err)
|
||||
}
|
||||
@ -1068,6 +1075,7 @@ func TestWithdrawals(t *testing.T) {
|
||||
FeeRecipient: blockParams.SuggestedFeeRecipient,
|
||||
Random: blockParams.Random,
|
||||
Withdrawals: blockParams.Withdrawals,
|
||||
BeaconRoot: blockParams.BeaconRoot,
|
||||
}).Id()
|
||||
execData, err := api.GetPayloadV2(payloadID)
|
||||
if err != nil {
|
||||
@ -1115,6 +1123,7 @@ func TestWithdrawals(t *testing.T) {
|
||||
FeeRecipient: blockParams.SuggestedFeeRecipient,
|
||||
Random: blockParams.Random,
|
||||
Withdrawals: blockParams.Withdrawals,
|
||||
BeaconRoot: blockParams.BeaconRoot,
|
||||
}).Id()
|
||||
execData, err = api.GetPayloadV2(payloadID)
|
||||
if err != nil {
|
||||
@ -1245,6 +1254,7 @@ func TestNilWithdrawals(t *testing.T) {
|
||||
Timestamp: test.blockParams.Timestamp,
|
||||
FeeRecipient: test.blockParams.SuggestedFeeRecipient,
|
||||
Random: test.blockParams.Random,
|
||||
BeaconRoot: test.blockParams.BeaconRoot,
|
||||
}).Id()
|
||||
execData, err := api.GetPayloadV2(payloadID)
|
||||
if err != nil {
|
||||
@ -1544,8 +1554,91 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
|
||||
if got := len(envelope.BlobsBundle.Blobs); 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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1345,6 +1345,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
|
||||
if head.ExcessBlobGas != nil {
|
||||
result["excessBlobGas"] = hexutil.Uint64(*head.ExcessBlobGas)
|
||||
}
|
||||
if head.ParentBeaconRoot != nil {
|
||||
result["parentBeaconBlockRoot"] = head.ParentBeaconRoot
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ type BuildPayloadArgs struct {
|
||||
FeeRecipient common.Address // The provided recipient address for collecting transaction fee
|
||||
Random common.Hash // The provided randomness value
|
||||
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.
|
||||
@ -51,6 +52,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
|
||||
hasher.Write(args.Random[:])
|
||||
hasher.Write(args.FeeRecipient[:])
|
||||
rlp.Encode(hasher, args.Withdrawals)
|
||||
if args.BeaconRoot != nil {
|
||||
hasher.Write(args.BeaconRoot[:])
|
||||
}
|
||||
var out engine.PayloadID
|
||||
copy(out[:], hasher.Sum(nil)[:8])
|
||||
return out
|
||||
@ -182,6 +186,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
||||
coinbase: args.FeeRecipient,
|
||||
random: args.Random,
|
||||
withdrawals: args.Withdrawals,
|
||||
beaconRoot: args.BeaconRoot,
|
||||
noTxs: true,
|
||||
}
|
||||
empty := w.getSealingBlock(emptyParams)
|
||||
@ -212,6 +217,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
||||
coinbase: args.FeeRecipient,
|
||||
random: args.Random,
|
||||
withdrawals: args.Withdrawals,
|
||||
beaconRoot: args.BeaconRoot,
|
||||
noTxs: false,
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"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/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
@ -738,36 +739,58 @@ func (w *worker) updateSnapshot(env *environment) {
|
||||
}
|
||||
|
||||
func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
|
||||
var (
|
||||
snap = env.state.Snapshot()
|
||||
gp = env.gasPool.Gas()
|
||||
)
|
||||
if tx.Type() == types.BlobTxType {
|
||||
return w.commitBlobTransaction(env, tx)
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
// 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")
|
||||
}
|
||||
|
||||
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 {
|
||||
env.state.RevertToSnapshot(snap)
|
||||
env.gasPool.SetGas(gp)
|
||||
return nil, err
|
||||
}
|
||||
env.txs = append(env.txs, tx.WithoutBlobTxSidecar())
|
||||
env.receipts = append(env.receipts, receipt)
|
||||
|
||||
if sc := tx.BlobTxSidecar(); sc != nil {
|
||||
env.sidecars = append(env.sidecars, sc)
|
||||
env.blobs += len(sc.Blobs)
|
||||
}
|
||||
|
||||
*env.header.BlobGasUsed += receipt.BlobGasUsed
|
||||
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 {
|
||||
gasLimit := env.header.GasLimit
|
||||
if env.gasPool == nil {
|
||||
@ -860,6 +883,7 @@ type generateParams struct {
|
||||
coinbase common.Address // The fee recipient address for including transaction
|
||||
random common.Hash // The randomness generated by beacon chain, empty before the merge
|
||||
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
|
||||
}
|
||||
|
||||
@ -912,6 +936,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
|
||||
header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil)
|
||||
}
|
||||
}
|
||||
// Apply EIP-4844, EIP-4788.
|
||||
if w.chainConfig.IsCancun(header.Number, header.Time) {
|
||||
var excessBlobGas uint64
|
||||
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
|
||||
excessBlobGas = eip4844.CalcExcessBlobGas(0, 0)
|
||||
}
|
||||
header.BlobGasUsed = new(uint64)
|
||||
header.ExcessBlobGas = &excessBlobGas
|
||||
header.ParentBeaconRoot = genParams.beaconRoot
|
||||
}
|
||||
// Run the consensus preparation with the default or customized consensus engine.
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -458,6 +458,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
|
||||
coinbase: c.coinbase,
|
||||
random: c.random,
|
||||
withdrawals: nil,
|
||||
beaconRoot: nil,
|
||||
noTxs: false,
|
||||
forceTime: true,
|
||||
})
|
||||
@ -482,6 +483,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co
|
||||
coinbase: c.coinbase,
|
||||
random: c.random,
|
||||
withdrawals: nil,
|
||||
beaconRoot: nil,
|
||||
noTxs: false,
|
||||
forceTime: true,
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user