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