From 2a2b0419fb966c54fb86b17bbccea743a45b4d2a Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 25 Jan 2023 15:32:25 +0100 Subject: [PATCH] all: implement withdrawals (EIP-4895) (#26484) This change implements withdrawals as specified in EIP-4895. Co-authored-by: lightclient@protonmail.com Co-authored-by: marioevz Co-authored-by: Martin Holst Swende Co-authored-by: Felix Lange --- cmd/evm/internal/t8ntool/block.go | 108 +++++----- cmd/evm/internal/t8ntool/execution.go | 32 ++- cmd/evm/internal/t8ntool/flags.go | 4 + cmd/evm/internal/t8ntool/gen_header.go | 70 ++++--- cmd/evm/internal/t8ntool/gen_stenv.go | 7 + cmd/evm/internal/t8ntool/transition.go | 3 + cmd/evm/main.go | 1 + cmd/evm/t8n_test.go | 37 +++- cmd/evm/testdata/26/alloc.json | 8 + cmd/evm/testdata/26/env.json | 17 ++ cmd/evm/testdata/26/exp.json | 20 ++ cmd/evm/testdata/26/txs.json | 1 + cmd/evm/testdata/27/exp.json | 4 + cmd/evm/testdata/27/header.json | 12 ++ cmd/evm/testdata/27/ommers.json | 1 + cmd/evm/testdata/27/txs.rlp | 1 + cmd/evm/testdata/27/withdrawals.json | 8 + consensus/beacon/consensus.go | 45 +++- consensus/beacon/faker.go | 41 ++++ consensus/clique/clique.go | 16 +- consensus/consensus.go | 4 +- consensus/ethash/consensus.go | 17 +- core/beacon/gen_blockparams.go | 37 ++-- core/beacon/gen_ed.go | 103 ++++----- core/beacon/gen_epe.go | 46 +++++ core/beacon/types.go | 120 ++++++----- core/block_validator.go | 5 + core/blockchain_test.go | 12 +- core/chain_makers.go | 35 +++- core/genesis.go | 3 + core/rawdb/accessors_chain.go | 6 +- core/state_processor.go | 7 +- core/state_processor_test.go | 65 +++--- core/types/block.go | 69 ++++++- core/types/gen_header_json.go | 72 ++++--- core/types/gen_header_rlp.go | 10 +- core/types/gen_withdrawal_json.go | 55 +++++ core/types/gen_withdrawal_rlp.go | 20 ++ core/types/withdrawal.go | 56 +++++ core/vm/runtime/runtime.go | 2 - eth/catalyst/api.go | 46 ++++- eth/catalyst/api_test.go | 207 +++++++++++++++---- eth/catalyst/queue.go | 2 +- eth/downloader/downloader.go | 6 +- eth/downloader/downloader_test.go | 7 +- eth/downloader/fetchers_concurrent_bodies.go | 6 +- eth/downloader/queue.go | 12 +- eth/downloader/queue_test.go | 2 +- eth/fetcher/block_fetcher.go | 4 +- eth/protocols/eth/handler_test.go | 71 +++++-- eth/protocols/eth/handlers.go | 10 +- eth/protocols/eth/protocol.go | 13 +- internal/ethapi/api.go | 6 + les/catalyst/api.go | 6 +- les/catalyst/api_test.go | 2 +- miner/payload_building.go | 27 +-- miner/payload_building_test.go | 13 +- miner/stress/beacon/main.go | 8 +- miner/worker.go | 37 ++-- miner/worker_test.go | 4 +- 60 files changed, 1233 insertions(+), 436 deletions(-) create mode 100644 cmd/evm/testdata/26/alloc.json create mode 100644 cmd/evm/testdata/26/env.json create mode 100644 cmd/evm/testdata/26/exp.json create mode 100644 cmd/evm/testdata/26/txs.json create mode 100644 cmd/evm/testdata/27/exp.json create mode 100644 cmd/evm/testdata/27/header.json create mode 100644 cmd/evm/testdata/27/ommers.json create mode 100644 cmd/evm/testdata/27/txs.rlp create mode 100644 cmd/evm/testdata/27/withdrawals.json create mode 100644 consensus/beacon/faker.go create mode 100644 core/beacon/gen_epe.go create mode 100644 core/types/gen_withdrawal_json.go create mode 100644 core/types/gen_withdrawal_rlp.go create mode 100644 core/types/withdrawal.go diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index 4a070b6c7..1140daa2c 100644 --- a/cmd/evm/internal/t8ntool/block.go +++ b/cmd/evm/internal/t8ntool/block.go @@ -38,22 +38,23 @@ import ( //go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go type header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *big.Int `json:"difficulty"` - Number *big.Int `json:"number" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed"` - Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + ParentHash common.Hash `json:"parentHash"` + OmmerHash *common.Hash `json:"sha3Uncles"` + Coinbase *common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot"` + ReceiptHash *common.Hash `json:"receiptsRoot"` + Bloom types.Bloom `json:"logsBloom"` + Difficulty *big.Int `json:"difficulty"` + Number *big.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed"` + Time uint64 `json:"timestamp" gencodec:"required"` + Extra []byte `json:"extraData"` + MixDigest common.Hash `json:"mixHash"` + Nonce *types.BlockNonce `json:"nonce"` + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } type headerMarshaling struct { @@ -67,10 +68,11 @@ type headerMarshaling struct { } type bbInput struct { - Header *header `json:"header,omitempty"` - OmmersRlp []string `json:"ommers,omitempty"` - TxRlp string `json:"txs,omitempty"` - Clique *cliqueInput `json:"clique,omitempty"` + Header *header `json:"header,omitempty"` + OmmersRlp []string `json:"ommers,omitempty"` + TxRlp string `json:"txs,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` + Clique *cliqueInput `json:"clique,omitempty"` Ethash bool `json:"-"` EthashDir string `json:"-"` @@ -114,21 +116,22 @@ func (c *cliqueInput) UnmarshalJSON(input []byte) error { // ToBlock converts i into a *types.Block func (i *bbInput) ToBlock() *types.Block { header := &types.Header{ - ParentHash: i.Header.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: common.Address{}, - Root: i.Header.Root, - TxHash: types.EmptyRootHash, - ReceiptHash: types.EmptyRootHash, - Bloom: i.Header.Bloom, - Difficulty: common.Big0, - Number: i.Header.Number, - GasLimit: i.Header.GasLimit, - GasUsed: i.Header.GasUsed, - Time: i.Header.Time, - Extra: i.Header.Extra, - MixDigest: i.Header.MixDigest, - BaseFee: i.Header.BaseFee, + ParentHash: i.Header.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: common.Address{}, + Root: i.Header.Root, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + Bloom: i.Header.Bloom, + Difficulty: common.Big0, + Number: i.Header.Number, + GasLimit: i.Header.GasLimit, + GasUsed: i.Header.GasUsed, + Time: i.Header.Time, + Extra: i.Header.Extra, + MixDigest: i.Header.MixDigest, + BaseFee: i.Header.BaseFee, + WithdrawalsHash: i.Header.WithdrawalsHash, } // Fill optional values. @@ -153,7 +156,7 @@ func (i *bbInput) ToBlock() *types.Block { if header.Difficulty != nil { header.Difficulty = i.Header.Difficulty } - return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers) + return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers).WithWithdrawals(i.Withdrawals) } // SealBlock seals the given block using the configured engine. @@ -259,14 +262,15 @@ func BuildBlock(ctx *cli.Context) error { func readInput(ctx *cli.Context) (*bbInput, error) { var ( - headerStr = ctx.String(InputHeaderFlag.Name) - ommersStr = ctx.String(InputOmmersFlag.Name) - txsStr = ctx.String(InputTxsRlpFlag.Name) - cliqueStr = ctx.String(SealCliqueFlag.Name) - ethashOn = ctx.Bool(SealEthashFlag.Name) - ethashDir = ctx.String(SealEthashDirFlag.Name) - ethashMode = ctx.String(SealEthashModeFlag.Name) - inputData = &bbInput{} + headerStr = ctx.String(InputHeaderFlag.Name) + ommersStr = ctx.String(InputOmmersFlag.Name) + withdrawalsStr = ctx.String(InputWithdrawalsFlag.Name) + txsStr = ctx.String(InputTxsRlpFlag.Name) + cliqueStr = ctx.String(SealCliqueFlag.Name) + ethashOn = ctx.Bool(SealEthashFlag.Name) + ethashDir = ctx.String(SealEthashDirFlag.Name) + ethashMode = ctx.String(SealEthashModeFlag.Name) + inputData = &bbInput{} ) if ethashOn && cliqueStr != "" { return nil, NewError(ErrorConfig, fmt.Errorf("both ethash and clique sealing specified, only one may be chosen")) @@ -312,6 +316,13 @@ func readInput(ctx *cli.Context) (*bbInput, error) { } inputData.OmmersRlp = ommers } + if withdrawalsStr != stdinSelector && withdrawalsStr != "" { + var withdrawals []*types.Withdrawal + if err := readFile(withdrawalsStr, "withdrawals", &withdrawals); err != nil { + return nil, err + } + inputData.Withdrawals = withdrawals + } if txsStr != stdinSelector { var txs string if err := readFile(txsStr, "txs", &txs); err != nil { @@ -351,15 +362,14 @@ func readInput(ctx *cli.Context) (*bbInput, error) { // files func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { raw, _ := rlp.EncodeToBytes(block) - type blockInfo struct { Rlp hexutil.Bytes `json:"rlp"` Hash common.Hash `json:"hash"` } - var enc blockInfo - enc.Rlp = raw - enc.Hash = block.Hash() - + enc := blockInfo{ + Rlp: raw, + Hash: block.Hash(), + } b, err := json.MarshalIndent(enc, "", " ") if err != nil { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 63c54b1d8..2c6865994 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -47,16 +47,17 @@ type Prestate struct { // ExecutionResult contains the execution status after running a state test, any // error that might have occurred and a dump of the final state if requested. type ExecutionResult struct { - StateRoot common.Hash `json:"stateRoot"` - TxRoot common.Hash `json:"txRoot"` - ReceiptRoot common.Hash `json:"receiptsRoot"` - LogsHash common.Hash `json:"logsHash"` - Bloom types.Bloom `json:"logsBloom" gencodec:"required"` - Receipts types.Receipts `json:"receipts"` - Rejected []*rejectedTx `json:"rejected,omitempty"` - Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptsRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom" gencodec:"required"` + Receipts types.Receipts `json:"receipts"` + Rejected []*rejectedTx `json:"rejected,omitempty"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` + WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` } type ommer struct { @@ -79,6 +80,7 @@ type stEnv struct { ParentTimestamp uint64 `json:"parentTimestamp,omitempty"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` BaseFee *big.Int `json:"currentBaseFee,omitempty"` ParentUncleHash common.Hash `json:"parentUncleHash"` } @@ -254,6 +256,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } statedb.AddBalance(pre.Env.Coinbase, minerReward) } + // Apply withdrawals + for _, w := range pre.Env.Withdrawals { + // Amount is in gwei, turn into wei + amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) + statedb.AddBalance(w.Address, amount) + } // Commit block root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { @@ -272,6 +280,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, GasUsed: (math.HexOrDecimal64)(gasUsed), BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), } + if pre.Env.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) + execRs.WithdrawalsRoot = &h + } return statedb, execRs, nil } diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index 626220315..339523593 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -112,6 +112,10 @@ var ( Name: "input.ommers", Usage: "`stdin` or file name of where to find the list of ommer header RLPs to use.", } + InputWithdrawalsFlag = &cli.StringFlag{ + Name: "input.withdrawals", + Usage: "`stdin` or file name of where to find the list of withdrawals to use.", + } InputTxsRlpFlag = &cli.StringFlag{ Name: "input.txs", Usage: "`stdin` or file name of where to find the transactions list in RLP form.", diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go index 196e49dd7..76228394d 100644 --- a/cmd/evm/internal/t8ntool/gen_header.go +++ b/cmd/evm/internal/t8ntool/gen_header.go @@ -18,22 +18,23 @@ var _ = (*headerMarshaling)(nil) // MarshalJSON marshals as JSON. func (h header) MarshalJSON() ([]byte, error) { type header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` - GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` + ParentHash common.Hash `json:"parentHash"` + OmmerHash *common.Hash `json:"sha3Uncles"` + Coinbase *common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot"` + ReceiptHash *common.Hash `json:"receiptsRoot"` + Bloom types.Bloom `json:"logsBloom"` + Difficulty *math.HexOrDecimal256 `json:"difficulty"` + Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` + GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData"` + MixDigest common.Hash `json:"mixHash"` + Nonce *types.BlockNonce `json:"nonce"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } var enc header enc.ParentHash = h.ParentHash @@ -52,28 +53,30 @@ func (h header) MarshalJSON() ([]byte, error) { enc.MixDigest = h.MixDigest enc.Nonce = h.Nonce enc.BaseFee = (*math.HexOrDecimal256)(h.BaseFee) + enc.WithdrawalsHash = h.WithdrawalsHash return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (h *header) UnmarshalJSON(input []byte) error { type header struct { - ParentHash *common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root *common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom *types.Bloom `json:"logsBloom"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` - GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - GasUsed *math.HexOrDecimal64 `json:"gasUsed"` - Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` - Extra *hexutil.Bytes `json:"extraData"` - MixDigest *common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` + ParentHash *common.Hash `json:"parentHash"` + OmmerHash *common.Hash `json:"sha3Uncles"` + Coinbase *common.Address `json:"miner"` + Root *common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot"` + ReceiptHash *common.Hash `json:"receiptsRoot"` + Bloom *types.Bloom `json:"logsBloom"` + Difficulty *math.HexOrDecimal256 `json:"difficulty"` + Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` + GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + GasUsed *math.HexOrDecimal64 `json:"gasUsed"` + Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` + Extra *hexutil.Bytes `json:"extraData"` + MixDigest *common.Hash `json:"mixHash"` + Nonce *types.BlockNonce `json:"nonce"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } var dec header if err := json.Unmarshal(input, &dec); err != nil { @@ -131,5 +134,8 @@ func (h *header) UnmarshalJSON(input []byte) error { if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.WithdrawalsHash != nil { + h.WithdrawalsHash = dec.WithdrawalsHash + } return nil } diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go index da449e659..c2cc3a2c8 100644 --- a/cmd/evm/internal/t8ntool/gen_stenv.go +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" ) var _ = (*stEnvMarshaling)(nil) @@ -29,6 +30,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` ParentUncleHash common.Hash `json:"parentUncleHash"` } @@ -46,6 +48,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { enc.ParentTimestamp = math.HexOrDecimal64(s.ParentTimestamp) enc.BlockHashes = s.BlockHashes enc.Ommers = s.Ommers + enc.Withdrawals = s.Withdrawals enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) enc.ParentUncleHash = s.ParentUncleHash return json.Marshal(&enc) @@ -67,6 +70,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` ParentUncleHash *common.Hash `json:"parentUncleHash"` } @@ -117,6 +121,9 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { if dec.Ommers != nil { s.Ommers = dec.Ommers } + if dec.Withdrawals != nil { + s.Withdrawals = dec.Withdrawals + } if dec.BaseFee != nil { s.BaseFee = (*big.Int)(dec.BaseFee) } diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 8b05f1def..cb7466d86 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -262,6 +262,9 @@ func Transition(ctx *cli.Context) error { return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) } } + if chainConfig.IsShanghai(prestate.Env.Number) && prestate.Env.Withdrawals == nil { + return NewError(ErrorConfig, errors.New("Shanghai config but missing 'withdrawals' in env section")) + } isMerged := chainConfig.TerminalTotalDifficulty != nil && chainConfig.TerminalTotalDifficulty.BitLen() == 0 env := prestate.Env if isMerged { diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 5f9e75f48..a9ce83a3e 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -176,6 +176,7 @@ var blockBuilderCommand = &cli.Command{ t8ntool.OutputBlockFlag, t8ntool.InputHeaderFlag, t8ntool.InputOmmersFlag, + t8ntool.InputWithdrawalsFlag, t8ntool.InputTxsRlpFlag, t8ntool.SealCliqueFlag, t8ntool.SealEthashFlag, diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index b7a0d9c2c..067660c7e 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -251,6 +251,14 @@ func TestT8n(t *testing.T) { output: t8nOutput{alloc: true, result: true}, expOut: "exp.json", }, + { // Test withdrawals transition + base: "./testdata/26", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Shanghai", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, } { args := []string{"t8n"} args = append(args, tc.output.get()...) @@ -391,13 +399,14 @@ func TestT9n(t *testing.T) { } type b11rInput struct { - inEnv string - inOmmersRlp string - inTxsRlp string - inClique string - ethash bool - ethashMode string - ethashDir string + inEnv string + inOmmersRlp string + inWithdrawals string + inTxsRlp string + inClique string + ethash bool + ethashMode string + ethashDir string } func (args *b11rInput) get(base string) []string { @@ -410,6 +419,10 @@ func (args *b11rInput) get(base string) []string { out = append(out, "--input.ommers") out = append(out, fmt.Sprintf("%v/%v", base, opt)) } + if opt := args.inWithdrawals; opt != "" { + out = append(out, "--input.withdrawals") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } if opt := args.inTxsRlp; opt != "" { out = append(out, "--input.txs") out = append(out, fmt.Sprintf("%v/%v", base, opt)) @@ -480,6 +493,16 @@ func TestB11r(t *testing.T) { }, expOut: "exp.json", }, + { // block with withdrawals + base: "./testdata/27", + input: b11rInput{ + inEnv: "header.json", + inOmmersRlp: "ommers.json", + inWithdrawals: "withdrawals.json", + inTxsRlp: "txs.rlp", + }, + expOut: "exp.json", + }, } { args := []string{"b11r"} args = append(args, tc.input.get(tc.base)...) diff --git a/cmd/evm/testdata/26/alloc.json b/cmd/evm/testdata/26/alloc.json new file mode 100644 index 000000000..d67655a8a --- /dev/null +++ b/cmd/evm/testdata/26/alloc.json @@ -0,0 +1,8 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0", + "code": "0x", + "nonce": "0xac", + "storage": {} + } +} diff --git a/cmd/evm/testdata/26/env.json b/cmd/evm/testdata/26/env.json new file mode 100644 index 000000000..03d817b93 --- /dev/null +++ b/cmd/evm/testdata/26/env.json @@ -0,0 +1,17 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": null, + "currentRandom": "0xdeadc0de", + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "1", + "currentTimestamp": "1000", + "withdrawals": [ + { + "index": "0x42", + "validatorIndex": "0x42", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "amount": "0x2a" + } + ] +} diff --git a/cmd/evm/testdata/26/exp.json b/cmd/evm/testdata/26/exp.json new file mode 100644 index 000000000..4815e5cb6 --- /dev/null +++ b/cmd/evm/testdata/26/exp.json @@ -0,0 +1,20 @@ +{ + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x9c7652400", + "nonce": "0xac" + } + }, + "result": { + "stateRoot": "0x6e061c2f6513af27d267a0e3b07cb9a10f1ba3a0f65ab648d3a17c36e15021d2", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": null, + "gasUsed": "0x0", + "currentBaseFee": "0x500", + "withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5" + } +} diff --git a/cmd/evm/testdata/26/txs.json b/cmd/evm/testdata/26/txs.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/cmd/evm/testdata/26/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/27/exp.json b/cmd/evm/testdata/27/exp.json new file mode 100644 index 000000000..5975a9c25 --- /dev/null +++ b/cmd/evm/testdata/27/exp.json @@ -0,0 +1,4 @@ +{ + "rlp": "0xf90239f9021aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88000000000000000080a04921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5c0c0d9d8424394a94f5374fce5edbc8e2a8697c15331677e6ebf0b2a", + "hash": "0xdc42abd3698499675819e0a85cc1266f16da90277509b867446a6b25fa2b9d87" +} diff --git a/cmd/evm/testdata/27/header.json b/cmd/evm/testdata/27/header.json new file mode 100644 index 000000000..4ed7eaca0 --- /dev/null +++ b/cmd/evm/testdata/27/header.json @@ -0,0 +1,12 @@ +{ + "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e", + "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x1000", + "number": "0xc3be", + "gasLimit": "0x50785", + "gasUsed": "0x0", + "timestamp": "0x55c5277e", + "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf", + "withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5" +} diff --git a/cmd/evm/testdata/27/ommers.json b/cmd/evm/testdata/27/ommers.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/cmd/evm/testdata/27/ommers.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/27/txs.rlp b/cmd/evm/testdata/27/txs.rlp new file mode 100644 index 000000000..e815397b3 --- /dev/null +++ b/cmd/evm/testdata/27/txs.rlp @@ -0,0 +1 @@ +"c0" diff --git a/cmd/evm/testdata/27/withdrawals.json b/cmd/evm/testdata/27/withdrawals.json new file mode 100644 index 000000000..6634aff08 --- /dev/null +++ b/cmd/evm/testdata/27/withdrawals.json @@ -0,0 +1,8 @@ +[ + { + "index": "0x42", + "validatorIndex": "0x43", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "amount": "0x2a" + } +] diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 274b9cf36..eb5aa58ca 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -257,7 +257,18 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return consensus.ErrInvalidNumber } // Verify the header's EIP-1559 attributes. - return misc.VerifyEip1559Header(chain.Config(), parent, header) + if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil { + return err + } + // Verify existence / non-existence of withdrawalsHash. + shanghai := chain.Config().IsShanghai(header.Time) + if shanghai && header.WithdrawalsHash == nil { + return fmt.Errorf("missing withdrawalsHash") + } + if !shanghai && header.WithdrawalsHash != nil { + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) + } + return nil } // verifyHeaders is similar to verifyHeader, but verifies a batch of headers @@ -316,13 +327,20 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H } // Finalize implements consensus.Engine, setting the final state on the header -func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { +func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { // Finalize is different with Prepare, it can be used in both block generation // and verification. So determine the consensus rules by header type. if !beacon.IsPoSHeader(header) { - beacon.ethone.Finalize(chain, header, state, txs, uncles) + beacon.ethone.Finalize(chain, header, state, txs, uncles, nil) return } + // Withdrawals processing. + for _, w := range withdrawals { + // Convert amount from gwei to wei. + amount := new(big.Int).SetUint64(w.Amount) + amount = amount.Mul(amount, big.NewInt(params.GWei)) + state.AddBalance(w.Address, amount) + } // The block reward is no longer handled here. It's done by the // external consensus engine. header.Root = state.IntermediateRoot(true) @@ -330,15 +348,26 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // FinalizeAndAssemble implements consensus.Engine, setting the final state and // assembling the block. -func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { +func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) { // FinalizeAndAssemble is different with Prepare, it can be used in both block // generation and verification. So determine the consensus rules by header type. if !beacon.IsPoSHeader(header) { - return beacon.ethone.FinalizeAndAssemble(chain, header, state, txs, uncles, receipts) + return beacon.ethone.FinalizeAndAssemble(chain, header, state, txs, uncles, receipts, nil) } - // Finalize and assemble the block - beacon.Finalize(chain, header, state, txs, uncles) - return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil + shanghai := chain.Config().IsShanghai(header.Time) + if shanghai { + // All blocks after Shanghai must include a withdrawals root. + if withdrawals == nil { + withdrawals = make([]*types.Withdrawal, 0) + } + } else { + if len(withdrawals) > 0 { + return nil, errors.New("withdrawals set before Shanghai activation") + } + } + // Finalize and assemble the block. + beacon.Finalize(chain, header, state, txs, uncles, withdrawals) + return types.NewBlockWithWithdrawals(header, txs, uncles, receipts, withdrawals, trie.NewStackTrie(nil)), nil } // Seal generates a new sealing request for the given input block and pushes diff --git a/consensus/beacon/faker.go b/consensus/beacon/faker.go new file mode 100644 index 000000000..981e345e3 --- /dev/null +++ b/consensus/beacon/faker.go @@ -0,0 +1,41 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package beacon + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" +) + +// NewFaker creates a fake consensus engine for testing. +// The fake engine simulates a merged network. +// It can not be used to test the merge transition. +// This type is needed since the fakeChainReader can not be used with +// a normal beacon consensus engine. +func NewFaker() consensus.Engine { + return new(faker) +} + +type faker struct { + Beacon +} + +func (f *faker) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + return beaconDifficulty +} diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 53ccc34cc..4706bbac1 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -298,6 +298,9 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if header.GasLimit > params.MaxGasLimit { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) } + if chain.Config().IsShanghai(header.Time) { + return fmt.Errorf("clique does not support shanghai fork") + } // If all checks passed, validate any special fields for hard forks if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil { return err @@ -564,7 +567,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given. -func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { +func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { // No block rewards in PoA, so the state remains as is and uncles are dropped header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.UncleHash = types.CalcUncleHash(nil) @@ -572,9 +575,13 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, // nor block rewards given, and returns the final block. -func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { +func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) { + if len(withdrawals) > 0 { + return nil, errors.New("clique does not support withdrawals") + } + // Finalize block - c.Finalize(chain, header, state, txs, uncles) + c.Finalize(chain, header, state, txs, uncles, nil) // Assemble and return the final block for sealing return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil @@ -743,6 +750,9 @@ func encodeSigHeader(w io.Writer, header *types.Header) { if header.BaseFee != nil { enc = append(enc, header.BaseFee) } + if header.WithdrawalsHash != nil { + panic("unexpected withdrawal hash value in clique") + } if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } diff --git a/consensus/consensus.go b/consensus/consensus.go index af8ce98ff..190d5ae12 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -90,7 +90,7 @@ type Engine interface { // Note: The block header and state database might be updated to reflect any // consensus rules that happen at finalization (e.g. block rewards). Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, - uncles []*types.Header) + uncles []*types.Header, withdrawals []*types.Withdrawal) // FinalizeAndAssemble runs any post-transaction state modifications (e.g. block // rewards) and assembles the final block. @@ -98,7 +98,7 @@ type Engine interface { // Note: The block header and state database might be updated to reflect any // consensus rules that happen at finalization (e.g. block rewards). FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, - uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) + uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) // Seal generates a new sealing request for the given input block and pushes // the result into the given channel. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index b49fcf0ce..da29e1659 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -310,6 +310,9 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { return consensus.ErrInvalidNumber } + if chain.Config().IsShanghai(header.Time) { + return fmt.Errorf("ethash does not support shanghai fork") + } // Verify the engine specific seal securing the block if seal { if err := ethash.verifySeal(chain, header, false); err != nil { @@ -597,7 +600,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H // Finalize implements consensus.Engine, accumulating the block and uncle rewards, // setting the final state on the header -func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { +func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { // Accumulate any block and uncle rewards and commit the final state root accumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) @@ -605,10 +608,13 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types. // FinalizeAndAssemble implements consensus.Engine, accumulating the block and // uncle rewards, setting the final state and assembling the block. -func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - // Finalize block - ethash.Finalize(chain, header, state, txs, uncles) +func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) { + if len(withdrawals) > 0 { + return nil, errors.New("ethash does not support withdrawals") + } + // Finalize block + ethash.Finalize(chain, header, state, txs, uncles, nil) // Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil } @@ -635,6 +641,9 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { if header.BaseFee != nil { enc = append(enc, header.BaseFee) } + if header.WithdrawalsHash != nil { + panic("withdrawal hash set on ethash") + } rlp.Encode(hasher, enc) hasher.Sum(hash[:0]) return hash diff --git a/core/beacon/gen_blockparams.go b/core/beacon/gen_blockparams.go index 0e2ea4bb1..a7df96e09 100644 --- a/core/beacon/gen_blockparams.go +++ b/core/beacon/gen_blockparams.go @@ -8,46 +8,53 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" ) var _ = (*payloadAttributesMarshaling)(nil) // MarshalJSON marshals as JSON. -func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) { - type PayloadAttributesV1 struct { - Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` +func (p PayloadAttributes) MarshalJSON() ([]byte, error) { + type PayloadAttributes struct { + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` } - var enc PayloadAttributesV1 + var enc PayloadAttributes enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Random = p.Random enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient + enc.Withdrawals = p.Withdrawals return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. -func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error { - type PayloadAttributesV1 struct { - Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Random *common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` +func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { + type PayloadAttributes struct { + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` } - var dec PayloadAttributesV1 + var dec PayloadAttributes if err := json.Unmarshal(input, &dec); err != nil { return err } if dec.Timestamp == nil { - return errors.New("missing required field 'timestamp' for PayloadAttributesV1") + return errors.New("missing required field 'timestamp' for PayloadAttributes") } p.Timestamp = uint64(*dec.Timestamp) if dec.Random == nil { - return errors.New("missing required field 'prevRandao' for PayloadAttributesV1") + return errors.New("missing required field 'prevRandao' for PayloadAttributes") } p.Random = *dec.Random if dec.SuggestedFeeRecipient == nil { - return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributesV1") + return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributes") } p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient + if dec.Withdrawals != nil { + p.Withdrawals = dec.Withdrawals + } return nil } diff --git a/core/beacon/gen_ed.go b/core/beacon/gen_ed.go index dcee3bf18..397504da7 100644 --- a/core/beacon/gen_ed.go +++ b/core/beacon/gen_ed.go @@ -9,29 +9,31 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" ) var _ = (*executableDataMarshaling)(nil) // MarshalJSON marshals as JSON. -func (e ExecutableDataV1) MarshalJSON() ([]byte, error) { - type ExecutableDataV1 struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - 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"` +func (e ExecutableData) MarshalJSON() ([]byte, error) { + type ExecutableData struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + 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"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` } - var enc ExecutableDataV1 + var enc ExecutableData enc.ParentHash = e.ParentHash enc.FeeRecipient = e.FeeRecipient enc.StateRoot = e.StateRoot @@ -51,89 +53,94 @@ func (e ExecutableDataV1) MarshalJSON() ([]byte, error) { enc.Transactions[k] = v } } + enc.Withdrawals = e.Withdrawals return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. -func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error { - type ExecutableDataV1 struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random *common.Hash `json:"prevRandao" gencodec:"required"` - Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - 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"` +func (e *ExecutableData) UnmarshalJSON(input []byte) error { + type ExecutableData struct { + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + 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"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` } - var dec ExecutableDataV1 + var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { return err } if dec.ParentHash == nil { - return errors.New("missing required field 'parentHash' for ExecutableDataV1") + return errors.New("missing required field 'parentHash' for ExecutableData") } e.ParentHash = *dec.ParentHash if dec.FeeRecipient == nil { - return errors.New("missing required field 'feeRecipient' for ExecutableDataV1") + return errors.New("missing required field 'feeRecipient' for ExecutableData") } e.FeeRecipient = *dec.FeeRecipient if dec.StateRoot == nil { - return errors.New("missing required field 'stateRoot' for ExecutableDataV1") + return errors.New("missing required field 'stateRoot' for ExecutableData") } e.StateRoot = *dec.StateRoot if dec.ReceiptsRoot == nil { - return errors.New("missing required field 'receiptsRoot' for ExecutableDataV1") + return errors.New("missing required field 'receiptsRoot' for ExecutableData") } e.ReceiptsRoot = *dec.ReceiptsRoot if dec.LogsBloom == nil { - return errors.New("missing required field 'logsBloom' for ExecutableDataV1") + return errors.New("missing required field 'logsBloom' for ExecutableData") } e.LogsBloom = *dec.LogsBloom if dec.Random == nil { - return errors.New("missing required field 'prevRandao' for ExecutableDataV1") + return errors.New("missing required field 'prevRandao' for ExecutableData") } e.Random = *dec.Random if dec.Number == nil { - return errors.New("missing required field 'blockNumber' for ExecutableDataV1") + return errors.New("missing required field 'blockNumber' for ExecutableData") } e.Number = uint64(*dec.Number) if dec.GasLimit == nil { - return errors.New("missing required field 'gasLimit' for ExecutableDataV1") + return errors.New("missing required field 'gasLimit' for ExecutableData") } e.GasLimit = uint64(*dec.GasLimit) if dec.GasUsed == nil { - return errors.New("missing required field 'gasUsed' for ExecutableDataV1") + return errors.New("missing required field 'gasUsed' for ExecutableData") } e.GasUsed = uint64(*dec.GasUsed) if dec.Timestamp == nil { - return errors.New("missing required field 'timestamp' for ExecutableDataV1") + return errors.New("missing required field 'timestamp' for ExecutableData") } e.Timestamp = uint64(*dec.Timestamp) if dec.ExtraData == nil { - return errors.New("missing required field 'extraData' for ExecutableDataV1") + return errors.New("missing required field 'extraData' for ExecutableData") } e.ExtraData = *dec.ExtraData if dec.BaseFeePerGas == nil { - return errors.New("missing required field 'baseFeePerGas' for ExecutableDataV1") + return errors.New("missing required field 'baseFeePerGas' for ExecutableData") } e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) if dec.BlockHash == nil { - return errors.New("missing required field 'blockHash' for ExecutableDataV1") + return errors.New("missing required field 'blockHash' for ExecutableData") } e.BlockHash = *dec.BlockHash if dec.Transactions == nil { - return errors.New("missing required field 'transactions' for ExecutableDataV1") + return errors.New("missing required field 'transactions' for ExecutableData") } e.Transactions = make([][]byte, len(dec.Transactions)) for k, v := range dec.Transactions { e.Transactions[k] = v } + if dec.Withdrawals != nil { + e.Withdrawals = dec.Withdrawals + } return nil } diff --git a/core/beacon/gen_epe.go b/core/beacon/gen_epe.go new file mode 100644 index 000000000..0b4d8598f --- /dev/null +++ b/core/beacon/gen_epe.go @@ -0,0 +1,46 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package beacon + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*executionPayloadEnvelopeMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) { + type ExecutionPayloadEnvelope struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + } + var enc ExecutionPayloadEnvelope + enc.ExecutionPayload = e.ExecutionPayload + enc.BlockValue = (*hexutil.Big)(e.BlockValue) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { + type ExecutionPayloadEnvelope struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + } + var dec ExecutionPayloadEnvelope + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ExecutionPayload == nil { + return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope") + } + e.ExecutionPayload = dec.ExecutionPayload + if dec.BlockValue == nil { + return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope") + } + e.BlockValue = (*big.Int)(dec.BlockValue) + return nil +} diff --git a/core/beacon/types.go b/core/beacon/types.go index e06ab5c69..656115c78 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -26,38 +26,41 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go +//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go -// PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74 -type PayloadAttributesV1 struct { - Timestamp uint64 `json:"timestamp" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` +// PayloadAttributes describes the environment context in which a block should +// be built. +type PayloadAttributes struct { + Timestamp uint64 `json:"timestamp" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` } -// JSON type overrides for PayloadAttributesV1. +// JSON type overrides for PayloadAttributes. type payloadAttributesMarshaling struct { Timestamp hexutil.Uint64 } -//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go +//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go -// ExecutableDataV1 structure described at https://github.com/ethereum/execution-apis/tree/main/src/engine/specification.md -type ExecutableDataV1 struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom []byte `json:"logsBloom" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - Number uint64 `json:"blockNumber" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed" gencodec:"required"` - 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"` +// ExecutableData is the data necessary to execute an EL payload. +type ExecutableData struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom []byte `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number uint64 `json:"blockNumber" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + 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"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` } // JSON type overrides for executableData. @@ -72,6 +75,18 @@ type executableDataMarshaling struct { Transactions []hexutil.Bytes } +//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go + +type ExecutionPayloadEnvelope struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *big.Int `json:"blockValue" gencodec:"required"` +} + +// JSON type overrides for ExecutionPayloadEnvelope. +type executionPayloadEnvelopeMarshaling struct { + BlockValue *hexutil.Big +} + type PayloadStatusV1 struct { Status string `json:"status"` LatestValidHash *common.Hash `json:"latestValidHash"` @@ -141,8 +156,10 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { // uncleHash = emptyUncleHash // difficulty = 0 // -// and that the blockhash of the constructed block matches the parameters. -func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, 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) (*types.Block, error) { txs, err := decodeTransactions(params.Transactions) if err != nil { return nil, err @@ -157,34 +174,43 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) { return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas) } - header := &types.Header{ - ParentHash: params.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: params.FeeRecipient, - Root: params.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: params.ReceiptsRoot, - Bloom: types.BytesToBloom(params.LogsBloom), - Difficulty: common.Big0, - Number: new(big.Int).SetUint64(params.Number), - GasLimit: params.GasLimit, - GasUsed: params.GasUsed, - Time: params.Timestamp, - BaseFee: params.BaseFeePerGas, - Extra: params.ExtraData, - MixDigest: params.Random, + // Only set withdrawalsRoot if it is non-nil. This allows CLs to use + // ExecutableData before withdrawals are enabled by marshaling + // Withdrawals as the json null value. + var withdrawalsRoot *common.Hash + if params.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(params.Withdrawals), trie.NewStackTrie(nil)) + withdrawalsRoot = &h } - block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) + header := &types.Header{ + ParentHash: params.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.FeeRecipient, + Root: params.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: params.ReceiptsRoot, + Bloom: types.BytesToBloom(params.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(params.Number), + GasLimit: params.GasLimit, + GasUsed: params.GasUsed, + Time: params.Timestamp, + BaseFee: params.BaseFeePerGas, + Extra: params.ExtraData, + MixDigest: params.Random, + WithdrawalsHash: withdrawalsRoot, + } + block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) if block.Hash() != params.BlockHash { return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) } return block, nil } -// BlockToExecutableData constructs the executableDataV1 structure by filling the +// BlockToExecutableData constructs the ExecutableData structure by filling the // fields from the given block. It assumes the given block is post-merge block. -func BlockToExecutableData(block *types.Block) *ExecutableDataV1 { - return &ExecutableDataV1{ +func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadEnvelope { + data := &ExecutableData{ BlockHash: block.Hash(), ParentHash: block.ParentHash(), FeeRecipient: block.Coinbase(), @@ -199,5 +225,7 @@ func BlockToExecutableData(block *types.Block) *ExecutableDataV1 { Transactions: encodeTransactions(block.Transactions()), Random: block.MixDigest(), ExtraData: block.Extra(), + Withdrawals: block.Withdrawals(), } + return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees} } diff --git a/core/block_validator.go b/core/block_validator.go index 3763be0be..813ac0a43 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -65,6 +65,11 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash { return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash) } + if header.WithdrawalsHash != nil { + if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash { + return fmt.Errorf("withdrawals root hash mismatch: have %x, want %x", hash, *header.WithdrawalsHash) + } + } if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { return consensus.ErrUnknownAncestor diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 2f9e604de..5554361db 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4229,7 +4229,7 @@ func TestEIP3651(t *testing.T) { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") - engine = ethash.NewFaker() + engine = beacon.NewFaker() // A sender who makes transactions, has some funds key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -4237,8 +4237,9 @@ func TestEIP3651(t *testing.T) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + config = *params.AllEthashProtocolChanges gspec = &Genesis{ - Config: params.AllEthashProtocolChanges, + Config: &config, Alloc: GenesisAlloc{ addr1: {Balance: funds}, addr2: {Balance: funds}, @@ -4275,6 +4276,8 @@ func TestEIP3651(t *testing.T) { gspec.Config.BerlinBlock = common.Big0 gspec.Config.LondonBlock = common.Big0 + gspec.Config.TerminalTotalDifficulty = common.Big0 + gspec.Config.TerminalTotalDifficultyPassed = true gspec.Config.ShanghaiTime = u64(0) signer := types.LatestSigner(gspec.Config) @@ -4317,10 +4320,7 @@ func TestEIP3651(t *testing.T) { // 3: Ensure that miner received only the tx's tip. actual := state.GetBalance(block.Coinbase()) - expected := new(big.Int).Add( - new(big.Int).SetUint64(block.GasUsed()*block.Transactions()[0].GasTipCap().Uint64()), - ethash.ConstantinopleBlockReward, - ) + expected := new(big.Int).SetUint64(block.GasUsed() * block.Transactions()[0].GasTipCap().Uint64()) if actual.Cmp(expected) != 0 { t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) } diff --git a/core/chain_makers.go b/core/chain_makers.go index cbfe5c3ec..de63f234a 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -41,10 +41,11 @@ type BlockGen struct { header *types.Header statedb *state.StateDB - gasPool *GasPool - txs []*types.Transaction - receipts []*types.Receipt - uncles []*types.Header + gasPool *GasPool + txs []*types.Transaction + receipts []*types.Receipt + uncles []*types.Header + withdrawals []*types.Withdrawal config *params.ChainConfig engine consensus.Engine @@ -205,6 +206,26 @@ func (b *BlockGen) AddUncle(h *types.Header) { b.uncles = append(b.uncles, h) } +// AddWithdrawal adds a withdrawal to the generated block. +func (b *BlockGen) AddWithdrawal(w *types.Withdrawal) { + // The withdrawal will be assigned the next valid index. + var idx uint64 + for i := b.i - 1; i >= 0; i-- { + if wd := b.chain[i].Withdrawals(); len(wd) != 0 { + idx = wd[len(wd)-1].Index + 1 + break + } + if i == 0 { + // Correctly set the index if no parent had withdrawals + if wd := b.parent.Withdrawals(); len(wd) != 0 { + idx = wd[len(wd)-1].Index + 1 + } + } + } + w.Index = idx + b.withdrawals = append(b.withdrawals, w) +} + // PrevBlock returns a previously generated block by number. It panics if // num is greater or equal to the number of the block being generated. // For index -1, PrevBlock returns the parent block given to GenerateChain. @@ -281,8 +302,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse gen(i, b) } if b.engine != nil { - // Finalize and seal the block - block, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts) + block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) + if err != nil { + panic(err) + } // Write state changes to db root, err := statedb.Commit(config.IsEIP158(b.header.Number)) diff --git a/core/genesis.go b/core/genesis.go index b4f49a1e1..62096541f 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -469,6 +469,9 @@ func (g *Genesis) ToBlock() *types.Block { head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee) } } + if g.Config != nil && g.Config.IsShanghai(g.Timestamp) { + head.WithdrawalsHash = &types.EmptyRootHash + } return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index a323ab9ad..4df580e8c 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -760,7 +760,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block { if body == nil { return nil } - return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles) + return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals) } // WriteBlock serializes a block into the database, header and body separately. @@ -860,7 +860,7 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { } for _, bad := range badBlocks { if bad.Header.Hash() == hash { - return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles) + return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals) } } return nil @@ -879,7 +879,7 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { } var blocks []*types.Block for _, bad := range badBlocks { - blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)) + blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals)) } return blocks } diff --git a/core/state_processor.go b/core/state_processor.go index da886781e..163ea0a02 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -86,8 +86,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) } + // Fail if Shanghai not enabled and len(withdrawals) is non-zero. + withdrawals := block.Withdrawals() + if len(withdrawals) > 0 && !p.config.IsShanghai(block.Time()) { + return nil, nil, 0, fmt.Errorf("withdrawals before shanghai") + } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) + p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) return receipts, allLogs, *usedGas, nil } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index c91adc36e..305948d54 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/rawdb" @@ -314,22 +315,24 @@ func TestStateProcessorErrors(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{ Config: ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - ArrowGlacierBlock: big.NewInt(0), - GrayGlacierBlock: big.NewInt(0), - MergeNetsplitBlock: big.NewInt(0), - ShanghaiTime: u64(0), + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + ShanghaiTime: u64(0), }, Alloc: GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ @@ -339,7 +342,7 @@ func TestStateProcessorErrors(t *testing.T) { }, } genesis = gspec.MustCommit(db) - blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} smallInitCode = [320]byte{} ) @@ -361,7 +364,7 @@ func TestStateProcessorErrors(t *testing.T) { want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300", }, } { - block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) + block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) _, err := blockchain.InsertChain(types.Blocks{block}) if err == nil { t.Fatal("block imported without errors") @@ -378,23 +381,31 @@ func TestStateProcessorErrors(t *testing.T) { // valid to be considered for import: // - valid pow (fake), ancestry, difficulty, gaslimit etc func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block { - header := &types.Header{ - ParentHash: parent.Hash(), - Coinbase: parent.Coinbase(), - Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{ + difficulty := big.NewInt(0) + if !config.TerminalTotalDifficultyPassed { + difficulty = engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{ Number: parent.Number(), Time: parent.Time(), Difficulty: parent.Difficulty(), UncleHash: parent.UncleHash(), - }), - GasLimit: parent.GasLimit(), - Number: new(big.Int).Add(parent.Number(), common.Big1), - Time: parent.Time() + 10, - UncleHash: types.EmptyUncleHash, + }) + } + + header := &types.Header{ + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: difficulty, + GasLimit: parent.GasLimit(), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: parent.Time() + 10, + UncleHash: types.EmptyUncleHash, } if config.IsLondon(header.Number) { header.BaseFee = misc.CalcBaseFee(config, parent.Header()) } + if config.IsShanghai(header.Time) { + header.WithdrawalsHash = &types.EmptyRootHash + } var receipts []*types.Receipt // The post-state result doesn't need to be correct (this is a bad block), but we do need something there // Preferably something unique. So let's use a combo of blocknum + txhash diff --git a/core/types/block.go b/core/types/block.go index 603a3f771..ac8c031ac 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -87,6 +87,9 @@ type Header struct { // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + // WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers. + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + /* TODO (MariusVanDerWijden) Add this field once needed // Random was added during the merge and contains the BeaconState randomness @@ -149,9 +152,12 @@ func (h *Header) SanityCheck() error { } // EmptyBody returns true if there is no additional 'body' to complete the header -// that is: no transactions and no uncles. +// that is: no transactions, no uncles and no withdrawals. func (h *Header) EmptyBody() bool { - return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash + if h.WithdrawalsHash == nil { + return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash + } + return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash && *h.WithdrawalsHash == EmptyRootHash } // EmptyReceipts returns true if there are no receipts for this header/block. @@ -164,6 +170,7 @@ func (h *Header) EmptyReceipts() bool { type Body struct { Transactions []*Transaction Uncles []*Header + Withdrawals []*Withdrawal `rlp:"optional"` } // Block represents an entire block in the Ethereum blockchain. @@ -171,6 +178,7 @@ type Block struct { header *Header uncles []*Header transactions Transactions + withdrawals Withdrawals // caches hash atomic.Value @@ -184,9 +192,10 @@ type Block struct { // "external" block encoding. used for eth protocol, etc. type extblock struct { - Header *Header - Txs []*Transaction - Uncles []*Header + Header *Header + Txs []*Transaction + Uncles []*Header + Withdrawals []*Withdrawal `rlp:"optional"` } // NewBlock creates a new block. The input data is copied, @@ -228,6 +237,28 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* return b } +// NewBlockWithWithdrawals creates a new block with withdrawals. The input data +// is copied, changes to header and to the field values will not +// affect the block. +// +// The values of TxHash, UncleHash, ReceiptHash and Bloom in header +// are ignored and set to values derived from the given txs, uncles +// and receipts. +func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block { + b := NewBlock(header, txs, uncles, receipts, hasher) + + if withdrawals == nil { + b.header.WithdrawalsHash = nil + } else if len(withdrawals) == 0 { + b.header.WithdrawalsHash = &EmptyRootHash + } else { + h := DeriveSha(Withdrawals(withdrawals), hasher) + b.header.WithdrawalsHash = &h + } + + return b.WithWithdrawals(withdrawals) +} + // NewBlockWithHeader creates a block with the given header data. The // header data is copied, changes to header and to the field values // will not affect the block. @@ -252,6 +283,9 @@ func CopyHeader(h *Header) *Header { cpy.Extra = make([]byte, len(h.Extra)) copy(cpy.Extra, h.Extra) } + if h.WithdrawalsHash != nil { + *cpy.WithdrawalsHash = *h.WithdrawalsHash + } return &cpy } @@ -262,7 +296,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { if err := s.Decode(&eb); err != nil { return err } - b.header, b.uncles, b.transactions = eb.Header, eb.Uncles, eb.Txs + b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals b.size.Store(rlp.ListSize(size)) return nil } @@ -270,9 +304,10 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { // EncodeRLP serializes b into the Ethereum RLP block format. func (b *Block) EncodeRLP(w io.Writer) error { return rlp.Encode(w, extblock{ - Header: b.header, - Txs: b.transactions, - Uncles: b.uncles, + Header: b.header, + Txs: b.transactions, + Uncles: b.uncles, + Withdrawals: b.withdrawals, }) } @@ -315,10 +350,14 @@ func (b *Block) BaseFee() *big.Int { return new(big.Int).Set(b.header.BaseFee) } +func (b *Block) Withdrawals() Withdrawals { + return b.withdrawals +} + func (b *Block) Header() *Header { return CopyHeader(b.header) } // Body returns the non-header content of the block. -func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} } +func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.withdrawals} } // Size returns the true RLP encoded storage size of the block, either by encoding // and returning it, or returning a previously cached value. @@ -361,6 +400,7 @@ func (b *Block) WithSeal(header *Header) *Block { header: &cpy, transactions: b.transactions, uncles: b.uncles, + withdrawals: b.withdrawals, } } @@ -378,6 +418,15 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { return block } +// WithWithdrawals sets the withdrawal contents of a block, does not return a new block. +func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { + if withdrawals != nil { + b.withdrawals = make([]*Withdrawal, len(withdrawals)) + copy(b.withdrawals, withdrawals) + } + return b +} + // Hash returns the keccak256 hash of b's header. // The hash is computed on the first call and cached thereafter. func (b *Block) Hash() common.Hash { diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 74746d033..5c8b81652 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -16,23 +16,24 @@ var _ = (*headerMarshaling)(nil) // MarshalJSON marshals as JSON. func (h Header) MarshalJSON() ([]byte, error) { type Header struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest common.Hash `json:"mixHash"` - Nonce BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` - Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + Hash common.Hash `json:"hash"` } var enc Header enc.ParentHash = h.ParentHash @@ -51,6 +52,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.MixDigest = h.MixDigest enc.Nonce = h.Nonce enc.BaseFee = (*hexutil.Big)(h.BaseFee) + enc.WithdrawalsHash = h.WithdrawalsHash enc.Hash = h.Hash() return json.Marshal(&enc) } @@ -58,22 +60,23 @@ func (h Header) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (h *Header) UnmarshalJSON(input []byte) error { type Header struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase *common.Address `json:"miner"` - Root *common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom *Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest *common.Hash `json:"mixHash"` - Nonce *BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase *common.Address `json:"miner"` + Root *common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom *Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest *common.Hash `json:"mixHash"` + Nonce *BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } var dec Header if err := json.Unmarshal(input, &dec); err != nil { @@ -139,5 +142,8 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.WithdrawalsHash != nil { + h.WithdrawalsHash = dec.WithdrawalsHash + } return nil } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index e1a687331..7fd2cf8f2 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -41,7 +41,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.MixDigest[:]) w.WriteBytes(obj.Nonce[:]) _tmp1 := obj.BaseFee != nil - if _tmp1 { + _tmp2 := obj.WithdrawalsHash != nil + if _tmp1 || _tmp2 { if obj.BaseFee == nil { w.Write(rlp.EmptyString) } else { @@ -51,6 +52,13 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBigInt(obj.BaseFee) } } + if _tmp2 { + if obj.WithdrawalsHash == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.WithdrawalsHash[:]) + } + } w.ListEnd(_tmp0) return w.Flush() } diff --git a/core/types/gen_withdrawal_json.go b/core/types/gen_withdrawal_json.go new file mode 100644 index 000000000..983a7f7a1 --- /dev/null +++ b/core/types/gen_withdrawal_json.go @@ -0,0 +1,55 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*withdrawalMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (w Withdrawal) MarshalJSON() ([]byte, error) { + type Withdrawal struct { + Index hexutil.Uint64 `json:"index"` + Validator hexutil.Uint64 `json:"validatorIndex"` + Address common.Address `json:"address"` + Amount hexutil.Uint64 `json:"amount"` + } + var enc Withdrawal + enc.Index = hexutil.Uint64(w.Index) + enc.Validator = hexutil.Uint64(w.Validator) + enc.Address = w.Address + enc.Amount = hexutil.Uint64(w.Amount) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (w *Withdrawal) UnmarshalJSON(input []byte) error { + type Withdrawal struct { + Index *hexutil.Uint64 `json:"index"` + Validator *hexutil.Uint64 `json:"validatorIndex"` + Address *common.Address `json:"address"` + Amount *hexutil.Uint64 `json:"amount"` + } + var dec Withdrawal + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Index != nil { + w.Index = uint64(*dec.Index) + } + if dec.Validator != nil { + w.Validator = uint64(*dec.Validator) + } + if dec.Address != nil { + w.Address = *dec.Address + } + if dec.Amount != nil { + w.Amount = uint64(*dec.Amount) + } + return nil +} diff --git a/core/types/gen_withdrawal_rlp.go b/core/types/gen_withdrawal_rlp.go new file mode 100644 index 000000000..d0b4e0147 --- /dev/null +++ b/core/types/gen_withdrawal_rlp.go @@ -0,0 +1,20 @@ +// Code generated by rlpgen. DO NOT EDIT. + +//go:build !norlpgen +// +build !norlpgen + +package types + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Withdrawal) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteUint64(obj.Index) + w.WriteUint64(obj.Validator) + w.WriteBytes(obj.Address[:]) + w.WriteUint64(obj.Amount) + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/withdrawal.go b/core/types/withdrawal.go new file mode 100644 index 000000000..d1ad918f9 --- /dev/null +++ b/core/types/withdrawal.go @@ -0,0 +1,56 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" +) + +//go:generate go run github.com/fjl/gencodec -type Withdrawal -field-override withdrawalMarshaling -out gen_withdrawal_json.go +//go:generate go run ../../rlp/rlpgen -type Withdrawal -out gen_withdrawal_rlp.go + +// Withdrawal represents a validator withdrawal from the consensus layer. +type Withdrawal struct { + Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer + Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal + Address common.Address `json:"address"` // target address for withdrawn ether + Amount uint64 `json:"amount"` // value of withdrawal in Gwei +} + +// field type overrides for gencodec +type withdrawalMarshaling struct { + Index hexutil.Uint64 + Validator hexutil.Uint64 + Amount hexutil.Uint64 +} + +// Withdrawals implements DerivableList for withdrawals. +type Withdrawals []*Withdrawal + +// Len returns the length of s. +func (s Withdrawals) Len() int { return len(s) } + +// EncodeIndex encodes the i'th withdrawal to w. Note that this does not check for errors +// because we assume that *Withdrawal will only ever contain valid withdrawals that were either +// constructed by decoding or via public API in this package. +func (s Withdrawals) EncodeIndex(i int, w *bytes.Buffer) { + rlp.Encode(w, s[i]) +} diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 15a1f1a1b..56ff5eeab 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -119,7 +119,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) - cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -153,7 +152,6 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) - // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 8dd71f48a..39dcba04f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -155,7 +155,19 @@ func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { // // If there are payloadAttributes: we try to assemble a block with the payloadAttributes // and return its payloadID. -func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { +func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributes) (beacon.ForkChoiceResponse, error) { + if payloadAttributes != nil && payloadAttributes.Withdrawals != nil { + return beacon.STATUS_INVALID, fmt.Errorf("withdrawals not supported in V1") + } + return api.forkchoiceUpdated(update, payloadAttributes) +} + +// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload attributes. +func (api *ConsensusAPI) ForkchoiceUpdatedV2(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributes) (beacon.ForkChoiceResponse, error) { + return api.forkchoiceUpdated(update, payloadAttributes) +} + +func (api *ConsensusAPI) forkchoiceUpdated(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributes) (beacon.ForkChoiceResponse, error) { api.forkchoiceLock.Lock() defer api.forkchoiceLock.Unlock() @@ -285,6 +297,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa Timestamp: payloadAttributes.Timestamp, FeeRecipient: payloadAttributes.SuggestedFeeRecipient, Random: payloadAttributes.Random, + Withdrawals: payloadAttributes.Withdrawals, } id := args.Id() // If we already are busy generating this work, then we do not need @@ -334,7 +347,20 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.Transit } // GetPayloadV1 returns a cached payload by id. -func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { +func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableData, error) { + data, err := api.getPayload(payloadID) + if err != nil { + return nil, err + } + return data.ExecutionPayload, nil +} + +// GetPayloadV2 returns a cached payload by id. +func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutionPayloadEnvelope, error) { + return api.getPayload(payloadID) +} + +func (api *ConsensusAPI) getPayload(payloadID beacon.PayloadID) (*beacon.ExecutionPayloadEnvelope, error) { log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) data := api.localBlocks.get(payloadID) if data == nil { @@ -344,7 +370,19 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu } // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. -func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) { +func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableData) (beacon.PayloadStatusV1, error) { + if params.Withdrawals != nil { + return beacon.PayloadStatusV1{Status: beacon.INVALID}, fmt.Errorf("withdrawals not supported in V1") + } + return api.newPayload(params) +} + +// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. +func (api *ConsensusAPI) NewPayloadV2(params beacon.ExecutableData) (beacon.PayloadStatusV1, error) { + return api.newPayload(params) +} + +func (api *ConsensusAPI) newPayload(params beacon.ExecutableData) (beacon.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 @@ -361,7 +399,7 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.Pa api.newPayloadLock.Lock() defer api.newPayloadLock.Unlock() - log.Trace("Engine API request received", "method", "ExecutePayload", "number", params.Number, "hash", params.BlockHash) + log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) block, err := beacon.ExecutableDataToBlock(params) if err != nil { log.Debug("Invalid NewPayload params", "params", params, "error", err) diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 00ef2203f..8df48bd08 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -18,6 +18,7 @@ package catalyst import ( "bytes" + "context" "fmt" "math/big" "sync" @@ -26,6 +27,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + beaconConsensus "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/beacon" @@ -38,6 +41,7 @@ import ( "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" ) @@ -51,8 +55,14 @@ var ( testBalance = big.NewInt(2e18) ) -func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) { +func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { config := *params.AllEthashProtocolChanges + engine := consensus.Engine(beaconConsensus.New(ethash.NewFaker())) + if merged { + config.TerminalTotalDifficulty = common.Big0 + config.TerminalTotalDifficultyPassed = true + engine = beaconConsensus.NewFaker() + } genesis := &core.Genesis{ Config: &config, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, @@ -69,17 +79,21 @@ func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) { g.AddTx(tx) testNonce++ } - _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), n, generate) - totalDifficulty := big.NewInt(0) - for _, b := range blocks { - totalDifficulty.Add(totalDifficulty, b.Difficulty()) + _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate) + + if !merged { + totalDifficulty := big.NewInt(0) + for _, b := range blocks { + totalDifficulty.Add(totalDifficulty, b.Difficulty()) + } + config.TerminalTotalDifficulty = totalDifficulty } - config.TerminalTotalDifficulty = totalDifficulty + return genesis, blocks } func TestEth2AssembleBlock(t *testing.T) { - genesis, blocks := generatePreMergeChain(10) + genesis, blocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, blocks) defer n.Close() @@ -90,7 +104,7 @@ func TestEth2AssembleBlock(t *testing.T) { t.Fatalf("error signing transaction, err=%v", err) } ethservice.TxPool().AddLocal(tx) - blockParams := beacon.PayloadAttributesV1{ + blockParams := beacon.PayloadAttributes{ Timestamp: blocks[9].Time() + 5, } // The miner needs to pick up on the txs in the pool, so a few retries might be @@ -102,7 +116,7 @@ func TestEth2AssembleBlock(t *testing.T) { // assembleWithTransactions tries to assemble a block, retrying until it has 'want', // number of transactions in it, or it has retried three times. -func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1, want int) (execData *beacon.ExecutableDataV1, err error) { +func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributes, want int) (execData *beacon.ExecutableData, err error) { for retries := 3; retries > 0; retries-- { execData, err = assembleBlock(api, parentHash, params) if err != nil { @@ -118,7 +132,7 @@ func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params } func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { - genesis, blocks := generatePreMergeChain(10) + genesis, blocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, blocks[:9]) defer n.Close() @@ -126,7 +140,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { // Put the 10th block's tx in the pool and produce a new block api.eth.TxPool().AddRemotesSync(blocks[9].Transactions()) - blockParams := beacon.PayloadAttributesV1{ + blockParams := beacon.PayloadAttributes{ Timestamp: blocks[8].Time() + 5, } // The miner needs to pick up on the txs in the pool, so a few retries might be @@ -137,7 +151,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { } func TestSetHeadBeforeTotalDifficulty(t *testing.T) { - genesis, blocks := generatePreMergeChain(10) + genesis, blocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, blocks) defer n.Close() @@ -155,7 +169,7 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) { } func TestEth2PrepareAndGetPayload(t *testing.T) { - genesis, blocks := generatePreMergeChain(10) + genesis, blocks := generateMergeChain(10, false) // 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]) @@ -165,7 +179,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { // Put the 10th block's tx in the pool and produce a new block ethservice.TxPool().AddLocals(blocks[9].Transactions()) - blockParams := beacon.PayloadAttributesV1{ + blockParams := beacon.PayloadAttributes{ Timestamp: blocks[8].Time() + 5, } fcState := beacon.ForkchoiceStateV1{ @@ -221,7 +235,7 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan co } func TestInvalidPayloadTimestamp(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() var ( @@ -244,7 +258,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) { - params := beacon.PayloadAttributesV1{ + params := beacon.PayloadAttributes{ Timestamp: test.time, Random: crypto.Keccak256Hash([]byte{byte(123)}), SuggestedFeeRecipient: parent.Coinbase(), @@ -265,7 +279,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) { } func TestEth2NewBlock(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -288,7 +302,7 @@ 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 := assembleWithTransactions(api, parent.Hash(), &beacon.PayloadAttributesV1{ + execData, err := assembleWithTransactions(api, parent.Hash(), &beacon.PayloadAttributes{ Timestamp: parent.Time() + 5, }, 1) if err != nil { @@ -330,7 +344,7 @@ func TestEth2NewBlock(t *testing.T) { ) parent = preMergeBlocks[len(preMergeBlocks)-1] for i := 0; i < 10; i++ { - execData, err := assembleBlock(api, parent.Hash(), &beacon.PayloadAttributesV1{ + execData, err := assembleBlock(api, parent.Hash(), &beacon.PayloadAttributes{ Timestamp: parent.Time() + 6, }) if err != nil { @@ -367,7 +381,7 @@ func TestEth2DeepReorg(t *testing.T) { // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg // before the totalTerminalDifficulty threshold /* - genesis, preMergeBlocks := generatePreMergeChain(core.TriesInMemory * 2) + genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -442,7 +456,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) } func TestFullAPI(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() var ( @@ -494,7 +508,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Bl } func TestExchangeTransitionConfig(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -555,7 +569,7 @@ We expect └── P1'' */ func TestNewPayloadOnInvalidChain(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -577,7 +591,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { }) ethservice.TxPool().AddRemotesSync([]*types.Transaction{tx}) var ( - params = beacon.PayloadAttributesV1{ + params = beacon.PayloadAttributes{ Timestamp: parent.Time() + 1, Random: crypto.Keccak256Hash([]byte{byte(i)}), SuggestedFeeRecipient: parent.Coinbase(), @@ -587,7 +601,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } - payload *beacon.ExecutableDataV1 + payload *beacon.ExecutableData resp beacon.ForkChoiceResponse err error ) @@ -634,22 +648,23 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { } } -func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) { +func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributes) (*beacon.ExecutableData, error) { args := &miner.BuildPayloadArgs{ Parent: parentHash, Timestamp: params.Timestamp, FeeRecipient: params.SuggestedFeeRecipient, Random: params.Random, + Withdrawals: params.Withdrawals, } payload, err := api.eth.Miner().BuildPayload(args) if err != nil { return nil, err } - return payload.ResolveFull(), nil + return payload.ResolveFull().ExecutionPayload, nil } func TestEmptyBlocks(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -708,8 +723,8 @@ func TestEmptyBlocks(t *testing.T) { } } -func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Block) *beacon.ExecutableDataV1 { - params := beacon.PayloadAttributesV1{ +func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Block) *beacon.ExecutableData { + params := beacon.PayloadAttributes{ Timestamp: parent.Time() + 1, Random: crypto.Keccak256Hash([]byte{byte(1)}), SuggestedFeeRecipient: parent.Coinbase(), @@ -724,7 +739,7 @@ func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Block) *beacon // setBlockhash sets the blockhash of a modified ExecutableData. // Can be used to make modified payloads look valid. -func setBlockhash(data *beacon.ExecutableDataV1) *beacon.ExecutableDataV1 { +func setBlockhash(data *beacon.ExecutableData) *beacon.ExecutableData { txs, _ := decodeTransactions(data.Transactions) number := big.NewInt(0) number.SetUint64(data.Number) @@ -764,7 +779,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { func TestTrickRemoteBlockCache(t *testing.T) { // Setup two nodes - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) nodeA, ethserviceA := startEthService(t, genesis, preMergeBlocks) nodeB, ethserviceB := startEthService(t, genesis, preMergeBlocks) defer nodeA.Close() @@ -783,7 +798,7 @@ func TestTrickRemoteBlockCache(t *testing.T) { setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Block) {}) commonAncestor = ethserviceA.BlockChain().CurrentBlock() - var invalidChain []*beacon.ExecutableDataV1 + var invalidChain []*beacon.ExecutableData // create a valid payload (P1) //payload1 := getNewPayload(t, apiA, commonAncestor) //invalidChain = append(invalidChain, payload1) @@ -827,7 +842,7 @@ func TestTrickRemoteBlockCache(t *testing.T) { } func TestInvalidBloom(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) ethservice.Merger().ReachTTD() defer n.Close() @@ -851,12 +866,12 @@ func TestInvalidBloom(t *testing.T) { } func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(100) - + genesis, preMergeBlocks := generateMergeChain(100, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty()) + var ( api = NewConsensusAPI(ethservice) parent = preMergeBlocks[len(preMergeBlocks)-1] @@ -887,7 +902,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { if err != nil { t.Fatalf("error preparing payload, err=%v", err) } - data := *payload.Resolve() + data := *payload.Resolve().ExecutionPayload resp2, err := api.NewPayloadV1(data) if err != nil { t.Fatalf("error sending NewPayload, err=%v", err) @@ -901,7 +916,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { // newPayLoad and forkchoiceUpdate. This is to test that the api behaves // well even of the caller is not being 'serial'. func TestSimultaneousNewBlock(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(10) + genesis, preMergeBlocks := generateMergeChain(10, false) n, ethservice := startEthService(t, genesis, preMergeBlocks) defer n.Close() @@ -910,7 +925,7 @@ func TestSimultaneousNewBlock(t *testing.T) { parent = preMergeBlocks[len(preMergeBlocks)-1] ) for i := 0; i < 10; i++ { - execData, err := assembleBlock(api, parent.Hash(), &beacon.PayloadAttributesV1{ + execData, err := assembleBlock(api, parent.Hash(), &beacon.PayloadAttributes{ Timestamp: parent.Time() + 5, }) if err != nil { @@ -984,3 +999,117 @@ func TestSimultaneousNewBlock(t *testing.T) { parent = block } } + +// TestWithdrawals creates and verifies two post-Shanghai blocks. The first +// includes zero withdrawals and the second includes two. +func TestWithdrawals(t *testing.T) { + genesis, blocks := generateMergeChain(10, true) + // Set shanghai time to last block + 5 seconds (first post-merge block) + time := blocks[len(blocks)-1].Time() + 5 + genesis.Config.ShanghaiTime = &time + + n, ethservice := startEthService(t, genesis, blocks) + ethservice.Merger().ReachTTD() + defer n.Close() + + api := NewConsensusAPI(ethservice) + + // 10: Build Shanghai block with no withdrawals. + parent := ethservice.BlockChain().CurrentHeader() + blockParams := beacon.PayloadAttributes{ + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + } + fcState := beacon.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + } + resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + if resp.PayloadStatus.Status != beacon.VALID { + t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, beacon.VALID) + } + + // 10: verify state root is the same as parent + payloadID := (&miner.BuildPayloadArgs{ + Parent: fcState.HeadBlockHash, + Timestamp: blockParams.Timestamp, + FeeRecipient: blockParams.SuggestedFeeRecipient, + Random: blockParams.Random, + }).Id() + execData, err := api.GetPayloadV2(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + if execData.ExecutionPayload.StateRoot != parent.Root { + t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.ExecutionPayload.StateRoot, blocks[8].Root()) + } + + // 10: verify locally built block + if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { + t.Fatalf("error validating payload: %v", err) + } else if status.Status != beacon.VALID { + t.Fatalf("invalid payload") + } + + // 11: build shanghai block with withdrawal + aa := common.Address{0xaa} + bb := common.Address{0xbb} + blockParams = beacon.PayloadAttributes{ + Timestamp: execData.ExecutionPayload.Timestamp + 5, + Withdrawals: []*types.Withdrawal{ + { + Index: 0, + Address: aa, + Amount: 32, + }, + { + Index: 1, + Address: bb, + Amount: 33, + }, + }, + } + fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash + _, err = api.ForkchoiceUpdatedV2(fcState, &blockParams) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + + // 11: verify locally build block. + payloadID = (&miner.BuildPayloadArgs{ + Parent: fcState.HeadBlockHash, + Timestamp: blockParams.Timestamp, + FeeRecipient: blockParams.SuggestedFeeRecipient, + Random: blockParams.Random, + }).Id() + execData, err = api.GetPayloadV2(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { + t.Fatalf("error validating payload: %v", err) + } else if status.Status != beacon.VALID { + t.Fatalf("invalid payload") + } + + // 11: set block as head. + fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash + _, err = api.ForkchoiceUpdatedV2(fcState, nil) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + + // 11: verify withdrawals were processed. + db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number)) + if err != nil { + t.Fatalf("unable to load db: %v", err) + } + for i, w := range blockParams.Withdrawals { + // w.Amount is in gwei, balance in wei + if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei { + t.Fatalf("failed to process withdrawal %d", i) + } + } +} diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go index c15799487..5c60f70e4 100644 --- a/eth/catalyst/queue.go +++ b/eth/catalyst/queue.go @@ -70,7 +70,7 @@ func (q *payloadQueue) put(id beacon.PayloadID, payload *miner.Payload) { } // get retrieves a previously stored payload item or nil if it does not exist. -func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutableDataV1 { +func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutionPayloadEnvelope { q.lock.RLock() defer q.lock.RUnlock() diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index f58da869e..f7790b2d8 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -1548,7 +1548,7 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error { ) blocks := make([]*types.Block, len(results)) for i, result := range results { - blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) + blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) } // Downloaded blocks are always regarded as trusted after the // transition. Because the downloaded chain is guided by the @@ -1748,7 +1748,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state blocks := make([]*types.Block, len(results)) receipts := make([]types.Receipts, len(results)) for i, result := range results { - blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) + blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) receipts[i] = result.Receipts } if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { @@ -1759,7 +1759,7 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state } func (d *Downloader) commitPivotBlock(result *fetchResult) error { - block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) + block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals) log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash()) // Commit the pivot block as the new head, will require full sync from here on diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 36d6795e7..2f0c4acf7 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -273,8 +273,9 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et rlp.DecodeBytes(blob, bodies[i]) } var ( - txsHashes = make([]common.Hash, len(bodies)) - uncleHashes = make([]common.Hash, len(bodies)) + txsHashes = make([]common.Hash, len(bodies)) + uncleHashes = make([]common.Hash, len(bodies)) + withdrawalHashes = make([]common.Hash, len(bodies)) ) hasher := trie.NewStackTrie(nil) for i, body := range bodies { @@ -287,7 +288,7 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et res := ð.Response{ Req: req, Res: (*eth.BlockBodiesPacket)(&bodies), - Meta: [][]common.Hash{txsHashes, uncleHashes}, + Meta: [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes}, Time: 1, Done: make(chan error, 1), // Ignore the returned status } diff --git a/eth/downloader/fetchers_concurrent_bodies.go b/eth/downloader/fetchers_concurrent_bodies.go index e84206fe9..9440972c6 100644 --- a/eth/downloader/fetchers_concurrent_bodies.go +++ b/eth/downloader/fetchers_concurrent_bodies.go @@ -89,10 +89,10 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan // deliver is responsible for taking a generic response packet from the concurrent // fetcher, unpacking the body data and delivering it to the downloader's queue. func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) { - txs, uncles := packet.Res.(*eth.BlockBodiesPacket).Unpack() - hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes} + txs, uncles, withdrawals := packet.Res.(*eth.BlockBodiesPacket).Unpack() + hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes} - accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1]) + accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2]) switch { case err == nil && len(txs) == 0: peer.log.Trace("Requested bodies delivered") diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 60a83a7fb..c71b36466 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -67,6 +67,7 @@ type fetchResult struct { Uncles []*types.Header Transactions types.Transactions Receipts types.Receipts + Withdrawals types.Withdrawals } func newFetchResult(header *types.Header, fastSync bool) *fetchResult { @@ -764,7 +765,9 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []comm // DeliverBodies injects a block body retrieval response into the results queue. // The method returns the number of blocks bodies accepted from the delivery and // also wakes any threads waiting for data delivery. -func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash, uncleLists [][]*types.Header, uncleListHashes []common.Hash) (int, error) { +func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash, + uncleLists [][]*types.Header, uncleListHashes []common.Hash, + withdrawalLists [][]*types.Withdrawal, withdrawalListHashes []common.Hash) (int, error) { q.lock.Lock() defer q.lock.Unlock() @@ -775,12 +778,19 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if uncleListHashes[index] != header.UncleHash { return errInvalidBody } + if header.WithdrawalsHash == nil { + // discard any withdrawals if we don't have a withdrawal hash set + withdrawalLists[index] = nil + } else if withdrawalListHashes[index] != *header.WithdrawalsHash { + return errInvalidBody + } return nil } reconstruct := func(index int, result *fetchResult) { result.Transactions = txLists[index] result.Uncles = uncleLists[index] + result.Withdrawals = withdrawalLists[index] result.SetBodyDone() } return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index 8631b27c9..6babf9440 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -339,7 +339,7 @@ func XTestDelivery(t *testing.T) { uncleHashes[i] = types.CalcUncleHash(uncles) } time.Sleep(100 * time.Millisecond) - _, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes) + _, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes, nil, nil) if err != nil { fmt.Printf("delivered %d bodies %v\n", len(txset), err) } diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index bd1a34c83..156d07e91 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -540,8 +540,8 @@ func (f *BlockFetcher) loop() { select { case res := <-resCh: res.Done <- nil - - txs, uncles := res.Res.(*eth.BlockBodiesPacket).Unpack() + // Ignoring withdrawals here, since the block fetcher is not used post-merge. + txs, uncles, _ := res.Res.(*eth.BlockBodiesPacket).Unpack() f.FilterBodies(peer, txs, uncles, time.Now()) case <-timeout.C: diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 5c3d1be0a..201dc98b6 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -23,6 +23,8 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -45,6 +47,8 @@ var ( testAddr = crypto.PubkeyToAddress(testKey.PublicKey) ) +func u64(val uint64) *uint64 { return &val } + // testBackend is a mock implementation of the live Ethereum message handler. Its // purpose is to allow testing the request/reply workflows and wire serialization // in the `eth` protocol without actually doing any data processing. @@ -56,21 +60,53 @@ type testBackend struct { // newTestBackend creates an empty chain and wraps it into a mock backend. func newTestBackend(blocks int) *testBackend { - return newTestBackendWithGenerator(blocks, nil) + return newTestBackendWithGenerator(blocks, false, nil) } // newTestBackend creates a chain with a number of explicitly defined blocks and // wraps it into a mock backend. -func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen)) *testBackend { - // Create a database pre-initialize with a genesis block - db := rawdb.NewMemoryDatabase() +func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, *core.BlockGen)) *testBackend { + var ( + // Create a database pre-initialize with a genesis block + db = rawdb.NewMemoryDatabase() + config = params.TestChainConfig + engine consensus.Engine = ethash.NewFaker() + ) + + if shanghai { + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + ShanghaiTime: u64(0), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + Ethash: new(params.EthashConfig), + } + engine = beacon.NewFaker() + } + gspec := &core.Genesis{ - Config: params.TestChainConfig, + Config: config, Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}}, } - chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil) - _, bs, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), blocks, generator) + _, bs, _ := core.GenerateChainWithGenesis(gspec, engine, blocks, generator) if _, err := chain.InsertChain(bs); err != nil { panic(err) } @@ -305,7 +341,17 @@ func TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) } func testGetBlockBodies(t *testing.T, protocol uint) { t.Parallel() - backend := newTestBackend(maxBodiesServe + 15) + gen := func(n int, g *core.BlockGen) { + if n%2 == 0 { + w := &types.Withdrawal{ + Address: common.Address{0xaa}, + Amount: 42, + } + g.AddWithdrawal(w) + } + } + + backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen) defer backend.close() peer, _ := newTestPeer("peer", protocol, backend) @@ -355,7 +401,7 @@ func testGetBlockBodies(t *testing.T, protocol uint) { block := backend.chain.GetBlockByNumber(uint64(num)) hashes = append(hashes, block.Hash()) if len(bodies) < tt.expected { - bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles(), Withdrawals: block.Withdrawals()}) } break } @@ -365,9 +411,10 @@ func testGetBlockBodies(t *testing.T, protocol uint) { hashes = append(hashes, hash) if tt.available[j] && len(bodies) < tt.expected { block := backend.chain.GetBlockByHash(hash) - bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles(), Withdrawals: block.Withdrawals()}) } } + // Send the hash request and verify the response p2p.Send(peer.app, GetBlockBodiesMsg, &GetBlockBodiesPacket66{ RequestId: 123, @@ -426,7 +473,7 @@ func testGetNodeData(t *testing.T, protocol uint, drop bool) { } } // Assemble the test environment - backend := newTestBackendWithGenerator(4, generator) + backend := newTestBackendWithGenerator(4, false, generator) defer backend.close() peer, _ := newTestPeer("peer", protocol, backend) @@ -544,7 +591,7 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { } } // Assemble the test environment - backend := newTestBackendWithGenerator(4, generator) + backend := newTestBackendWithGenerator(4, false, generator) defer backend.close() peer, _ := newTestPeer("peer", protocol, backend) diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 85a59969e..74e514b86 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -379,15 +379,19 @@ func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { } metadata := func() interface{} { var ( - txsHashes = make([]common.Hash, len(res.BlockBodiesPacket)) - uncleHashes = make([]common.Hash, len(res.BlockBodiesPacket)) + txsHashes = make([]common.Hash, len(res.BlockBodiesPacket)) + uncleHashes = make([]common.Hash, len(res.BlockBodiesPacket)) + withdrawalHashes = make([]common.Hash, len(res.BlockBodiesPacket)) ) hasher := trie.NewStackTrie(nil) for i, body := range res.BlockBodiesPacket { txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher) uncleHashes[i] = types.CalcUncleHash(body.Uncles) + if body.Withdrawals != nil { + withdrawalHashes[i] = types.DeriveSha(types.Withdrawals(body.Withdrawals), hasher) + } } - return [][]common.Hash{txsHashes, uncleHashes} + return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes} } return peer.dispatchResponse(&Response{ id: res.RequestId, diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 6c59fcae6..0d4b36898 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -239,19 +239,22 @@ type BlockBodiesRLPPacket66 struct { type BlockBody struct { Transactions []*types.Transaction // Transactions contained within a block Uncles []*types.Header // Uncles contained within a block + Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block } // Unpack retrieves the transactions and uncles from the range packet and returns // them in a split flat format that's more consistent with the internal data structures. -func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header) { +func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) { + // TODO(matt): add support for withdrawals to fetchers var ( - txset = make([][]*types.Transaction, len(*p)) - uncleset = make([][]*types.Header, len(*p)) + txset = make([][]*types.Transaction, len(*p)) + uncleset = make([][]*types.Header, len(*p)) + withdrawalset = make([][]*types.Withdrawal, len(*p)) ) for i, body := range *p { - txset[i], uncleset[i] = body.Transactions, body.Uncles + txset[i], uncleset[i], withdrawalset[i] = body.Transactions, body.Uncles, body.Withdrawals } - return txset, uncleset + return txset, uncleset, withdrawalset } // GetNodeDataPacket represents a trie node data query. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e0e4278bb..1ac9c5faa 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1214,6 +1214,10 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) } + if head.WithdrawalsHash != nil { + result["withdrawalsRoot"] = head.WithdrawalsHash + } + return result } @@ -1242,6 +1246,8 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param } } fields["transactions"] = transactions + // inclTx also expands withdrawals + fields["withdrawals"] = block.Withdrawals() } uncles := block.Uncles() uncleHashes := make([]common.Hash, len(uncles)) diff --git a/les/catalyst/api.go b/les/catalyst/api.go index 822e0af03..b59575832 100644 --- a/les/catalyst/api.go +++ b/les/catalyst/api.go @@ -70,7 +70,7 @@ func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI { // // If there are payloadAttributes: we return an error since block creation is not // supported in les mode. -func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { +func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributes) (beacon.ForkChoiceResponse, error) { if heads.HeadBlockHash == (common.Hash{}) { log.Warn("Forkchoice requested update to zero hash") return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? @@ -100,12 +100,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, pay } // GetPayloadV1 returns a cached payload by id. It's not supported in les mode. -func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { +func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableData, error) { return nil, beacon.GenericServerError.With(errors.New("not supported in light client mode")) } // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. -func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) { +func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableData) (beacon.PayloadStatusV1, error) { block, err := beacon.ExecutableDataToBlock(params) if err != nil { return api.invalid(), err diff --git a/les/catalyst/api_test.go b/les/catalyst/api_test.go index 91d5c9bbb..2af90dfc6 100644 --- a/les/catalyst/api_test.go +++ b/les/catalyst/api_test.go @@ -130,7 +130,7 @@ func TestExecutePayloadV1(t *testing.T) { BaseFee: block.BaseFee(), }, nil, nil, nil, trie.NewStackTrie(nil)) - _, err := api.ExecutePayloadV1(beacon.ExecutableDataV1{ + _, err := api.ExecutePayloadV1(beacon.ExecutableData{ ParentHash: fakeBlock.ParentHash(), FeeRecipient: fakeBlock.Coinbase(), StateRoot: fakeBlock.Root(), diff --git a/miner/payload_building.go b/miner/payload_building.go index 2e3ebe356..75bca67cb 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -34,10 +34,11 @@ import ( // Check engine-api specification for more details. // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1 type BuildPayloadArgs struct { - Parent common.Hash // The parent block to build payload on top - Timestamp uint64 // The provided timestamp of generated payload - FeeRecipient common.Address // The provided recipient address for collecting transaction fee - Random common.Hash // The provided randomness value + Parent common.Hash // The parent block to build payload on top + Timestamp uint64 // The provided timestamp of generated payload + FeeRecipient common.Address // The provided recipient address for collecting transaction fee + Random common.Hash // The provided randomness value + Withdrawals types.Withdrawals // The provided withdrawals } // Id computes an 8-byte identifier by hashing the components of the payload arguments. @@ -107,7 +108,7 @@ func (payload *Payload) update(block *types.Block, fees *big.Int, elapsed time.D // Resolve returns the latest built payload and also terminates the background // thread for updating payload. It's safe to be called multiple times. -func (payload *Payload) Resolve() *beacon.ExecutableDataV1 { +func (payload *Payload) Resolve() *beacon.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() @@ -117,23 +118,23 @@ func (payload *Payload) Resolve() *beacon.ExecutableDataV1 { close(payload.stop) } if payload.full != nil { - return beacon.BlockToExecutableData(payload.full) + return beacon.BlockToExecutableData(payload.full, payload.fullFees) } - return beacon.BlockToExecutableData(payload.empty) + return beacon.BlockToExecutableData(payload.empty, big.NewInt(0)) } // ResolveEmpty is basically identical to Resolve, but it expects empty block only. // It's only used in tests. -func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV1 { +func (payload *Payload) ResolveEmpty() *beacon.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() - return beacon.BlockToExecutableData(payload.empty) + return beacon.BlockToExecutableData(payload.empty, big.NewInt(0)) } // ResolveFull is basically identical to Resolve, but it expects full block only. // It's only used in tests. -func (payload *Payload) ResolveFull() *beacon.ExecutableDataV1 { +func (payload *Payload) ResolveFull() *beacon.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() @@ -145,7 +146,7 @@ func (payload *Payload) ResolveFull() *beacon.ExecutableDataV1 { } payload.cond.Wait() } - return beacon.BlockToExecutableData(payload.full) + return beacon.BlockToExecutableData(payload.full, payload.fullFees) } // buildPayload builds the payload according to the provided parameters. @@ -153,7 +154,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { // Build the initial version with no transaction included. It should be fast // enough to run. The empty payload can at least make sure there is something // to deliver for not missing slot. - empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, true) + empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, true) if err != nil { return nil, err } @@ -177,7 +178,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { select { case <-timer.C: start := time.Now() - block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, false) + block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, false) if err == nil { payload.update(block, fees, time.Since(start)) } diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index 226ae71b4..8d6ffaff1 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -47,20 +47,21 @@ func TestBuildPayload(t *testing.T) { if err != nil { t.Fatalf("Failed to build payload %v", err) } - verify := func(data *beacon.ExecutableDataV1, txs int) { - if data.ParentHash != b.chain.CurrentBlock().Hash() { + verify := func(outer *beacon.ExecutionPayloadEnvelope, txs int) { + payload := outer.ExecutionPayload + if payload.ParentHash != b.chain.CurrentBlock().Hash() { t.Fatal("Unexpect parent hash") } - if data.Random != (common.Hash{}) { + if payload.Random != (common.Hash{}) { t.Fatal("Unexpect random value") } - if data.Timestamp != timestamp { + if payload.Timestamp != timestamp { t.Fatal("Unexpect timestamp") } - if data.FeeRecipient != recipient { + if payload.FeeRecipient != recipient { t.Fatal("Unexpect fee recipient") } - if len(data.Transactions) != txs { + if len(payload.Transactions) != txs { t.Fatal("Unexpect transaction set") } } diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go index 7dabc97c0..bd500453d 100644 --- a/miner/stress/beacon/main.go +++ b/miner/stress/beacon/main.go @@ -142,7 +142,7 @@ func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode } } -func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*beacon.ExecutableDataV1, error) { +func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*beacon.ExecutableData, error) { if n.typ != eth2MiningNode { return nil, errors.New("invalid node type") } @@ -150,7 +150,7 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) if timestamp <= parentTimestamp { timestamp = parentTimestamp + 1 } - payloadAttribute := beacon.PayloadAttributesV1{ + payloadAttribute := beacon.PayloadAttributes{ Timestamp: timestamp, Random: common.Hash{}, SuggestedFeeRecipient: common.HexToAddress("0xdeadbeef"), @@ -168,7 +168,7 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) return n.api.GetPayloadV1(*payload.PayloadID) } -func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error { +func (n *ethNode) insertBlock(eb beacon.ExecutableData) error { if !eth2types(n.typ) { return errors.New("invalid node type") } @@ -194,7 +194,7 @@ func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error { } } -func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed beacon.ExecutableDataV1) error { +func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed beacon.ExecutableData) error { if !eth2types(n.typ) { return errors.New("invalid node type") } diff --git a/miner/worker.go b/miner/worker.go index ee4969623..49204f71a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -968,13 +968,14 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // generateParams wraps various of settings for generating sealing task. type generateParams struct { - timestamp uint64 // The timestamp for sealing task - forceTime bool // Flag whether the given timestamp is immutable or not - parentHash common.Hash // Parent block hash, empty means the latest chain head - coinbase common.Address // The fee recipient address for including transaction - random common.Hash // The randomness generated by beacon chain, empty before the merge - noUncle bool // Flag whether the uncle block inclusion is allowed - noTxs bool // Flag whether an empty block without any transaction is expected + timestamp uint64 // The timstamp for sealing task + forceTime bool // Flag whether the given timestamp is immutable or not + parentHash common.Hash // Parent block hash, empty means the latest chain head + 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. + noUncle bool // Flag whether the uncle block inclusion is allowed + noTxs bool // Flag whether an empty block without any transaction is expected } // prepareWork constructs the sealing task according to the given parameters, @@ -1108,7 +1109,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout)) } } - block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts) + block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, params.withdrawals) if err != nil { return nil, nil, err } @@ -1193,7 +1194,8 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti // Create a local environment copy, avoid the data race with snapshot state. // https://github.com/ethereum/go-ethereum/issues/24299 env := env.copy() - block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.unclelist(), env.receipts) + // Withdrawals are set to nil here, because this is only called in PoW. + block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.unclelist(), env.receipts, nil) if err != nil { return err } @@ -1224,16 +1226,17 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti // getSealingBlock generates the sealing block based on the given parameters. // The generation result will be passed back via the given channel no matter // the generation itself succeeds or not. -func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, noTxs bool) (*types.Block, *big.Int, error) { +func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, withdrawals types.Withdrawals, noTxs bool) (*types.Block, *big.Int, error) { req := &getWorkReq{ params: &generateParams{ - timestamp: timestamp, - forceTime: true, - parentHash: parent, - coinbase: coinbase, - random: random, - noUncle: true, - noTxs: noTxs, + timestamp: timestamp, + forceTime: true, + parentHash: parent, + coinbase: coinbase, + random: random, + withdrawals: withdrawals, + noUncle: true, + noTxs: noTxs, }, result: make(chan *newPayloadResult, 1), } diff --git a/miner/worker_test.go b/miner/worker_test.go index 5db90546c..a3f46db17 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -637,7 +637,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // This API should work even when the automatic sealing is not enabled for _, c := range cases { - block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, false) + block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false) if c.expectErr { if err == nil { t.Error("Expect error but get nil") @@ -653,7 +653,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // This API should work even when the automatic sealing is enabled w.start() for _, c := range cases { - block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, false) + block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false) if c.expectErr { if err == nil { t.Error("Expect error but get nil")