From 12227ef2cd7cb3df723c314c4bf2082cfb1f4458 Mon Sep 17 00:00:00 2001 From: lightclient Date: Tue, 23 Apr 2024 08:28:36 -0600 Subject: [PATCH] all: implement eip-7002 EL triggered withdrawal requests --- beacon/engine/gen_ed.go | 78 +++++++++-------- beacon/engine/types.go | 83 ++++++++++-------- cmd/evm/internal/t8ntool/execution.go | 101 +++++++++++++--------- core/blockchain_test.go | 96 ++++++++++++++++++++ core/chain_makers.go | 7 ++ core/state_processor.go | 40 +++++++++ core/types/block.go | 15 ++++ core/types/gen_withdrawal_request_json.go | 49 +++++++++++ core/types/request.go | 22 ++++- core/types/withdrawal_request.go | 79 +++++++++++++++++ eth/catalyst/api.go | 1 + miner/worker.go | 10 ++- params/protocol_params.go | 4 +- 13 files changed, 464 insertions(+), 121 deletions(-) create mode 100644 core/types/gen_withdrawal_request_json.go create mode 100644 core/types/withdrawal_request.go diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index c3db18022640..64e4453d4bd7 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -17,24 +17,25 @@ var _ = (*executableDataMarshaling)(nil) // MarshalJSON marshals as JSON. 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"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - Deposits types.Deposits `json:"depositRequests"` + 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"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + Deposits types.Deposits `json:"depositRequests"` + WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -60,30 +61,32 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.Deposits = e.Deposits + enc.WithdrawalRequests = e.WithdrawalRequests return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. 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"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - Deposits *types.Deposits `json:"depositRequests"` + 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"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + Deposits *types.Deposits `json:"depositRequests"` + WithdrawalRequests *types.WithdrawalRequests `json:"withdrawalRequests"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -160,5 +163,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.Deposits != nil { e.Deposits = *dec.Deposits } + if dec.WithdrawalRequests != nil { + e.WithdrawalRequests = *dec.WithdrawalRequests + } return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 204c02f123bf..c27d8dbb4669 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -59,24 +59,25 @@ type payloadAttributesMarshaling struct { // 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"` - BlobGasUsed *uint64 `json:"blobGasUsed"` - ExcessBlobGas *uint64 `json:"excessBlobGas"` - Deposits types.Deposits `json:"depositRequests"` + 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"` + BlobGasUsed *uint64 `json:"blobGasUsed"` + ExcessBlobGas *uint64 `json:"excessBlobGas"` + Deposits types.Deposits `json:"depositRequests"` + WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` } // JSON type overrides for executableData. @@ -232,6 +233,9 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, withdrawalsRoot = &h } + // Only set requestsHash if there exists requests in the ExecutableData. This + // allows CLs to continue using the data structure before requests are + // enabled. var ( requestsHash *common.Hash requests types.Requests @@ -239,9 +243,15 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, if params.Deposits != nil { requests = make(types.Requests, 0, len(params.Deposits.Requests())) requests = append(requests, params.Deposits.Requests()...) + } + if params.WithdrawalRequests != nil { + requests = append(requests, params.WithdrawalRequests.Requests()...) + } + if requests != nil { h := types.DeriveSha(requests, trie.NewStackTrie(nil)) requestsHash = &h } + header := &types.Header{ ParentHash: params.ParentHash, UncleHash: types.EmptyUncleHash, @@ -283,24 +293,25 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, // fields from the given block. It assumes the given block is post-merge block. func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope { data := &ExecutableData{ - BlockHash: block.Hash(), - ParentHash: block.ParentHash(), - FeeRecipient: block.Coinbase(), - StateRoot: block.Root(), - Number: block.NumberU64(), - GasLimit: block.GasLimit(), - GasUsed: block.GasUsed(), - BaseFeePerGas: block.BaseFee(), - Timestamp: block.Time(), - ReceiptsRoot: block.ReceiptHash(), - LogsBloom: block.Bloom().Bytes(), - Transactions: encodeTransactions(block.Transactions()), - Random: block.MixDigest(), - ExtraData: block.Extra(), - Withdrawals: block.Withdrawals(), - BlobGasUsed: block.BlobGasUsed(), - ExcessBlobGas: block.ExcessBlobGas(), - Deposits: block.Deposits(), + BlockHash: block.Hash(), + ParentHash: block.ParentHash(), + FeeRecipient: block.Coinbase(), + StateRoot: block.Root(), + Number: block.NumberU64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + BaseFeePerGas: block.BaseFee(), + Timestamp: block.Time(), + ReceiptsRoot: block.ReceiptHash(), + LogsBloom: block.Bloom().Bytes(), + Transactions: encodeTransactions(block.Transactions()), + Random: block.MixDigest(), + ExtraData: block.Extra(), + Withdrawals: block.Withdrawals(), + BlobGasUsed: block.BlobGasUsed(), + ExcessBlobGas: block.ExcessBlobGas(), + Deposits: block.Deposits(), + WithdrawalRequests: block.WithdrawalRequests(), } bundle := BlobsBundleV1{ Commitments: make([]hexutil.Bytes, 0), diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index eddaa50d4dc2..e3051d65aadb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -53,21 +53,22 @@ 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"` - WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` - CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` - CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` - RequestsHash *common.Hash `json:"requestsRoot,omitempty"` - DepositRequests *types.Deposits `json:"depositRequests,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"` + CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` + CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` + RequestsHash *common.Hash `json:"requestsRoot,omitempty"` + DepositRequests *types.Deposits `json:"depositRequests,omitempty"` + WithdrawalRequests *types.WithdrawalRequests `json:"withdrawalRequests,omitempty"` } type ommer struct { @@ -345,22 +346,55 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal) } + // Retrieve deposit and withdrawal requests + var ( + depositRequests *types.Deposits + withdrawalRequests *types.WithdrawalRequests + requestsHash *common.Hash + ) + if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) { + // Parse deposit requests from the logs + var allLogs []*types.Log + for _, receipt := range receipts { + allLogs = append(allLogs, receipt.Logs...) + } + requests, err := core.ParseDepositLogs(allLogs, chainConfig) + if err != nil { + return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err)) + } + // Process the withdrawal requests contract execution + vmenv := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) + wxs := core.ProcessDequeueWithdrawalRequests(vmenv, statedb) + requests = append(requests, wxs...) + // Calculate the requests root + h := types.DeriveSha(requests, trie.NewStackTrie(nil)) + requestsHash = &h + // Get the deposits from the requests + deposits := requests.Deposits() + depositRequests = &deposits + // Get the withdrawals from the requests + withdrawals := requests.Withdrawals() + withdrawalRequests = &withdrawals + } // Commit block root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) } execRs := &ExecutionResult{ - StateRoot: root, - TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), - ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), - Bloom: types.CreateBloom(receipts), - LogsHash: rlpHash(statedb.Logs()), - Receipts: receipts, - Rejected: rejectedTxs, - Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty), - GasUsed: (math.HexOrDecimal64)(gasUsed), - BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), + StateRoot: root, + TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), + ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), + Bloom: types.CreateBloom(receipts), + LogsHash: rlpHash(statedb.Logs()), + Receipts: receipts, + Rejected: rejectedTxs, + Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty), + GasUsed: (math.HexOrDecimal64)(gasUsed), + BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), + RequestsHash: requestsHash, + DepositRequests: depositRequests, + WithdrawalRequests: withdrawalRequests, } if pre.Env.Withdrawals != nil { h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) @@ -370,23 +404,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) } - if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) { - // Parse the requests from the logs - var allLogs []*types.Log - for _, receipt := range receipts { - allLogs = append(allLogs, receipt.Logs...) - } - requests, err := core.ParseDepositLogs(allLogs, chainConfig) - if err != nil { - return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err)) - } - // Calculate the requests root - h := types.DeriveSha(requests, trie.NewStackTrie(nil)) - execRs.RequestsHash = &h - // Get the deposits from the requests - deposits := requests.Deposits() - execRs.DepositRequests = &deposits - } // Re-create statedb instance with new root upon the updated database // for accessing latest states. statedb, err = state.New(root, statedb.Database(), nil) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index ba47f36dfed0..874f77f2a564 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -4304,3 +4305,98 @@ func TestEIP6110(t *testing.T) { } } } + +// TestEIP7002 verifies that withdrawal requests are processed correctly in the +// pre-deploy and parsed out correctly via the system call. +func TestEIP7002(t *testing.T) { + var ( + engine = beacon.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + config = *params.AllEthashProtocolChanges + gspec = &Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{ + addr: {Balance: funds}, + params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")}, + }, + } + ) + gspec.Config.BerlinBlock = common.Big0 + gspec.Config.LondonBlock = common.Big0 + gspec.Config.TerminalTotalDifficulty = common.Big0 + gspec.Config.TerminalTotalDifficultyPassed = true + gspec.Config.ShanghaiTime = u64(0) + gspec.Config.CancunTime = u64(0) + gspec.Config.PragueTime = u64(0) + signer := types.LatestSigner(gspec.Config) + + // Withdrawal requests to send. + wxs := types.WithdrawalRequests{ + { + Source: addr, + PublicKey: [48]byte{42}, + Amount: 42, + }, + { + Source: addr, + PublicKey: [48]byte{13, 37}, + Amount: 1337, + }, + } + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + for i, wx := range wxs { + data := make([]byte, 56) + copy(data, wx.PublicKey[:]) + binary.BigEndian.PutUint64(data[48:], wx.Amount) + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: uint64(i), + To: ¶ms.WithdrawalRequestsAddress, + Value: big.NewInt(1), + Gas: 500000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + AccessList: nil, + Data: data, + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key) + b.AddTx(tx) + } + }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + block := chain.GetBlockByNumber(1) + if block == nil { + t.Fatalf("failed to retrieve block 1") + } + + // Verify the withdrawal requests match. + got := block.WithdrawalRequests() + if len(got) != 2 { + t.Fatalf("wrong number of withdrawal requests: wanted 2, got %d", len(wxs)) + } + for i, want := range wxs { + if want.Source != got[i].Source { + t.Fatalf("wrong source address: want %s, got %s", want.Source, got[i].Source) + } + if want.PublicKey != got[i].PublicKey { + t.Fatalf("wrong public key: want %s, got %s", common.Bytes2Hex(want.PublicKey[:]), common.Bytes2Hex(got[i].PublicKey[:])) + } + if want.Amount != got[i].Amount { + t.Fatalf("wrong amount: want %d, got %d", want.Amount, got[i].Amount) + } + } + +} diff --git a/core/chain_makers.go b/core/chain_makers.go index da3e148508c7..b8b1a1ea738d 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -355,6 +355,13 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } requests = append(requests, d...) } + + var ( + blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) + vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{}) + ) + wxs := ProcessDequeueWithdrawalRequests(vmenv, statedb) + requests = append(requests, wxs...) } body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals, Requests: requests} diff --git a/core/state_processor.go b/core/state_processor.go index f6abdd53af34..6dfeea31075b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,6 +17,7 @@ package core import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -108,6 +109,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if err != nil { return nil, err } + wxs := ProcessDequeueWithdrawalRequests(vmenv, statedb) + requests = append(requests, wxs...) } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) @@ -240,3 +243,40 @@ func ParseDepositLogs(logs []*types.Log, config *params.ChainConfig) (types.Requ } return deposits, nil } + +// ProcessDequeueWithdrawalRequests applies the EIP-7002 system call to the withdrawal requests contract. +func ProcessDequeueWithdrawalRequests(vmenv *vm.EVM, statedb *state.StateDB) types.Requests { + if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallStart != nil { + vmenv.Config.Tracer.OnSystemCallStart() + } + if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallEnd != nil { + defer vmenv.Config.Tracer.OnSystemCallEnd() + } + msg := &Message{ + From: params.SystemAddress, + GasLimit: 30_000_000, + GasPrice: common.Big0, + GasFeeCap: common.Big0, + GasTipCap: common.Big0, + To: ¶ms.WithdrawalRequestsAddress, + } + vmenv.Reset(NewEVMTxContext(msg), statedb) + statedb.AddAddressToAccessList(params.WithdrawalRequestsAddress) + ret, _, _ := vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) + statedb.Finalise(true) + + // Parse out the exits. + var reqs types.Requests + for i := 0; i < len(ret)/76; i++ { + start := i * 76 + var pubkey [48]byte + copy(pubkey[:], ret[start+20:start+68]) + wx := &types.WithdrawalRequest{ + Source: common.BytesToAddress(ret[start : start+20]), + PublicKey: pubkey, + Amount: binary.BigEndian.Uint64(ret[start+68:]), + } + reqs = append(reqs, types.NewRequest(wx)) + } + return reqs +} diff --git a/core/types/block.go b/core/types/block.go index 22006e188730..7d65a43d3358 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -379,6 +379,21 @@ func (b *Block) Deposits() Deposits { return deps } +func (b *Block) WithdrawalRequests() WithdrawalRequests { + var wxs WithdrawalRequests + if b.requests != nil { + // If requests is non-nil, it means deposits are available in block and we + // should return an empty slice instead of nil if there are no deposits. + wxs = make(WithdrawalRequests, 0) + } + for _, r := range b.requests { + if w, ok := r.inner.(*WithdrawalRequest); ok { + wxs = append(wxs, w) + } + } + return wxs +} + func (b *Block) Transaction(hash common.Hash) *Transaction { for _, transaction := range b.transactions { if transaction.Hash() == hash { diff --git a/core/types/gen_withdrawal_request_json.go b/core/types/gen_withdrawal_request_json.go new file mode 100644 index 000000000000..2042c4ff58d9 --- /dev/null +++ b/core/types/gen_withdrawal_request_json.go @@ -0,0 +1,49 @@ +// 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 _ = (*withdrawalRequestMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (w WithdrawalRequest) MarshalJSON() ([]byte, error) { + type WithdrawalRequest struct { + Source common.Address `json:"sourceAddress"` + PublicKey [48]byte `json:"validatorPublicKey"` + Amount hexutil.Uint64 `json:"amount"` + } + var enc WithdrawalRequest + enc.Source = w.Source + enc.PublicKey = w.PublicKey + enc.Amount = hexutil.Uint64(w.Amount) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (w *WithdrawalRequest) UnmarshalJSON(input []byte) error { + type WithdrawalRequest struct { + Source *common.Address `json:"sourceAddress"` + PublicKey *[48]byte `json:"validatorPublicKey"` + Amount *hexutil.Uint64 `json:"amount"` + } + var dec WithdrawalRequest + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Source != nil { + w.Source = *dec.Source + } + if dec.PublicKey != nil { + w.PublicKey = *dec.PublicKey + } + if dec.Amount != nil { + w.Amount = uint64(*dec.Amount) + } + return nil +} diff --git a/core/types/request.go b/core/types/request.go index ebb2f5645904..0033a0bb079b 100644 --- a/core/types/request.go +++ b/core/types/request.go @@ -32,7 +32,8 @@ var ( // Request types. const ( - DepositRequestType = 0x00 + DepositRequestType = 0x00 + WithdrawalRequestType = 0x01 ) // Request is an EIP-7685 request object. It represents execution layer @@ -66,13 +67,24 @@ func (s Requests) EncodeIndex(i int, w *bytes.Buffer) { // Retrieve deposits from a requests list. func (s Requests) Deposits() Deposits { - deposits := make(Deposits, 0, len(s)) + dr := make(Deposits, 0, len(s)) for _, req := range s { if d, ok := req.inner.(*Deposit); ok { - deposits = append(deposits, d) + dr = append(dr, d) } } - return deposits + return dr +} + +// Retrieve withdrawals requests from a requests list. +func (s Requests) Withdrawals() WithdrawalRequests { + wr := make(WithdrawalRequests, 0, len(s)) + for _, req := range s { + if req.Type() == WithdrawalRequestType { + wr = append(wr, req.inner.(*WithdrawalRequest)) + } + } + return wr } type RequestData interface { @@ -154,6 +166,8 @@ func (r *Request) decode(b []byte) (RequestData, error) { switch b[0] { case DepositRequestType: inner = new(Deposit) + case WithdrawalRequestType: + inner = new(WithdrawalRequest) default: return nil, ErrRequestTypeNotSupported } diff --git a/core/types/withdrawal_request.go b/core/types/withdrawal_request.go new file mode 100644 index 000000000000..a38900b285fa --- /dev/null +++ b/core/types/withdrawal_request.go @@ -0,0 +1,79 @@ +// Copyright 2024 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" + "encoding/binary" + + "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 WithdrawalRequest -field-override withdrawalRequestMarshaling -out gen_withdrawal_request_json.go + +// WithdrawalRequest represents an EIP-7002 withdrawal request from source for +// the validator associated with the public key for amount. +type WithdrawalRequest struct { + Source common.Address `json:"sourceAddress"` + PublicKey [48]byte `json:"validatorPublicKey"` + Amount uint64 `json:"amount"` +} + +// field type overrides for gencodec +type withdrawalRequestMarshaling struct { + Amount hexutil.Uint64 +} + +func (w *WithdrawalRequest) Bytes() []byte { + out := make([]byte, 76) + copy(out, w.Source.Bytes()) + copy(out[20:], w.PublicKey[:]) + binary.LittleEndian.PutUint64(out, w.Amount) + return out +} + +// WithdrawalRequests implements DerivableList for withdrawal requests. +type WithdrawalRequests []*WithdrawalRequest + +// Len returns the length of s. +func (s WithdrawalRequests) Len() int { return len(s) } + +// EncodeIndex encodes the i'th withdrawal request to w. +func (s WithdrawalRequests) EncodeIndex(i int, w *bytes.Buffer) { + rlp.Encode(w, s[i]) +} + +// Requests creates a deep copy of each deposit and returns a slice of the +// withdrwawal requests as Request objects. +func (s WithdrawalRequests) Requests() (reqs Requests) { + for _, d := range s { + reqs = append(reqs, NewRequest(d)) + } + return +} + +func (w *WithdrawalRequest) requestType() byte { return WithdrawalRequestType } +func (w *WithdrawalRequest) encode(b *bytes.Buffer) error { return rlp.Encode(b, w) } +func (w *WithdrawalRequest) decode(input []byte) error { return rlp.DecodeBytes(input, w) } +func (w *WithdrawalRequest) copy() RequestData { + return &WithdrawalRequest{ + Source: w.Source, + PublicKey: w.PublicKey, + Amount: w.Amount, + } +} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index caedc3abc537..b6d3bcb0186f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -609,6 +609,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe "len(params.Transactions)", len(params.Transactions), "len(params.Withdrawals)", len(params.Withdrawals), "len(params.Deposits)", len(params.Deposits), + "len(params.WithdrawalRequests)", len(params.WithdrawalRequests), "beaconRoot", beaconRoot, "error", err) return api.invalid(err, nil), nil diff --git a/miner/worker.go b/miner/worker.go index 297f02f7b1ce..2e3610120169 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -113,10 +113,18 @@ func (miner *Miner) generateWork(params *generateParams) *newPayloadResult { } // Read requests if Prague is enabled. if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) { - requests, err := core.ParseDepositLogs(allLogs, miner.chainConfig) + // Parse Deposits + requests := make([]*types.Request, 0) + reqs, err := core.ParseDepositLogs(allLogs, miner.chainConfig) if err != nil { return &newPayloadResult{err: err} } + requests = append(requests, reqs...) + // Process WithdrawalRequests + context := core.NewEVMBlockContext(work.header, miner.chain, nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, work.state, miner.chainConfig, vm.Config{}) + wxs := core.ProcessDequeueWithdrawalRequests(vmenv, work.state) + requests = append(requests, wxs...) body.Requests = requests } block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts) diff --git a/params/protocol_params.go b/params/protocol_params.go index 8ffe8ee75db1..2ba7ef758f3a 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -187,10 +187,10 @@ var ( // BeaconRootsAddress is the address where historical beacon roots are stored as per EIP-4788 BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") - // BeaconRootsCode is the code where historical beacon roots are stored as per EIP-4788 BeaconRootsCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500") - + // WithdrawalRequests is the address where the EIP-7002 withdrawal requests queue is maintained. + WithdrawalRequestsAddress = common.HexToAddress("0x00A3ca265EBcb825B45F985A16CEFB49958cE017") // SystemAddress is where the system-transaction is sent from as per EIP-4788 SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") )