forked from cerc-io/plugeth
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:
+50
-23
@@ -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")
|
||||
}
|
||||
} else {
|
||||
// Reject payload attributes with nil withdrawals after shanghai
|
||||
if attr.Withdrawals == nil {
|
||||
return errors.New("missing withdrawals list")
|
||||
}
|
||||
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)
|
||||
}
|
||||
// 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 (
|
||||
@@ -68,8 +70,11 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) {
|
||||
engine = beaconConsensus.NewFaker()
|
||||
}
|
||||
genesis := &core.Genesis{
|
||||
Config: &config,
|
||||
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
|
||||
Config: &config,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user