eth/catalyst: implement kintsugi spec v1.0.0-alpha.4 (#23984)

This PR implements the new kintsugi specification which can be found here: https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.4/src/engine/specification.md
This commit is contained in:
Marius van der Wijden 2021-12-03 16:26:28 +01:00 committed by GitHub
parent 46f701ca93
commit 93f196c4b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 259 additions and 180 deletions

@ -159,7 +159,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
cfg.Eth.OverrideArrowGlacier = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideArrowGlacierFlag.Name))
}
if ctx.GlobalIsSet(utils.OverrideTerminalTotalDifficulty.Name) {
cfg.Eth.Genesis.Config.TerminalTotalDifficulty = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideTerminalTotalDifficulty.Name))
cfg.Eth.OverrideTerminalTotalDifficulty = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideTerminalTotalDifficulty.Name))
}
backend, _ := utils.RegisterEthService(stack, &cfg.Eth, ctx.GlobalBool(utils.CatalystFlag.Name))

@ -18,7 +18,8 @@
package catalyst
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"math/big"
@ -42,11 +43,13 @@ import (
)
var (
VALID = GenericStringResponse{"VALID"}
INVALID = GenericStringResponse{"INVALID"}
SYNCING = GenericStringResponse{"SYNCING"}
UnknownHeader = rpc.CustomError{Code: -32000, Message: "unknown header"}
UnknownPayload = rpc.CustomError{Code: -32001, Message: "unknown payload"}
VALID = GenericStringResponse{"VALID"}
SUCCESS = GenericStringResponse{"SUCCESS"}
INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil}
SYNCING = ForkChoiceResponse{Status: "INVALID", PayloadID: nil}
UnknownHeader = rpc.CustomError{Code: -32000, Message: "unknown header"}
UnknownPayload = rpc.CustomError{Code: -32001, Message: "unknown payload"}
InvalidPayloadID = rpc.CustomError{Code: 1, Message: "invalid payload id"}
)
// Register adds catalyst APIs to the full node.
@ -82,7 +85,7 @@ type ConsensusAPI struct {
eth *eth.Ethereum
les *les.LightEthereum
engine consensus.Engine // engine is the post-merge consensus engine, only for block creation
preparedBlocks map[int]*ExecutableData
preparedBlocks map[uint64]*ExecutableDataV1
}
func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI {
@ -111,7 +114,7 @@ func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI {
eth: eth,
les: les,
engine: engine,
preparedBlocks: make(map[int]*ExecutableData),
preparedBlocks: make(map[uint64]*ExecutableDataV1),
}
}
@ -171,64 +174,90 @@ func (api *ConsensusAPI) makeEnv(parent *types.Block, header *types.Header) (*bl
return env, nil
}
func (api *ConsensusAPI) PreparePayload(params AssembleBlockParams) (*PayloadResponse, error) {
data, err := api.assembleBlock(params)
if err != nil {
return nil, err
func (api *ConsensusAPI) GetPayloadV1(payloadID hexutil.Bytes) (*ExecutableDataV1, error) {
hash := []byte(payloadID)
if len(hash) < 8 {
return nil, &InvalidPayloadID
}
id := len(api.preparedBlocks)
api.preparedBlocks[id] = data
return &PayloadResponse{PayloadID: uint64(id)}, nil
}
func (api *ConsensusAPI) GetPayload(PayloadID hexutil.Uint64) (*ExecutableData, error) {
data, ok := api.preparedBlocks[int(PayloadID)]
id := binary.BigEndian.Uint64(hash[:8])
data, ok := api.preparedBlocks[id]
if !ok {
return nil, &UnknownPayload
}
return data, nil
}
// ConsensusValidated is called to mark a block as valid, so
// that data that is no longer needed can be removed.
func (api *ConsensusAPI) ConsensusValidated(params ConsensusValidatedParams) error {
switch params.Status {
case VALID.Status:
return nil
case INVALID.Status:
// TODO (MariusVanDerWijden) delete the block from the bc
return nil
default:
return errors.New("invalid params.status")
func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads ForkchoiceStateV1, PayloadAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) {
if heads.HeadBlockHash == (common.Hash{}) {
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil
}
if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil {
if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil {
// TODO (MariusVanDerWijden) trigger sync
return SYNCING, nil
}
return INVALID, err
}
// If the finalized block is set, check if it is in our blockchain
if heads.FinalizedBlockHash != (common.Hash{}) {
if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil {
// TODO (MariusVanDerWijden) trigger sync
return SYNCING, nil
}
}
// SetHead
if err := api.setHead(heads.HeadBlockHash); err != nil {
return INVALID, err
}
// Assemble block (if needed)
if PayloadAttributes != nil {
data, err := api.assembleBlock(heads.HeadBlockHash, PayloadAttributes)
if err != nil {
return INVALID, err
}
hash := computePayloadId(heads.HeadBlockHash, PayloadAttributes)
id := binary.BigEndian.Uint64(hash)
api.preparedBlocks[id] = data
log.Info("Created payload", "payloadid", id)
// TODO (MariusVanDerWijden) do something with the payloadID?
hex := hexutil.Bytes(hash)
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: &hex}, nil
}
return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil
}
func (api *ConsensusAPI) ForkchoiceUpdated(params ForkChoiceParams) error {
var emptyHash = common.Hash{}
if !bytes.Equal(params.HeadBlockHash[:], emptyHash[:]) {
if err := api.checkTerminalTotalDifficulty(params.HeadBlockHash); err != nil {
return err
}
return api.setHead(params.HeadBlockHash)
func computePayloadId(headBlockHash common.Hash, params *PayloadAttributesV1) []byte {
// Hash
hasher := sha256.New()
hasher.Write(headBlockHash[:])
binary.Write(hasher, binary.BigEndian, params.Timestamp)
hasher.Write(params.Random[:])
hasher.Write(params.FeeRecipient[:])
return hasher.Sum([]byte{})[:8]
}
func (api *ConsensusAPI) invalid() ExecutePayloadResponse {
if api.light {
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()}
}
return nil
return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()}
}
// ExecutePayload creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) ExecutePayload(params ExecutableData) (GenericStringResponse, error) {
func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePayloadResponse, error) {
block, err := ExecutableDataToBlock(params)
if err != nil {
return INVALID, err
return api.invalid(), err
}
if api.light {
parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash)
if parent == nil {
return INVALID, fmt.Errorf("could not find parent %x", params.ParentHash)
return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash)
}
if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil {
return INVALID, err
return api.invalid(), err
}
return VALID, nil
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
}
if !api.eth.BlockChain().HasBlock(block.ParentHash(), block.NumberU64()-1) {
/*
@ -237,37 +266,38 @@ func (api *ConsensusAPI) ExecutePayload(params ExecutableData) (GenericStringRes
return SYNCING, err
}
*/
return SYNCING, nil
// TODO (MariusVanDerWijden) we should return nil here not empty hash
return ExecutePayloadResponse{Status: SYNCING.Status, LatestValidHash: common.Hash{}}, nil
}
parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
td := api.eth.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1)
ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
if td.Cmp(ttd) < 0 {
return INVALID, fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
}
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
return INVALID, err
return api.invalid(), err
}
merger := api.merger()
if !merger.TDDReached() {
if merger := api.merger(); !merger.TDDReached() {
merger.ReachTTD()
}
return VALID, nil
return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil
}
// AssembleBlock creates a new block, inserts it into the chain, and returns the "execution
// data" required for eth2 clients to process the new block.
func (api *ConsensusAPI) assembleBlock(params AssembleBlockParams) (*ExecutableData, error) {
func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAttributesV1) (*ExecutableDataV1, error) {
if api.light {
return nil, errors.New("not supported")
}
log.Info("Producing block", "parentHash", params.ParentHash)
log.Info("Producing block", "parentHash", parentHash)
bc := api.eth.BlockChain()
parent := bc.GetBlockByHash(params.ParentHash)
parent := bc.GetBlockByHash(parentHash)
if parent == nil {
log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", params.ParentHash)
return nil, fmt.Errorf("cannot assemble block with unknown parent %s", params.ParentHash)
log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", parentHash)
return nil, fmt.Errorf("cannot assemble block with unknown parent %s", parentHash)
}
if params.Timestamp < parent.Time() {
@ -376,7 +406,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
return txs, nil
}
func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
txs, err := decodeTransactions(params.Transactions)
if err != nil {
return nil, err
@ -410,8 +440,8 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
return block, nil
}
func BlockToExecutableData(block *types.Block, random common.Hash) *ExecutableData {
return &ExecutableData{
func BlockToExecutableData(block *types.Block, random common.Hash) *ExecutableDataV1 {
return &ExecutableDataV1{
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
Coinbase: block.Coinbase(),
@ -447,11 +477,7 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
if newHeadBlock == nil {
return &UnknownHeader
}
parent := api.eth.BlockChain().GetBlockByHash(newHeadBlock.ParentHash())
if parent == nil {
return fmt.Errorf("parent unavailable: %v", newHeadBlock.ParentHash())
}
td := api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64())
td := api.eth.BlockChain().GetTd(newHeadBlock.Hash(), newHeadBlock.NumberU64())
if td != nil && td.Cmp(api.eth.BlockChain().Config().TerminalTotalDifficulty) < 0 {
return errors.New("total difficulty not reached yet")
}
@ -460,11 +486,6 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
// setHead is called to perform a force choice.
func (api *ConsensusAPI) setHead(newHead common.Hash) error {
// Trigger the transition if it's the first `NewHead` event.
merger := api.merger()
if !merger.PoSFinalized() {
merger.FinalizePoS()
}
log.Info("Setting head", "head", newHead)
if api.light {
headHeader := api.les.BlockChain().CurrentHeader()
@ -478,10 +499,19 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error {
if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil {
return err
}
// Trigger the transition if it's the first `NewHead` event.
merger := api.merger()
if !merger.PoSFinalized() {
merger.FinalizePoS()
}
return nil
}
headBlock := api.eth.BlockChain().CurrentBlock()
if headBlock.Hash() == newHead {
// Trigger the transition if it's the first `NewHead` event.
if merger := api.merger(); !merger.PoSFinalized() {
merger.FinalizePoS()
}
return nil
}
newHeadBlock := api.eth.BlockChain().GetBlockByHash(newHead)
@ -491,6 +521,11 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error {
if err := api.eth.BlockChain().SetChainHead(newHeadBlock); err != nil {
return err
}
// Trigger the transition if it's the first `NewHead` event.
if merger := api.merger(); !merger.PoSFinalized() {
merger.FinalizePoS()
}
// TODO (MariusVanDerWijden) are we really synced now?
api.eth.SetSynced()
return nil
}

@ -85,11 +85,10 @@ func TestEth2AssembleBlock(t *testing.T) {
t.Fatalf("error signing transaction, err=%v", err)
}
ethservice.TxPool().AddLocal(tx)
blockParams := AssembleBlockParams{
ParentHash: blocks[9].Hash(),
Timestamp: blocks[9].Time() + 5,
blockParams := PayloadAttributesV1{
Timestamp: blocks[9].Time() + 5,
}
execData, err := api.assembleBlock(blockParams)
execData, err := api.assembleBlock(blocks[9].Hash(), &blockParams)
if err != nil {
t.Fatalf("error producing block, err=%v", err)
}
@ -107,11 +106,10 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
// Put the 10th block's tx in the pool and produce a new block
api.insertTransactions(blocks[9].Transactions())
blockParams := AssembleBlockParams{
ParentHash: blocks[8].Hash(),
Timestamp: blocks[8].Time() + 5,
blockParams := PayloadAttributesV1{
Timestamp: blocks[8].Time() + 5,
}
execData, err := api.assembleBlock(blockParams)
execData, err := api.assembleBlock(blocks[8].Hash(), &blockParams)
if err != nil {
t.Fatalf("error producing block, err=%v", err)
}
@ -126,14 +124,20 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
defer n.Close()
api := NewConsensusAPI(ethservice, nil)
if err := api.ForkchoiceUpdated(ForkChoiceParams{HeadBlockHash: blocks[5].Hash()}); err == nil {
fcState := ForkchoiceStateV1{
HeadBlockHash: blocks[5].Hash(),
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil {
t.Errorf("fork choice updated before total terminal difficulty should fail")
}
}
func TestEth2PrepareAndGetPayload(t *testing.T) {
genesis, blocks := generatePreMergeChain(10)
// We need to properly set the terminal total difficulty
genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty())
n, ethservice := startEthService(t, genesis, blocks[:9])
defer n.Close()
@ -141,15 +145,20 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
// Put the 10th block's tx in the pool and produce a new block
api.insertTransactions(blocks[9].Transactions())
blockParams := AssembleBlockParams{
ParentHash: blocks[8].Hash(),
Timestamp: blocks[8].Time() + 5,
blockParams := PayloadAttributesV1{
Timestamp: blocks[8].Time() + 5,
}
respID, err := api.PreparePayload(blockParams)
fcState := ForkchoiceStateV1{
HeadBlockHash: blocks[8].Hash(),
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
_, err := api.ForkchoiceUpdatedV1(fcState, &blockParams)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
execData, err := api.GetPayload(hexutil.Uint64(respID.PayloadID))
payloadID := computePayloadId(fcState.HeadBlockHash, &blockParams)
execData, err := api.GetPayloadV1(hexutil.Bytes(payloadID))
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
@ -201,9 +210,8 @@ func TestEth2NewBlock(t *testing.T) {
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
ethservice.TxPool().AddLocal(tx)
execData, err := api.assembleBlock(AssembleBlockParams{
ParentHash: parent.Hash(),
Timestamp: parent.Time() + 5,
execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
Timestamp: parent.Time() + 5,
})
if err != nil {
t.Fatalf("Failed to create the executable data %v", err)
@ -212,7 +220,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
newResp, err := api.ExecutePayload(*execData)
newResp, err := api.ExecutePayloadV1(*execData)
if err != nil || newResp.Status != "VALID" {
t.Fatalf("Failed to insert block: %v", err)
}
@ -220,8 +228,12 @@ func TestEth2NewBlock(t *testing.T) {
t.Fatalf("Chain head shouldn't be updated")
}
checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
if err := api.ForkchoiceUpdated(ForkChoiceParams{HeadBlockHash: block.Hash(), FinalizedBlockHash: block.Hash()}); err != nil {
fcState := ForkchoiceStateV1{
HeadBlockHash: block.Hash(),
SafeBlockHash: block.Hash(),
FinalizedBlockHash: block.Hash(),
}
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err)
}
if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
@ -238,9 +250,8 @@ func TestEth2NewBlock(t *testing.T) {
)
parent = preMergeBlocks[len(preMergeBlocks)-1]
for i := 0; i < 10; i++ {
execData, err := api.assembleBlock(AssembleBlockParams{
ParentHash: parent.Hash(),
Timestamp: parent.Time() + 6,
execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
Timestamp: parent.Time() + 6,
})
if err != nil {
t.Fatalf("Failed to create the executable data %v", err)
@ -249,7 +260,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
newResp, err := api.ExecutePayload(*execData)
newResp, err := api.ExecutePayloadV1(*execData)
if err != nil || newResp.Status != "VALID" {
t.Fatalf("Failed to insert block: %v", err)
}
@ -257,10 +268,12 @@ func TestEth2NewBlock(t *testing.T) {
t.Fatalf("Chain head shouldn't be updated")
}
if err := api.ConsensusValidated(ConsensusValidatedParams{BlockHash: block.Hash(), Status: "VALID"}); err != nil {
t.Fatalf("Failed to insert block: %v", err)
fcState := ForkchoiceStateV1{
HeadBlockHash: block.Hash(),
SafeBlockHash: block.Hash(),
FinalizedBlockHash: block.Hash(),
}
if err := api.ForkchoiceUpdated(ForkChoiceParams{FinalizedBlockHash: block.Hash(), HeadBlockHash: block.Hash()}); err != nil {
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err)
}
if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
@ -360,33 +373,41 @@ func TestFullAPI(t *testing.T) {
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
ethservice.TxPool().AddLocal(tx)
params := AssembleBlockParams{
ParentHash: parent.Hash(),
params := PayloadAttributesV1{
Timestamp: parent.Time() + 1,
Random: crypto.Keccak256Hash([]byte{byte(i)}),
FeeRecipient: parent.Coinbase(),
}
resp, err := api.PreparePayload(params)
if err != nil {
t.Fatalf("can't prepare payload: %v", err)
fcState := ForkchoiceStateV1{
HeadBlockHash: parent.Hash(),
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
payload, err := api.GetPayload(hexutil.Uint64(resp.PayloadID))
resp, err := api.ForkchoiceUpdatedV1(fcState, &params)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
if resp.Status != SUCCESS.Status {
t.Fatalf("error preparing payload, invalid status: %v", resp.Status)
}
payloadID := computePayloadId(parent.Hash(), &params)
payload, err := api.GetPayloadV1(hexutil.Bytes(payloadID))
if err != nil {
t.Fatalf("can't get payload: %v", err)
}
execResp, err := api.ExecutePayload(*payload)
execResp, err := api.ExecutePayloadV1(*payload)
if err != nil {
t.Fatalf("can't execute payload: %v", err)
}
if execResp.Status != VALID.Status {
t.Fatalf("invalid status: %v", execResp.Status)
}
if err := api.ConsensusValidated(ConsensusValidatedParams{BlockHash: payload.BlockHash, Status: VALID.Status}); err != nil {
t.Fatalf("failed to validate consensus: %v", err)
fcState = ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: payload.ParentHash,
FinalizedBlockHash: payload.ParentHash,
}
if err := api.ForkchoiceUpdated(ForkChoiceParams{HeadBlockHash: payload.BlockHash, FinalizedBlockHash: payload.BlockHash}); err != nil {
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err)
}
if ethservice.BlockChain().CurrentBlock().NumberU64() != payload.Number {

@ -23,26 +23,24 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)
//go:generate go run github.com/fjl/gencodec -type AssembleBlockParams -field-override assembleBlockParamsMarshaling -out gen_blockparams.go
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go
// Structure described at https://github.com/ethereum/execution-apis/pull/74
type AssembleBlockParams struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
type PayloadAttributesV1 struct {
Timestamp uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"random" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
}
// JSON type overrides for assembleBlockParams.
type assembleBlockParamsMarshaling struct {
// JSON type overrides for PayloadAttributesV1.
type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64
}
//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go
//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go
// Structure described at https://github.com/ethereum/execution-apis/pull/74/files
type ExecutableData struct {
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
// Structure described at https://github.com/ethereum/execution-apis/src/engine/specification.md
type ExecutableDataV1 struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
Coinbase common.Address `json:"coinbase" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
@ -55,6 +53,7 @@ type ExecutableData struct {
Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"`
}
@ -93,12 +92,23 @@ type GenericStringResponse struct {
Status string `json:"status"`
}
type ExecutePayloadResponse struct {
Status string `json:"status"`
LatestValidHash common.Hash `json:"latestValidHash"`
}
type ConsensusValidatedParams struct {
BlockHash common.Hash `json:"blockHash"`
Status string `json:"status"`
}
type ForkChoiceParams struct {
type ForkChoiceResponse struct {
Status string `json:"status"`
PayloadID *hexutil.Bytes `json:"payloadId"`
}
type ForkchoiceStateV1 struct {
HeadBlockHash common.Hash `json:"headBlockHash"`
SafeBlockHash common.Hash `json:"safeBlockHash"`
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
}

@ -10,51 +10,44 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*assembleBlockParamsMarshaling)(nil)
var _ = (*payloadAttributesMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (a AssembleBlockParams) MarshalJSON() ([]byte, error) {
type AssembleBlockParams struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) {
type PayloadAttributesV1 struct {
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"random" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
}
var enc AssembleBlockParams
enc.ParentHash = a.ParentHash
enc.Timestamp = hexutil.Uint64(a.Timestamp)
enc.Random = a.Random
enc.FeeRecipient = a.FeeRecipient
var enc PayloadAttributesV1
enc.Timestamp = hexutil.Uint64(p.Timestamp)
enc.Random = p.Random
enc.FeeRecipient = p.FeeRecipient
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (a *AssembleBlockParams) UnmarshalJSON(input []byte) error {
type AssembleBlockParams struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error {
type PayloadAttributesV1 struct {
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random *common.Hash `json:"random" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
}
var dec AssembleBlockParams
var dec PayloadAttributesV1
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.ParentHash == nil {
return errors.New("missing required field 'parentHash' for AssembleBlockParams")
}
a.ParentHash = *dec.ParentHash
if dec.Timestamp == nil {
return errors.New("missing required field 'timestamp' for AssembleBlockParams")
return errors.New("missing required field 'timestamp' for PayloadAttributesV1")
}
a.Timestamp = uint64(*dec.Timestamp)
p.Timestamp = uint64(*dec.Timestamp)
if dec.Random == nil {
return errors.New("missing required field 'random' for AssembleBlockParams")
return errors.New("missing required field 'random' for PayloadAttributesV1")
}
a.Random = *dec.Random
p.Random = *dec.Random
if dec.FeeRecipient == nil {
return errors.New("missing required field 'feeRecipient' for AssembleBlockParams")
return errors.New("missing required field 'feeRecipient' for PayloadAttributesV1")
}
a.FeeRecipient = *dec.FeeRecipient
p.FeeRecipient = *dec.FeeRecipient
return nil
}

@ -14,9 +14,8 @@ import (
var _ = (*executableDataMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (e ExecutableData) MarshalJSON() ([]byte, error) {
type ExecutableData struct {
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
func (e ExecutableDataV1) MarshalJSON() ([]byte, error) {
type ExecutableDataV1 struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
Coinbase common.Address `json:"coinbase" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
@ -29,10 +28,10 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
}
var enc ExecutableData
enc.BlockHash = e.BlockHash
var enc ExecutableDataV1
enc.ParentHash = e.ParentHash
enc.Coinbase = e.Coinbase
enc.StateRoot = e.StateRoot
@ -45,6 +44,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.Timestamp = hexutil.Uint64(e.Timestamp)
enc.ExtraData = e.ExtraData
enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas)
enc.BlockHash = e.BlockHash
if e.Transactions != nil {
enc.Transactions = make([]hexutil.Bytes, len(e.Transactions))
for k, v := range e.Transactions {
@ -55,9 +55,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
}
// UnmarshalJSON unmarshals from JSON.
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
type ExecutableData struct {
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error {
type ExecutableDataV1 struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
Coinbase *common.Address `json:"coinbase" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
@ -70,66 +69,67 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
}
var dec ExecutableData
var dec ExecutableDataV1
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.BlockHash == nil {
return errors.New("missing required field 'blockHash' for ExecutableData")
}
e.BlockHash = *dec.BlockHash
if dec.ParentHash == nil {
return errors.New("missing required field 'parentHash' for ExecutableData")
return errors.New("missing required field 'parentHash' for ExecutableDataV1")
}
e.ParentHash = *dec.ParentHash
if dec.Coinbase == nil {
return errors.New("missing required field 'coinbase' for ExecutableData")
return errors.New("missing required field 'coinbase' for ExecutableDataV1")
}
e.Coinbase = *dec.Coinbase
if dec.StateRoot == nil {
return errors.New("missing required field 'stateRoot' for ExecutableData")
return errors.New("missing required field 'stateRoot' for ExecutableDataV1")
}
e.StateRoot = *dec.StateRoot
if dec.ReceiptRoot == nil {
return errors.New("missing required field 'receiptRoot' for ExecutableData")
return errors.New("missing required field 'receiptRoot' for ExecutableDataV1")
}
e.ReceiptRoot = *dec.ReceiptRoot
if dec.LogsBloom == nil {
return errors.New("missing required field 'logsBloom' for ExecutableData")
return errors.New("missing required field 'logsBloom' for ExecutableDataV1")
}
e.LogsBloom = *dec.LogsBloom
if dec.Random == nil {
return errors.New("missing required field 'random' for ExecutableData")
return errors.New("missing required field 'random' for ExecutableDataV1")
}
e.Random = *dec.Random
if dec.Number == nil {
return errors.New("missing required field 'blockNumber' for ExecutableData")
return errors.New("missing required field 'blockNumber' for ExecutableDataV1")
}
e.Number = uint64(*dec.Number)
if dec.GasLimit == nil {
return errors.New("missing required field 'gasLimit' for ExecutableData")
return errors.New("missing required field 'gasLimit' for ExecutableDataV1")
}
e.GasLimit = uint64(*dec.GasLimit)
if dec.GasUsed == nil {
return errors.New("missing required field 'gasUsed' for ExecutableData")
return errors.New("missing required field 'gasUsed' for ExecutableDataV1")
}
e.GasUsed = uint64(*dec.GasUsed)
if dec.Timestamp == nil {
return errors.New("missing required field 'timestamp' for ExecutableData")
return errors.New("missing required field 'timestamp' for ExecutableDataV1")
}
e.Timestamp = uint64(*dec.Timestamp)
if dec.ExtraData == nil {
return errors.New("missing required field 'extraData' for ExecutableData")
return errors.New("missing required field 'extraData' for ExecutableDataV1")
}
e.ExtraData = *dec.ExtraData
if dec.BaseFeePerGas == nil {
return errors.New("missing required field 'baseFeePerGas' for ExecutableData")
return errors.New("missing required field 'baseFeePerGas' for ExecutableDataV1")
}
e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas)
if dec.BlockHash == nil {
return errors.New("missing required field 'blockHash' for ExecutableDataV1")
}
e.BlockHash = *dec.BlockHash
if dec.Transactions == nil {
return errors.New("missing required field 'transactions' for ExecutableData")
return errors.New("missing required field 'transactions' for ExecutableDataV1")
}
e.Transactions = make([][]byte, len(dec.Transactions))
for k, v := range dec.Transactions {

@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/fdlimit"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
@ -138,25 +137,30 @@ func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode
}
}
func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*catalyst.ExecutableData, error) {
func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*catalyst.ExecutableDataV1, error) {
if n.typ != eth2MiningNode {
return nil, errors.New("invalid node type")
}
payload, err := n.api.PreparePayload(catalyst.AssembleBlockParams{
ParentHash: parentHash,
Timestamp: uint64(time.Now().Unix()),
})
payloadAttribute := catalyst.PayloadAttributesV1{
Timestamp: uint64(time.Now().Unix()),
}
fcState := catalyst.ForkchoiceStateV1{
HeadBlockHash: parentHash,
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
payload, err := n.api.ForkchoiceUpdatedV1(fcState, &payloadAttribute)
if err != nil {
return nil, err
}
return n.api.GetPayload(hexutil.Uint64(payload.PayloadID))
return n.api.GetPayloadV1(*payload.PayloadID)
}
func (n *ethNode) insertBlock(eb catalyst.ExecutableData) error {
func (n *ethNode) insertBlock(eb catalyst.ExecutableDataV1) error {
if !eth2types(n.typ) {
return errors.New("invalid node type")
}
newResp, err := n.api.ExecutePayload(eb)
newResp, err := n.api.ExecutePayloadV1(eb)
if err != nil {
return err
} else if newResp.Status != "VALID" {
@ -165,7 +169,7 @@ func (n *ethNode) insertBlock(eb catalyst.ExecutableData) error {
return nil
}
func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed catalyst.ExecutableData) error {
func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed catalyst.ExecutableDataV1) error {
if !eth2types(n.typ) {
return errors.New("invalid node type")
}
@ -176,7 +180,12 @@ func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed catalyst.Execut
if err != nil {
return err
}
if err := n.api.ConsensusValidated(catalyst.ConsensusValidatedParams{BlockHash: block.Hash(), Status: "VALID"}); err != nil {
fcState := catalyst.ForkchoiceStateV1{
HeadBlockHash: block.ParentHash(),
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
if _, err := n.api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
return err
}
return nil
@ -275,7 +284,12 @@ func (mgr *nodeManager) run() {
nodes = append(nodes, mgr.getNodes(eth2NormalNode)...)
nodes = append(nodes, mgr.getNodes(eth2LightClient)...)
for _, node := range append(nodes) {
node.api.ConsensusValidated(catalyst.ConsensusValidatedParams{BlockHash: oldest.Hash(), Status: catalyst.VALID.Status})
fcState := catalyst.ForkchoiceStateV1{
HeadBlockHash: oldest.Hash(),
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
node.api.ForkchoiceUpdatedV1(fcState, nil)
}
log.Info("Finalised eth2 block", "number", oldest.NumberU64(), "hash", oldest.Hash())
waitFinalise = waitFinalise[1:]

@ -1040,7 +1040,13 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
return err
}
if w.isRunning() && !w.merger.TDDReached() {
// If we're post merge, just ignore
td, ttd := w.chain.GetTd(block.ParentHash(), block.NumberU64()-1), w.chain.Config().TerminalTotalDifficulty
if td != nil && ttd != nil && td.Cmp(ttd) >= 0 {
return nil
}
if w.isRunning() {
if interval != nil {
interval()
}