From 93f196c4b052983dd89d8ba749dcdc98a80cf69c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 3 Dec 2021 16:26:28 +0100 Subject: [PATCH] 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 --- cmd/geth/config.go | 2 +- eth/catalyst/api.go | 169 +++++++++++++++++++------------- eth/catalyst/api_test.go | 101 +++++++++++-------- eth/catalyst/api_types.go | 30 ++++-- eth/catalyst/gen_blockparams.go | 39 +++----- eth/catalyst/gen_ed.go | 52 +++++----- miner/stress/beacon/main.go | 38 ++++--- miner/worker.go | 8 +- 8 files changed, 259 insertions(+), 180 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 18829bade..7a642edd0 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -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)) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 059df3670..9e584b822 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -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 } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 98130673e..012b83f9c 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -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, ¶ms) + 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(), ¶ms) + 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 { diff --git a/eth/catalyst/api_types.go b/eth/catalyst/api_types.go index ff0aea39b..2b684e564 100644 --- a/eth/catalyst/api_types.go +++ b/eth/catalyst/api_types.go @@ -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"` } diff --git a/eth/catalyst/gen_blockparams.go b/eth/catalyst/gen_blockparams.go index 9928c1290..04ea6faf0 100644 --- a/eth/catalyst/gen_blockparams.go +++ b/eth/catalyst/gen_blockparams.go @@ -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 } diff --git a/eth/catalyst/gen_ed.go b/eth/catalyst/gen_ed.go index 2953ab820..babfb44df 100644 --- a/eth/catalyst/gen_ed.go +++ b/eth/catalyst/gen_ed.go @@ -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 { diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go index dc5c22941..70005e20d 100644 --- a/miner/stress/beacon/main.go +++ b/miner/stress/beacon/main.go @@ -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:] diff --git a/miner/worker.go b/miner/worker.go index 54932a474..041c03af8 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -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() }