From 774be0eaf50b4ab1b2495f584b51c8e0d62b47c6 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:52:03 -0600 Subject: [PATCH 01/28] 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 c27745f61a6c30521c498bbcb29da6aa2114b772 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:53:40 -0600 Subject: [PATCH 02/28] 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 994b7619af45f65a0519777ce96fe1537fcc8e0e Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:54:45 -0600 Subject: [PATCH 03/28] eth/catalyst,les/catalyst: add withdrawals and make types version agnostic --- eth/catalyst/api.go | 42 +++++++- eth/catalyst/api_test.go | 196 +++++++++++++++++++++++++++++++------- eth/catalyst/queue.go | 2 +- les/catalyst/api.go | 6 +- les/catalyst/api_test.go | 2 +- miner/payload_building.go | 19 ++-- 6 files changed, 213 insertions(+), 54 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 7872c7f711f1..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,7 +863,7 @@ 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) @@ -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 6d0eedeccb77..207734bdd29e 100644 --- a/les/catalyst/api_test.go +++ b/les/catalyst/api_test.go @@ -118,7 +118,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 a512496b37c33c5ae34d79877f81465fbf9b24ce Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:55:55 -0600 Subject: [PATCH 04/28] miner: add withdrawals and update catalyst types --- miner/payload_building_test.go | 2 +- miner/stress/beacon/main.go | 8 +++---- miner/worker.go | 40 ++++++++++++++++++---------------- miner/worker_test.go | 4 ++-- 4 files changed, 28 insertions(+), 26 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 e00a494d0155..ceaac788f16f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -959,14 +959,15 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // generateParams wraps various of settings for generating sealing task. type generateParams struct { - 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 - noUncle bool // Flag whether the uncle block inclusion is allowed - noExtra bool // Flag whether the extra field assignment 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 + noExtra bool // Flag whether the extra field assignment is allowed + noTxs bool // Flag whether an empty block without any transaction is expected } // prepareWork constructs the sealing task according to the given parameters, @@ -1100,7 +1101,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 } @@ -1185,7 +1186,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 } @@ -1216,17 +1217,18 @@ 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, - noExtra: true, - noTxs: noTxs, + timestamp: timestamp, + forceTime: true, + parentHash: parent, + coinbase: coinbase, + random: random, + withdrawals: withdrawals, + noUncle: true, + noExtra: true, + noTxs: noTxs, }, result: make(chan *newPayloadResult, 1), } diff --git a/miner/worker_test.go b/miner/worker_test.go index 859495d7bf16..dcd4b3865bbd 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 7d7fb2f35c60fed59594c2b774e179c48d45cffe Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:56:41 -0600 Subject: [PATCH 05/28] 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 6d108856e6d6..cb21598dd330 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -264,7 +264,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 @@ -323,13 +334,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) @@ -337,15 +354,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 58218c9b1f03534c100c6424a4de6c39451b3da0 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:57:45 -0600 Subject: [PATCH 06/28] 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 db17481804ab..c7f929f0d877 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 72ec0368b921e8b1fa62c2f256c93db1481589d2 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:58:12 -0600 Subject: [PATCH 07/28] 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 52dd6e2e47be..f8cd07eb7281 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 @@ -200,6 +201,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. @@ -277,7 +292,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)) @@ -398,4 +420,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 2ebb038bd94921610e4311f23a11f5cda077e0bf Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:59:01 -0600 Subject: [PATCH 08/28] 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 a6e56c5c627922278a7f8dc7c83f5ed6a97dbf7c Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 08:59:43 -0600 Subject: [PATCH 09/28] 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 881660aa8e8f..16c2f60ad089 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -782,7 +782,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. @@ -882,7 +882,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 @@ -901,7 +901,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 a5853bb5d7db6304682fbad137e6ead5e7ca274d Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 09:00:11 -0600 Subject: [PATCH 10/28] 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 9ad8070117c0a128a89788f867563aa2ef61654e Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 09:00:42 -0600 Subject: [PATCH 11/28] 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 ++ tests/init.go | 18 +++++ 18 files changed, 252 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 a05dbedea700..a7fb1b8d267b 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 031def0211b1..e6974eddddf9 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" + } +] diff --git a/tests/init.go b/tests/init.go index ef5ea4bb9a9a..5f8196512bc4 100644 --- a/tests/init.go +++ b/tests/init.go @@ -230,6 +230,24 @@ var Forks = map[string]*params.ChainConfig{ MergeNetsplitBlock: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), }, + "Shanghai": { + 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), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiBlock: big.NewInt(0), + }, } // AvailableForks returns the set of defined fork names From 97c247484c2ca5d4ea7fd91b7db0d3cbb8b0943e Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 29 Nov 2022 18:05:31 -0700 Subject: [PATCH 12/28] 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 41c5d66edb38..50751bc7139e 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 256e54ccc4d6ca32c4a68cb7a672833c53ffb4cc Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Tue, 29 Nov 2022 18:11:23 -0700 Subject: [PATCH 13/28] 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 7a817bab88e8cd7689bddd750ff881d02f2631ec Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 27 Sep 2022 13:42:57 +0200 Subject: [PATCH 14/28] params: core: enable shanghai based on timestamps --- core/state_transition.go | 2 +- core/vm/evm.go | 2 +- core/vm/runtime/runtime.go | 8 +++----- eth/tracers/js/goja.go | 2 +- eth/tracers/native/4byte.go | 2 +- internal/ethapi/api.go | 2 +- params/config.go | 24 +++++++++++++----------- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index a6cf3f7c32d6..797aab3ab3c9 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -300,7 +300,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { var ( msg = st.msg sender = vm.AccountRef(msg.From()) - rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil) + rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time) contractCreation = msg.To() == nil ) diff --git a/core/vm/evm.go b/core/vm/evm.go index 888f4812a590..81e897988c27 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -132,7 +132,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig StateDB: statedb, Config: config, chainConfig: chainConfig, - chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil), + chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } evm.interpreter = NewEVMInterpreter(evm, config) return evm diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 6b355deeb6e2..d8d522563b7d 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -117,13 +117,12 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { address = common.BytesToAddress([]byte("contract")) vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) - rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil) + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) ) // Execute the preparatory steps for state transition which includes: // - 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) @@ -151,13 +150,12 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { var ( vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) - rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil) + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) ) // Execute the preparatory steps for state transition which includes: // - 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, @@ -180,7 +178,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv = NewEnv(cfg) sender = cfg.State.GetOrNewStateObject(cfg.Origin) statedb = cfg.State - rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil) + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) ) // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index ceb591a79afe..deafb142021e 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -243,7 +243,7 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr t.ctx["value"] = valueBig t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64()) // Update list of precompiles based on current block - rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil) + rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) } diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 00cd5fe77b8b..afe70ea089aa 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -81,7 +81,7 @@ func (t *fourByteTracer) store(id []byte, size int) { // CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { // Update list of precompiles based on current block - rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil) + rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) // Save the outer calldata also diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ea0cbfe867aa..50d690ba9b74 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1438,7 +1438,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } isPostMerge := header.Difficulty.Cmp(common.Big0) == 0 // Retrieve the precompiles since they don't need to be added to the access list - precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge)) + precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, new(big.Int).SetUint64(header.Time))) // Create an initial tracer prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles) diff --git a/params/config.go b/params/config.go index e3e48f9c2597..2ff9ad12aab9 100644 --- a/params/config.go +++ b/params/config.go @@ -281,7 +281,7 @@ var ( TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, false, new(EthashConfig), nil} NonActivatedConfig = &ChainConfig{big.NewInt(1), nil, nil, false, nil, common.Hash{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, false, new(EthashConfig), nil} - TestRules = TestChainConfig.Rules(new(big.Int), false) + TestRules = TestChainConfig.Rules(new(big.Int), false, new(big.Int)) ) // NetworkNames are user friendly names to use in the chain spec banner. @@ -371,7 +371,7 @@ type ChainConfig struct { ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated) GrayGlacierBlock *big.Int `json:"grayGlacierBlock,omitempty"` // Eip-5133 (bomb delay) switch block (nil = no fork, 0 = already activated) MergeNetsplitBlock *big.Int `json:"mergeNetsplitBlock,omitempty"` // Virtual fork after The Merge to use as a network splitter - ShanghaiBlock *big.Int `json:"shanghaiBlock,omitempty"` // Shanghai switch block (nil = no fork, 0 = already on shanghai) + ShanghaiTime *big.Int `json:"shanghaiBlock,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) CancunBlock *big.Int `json:"cancunBlock,omitempty"` // Cancun switch block (nil = no fork, 0 = already on cancun) // TerminalTotalDifficulty is the amount of total difficulty reached by @@ -465,8 +465,8 @@ 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.ShanghaiBlock != nil { - banner += fmt.Sprintf(" - Shanghai: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md)\n", c.ShanghaiBlock) + 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) @@ -569,7 +569,7 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi // IsShanghai returns whether num is either equal to the Shanghai fork block or greater. func (c *ChainConfig) IsShanghai(num *big.Int) bool { - return isForked(c.ShanghaiBlock, num) + return isForked(c.ShanghaiTime, num) } // IsCancun returns whether num is either equal to the Cancun fork block or greater. @@ -620,7 +620,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "arrowGlacierBlock", block: c.ArrowGlacierBlock, optional: true}, {name: "grayGlacierBlock", block: c.GrayGlacierBlock, optional: true}, {name: "mergeNetsplitBlock", block: c.MergeNetsplitBlock, optional: true}, - {name: "shanghaiBlock", block: c.ShanghaiBlock, optional: true}, + //{name: "shanghaiBlock", block: c.ShanghaiBlock, optional: true}, {name: "cancunBlock", block: c.CancunBlock, optional: true}, } { if lastFork.name != "" { @@ -700,9 +700,11 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock, head) { return newCompatError("Merge netsplit fork block", c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock) } - if isForkIncompatible(c.ShanghaiBlock, newcfg.ShanghaiBlock, head) { - return newCompatError("Shanghai fork block", c.ShanghaiBlock, newcfg.ShanghaiBlock) - } + /* + if isForkIncompatible(c.ShanghaiBlock, newcfg.ShanghaiBlock, head) { + return newCompatError("Shanghai fork block", c.ShanghaiBlock, newcfg.ShanghaiBlock) + } + */ if isForkIncompatible(c.CancunBlock, newcfg.CancunBlock, head) { return newCompatError("Cancun fork block", c.CancunBlock, newcfg.CancunBlock) } @@ -788,7 +790,7 @@ type Rules struct { } // Rules ensures c's ChainID is not nil. -func (c *ChainConfig) Rules(num *big.Int, isMerge bool) Rules { +func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp *big.Int) Rules { chainID := c.ChainID if chainID == nil { chainID = new(big.Int) @@ -806,7 +808,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool) Rules { IsBerlin: c.IsBerlin(num), IsLondon: c.IsLondon(num), IsMerge: isMerge, - IsShanghai: c.IsShanghai(num), + IsShanghai: c.IsShanghai(timestamp), isCancun: c.IsCancun(num), } } From 858ac2fb465c3b54a576811cafdb0af3c72f790f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 31 Oct 2022 13:58:10 +0100 Subject: [PATCH 15/28] params: nitpicks --- params/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/params/config.go b/params/config.go index 2ff9ad12aab9..c4fc021c722c 100644 --- a/params/config.go +++ b/params/config.go @@ -567,9 +567,9 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi return parentTotalDiff.Cmp(c.TerminalTotalDifficulty) < 0 && totalDiff.Cmp(c.TerminalTotalDifficulty) >= 0 } -// IsShanghai returns whether num is either equal to the Shanghai fork block or greater. -func (c *ChainConfig) IsShanghai(num *big.Int) bool { - return isForked(c.ShanghaiTime, num) +// IsShanghai returns whether time is either equal to the Shanghai fork time or greater. +func (c *ChainConfig) IsShanghai(time *big.Int) bool { + return isForked(c.ShanghaiTime, time) } // IsCancun returns whether num is either equal to the Cancun fork block or greater. From 798a3e4177ea139e7baae22a7bf2f074fd265303 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sat, 5 Nov 2022 20:56:31 -0600 Subject: [PATCH 16/28] 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 cb21598dd330..6863a8ab073a 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -268,7 +268,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") } @@ -342,7 +342,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 f8cd07eb7281..e4f01c497195 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -292,7 +292,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 c7f929f0d877..36e355269a11 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 0f30e7454d99e9a45892f2abaa9415e0fe48ac18 Mon Sep 17 00:00:00 2001 From: "lightclient@protonmail.com" Date: Sun, 6 Nov 2022 06:44:10 -0700 Subject: [PATCH 17/28] 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 e8c8df49dc3f75b372e17925ff2ed68a622f9f94 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Nov 2022 10:48:12 +0100 Subject: [PATCH 18/28] 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 ++++ tests/init.go | 2 +- 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/genesis.go b/core/genesis.go index bbfa356af9f4..88b0cde7bf95 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -476,6 +476,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 50d690ba9b74..f0406cc6f7ea 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1208,6 +1208,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 } diff --git a/tests/init.go b/tests/init.go index 5f8196512bc4..b0d0f61d2444 100644 --- a/tests/init.go +++ b/tests/init.go @@ -246,7 +246,7 @@ var Forks = map[string]*params.ChainConfig{ ArrowGlacierBlock: big.NewInt(0), MergeNetsplitBlock: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), - ShanghaiBlock: big.NewInt(0), + ShanghaiTime: big.NewInt(0), }, } From 19b4bc8b54af467172e4a0a284503b5957007af5 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 10 Nov 2022 15:25:32 +0100 Subject: [PATCH 19/28] all: implement forkid changes for shanghai --- cmd/devp2p/internal/ethtest/chain.go | 2 +- core/blockchain_test.go | 14 ++-- core/forkid/forkid.go | 60 +++++++++++----- core/forkid/forkid_test.go | 100 ++++++++++++++++++++++++++- eth/catalyst/api_test.go | 3 +- eth/handler.go | 2 +- eth/protocols/eth/discovery.go | 2 +- eth/protocols/eth/handshake_test.go | 2 +- eth/protocols/eth/protocol.go | 2 +- les/client_handler.go | 2 +- les/peer_test.go | 4 +- les/server_handler.go | 2 +- les/test_helper.go | 4 +- tests/state_test.go | 2 +- 14 files changed, 163 insertions(+), 38 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 83ceb2a4f2c5..f4db05db8e39 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -76,7 +76,7 @@ func (c *Chain) RootAt(height int) common.Hash { // ForkID gets the fork id of the chain. func (c *Chain) ForkID() forkid.ID { - return forkid.NewID(c.chainConfig, c.blocks[0].Hash(), uint64(c.Len())) + return forkid.NewID(c.chainConfig, c.blocks[0].Hash(), uint64(c.Len()), c.blocks[0].Time()) } // Shorten returns a copy chain of a desired height from the imported diff --git a/core/blockchain_test.go b/core/blockchain_test.go index faa4b383feb4..fb8579a64edf 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4192,7 +4192,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") @@ -4200,8 +4200,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}, @@ -4238,7 +4239,9 @@ func TestEIP3651(t *testing.T) { gspec.Config.BerlinBlock = common.Big0 gspec.Config.LondonBlock = common.Big0 - gspec.Config.ShanghaiBlock = common.Big0 + gspec.Config.ShanghaiTime = common.Big0 + gspec.Config.TerminalTotalDifficulty = common.Big0 + gspec.Config.TerminalTotalDifficultyPassed = true signer := types.LatestSigner(gspec.Config) _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { @@ -4280,10 +4283,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 f56ce85feeed..407c50827239 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -24,6 +24,7 @@ import ( "math" "math/big" "reflect" + "sort" "strings" "github.com/ethereum/go-ethereum/common" @@ -65,19 +66,28 @@ type ID struct { // Filter is a fork id filter to validate a remotely advertised ID. type Filter func(id ID) error -// NewID calculates the Ethereum fork ID from the chain config, genesis hash, and head. -func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { +// NewID calculates the Ethereum fork ID from the chain config, genesis hash, head and time. +func NewID(config *params.ChainConfig, genesis common.Hash, head, time uint64) ID { // Calculate the starting checksum from the genesis hash hash := crc32.ChecksumIEEE(genesis[:]) // Calculate the current fork checksum and the next fork block - var next uint64 - for _, fork := range gatherForks(config) { + forks, forksByTime := gatherForks(config) + for _, fork := range forks { if fork <= head { // Fork already passed, checksum the previous hash and the fork number hash = checksumUpdate(hash, fork) continue } + return ID{Hash: checksumToBytes(hash), Next: fork} + } + var next uint64 + for _, fork := range forksByTime { + if time >= fork { + // Fork passed, checksum previous hash and fork time + hash = checksumUpdate(hash, fork) + continue + } next = fork break } @@ -90,6 +100,7 @@ func NewIDWithChain(chain Blockchain) ID { chain.Config(), chain.Genesis().Hash(), chain.CurrentHeader().Number.Uint64(), + chain.CurrentHeader().Time, ) } @@ -117,9 +128,10 @@ func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter { func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) Filter { // Calculate the all the valid fork hash and fork next combos var ( - forks = gatherForks(config) - sums = make([][4]byte, len(forks)+1) // 0th is the genesis + forks, forksByTime = gatherForks(config) + sums = make([][4]byte, len(forks)+len(forksByTime)+1) // 0th is the genesis ) + forks = append(forks, forksByTime...) hash := crc32.ChecksumIEEE(genesis[:]) sums[0] = checksumToBytes(hash) for i, fork := range forks { @@ -212,17 +224,22 @@ func checksumToBytes(hash uint32) [4]byte { } // gatherForks gathers all the known forks and creates a sorted list out of them. -func gatherForks(config *params.ChainConfig) []uint64 { +func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) { // Gather all the fork block numbers via reflection kind := reflect.TypeOf(params.ChainConfig{}) conf := reflect.ValueOf(config).Elem() var forks []uint64 + var forksByTime []uint64 for i := 0; i < kind.NumField(); i++ { // Fetch the next field and skip non-fork rules field := kind.Field(i) + time := false if !strings.HasSuffix(field.Name, "Block") { - continue + if !strings.HasSuffix(field.Name, "Time") { + continue + } + time = true } if field.Type != reflect.TypeOf(new(big.Int)) { continue @@ -230,17 +247,17 @@ func gatherForks(config *params.ChainConfig) []uint64 { // Extract the fork rule block number and aggregate it rule := conf.Field(i).Interface().(*big.Int) if rule != nil { - forks = append(forks, rule.Uint64()) - } - } - // Sort the fork block numbers to permit chronological XOR - for i := 0; i < len(forks); i++ { - for j := i + 1; j < len(forks); j++ { - if forks[i] > forks[j] { - forks[i], forks[j] = forks[j], forks[i] + if time { + forksByTime = append(forksByTime, rule.Uint64()) + } else { + forks = append(forks, rule.Uint64()) } } } + + sort.Slice(forks, func(i, j int) bool { return forks[i] < forks[j] }) + sort.Slice(forksByTime, func(i, j int) bool { return forksByTime[i] < forksByTime[j] }) + // Deduplicate block numbers applying multiple forks for i := 1; i < len(forks); i++ { if forks[i] == forks[i-1] { @@ -248,9 +265,18 @@ func gatherForks(config *params.ChainConfig) []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(forks) > 0 && forks[0] == 0 { forks = forks[1:] } - return forks + if len(forksByTime) > 0 && forksByTime[0] == 0 { + forksByTime = forksByTime[1:] + } + return forks, forksByTime } diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 2a0fb167d516..0b392c06a7bb 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -185,7 +185,105 @@ func TestCreation(t *testing.T) { } for i, tt := range tests { for j, ttt := range tt.cases { - if have := NewID(tt.config, tt.genesis, ttt.head); have != ttt.want { + if have := NewID(tt.config, tt.genesis, ttt.head, 0); have != ttt.want { + t.Errorf("test %d, case %d: fork ID mismatch: have %x, want %x", i, j, have, ttt.want) + } + } + } +} + +// TestCreationWithTimestamps tests that different genesis and fork rule combinations result in +// the correct fork ID even for time based forks. +func TestCreationWithTimestamps(t *testing.T) { + mergeConfig := *params.MainnetChainConfig + mergeConfig.MergeNetsplitBlock = big.NewInt(18000000) + + withdrawalConfig := *params.MainnetChainConfig + withdrawalConfig.MergeNetsplitBlock = big.NewInt(18000000) + withdrawalConfig.ShanghaiTime = big.NewInt(1668000000) + type testcase struct { + head uint64 + time uint64 + want ID + } + tests := []struct { + config *params.ChainConfig + genesis common.Hash + cases []testcase + }{ + // Mainnet test cases + { + params.MainnetChainConfig, + params.MainnetGenesisHash, + []testcase{ + {0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block + {12243999, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block + {12244000, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block + {12964999, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block + {12965000, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block + {13772999, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block + {13773000, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block + {15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block + {15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}}, // First Gray Glacier block + {20000000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}}, // Future Gray Glacier block + }, + }, + // Withdrawal test cases + { + &withdrawalConfig, + params.MainnetGenesisHash, + []testcase{ + {0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block + {12243999, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block + {12244000, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block + {12964999, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block + {12965000, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block + {13772999, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block + {13773000, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block + {15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block + {15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 18000000}}, // First Gray Glacier block + {18000000, 0, ID{Hash: checksumToBytes(0x4fb8a872), Next: 1668000000}}, // First Merge Start block + {20000000, 0, ID{Hash: checksumToBytes(0x4fb8a872), Next: 1668000000}}, // Last Merge Start block + {20000000, 1668000000, ID{Hash: checksumToBytes(0xc1fdf181), Next: 0}}, // First Merge Start block + {20000000, 2668000000, ID{Hash: checksumToBytes(0xc1fdf181), Next: 0}}, // Future Merge Start block + }, + }, + } + for i, tt := range tests { + for j, ttt := range tt.cases { + if have := NewID(tt.config, tt.genesis, ttt.head, ttt.time); have != ttt.want { t.Errorf("test %d, case %d: fork ID mismatch: have %x, want %x", i, j, have, ttt.want) } } 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/handler.go b/eth/handler.go index 143147b0c815..c67a62b86700 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -331,7 +331,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { number = head.Number.Uint64() td = h.chain.GetTd(hash, number) ) - forkID := forkid.NewID(h.chain.Config(), h.chain.Genesis().Hash(), h.chain.CurrentHeader().Number.Uint64()) + forkID := forkid.NewID(h.chain.Config(), h.chain.Genesis().Hash(), h.chain.CurrentHeader().Number.Uint64(), h.chain.CurrentHeader().Time) if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil { peer.Log().Debug("Ethereum handshake failed", "err", err) return err diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go index 03f2ea3cc297..345c6e5e2768 100644 --- a/eth/protocols/eth/discovery.go +++ b/eth/protocols/eth/discovery.go @@ -60,6 +60,6 @@ func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) { // currentENREntry constructs an `eth` ENR entry based on the current state of the chain. func currentENREntry(chain *core.BlockChain) *enrEntry { return &enrEntry{ - ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), chain.CurrentHeader().Number.Uint64()), + ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), chain.CurrentHeader().Number.Uint64(), chain.CurrentHeader().Time), } } diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go index 8cf5216cf442..c768edaeac01 100644 --- a/eth/protocols/eth/handshake_test.go +++ b/eth/protocols/eth/handshake_test.go @@ -40,7 +40,7 @@ func testHandshake(t *testing.T, protocol uint) { genesis = backend.chain.Genesis() head = backend.chain.CurrentBlock() td = backend.chain.GetTd(head.Hash(), head.NumberU64()) - forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64()) + forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time) ) tests := []struct { code uint64 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. diff --git a/les/client_handler.go b/les/client_handler.go index e416f92e29a9..cce99d41dc14 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -111,7 +111,7 @@ func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error { p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) // Execute the LES handshake - forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.genesis, h.backend.blockchain.CurrentHeader().Number.Uint64()) + forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.genesis, h.backend.blockchain.CurrentHeader().Number.Uint64(), h.backend.blockchain.CurrentHeader().Time) if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err diff --git a/les/peer_test.go b/les/peer_test.go index b8a1482a040a..021d5cb594c4 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -124,8 +124,8 @@ func TestHandshake(t *testing.T) { genesis = common.HexToHash("cafebabe") chain1, chain2 = &fakeChain{}, &fakeChain{} - forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis().Hash(), chain1.CurrentHeader().Number.Uint64()) - forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), chain2.CurrentHeader().Number.Uint64()) + forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis().Hash(), chain1.CurrentHeader().Number.Uint64(), chain1.CurrentHeader().Time) + forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), chain2.CurrentHeader().Number.Uint64(), chain2.CurrentHeader().Time) filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2) ) diff --git a/les/server_handler.go b/les/server_handler.go index 28815c3d85ef..b9fb1d19bfc2 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -117,7 +117,7 @@ func (h *serverHandler) handle(p *clientPeer) error { hash = head.Hash() number = head.Number.Uint64() td = h.blockchain.GetTd(hash, number) - forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), h.blockchain.CurrentBlock().NumberU64()) + forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), h.blockchain.CurrentBlock().NumberU64(), h.blockchain.CurrentBlock().Time()) ) if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) diff --git a/les/test_helper.go b/les/test_helper.go index 33a76252bf05..714bc7b3f625 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -489,7 +489,7 @@ func (client *testClient) newRawPeer(t *testing.T, name string, version int, rec head = client.handler.backend.blockchain.CurrentHeader() td = client.handler.backend.blockchain.GetTd(head.Hash(), head.Number.Uint64()) ) - forkID := forkid.NewID(client.handler.backend.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + forkID := forkid.NewID(client.handler.backend.blockchain.Config(), genesis.Hash(), head.Number.Uint64(), head.Time) tp.handshakeWithClient(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(0), recentTxLookup) // disable flow control by default // Ensure the connection is established or exits when any error occurs @@ -553,7 +553,7 @@ func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*t head = server.handler.blockchain.CurrentHeader() td = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) ) - forkID := forkid.NewID(server.handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + forkID := forkid.NewID(server.handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64(), head.Time) tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID) // Ensure the connection is established or exits when any error occurs diff --git a/tests/state_test.go b/tests/state_test.go index cb7f76521780..accf6eaf69b4 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -185,7 +185,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.Error(err) return } - var rules = config.Rules(new(big.Int), false) + var rules = config.Rules(new(big.Int), false, new(big.Int)) vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock() From dae68e221510a1f7bd9a42c37eeca8eb369eff03 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 1 Dec 2022 16:26:12 +0100 Subject: [PATCH 20/28] 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 b5036349c0497df79403d5c08bbedc124dd581d3 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 22 Nov 2022 13:59:48 +0100 Subject: [PATCH 21/28] 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 f0406cc6f7ea..f47b9011c994 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1240,6 +1240,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 a47d17419af17f6ea591ccd1cb0a46c4c67802e7 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 7 Dec 2022 11:36:00 +0100 Subject: [PATCH 22/28] 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 f47b9011c994..80bf2c0c8a29 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1241,6 +1241,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 929035ea83da7defcc7f3176bd3ce967abc7bc41 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 13 Dec 2022 11:32:06 +0100 Subject: [PATCH 23/28] core/forkid: fix forkid filter --- core/forkid/forkid.go | 61 ++++++++++++++++++++++---- core/forkid/forkid_test.go | 88 +++++++++++++++++++++++++++++++++++++- 2 files changed, 139 insertions(+), 10 deletions(-) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 407c50827239..c9eda246d4fd 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -110,37 +110,40 @@ func NewFilter(chain Blockchain) Filter { return newFilter( chain.Config(), chain.Genesis().Hash(), - func() uint64 { - return chain.CurrentHeader().Number.Uint64() + func() (uint64, uint64) { + return chain.CurrentHeader().Number.Uint64(), chain.CurrentHeader().Time }, ) } // NewStaticFilter creates a filter at block zero. func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter { - head := func() uint64 { return 0 } + head := func() (uint64, uint64) { return 0, 0 } return newFilter(config, genesis, head) } // newFilter is the internal version of NewFilter, taking closures as its arguments // instead of a chain. The reason is to allow testing it without having to simulate // an entire blockchain. -func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) Filter { +func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (uint64, uint64)) Filter { // Calculate the all the valid fork hash and fork next combos var ( forks, forksByTime = gatherForks(config) sums = make([][4]byte, len(forks)+len(forksByTime)+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) } // Add two sentries to simplify the fork checks and don't require special // casing the last one. - forks = append(forks, math.MaxUint64) // Last fork will never be passed + if len(forksByTime) == 0 { + forks = append(forks, math.MaxUint64) + } + forksByTime = append(forksByTime, math.MaxUint64) // Last fork will never be passed // Create a validator that will filter out incompatible chains return func(id ID) error { @@ -163,7 +166,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() ui // the remote, but at this current point in time we don't have enough // information. // 4. Reject in all other cases. - head := headfn() + head, time := headfn() 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). @@ -186,7 +189,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() ui 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 @@ -203,6 +206,46 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() ui // 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 } diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 0b392c06a7bb..0158545f345e 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -365,7 +365,93 @@ func TestValidation(t *testing.T) { {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrLocalIncompatibleOrStale}, } for i, tt := range tests { - filter := newFilter(params.MainnetChainConfig, params.MainnetGenesisHash, func() uint64 { return tt.head }) + filter := newFilter(params.MainnetChainConfig, params.MainnetGenesisHash, func() (uint64, uint64) { return tt.head, 0 }) + if err := filter(tt.id); err != tt.err { + t.Errorf("test %d: validation error mismatch: have %v, want %v", i, err, tt.err) + } + } +} + +// TestValidationByTimestamp tests that a local peer correctly validates and accepts a remote +// fork ID. +func TestValidationByTimestamp(t *testing.T) { + withdrawalConfig := *params.MainnetChainConfig + withdrawalConfig.MergeNetsplitBlock = big.NewInt(18000000) + withdrawalConfig.ShanghaiTime = big.NewInt(1668000000) + tests := []struct { + head uint64 + time uint64 + id ID + err error + }{ + // Local is mainnet Withdrawals, remote announces the same. No future fork is announced. + {20000000, 1668000001, ID{Hash: checksumToBytes(0xc1fdf181), Next: 0}, nil}, + + // Local is mainnet Withdrawals, remote announces the same also announces a next fork + // at block/time 0xffffffff, but that is uncertain. + {20000000, 1668000001, ID{Hash: checksumToBytes(0xc1fdf181), Next: math.MaxUint64}, nil}, + + // Local is mainnet currently in Byzantium only (so it's aware of Petersburg & Withdrawals), remote announces + // also Byzantium, but it's not yet aware of Petersburg (e.g. non updated node before the fork). + // In this case we don't know if Petersburg passed yet or not. + {7279999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, + + // Local is mainnet currently in Byzantium only (so it's aware of Petersburg & Withdrawals), remote announces + // also Byzantium, and it's also aware of Petersburg (e.g. updated node before the fork). We + // don't know if Petersburg passed yet (will pass) or not. + {7279999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, + + // Local is mainnet currently in Byzantium only (so it's aware of Petersburg & Withdrawals), remote announces + // also Byzantium, and it's also aware of some random fork (e.g. misconfigured Petersburg). As + // neither forks passed at neither nodes, they may mismatch, but we still connect for now. + {7279999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: math.MaxUint64}, nil}, + + // Local is mainnet exactly on Withdrawals, remote announces Byzantium + knowledge about Petersburg. Remote + // is simply out of sync, accept. + {20000000, 1668000000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, + + // Local is mainnet Withdrawals, remote announces Byzantium + knowledge about Petersburg. Remote + // is simply out of sync, accept. + {20000000, 1668000001, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, + + // Local is mainnet Withdrawals, remote announces Spurious + knowledge about Byzantium. Remote + // is definitely out of sync. It may or may not need the Petersburg update, we don't know yet. + {20000000, 1668000001, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil}, + + // Local is mainnet Byzantium & pre-withdrawals, remote announces Petersburg. Local is out of sync, accept. + {7279999, 1667999999, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil}, + + // Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. Local + // out of sync. Local also knows about a future fork, but that is uncertain yet. + {4369999, 1667999999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, + + // Local is mainnet Withdrawals. remote announces Byzantium but is not aware of further forks. + // Remote needs software update. + {20000000, 1668000001, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, ErrRemoteStale}, + + // Local is mainnet Withdrawals, and isn't aware of more forks. Remote announces Petersburg + + // 0xffffffff. Local needs software update, reject. + {20000000, 1668000001, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Withdrawals, and is aware of Petersburg. Remote announces Petersburg + + // 0xffffffff. Local needs software update, reject. + {20000000, 1668000001, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Withdrawals, remote is Rinkeby Petersburg. + {20000000, 1668000001, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Withdrawals, far in the future. Remote announces Gopherium (non existing fork) + // at some future block 88888888, for itself, but past block for local. Local is incompatible. + // + // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). + {88888888, 1668000001, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 88888888}, ErrRemoteStale}, + + // Local is mainnet Withdrawals. Remote is in Byzantium, but announces Gopherium (non existing + // fork) at block 7279999, before Petersburg. Local is incompatible. + {20000000, 1668000001, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrRemoteStale}, + } + for i, tt := range tests { + filter := newFilter(&withdrawalConfig, params.MainnetGenesisHash, func() (uint64, uint64) { return tt.head, tt.time }) if err := filter(tt.id); err != tt.err { t.Errorf("test %d: validation error mismatch: have %v, want %v", i, err, tt.err) } From 8d6f9650e5c42e2dbcf33695fa31be9b384e8de5 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 14 Dec 2022 12:32:23 +0100 Subject: [PATCH 24/28] params: use shanghai time instead of block --- params/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index c4fc021c722c..a1a462e6a620 100644 --- a/params/config.go +++ b/params/config.go @@ -371,7 +371,7 @@ type ChainConfig struct { ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated) GrayGlacierBlock *big.Int `json:"grayGlacierBlock,omitempty"` // Eip-5133 (bomb delay) switch block (nil = no fork, 0 = already activated) MergeNetsplitBlock *big.Int `json:"mergeNetsplitBlock,omitempty"` // Virtual fork after The Merge to use as a network splitter - ShanghaiTime *big.Int `json:"shanghaiBlock,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) + ShanghaiTime *big.Int `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) CancunBlock *big.Int `json:"cancunBlock,omitempty"` // Cancun switch block (nil = no fork, 0 = already on cancun) // TerminalTotalDifficulty is the amount of total difficulty reached by From 6d7e0b77fdbbd83159edc6cac25dbe4db2e65962 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sun, 16 Oct 2022 22:02:54 +0200 Subject: [PATCH 25/28] 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 +- eth/catalyst/tester.go | 5 +++-- miner/payload_building.go | 14 +++++++------- miner/payload_building_test.go | 13 +++++++------ 7 files changed, 38 insertions(+), 30 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/eth/catalyst/tester.go b/eth/catalyst/tester.go index 63ee5feb26be..eecfe280f03f 100644 --- a/eth/catalyst/tester.go +++ b/eth/catalyst/tester.go @@ -17,6 +17,7 @@ package catalyst import ( + "math/big" "sync" "time" @@ -76,8 +77,8 @@ func (tester *FullSyncTester) Start() error { return } // Shoot out consensus events in order to trigger syncing. - data := beacon.BlockToExecutableData(tester.block) - tester.api.NewPayloadV1(*data) + data := beacon.BlockToExecutableData(tester.block, big.NewInt(0)) + tester.api.NewPayloadV1(*data.ExecutionPayload) tester.api.ForkchoiceUpdatedV1(beacon.ForkchoiceStateV1{ HeadBlockHash: tester.block.Hash(), SafeBlockHash: tester.block.Hash(), 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 87e1b16eed9aa50ad3b8d6cae374838adb4e7be1 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 15 Dec 2022 14:16:15 +0100 Subject: [PATCH 26/28] core/forkid: reduce diff by anonymous function --- core/forkid/forkid.go | 63 ++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 43 deletions(-) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index c9eda246d4fd..1d738b6d5b23 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -166,19 +166,14 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u // the remote, but at this current point in time we don't have enough // information. // 4. Reject in all other cases. - head, time := headfn() - 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 - } + + verify := func(index int, headOrTime uint64) error { // 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 { + if id.Next > 0 && headOrTime >= id.Next { return ErrLocalIncompatibleOrStale } // Haven't passed locally a remote-only fork, accept the connection (rule #1b). @@ -186,7 +181,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 { @@ -197,7 +192,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 @@ -206,44 +201,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 90e7d81eeb9ff34971631ea7914973509e3e3dec Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sat, 17 Dec 2022 14:20:53 +0100 Subject: [PATCH 27/28] eth/catalyst: require withdrawals on V2 --- eth/catalyst/api.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 18c38cd9d3e7..ca57551e5653 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -368,13 +368,16 @@ func (api *ConsensusAPI) GetPayloadV2(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.ExecutableData) (beacon.PayloadStatusV1, error) { if params.Withdrawals != nil { - return beacon.PayloadStatusV1{Status: beacon.INVALID}, fmt.Errorf("withdrawals not supported in V1") + return api.invalid(fmt.Errorf("withdrawals not supported in V1"), nil), nil } 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) { + if params.Withdrawals == nil { + return api.invalid(fmt.Errorf("withdrawals required in V2"), nil), nil + } return api.newPayload(params) } From 86d50690c47dfac25c34e7f5c229945797f07dda Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sat, 17 Dec 2022 15:23:43 +0100 Subject: [PATCH 28/28] eth/catalyst: require withdrawals post-shanghai --- eth/catalyst/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index ca57551e5653..9ba7427abcc3 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -375,8 +375,8 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableData) (beacon.Payl // 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) { - if params.Withdrawals == nil { - return api.invalid(fmt.Errorf("withdrawals required in V2"), nil), nil + if api.eth.BlockChain().Config().IsShanghai(new(big.Int).SetUint64(params.Timestamp)) && params.Withdrawals == nil { + return api.invalid(fmt.Errorf("withdrawals required post-shanghai"), nil), nil } return api.newPayload(params) }