all: implement withdrawals (EIP-4895) (#26484)

This change implements withdrawals as specified in EIP-4895.

Co-authored-by: lightclient@protonmail.com <lightclient@protonmail.com>
Co-authored-by: marioevz <marioevz@gmail.com>
Co-authored-by: Martin Holst Swende <martin@swende.se>
Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Marius van der Wijden 2023-01-25 15:32:25 +01:00 committed by GitHub
parent 2b57a27d9e
commit 2a2b0419fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1233 additions and 436 deletions

@ -38,22 +38,23 @@ import (
//go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go //go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go
type header struct { type header struct {
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
OmmerHash *common.Hash `json:"sha3Uncles"` OmmerHash *common.Hash `json:"sha3Uncles"`
Coinbase *common.Address `json:"miner"` Coinbase *common.Address `json:"miner"`
Root common.Hash `json:"stateRoot" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash *common.Hash `json:"transactionsRoot"` TxHash *common.Hash `json:"transactionsRoot"`
ReceiptHash *common.Hash `json:"receiptsRoot"` ReceiptHash *common.Hash `json:"receiptsRoot"`
Bloom types.Bloom `json:"logsBloom"` Bloom types.Bloom `json:"logsBloom"`
Difficulty *big.Int `json:"difficulty"` Difficulty *big.Int `json:"difficulty"`
Number *big.Int `json:"number" gencodec:"required"` Number *big.Int `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed"` GasUsed uint64 `json:"gasUsed"`
Time uint64 `json:"timestamp" gencodec:"required"` Time uint64 `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData"` Extra []byte `json:"extraData"`
MixDigest common.Hash `json:"mixHash"` MixDigest common.Hash `json:"mixHash"`
Nonce *types.BlockNonce `json:"nonce"` Nonce *types.BlockNonce `json:"nonce"`
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
} }
type headerMarshaling struct { type headerMarshaling struct {
@ -67,10 +68,11 @@ type headerMarshaling struct {
} }
type bbInput struct { type bbInput struct {
Header *header `json:"header,omitempty"` Header *header `json:"header,omitempty"`
OmmersRlp []string `json:"ommers,omitempty"` OmmersRlp []string `json:"ommers,omitempty"`
TxRlp string `json:"txs,omitempty"` TxRlp string `json:"txs,omitempty"`
Clique *cliqueInput `json:"clique,omitempty"` Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
Clique *cliqueInput `json:"clique,omitempty"`
Ethash bool `json:"-"` Ethash bool `json:"-"`
EthashDir string `json:"-"` EthashDir string `json:"-"`
@ -114,21 +116,22 @@ func (c *cliqueInput) UnmarshalJSON(input []byte) error {
// ToBlock converts i into a *types.Block // ToBlock converts i into a *types.Block
func (i *bbInput) ToBlock() *types.Block { func (i *bbInput) ToBlock() *types.Block {
header := &types.Header{ header := &types.Header{
ParentHash: i.Header.ParentHash, ParentHash: i.Header.ParentHash,
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
Coinbase: common.Address{}, Coinbase: common.Address{},
Root: i.Header.Root, Root: i.Header.Root,
TxHash: types.EmptyRootHash, TxHash: types.EmptyRootHash,
ReceiptHash: types.EmptyRootHash, ReceiptHash: types.EmptyRootHash,
Bloom: i.Header.Bloom, Bloom: i.Header.Bloom,
Difficulty: common.Big0, Difficulty: common.Big0,
Number: i.Header.Number, Number: i.Header.Number,
GasLimit: i.Header.GasLimit, GasLimit: i.Header.GasLimit,
GasUsed: i.Header.GasUsed, GasUsed: i.Header.GasUsed,
Time: i.Header.Time, Time: i.Header.Time,
Extra: i.Header.Extra, Extra: i.Header.Extra,
MixDigest: i.Header.MixDigest, MixDigest: i.Header.MixDigest,
BaseFee: i.Header.BaseFee, BaseFee: i.Header.BaseFee,
WithdrawalsHash: i.Header.WithdrawalsHash,
} }
// Fill optional values. // Fill optional values.
@ -153,7 +156,7 @@ func (i *bbInput) ToBlock() *types.Block {
if header.Difficulty != nil { if header.Difficulty != nil {
header.Difficulty = i.Header.Difficulty header.Difficulty = i.Header.Difficulty
} }
return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers) return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers).WithWithdrawals(i.Withdrawals)
} }
// SealBlock seals the given block using the configured engine. // 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) { func readInput(ctx *cli.Context) (*bbInput, error) {
var ( var (
headerStr = ctx.String(InputHeaderFlag.Name) headerStr = ctx.String(InputHeaderFlag.Name)
ommersStr = ctx.String(InputOmmersFlag.Name) ommersStr = ctx.String(InputOmmersFlag.Name)
txsStr = ctx.String(InputTxsRlpFlag.Name) withdrawalsStr = ctx.String(InputWithdrawalsFlag.Name)
cliqueStr = ctx.String(SealCliqueFlag.Name) txsStr = ctx.String(InputTxsRlpFlag.Name)
ethashOn = ctx.Bool(SealEthashFlag.Name) cliqueStr = ctx.String(SealCliqueFlag.Name)
ethashDir = ctx.String(SealEthashDirFlag.Name) ethashOn = ctx.Bool(SealEthashFlag.Name)
ethashMode = ctx.String(SealEthashModeFlag.Name) ethashDir = ctx.String(SealEthashDirFlag.Name)
inputData = &bbInput{} ethashMode = ctx.String(SealEthashModeFlag.Name)
inputData = &bbInput{}
) )
if ethashOn && cliqueStr != "" { if ethashOn && cliqueStr != "" {
return nil, NewError(ErrorConfig, fmt.Errorf("both ethash and clique sealing specified, only one may be chosen")) 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 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 { if txsStr != stdinSelector {
var txs string var txs string
if err := readFile(txsStr, "txs", &txs); err != nil { if err := readFile(txsStr, "txs", &txs); err != nil {
@ -351,15 +362,14 @@ func readInput(ctx *cli.Context) (*bbInput, error) {
// files // files
func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error {
raw, _ := rlp.EncodeToBytes(block) raw, _ := rlp.EncodeToBytes(block)
type blockInfo struct { type blockInfo struct {
Rlp hexutil.Bytes `json:"rlp"` Rlp hexutil.Bytes `json:"rlp"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc blockInfo enc := blockInfo{
enc.Rlp = raw Rlp: raw,
enc.Hash = block.Hash() Hash: block.Hash(),
}
b, err := json.MarshalIndent(enc, "", " ") b, err := json.MarshalIndent(enc, "", " ")
if err != nil { if err != nil {
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))

@ -47,16 +47,17 @@ type Prestate struct {
// ExecutionResult contains the execution status after running a state test, any // 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. // error that might have occurred and a dump of the final state if requested.
type ExecutionResult struct { type ExecutionResult struct {
StateRoot common.Hash `json:"stateRoot"` StateRoot common.Hash `json:"stateRoot"`
TxRoot common.Hash `json:"txRoot"` TxRoot common.Hash `json:"txRoot"`
ReceiptRoot common.Hash `json:"receiptsRoot"` ReceiptRoot common.Hash `json:"receiptsRoot"`
LogsHash common.Hash `json:"logsHash"` LogsHash common.Hash `json:"logsHash"`
Bloom types.Bloom `json:"logsBloom" gencodec:"required"` Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
Receipts types.Receipts `json:"receipts"` Receipts types.Receipts `json:"receipts"`
Rejected []*rejectedTx `json:"rejected,omitempty"` Rejected []*rejectedTx `json:"rejected,omitempty"`
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
} }
type ommer struct { type ommer struct {
@ -79,6 +80,7 @@ type stEnv struct {
ParentTimestamp uint64 `json:"parentTimestamp,omitempty"` ParentTimestamp uint64 `json:"parentTimestamp,omitempty"`
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
Ommers []ommer `json:"ommers,omitempty"` Ommers []ommer `json:"ommers,omitempty"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
BaseFee *big.Int `json:"currentBaseFee,omitempty"` BaseFee *big.Int `json:"currentBaseFee,omitempty"`
ParentUncleHash common.Hash `json:"parentUncleHash"` ParentUncleHash common.Hash `json:"parentUncleHash"`
} }
@ -254,6 +256,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
} }
statedb.AddBalance(pre.Env.Coinbase, minerReward) statedb.AddBalance(pre.Env.Coinbase, minerReward)
} }
// Apply withdrawals
for _, w := range pre.Env.Withdrawals {
// Amount is in gwei, turn into wei
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
statedb.AddBalance(w.Address, amount)
}
// Commit block // Commit block
root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber))
if err != nil { if err != nil {
@ -272,6 +280,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
GasUsed: (math.HexOrDecimal64)(gasUsed), GasUsed: (math.HexOrDecimal64)(gasUsed),
BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), 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 return statedb, execRs, nil
} }

@ -112,6 +112,10 @@ var (
Name: "input.ommers", Name: "input.ommers",
Usage: "`stdin` or file name of where to find the list of ommer header RLPs to use.", 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{ InputTxsRlpFlag = &cli.StringFlag{
Name: "input.txs", Name: "input.txs",
Usage: "`stdin` or file name of where to find the transactions list in RLP form.", Usage: "`stdin` or file name of where to find the transactions list in RLP form.",

@ -18,22 +18,23 @@ var _ = (*headerMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (h header) MarshalJSON() ([]byte, error) { func (h header) MarshalJSON() ([]byte, error) {
type header struct { type header struct {
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
OmmerHash *common.Hash `json:"sha3Uncles"` OmmerHash *common.Hash `json:"sha3Uncles"`
Coinbase *common.Address `json:"miner"` Coinbase *common.Address `json:"miner"`
Root common.Hash `json:"stateRoot" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash *common.Hash `json:"transactionsRoot"` TxHash *common.Hash `json:"transactionsRoot"`
ReceiptHash *common.Hash `json:"receiptsRoot"` ReceiptHash *common.Hash `json:"receiptsRoot"`
Bloom types.Bloom `json:"logsBloom"` Bloom types.Bloom `json:"logsBloom"`
Difficulty *math.HexOrDecimal256 `json:"difficulty"` Difficulty *math.HexOrDecimal256 `json:"difficulty"`
Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` Number *math.HexOrDecimal256 `json:"number" gencodec:"required"`
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"`
Extra hexutil.Bytes `json:"extraData"` Extra hexutil.Bytes `json:"extraData"`
MixDigest common.Hash `json:"mixHash"` MixDigest common.Hash `json:"mixHash"`
Nonce *types.BlockNonce `json:"nonce"` Nonce *types.BlockNonce `json:"nonce"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
} }
var enc header var enc header
enc.ParentHash = h.ParentHash enc.ParentHash = h.ParentHash
@ -52,28 +53,30 @@ func (h header) MarshalJSON() ([]byte, error) {
enc.MixDigest = h.MixDigest enc.MixDigest = h.MixDigest
enc.Nonce = h.Nonce enc.Nonce = h.Nonce
enc.BaseFee = (*math.HexOrDecimal256)(h.BaseFee) enc.BaseFee = (*math.HexOrDecimal256)(h.BaseFee)
enc.WithdrawalsHash = h.WithdrawalsHash
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (h *header) UnmarshalJSON(input []byte) error { func (h *header) UnmarshalJSON(input []byte) error {
type header struct { type header struct {
ParentHash *common.Hash `json:"parentHash"` ParentHash *common.Hash `json:"parentHash"`
OmmerHash *common.Hash `json:"sha3Uncles"` OmmerHash *common.Hash `json:"sha3Uncles"`
Coinbase *common.Address `json:"miner"` Coinbase *common.Address `json:"miner"`
Root *common.Hash `json:"stateRoot" gencodec:"required"` Root *common.Hash `json:"stateRoot" gencodec:"required"`
TxHash *common.Hash `json:"transactionsRoot"` TxHash *common.Hash `json:"transactionsRoot"`
ReceiptHash *common.Hash `json:"receiptsRoot"` ReceiptHash *common.Hash `json:"receiptsRoot"`
Bloom *types.Bloom `json:"logsBloom"` Bloom *types.Bloom `json:"logsBloom"`
Difficulty *math.HexOrDecimal256 `json:"difficulty"` Difficulty *math.HexOrDecimal256 `json:"difficulty"`
Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` Number *math.HexOrDecimal256 `json:"number" gencodec:"required"`
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
GasUsed *math.HexOrDecimal64 `json:"gasUsed"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"`
Extra *hexutil.Bytes `json:"extraData"` Extra *hexutil.Bytes `json:"extraData"`
MixDigest *common.Hash `json:"mixHash"` MixDigest *common.Hash `json:"mixHash"`
Nonce *types.BlockNonce `json:"nonce"` Nonce *types.BlockNonce `json:"nonce"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
} }
var dec header var dec header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -131,5 +134,8 @@ func (h *header) UnmarshalJSON(input []byte) error {
if dec.BaseFee != nil { if dec.BaseFee != nil {
h.BaseFee = (*big.Int)(dec.BaseFee) h.BaseFee = (*big.Int)(dec.BaseFee)
} }
if dec.WithdrawalsHash != nil {
h.WithdrawalsHash = dec.WithdrawalsHash
}
return nil return nil
} }

@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
) )
var _ = (*stEnvMarshaling)(nil) var _ = (*stEnvMarshaling)(nil)
@ -29,6 +30,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"`
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
Ommers []ommer `json:"ommers,omitempty"` Ommers []ommer `json:"ommers,omitempty"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
ParentUncleHash common.Hash `json:"parentUncleHash"` ParentUncleHash common.Hash `json:"parentUncleHash"`
} }
@ -46,6 +48,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
enc.ParentTimestamp = math.HexOrDecimal64(s.ParentTimestamp) enc.ParentTimestamp = math.HexOrDecimal64(s.ParentTimestamp)
enc.BlockHashes = s.BlockHashes enc.BlockHashes = s.BlockHashes
enc.Ommers = s.Ommers enc.Ommers = s.Ommers
enc.Withdrawals = s.Withdrawals
enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee)
enc.ParentUncleHash = s.ParentUncleHash enc.ParentUncleHash = s.ParentUncleHash
return json.Marshal(&enc) return json.Marshal(&enc)
@ -67,6 +70,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"`
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
Ommers []ommer `json:"ommers,omitempty"` Ommers []ommer `json:"ommers,omitempty"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"`
ParentUncleHash *common.Hash `json:"parentUncleHash"` ParentUncleHash *common.Hash `json:"parentUncleHash"`
} }
@ -117,6 +121,9 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
if dec.Ommers != nil { if dec.Ommers != nil {
s.Ommers = dec.Ommers s.Ommers = dec.Ommers
} }
if dec.Withdrawals != nil {
s.Withdrawals = dec.Withdrawals
}
if dec.BaseFee != nil { if dec.BaseFee != nil {
s.BaseFee = (*big.Int)(dec.BaseFee) s.BaseFee = (*big.Int)(dec.BaseFee)
} }

@ -262,6 +262,9 @@ func Transition(ctx *cli.Context) error {
return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
} }
} }
if chainConfig.IsShanghai(prestate.Env.Number) && prestate.Env.Withdrawals == nil {
return NewError(ErrorConfig, errors.New("Shanghai config but missing 'withdrawals' in env section"))
}
isMerged := chainConfig.TerminalTotalDifficulty != nil && chainConfig.TerminalTotalDifficulty.BitLen() == 0 isMerged := chainConfig.TerminalTotalDifficulty != nil && chainConfig.TerminalTotalDifficulty.BitLen() == 0
env := prestate.Env env := prestate.Env
if isMerged { if isMerged {

@ -176,6 +176,7 @@ var blockBuilderCommand = &cli.Command{
t8ntool.OutputBlockFlag, t8ntool.OutputBlockFlag,
t8ntool.InputHeaderFlag, t8ntool.InputHeaderFlag,
t8ntool.InputOmmersFlag, t8ntool.InputOmmersFlag,
t8ntool.InputWithdrawalsFlag,
t8ntool.InputTxsRlpFlag, t8ntool.InputTxsRlpFlag,
t8ntool.SealCliqueFlag, t8ntool.SealCliqueFlag,
t8ntool.SealEthashFlag, t8ntool.SealEthashFlag,

@ -251,6 +251,14 @@ func TestT8n(t *testing.T) {
output: t8nOutput{alloc: true, result: true}, output: t8nOutput{alloc: true, result: true},
expOut: "exp.json", 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 := []string{"t8n"}
args = append(args, tc.output.get()...) args = append(args, tc.output.get()...)
@ -391,13 +399,14 @@ func TestT9n(t *testing.T) {
} }
type b11rInput struct { type b11rInput struct {
inEnv string inEnv string
inOmmersRlp string inOmmersRlp string
inTxsRlp string inWithdrawals string
inClique string inTxsRlp string
ethash bool inClique string
ethashMode string ethash bool
ethashDir string ethashMode string
ethashDir string
} }
func (args *b11rInput) get(base string) []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, "--input.ommers")
out = append(out, fmt.Sprintf("%v/%v", base, opt)) 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 != "" { if opt := args.inTxsRlp; opt != "" {
out = append(out, "--input.txs") out = append(out, "--input.txs")
out = append(out, fmt.Sprintf("%v/%v", base, opt)) out = append(out, fmt.Sprintf("%v/%v", base, opt))
@ -480,6 +493,16 @@ func TestB11r(t *testing.T) {
}, },
expOut: "exp.json", 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 := []string{"b11r"}
args = append(args, tc.input.get(tc.base)...) args = append(args, tc.input.get(tc.base)...)

8
cmd/evm/testdata/26/alloc.json vendored Normal file

@ -0,0 +1,8 @@
{
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0x0",
"code": "0x",
"nonce": "0xac",
"storage": {}
}
}

17
cmd/evm/testdata/26/env.json vendored Normal file

@ -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"
}
]
}

20
cmd/evm/testdata/26/exp.json vendored Normal file

@ -0,0 +1,20 @@
{
"alloc": {
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "0x9c7652400",
"nonce": "0xac"
}
},
"result": {
"stateRoot": "0x6e061c2f6513af27d267a0e3b07cb9a10f1ba3a0f65ab648d3a17c36e15021d2",
"txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"receipts": [],
"currentDifficulty": null,
"gasUsed": "0x0",
"currentBaseFee": "0x500",
"withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5"
}
}

1
cmd/evm/testdata/26/txs.json vendored Normal file

@ -0,0 +1 @@
[]

4
cmd/evm/testdata/27/exp.json vendored Normal file

@ -0,0 +1,4 @@
{
"rlp": "0xf90239f9021aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88000000000000000080a04921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5c0c0d9d8424394a94f5374fce5edbc8e2a8697c15331677e6ebf0b2a",
"hash": "0xdc42abd3698499675819e0a85cc1266f16da90277509b867446a6b25fa2b9d87"
}

12
cmd/evm/testdata/27/header.json vendored Normal file

@ -0,0 +1,12 @@
{
"parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e",
"stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x1000",
"number": "0xc3be",
"gasLimit": "0x50785",
"gasUsed": "0x0",
"timestamp": "0x55c5277e",
"mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf",
"withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5"
}

1
cmd/evm/testdata/27/ommers.json vendored Normal file

@ -0,0 +1 @@
[]

1
cmd/evm/testdata/27/txs.rlp vendored Normal file

@ -0,0 +1 @@
"c0"

8
cmd/evm/testdata/27/withdrawals.json vendored Normal file

@ -0,0 +1,8 @@
[
{
"index": "0x42",
"validatorIndex": "0x43",
"address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"amount": "0x2a"
}
]

@ -257,7 +257,18 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return consensus.ErrInvalidNumber return consensus.ErrInvalidNumber
} }
// Verify the header's EIP-1559 attributes. // Verify the header's EIP-1559 attributes.
return misc.VerifyEip1559Header(chain.Config(), parent, header) if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil {
return err
}
// Verify existence / non-existence of withdrawalsHash.
shanghai := chain.Config().IsShanghai(header.Time)
if shanghai && header.WithdrawalsHash == nil {
return fmt.Errorf("missing withdrawalsHash")
}
if !shanghai && header.WithdrawalsHash != nil {
return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash)
}
return nil
} }
// verifyHeaders is similar to verifyHeader, but verifies a batch of headers // verifyHeaders is similar to verifyHeader, but verifies a batch of headers
@ -316,13 +327,20 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H
} }
// Finalize implements consensus.Engine, setting the final state on the header // 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 // Finalize is different with Prepare, it can be used in both block generation
// and verification. So determine the consensus rules by header type. // and verification. So determine the consensus rules by header type.
if !beacon.IsPoSHeader(header) { if !beacon.IsPoSHeader(header) {
beacon.ethone.Finalize(chain, header, state, txs, uncles) beacon.ethone.Finalize(chain, header, state, txs, uncles, nil)
return return
} }
// Withdrawals processing.
for _, w := range withdrawals {
// Convert amount from gwei to wei.
amount := new(big.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, big.NewInt(params.GWei))
state.AddBalance(w.Address, amount)
}
// The block reward is no longer handled here. It's done by the // The block reward is no longer handled here. It's done by the
// external consensus engine. // external consensus engine.
header.Root = state.IntermediateRoot(true) header.Root = state.IntermediateRoot(true)
@ -330,15 +348,26 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// FinalizeAndAssemble implements consensus.Engine, setting the final state and // FinalizeAndAssemble implements consensus.Engine, setting the final state and
// assembling the block. // 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 // FinalizeAndAssemble is different with Prepare, it can be used in both block
// generation and verification. So determine the consensus rules by header type. // generation and verification. So determine the consensus rules by header type.
if !beacon.IsPoSHeader(header) { 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 shanghai := chain.Config().IsShanghai(header.Time)
beacon.Finalize(chain, header, state, txs, uncles) if shanghai {
return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil // All blocks after Shanghai must include a withdrawals root.
if withdrawals == nil {
withdrawals = make([]*types.Withdrawal, 0)
}
} else {
if len(withdrawals) > 0 {
return nil, errors.New("withdrawals set before Shanghai activation")
}
}
// Finalize and assemble the block.
beacon.Finalize(chain, header, state, txs, uncles, withdrawals)
return types.NewBlockWithWithdrawals(header, txs, uncles, receipts, withdrawals, trie.NewStackTrie(nil)), nil
} }
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes

41
consensus/beacon/faker.go Normal file

@ -0,0 +1,41 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package beacon
import (
"math/big"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types"
)
// NewFaker creates a fake consensus engine for testing.
// The fake engine simulates a merged network.
// It can not be used to test the merge transition.
// This type is needed since the fakeChainReader can not be used with
// a normal beacon consensus engine.
func NewFaker() consensus.Engine {
return new(faker)
}
type faker struct {
Beacon
}
func (f *faker) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
return beaconDifficulty
}

@ -298,6 +298,9 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H
if header.GasLimit > params.MaxGasLimit { if header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
} }
if chain.Config().IsShanghai(header.Time) {
return fmt.Errorf("clique does not support shanghai fork")
}
// If all checks passed, validate any special fields for hard forks // If all checks passed, validate any special fields for hard forks
if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil { if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil {
return err return err
@ -564,7 +567,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block // Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given. // 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 // 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.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.UncleHash = types.CalcUncleHash(nil) header.UncleHash = types.CalcUncleHash(nil)
@ -572,9 +575,13 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
// nor block rewards given, and returns the final block. // nor block rewards given, and returns the final block.
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) {
if len(withdrawals) > 0 {
return nil, errors.New("clique does not support withdrawals")
}
// Finalize block // 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 // Assemble and return the final block for sealing
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil
@ -743,6 +750,9 @@ func encodeSigHeader(w io.Writer, header *types.Header) {
if header.BaseFee != nil { if header.BaseFee != nil {
enc = append(enc, header.BaseFee) enc = append(enc, header.BaseFee)
} }
if header.WithdrawalsHash != nil {
panic("unexpected withdrawal hash value in clique")
}
if err := rlp.Encode(w, enc); err != nil { if err := rlp.Encode(w, enc); err != nil {
panic("can't encode: " + err.Error()) panic("can't encode: " + err.Error())
} }

@ -90,7 +90,7 @@ type Engine interface {
// Note: The block header and state database might be updated to reflect any // Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards). // consensus rules that happen at finalization (e.g. block rewards).
Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, 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 // FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
// rewards) and assembles the final 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 // Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards). // consensus rules that happen at finalization (e.g. block rewards).
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, 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 // Seal generates a new sealing request for the given input block and pushes
// the result into the given channel. // the result into the given channel.

@ -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 { if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
return consensus.ErrInvalidNumber return consensus.ErrInvalidNumber
} }
if chain.Config().IsShanghai(header.Time) {
return fmt.Errorf("ethash does not support shanghai fork")
}
// Verify the engine specific seal securing the block // Verify the engine specific seal securing the block
if seal { if seal {
if err := ethash.verifySeal(chain, header, false); err != nil { if err := ethash.verifySeal(chain, header, false); err != nil {
@ -597,7 +600,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H
// Finalize implements consensus.Engine, accumulating the block and uncle rewards, // Finalize implements consensus.Engine, accumulating the block and uncle rewards,
// setting the final state on the header // 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 // Accumulate any block and uncle rewards and commit the final state root
accumulateRewards(chain.Config(), state, header, uncles) accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
@ -605,10 +608,13 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and // FinalizeAndAssemble implements consensus.Engine, accumulating the block and
// uncle rewards, setting the final state and assembling the block. // 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 if len(withdrawals) > 0 {
ethash.Finalize(chain, header, state, txs, uncles) return nil, errors.New("ethash does not support withdrawals")
}
// Finalize block
ethash.Finalize(chain, header, state, txs, uncles, nil)
// Header seems complete, assemble into a block and return // Header seems complete, assemble into a block and return
return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil
} }
@ -635,6 +641,9 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
if header.BaseFee != nil { if header.BaseFee != nil {
enc = append(enc, header.BaseFee) enc = append(enc, header.BaseFee)
} }
if header.WithdrawalsHash != nil {
panic("withdrawal hash set on ethash")
}
rlp.Encode(hasher, enc) rlp.Encode(hasher, enc)
hasher.Sum(hash[:0]) hasher.Sum(hash[:0])
return hash return hash

@ -8,46 +8,53 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
) )
var _ = (*payloadAttributesMarshaling)(nil) var _ = (*payloadAttributesMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) { func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
type PayloadAttributesV1 struct { type PayloadAttributes struct {
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" 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.Timestamp = hexutil.Uint64(p.Timestamp)
enc.Random = p.Random enc.Random = p.Random
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error { func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
type PayloadAttributesV1 struct { type PayloadAttributes struct {
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random *common.Hash `json:"prevRandao" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" 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 { if err := json.Unmarshal(input, &dec); err != nil {
return err return err
} }
if dec.Timestamp == nil { 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) p.Timestamp = uint64(*dec.Timestamp)
if dec.Random == nil { 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 p.Random = *dec.Random
if dec.SuggestedFeeRecipient == nil { 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 p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient
if dec.Withdrawals != nil {
p.Withdrawals = dec.Withdrawals
}
return nil return nil
} }

@ -9,29 +9,31 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
) )
var _ = (*executableDataMarshaling)(nil) var _ = (*executableDataMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (e ExecutableDataV1) MarshalJSON() ([]byte, error) { func (e ExecutableData) MarshalJSON() ([]byte, error) {
type ExecutableDataV1 struct { type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
} }
var enc ExecutableDataV1 var enc ExecutableData
enc.ParentHash = e.ParentHash enc.ParentHash = e.ParentHash
enc.FeeRecipient = e.FeeRecipient enc.FeeRecipient = e.FeeRecipient
enc.StateRoot = e.StateRoot enc.StateRoot = e.StateRoot
@ -51,89 +53,94 @@ func (e ExecutableDataV1) MarshalJSON() ([]byte, error) {
enc.Transactions[k] = v enc.Transactions[k] = v
} }
} }
enc.Withdrawals = e.Withdrawals
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error { func (e *ExecutableData) UnmarshalJSON(input []byte) error {
type ExecutableDataV1 struct { type ExecutableData struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random *common.Hash `json:"prevRandao" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"`
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash *common.Hash `json:"blockHash" gencodec:"required"` BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
} }
var dec ExecutableDataV1 var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
return err return err
} }
if dec.ParentHash == nil { 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 e.ParentHash = *dec.ParentHash
if dec.FeeRecipient == nil { 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 e.FeeRecipient = *dec.FeeRecipient
if dec.StateRoot == nil { 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 e.StateRoot = *dec.StateRoot
if dec.ReceiptsRoot == nil { 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 e.ReceiptsRoot = *dec.ReceiptsRoot
if dec.LogsBloom == nil { 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 e.LogsBloom = *dec.LogsBloom
if dec.Random == nil { 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 e.Random = *dec.Random
if dec.Number == nil { 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) e.Number = uint64(*dec.Number)
if dec.GasLimit == nil { 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) e.GasLimit = uint64(*dec.GasLimit)
if dec.GasUsed == nil { 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) e.GasUsed = uint64(*dec.GasUsed)
if dec.Timestamp == nil { 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) e.Timestamp = uint64(*dec.Timestamp)
if dec.ExtraData == nil { 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 e.ExtraData = *dec.ExtraData
if dec.BaseFeePerGas == nil { 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) e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas)
if dec.BlockHash == nil { 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 e.BlockHash = *dec.BlockHash
if dec.Transactions == nil { 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)) e.Transactions = make([][]byte, len(dec.Transactions))
for k, v := range dec.Transactions { for k, v := range dec.Transactions {
e.Transactions[k] = v e.Transactions[k] = v
} }
if dec.Withdrawals != nil {
e.Withdrawals = dec.Withdrawals
}
return nil return nil
} }

46
core/beacon/gen_epe.go Normal file

@ -0,0 +1,46 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package beacon
import (
"encoding/json"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*executionPayloadEnvelopeMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
}
var enc ExecutionPayloadEnvelope
enc.ExecutionPayload = e.ExecutionPayload
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
}
var dec ExecutionPayloadEnvelope
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.ExecutionPayload == nil {
return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope")
}
e.ExecutionPayload = dec.ExecutionPayload
if dec.BlockValue == nil {
return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope")
}
e.BlockValue = (*big.Int)(dec.BlockValue)
return nil
}

@ -26,38 +26,41 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go //go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
// PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74 // PayloadAttributes describes the environment context in which a block should
type PayloadAttributesV1 struct { // be built.
Timestamp uint64 `json:"timestamp" gencodec:"required"` type PayloadAttributes struct {
Random common.Hash `json:"prevRandao" gencodec:"required"` Timestamp uint64 `json:"timestamp" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" 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 { type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64 Timestamp hexutil.Uint64
} }
//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go //go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go
// ExecutableDataV1 structure described at https://github.com/ethereum/execution-apis/tree/main/src/engine/specification.md // ExecutableData is the data necessary to execute an EL payload.
type ExecutableDataV1 struct { type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"` LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"` Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"` Timestamp uint64 `json:"timestamp" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"` ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"` Transactions [][]byte `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
} }
// JSON type overrides for executableData. // JSON type overrides for executableData.
@ -72,6 +75,18 @@ type executableDataMarshaling struct {
Transactions []hexutil.Bytes Transactions []hexutil.Bytes
} }
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
}
// JSON type overrides for ExecutionPayloadEnvelope.
type executionPayloadEnvelopeMarshaling struct {
BlockValue *hexutil.Big
}
type PayloadStatusV1 struct { type PayloadStatusV1 struct {
Status string `json:"status"` Status string `json:"status"`
LatestValidHash *common.Hash `json:"latestValidHash"` LatestValidHash *common.Hash `json:"latestValidHash"`
@ -141,8 +156,10 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// uncleHash = emptyUncleHash // uncleHash = emptyUncleHash
// difficulty = 0 // difficulty = 0
// //
// and that the blockhash of the constructed block matches the parameters. // and that the blockhash of the constructed block matches the parameters. Nil
func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { // 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) txs, err := decodeTransactions(params.Transactions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -157,34 +174,43 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) { if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas) return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
} }
header := &types.Header{ // Only set withdrawalsRoot if it is non-nil. This allows CLs to use
ParentHash: params.ParentHash, // ExecutableData before withdrawals are enabled by marshaling
UncleHash: types.EmptyUncleHash, // Withdrawals as the json null value.
Coinbase: params.FeeRecipient, var withdrawalsRoot *common.Hash
Root: params.StateRoot, if params.Withdrawals != nil {
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), h := types.DeriveSha(types.Withdrawals(params.Withdrawals), trie.NewStackTrie(nil))
ReceiptHash: params.ReceiptsRoot, withdrawalsRoot = &h
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,
} }
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) header := &types.Header{
ParentHash: params.ParentHash,
UncleHash: types.EmptyUncleHash,
Coinbase: params.FeeRecipient,
Root: params.StateRoot,
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
ReceiptHash: params.ReceiptsRoot,
Bloom: types.BytesToBloom(params.LogsBloom),
Difficulty: common.Big0,
Number: new(big.Int).SetUint64(params.Number),
GasLimit: params.GasLimit,
GasUsed: params.GasUsed,
Time: params.Timestamp,
BaseFee: params.BaseFeePerGas,
Extra: params.ExtraData,
MixDigest: params.Random,
WithdrawalsHash: withdrawalsRoot,
}
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
if block.Hash() != params.BlockHash { if block.Hash() != params.BlockHash {
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
} }
return block, nil 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. // fields from the given block. It assumes the given block is post-merge block.
func BlockToExecutableData(block *types.Block) *ExecutableDataV1 { func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadEnvelope {
return &ExecutableDataV1{ data := &ExecutableData{
BlockHash: block.Hash(), BlockHash: block.Hash(),
ParentHash: block.ParentHash(), ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(), FeeRecipient: block.Coinbase(),
@ -199,5 +225,7 @@ func BlockToExecutableData(block *types.Block) *ExecutableDataV1 {
Transactions: encodeTransactions(block.Transactions()), Transactions: encodeTransactions(block.Transactions()),
Random: block.MixDigest(), Random: block.MixDigest(),
ExtraData: block.Extra(), ExtraData: block.Extra(),
Withdrawals: block.Withdrawals(),
} }
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees}
} }

@ -65,6 +65,11 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash { 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) return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
} }
if header.WithdrawalsHash != nil {
if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash {
return fmt.Errorf("withdrawals root hash mismatch: have %x, want %x", hash, *header.WithdrawalsHash)
}
}
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
return consensus.ErrUnknownAncestor return consensus.ErrUnknownAncestor

@ -4229,7 +4229,7 @@ func TestEIP3651(t *testing.T) {
var ( var (
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb")
engine = ethash.NewFaker() engine = beacon.NewFaker()
// A sender who makes transactions, has some funds // A sender who makes transactions, has some funds
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -4237,8 +4237,9 @@ func TestEIP3651(t *testing.T) {
addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey)
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
config = *params.AllEthashProtocolChanges
gspec = &Genesis{ gspec = &Genesis{
Config: params.AllEthashProtocolChanges, Config: &config,
Alloc: GenesisAlloc{ Alloc: GenesisAlloc{
addr1: {Balance: funds}, addr1: {Balance: funds},
addr2: {Balance: funds}, addr2: {Balance: funds},
@ -4275,6 +4276,8 @@ func TestEIP3651(t *testing.T) {
gspec.Config.BerlinBlock = common.Big0 gspec.Config.BerlinBlock = common.Big0
gspec.Config.LondonBlock = common.Big0 gspec.Config.LondonBlock = common.Big0
gspec.Config.TerminalTotalDifficulty = common.Big0
gspec.Config.TerminalTotalDifficultyPassed = true
gspec.Config.ShanghaiTime = u64(0) gspec.Config.ShanghaiTime = u64(0)
signer := types.LatestSigner(gspec.Config) signer := types.LatestSigner(gspec.Config)
@ -4317,10 +4320,7 @@ func TestEIP3651(t *testing.T) {
// 3: Ensure that miner received only the tx's tip. // 3: Ensure that miner received only the tx's tip.
actual := state.GetBalance(block.Coinbase()) actual := state.GetBalance(block.Coinbase())
expected := new(big.Int).Add( expected := new(big.Int).SetUint64(block.GasUsed() * block.Transactions()[0].GasTipCap().Uint64())
new(big.Int).SetUint64(block.GasUsed()*block.Transactions()[0].GasTipCap().Uint64()),
ethash.ConstantinopleBlockReward,
)
if actual.Cmp(expected) != 0 { if actual.Cmp(expected) != 0 {
t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual)
} }

@ -41,10 +41,11 @@ type BlockGen struct {
header *types.Header header *types.Header
statedb *state.StateDB statedb *state.StateDB
gasPool *GasPool gasPool *GasPool
txs []*types.Transaction txs []*types.Transaction
receipts []*types.Receipt receipts []*types.Receipt
uncles []*types.Header uncles []*types.Header
withdrawals []*types.Withdrawal
config *params.ChainConfig config *params.ChainConfig
engine consensus.Engine engine consensus.Engine
@ -205,6 +206,26 @@ func (b *BlockGen) AddUncle(h *types.Header) {
b.uncles = append(b.uncles, h) b.uncles = append(b.uncles, h)
} }
// AddWithdrawal adds a withdrawal to the generated block.
func (b *BlockGen) AddWithdrawal(w *types.Withdrawal) {
// The withdrawal will be assigned the next valid index.
var idx uint64
for i := b.i - 1; i >= 0; i-- {
if wd := b.chain[i].Withdrawals(); len(wd) != 0 {
idx = wd[len(wd)-1].Index + 1
break
}
if i == 0 {
// Correctly set the index if no parent had withdrawals
if wd := b.parent.Withdrawals(); len(wd) != 0 {
idx = wd[len(wd)-1].Index + 1
}
}
}
w.Index = idx
b.withdrawals = append(b.withdrawals, w)
}
// PrevBlock returns a previously generated block by number. It panics if // PrevBlock returns a previously generated block by number. It panics if
// num is greater or equal to the number of the block being generated. // num is greater or equal to the number of the block being generated.
// For index -1, PrevBlock returns the parent block given to GenerateChain. // For index -1, PrevBlock returns the parent block given to GenerateChain.
@ -281,8 +302,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
gen(i, b) gen(i, b)
} }
if b.engine != nil { if b.engine != nil {
// Finalize and seal the block block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals)
block, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts) if err != nil {
panic(err)
}
// Write state changes to db // Write state changes to db
root, err := statedb.Commit(config.IsEIP158(b.header.Number)) root, err := statedb.Commit(config.IsEIP158(b.header.Number))

@ -469,6 +469,9 @@ func (g *Genesis) ToBlock() *types.Block {
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee) head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
} }
} }
if g.Config != nil && g.Config.IsShanghai(g.Timestamp) {
head.WithdrawalsHash = &types.EmptyRootHash
}
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
} }

@ -760,7 +760,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
if body == nil { if body == nil {
return nil return nil
} }
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles) return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals)
} }
// WriteBlock serializes a block into the database, header and body separately. // WriteBlock serializes a block into the database, header and body separately.
@ -860,7 +860,7 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
} }
for _, bad := range badBlocks { for _, bad := range badBlocks {
if bad.Header.Hash() == hash { if bad.Header.Hash() == hash {
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles) return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals)
} }
} }
return nil return nil
@ -879,7 +879,7 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
} }
var blocks []*types.Block var blocks []*types.Block
for _, bad := range badBlocks { for _, bad := range badBlocks {
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)) blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals))
} }
return blocks return blocks
} }

@ -86,8 +86,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
receipts = append(receipts, receipt) receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
} }
// Fail if Shanghai not enabled and len(withdrawals) is non-zero.
withdrawals := block.Withdrawals()
if len(withdrawals) > 0 && !p.config.IsShanghai(block.Time()) {
return nil, nil, 0, fmt.Errorf("withdrawals before shanghai")
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals)
return receipts, allLogs, *usedGas, nil return receipts, allLogs, *usedGas, nil
} }

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -314,22 +315,24 @@ func TestStateProcessorErrors(t *testing.T) {
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
gspec = &Genesis{ gspec = &Genesis{
Config: &params.ChainConfig{ Config: &params.ChainConfig{
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0), EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0), EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0), EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0), ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0), BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0),
MergeNetsplitBlock: big.NewInt(0), MergeNetsplitBlock: big.NewInt(0),
ShanghaiTime: u64(0), TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
ShanghaiTime: u64(0),
}, },
Alloc: GenesisAlloc{ Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
@ -339,7 +342,7 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
} }
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil)
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
smallInitCode = [320]byte{} smallInitCode = [320]byte{}
) )
@ -361,7 +364,7 @@ func TestStateProcessorErrors(t *testing.T) {
want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300", want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300",
}, },
} { } {
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block}) _, err := blockchain.InsertChain(types.Blocks{block})
if err == nil { if err == nil {
t.Fatal("block imported without errors") t.Fatal("block imported without errors")
@ -378,23 +381,31 @@ func TestStateProcessorErrors(t *testing.T) {
// valid to be considered for import: // valid to be considered for import:
// - valid pow (fake), ancestry, difficulty, gaslimit etc // - valid pow (fake), ancestry, difficulty, gaslimit etc
func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block { func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block {
header := &types.Header{ difficulty := big.NewInt(0)
ParentHash: parent.Hash(), if !config.TerminalTotalDifficultyPassed {
Coinbase: parent.Coinbase(), difficulty = engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
Number: parent.Number(), Number: parent.Number(),
Time: parent.Time(), Time: parent.Time(),
Difficulty: parent.Difficulty(), Difficulty: parent.Difficulty(),
UncleHash: parent.UncleHash(), UncleHash: parent.UncleHash(),
}), })
GasLimit: parent.GasLimit(), }
Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: parent.Time() + 10, header := &types.Header{
UncleHash: types.EmptyUncleHash, ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: difficulty,
GasLimit: parent.GasLimit(),
Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: parent.Time() + 10,
UncleHash: types.EmptyUncleHash,
} }
if config.IsLondon(header.Number) { if config.IsLondon(header.Number) {
header.BaseFee = misc.CalcBaseFee(config, parent.Header()) header.BaseFee = misc.CalcBaseFee(config, parent.Header())
} }
if config.IsShanghai(header.Time) {
header.WithdrawalsHash = &types.EmptyRootHash
}
var receipts []*types.Receipt var receipts []*types.Receipt
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there // The post-state result doesn't need to be correct (this is a bad block), but we do need something there
// Preferably something unique. So let's use a combo of blocknum + txhash // Preferably something unique. So let's use a combo of blocknum + txhash

@ -87,6 +87,9 @@ type Header struct {
// BaseFee was added by EIP-1559 and is ignored in legacy headers. // BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` 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 TODO (MariusVanDerWijden) Add this field once needed
// Random was added during the merge and contains the BeaconState randomness // Random was added during the merge and contains the BeaconState randomness
@ -149,9 +152,12 @@ func (h *Header) SanityCheck() error {
} }
// EmptyBody returns true if there is no additional 'body' to complete the header // 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 { 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. // EmptyReceipts returns true if there are no receipts for this header/block.
@ -164,6 +170,7 @@ func (h *Header) EmptyReceipts() bool {
type Body struct { type Body struct {
Transactions []*Transaction Transactions []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"`
} }
// Block represents an entire block in the Ethereum blockchain. // Block represents an entire block in the Ethereum blockchain.
@ -171,6 +178,7 @@ type Block struct {
header *Header header *Header
uncles []*Header uncles []*Header
transactions Transactions transactions Transactions
withdrawals Withdrawals
// caches // caches
hash atomic.Value hash atomic.Value
@ -184,9 +192,10 @@ type Block struct {
// "external" block encoding. used for eth protocol, etc. // "external" block encoding. used for eth protocol, etc.
type extblock struct { type extblock struct {
Header *Header Header *Header
Txs []*Transaction Txs []*Transaction
Uncles []*Header Uncles []*Header
Withdrawals []*Withdrawal `rlp:"optional"`
} }
// NewBlock creates a new block. The input data is copied, // NewBlock creates a new block. The input data is copied,
@ -228,6 +237,28 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
return b return b
} }
// NewBlockWithWithdrawals creates a new block with withdrawals. The input data
// is copied, changes to header and to the field values will not
// affect the block.
//
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
// are ignored and set to values derived from the given txs, uncles
// and receipts.
func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block {
b := NewBlock(header, txs, uncles, receipts, hasher)
if withdrawals == nil {
b.header.WithdrawalsHash = nil
} else if len(withdrawals) == 0 {
b.header.WithdrawalsHash = &EmptyRootHash
} else {
h := DeriveSha(Withdrawals(withdrawals), hasher)
b.header.WithdrawalsHash = &h
}
return b.WithWithdrawals(withdrawals)
}
// NewBlockWithHeader creates a block with the given header data. The // NewBlockWithHeader creates a block with the given header data. The
// header data is copied, changes to header and to the field values // header data is copied, changes to header and to the field values
// will not affect the block. // will not affect the block.
@ -252,6 +283,9 @@ func CopyHeader(h *Header) *Header {
cpy.Extra = make([]byte, len(h.Extra)) cpy.Extra = make([]byte, len(h.Extra))
copy(cpy.Extra, h.Extra) copy(cpy.Extra, h.Extra)
} }
if h.WithdrawalsHash != nil {
*cpy.WithdrawalsHash = *h.WithdrawalsHash
}
return &cpy return &cpy
} }
@ -262,7 +296,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
if err := s.Decode(&eb); err != nil { if err := s.Decode(&eb); err != nil {
return err 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)) b.size.Store(rlp.ListSize(size))
return nil return nil
} }
@ -270,9 +304,10 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
// EncodeRLP serializes b into the Ethereum RLP block format. // EncodeRLP serializes b into the Ethereum RLP block format.
func (b *Block) EncodeRLP(w io.Writer) error { func (b *Block) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, extblock{ return rlp.Encode(w, extblock{
Header: b.header, Header: b.header,
Txs: b.transactions, Txs: b.transactions,
Uncles: b.uncles, Uncles: b.uncles,
Withdrawals: b.withdrawals,
}) })
} }
@ -315,10 +350,14 @@ func (b *Block) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee) return new(big.Int).Set(b.header.BaseFee)
} }
func (b *Block) Withdrawals() Withdrawals {
return b.withdrawals
}
func (b *Block) Header() *Header { return CopyHeader(b.header) } func (b *Block) Header() *Header { return CopyHeader(b.header) }
// Body returns the non-header content of the block. // 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 // Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previously cached value. // and returning it, or returning a previously cached value.
@ -361,6 +400,7 @@ func (b *Block) WithSeal(header *Header) *Block {
header: &cpy, header: &cpy,
transactions: b.transactions, transactions: b.transactions,
uncles: b.uncles, uncles: b.uncles,
withdrawals: b.withdrawals,
} }
} }
@ -378,6 +418,15 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
return block return block
} }
// WithWithdrawals sets the withdrawal contents of a block, does not return a new block.
func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
if withdrawals != nil {
b.withdrawals = make([]*Withdrawal, len(withdrawals))
copy(b.withdrawals, withdrawals)
}
return b
}
// Hash returns the keccak256 hash of b's header. // Hash returns the keccak256 hash of b's header.
// The hash is computed on the first call and cached thereafter. // The hash is computed on the first call and cached thereafter.
func (b *Block) Hash() common.Hash { func (b *Block) Hash() common.Hash {

@ -16,23 +16,24 @@ var _ = (*headerMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (h Header) MarshalJSON() ([]byte, error) { func (h Header) MarshalJSON() ([]byte, error) {
type Header struct { type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner"` Coinbase common.Address `json:"miner"`
Root common.Hash `json:"stateRoot" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
Number *hexutil.Big `json:"number" gencodec:"required"` Number *hexutil.Big `json:"number" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Extra hexutil.Bytes `json:"extraData" gencodec:"required"` Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash"` MixDigest common.Hash `json:"mixHash"`
Nonce BlockNonce `json:"nonce"` Nonce BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
Hash common.Hash `json:"hash"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
enc.ParentHash = h.ParentHash enc.ParentHash = h.ParentHash
@ -51,6 +52,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.MixDigest = h.MixDigest enc.MixDigest = h.MixDigest
enc.Nonce = h.Nonce enc.Nonce = h.Nonce
enc.BaseFee = (*hexutil.Big)(h.BaseFee) enc.BaseFee = (*hexutil.Big)(h.BaseFee)
enc.WithdrawalsHash = h.WithdrawalsHash
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -58,22 +60,23 @@ func (h Header) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (h *Header) UnmarshalJSON(input []byte) error { func (h *Header) UnmarshalJSON(input []byte) error {
type Header struct { type Header struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase *common.Address `json:"miner"` Coinbase *common.Address `json:"miner"`
Root *common.Hash `json:"stateRoot" gencodec:"required"` Root *common.Hash `json:"stateRoot" gencodec:"required"`
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom *Bloom `json:"logsBloom" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
Number *hexutil.Big `json:"number" gencodec:"required"` Number *hexutil.Big `json:"number" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest *common.Hash `json:"mixHash"` MixDigest *common.Hash `json:"mixHash"`
Nonce *BlockNonce `json:"nonce"` Nonce *BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -139,5 +142,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.BaseFee != nil { if dec.BaseFee != nil {
h.BaseFee = (*big.Int)(dec.BaseFee) h.BaseFee = (*big.Int)(dec.BaseFee)
} }
if dec.WithdrawalsHash != nil {
h.WithdrawalsHash = dec.WithdrawalsHash
}
return nil return nil
} }

@ -41,7 +41,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBytes(obj.MixDigest[:]) w.WriteBytes(obj.MixDigest[:])
w.WriteBytes(obj.Nonce[:]) w.WriteBytes(obj.Nonce[:])
_tmp1 := obj.BaseFee != nil _tmp1 := obj.BaseFee != nil
if _tmp1 { _tmp2 := obj.WithdrawalsHash != nil
if _tmp1 || _tmp2 {
if obj.BaseFee == nil { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
} else { } else {
@ -51,6 +52,13 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee) w.WriteBigInt(obj.BaseFee)
} }
} }
if _tmp2 {
if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.WithdrawalsHash[:])
}
}
w.ListEnd(_tmp0) w.ListEnd(_tmp0)
return w.Flush() return w.Flush()
} }

@ -0,0 +1,55 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package types
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*withdrawalMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (w Withdrawal) MarshalJSON() ([]byte, error) {
type Withdrawal struct {
Index hexutil.Uint64 `json:"index"`
Validator hexutil.Uint64 `json:"validatorIndex"`
Address common.Address `json:"address"`
Amount hexutil.Uint64 `json:"amount"`
}
var enc Withdrawal
enc.Index = hexutil.Uint64(w.Index)
enc.Validator = hexutil.Uint64(w.Validator)
enc.Address = w.Address
enc.Amount = hexutil.Uint64(w.Amount)
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (w *Withdrawal) UnmarshalJSON(input []byte) error {
type Withdrawal struct {
Index *hexutil.Uint64 `json:"index"`
Validator *hexutil.Uint64 `json:"validatorIndex"`
Address *common.Address `json:"address"`
Amount *hexutil.Uint64 `json:"amount"`
}
var dec Withdrawal
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Index != nil {
w.Index = uint64(*dec.Index)
}
if dec.Validator != nil {
w.Validator = uint64(*dec.Validator)
}
if dec.Address != nil {
w.Address = *dec.Address
}
if dec.Amount != nil {
w.Amount = uint64(*dec.Amount)
}
return nil
}

@ -0,0 +1,20 @@
// Code generated by rlpgen. DO NOT EDIT.
//go:build !norlpgen
// +build !norlpgen
package types
import "github.com/ethereum/go-ethereum/rlp"
import "io"
func (obj *Withdrawal) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteUint64(obj.Index)
w.WriteUint64(obj.Validator)
w.WriteBytes(obj.Address[:])
w.WriteUint64(obj.Amount)
w.ListEnd(_tmp0)
return w.Flush()
}

56
core/types/withdrawal.go Normal file

@ -0,0 +1,56 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"bytes"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
)
//go:generate go run github.com/fjl/gencodec -type Withdrawal -field-override withdrawalMarshaling -out gen_withdrawal_json.go
//go:generate go run ../../rlp/rlpgen -type Withdrawal -out gen_withdrawal_rlp.go
// Withdrawal represents a validator withdrawal from the consensus layer.
type Withdrawal struct {
Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer
Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal
Address common.Address `json:"address"` // target address for withdrawn ether
Amount uint64 `json:"amount"` // value of withdrawal in Gwei
}
// field type overrides for gencodec
type withdrawalMarshaling struct {
Index hexutil.Uint64
Validator hexutil.Uint64
Amount hexutil.Uint64
}
// Withdrawals implements DerivableList for withdrawals.
type Withdrawals []*Withdrawal
// Len returns the length of s.
func (s Withdrawals) Len() int { return len(s) }
// EncodeIndex encodes the i'th withdrawal to w. Note that this does not check for errors
// because we assume that *Withdrawal will only ever contain valid withdrawals that were either
// constructed by decoding or via public API in this package.
func (s Withdrawals) EncodeIndex(i int, w *bytes.Buffer) {
rlp.Encode(w, s[i])
}

@ -119,7 +119,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
cfg.State.CreateAccount(address) cfg.State.CreateAccount(address)
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code) cfg.State.SetCode(address, code)
@ -153,7 +152,6 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
// Call the code with the given configuration. // Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create( code, address, leftOverGas, err := vmenv.Create(
sender, sender,

@ -155,7 +155,19 @@ func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
// //
// If there are payloadAttributes: we try to assemble a block with the payloadAttributes // If there are payloadAttributes: we try to assemble a block with the payloadAttributes
// and return its payloadID. // 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() api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock() defer api.forkchoiceLock.Unlock()
@ -285,6 +297,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
Timestamp: payloadAttributes.Timestamp, Timestamp: payloadAttributes.Timestamp,
FeeRecipient: payloadAttributes.SuggestedFeeRecipient, FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
Random: payloadAttributes.Random, Random: payloadAttributes.Random,
Withdrawals: payloadAttributes.Withdrawals,
} }
id := args.Id() id := args.Id()
// If we already are busy generating this work, then we do not need // If we already are busy generating this work, then we do not need
@ -334,7 +347,20 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.Transit
} }
// GetPayloadV1 returns a cached payload by id. // GetPayloadV1 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableData, error) {
data, err := api.getPayload(payloadID)
if err != nil {
return nil, err
}
return data.ExecutionPayload, nil
}
// GetPayloadV2 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV2(payloadID beacon.PayloadID) (*beacon.ExecutionPayloadEnvelope, error) {
return api.getPayload(payloadID)
}
func (api *ConsensusAPI) getPayload(payloadID beacon.PayloadID) (*beacon.ExecutionPayloadEnvelope, error) {
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
data := api.localBlocks.get(payloadID) data := api.localBlocks.get(payloadID)
if data == nil { if data == nil {
@ -344,7 +370,19 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu
} }
// NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // 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: // 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 // 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
@ -361,7 +399,7 @@ func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.Pa
api.newPayloadLock.Lock() api.newPayloadLock.Lock()
defer api.newPayloadLock.Unlock() 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) block, err := beacon.ExecutableDataToBlock(params)
if err != nil { if err != nil {
log.Debug("Invalid NewPayload params", "params", params, "error", err) log.Debug("Invalid NewPayload params", "params", params, "error", err)

@ -18,6 +18,7 @@ package catalyst
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"math/big" "math/big"
"sync" "sync"
@ -26,6 +27,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
beaconConsensus "github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/beacon"
@ -38,6 +41,7 @@ import (
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -51,8 +55,14 @@ var (
testBalance = big.NewInt(2e18) 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 config := *params.AllEthashProtocolChanges
engine := consensus.Engine(beaconConsensus.New(ethash.NewFaker()))
if merged {
config.TerminalTotalDifficulty = common.Big0
config.TerminalTotalDifficultyPassed = true
engine = beaconConsensus.NewFaker()
}
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: &config, Config: &config,
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
@ -69,17 +79,21 @@ func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) {
g.AddTx(tx) g.AddTx(tx)
testNonce++ testNonce++
} }
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), n, generate) _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate)
totalDifficulty := big.NewInt(0)
for _, b := range blocks { if !merged {
totalDifficulty.Add(totalDifficulty, b.Difficulty()) totalDifficulty := big.NewInt(0)
for _, b := range blocks {
totalDifficulty.Add(totalDifficulty, b.Difficulty())
}
config.TerminalTotalDifficulty = totalDifficulty
} }
config.TerminalTotalDifficulty = totalDifficulty
return genesis, blocks return genesis, blocks
} }
func TestEth2AssembleBlock(t *testing.T) { func TestEth2AssembleBlock(t *testing.T) {
genesis, blocks := generatePreMergeChain(10) genesis, blocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, blocks) n, ethservice := startEthService(t, genesis, blocks)
defer n.Close() defer n.Close()
@ -90,7 +104,7 @@ func TestEth2AssembleBlock(t *testing.T) {
t.Fatalf("error signing transaction, err=%v", err) t.Fatalf("error signing transaction, err=%v", err)
} }
ethservice.TxPool().AddLocal(tx) ethservice.TxPool().AddLocal(tx)
blockParams := beacon.PayloadAttributesV1{ blockParams := beacon.PayloadAttributes{
Timestamp: blocks[9].Time() + 5, Timestamp: blocks[9].Time() + 5,
} }
// The miner needs to pick up on the txs in the pool, so a few retries might be // The miner needs to pick up on the txs in the pool, so a few retries might be
@ -102,7 +116,7 @@ func TestEth2AssembleBlock(t *testing.T) {
// assembleWithTransactions tries to assemble a block, retrying until it has 'want', // assembleWithTransactions tries to assemble a block, retrying until it has 'want',
// number of transactions in it, or it has retried three times. // 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-- { for retries := 3; retries > 0; retries-- {
execData, err = assembleBlock(api, parentHash, params) execData, err = assembleBlock(api, parentHash, params)
if err != nil { if err != nil {
@ -118,7 +132,7 @@ func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params
} }
func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
genesis, blocks := generatePreMergeChain(10) genesis, blocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, blocks[:9]) n, ethservice := startEthService(t, genesis, blocks[:9])
defer n.Close() defer n.Close()
@ -126,7 +140,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
// Put the 10th block's tx in the pool and produce a new block // Put the 10th block's tx in the pool and produce a new block
api.eth.TxPool().AddRemotesSync(blocks[9].Transactions()) api.eth.TxPool().AddRemotesSync(blocks[9].Transactions())
blockParams := beacon.PayloadAttributesV1{ blockParams := beacon.PayloadAttributes{
Timestamp: blocks[8].Time() + 5, Timestamp: blocks[8].Time() + 5,
} }
// The miner needs to pick up on the txs in the pool, so a few retries might be // The miner needs to pick up on the txs in the pool, so a few retries might be
@ -137,7 +151,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
} }
func TestSetHeadBeforeTotalDifficulty(t *testing.T) { func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
genesis, blocks := generatePreMergeChain(10) genesis, blocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, blocks) n, ethservice := startEthService(t, genesis, blocks)
defer n.Close() defer n.Close()
@ -155,7 +169,7 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
} }
func TestEth2PrepareAndGetPayload(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 // We need to properly set the terminal total difficulty
genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty()) genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty())
n, ethservice := startEthService(t, genesis, blocks[:9]) n, ethservice := startEthService(t, genesis, blocks[:9])
@ -165,7 +179,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
// Put the 10th block's tx in the pool and produce a new block // Put the 10th block's tx in the pool and produce a new block
ethservice.TxPool().AddLocals(blocks[9].Transactions()) ethservice.TxPool().AddLocals(blocks[9].Transactions())
blockParams := beacon.PayloadAttributesV1{ blockParams := beacon.PayloadAttributes{
Timestamp: blocks[8].Time() + 5, Timestamp: blocks[8].Time() + 5,
} }
fcState := beacon.ForkchoiceStateV1{ fcState := beacon.ForkchoiceStateV1{
@ -221,7 +235,7 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan co
} }
func TestInvalidPayloadTimestamp(t *testing.T) { func TestInvalidPayloadTimestamp(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
var ( var (
@ -244,7 +258,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) {
for i, test := range tests { for i, test := range tests {
t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) { t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) {
params := beacon.PayloadAttributesV1{ params := beacon.PayloadAttributes{
Timestamp: test.time, Timestamp: test.time,
Random: crypto.Keccak256Hash([]byte{byte(123)}), Random: crypto.Keccak256Hash([]byte{byte(123)}),
SuggestedFeeRecipient: parent.Coinbase(), SuggestedFeeRecipient: parent.Coinbase(),
@ -265,7 +279,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) {
} }
func TestEth2NewBlock(t *testing.T) { func TestEth2NewBlock(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
@ -288,7 +302,7 @@ func TestEth2NewBlock(t *testing.T) {
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) 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) ethservice.TxPool().AddLocal(tx)
execData, err := assembleWithTransactions(api, parent.Hash(), &beacon.PayloadAttributesV1{ execData, err := assembleWithTransactions(api, parent.Hash(), &beacon.PayloadAttributes{
Timestamp: parent.Time() + 5, Timestamp: parent.Time() + 5,
}, 1) }, 1)
if err != nil { if err != nil {
@ -330,7 +344,7 @@ func TestEth2NewBlock(t *testing.T) {
) )
parent = preMergeBlocks[len(preMergeBlocks)-1] parent = preMergeBlocks[len(preMergeBlocks)-1]
for i := 0; i < 10; i++ { 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, Timestamp: parent.Time() + 6,
}) })
if err != nil { if err != nil {
@ -367,7 +381,7 @@ func TestEth2DeepReorg(t *testing.T) {
// TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg
// before the totalTerminalDifficulty threshold // before the totalTerminalDifficulty threshold
/* /*
genesis, preMergeBlocks := generatePreMergeChain(core.TriesInMemory * 2) genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
@ -442,7 +456,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
} }
func TestFullAPI(t *testing.T) { func TestFullAPI(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
var ( var (
@ -494,7 +508,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Bl
} }
func TestExchangeTransitionConfig(t *testing.T) { func TestExchangeTransitionConfig(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
@ -555,7 +569,7 @@ We expect
P1'' P1''
*/ */
func TestNewPayloadOnInvalidChain(t *testing.T) { func TestNewPayloadOnInvalidChain(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
@ -577,7 +591,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
}) })
ethservice.TxPool().AddRemotesSync([]*types.Transaction{tx}) ethservice.TxPool().AddRemotesSync([]*types.Transaction{tx})
var ( var (
params = beacon.PayloadAttributesV1{ params = beacon.PayloadAttributes{
Timestamp: parent.Time() + 1, Timestamp: parent.Time() + 1,
Random: crypto.Keccak256Hash([]byte{byte(i)}), Random: crypto.Keccak256Hash([]byte{byte(i)}),
SuggestedFeeRecipient: parent.Coinbase(), SuggestedFeeRecipient: parent.Coinbase(),
@ -587,7 +601,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
SafeBlockHash: common.Hash{}, SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{},
} }
payload *beacon.ExecutableDataV1 payload *beacon.ExecutableData
resp beacon.ForkChoiceResponse resp beacon.ForkChoiceResponse
err error err error
) )
@ -634,22 +648,23 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
} }
} }
func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) { func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *beacon.PayloadAttributes) (*beacon.ExecutableData, error) {
args := &miner.BuildPayloadArgs{ args := &miner.BuildPayloadArgs{
Parent: parentHash, Parent: parentHash,
Timestamp: params.Timestamp, Timestamp: params.Timestamp,
FeeRecipient: params.SuggestedFeeRecipient, FeeRecipient: params.SuggestedFeeRecipient,
Random: params.Random, Random: params.Random,
Withdrawals: params.Withdrawals,
} }
payload, err := api.eth.Miner().BuildPayload(args) payload, err := api.eth.Miner().BuildPayload(args)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return payload.ResolveFull(), nil return payload.ResolveFull().ExecutionPayload, nil
} }
func TestEmptyBlocks(t *testing.T) { func TestEmptyBlocks(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
@ -708,8 +723,8 @@ func TestEmptyBlocks(t *testing.T) {
} }
} }
func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Block) *beacon.ExecutableDataV1 { func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Block) *beacon.ExecutableData {
params := beacon.PayloadAttributesV1{ params := beacon.PayloadAttributes{
Timestamp: parent.Time() + 1, Timestamp: parent.Time() + 1,
Random: crypto.Keccak256Hash([]byte{byte(1)}), Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase(), SuggestedFeeRecipient: parent.Coinbase(),
@ -724,7 +739,7 @@ func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Block) *beacon
// setBlockhash sets the blockhash of a modified ExecutableData. // setBlockhash sets the blockhash of a modified ExecutableData.
// Can be used to make modified payloads look valid. // 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) txs, _ := decodeTransactions(data.Transactions)
number := big.NewInt(0) number := big.NewInt(0)
number.SetUint64(data.Number) number.SetUint64(data.Number)
@ -764,7 +779,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
func TestTrickRemoteBlockCache(t *testing.T) { func TestTrickRemoteBlockCache(t *testing.T) {
// Setup two nodes // Setup two nodes
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
nodeA, ethserviceA := startEthService(t, genesis, preMergeBlocks) nodeA, ethserviceA := startEthService(t, genesis, preMergeBlocks)
nodeB, ethserviceB := startEthService(t, genesis, preMergeBlocks) nodeB, ethserviceB := startEthService(t, genesis, preMergeBlocks)
defer nodeA.Close() defer nodeA.Close()
@ -783,7 +798,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Block) {}) setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Block) {})
commonAncestor = ethserviceA.BlockChain().CurrentBlock() commonAncestor = ethserviceA.BlockChain().CurrentBlock()
var invalidChain []*beacon.ExecutableDataV1 var invalidChain []*beacon.ExecutableData
// create a valid payload (P1) // create a valid payload (P1)
//payload1 := getNewPayload(t, apiA, commonAncestor) //payload1 := getNewPayload(t, apiA, commonAncestor)
//invalidChain = append(invalidChain, payload1) //invalidChain = append(invalidChain, payload1)
@ -827,7 +842,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
} }
func TestInvalidBloom(t *testing.T) { func TestInvalidBloom(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
ethservice.Merger().ReachTTD() ethservice.Merger().ReachTTD()
defer n.Close() defer n.Close()
@ -851,12 +866,12 @@ func TestInvalidBloom(t *testing.T) {
} }
func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(100) genesis, preMergeBlocks := generateMergeChain(100, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty()) genesis.Config.TerminalTotalDifficulty = preMergeBlocks[0].Difficulty() //.Sub(genesis.Config.TerminalTotalDifficulty, preMergeBlocks[len(preMergeBlocks)-1].Difficulty())
var ( var (
api = NewConsensusAPI(ethservice) api = NewConsensusAPI(ethservice)
parent = preMergeBlocks[len(preMergeBlocks)-1] parent = preMergeBlocks[len(preMergeBlocks)-1]
@ -887,7 +902,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err) t.Fatalf("error preparing payload, err=%v", err)
} }
data := *payload.Resolve() data := *payload.Resolve().ExecutionPayload
resp2, err := api.NewPayloadV1(data) resp2, err := api.NewPayloadV1(data)
if err != nil { if err != nil {
t.Fatalf("error sending NewPayload, err=%v", err) t.Fatalf("error sending NewPayload, err=%v", err)
@ -901,7 +916,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
// newPayLoad and forkchoiceUpdate. This is to test that the api behaves // newPayLoad and forkchoiceUpdate. This is to test that the api behaves
// well even of the caller is not being 'serial'. // well even of the caller is not being 'serial'.
func TestSimultaneousNewBlock(t *testing.T) { func TestSimultaneousNewBlock(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
@ -910,7 +925,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
parent = preMergeBlocks[len(preMergeBlocks)-1] parent = preMergeBlocks[len(preMergeBlocks)-1]
) )
for i := 0; i < 10; i++ { 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, Timestamp: parent.Time() + 5,
}) })
if err != nil { if err != nil {
@ -984,3 +999,117 @@ func TestSimultaneousNewBlock(t *testing.T) {
parent = block parent = block
} }
} }
// TestWithdrawals creates and verifies two post-Shanghai blocks. The first
// includes zero withdrawals and the second includes two.
func TestWithdrawals(t *testing.T) {
genesis, blocks := generateMergeChain(10, true)
// Set shanghai time to last block + 5 seconds (first post-merge block)
time := blocks[len(blocks)-1].Time() + 5
genesis.Config.ShanghaiTime = &time
n, ethservice := startEthService(t, genesis, blocks)
ethservice.Merger().ReachTTD()
defer n.Close()
api := NewConsensusAPI(ethservice)
// 10: Build Shanghai block with no withdrawals.
parent := ethservice.BlockChain().CurrentHeader()
blockParams := beacon.PayloadAttributes{
Timestamp: parent.Time + 5,
Withdrawals: make([]*types.Withdrawal, 0),
}
fcState := beacon.ForkchoiceStateV1{
HeadBlockHash: parent.Hash(),
}
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
if resp.PayloadStatus.Status != beacon.VALID {
t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, beacon.VALID)
}
// 10: verify state root is the same as parent
payloadID := (&miner.BuildPayloadArgs{
Parent: fcState.HeadBlockHash,
Timestamp: blockParams.Timestamp,
FeeRecipient: blockParams.SuggestedFeeRecipient,
Random: blockParams.Random,
}).Id()
execData, err := api.GetPayloadV2(payloadID)
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
if execData.ExecutionPayload.StateRoot != parent.Root {
t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.ExecutionPayload.StateRoot, blocks[8].Root())
}
// 10: verify locally built block
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
t.Fatalf("error validating payload: %v", err)
} else if status.Status != beacon.VALID {
t.Fatalf("invalid payload")
}
// 11: build shanghai block with withdrawal
aa := common.Address{0xaa}
bb := common.Address{0xbb}
blockParams = beacon.PayloadAttributes{
Timestamp: execData.ExecutionPayload.Timestamp + 5,
Withdrawals: []*types.Withdrawal{
{
Index: 0,
Address: aa,
Amount: 32,
},
{
Index: 1,
Address: bb,
Amount: 33,
},
},
}
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
_, err = api.ForkchoiceUpdatedV2(fcState, &blockParams)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
// 11: verify locally build block.
payloadID = (&miner.BuildPayloadArgs{
Parent: fcState.HeadBlockHash,
Timestamp: blockParams.Timestamp,
FeeRecipient: blockParams.SuggestedFeeRecipient,
Random: blockParams.Random,
}).Id()
execData, err = api.GetPayloadV2(payloadID)
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
t.Fatalf("error validating payload: %v", err)
} else if status.Status != beacon.VALID {
t.Fatalf("invalid payload")
}
// 11: set block as head.
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
_, err = api.ForkchoiceUpdatedV2(fcState, nil)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
// 11: verify withdrawals were processed.
db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
if err != nil {
t.Fatalf("unable to load db: %v", err)
}
for i, w := range blockParams.Withdrawals {
// w.Amount is in gwei, balance in wei
if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei {
t.Fatalf("failed to process withdrawal %d", i)
}
}
}

@ -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. // get retrieves a previously stored payload item or nil if it does not exist.
func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutableDataV1 { func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutionPayloadEnvelope {
q.lock.RLock() q.lock.RLock()
defer q.lock.RUnlock() defer q.lock.RUnlock()

@ -1548,7 +1548,7 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error {
) )
blocks := make([]*types.Block, len(results)) blocks := make([]*types.Block, len(results))
for i, result := range results { for i, result := range results {
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals)
} }
// Downloaded blocks are always regarded as trusted after the // Downloaded blocks are always regarded as trusted after the
// transition. Because the downloaded chain is guided by 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)) blocks := make([]*types.Block, len(results))
receipts := make([]types.Receipts, len(results)) receipts := make([]types.Receipts, len(results))
for i, result := range results { for i, result := range results {
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals)
receipts[i] = result.Receipts receipts[i] = result.Receipts
} }
if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { 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 { func (d *Downloader) commitPivotBlock(result *fetchResult) error {
block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles).WithWithdrawals(result.Withdrawals)
log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash()) 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 // Commit the pivot block as the new head, will require full sync from here on

@ -273,8 +273,9 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
rlp.DecodeBytes(blob, bodies[i]) rlp.DecodeBytes(blob, bodies[i])
} }
var ( var (
txsHashes = make([]common.Hash, len(bodies)) txsHashes = make([]common.Hash, len(bodies))
uncleHashes = make([]common.Hash, len(bodies)) uncleHashes = make([]common.Hash, len(bodies))
withdrawalHashes = make([]common.Hash, len(bodies))
) )
hasher := trie.NewStackTrie(nil) hasher := trie.NewStackTrie(nil)
for i, body := range bodies { for i, body := range bodies {
@ -287,7 +288,7 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
res := &eth.Response{ res := &eth.Response{
Req: req, Req: req,
Res: (*eth.BlockBodiesPacket)(&bodies), Res: (*eth.BlockBodiesPacket)(&bodies),
Meta: [][]common.Hash{txsHashes, uncleHashes}, Meta: [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes},
Time: 1, Time: 1,
Done: make(chan error, 1), // Ignore the returned status Done: make(chan error, 1), // Ignore the returned status
} }

@ -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 // 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. // fetcher, unpacking the body data and delivering it to the downloader's queue.
func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) { func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
txs, uncles := packet.Res.(*eth.BlockBodiesPacket).Unpack() txs, uncles, withdrawals := packet.Res.(*eth.BlockBodiesPacket).Unpack()
hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes} 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 { switch {
case err == nil && len(txs) == 0: case err == nil && len(txs) == 0:
peer.log.Trace("Requested bodies delivered") peer.log.Trace("Requested bodies delivered")

@ -67,6 +67,7 @@ type fetchResult struct {
Uncles []*types.Header Uncles []*types.Header
Transactions types.Transactions Transactions types.Transactions
Receipts types.Receipts Receipts types.Receipts
Withdrawals types.Withdrawals
} }
func newFetchResult(header *types.Header, fastSync bool) *fetchResult { 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. // DeliverBodies injects a block body retrieval response into the results queue.
// The method returns the number of blocks bodies accepted from the delivery and // The method returns the number of blocks bodies accepted from the delivery and
// also wakes any threads waiting for data delivery. // 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() q.lock.Lock()
defer q.lock.Unlock() defer q.lock.Unlock()
@ -775,12 +778,19 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH
if uncleListHashes[index] != header.UncleHash { if uncleListHashes[index] != header.UncleHash {
return errInvalidBody return errInvalidBody
} }
if header.WithdrawalsHash == nil {
// discard any withdrawals if we don't have a withdrawal hash set
withdrawalLists[index] = nil
} else if withdrawalListHashes[index] != *header.WithdrawalsHash {
return errInvalidBody
}
return nil return nil
} }
reconstruct := func(index int, result *fetchResult) { reconstruct := func(index int, result *fetchResult) {
result.Transactions = txLists[index] result.Transactions = txLists[index]
result.Uncles = uncleLists[index] result.Uncles = uncleLists[index]
result.Withdrawals = withdrawalLists[index]
result.SetBodyDone() result.SetBodyDone()
} }
return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool,

@ -339,7 +339,7 @@ func XTestDelivery(t *testing.T) {
uncleHashes[i] = types.CalcUncleHash(uncles) uncleHashes[i] = types.CalcUncleHash(uncles)
} }
time.Sleep(100 * time.Millisecond) 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 { if err != nil {
fmt.Printf("delivered %d bodies %v\n", len(txset), err) fmt.Printf("delivered %d bodies %v\n", len(txset), err)
} }

@ -540,8 +540,8 @@ func (f *BlockFetcher) loop() {
select { select {
case res := <-resCh: case res := <-resCh:
res.Done <- nil res.Done <- nil
// Ignoring withdrawals here, since the block fetcher is not used post-merge.
txs, uncles := res.Res.(*eth.BlockBodiesPacket).Unpack() txs, uncles, _ := res.Res.(*eth.BlockBodiesPacket).Unpack()
f.FilterBodies(peer, txs, uncles, time.Now()) f.FilterBodies(peer, txs, uncles, time.Now())
case <-timeout.C: case <-timeout.C:

@ -23,6 +23,8 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "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/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -45,6 +47,8 @@ var (
testAddr = crypto.PubkeyToAddress(testKey.PublicKey) testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
) )
func u64(val uint64) *uint64 { return &val }
// testBackend is a mock implementation of the live Ethereum message handler. Its // testBackend is a mock implementation of the live Ethereum message handler. Its
// purpose is to allow testing the request/reply workflows and wire serialization // purpose is to allow testing the request/reply workflows and wire serialization
// in the `eth` protocol without actually doing any data processing. // in the `eth` protocol without actually doing any data processing.
@ -56,21 +60,53 @@ type testBackend struct {
// newTestBackend creates an empty chain and wraps it into a mock backend. // newTestBackend creates an empty chain and wraps it into a mock backend.
func newTestBackend(blocks int) *testBackend { 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 // newTestBackend creates a chain with a number of explicitly defined blocks and
// wraps it into a mock backend. // wraps it into a mock backend.
func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen)) *testBackend { func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, *core.BlockGen)) *testBackend {
// Create a database pre-initialize with a genesis block var (
db := rawdb.NewMemoryDatabase() // Create a database pre-initialize with a genesis block
db = rawdb.NewMemoryDatabase()
config = params.TestChainConfig
engine consensus.Engine = ethash.NewFaker()
)
if shanghai {
config = &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
MergeNetsplitBlock: big.NewInt(0),
ShanghaiTime: u64(0),
TerminalTotalDifficulty: big.NewInt(0),
TerminalTotalDifficultyPassed: true,
Ethash: new(params.EthashConfig),
}
engine = beacon.NewFaker()
}
gspec := &core.Genesis{ gspec := &core.Genesis{
Config: params.TestChainConfig, Config: config,
Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}}, 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 { if _, err := chain.InsertChain(bs); err != nil {
panic(err) panic(err)
} }
@ -305,7 +341,17 @@ func TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) }
func testGetBlockBodies(t *testing.T, protocol uint) { func testGetBlockBodies(t *testing.T, protocol uint) {
t.Parallel() t.Parallel()
backend := newTestBackend(maxBodiesServe + 15) gen := func(n int, g *core.BlockGen) {
if n%2 == 0 {
w := &types.Withdrawal{
Address: common.Address{0xaa},
Amount: 42,
}
g.AddWithdrawal(w)
}
}
backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen)
defer backend.close() defer backend.close()
peer, _ := newTestPeer("peer", protocol, backend) peer, _ := newTestPeer("peer", protocol, backend)
@ -355,7 +401,7 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
block := backend.chain.GetBlockByNumber(uint64(num)) block := backend.chain.GetBlockByNumber(uint64(num))
hashes = append(hashes, block.Hash()) hashes = append(hashes, block.Hash())
if len(bodies) < tt.expected { 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 break
} }
@ -365,9 +411,10 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
hashes = append(hashes, hash) hashes = append(hashes, hash)
if tt.available[j] && len(bodies) < tt.expected { if tt.available[j] && len(bodies) < tt.expected {
block := backend.chain.GetBlockByHash(hash) 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 // Send the hash request and verify the response
p2p.Send(peer.app, GetBlockBodiesMsg, &GetBlockBodiesPacket66{ p2p.Send(peer.app, GetBlockBodiesMsg, &GetBlockBodiesPacket66{
RequestId: 123, RequestId: 123,
@ -426,7 +473,7 @@ func testGetNodeData(t *testing.T, protocol uint, drop bool) {
} }
} }
// Assemble the test environment // Assemble the test environment
backend := newTestBackendWithGenerator(4, generator) backend := newTestBackendWithGenerator(4, false, generator)
defer backend.close() defer backend.close()
peer, _ := newTestPeer("peer", protocol, backend) peer, _ := newTestPeer("peer", protocol, backend)
@ -544,7 +591,7 @@ func testGetBlockReceipts(t *testing.T, protocol uint) {
} }
} }
// Assemble the test environment // Assemble the test environment
backend := newTestBackendWithGenerator(4, generator) backend := newTestBackendWithGenerator(4, false, generator)
defer backend.close() defer backend.close()
peer, _ := newTestPeer("peer", protocol, backend) peer, _ := newTestPeer("peer", protocol, backend)

@ -379,15 +379,19 @@ func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error {
} }
metadata := func() interface{} { metadata := func() interface{} {
var ( var (
txsHashes = make([]common.Hash, len(res.BlockBodiesPacket)) txsHashes = make([]common.Hash, len(res.BlockBodiesPacket))
uncleHashes = make([]common.Hash, len(res.BlockBodiesPacket)) uncleHashes = make([]common.Hash, len(res.BlockBodiesPacket))
withdrawalHashes = make([]common.Hash, len(res.BlockBodiesPacket))
) )
hasher := trie.NewStackTrie(nil) hasher := trie.NewStackTrie(nil)
for i, body := range res.BlockBodiesPacket { for i, body := range res.BlockBodiesPacket {
txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher) txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher)
uncleHashes[i] = types.CalcUncleHash(body.Uncles) 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{ return peer.dispatchResponse(&Response{
id: res.RequestId, id: res.RequestId,

@ -239,19 +239,22 @@ type BlockBodiesRLPPacket66 struct {
type BlockBody struct { type BlockBody struct {
Transactions []*types.Transaction // Transactions contained within a block Transactions []*types.Transaction // Transactions contained within a block
Uncles []*types.Header // Uncles 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 // 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. // 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 ( var (
txset = make([][]*types.Transaction, len(*p)) txset = make([][]*types.Transaction, len(*p))
uncleset = make([][]*types.Header, len(*p)) uncleset = make([][]*types.Header, len(*p))
withdrawalset = make([][]*types.Withdrawal, len(*p))
) )
for i, body := range *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. // GetNodeDataPacket represents a trie node data query.

@ -1214,6 +1214,10 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee)
} }
if head.WithdrawalsHash != nil {
result["withdrawalsRoot"] = head.WithdrawalsHash
}
return result return result
} }
@ -1242,6 +1246,8 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param
} }
} }
fields["transactions"] = transactions fields["transactions"] = transactions
// inclTx also expands withdrawals
fields["withdrawals"] = block.Withdrawals()
} }
uncles := block.Uncles() uncles := block.Uncles()
uncleHashes := make([]common.Hash, len(uncles)) uncleHashes := make([]common.Hash, len(uncles))

@ -70,7 +70,7 @@ func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI {
// //
// If there are payloadAttributes: we return an error since block creation is not // If there are payloadAttributes: we return an error since block creation is not
// supported in les mode. // 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{}) { if heads.HeadBlockHash == (common.Hash{}) {
log.Warn("Forkchoice requested update to zero hash") log.Warn("Forkchoice requested update to zero hash")
return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? 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. // 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")) 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. // 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) block, err := beacon.ExecutableDataToBlock(params)
if err != nil { if err != nil {
return api.invalid(), err return api.invalid(), err

@ -130,7 +130,7 @@ func TestExecutePayloadV1(t *testing.T) {
BaseFee: block.BaseFee(), BaseFee: block.BaseFee(),
}, nil, nil, nil, trie.NewStackTrie(nil)) }, nil, nil, nil, trie.NewStackTrie(nil))
_, err := api.ExecutePayloadV1(beacon.ExecutableDataV1{ _, err := api.ExecutePayloadV1(beacon.ExecutableData{
ParentHash: fakeBlock.ParentHash(), ParentHash: fakeBlock.ParentHash(),
FeeRecipient: fakeBlock.Coinbase(), FeeRecipient: fakeBlock.Coinbase(),
StateRoot: fakeBlock.Root(), StateRoot: fakeBlock.Root(),

@ -34,10 +34,11 @@ import (
// Check engine-api specification for more details. // Check engine-api specification for more details.
// https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1 // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1
type BuildPayloadArgs struct { type BuildPayloadArgs struct {
Parent common.Hash // The parent block to build payload on top Parent common.Hash // The parent block to build payload on top
Timestamp uint64 // The provided timestamp of generated payload Timestamp uint64 // The provided timestamp of generated payload
FeeRecipient common.Address // The provided recipient address for collecting transaction fee FeeRecipient common.Address // The provided recipient address for collecting transaction fee
Random common.Hash // The provided randomness value 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. // 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 // Resolve returns the latest built payload and also terminates the background
// thread for updating payload. It's safe to be called multiple times. // thread for updating payload. It's safe to be called multiple times.
func (payload *Payload) Resolve() *beacon.ExecutableDataV1 { func (payload *Payload) Resolve() *beacon.ExecutionPayloadEnvelope {
payload.lock.Lock() payload.lock.Lock()
defer payload.lock.Unlock() defer payload.lock.Unlock()
@ -117,23 +118,23 @@ func (payload *Payload) Resolve() *beacon.ExecutableDataV1 {
close(payload.stop) close(payload.stop)
} }
if payload.full != nil { 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. // ResolveEmpty is basically identical to Resolve, but it expects empty block only.
// It's only used in tests. // It's only used in tests.
func (payload *Payload) ResolveEmpty() *beacon.ExecutableDataV1 { func (payload *Payload) ResolveEmpty() *beacon.ExecutionPayloadEnvelope {
payload.lock.Lock() payload.lock.Lock()
defer payload.lock.Unlock() 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. // ResolveFull is basically identical to Resolve, but it expects full block only.
// It's only used in tests. // It's only used in tests.
func (payload *Payload) ResolveFull() *beacon.ExecutableDataV1 { func (payload *Payload) ResolveFull() *beacon.ExecutionPayloadEnvelope {
payload.lock.Lock() payload.lock.Lock()
defer payload.lock.Unlock() defer payload.lock.Unlock()
@ -145,7 +146,7 @@ func (payload *Payload) ResolveFull() *beacon.ExecutableDataV1 {
} }
payload.cond.Wait() payload.cond.Wait()
} }
return beacon.BlockToExecutableData(payload.full) return beacon.BlockToExecutableData(payload.full, payload.fullFees)
} }
// buildPayload builds the payload according to the provided parameters. // buildPayload builds the payload according to the provided parameters.
@ -153,7 +154,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
// Build the initial version with no transaction included. It should be fast // 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 // enough to run. The empty payload can at least make sure there is something
// to deliver for not missing slot. // 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 { if err != nil {
return nil, err return nil, err
} }
@ -177,7 +178,7 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
select { select {
case <-timer.C: case <-timer.C:
start := time.Now() 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 { if err == nil {
payload.update(block, fees, time.Since(start)) payload.update(block, fees, time.Since(start))
} }

@ -47,20 +47,21 @@ func TestBuildPayload(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to build payload %v", err) t.Fatalf("Failed to build payload %v", err)
} }
verify := func(data *beacon.ExecutableDataV1, txs int) { verify := func(outer *beacon.ExecutionPayloadEnvelope, txs int) {
if data.ParentHash != b.chain.CurrentBlock().Hash() { payload := outer.ExecutionPayload
if payload.ParentHash != b.chain.CurrentBlock().Hash() {
t.Fatal("Unexpect parent hash") t.Fatal("Unexpect parent hash")
} }
if data.Random != (common.Hash{}) { if payload.Random != (common.Hash{}) {
t.Fatal("Unexpect random value") t.Fatal("Unexpect random value")
} }
if data.Timestamp != timestamp { if payload.Timestamp != timestamp {
t.Fatal("Unexpect timestamp") t.Fatal("Unexpect timestamp")
} }
if data.FeeRecipient != recipient { if payload.FeeRecipient != recipient {
t.Fatal("Unexpect fee recipient") t.Fatal("Unexpect fee recipient")
} }
if len(data.Transactions) != txs { if len(payload.Transactions) != txs {
t.Fatal("Unexpect transaction set") t.Fatal("Unexpect transaction set")
} }
} }

@ -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 { if n.typ != eth2MiningNode {
return nil, errors.New("invalid node type") return nil, errors.New("invalid node type")
} }
@ -150,7 +150,7 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64)
if timestamp <= parentTimestamp { if timestamp <= parentTimestamp {
timestamp = parentTimestamp + 1 timestamp = parentTimestamp + 1
} }
payloadAttribute := beacon.PayloadAttributesV1{ payloadAttribute := beacon.PayloadAttributes{
Timestamp: timestamp, Timestamp: timestamp,
Random: common.Hash{}, Random: common.Hash{},
SuggestedFeeRecipient: common.HexToAddress("0xdeadbeef"), SuggestedFeeRecipient: common.HexToAddress("0xdeadbeef"),
@ -168,7 +168,7 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64)
return n.api.GetPayloadV1(*payload.PayloadID) 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) { if !eth2types(n.typ) {
return errors.New("invalid node type") 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) { if !eth2types(n.typ) {
return errors.New("invalid node type") return errors.New("invalid node type")
} }

@ -968,13 +968,14 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
// generateParams wraps various of settings for generating sealing task. // generateParams wraps various of settings for generating sealing task.
type generateParams struct { type generateParams struct {
timestamp uint64 // The timestamp for sealing task timestamp uint64 // The timstamp for sealing task
forceTime bool // Flag whether the given timestamp is immutable or not forceTime bool // Flag whether the given timestamp is immutable or not
parentHash common.Hash // Parent block hash, empty means the latest chain head parentHash common.Hash // Parent block hash, empty means the latest chain head
coinbase common.Address // The fee recipient address for including transaction coinbase common.Address // The fee recipient address for including transaction
random common.Hash // The randomness generated by beacon chain, empty before the merge random common.Hash // The randomness generated by beacon chain, empty before the merge
noUncle bool // Flag whether the uncle block inclusion is allowed withdrawals types.Withdrawals // List of withdrawals to include in block.
noTxs bool // Flag whether an empty block without any transaction is expected noUncle bool // Flag whether the uncle block inclusion is allowed
noTxs bool // Flag whether an empty block without any transaction is expected
} }
// prepareWork constructs the sealing task according to the given parameters, // prepareWork constructs the sealing task according to the given parameters,
@ -1108,7 +1109,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e
log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout)) 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -1193,7 +1194,8 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// Create a local environment copy, avoid the data race with snapshot state. // Create a local environment copy, avoid the data race with snapshot state.
// https://github.com/ethereum/go-ethereum/issues/24299 // https://github.com/ethereum/go-ethereum/issues/24299
env := env.copy() env := env.copy()
block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.unclelist(), env.receipts) // Withdrawals are set to nil here, because this is only called in PoW.
block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.unclelist(), env.receipts, nil)
if err != nil { if err != nil {
return err return err
} }
@ -1224,16 +1226,17 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti
// getSealingBlock generates the sealing block based on the given parameters. // 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 result will be passed back via the given channel no matter
// the generation itself succeeds or not. // 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{ req := &getWorkReq{
params: &generateParams{ params: &generateParams{
timestamp: timestamp, timestamp: timestamp,
forceTime: true, forceTime: true,
parentHash: parent, parentHash: parent,
coinbase: coinbase, coinbase: coinbase,
random: random, random: random,
noUncle: true, withdrawals: withdrawals,
noTxs: noTxs, noUncle: true,
noTxs: noTxs,
}, },
result: make(chan *newPayloadResult, 1), result: make(chan *newPayloadResult, 1),
} }

@ -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 // This API should work even when the automatic sealing is not enabled
for _, c := range cases { 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 c.expectErr {
if err == nil { if err == nil {
t.Error("Expect error but get 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 // This API should work even when the automatic sealing is enabled
w.start() w.start()
for _, c := range cases { 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 c.expectErr {
if err == nil { if err == nil {
t.Error("Expect error but get nil") t.Error("Expect error but get nil")