From be4ad1973da4f229cc965c24b8cd41708d3a2f76 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:52:03 -0600 Subject: [PATCH 01/43] core/types: add withdrawal type --- core/types/block.go | 77 +++++++++++++++++++++++++++---- core/types/gen_header_json.go | 72 ++++++++++++++++------------- core/types/gen_header_rlp.go | 10 +++- core/types/gen_withdrawal_json.go | 56 ++++++++++++++++++++++ core/types/gen_withdrawal_rlp.go | 27 +++++++++++ core/types/withdrawal.go | 57 +++++++++++++++++++++++ 6 files changed, 257 insertions(+), 42 deletions(-) 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/core/types/block.go b/core/types/block.go index 603a3f771208..336a3998d01f 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 @@ -164,6 +167,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 +175,7 @@ type Block struct { header *Header uncles []*Header transactions Transactions + withdrawals []*Withdrawal // caches hash atomic.Value @@ -184,9 +189,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 +234,31 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* return b } +// NewBlock2 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 NewBlock2(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block { + b := NewBlock(header, txs, uncles, receipts, hasher) + + if withdrawals == nil { + // leave withdrawal hash empty + } else if len(withdrawals) == 0 { + b.header.WithdrawalsHash = &EmptyRootHash + b.withdrawals = make(Withdrawals, len(withdrawals)) + } else { + h := DeriveSha(Withdrawals(withdrawals), hasher) + b.header.WithdrawalsHash = &h + b.withdrawals = make(Withdrawals, len(withdrawals)) + copy(b.withdrawals, withdrawals) + } + + return b +} + // 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,10 @@ func CopyHeader(h *Header) *Header { cpy.Extra = make([]byte, len(h.Extra)) copy(cpy.Extra, h.Extra) } + if h.WithdrawalsHash != nil { + cpy.WithdrawalsHash = new(common.Hash) + cpy.WithdrawalsHash.SetBytes(h.WithdrawalsHash.Bytes()) + } return &cpy } @@ -262,7 +297,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 +305,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 +351,23 @@ func (b *Block) BaseFee() *big.Int { return new(big.Int).Set(b.header.BaseFee) } +func (b *Block) WithdrawalsHash() *common.Hash { + if b.header.WithdrawalsHash == nil { + return nil + } + var h common.Hash + h.SetBytes(b.header.WithdrawalsHash.Bytes()) + return &h +} + +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 +410,7 @@ func (b *Block) WithSeal(header *Header) *Block { header: &cpy, transactions: b.transactions, uncles: b.uncles, + withdrawals: b.withdrawals, } } @@ -378,6 +428,17 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { return block } +// WithBody2 returns a new block with the given transaction, uncle, and +// withdrawal contents. +func (b *Block) WithBody2(transactions []*Transaction, uncles []*Header, withdrawals []*Withdrawal) *Block { + block := b.WithBody(transactions, uncles) + if withdrawals != nil { + block.withdrawals = make([]*Withdrawal, len(withdrawals)) + copy(block.withdrawals, withdrawals) + } + return block +} + // 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 74746d033aa0..5c8b81652d2e 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 e1a687331853..7fd2cf8f2d3e 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 000000000000..6c259a94c468 --- /dev/null +++ b/core/types/gen_withdrawal_json.go @@ -0,0 +1,56 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "math/big" + + "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.Big `json:"amount"` + } + var enc Withdrawal + enc.Index = hexutil.Uint64(w.Index) + enc.Validator = hexutil.Uint64(w.Validator) + enc.Address = w.Address + enc.Amount = (*hexutil.Big)(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.Big `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 = (*big.Int)(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 000000000000..a16c828afb60 --- /dev/null +++ b/core/types/gen_withdrawal_rlp.go @@ -0,0 +1,27 @@ +// 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[:]) + if obj.Amount == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Amount.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(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 000000000000..5a93c97fe54f --- /dev/null +++ b/core/types/withdrawal.go @@ -0,0 +1,57 @@ +// 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" + "math/big" + + "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 *big.Int `json:"amount"` // value of withdrawal in wei +} + +// field type overrides for gencodec +type withdrawalMarshaling struct { + Index hexutil.Uint64 + Validator hexutil.Uint64 + Amount *hexutil.Big +} + +// 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]) +} From 72c91a4a788bfd11076138a38fa1bf59b24e2435 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:53:40 -0600 Subject: [PATCH 02/43] core/beacon: add withdrawals and make types version agnostic --- core/beacon/gen_blockparams.go | 37 ++++++----- core/beacon/gen_ed.go | 107 +++++++++++++++++--------------- core/beacon/types.go | 109 +++++++++++++++++++-------------- 3 files changed, 141 insertions(+), 112 deletions(-) diff --git a/core/beacon/gen_blockparams.go b/core/beacon/gen_blockparams.go index 0e2ea4bb1338..a7df96e091c1 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 dcee3bf18c79..397504da7f05 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"` - } - var enc ExecutableDataV1 +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 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"` - } - var dec ExecutableDataV1 +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 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/types.go b/core/beacon/types.go index e06ab5c692d9..5fcdfc4caec4 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 - -// 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"` +//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go + +// 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 - -// 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"` +//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go + +// 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. @@ -141,8 +144,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 +162,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) } + // 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 + } 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, + 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 */) + block := types.NewBlockWithHeader(header).WithBody2(txs, nil /* uncles */, 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) *ExecutableData { + return &ExecutableData{ BlockHash: block.Hash(), ParentHash: block.ParentHash(), FeeRecipient: block.Coinbase(), @@ -199,5 +213,6 @@ func BlockToExecutableData(block *types.Block) *ExecutableDataV1 { Transactions: encodeTransactions(block.Transactions()), Random: block.MixDigest(), ExtraData: block.Extra(), + Withdrawals: block.Withdrawals(), } } From 1779e45d0b34f5b1db13fd770b40fc47459bb566 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:54:45 -0600 Subject: [PATCH 03/43] eth/catalyst,les/catalyst: add withdrawals and make types version agnostic --- eth/catalyst/api.go | 42 +++++++- eth/catalyst/api_test.go | 198 +++++++++++++++++++++++++++++++------- eth/catalyst/queue.go | 2 +- les/catalyst/api.go | 6 +- les/catalyst/api_test.go | 2 +- miner/payload_building.go | 19 ++-- 6 files changed, 214 insertions(+), 55 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 8dd71f48a7f8..c9ecdb9f7e7c 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,16 @@ 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.GetPayloadV2(payloadID) + if err != nil { + return nil, err + } + return data, nil +} + +// GetPayloadV2 returns a cached payload by id. +func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutableData, error) { log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) data := api.localBlocks.get(payloadID) if data == nil { @@ -344,7 +366,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 +395,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 00ef2203f832..e59ab6a34849 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,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + 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 +40,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 +54,12 @@ 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 + if merged { + config.TerminalTotalDifficulty = common.Big0 + config.TerminalTotalDifficultyPassed = true + } genesis := &core.Genesis{ Config: &config, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, @@ -69,17 +76,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, beaconConsensus.New(ethash.NewFaker()), 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 +101,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 +113,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 +129,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 +137,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 +148,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 +166,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 +176,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 +232,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 +255,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 +276,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 +299,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 +341,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 +378,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 +453,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 +505,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 +566,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 +588,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 +598,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } - payload *beacon.ExecutableDataV1 + payload *beacon.ExecutableData resp beacon.ForkChoiceResponse err error ) @@ -634,12 +645,13 @@ 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 { @@ -649,7 +661,7 @@ func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.Pay } 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 +720,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 +736,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 +776,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 +795,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 +839,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 +863,12 @@ func TestInvalidBloom(t *testing.T) { } func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { - genesis, preMergeBlocks := generatePreMergeChain(100) + genesis, preMergeBlocks := generateMergeChain(100, false) + genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty()) 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] @@ -901,7 +913,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 +922,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 +996,115 @@ 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) + genesis.Config.ShanghaiBlock = big.NewInt(11) + genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[0].Difficulty()) + + 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() + params := beacon.PayloadAttributes{ + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + } + fcState := beacon.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + } + resp, err := api.ForkchoiceUpdatedV2(fcState, ¶ms) + 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: params.Timestamp, + FeeRecipient: params.SuggestedFeeRecipient, + Random: params.Random, + }).Id() + execData, err := api.GetPayloadV2(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + if execData.StateRoot != parent.Root { + t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.StateRoot, blocks[8].Root()) + } + + // 10: verify locally built block + if status, err := api.NewPayloadV2(*execData); 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} + params = beacon.PayloadAttributes{ + Timestamp: execData.Timestamp + 5, + Withdrawals: []*types.Withdrawal{ + { + Index: 0, + Address: aa, + Amount: big.NewInt(32), + }, + { + Index: 1, + Address: bb, + Amount: big.NewInt(33), + }, + }, + } + fcState.HeadBlockHash = execData.BlockHash + _, err = api.ForkchoiceUpdatedV2(fcState, ¶ms) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + + // 11: verify locally build block. + payloadID = (&miner.BuildPayloadArgs{ + Parent: fcState.HeadBlockHash, + Timestamp: params.Timestamp, + FeeRecipient: params.SuggestedFeeRecipient, + Random: params.Random, + }).Id() + execData, err = api.GetPayloadV2(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + if status, err := api.NewPayloadV2(*execData); 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.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.Number)) + if err != nil { + t.Fatalf("unable to load db: %v", err) + } + for i, w := range params.Withdrawals { + if db.GetBalance(w.Address).Uint64() != w.Amount.Uint64() { + t.Fatalf("failed to process withdrawal %d", i) + } + } +} diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go index c15799487f20..cfc487ab5021 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.ExecutableData { q.lock.RLock() defer q.lock.RUnlock() diff --git a/les/catalyst/api.go b/les/catalyst/api.go index 822e0af038a7..b5957583289d 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 91d5c9bbb988..2af90dfc6e69 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 2e3ebe356c59..e24fbc3c6a32 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.ExecutableData { payload.lock.Lock() defer payload.lock.Unlock() @@ -124,7 +125,7 @@ func (payload *Payload) Resolve() *beacon.ExecutableDataV1 { // 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.ExecutableData { payload.lock.Lock() defer payload.lock.Unlock() @@ -133,7 +134,7 @@ func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV1 { // 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.ExecutableData { payload.lock.Lock() defer payload.lock.Unlock() @@ -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)) } From 79ed6d4f32cd6dab917dd1eb9c09f7bad4e519a3 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:55:55 -0600 Subject: [PATCH 04/43] miner: add withdrawals and update catalyst types --- miner/payload_building_test.go | 2 +- miner/stress/beacon/main.go | 8 ++++---- miner/worker.go | 36 ++++++++++++++++++---------------- miner/worker_test.go | 4 ++-- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index 226ae71b4add..76bce583c1a7 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -47,7 +47,7 @@ func TestBuildPayload(t *testing.T) { if err != nil { t.Fatalf("Failed to build payload %v", err) } - verify := func(data *beacon.ExecutableDataV1, txs int) { + verify := func(data *beacon.ExecutableData, txs int) { if data.ParentHash != b.chain.CurrentBlock().Hash() { t.Fatal("Unexpect parent hash") } diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go index 7dabc97c003f..bd500453d2b3 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 ee4969623d12..37a31e1003ba 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,7 @@ 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) + 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 +1225,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 5db90546ceca..a3f46db17cc9 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") From ed15060bb4108c8868d8592ffc339c3020fabf67 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:56:41 -0600 Subject: [PATCH 05/43] consensus: process withdrawals --- consensus/beacon/consensus.go | 33 +++++++++++++++++++++++++-------- consensus/clique/clique.go | 9 ++++++--- consensus/consensus.go | 4 ++-- consensus/ethash/consensus.go | 9 ++++++--- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 274b9cf3603e..cba36044a19e 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.Number) + if shanghai && header.WithdrawalsHash == nil { + return fmt.Errorf("missing withdrawalsHash") + } + if !shanghai && header.WithdrawalsHash != nil { + return fmt.Errorf("invalid withdrawalsHash: have %s, expected nil", header.WithdrawalsHash) + } + return nil } // verifyHeaders is similar to verifyHeader, but verifies a batch of headers @@ -316,13 +327,19 @@ 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 } + // If withdrawals have been activated, process each one. + if chain.Config().IsShanghai(header.Number) { + for _, w := range withdrawals { + state.AddBalance(w.Address, w.Amount) + } + } // The block reward is no longer handled here. It's done by the // external consensus engine. header.Root = state.IntermediateRoot(true) @@ -330,15 +347,15 @@ 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 + // Finalize and assemble the block. + beacon.Finalize(chain, header, state, txs, uncles, withdrawals) + return types.NewBlock2(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/clique/clique.go b/consensus/clique/clique.go index 53ccc34ccb8e..5b22ba07acca 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -564,7 +564,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 +572,9 @@ 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) { // 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 +743,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 af8ce98ff3be..190d5ae12c8a 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 b49fcf0ce5a7..c82bbea26741 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -597,7 +597,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,9 +605,9 @@ 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) { +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) { // Finalize block - ethash.Finalize(chain, header, state, txs, uncles) + 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 +635,9 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { if header.BaseFee != nil { enc = append(enc, header.BaseFee) } + if header.WithdrawalsHash != nil { + enc = append(enc, header.WithdrawalsHash) + } rlp.Encode(hasher, enc) hasher.Sum(hash[:0]) return hash From 43ff6856ae7e79abff59b43bd4bc31cfab08a8a9 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:57:45 -0600 Subject: [PATCH 06/43] core/state_processor: finalize with withdrawals --- core/state_processor.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/state_processor.go b/core/state_processor.go index da886781e322..5942cbff9f33 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -86,8 +86,12 @@ 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. + if !p.config.IsShanghai(block.Number()) && len(block.Withdrawals()) != 0 { + return nil, nil, 0, fmt.Errorf("non-nil withdrawal 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(), block.Withdrawals()) return receipts, allLogs, *usedGas, nil } From 5e37321ffb46e3b43783446505fb692f897f920f Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:58:12 -0600 Subject: [PATCH 07/43] core/chain_makers: add withdrawal support --- core/chain_makers.go | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/core/chain_makers.go b/core/chain_makers.go index cbfe5c3ece16..2e065b933031 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,20 @@ 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 + } + } + 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. @@ -282,7 +297,14 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } if b.engine != nil { // Finalize and seal the block - block, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts) + shanghai := config.IsShanghai(b.header.Number) + if shanghai && b.withdrawals == nil { + // need to make empty list to denote non-nil, but empty withdrawals to calc withdrawals hash + b.withdrawals = make([]*types.Withdrawal, 0) + } else if !shanghai && b.withdrawals != nil { + panic("withdrawals set before activation") + } + block, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) // Write state changes to db root, err := statedb.Commit(config.IsEIP158(b.header.Number)) @@ -403,4 +425,9 @@ func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil } func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil } func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } -func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil } +func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { + if cr.config.TerminalTotalDifficultyPassed { + return cr.config.TerminalTotalDifficulty + } + return nil +} From cf344656d87008d6669f5849525bd86d8d909a26 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:59:01 -0600 Subject: [PATCH 08/43] core/block_validator: verify withdrawals root --- core/block_validator.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/block_validator.go b/core/block_validator.go index 3763be0be08d..42fcc5b45747 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 v.config.IsShanghai(block.Number()) { + 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 From e7170c22b1940135328cb6a94929a30bb57379bc Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:59:43 -0600 Subject: [PATCH 09/43] core/rawdb: update block readers with new block format --- core/rawdb/accessors_chain.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index a323ab9ad8b9..6487716e6255 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).WithBody2(body.Transactions, body.Uncles, 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).WithBody2(bad.Body.Transactions, bad.Body.Uncles, 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).WithBody2(bad.Body.Transactions, bad.Body.Uncles, bad.Body.Withdrawals)) } return blocks } From 18a3ceddd864eee90f3fff3349358e1180a6e230 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 09:00:11 -0600 Subject: [PATCH 10/43] eth: add p2p support for withdrawals --- eth/downloader/downloader_test.go | 7 +- eth/downloader/fetchers_concurrent_bodies.go | 6 +- eth/downloader/queue.go | 9 ++- eth/downloader/queue_test.go | 2 +- eth/fetcher/block_fetcher.go | 19 +++--- eth/fetcher/block_fetcher_test.go | 63 ++++++++++++++---- eth/protocols/eth/handler_test.go | 69 ++++++++++++++++---- eth/protocols/eth/handlers.go | 10 ++- eth/protocols/eth/protocol.go | 13 ++-- 9 files changed, 150 insertions(+), 48 deletions(-) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 36d6795e7afe..2f0c4acf7887 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 e84206fe9951..9440972c6d70 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 60a83a7fb0d9..61c61352d564 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,16 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if uncleListHashes[index] != header.UncleHash { return errInvalidBody } + if header.WithdrawalsHash != nil && (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 8631b27c9275..6babf9440869 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 bd1a34c83c00..1d256c294477 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -125,6 +125,7 @@ type bodyFilterTask struct { peer string // The source peer of block bodies transactions [][]*types.Transaction // Collection of transactions per block bodies uncles [][]*types.Header // Collection of uncles per block bodies + withdrawals [][]*types.Withdrawal // Collection of withdrawals per block bodies time time.Time // Arrival time of the blocks' contents } @@ -302,8 +303,8 @@ func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time // FilterBodies extracts all the block bodies that were explicitly requested by // the fetcher, returning those that should be handled differently. -func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) { - log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles)) +func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, withdrawals [][]*types.Withdrawal, time time.Time) ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) { + log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles), "withdrawals", len(withdrawals)) // Send the filter channel to the fetcher filter := make(chan *bodyFilterTask) @@ -311,20 +312,20 @@ func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transac select { case f.bodyFilter <- filter: case <-f.quit: - return nil, nil + return nil, nil, nil } // Request the filtering of the body list select { - case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, time: time}: + case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, withdrawals: withdrawals, time: time}: case <-f.quit: - return nil, nil + return nil, nil, nil } // Retrieve the bodies remaining after filtering select { case task := <-filter: - return task.transactions, task.uncles + return task.transactions, task.uncles, task.withdrawals case <-f.quit: - return nil, nil + return nil, nil, nil } } @@ -541,8 +542,8 @@ func (f *BlockFetcher) loop() { case res := <-resCh: res.Done <- nil - txs, uncles := res.Res.(*eth.BlockBodiesPacket).Unpack() - f.FilterBodies(peer, txs, uncles, time.Now()) + txs, uncles, wxs := res.Res.(*eth.BlockBodiesPacket).Unpack() + f.FilterBodies(peer, txs, uncles, wxs, time.Now()) case <-timeout.C: // The peer didn't respond in time. The request diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 9e5693c02e5a..05c2c642dcd4 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "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" @@ -36,24 +37,53 @@ import ( ) var ( - testdb = rawdb.NewMemoryDatabase() - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddress = crypto.PubkeyToAddress(testKey.PublicKey) - gspec = &core.Genesis{ - Config: params.TestChainConfig, + testdb = rawdb.NewMemoryDatabase() + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddress = crypto.PubkeyToAddress(testKey.PublicKey) + unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) + gspec *core.Genesis + genesis *types.Block +) + +func init() { + 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), + ShanghaiBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + Ethash: new(params.EthashConfig), + } + + gspec = &core.Genesis{ + Config: config, Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, BaseFee: big.NewInt(params.InitialBaseFee), } - genesis = gspec.MustCommit(testdb) - unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) -) + genesis = gspec.MustCommit(testdb) +} // makeChain creates a chain of n blocks starting at and including parent. // the returned hash chain is ordered head->parent. In addition, every 3rd block // contains a transaction and every 5th an uncle to allow testing correct block // reassembly. func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { - blocks, _ := core.GenerateChain(gspec.Config, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(gspec.Config, parent, beacon.New(ethash.NewFaker()), testdb, n, func(i int, block *core.BlockGen) { block.SetCoinbase(common.Address{seed}) // If the block number is multiple of 3, send a bonus transaction to the miner @@ -69,6 +99,12 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common if i > 0 && i%5 == 0 { block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))}) } + // If the block number is a multiple of 7, add a withdrawal + if i > 0 && i%7 == 0 { + block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: big.NewInt(42)}) + block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: big.NewInt(43)}) + block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: big.NewInt(44)}) + } }) hashes := make([]common.Hash, n+1) hashes[len(hashes)-1] = parent.Hash() @@ -233,13 +269,17 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ // Create a function that returns blocks from the closure return func(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) { // Gather the block bodies to return - transactions := make([][]*types.Transaction, 0, len(hashes)) - uncles := make([][]*types.Header, 0, len(hashes)) + var ( + transactions = make([][]*types.Transaction, 0, len(hashes)) + uncles = make([][]*types.Header, 0, len(hashes)) + withdrawals = make([][]*types.Withdrawal, 0, len(hashes)) + ) for _, hash := range hashes { if block, ok := closure[hash]; ok { transactions = append(transactions, block.Transactions()) uncles = append(uncles, block.Uncles()) + withdrawals = append(withdrawals, block.Withdrawals()) } } // Return on a new thread @@ -248,6 +288,7 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ bodies[i] = ð.BlockBody{ Transactions: txs, Uncles: uncles[i], + Withdrawals: withdrawals[i], } } req := ð.Request{ diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 5c3d1be0a123..f28b4ed9b853 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" @@ -56,21 +58,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), + ShanghaiBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + Ethash: new(params.EthashConfig), + } + engine = beacon.New(ethash.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 +339,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: big.NewInt(42), + } + g.AddWithdrawal(w) + } + } + + backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen) defer backend.close() peer, _ := newTestPeer("peer", protocol, backend) @@ -355,7 +399,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 +409,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 +471,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 +589,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 85a59969ebf8..74e514b863a3 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 6c59fcae655a..0d4b368988f7 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. From 8c55bd6294d957caf5ee1013e7541c46ec81d002 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 09:00:42 -0600 Subject: [PATCH 11/43] cmd/evm: support withdrawals in t8n and b11r --- cmd/evm/internal/t8ntool/block.go | 108 ++++++++++++++----------- cmd/evm/internal/t8ntool/execution.go | 30 ++++--- 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 ++ 17 files changed, 234 insertions(+), 98 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 diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index 4a070b6c71b5..18e7abcc00e1 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).WithBody2(i.Txs, i.Ommers, 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 63c54b1d8a6c..1ba334a85dae 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,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } statedb.AddBalance(pre.Env.Coinbase, minerReward) } + // Apply withdrawals + for _, w := range pre.Env.Withdrawals { + statedb.AddBalance(w.Address, w.Amount) + } // Commit block root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { @@ -272,6 +278,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 626220315e19..339523593a90 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 196e49dd716f..76228394dc1d 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 da449e659dca..c2cc3a2c8a38 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 8b05f1def9db..97217e4cfa2e 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(big.NewInt(int64(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 5f9e75f48c6f..a9ce83a3e657 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 b7a0d9c2c3c7..067660c7e7ff 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 000000000000..d67655a8a8e6 --- /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 000000000000..03d817b93bc6 --- /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 000000000000..c5f8fee6ab2b --- /dev/null +++ b/cmd/evm/testdata/26/exp.json @@ -0,0 +1,20 @@ +{ + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x2a", + "nonce": "0xac" + } + }, + "result": { + "stateRoot": "0x0c34df727f6f9a5243d21ded6349df8bbe8498e23fffef65ebb7c2bbecc53057", + "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 000000000000..fe51488c7066 --- /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 000000000000..5975a9c25af5 --- /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 000000000000..4ed7eaca09d6 --- /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 000000000000..fe51488c7066 --- /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 000000000000..e815397b333b --- /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 000000000000..6634aff089b1 --- /dev/null +++ b/cmd/evm/testdata/27/withdrawals.json @@ -0,0 +1,8 @@ +[ + { + "index": "0x42", + "validatorIndex": "0x43", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "amount": "0x2a" + } +] From a6dd7798094adf004dfa8fbbd0b009a0b20e17ba Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 29 Nov 2022 18:05:31 -0700 Subject: [PATCH 12/43] eth/downloader: always use WithBody2 when constructing blocks Co-authored-by: lightclient Co-authored-by: marioevz --- eth/downloader/downloader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index f58da869ee30..2aae3394fa4d 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).WithBody2(result.Transactions, result.Uncles, 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).WithBody2(result.Transactions, result.Uncles, 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).WithBody2(result.Transactions, result.Uncles, 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 From f00089fdd4de011aad2d2edf71b806279c0ef563 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 29 Nov 2022 18:11:23 -0700 Subject: [PATCH 13/43] eth/fetcher: always use WithBody2 when constructing blocks --- eth/fetcher/block_fetcher.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 1d256c294477..dbdad62d2fb3 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -687,7 +687,7 @@ func (f *BlockFetcher) loop() { // Mark the body matched, reassemble if still unknown matched = true if f.getBlock(hash) == nil { - block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i]) + block := types.NewBlockWithHeader(announce.header).WithBody2(task.transactions[i], task.uncles[i], task.withdrawals[i]) block.ReceivedAt = task.time blocks = append(blocks, block) } else { @@ -697,6 +697,7 @@ func (f *BlockFetcher) loop() { if matched { task.transactions = append(task.transactions[:i], task.transactions[i+1:]...) task.uncles = append(task.uncles[:i], task.uncles[i+1:]...) + task.withdrawals = append(task.withdrawals[:i], task.withdrawals[i+1:]...) i-- continue } From c66dc48c516a2e1ac837bd369d7765f62b80045e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 27 Sep 2022 13:42:57 +0200 Subject: [PATCH 14/43] params: core: enable shanghai based on timestamps --- core/vm/runtime/runtime.go | 2 -- params/config.go | 8 +++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 15a1f1a1bd90..56ff5eeabe33 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/params/config.go b/params/config.go index aac4392301de..3f36b356730b 100644 --- a/params/config.go +++ b/params/config.go @@ -569,7 +569,13 @@ func (c *ChainConfig) Description() string { banner += fmt.Sprintf(" - Arrow Glacier: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/arrow-glacier.md)\n", c.ArrowGlacierBlock) } if c.GrayGlacierBlock != nil { - banner += fmt.Sprintf(" - Gray Glacier: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock) + banner += fmt.Sprintf(" - Gray Glacier: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock) + } + if c.ShanghaiTime != nil { + banner += fmt.Sprintf(" - Shanghai: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md)\n", c.ShanghaiTime) + } + if c.CancunBlock != nil { + banner += fmt.Sprintf(" - Cancun: %-8v\n", c.CancunBlock) } banner += "\n" From 7c1651b9652676c7ccfa878c91aae4ddf8300483 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 20:56:31 -0600 Subject: [PATCH 15/43] core,consensus: use timestamp based is shanghai check --- consensus/beacon/consensus.go | 4 ++-- core/block_validator.go | 3 ++- core/chain_makers.go | 2 +- core/state_processor.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index cba36044a19e..6ab95e0fcdc4 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -261,7 +261,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return err } // Verify existence / non-existence of withdrawalsHash. - shanghai := chain.Config().IsShanghai(header.Number) + shanghai := chain.Config().IsShanghai(new(big.Int).SetUint64(header.Time)) if shanghai && header.WithdrawalsHash == nil { return fmt.Errorf("missing withdrawalsHash") } @@ -335,7 +335,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. return } // If withdrawals have been activated, process each one. - if chain.Config().IsShanghai(header.Number) { + if chain.Config().IsShanghai(new(big.Int).SetUint64(header.Time)) { for _, w := range withdrawals { state.AddBalance(w.Address, w.Amount) } diff --git a/core/block_validator.go b/core/block_validator.go index 42fcc5b45747..1effba9fc5a8 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -18,6 +18,7 @@ package core import ( "fmt" + "math/big" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" @@ -65,7 +66,7 @@ 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 v.config.IsShanghai(block.Number()) { + if v.config.IsShanghai(new(big.Int).SetUint64(block.Time())) { 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) } diff --git a/core/chain_makers.go b/core/chain_makers.go index 2e065b933031..d37a20125420 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -297,7 +297,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } if b.engine != nil { // Finalize and seal the block - shanghai := config.IsShanghai(b.header.Number) + shanghai := config.IsShanghai(new(big.Int).SetUint64(b.header.Time)) if shanghai && b.withdrawals == nil { // need to make empty list to denote non-nil, but empty withdrawals to calc withdrawals hash b.withdrawals = make([]*types.Withdrawal, 0) diff --git a/core/state_processor.go b/core/state_processor.go index 5942cbff9f33..c9473b2d77e5 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -87,7 +87,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg allLogs = append(allLogs, receipt.Logs...) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. - if !p.config.IsShanghai(block.Number()) && len(block.Withdrawals()) != 0 { + if !p.config.IsShanghai(new(big.Int).SetUint64(block.Time())) && len(block.Withdrawals()) != 0 { return nil, nil, 0, fmt.Errorf("non-nil withdrawal before shanghai") } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) From 218b71a33b23c4ba86869ebe769acaebfef4ab22 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sun, 6 Nov 2022 06:44:10 -0700 Subject: [PATCH 16/43] consensus: fail if running shanghai on clique or ethash --- consensus/clique/clique.go | 3 +++ consensus/ethash/consensus.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 5b22ba07acca..3d369d2aeeaf 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(new(big.Int).SetUint64(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 diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index c82bbea26741..29cd4a404e4b 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(new(big.Int).SetUint64(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 { From 5c5cd11c1f2c08cf3a7168e3ff2d3ae044667ffa Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Nov 2022 10:48:12 +0100 Subject: [PATCH 17/43] core: set withdrawals hash in genesis, fix tests, add withdrawalRoot --- core/genesis.go | 3 +++ eth/catalyst/api_test.go | 2 +- eth/fetcher/block_fetcher_test.go | 2 +- eth/protocols/eth/handler_test.go | 2 +- internal/ethapi/api.go | 4 ++++ 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/genesis.go b/core/genesis.go index b4f49a1e1a8e..142f478a4aef 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(big.NewInt(int64(g.Timestamp))) { + head.WithdrawalsHash = &types.EmptyRootHash + } return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index e59ab6a34849..ef0f91cceef8 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1001,7 +1001,7 @@ func TestSimultaneousNewBlock(t *testing.T) { // includes zero withdrawals and the second includes two. func TestWithdrawals(t *testing.T) { genesis, blocks := generateMergeChain(10, true) - genesis.Config.ShanghaiBlock = big.NewInt(11) + genesis.Config.ShanghaiTime = big.NewInt(11) genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[0].Difficulty()) n, ethservice := startEthService(t, genesis, blocks) diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 05c2c642dcd4..3b99af585e58 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -64,7 +64,7 @@ func init() { ArrowGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0), MergeNetsplitBlock: big.NewInt(0), - ShanghaiBlock: big.NewInt(0), + ShanghaiTime: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, Ethash: new(params.EthashConfig), diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index f28b4ed9b853..b1145a3bd433 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -90,7 +90,7 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, ArrowGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0), MergeNetsplitBlock: big.NewInt(0), - ShanghaiBlock: big.NewInt(0), + ShanghaiTime: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, Ethash: new(params.EthashConfig), diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e0e4278bb467..e19422bda6ad 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 } From 2e8233a9f6fe351a11f7aa0bb8a88e7177177cf2 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 10 Nov 2022 15:25:32 +0100 Subject: [PATCH 18/43] all: implement forkid changes for shanghai --- core/blockchain_test.go | 10 ++++------ core/forkid/forkid.go | 7 +++++++ eth/catalyst/api_test.go | 3 ++- eth/protocols/eth/protocol.go | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 2f9e604de9d1..723f57f389c8 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.New(ethash.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}, @@ -4317,10 +4318,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/forkid/forkid.go b/core/forkid/forkid.go index f536019dac12..ffc709e81543 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -139,6 +139,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u forks = append(append([]uint64{}, forksByBlock...), forksByTime...) sums = make([][4]byte, len(forks)+1) // 0th is the genesis ) + forks = append(forks, forksByTime...) hash := crc32.ChecksumIEEE(genesis[:]) sums[0] = checksumToBytes(hash) for i, fork := range forks { @@ -286,6 +287,12 @@ func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) { i-- } } + for i := 1; i < len(forksByTime); i++ { + if forksByTime[i] == forksByTime[i-1] { + forksByTime = append(forksByTime[:i], forksByTime[i+1:]...) + i-- + } + } // Skip any forks in block 0, that's the genesis ruleset if len(forksByBlock) > 0 && forksByBlock[0] == 0 { forksByBlock = forksByBlock[1:] diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index ef0f91cceef8..8075df275a83 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1001,7 +1001,8 @@ func TestSimultaneousNewBlock(t *testing.T) { // includes zero withdrawals and the second includes two. func TestWithdrawals(t *testing.T) { genesis, blocks := generateMergeChain(10, true) - genesis.Config.ShanghaiTime = big.NewInt(11) + // Set shanghai time to last block + 5 seconds (first post-merge block) + genesis.Config.ShanghaiTime = big.NewInt(int64(blocks[len(blocks)-1].Time()) + 5) genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[0].Difficulty()) n, ethservice := startEthService(t, genesis, blocks) diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 0d4b368988f7..37fc7ed67fcc 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -41,7 +41,7 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH68, ETH67, ETH66} +var ProtocolVersions = []uint{ETH67, ETH68, ETH66} // protocolLengths are the number of implemented message corresponding to // different protocol versions. From 9c905c29be30b1c5ce09ddc03430c28aabb0d86f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 1 Dec 2022 16:26:12 +0100 Subject: [PATCH 19/43] core/types: make fetchers fetch withdrawals without transactions --- core/types/block.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 336a3998d01f..0de37ed8a6fb 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -152,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. From 5daaafef3c6e384ddec4f7238e366a4b1478cc7d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 22 Nov 2022 13:59:48 +0100 Subject: [PATCH 20/43] internal/ethapi: add withdrawals to full block --- internal/ethapi/api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e19422bda6ad..1ac9c5faa74a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1246,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)) From f701ea43a142c1df65a5e4e90f9da3f20f81ccbf Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 7 Dec 2022 11:36:00 +0100 Subject: [PATCH 21/43] internal/ethapi: add comment --- internal/ethapi/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1ac9c5faa74a..59ad995011d9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1247,6 +1247,7 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param } fields["transactions"] = transactions // inclTx also expands withdrawals + // TODO @MariusVanDerWijden: add a second flag similar to inclTx to enable withdrawals fields["withdrawals"] = block.Withdrawals() } uncles := block.Uncles() From 91b284eb73f7e02c29b51e5a8cebdef2355936e6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sun, 16 Oct 2022 22:02:54 +0200 Subject: [PATCH 22/43] miner: catalyst: return transaction fees for block --- core/beacon/types.go | 10 ++++++++-- eth/catalyst/api.go | 4 ++-- eth/catalyst/api_test.go | 20 ++++++++++---------- eth/catalyst/queue.go | 2 +- miner/payload_building.go | 14 +++++++------- miner/payload_building_test.go | 13 +++++++------ 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/core/beacon/types.go b/core/beacon/types.go index 5fcdfc4caec4..8cf946681c35 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -75,6 +75,11 @@ type executableDataMarshaling struct { Transactions []hexutil.Bytes } +type ExecutableDataV2 struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *big.Int `json:"blockValue" gencodec:"required"` +} + type PayloadStatusV1 struct { Status string `json:"status"` LatestValidHash *common.Hash `json:"latestValidHash"` @@ -197,8 +202,8 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { // 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) *ExecutableData { - return &ExecutableData{ +func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutableDataV2 { + data := &ExecutableData{ BlockHash: block.Hash(), ParentHash: block.ParentHash(), FeeRecipient: block.Coinbase(), @@ -215,4 +220,5 @@ func BlockToExecutableData(block *types.Block) *ExecutableData { ExtraData: block.Extra(), Withdrawals: block.Withdrawals(), } + return &ExecutableDataV2{ExecutionPayload: data, BlockValue: fees} } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index c9ecdb9f7e7c..18c38cd9d3e7 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -352,11 +352,11 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu if err != nil { return nil, err } - return data, nil + return data.ExecutionPayload, nil } // GetPayloadV2 returns a cached payload by id. -func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutableData, error) { +func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutableDataV2, error) { log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) data := api.localBlocks.get(payloadID) if data == nil { diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 8075df275a83..7cdfb2d51aff 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -657,7 +657,7 @@ func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.Pay if err != nil { return nil, err } - return payload.ResolveFull(), nil + return payload.ResolveFull().ExecutionPayload, nil } func TestEmptyBlocks(t *testing.T) { @@ -899,7 +899,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) @@ -1039,12 +1039,12 @@ func TestWithdrawals(t *testing.T) { if err != nil { t.Fatalf("error getting payload, err=%v", err) } - if execData.StateRoot != parent.Root { - t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.StateRoot, blocks[8].Root()) + 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); err != nil { + 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") @@ -1054,7 +1054,7 @@ func TestWithdrawals(t *testing.T) { aa := common.Address{0xaa} bb := common.Address{0xbb} params = beacon.PayloadAttributes{ - Timestamp: execData.Timestamp + 5, + Timestamp: execData.ExecutionPayload.Timestamp + 5, Withdrawals: []*types.Withdrawal{ { Index: 0, @@ -1068,7 +1068,7 @@ func TestWithdrawals(t *testing.T) { }, }, } - fcState.HeadBlockHash = execData.BlockHash + fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash _, err = api.ForkchoiceUpdatedV2(fcState, ¶ms) if err != nil { t.Fatalf("error preparing payload, err=%v", err) @@ -1085,21 +1085,21 @@ func TestWithdrawals(t *testing.T) { if err != nil { t.Fatalf("error getting payload, err=%v", err) } - if status, err := api.NewPayloadV2(*execData); err != nil { + 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.BlockHash + 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.Number)) + db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number)) if err != nil { t.Fatalf("unable to load db: %v", err) } diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go index cfc487ab5021..a34880e493d6 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.ExecutableData { +func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutableDataV2 { q.lock.RLock() defer q.lock.RUnlock() diff --git a/miner/payload_building.go b/miner/payload_building.go index e24fbc3c6a32..846c2899c31c 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -108,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.ExecutableData { +func (payload *Payload) Resolve() *beacon.ExecutableDataV2 { payload.lock.Lock() defer payload.lock.Unlock() @@ -118,23 +118,23 @@ func (payload *Payload) Resolve() *beacon.ExecutableData { 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.ExecutableData { +func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV2 { 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.ExecutableData { +func (payload *Payload) ResolveFull() *beacon.ExecutableDataV2 { payload.lock.Lock() defer payload.lock.Unlock() @@ -146,7 +146,7 @@ func (payload *Payload) ResolveFull() *beacon.ExecutableData { } payload.cond.Wait() } - return beacon.BlockToExecutableData(payload.full) + return beacon.BlockToExecutableData(payload.full, payload.fullFees) } // buildPayload builds the payload according to the provided parameters. diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index 76bce583c1a7..ec4e9fb15134 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.ExecutableData, txs int) { - if data.ParentHash != b.chain.CurrentBlock().Hash() { + verify := func(outer *beacon.ExecutableDataV2, 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") } } From 47b582da0e2fe93badfb76f7450f873894d500d8 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 13 Dec 2022 11:32:06 +0100 Subject: [PATCH 23/43] core/forkid: fix forkid filter --- core/forkid/forkid.go | 46 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index ffc709e81543..0f5c43fb4f13 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -139,10 +139,10 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u forks = append(append([]uint64{}, forksByBlock...), forksByTime...) sums = make([][4]byte, len(forks)+1) // 0th is the genesis ) - forks = append(forks, forksByTime...) + allForks := append(forks, forksByTime...) hash := crc32.ChecksumIEEE(genesis[:]) sums[0] = checksumToBytes(hash) - for i, fork := range forks { + for i, fork := range allForks { hash = checksumUpdate(hash, fork) sums[i+1] = checksumToBytes(hash) } @@ -202,7 +202,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u for j := 0; j < i; j++ { if sums[j] == id.Hash { // Remote checksum is a subset, validate based on the announced next fork - if forks[j] != id.Next { + if allForks[j] != id.Next { return ErrRemoteStale } return nil @@ -219,6 +219,46 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u // No exact, subset or superset match. We are on differing chains, reject. return ErrLocalIncompatibleOrStale } + // Verify forks by time + for i := len(forks); i < len(forks)+len(forksByTime); i++ { + fork := forksByTime[i-len(forks)] + if time >= fork { + continue + } + // Found the first unpassed fork block, check if our current state matches + // the remote checksum (rule #1). + if sums[i] == id.Hash { + // Fork checksum matched, check if a remote future fork block already passed + // locally without the local node being aware of it (rule #1a). + if id.Next > 0 && time >= id.Next { + return ErrLocalIncompatibleOrStale + } + // Haven't passed locally a remote-only fork, accept the connection (rule #1b). + return nil + } + // The local and remote nodes are in different forks currently, check if the + // remote checksum is a subset of our local forks (rule #2). + for j := 0; j < i; j++ { + if sums[j] == id.Hash { + // Remote checksum is a subset, validate based on the announced next fork + if allForks[j] != id.Next { + return ErrRemoteStale + } + return nil + } + } + // Remote chain is not a subset of our local one, check if it's a superset by + // any chance, signalling that we're simply out of sync (rule #3). + for j := i + 1; j < len(sums); j++ { + if sums[j] == id.Hash { + // Yay, remote checksum is a superset, ignore upcoming forks + return nil + } + } + // No exact, subset or superset match. We are on differing chains, reject. + return ErrLocalIncompatibleOrStale + } + log.Error("Impossible fork ID validation", "id", id) return nil // Something's very wrong, accept rather than reject } From a6862762c9aaaf7eb4b4c19dae086a3c60a6801f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 15 Dec 2022 14:16:15 +0100 Subject: [PATCH 24/43] core/forkid: reduce diff by anonymous function --- core/forkid/forkid.go | 52 ++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 0f5c43fb4f13..5a649bc91d22 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -188,7 +188,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u } // Found the first unpassed fork block, check if our current state matches // the remote checksum (rule #1). - if sums[i] == id.Hash { + if sums[index] == id.Hash { // Fork checksum matched, check if a remote future fork block already passed // locally without the local node being aware of it (rule #1a). if id.Next > 0 && (head >= id.Next || (id.Next > timestampThreshold && time >= id.Next)) { @@ -199,7 +199,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u } // The local and remote nodes are in different forks currently, check if the // remote checksum is a subset of our local forks (rule #2). - for j := 0; j < i; j++ { + for j := 0; j < index; j++ { if sums[j] == id.Hash { // Remote checksum is a subset, validate based on the announced next fork if allForks[j] != id.Next { @@ -210,7 +210,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u } // Remote chain is not a subset of our local one, check if it's a superset by // any chance, signalling that we're simply out of sync (rule #3). - for j := i + 1; j < len(sums); j++ { + for j := index + 1; j < len(sums); j++ { if sums[j] == id.Hash { // Yay, remote checksum is a superset, ignore upcoming forks return nil @@ -219,44 +219,26 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u // No exact, subset or superset match. We are on differing chains, reject. return ErrLocalIncompatibleOrStale } + + head, time := headfn() + // Verify forks by block + for i, fork := range forks { + // If our head is beyond this fork, continue to the next (we have a dummy + // fork of maxuint64 as the last item to always fail this check eventually). + if head >= fork { + continue + } + return verify(i, head) + } // Verify forks by time for i := len(forks); i < len(forks)+len(forksByTime); i++ { fork := forksByTime[i-len(forks)] + // If our head is beyond this fork, continue to the next (we have a dummy + // fork of maxuint64 as the last item to always fail this check eventually). if time >= fork { continue } - // Found the first unpassed fork block, check if our current state matches - // the remote checksum (rule #1). - if sums[i] == id.Hash { - // Fork checksum matched, check if a remote future fork block already passed - // locally without the local node being aware of it (rule #1a). - if id.Next > 0 && time >= id.Next { - return ErrLocalIncompatibleOrStale - } - // Haven't passed locally a remote-only fork, accept the connection (rule #1b). - return nil - } - // The local and remote nodes are in different forks currently, check if the - // remote checksum is a subset of our local forks (rule #2). - for j := 0; j < i; j++ { - if sums[j] == id.Hash { - // Remote checksum is a subset, validate based on the announced next fork - if allForks[j] != id.Next { - return ErrRemoteStale - } - return nil - } - } - // Remote chain is not a subset of our local one, check if it's a superset by - // any chance, signalling that we're simply out of sync (rule #3). - for j := i + 1; j < len(sums); j++ { - if sums[j] == id.Hash { - // Yay, remote checksum is a superset, ignore upcoming forks - return nil - } - } - // No exact, subset or superset match. We are on differing chains, reject. - return ErrLocalIncompatibleOrStale + return verify(i, time) } log.Error("Impossible fork ID validation", "id", id) From 91b47e3dabaa9e944a72613673f01b4b06d99d7d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sat, 17 Dec 2022 14:12:43 +0100 Subject: [PATCH 25/43] core/beacon: correctly marshal block value --- core/beacon/gen_edv2.go | 46 +++++++++++++++++++++++++++++++++++++++++ core/beacon/types.go | 7 +++++++ 2 files changed, 53 insertions(+) create mode 100644 core/beacon/gen_edv2.go diff --git a/core/beacon/gen_edv2.go b/core/beacon/gen_edv2.go new file mode 100644 index 000000000000..4b3115871397 --- /dev/null +++ b/core/beacon/gen_edv2.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 _ = (*executableDataV2Marshaling)(nil) + +// MarshalJSON marshals as JSON. +func (e ExecutableDataV2) MarshalJSON() ([]byte, error) { + type ExecutableDataV2 struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + } + var enc ExecutableDataV2 + enc.ExecutionPayload = e.ExecutionPayload + enc.BlockValue = (*hexutil.Big)(e.BlockValue) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *ExecutableDataV2) UnmarshalJSON(input []byte) error { + type ExecutableDataV2 struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + } + var dec ExecutableDataV2 + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ExecutionPayload == nil { + return errors.New("missing required field 'executionPayload' for ExecutableDataV2") + } + e.ExecutionPayload = dec.ExecutionPayload + if dec.BlockValue == nil { + return errors.New("missing required field 'blockValue' for ExecutableDataV2") + } + e.BlockValue = (*big.Int)(dec.BlockValue) + return nil +} diff --git a/core/beacon/types.go b/core/beacon/types.go index 8cf946681c35..f0a179fc20b1 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -75,11 +75,18 @@ type executableDataMarshaling struct { Transactions []hexutil.Bytes } +//go:generate go run github.com/fjl/gencodec -type ExecutableDataV2 -field-override executableDataV2Marshaling -out gen_edv2.go + type ExecutableDataV2 struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *big.Int `json:"blockValue" gencodec:"required"` } +// JSON type overrides for ExecutableDataV2. +type executableDataV2Marshaling struct { + BlockValue *hexutil.Big +} + type PayloadStatusV1 struct { Status string `json:"status"` LatestValidHash *common.Hash `json:"latestValidHash"` From 101041a19cc116c0867d44f25583f019f714ac81 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 11 Jan 2023 15:01:39 +0100 Subject: [PATCH 26/43] core: fix rebasing issues --- core/blockchain_test.go | 2 ++ core/forkid/forkid.go | 39 +++------------------- core/state_processor_test.go | 65 +++++++++++++++++++++--------------- eth/catalyst/api_test.go | 4 +-- 4 files changed, 47 insertions(+), 63 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 723f57f389c8..cd3e8e218acf 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4276,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) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 5a649bc91d22..f536019dac12 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -139,10 +139,9 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u forks = append(append([]uint64{}, forksByBlock...), forksByTime...) sums = make([][4]byte, len(forks)+1) // 0th is the genesis ) - allForks := append(forks, forksByTime...) hash := crc32.ChecksumIEEE(genesis[:]) sums[0] = checksumToBytes(hash) - for i, fork := range allForks { + for i, fork := range forks { hash = checksumUpdate(hash, fork) sums[i+1] = checksumToBytes(hash) } @@ -188,7 +187,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u } // Found the first unpassed fork block, check if our current state matches // the remote checksum (rule #1). - if sums[index] == id.Hash { + if sums[i] == id.Hash { // Fork checksum matched, check if a remote future fork block already passed // locally without the local node being aware of it (rule #1a). if id.Next > 0 && (head >= id.Next || (id.Next > timestampThreshold && time >= id.Next)) { @@ -199,10 +198,10 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u } // The local and remote nodes are in different forks currently, check if the // remote checksum is a subset of our local forks (rule #2). - for j := 0; j < index; j++ { + for j := 0; j < i; j++ { if sums[j] == id.Hash { // Remote checksum is a subset, validate based on the announced next fork - if allForks[j] != id.Next { + if forks[j] != id.Next { return ErrRemoteStale } return nil @@ -210,7 +209,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u } // Remote chain is not a subset of our local one, check if it's a superset by // any chance, signalling that we're simply out of sync (rule #3). - for j := index + 1; j < len(sums); j++ { + for j := i + 1; j < len(sums); j++ { if sums[j] == id.Hash { // Yay, remote checksum is a superset, ignore upcoming forks return nil @@ -219,28 +218,6 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u // No exact, subset or superset match. We are on differing chains, reject. return ErrLocalIncompatibleOrStale } - - head, time := headfn() - // Verify forks by block - for i, fork := range forks { - // If our head is beyond this fork, continue to the next (we have a dummy - // fork of maxuint64 as the last item to always fail this check eventually). - if head >= fork { - continue - } - return verify(i, head) - } - // Verify forks by time - for i := len(forks); i < len(forks)+len(forksByTime); i++ { - fork := forksByTime[i-len(forks)] - // If our head is beyond this fork, continue to the next (we have a dummy - // fork of maxuint64 as the last item to always fail this check eventually). - if time >= fork { - continue - } - return verify(i, time) - } - log.Error("Impossible fork ID validation", "id", id) return nil // Something's very wrong, accept rather than reject } @@ -309,12 +286,6 @@ func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) { i-- } } - for i := 1; i < len(forksByTime); i++ { - if forksByTime[i] == forksByTime[i-1] { - forksByTime = append(forksByTime[:i], forksByTime[i+1:]...) - i-- - } - } // Skip any forks in block 0, that's the genesis ruleset if len(forksByBlock) > 0 && forksByBlock[0] == 0 { forksByBlock = forksByBlock[1:] diff --git a/core/state_processor_test.go b/core/state_processor_test.go index c91adc36ed46..305948d54c0d 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/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 7cdfb2d51aff..cea807c98fdf 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -864,11 +864,11 @@ func TestInvalidBloom(t *testing.T) { func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { genesis, preMergeBlocks := generateMergeChain(100, false) - genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty()) - 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] From bac9e8961adbb8c26e65e0b116813c9bf2a46918 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 13 Jan 2023 13:36:25 +0100 Subject: [PATCH 27/43] eth/protocols/eth: revert accidental modification --- eth/protocols/eth/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 37fc7ed67fcc..0d4b368988f7 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -41,7 +41,7 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH67, ETH68, ETH66} +var ProtocolVersions = []uint{ETH68, ETH67, ETH66} // protocolLengths are the number of implemented message corresponding to // different protocol versions. From 300f5f22e33eb7c3eba2e82e1397b89d3150f2cd Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 13 Jan 2023 13:25:31 +0100 Subject: [PATCH 28/43] core, eth: use gwei for withdrawals --- cmd/evm/internal/t8ntool/execution.go | 4 +++- cmd/evm/testdata/26/exp.json | 4 ++-- consensus/beacon/consensus.go | 4 +++- core/types/gen_withdrawal_json.go | 9 ++++----- core/types/gen_withdrawal_rlp.go | 9 +-------- core/types/withdrawal.go | 5 ++--- eth/catalyst/api_test.go | 7 ++++--- eth/fetcher/block_fetcher_test.go | 6 +++--- eth/protocols/eth/handler_test.go | 2 +- 9 files changed, 23 insertions(+), 27 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 1ba334a85dae..2c68659945eb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -258,7 +258,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } // Apply withdrawals for _, w := range pre.Env.Withdrawals { - statedb.AddBalance(w.Address, w.Amount) + // 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)) diff --git a/cmd/evm/testdata/26/exp.json b/cmd/evm/testdata/26/exp.json index c5f8fee6ab2b..4815e5cb65ef 100644 --- a/cmd/evm/testdata/26/exp.json +++ b/cmd/evm/testdata/26/exp.json @@ -1,12 +1,12 @@ { "alloc": { "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x2a", + "balance": "0x9c7652400", "nonce": "0xac" } }, "result": { - "stateRoot": "0x0c34df727f6f9a5243d21ded6349df8bbe8498e23fffef65ebb7c2bbecc53057", + "stateRoot": "0x6e061c2f6513af27d267a0e3b07cb9a10f1ba3a0f65ab648d3a17c36e15021d2", "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 6ab95e0fcdc4..dcbd1e8ffee4 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -337,7 +337,9 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // If withdrawals have been activated, process each one. if chain.Config().IsShanghai(new(big.Int).SetUint64(header.Time)) { for _, w := range withdrawals { - state.AddBalance(w.Address, w.Amount) + // Amount is in gwei, turn into wei + amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) + state.AddBalance(w.Address, amount) } } // The block reward is no longer handled here. It's done by the diff --git a/core/types/gen_withdrawal_json.go b/core/types/gen_withdrawal_json.go index 6c259a94c468..983a7f7a1248 100644 --- a/core/types/gen_withdrawal_json.go +++ b/core/types/gen_withdrawal_json.go @@ -4,7 +4,6 @@ package types import ( "encoding/json" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -18,13 +17,13 @@ func (w Withdrawal) MarshalJSON() ([]byte, error) { Index hexutil.Uint64 `json:"index"` Validator hexutil.Uint64 `json:"validatorIndex"` Address common.Address `json:"address"` - Amount *hexutil.Big `json:"amount"` + 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.Big)(w.Amount) + enc.Amount = hexutil.Uint64(w.Amount) return json.Marshal(&enc) } @@ -34,7 +33,7 @@ func (w *Withdrawal) UnmarshalJSON(input []byte) error { Index *hexutil.Uint64 `json:"index"` Validator *hexutil.Uint64 `json:"validatorIndex"` Address *common.Address `json:"address"` - Amount *hexutil.Big `json:"amount"` + Amount *hexutil.Uint64 `json:"amount"` } var dec Withdrawal if err := json.Unmarshal(input, &dec); err != nil { @@ -50,7 +49,7 @@ func (w *Withdrawal) UnmarshalJSON(input []byte) error { w.Address = *dec.Address } if dec.Amount != nil { - w.Amount = (*big.Int)(dec.Amount) + w.Amount = uint64(*dec.Amount) } return nil } diff --git a/core/types/gen_withdrawal_rlp.go b/core/types/gen_withdrawal_rlp.go index a16c828afb60..d0b4e0147a0d 100644 --- a/core/types/gen_withdrawal_rlp.go +++ b/core/types/gen_withdrawal_rlp.go @@ -14,14 +14,7 @@ func (obj *Withdrawal) EncodeRLP(_w io.Writer) error { w.WriteUint64(obj.Index) w.WriteUint64(obj.Validator) w.WriteBytes(obj.Address[:]) - if obj.Amount == nil { - w.Write(rlp.EmptyString) - } else { - if obj.Amount.Sign() == -1 { - return rlp.ErrNegativeBigInt - } - w.WriteBigInt(obj.Amount) - } + w.WriteUint64(obj.Amount) w.ListEnd(_tmp0) return w.Flush() } diff --git a/core/types/withdrawal.go b/core/types/withdrawal.go index 5a93c97fe54f..d1ad918f985c 100644 --- a/core/types/withdrawal.go +++ b/core/types/withdrawal.go @@ -18,7 +18,6 @@ package types import ( "bytes" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -33,14 +32,14 @@ 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 *big.Int `json:"amount"` // value of withdrawal in wei + 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.Big + Amount hexutil.Uint64 } // Withdrawals implements DerivableList for withdrawals. diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index cea807c98fdf..77b5ab8d20d8 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1059,12 +1059,12 @@ func TestWithdrawals(t *testing.T) { { Index: 0, Address: aa, - Amount: big.NewInt(32), + Amount: 32, }, { Index: 1, Address: bb, - Amount: big.NewInt(33), + Amount: 33, }, }, } @@ -1104,7 +1104,8 @@ func TestWithdrawals(t *testing.T) { t.Fatalf("unable to load db: %v", err) } for i, w := range params.Withdrawals { - if db.GetBalance(w.Address).Uint64() != w.Amount.Uint64() { + // w.Amount is in gwei, balance in wei + if db.GetBalance(w.Address).Uint64() != w.Amount*1e9 { t.Fatalf("failed to process withdrawal %d", i) } } diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 3b99af585e58..74065b70690f 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -101,9 +101,9 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common } // If the block number is a multiple of 7, add a withdrawal if i > 0 && i%7 == 0 { - block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: big.NewInt(42)}) - block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: big.NewInt(43)}) - block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: big.NewInt(44)}) + block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: 42}) + block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: 43}) + block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: 44}) } }) hashes := make([]common.Hash, n+1) diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index b1145a3bd433..d7b1b42200c5 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -343,7 +343,7 @@ func testGetBlockBodies(t *testing.T, protocol uint) { if n%2 == 0 { w := &types.Withdrawal{ Address: common.Address{0xaa}, - Amount: big.NewInt(42), + Amount: 42, } g.AddWithdrawal(w) } From f959d389c1f748225dccaa527c07b3c0f06fede7 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Fri, 13 Jan 2023 07:34:41 -0700 Subject: [PATCH 29/43] eth/catalyst: use gwei in test instead of hard coded value --- eth/catalyst/api_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 77b5ab8d20d8..7335e1226057 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1013,14 +1013,14 @@ func TestWithdrawals(t *testing.T) { // 10: Build Shanghai block with no withdrawals. parent := ethservice.BlockChain().CurrentHeader() - params := beacon.PayloadAttributes{ + blockParams := beacon.PayloadAttributes{ Timestamp: parent.Time + 5, Withdrawals: make([]*types.Withdrawal, 0), } fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: parent.Hash(), } - resp, err := api.ForkchoiceUpdatedV2(fcState, ¶ms) + resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams) if err != nil { t.Fatalf("error preparing payload, err=%v", err) } @@ -1031,9 +1031,9 @@ func TestWithdrawals(t *testing.T) { // 10: verify state root is the same as parent payloadID := (&miner.BuildPayloadArgs{ Parent: fcState.HeadBlockHash, - Timestamp: params.Timestamp, - FeeRecipient: params.SuggestedFeeRecipient, - Random: params.Random, + Timestamp: blockParams.Timestamp, + FeeRecipient: blockParams.SuggestedFeeRecipient, + Random: blockParams.Random, }).Id() execData, err := api.GetPayloadV2(payloadID) if err != nil { @@ -1053,7 +1053,7 @@ func TestWithdrawals(t *testing.T) { // 11: build shanghai block with withdrawal aa := common.Address{0xaa} bb := common.Address{0xbb} - params = beacon.PayloadAttributes{ + blockParams = beacon.PayloadAttributes{ Timestamp: execData.ExecutionPayload.Timestamp + 5, Withdrawals: []*types.Withdrawal{ { @@ -1069,7 +1069,7 @@ func TestWithdrawals(t *testing.T) { }, } fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash - _, err = api.ForkchoiceUpdatedV2(fcState, ¶ms) + _, err = api.ForkchoiceUpdatedV2(fcState, &blockParams) if err != nil { t.Fatalf("error preparing payload, err=%v", err) } @@ -1077,9 +1077,9 @@ func TestWithdrawals(t *testing.T) { // 11: verify locally build block. payloadID = (&miner.BuildPayloadArgs{ Parent: fcState.HeadBlockHash, - Timestamp: params.Timestamp, - FeeRecipient: params.SuggestedFeeRecipient, - Random: params.Random, + Timestamp: blockParams.Timestamp, + FeeRecipient: blockParams.SuggestedFeeRecipient, + Random: blockParams.Random, }).Id() execData, err = api.GetPayloadV2(payloadID) if err != nil { @@ -1103,9 +1103,9 @@ func TestWithdrawals(t *testing.T) { if err != nil { t.Fatalf("unable to load db: %v", err) } - for i, w := range params.Withdrawals { + for i, w := range blockParams.Withdrawals { // w.Amount is in gwei, balance in wei - if db.GetBalance(w.Address).Uint64() != w.Amount*1e9 { + if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei { t.Fatalf("failed to process withdrawal %d", i) } } From cf0ed02d25735e073e9c6c120b47b8b3f0b618cc Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 18 Jan 2023 11:27:53 +0100 Subject: [PATCH 30/43] core: fix nil panic --- core/block_validator.go | 4 ++++ core/chain_makers.go | 6 ++++++ eth/catalyst/api.go | 6 +++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/block_validator.go b/core/block_validator.go index 1effba9fc5a8..4a8c1861d486 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "math/big" @@ -67,6 +68,9 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash) } if v.config.IsShanghai(new(big.Int).SetUint64(block.Time())) { + if block.WithdrawalsHash() == nil { + return errors.New("nil withdrawal hash post-shanghai") + } 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) } diff --git a/core/chain_makers.go b/core/chain_makers.go index d37a20125420..9e0c76428343 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -215,6 +215,12 @@ func (b *BlockGen) AddWithdrawal(w *types.Withdrawal) { 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) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 18c38cd9d3e7..161baedfb06f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -348,7 +348,7 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.Transit // GetPayloadV1 returns a cached payload by id. func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableData, error) { - data, err := api.GetPayloadV2(payloadID) + data, err := api.getPayload(payloadID) if err != nil { return nil, err } @@ -357,6 +357,10 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu // GetPayloadV2 returns a cached payload by id. func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutableDataV2, error) { + return api.getPayload(payloadID) +} + +func (api *ConsensusAPI) getPayload(payloadID beacon.PayloadID) (*beacon.ExecutableDataV2, error) { log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) data := api.localBlocks.get(payloadID) if data == nil { From 8c74d6081b45f21f77e95981b630409d33d1205b Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 19 Jan 2023 12:50:02 +0100 Subject: [PATCH 31/43] all: fix review comments, add beacon.Faker, fix tests --- cmd/evm/internal/t8ntool/block.go | 2 +- consensus/beacon/consensus.go | 2 +- consensus/ethash/consensus.go | 2 +- core/beacon/gen_edv2.go | 46 ------------------------------- core/beacon/types.go | 14 +++++----- core/block_validator.go | 7 +---- core/blockchain_test.go | 2 +- core/chain_makers.go | 7 +---- core/rawdb/accessors_chain.go | 6 ++-- core/state_processor.go | 2 +- core/types/block.go | 28 ++++++++----------- eth/catalyst/api.go | 4 +-- eth/catalyst/api_test.go | 6 ++-- eth/catalyst/queue.go | 2 +- eth/downloader/downloader.go | 6 ++-- eth/downloader/queue.go | 5 +++- eth/fetcher/block_fetcher.go | 24 ++++++++-------- eth/protocols/eth/handler_test.go | 2 +- miner/payload_building.go | 6 ++-- miner/payload_building_test.go | 2 +- miner/worker.go | 1 + params/config.go | 6 ---- 22 files changed, 59 insertions(+), 123 deletions(-) delete mode 100644 core/beacon/gen_edv2.go diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index 18e7abcc00e1..1140daa2c27f 100644 --- a/cmd/evm/internal/t8ntool/block.go +++ b/cmd/evm/internal/t8ntool/block.go @@ -156,7 +156,7 @@ func (i *bbInput) ToBlock() *types.Block { if header.Difficulty != nil { header.Difficulty = i.Header.Difficulty } - return types.NewBlockWithHeader(header).WithBody2(i.Txs, i.Ommers, i.Withdrawals) + return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers).WithWithdrawals(i.Withdrawals) } // SealBlock seals the given block using the configured engine. diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index dcbd1e8ffee4..67a6057067c4 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -357,7 +357,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea } // Finalize and assemble the block. beacon.Finalize(chain, header, state, txs, uncles, withdrawals) - return types.NewBlock2(header, txs, uncles, receipts, withdrawals, trie.NewStackTrie(nil)), nil + 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/ethash/consensus.go b/consensus/ethash/consensus.go index 29cd4a404e4b..d782adce330e 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -639,7 +639,7 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { enc = append(enc, header.BaseFee) } if header.WithdrawalsHash != nil { - enc = append(enc, header.WithdrawalsHash) + panic("withdrawal hash set on ethash") } rlp.Encode(hasher, enc) hasher.Sum(hash[:0]) diff --git a/core/beacon/gen_edv2.go b/core/beacon/gen_edv2.go deleted file mode 100644 index 4b3115871397..000000000000 --- a/core/beacon/gen_edv2.go +++ /dev/null @@ -1,46 +0,0 @@ -// 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 _ = (*executableDataV2Marshaling)(nil) - -// MarshalJSON marshals as JSON. -func (e ExecutableDataV2) MarshalJSON() ([]byte, error) { - type ExecutableDataV2 struct { - ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - } - var enc ExecutableDataV2 - enc.ExecutionPayload = e.ExecutionPayload - enc.BlockValue = (*hexutil.Big)(e.BlockValue) - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (e *ExecutableDataV2) UnmarshalJSON(input []byte) error { - type ExecutableDataV2 struct { - ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - } - var dec ExecutableDataV2 - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.ExecutionPayload == nil { - return errors.New("missing required field 'executionPayload' for ExecutableDataV2") - } - e.ExecutionPayload = dec.ExecutionPayload - if dec.BlockValue == nil { - return errors.New("missing required field 'blockValue' for ExecutableDataV2") - } - e.BlockValue = (*big.Int)(dec.BlockValue) - return nil -} diff --git a/core/beacon/types.go b/core/beacon/types.go index f0a179fc20b1..656115c78542 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -75,15 +75,15 @@ type executableDataMarshaling struct { Transactions []hexutil.Bytes } -//go:generate go run github.com/fjl/gencodec -type ExecutableDataV2 -field-override executableDataV2Marshaling -out gen_edv2.go +//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go -type ExecutableDataV2 struct { +type ExecutionPayloadEnvelope struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *big.Int `json:"blockValue" gencodec:"required"` } -// JSON type overrides for ExecutableDataV2. -type executableDataV2Marshaling struct { +// JSON type overrides for ExecutionPayloadEnvelope. +type executionPayloadEnvelopeMarshaling struct { BlockValue *hexutil.Big } @@ -200,7 +200,7 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { MixDigest: params.Random, WithdrawalsHash: withdrawalsRoot, } - block := types.NewBlockWithHeader(header).WithBody2(txs, nil /* uncles */, params.Withdrawals) + 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()) } @@ -209,7 +209,7 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { // 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, fees *big.Int) *ExecutableDataV2 { +func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadEnvelope { data := &ExecutableData{ BlockHash: block.Hash(), ParentHash: block.ParentHash(), @@ -227,5 +227,5 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutableDataV2 ExtraData: block.Extra(), Withdrawals: block.Withdrawals(), } - return &ExecutableDataV2{ExecutionPayload: data, BlockValue: fees} + return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees} } diff --git a/core/block_validator.go b/core/block_validator.go index 4a8c1861d486..28be7db9dcfc 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -17,9 +17,7 @@ package core import ( - "errors" "fmt" - "math/big" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" @@ -67,10 +65,7 @@ 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 v.config.IsShanghai(new(big.Int).SetUint64(block.Time())) { - if block.WithdrawalsHash() == nil { - return errors.New("nil withdrawal hash post-shanghai") - } + if block.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) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index cd3e8e218acf..5554361dbf0c 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 = beacon.New(ethash.NewFaker()) + engine = beacon.NewFaker() // A sender who makes transactions, has some funds key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") diff --git a/core/chain_makers.go b/core/chain_makers.go index 9e0c76428343..b37e1ed4fd66 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -431,9 +431,4 @@ func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil } func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil } func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } -func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { - if cr.config.TerminalTotalDifficultyPassed { - return cr.config.TerminalTotalDifficulty - } - return nil -} +func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 6487716e6255..4df580e8c4ab 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).WithBody2(body.Transactions, body.Uncles, body.Withdrawals) + 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).WithBody2(bad.Body.Transactions, bad.Body.Uncles, bad.Body.Withdrawals) + 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).WithBody2(bad.Body.Transactions, bad.Body.Uncles, bad.Body.Withdrawals)) + 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 c9473b2d77e5..ae070247ab17 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -87,7 +87,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg allLogs = append(allLogs, receipt.Logs...) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. - if !p.config.IsShanghai(new(big.Int).SetUint64(block.Time())) && len(block.Withdrawals()) != 0 { + if !p.config.IsShanghai(new(big.Int).SetUint64(block.Time())) && block.Withdrawals() != nil { return nil, nil, 0, fmt.Errorf("non-nil withdrawal before shanghai") } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) diff --git a/core/types/block.go b/core/types/block.go index 0de37ed8a6fb..f17f9f88562a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -178,7 +178,7 @@ type Block struct { header *Header uncles []*Header transactions Transactions - withdrawals []*Withdrawal + withdrawals Withdrawals // caches hash atomic.Value @@ -237,29 +237,26 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* return b } -// NewBlock2 creates a new block with withdrawals. The input data +// 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 NewBlock2(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block { +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 { - // leave withdrawal hash empty + b.header.WithdrawalsHash = nil } else if len(withdrawals) == 0 { b.header.WithdrawalsHash = &EmptyRootHash - b.withdrawals = make(Withdrawals, len(withdrawals)) } else { h := DeriveSha(Withdrawals(withdrawals), hasher) b.header.WithdrawalsHash = &h - b.withdrawals = make(Withdrawals, len(withdrawals)) - copy(b.withdrawals, withdrawals) } - return b + return b.WithWithdrawals(withdrawals) } // NewBlockWithHeader creates a block with the given header data. The @@ -287,8 +284,7 @@ func CopyHeader(h *Header) *Header { copy(cpy.Extra, h.Extra) } if h.WithdrawalsHash != nil { - cpy.WithdrawalsHash = new(common.Hash) - cpy.WithdrawalsHash.SetBytes(h.WithdrawalsHash.Bytes()) + *cpy.WithdrawalsHash = *h.WithdrawalsHash } return &cpy } @@ -431,15 +427,13 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { return block } -// WithBody2 returns a new block with the given transaction, uncle, and -// withdrawal contents. -func (b *Block) WithBody2(transactions []*Transaction, uncles []*Header, withdrawals []*Withdrawal) *Block { - block := b.WithBody(transactions, uncles) +// WithWithdrawals sets the withdrawal contents of a block, does not return a new block. +func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { if withdrawals != nil { - block.withdrawals = make([]*Withdrawal, len(withdrawals)) - copy(block.withdrawals, withdrawals) + b.withdrawals = make([]*Withdrawal, len(withdrawals)) + copy(b.withdrawals, withdrawals) } - return block + return b } // Hash returns the keccak256 hash of b's header. diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 161baedfb06f..39dcba04f83e 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -356,11 +356,11 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu } // GetPayloadV2 returns a cached payload by id. -func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutableDataV2, error) { +func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutionPayloadEnvelope, error) { return api.getPayload(payloadID) } -func (api *ConsensusAPI) getPayload(payloadID beacon.PayloadID) (*beacon.ExecutableDataV2, error) { +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 { diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 7335e1226057..54d294daa9fb 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -27,6 +27,7 @@ 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" @@ -56,9 +57,11 @@ var ( 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, @@ -76,7 +79,7 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { g.AddTx(tx) testNonce++ } - _, blocks, _ := core.GenerateChainWithGenesis(genesis, beaconConsensus.New(ethash.NewFaker()), n, generate) + _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate) if !merged { totalDifficulty := big.NewInt(0) @@ -1003,7 +1006,6 @@ func TestWithdrawals(t *testing.T) { genesis, blocks := generateMergeChain(10, true) // Set shanghai time to last block + 5 seconds (first post-merge block) genesis.Config.ShanghaiTime = big.NewInt(int64(blocks[len(blocks)-1].Time()) + 5) - genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[0].Difficulty()) n, ethservice := startEthService(t, genesis, blocks) ethservice.Merger().ReachTTD() diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go index a34880e493d6..5c60f70e44b3 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.ExecutableDataV2 { +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 2aae3394fa4d..f7790b2d80f0 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).WithBody2(result.Transactions, result.Uncles, result.Withdrawals) + 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).WithBody2(result.Transactions, result.Uncles, result.Withdrawals) + 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).WithBody2(result.Transactions, result.Uncles, result.Withdrawals) + 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/queue.go b/eth/downloader/queue.go index 61c61352d564..c71b36466d41 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -778,7 +778,10 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if uncleListHashes[index] != header.UncleHash { return errInvalidBody } - if header.WithdrawalsHash != nil && (withdrawalListHashes[index] != *header.WithdrawalsHash) { + 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 diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index dbdad62d2fb3..156d07e9131f 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -125,7 +125,6 @@ type bodyFilterTask struct { peer string // The source peer of block bodies transactions [][]*types.Transaction // Collection of transactions per block bodies uncles [][]*types.Header // Collection of uncles per block bodies - withdrawals [][]*types.Withdrawal // Collection of withdrawals per block bodies time time.Time // Arrival time of the blocks' contents } @@ -303,8 +302,8 @@ func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time // FilterBodies extracts all the block bodies that were explicitly requested by // the fetcher, returning those that should be handled differently. -func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, withdrawals [][]*types.Withdrawal, time time.Time) ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) { - log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles), "withdrawals", len(withdrawals)) +func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) { + log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles)) // Send the filter channel to the fetcher filter := make(chan *bodyFilterTask) @@ -312,20 +311,20 @@ func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transac select { case f.bodyFilter <- filter: case <-f.quit: - return nil, nil, nil + return nil, nil } // Request the filtering of the body list select { - case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, withdrawals: withdrawals, time: time}: + case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, time: time}: case <-f.quit: - return nil, nil, nil + return nil, nil } // Retrieve the bodies remaining after filtering select { case task := <-filter: - return task.transactions, task.uncles, task.withdrawals + return task.transactions, task.uncles case <-f.quit: - return nil, nil, nil + return nil, nil } } @@ -541,9 +540,9 @@ func (f *BlockFetcher) loop() { select { case res := <-resCh: res.Done <- nil - - txs, uncles, wxs := res.Res.(*eth.BlockBodiesPacket).Unpack() - f.FilterBodies(peer, txs, uncles, wxs, time.Now()) + // 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: // The peer didn't respond in time. The request @@ -687,7 +686,7 @@ func (f *BlockFetcher) loop() { // Mark the body matched, reassemble if still unknown matched = true if f.getBlock(hash) == nil { - block := types.NewBlockWithHeader(announce.header).WithBody2(task.transactions[i], task.uncles[i], task.withdrawals[i]) + block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i]) block.ReceivedAt = task.time blocks = append(blocks, block) } else { @@ -697,7 +696,6 @@ func (f *BlockFetcher) loop() { if matched { task.transactions = append(task.transactions[:i], task.transactions[i+1:]...) task.uncles = append(task.uncles[:i], task.uncles[i+1:]...) - task.withdrawals = append(task.withdrawals[:i], task.withdrawals[i+1:]...) i-- continue } diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index d7b1b42200c5..69a14aea0628 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -95,7 +95,7 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, TerminalTotalDifficultyPassed: true, Ethash: new(params.EthashConfig), } - engine = beacon.New(ethash.NewFaker()) + engine = beacon.NewFaker() } gspec := &core.Genesis{ diff --git a/miner/payload_building.go b/miner/payload_building.go index 846c2899c31c..75bca67cbb32 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -108,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.ExecutableDataV2 { +func (payload *Payload) Resolve() *beacon.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() @@ -125,7 +125,7 @@ func (payload *Payload) Resolve() *beacon.ExecutableDataV2 { // ResolveEmpty is basically identical to Resolve, but it expects empty block only. // It's only used in tests. -func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV2 { +func (payload *Payload) ResolveEmpty() *beacon.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() @@ -134,7 +134,7 @@ func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV2 { // ResolveFull is basically identical to Resolve, but it expects full block only. // It's only used in tests. -func (payload *Payload) ResolveFull() *beacon.ExecutableDataV2 { +func (payload *Payload) ResolveFull() *beacon.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index ec4e9fb15134..8d6ffaff13a2 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -47,7 +47,7 @@ func TestBuildPayload(t *testing.T) { if err != nil { t.Fatalf("Failed to build payload %v", err) } - verify := func(outer *beacon.ExecutableDataV2, txs int) { + verify := func(outer *beacon.ExecutionPayloadEnvelope, txs int) { payload := outer.ExecutionPayload if payload.ParentHash != b.chain.CurrentBlock().Hash() { t.Fatal("Unexpect parent hash") diff --git a/miner/worker.go b/miner/worker.go index 37a31e1003ba..49204f71a076 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1194,6 +1194,7 @@ 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() + // 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 diff --git a/params/config.go b/params/config.go index 3f36b356730b..226bbc250a03 100644 --- a/params/config.go +++ b/params/config.go @@ -571,12 +571,6 @@ func (c *ChainConfig) Description() string { if c.GrayGlacierBlock != nil { banner += fmt.Sprintf(" - Gray Glacier: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock) } - if c.ShanghaiTime != nil { - banner += fmt.Sprintf(" - Shanghai: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md)\n", c.ShanghaiTime) - } - if c.CancunBlock != nil { - banner += fmt.Sprintf(" - Cancun: %-8v\n", c.CancunBlock) - } banner += "\n" // Add a special section for the merge as it's non-obvious From 3263936a292e860796f480526fe70bc763fa0764 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 19 Jan 2023 12:56:00 +0100 Subject: [PATCH 32/43] consensus/beacon: add faker --- consensus/beacon/faker.go | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 consensus/beacon/faker.go diff --git a/consensus/beacon/faker.go b/consensus/beacon/faker.go new file mode 100644 index 000000000000..409554a1b921 --- /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 simulateds 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 +} From 1931e38b301b280c5ef6398f3c204b3638a8287f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 19 Jan 2023 15:55:12 +0100 Subject: [PATCH 33/43] eth/catalyst: go generate, fix nitpicks --- core/beacon/gen_epe.go | 46 ++++++++++++++++++++++ eth/fetcher/block_fetcher_test.go | 63 ++++++------------------------- params/config.go | 2 +- 3 files changed, 58 insertions(+), 53 deletions(-) create mode 100644 core/beacon/gen_epe.go diff --git a/core/beacon/gen_epe.go b/core/beacon/gen_epe.go new file mode 100644 index 000000000000..0b4d8598f7a1 --- /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/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 74065b70690f..9e5693c02e5a 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -25,7 +25,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "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" @@ -37,53 +36,24 @@ import ( ) var ( - testdb = rawdb.NewMemoryDatabase() - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddress = crypto.PubkeyToAddress(testKey.PublicKey) - unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) - gspec *core.Genesis - genesis *types.Block -) - -func init() { - 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: big.NewInt(0), - TerminalTotalDifficulty: big.NewInt(0), - TerminalTotalDifficultyPassed: true, - Ethash: new(params.EthashConfig), - } - - gspec = &core.Genesis{ - Config: config, + testdb = rawdb.NewMemoryDatabase() + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddress = crypto.PubkeyToAddress(testKey.PublicKey) + gspec = &core.Genesis{ + Config: params.TestChainConfig, Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, BaseFee: big.NewInt(params.InitialBaseFee), } - genesis = gspec.MustCommit(testdb) -} + genesis = gspec.MustCommit(testdb) + unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) +) // makeChain creates a chain of n blocks starting at and including parent. // the returned hash chain is ordered head->parent. In addition, every 3rd block // contains a transaction and every 5th an uncle to allow testing correct block // reassembly. func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { - blocks, _ := core.GenerateChain(gspec.Config, parent, beacon.New(ethash.NewFaker()), testdb, n, func(i int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(gspec.Config, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) { block.SetCoinbase(common.Address{seed}) // If the block number is multiple of 3, send a bonus transaction to the miner @@ -99,12 +69,6 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common if i > 0 && i%5 == 0 { block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))}) } - // If the block number is a multiple of 7, add a withdrawal - if i > 0 && i%7 == 0 { - block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: 42}) - block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: 43}) - block.AddWithdrawal(&types.Withdrawal{Address: testAddress, Amount: 44}) - } }) hashes := make([]common.Hash, n+1) hashes[len(hashes)-1] = parent.Hash() @@ -269,17 +233,13 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ // Create a function that returns blocks from the closure return func(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) { // Gather the block bodies to return - var ( - transactions = make([][]*types.Transaction, 0, len(hashes)) - uncles = make([][]*types.Header, 0, len(hashes)) - withdrawals = make([][]*types.Withdrawal, 0, len(hashes)) - ) + transactions := make([][]*types.Transaction, 0, len(hashes)) + uncles := make([][]*types.Header, 0, len(hashes)) for _, hash := range hashes { if block, ok := closure[hash]; ok { transactions = append(transactions, block.Transactions()) uncles = append(uncles, block.Uncles()) - withdrawals = append(withdrawals, block.Withdrawals()) } } // Return on a new thread @@ -288,7 +248,6 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ bodies[i] = ð.BlockBody{ Transactions: txs, Uncles: uncles[i], - Withdrawals: withdrawals[i], } } req := ð.Request{ diff --git a/params/config.go b/params/config.go index 226bbc250a03..aac4392301de 100644 --- a/params/config.go +++ b/params/config.go @@ -569,7 +569,7 @@ func (c *ChainConfig) Description() string { banner += fmt.Sprintf(" - Arrow Glacier: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/arrow-glacier.md)\n", c.ArrowGlacierBlock) } if c.GrayGlacierBlock != nil { - banner += fmt.Sprintf(" - Gray Glacier: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock) + banner += fmt.Sprintf(" - Gray Glacier: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock) } banner += "\n" From 493741d1c3162a78d8d11c9861841ead0875f7ed Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 25 Jan 2023 05:16:40 -0500 Subject: [PATCH 34/43] Update consensus/beacon/consensus.go --- consensus/beacon/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 67a6057067c4..4bf5552bbed4 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -266,7 +266,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return fmt.Errorf("missing withdrawalsHash") } if !shanghai && header.WithdrawalsHash != nil { - return fmt.Errorf("invalid withdrawalsHash: have %s, expected nil", header.WithdrawalsHash) + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } return nil } From dd3c41a6cce7347e4defe74ad17429f41f28e543 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 25 Jan 2023 05:23:23 -0500 Subject: [PATCH 35/43] Apply suggestions from code review --- consensus/beacon/faker.go | 2 +- core/block_validator.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/beacon/faker.go b/consensus/beacon/faker.go index 409554a1b921..981e345e3e1d 100644 --- a/consensus/beacon/faker.go +++ b/consensus/beacon/faker.go @@ -24,7 +24,7 @@ import ( ) // NewFaker creates a fake consensus engine for testing. -// The fake engine simulateds a merged network. +// 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. diff --git a/core/block_validator.go b/core/block_validator.go index 28be7db9dcfc..813ac0a4384d 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -65,7 +65,7 @@ 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 block.WithdrawalsHash() != nil { + 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) } From 7b073d11695ff3922c5280808e6d4be728a80820 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 25 Jan 2023 05:55:10 -0500 Subject: [PATCH 36/43] Update internal/ethapi/api.go --- internal/ethapi/api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 59ad995011d9..1ac9c5faa74a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1247,7 +1247,6 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param } fields["transactions"] = transactions // inclTx also expands withdrawals - // TODO @MariusVanDerWijden: add a second flag similar to inclTx to enable withdrawals fields["withdrawals"] = block.Withdrawals() } uncles := block.Uncles() From 3481ebc9a05a0c0bcaed87aec28436f0b814f2ca Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 25 Jan 2023 05:55:20 -0500 Subject: [PATCH 37/43] Update core/types/block.go --- core/types/block.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index f17f9f88562a..ac8c031acf18 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -350,15 +350,6 @@ func (b *Block) BaseFee() *big.Int { return new(big.Int).Set(b.header.BaseFee) } -func (b *Block) WithdrawalsHash() *common.Hash { - if b.header.WithdrawalsHash == nil { - return nil - } - var h common.Hash - h.SetBytes(b.header.WithdrawalsHash.Bytes()) - return &h -} - func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } From b43da3710e9165b2921e91de19f1ba323b64c7ac Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 25 Jan 2023 13:28:11 +0100 Subject: [PATCH 38/43] all: rebase on uint64 timestamps --- cmd/evm/internal/t8ntool/transition.go | 2 +- consensus/beacon/consensus.go | 4 ++-- consensus/clique/clique.go | 2 +- consensus/ethash/consensus.go | 2 +- core/chain_makers.go | 2 +- core/genesis.go | 2 +- core/state_processor.go | 2 +- eth/catalyst/api_test.go | 3 ++- eth/protocols/eth/handler_test.go | 4 +++- 9 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 97217e4cfa2e..cb7466d86cae 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -262,7 +262,7 @@ func Transition(ctx *cli.Context) error { return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) } } - if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number))) && prestate.Env.Withdrawals == nil { + 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 diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 4bf5552bbed4..4da676ceff7e 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -261,7 +261,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return err } // Verify existence / non-existence of withdrawalsHash. - shanghai := chain.Config().IsShanghai(new(big.Int).SetUint64(header.Time)) + shanghai := chain.Config().IsShanghai(header.Time) if shanghai && header.WithdrawalsHash == nil { return fmt.Errorf("missing withdrawalsHash") } @@ -335,7 +335,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. return } // If withdrawals have been activated, process each one. - if chain.Config().IsShanghai(new(big.Int).SetUint64(header.Time)) { + if chain.Config().IsShanghai(header.Time) { for _, w := range withdrawals { // Amount is in gwei, turn into wei amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 3d369d2aeeaf..edcc25d13b3b 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -298,7 +298,7 @@ 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(new(big.Int).SetUint64(header.Time)) { + 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 diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index d782adce330e..649d483ffaef 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -310,7 +310,7 @@ 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(new(big.Int).SetUint64(header.Time)) { + if chain.Config().IsShanghai(header.Time) { return fmt.Errorf("ethash does not support shanghai fork") } // Verify the engine specific seal securing the block diff --git a/core/chain_makers.go b/core/chain_makers.go index b37e1ed4fd66..dea79031257c 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -303,7 +303,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } if b.engine != nil { // Finalize and seal the block - shanghai := config.IsShanghai(new(big.Int).SetUint64(b.header.Time)) + shanghai := config.IsShanghai(b.header.Time) if shanghai && b.withdrawals == nil { // need to make empty list to denote non-nil, but empty withdrawals to calc withdrawals hash b.withdrawals = make([]*types.Withdrawal, 0) diff --git a/core/genesis.go b/core/genesis.go index 142f478a4aef..62096541f984 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -469,7 +469,7 @@ func (g *Genesis) ToBlock() *types.Block { head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee) } } - if g.Config != nil && g.Config.IsShanghai(big.NewInt(int64(g.Timestamp))) { + 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/state_processor.go b/core/state_processor.go index ae070247ab17..1145fd2f2ef6 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -87,7 +87,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg allLogs = append(allLogs, receipt.Logs...) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. - if !p.config.IsShanghai(new(big.Int).SetUint64(block.Time())) && block.Withdrawals() != nil { + if !p.config.IsShanghai(block.Time()) && block.Withdrawals() != nil { return nil, nil, 0, fmt.Errorf("non-nil withdrawal before shanghai") } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 54d294daa9fb..8df48bd08e10 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1005,7 +1005,8 @@ func TestSimultaneousNewBlock(t *testing.T) { func TestWithdrawals(t *testing.T) { genesis, blocks := generateMergeChain(10, true) // Set shanghai time to last block + 5 seconds (first post-merge block) - genesis.Config.ShanghaiTime = big.NewInt(int64(blocks[len(blocks)-1].Time()) + 5) + time := blocks[len(blocks)-1].Time() + 5 + genesis.Config.ShanghaiTime = &time n, ethservice := startEthService(t, genesis, blocks) ethservice.Merger().ReachTTD() diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 69a14aea0628..201dc98b6aec 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -47,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. @@ -90,7 +92,7 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, ArrowGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0), MergeNetsplitBlock: big.NewInt(0), - ShanghaiTime: big.NewInt(0), + ShanghaiTime: u64(0), TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, Ethash: new(params.EthashConfig), From f66511142530090d94cdaf993053287c39e2eab2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 25 Jan 2023 14:45:45 +0100 Subject: [PATCH 39/43] consensus/beacon: add check for Shanghai activation in FinalizeAndAssemble Since this method returns an error, we can perform this check in the engine instead of checking for Shanghai in callers of FinalizeAndAssemble. --- consensus/beacon/consensus.go | 4 ++++ consensus/clique/clique.go | 4 ++++ consensus/ethash/consensus.go | 5 ++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 4da676ceff7e..b90ca9a457cf 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -355,6 +355,10 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea if !beacon.IsPoSHeader(header) { return beacon.ethone.FinalizeAndAssemble(chain, header, state, txs, uncles, receipts, nil) } + shanghai := chain.Config().IsShanghai(header.Time) + if !shanghai && 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 diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index edcc25d13b3b..4706bbac1ca9 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -576,6 +576,10 @@ 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, 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, nil) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 649d483ffaef..da29e16597b6 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -609,9 +609,12 @@ 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, 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 } From 31a979c6d77c5d01c6310614ec7338a055fa4095 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 25 Jan 2023 14:49:09 +0100 Subject: [PATCH 40/43] core: remove Shanghai fork check in GenerateChain This check is now performed by FinalizeAndAssemble. --- core/chain_makers.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/core/chain_makers.go b/core/chain_makers.go index dea79031257c..de63f234ac14 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -302,15 +302,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse gen(i, b) } if b.engine != nil { - // Finalize and seal the block - shanghai := config.IsShanghai(b.header.Time) - if shanghai && b.withdrawals == nil { - // need to make empty list to denote non-nil, but empty withdrawals to calc withdrawals hash - b.withdrawals = make([]*types.Withdrawal, 0) - } else if !shanghai && b.withdrawals != nil { - panic("withdrawals set before activation") + block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) + if err != nil { + panic(err) } - block, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) // Write state changes to db root, err := statedb.Commit(config.IsEIP158(b.header.Number)) From 8bdbf395cfdbb62c8d2447532126db3d88b61f41 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 25 Jan 2023 14:49:57 +0100 Subject: [PATCH 41/43] consensus/beacon: remove Shanghai fork check in Finalize It is safe to apply withdrawals without checking for the fork because the existence of withdrawals is a part of block validation. --- consensus/beacon/consensus.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index b90ca9a457cf..e55516a36d0b 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -334,13 +334,12 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. beacon.ethone.Finalize(chain, header, state, txs, uncles, nil) return } - // If withdrawals have been activated, process each one. - if chain.Config().IsShanghai(header.Time) { - for _, w := range withdrawals { - // Amount is in gwei, turn into wei - amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) - state.AddBalance(w.Address, amount) - } + // 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. From 8783b19c61be4190008f54895ed99360ee952717 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 25 Jan 2023 14:52:30 +0100 Subject: [PATCH 42/43] core: simplify withdrawals existence check in StateProcessor --- core/state_processor.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 1145fd2f2ef6..163ea0a0200a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -87,11 +87,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg allLogs = append(allLogs, receipt.Logs...) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. - if !p.config.IsShanghai(block.Time()) && block.Withdrawals() != nil { - return nil, nil, 0, fmt.Errorf("non-nil withdrawal before shanghai") + 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(), block.Withdrawals()) + p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) return receipts, allLogs, *usedGas, nil } From 428b0e653819f88c49fca3eaea48ac7cbb51f4a6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 25 Jan 2023 14:59:11 +0100 Subject: [PATCH 43/43] consensus/beacon: ensure correct empty withdrawals root in Shanghai blocks --- consensus/beacon/consensus.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index e55516a36d0b..eb5aa58ca887 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -355,8 +355,15 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea return beacon.ethone.FinalizeAndAssemble(chain, header, state, txs, uncles, receipts, nil) } shanghai := chain.Config().IsShanghai(header.Time) - if !shanghai && len(withdrawals) > 0 { - return nil, errors.New("withdrawals set before Shanghai activation") + 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)