From cc3e859d4181879072dc9bffd8f1d9d7d4182b53 Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 4 Sep 2024 14:41:24 +0200 Subject: [PATCH 1/8] all: implement eip-7002 EL triggered withdrawal requests --- beacon/engine/gen_ed.go | 82 ++++++++-------- beacon/engine/types.go | 70 ++++++++------ cmd/evm/internal/t8ntool/execution.go | 109 +++++++++++++--------- core/blockchain_test.go | 99 ++++++++++++++++++++ core/chain_makers.go | 7 ++ core/state_processor.go | 40 ++++++++ core/types/gen_withdrawal_request_json.go | 53 +++++++++++ core/types/request.go | 5 +- core/types/withdrawal_request.go | 80 ++++++++++++++++ eth/catalyst/api.go | 32 ++++--- miner/worker.go | 10 +- params/protocol_params.go | 4 +- 12 files changed, 463 insertions(+), 128 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 b2eb1dc9822..51595644b7d 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -17,25 +17,26 @@ 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"` - ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + 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"` + ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -61,6 +62,7 @@ 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 enc.ExecutionWitness = e.ExecutionWitness return json.Marshal(&enc) } @@ -68,25 +70,26 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { // 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"` - ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + 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"` + ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -163,6 +166,9 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.Deposits != nil { e.Deposits = *dec.Deposits } + if dec.WithdrawalRequests != nil { + e.WithdrawalRequests = *dec.WithdrawalRequests + } if dec.ExecutionWitness != nil { e.ExecutionWitness = dec.ExecutionWitness } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 19d2b2823e3..cb735c561d7 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -60,25 +60,26 @@ 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"` - ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + 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"` + ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } // JSON type overrides for executableData. @@ -233,7 +234,9 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b h := types.DeriveSha(types.Withdrawals(data.Withdrawals), trie.NewStackTrie(nil)) withdrawalsRoot = &h } - // Compute requestsHash if any requests are non-nil. + // 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 @@ -243,9 +246,17 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b for _, d := range data.Deposits { requests = append(requests, types.NewRequest(d)) } + } + if data.WithdrawalRequests != nil { + for _, w := range data.WithdrawalRequests { + requests = append(requests, types.NewRequest(w)) + } + } + if requests != nil { h := types.DeriveSha(requests, trie.NewStackTrie(nil)) requestsHash = &h } + header := &types.Header{ ParentHash: data.ParentHash, UncleHash: types.EmptyUncleHash, @@ -320,22 +331,27 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. // assigns them to the associated fields in ExecutableData. func setRequests(requests types.Requests, data *ExecutableData) { if 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. + // If requests is non-nil, it means the requests are available in block and + // we should return an empty slice instead of nil. data.Deposits = make(types.Deposits, 0) + data.WithdrawalRequests = make(types.WithdrawalRequests, 0) } for _, r := range requests { - if d, ok := r.Inner().(*types.Deposit); ok { - data.Deposits = append(data.Deposits, d) + switch v := r.Inner().(type) { + case *types.Deposit: + data.Deposits = append(data.Deposits, v) + case *types.WithdrawalRequest: + data.WithdrawalRequests = append(data.WithdrawalRequests, v) } } } // ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange type ExecutionPayloadBody struct { - TransactionData []hexutil.Bytes `json:"transactions"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - Deposits types.Deposits `json:"depositRequests"` + TransactionData []hexutil.Bytes `json:"transactions"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + Deposits types.Deposits `json:"depositRequests"` + WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` } // Client identifiers to support ClientVersionV1. diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index a0805722d66..6460e4e41ba 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 { @@ -354,33 +355,14 @@ 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) } - // 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), - } - if pre.Env.Withdrawals != nil { - h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) - execRs.WithdrawalsRoot = &h - } - if vmContext.BlobBaseFee != nil { - execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) - execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) - } + // Retrieve deposit and withdrawal requests + var ( + depositRequests types.Deposits + withdrawalRequests types.WithdrawalRequests + requestsHash *common.Hash + ) if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) { - // Parse the requests from the logs + // Parse deposit requests from the logs var allLogs []*types.Log for _, receipt := range receipts { allLogs = append(allLogs, receipt.Logs...) @@ -389,17 +371,52 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.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)) - execRs.RequestsHash = &h + requestsHash = &h // Get the deposits from the requests - deposits := make(types.Deposits, 0) + depositRequests = make(types.Deposits, 0) + withdrawalRequests = make(types.WithdrawalRequests, 0) for _, req := range requests { - if dep, ok := req.Inner().(*types.Deposit); ok { - deposits = append(deposits, dep) + switch v := req.Inner().(type) { + case *types.Deposit: + depositRequests = append(depositRequests, v) + case *types.WithdrawalRequest: + withdrawalRequests = append(withdrawalRequests, v) } } - execRs.DepositRequests = &deposits + } + // 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), + RequestsHash: requestsHash, + DepositRequests: depositRequests, + WithdrawalRequests: withdrawalRequests, + } + if pre.Env.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) + execRs.WithdrawalsRoot = &h + } + if vmContext.BlobBaseFee != nil { + execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) + execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) } // Re-create statedb instance with new root upon the updated database // for accessing latest states. diff --git a/core/blockchain_test.go b/core/blockchain_test.go index fe0c527a009..a4d418dab82 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -4314,3 +4315,101 @@ 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.Requests() + if len(got) != 2 { + t.Fatalf("wrong number of withdrawal requests: wanted 2, got %d", len(wxs)) + } + for i, want := range wxs { + got, ok := got[i].Inner().(*types.WithdrawalRequest) + if !ok { + t.Fatalf("expected withdrawal request") + } + if want.Source != got.Source { + t.Fatalf("wrong source address: want %s, got %s", want.Source, got.Source) + } + if want.PublicKey != got.PublicKey { + t.Fatalf("wrong public key: want %s, got %s", common.Bytes2Hex(want.PublicKey[:]), common.Bytes2Hex(got.PublicKey[:])) + } + if want.Amount != got.Amount { + t.Fatalf("wrong amount: want %d, got %d", want.Amount, got.Amount) + } + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index dcaa36e08cc..fde7484d2b2 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 2844693a9e5..4e3e27dab96 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,6 +17,7 @@ package core import ( + "encoding/binary" "fmt" "math/big" @@ -103,6 +104,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) @@ -270,3 +273,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/gen_withdrawal_request_json.go b/core/types/gen_withdrawal_request_json.go new file mode 100644 index 00000000000..655e1199170 --- /dev/null +++ b/core/types/gen_withdrawal_request_json.go @@ -0,0 +1,53 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "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 hexutil.Bytes `json:"validatorPubkey"` + 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 *hexutil.Bytes `json:"validatorPubkey"` + 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 { + if len(*dec.PublicKey) != len(w.PublicKey) { + return errors.New("field 'validatorPubkey' has wrong length, need 48 items") + } + copy(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 7b1cade26e7..c3f6417e49a 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 @@ -149,6 +150,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 00000000000..8a451f3f5f3 --- /dev/null +++ b/core/types/withdrawal_request.go @@ -0,0 +1,80 @@ +// 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:"validatorPubkey"` + Amount uint64 `json:"amount"` +} + +// field type overrides for gencodec +type withdrawalRequestMarshaling struct { + PublicKey hexutil.Bytes + 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 7ea1c9ee076..3515799854f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -872,12 +872,13 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin for i, hash := range hashes { block := api.eth.BlockChain().GetBlockByHash(hash) body := getBody(block) + // Nil out the V2 values, clients should know to not request V1 objects + // after Prague. if body != nil { - // Nil out the V2 values, clients should know to not request V1 objects - // after Prague. body.Deposits = nil + bodies[i].WithdrawalRequests = nil + bodies[i] = body } - bodies[i] = body } return bodies } @@ -905,6 +906,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) for i := range bodies { if bodies[i] != nil { bodies[i].Deposits = nil + bodies[i].WithdrawalRequests = nil } } return bodies, nil @@ -943,10 +945,11 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody { } var ( - body = block.Body() - txs = make([]hexutil.Bytes, len(body.Transactions)) - withdrawals = body.Withdrawals - depositRequests types.Deposits + body = block.Body() + txs = make([]hexutil.Bytes, len(body.Transactions)) + withdrawals = body.Withdrawals + depositRequests types.Deposits + withdrawalRequests types.WithdrawalRequests ) for j, tx := range body.Transactions { @@ -963,15 +966,18 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody { // type has activated yet or if there are just no requests of that type from // only the block. for _, req := range block.Requests() { - if d, ok := req.Inner().(*types.Deposit); ok { - depositRequests = append(depositRequests, d) + switch v := req.Inner().(type) { + case *types.Deposit: + depositRequests = append(depositRequests, v) + case *types.WithdrawalRequest: + withdrawalRequests = append(withdrawalRequests, v) } } } - return &engine.ExecutionPayloadBody{ - TransactionData: txs, - Withdrawals: withdrawals, - Deposits: depositRequests, + TransactionData: txs, + Withdrawals: withdrawals, + Deposits: depositRequests, + WithdrawalRequests: withdrawalRequests, } } diff --git a/miner/worker.go b/miner/worker.go index 1f49118c474..f0bdd027713 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 638f58a3399..7da77b18ebd 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -189,10 +189,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") // HistoryStorageAddress is where the historical block hashes are stored. From 9d87ac7c12ae6c2e81841c02997466c46a21a420 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Fri, 21 Jun 2024 17:41:17 +0000 Subject: [PATCH 2/8] all: implement eip-7251 consolidation requests Co-authored-by: Mario Vega Co-authored-by: lightclient --- beacon/engine/gen_ed.go | 86 +++++++------- beacon/engine/types.go | 56 +++++---- cmd/evm/internal/t8ntool/execution.go | 76 +++++++------ core/blockchain_test.go | 114 +++++++++++++++---- core/chain_makers.go | 2 + core/state_processor.go | 43 +++++++ core/types/consolidation_request.go | 79 +++++++++++++ core/types/gen_consolidation_request_json.go | 56 +++++++++ core/types/request.go | 7 +- eth/catalyst/api.go | 28 +++-- eth/catalyst/api_test.go | 41 +++++-- miner/worker.go | 3 + params/protocol_params.go | 2 + 13 files changed, 452 insertions(+), 141 deletions(-) create mode 100644 core/types/consolidation_request.go create mode 100644 core/types/gen_consolidation_request_json.go diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index 51595644b7d..ebab7c0cf8b 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -17,26 +17,27 @@ 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"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` - ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + 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"` + ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests"` + ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -63,6 +64,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.Deposits = e.Deposits enc.WithdrawalRequests = e.WithdrawalRequests + enc.ConsolidationRequests = e.ConsolidationRequests enc.ExecutionWitness = e.ExecutionWitness return json.Marshal(&enc) } @@ -70,26 +72,27 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { // 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"` - WithdrawalRequests *types.WithdrawalRequests `json:"withdrawalRequests"` - ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + 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"` + ConsolidationRequests *types.ConsolidationRequests `json:"consolidationRequests"` + ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -169,6 +172,9 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.WithdrawalRequests != nil { e.WithdrawalRequests = *dec.WithdrawalRequests } + if dec.ConsolidationRequests != nil { + e.ConsolidationRequests = *dec.ConsolidationRequests + } if dec.ExecutionWitness != nil { e.ExecutionWitness = dec.ExecutionWitness } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index cb735c561d7..bb24f223df3 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -60,26 +60,27 @@ 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"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` - ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` + 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"` + ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests"` + ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"` } // JSON type overrides for executableData. @@ -252,6 +253,9 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b requests = append(requests, types.NewRequest(w)) } } + if data.ConsolidationRequests != nil { + requests = append(requests, data.ConsolidationRequests.Requests()...) + } if requests != nil { h := types.DeriveSha(requests, trie.NewStackTrie(nil)) requestsHash = &h @@ -335,6 +339,7 @@ func setRequests(requests types.Requests, data *ExecutableData) { // we should return an empty slice instead of nil. data.Deposits = make(types.Deposits, 0) data.WithdrawalRequests = make(types.WithdrawalRequests, 0) + data.ConsolidationRequests = make(types.ConsolidationRequests, 0) } for _, r := range requests { switch v := r.Inner().(type) { @@ -342,16 +347,19 @@ func setRequests(requests types.Requests, data *ExecutableData) { data.Deposits = append(data.Deposits, v) case *types.WithdrawalRequest: data.WithdrawalRequests = append(data.WithdrawalRequests, v) + case *types.ConsolidationRequest: + data.ConsolidationRequests = append(data.ConsolidationRequests, v) } } } // ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange type ExecutionPayloadBody struct { - TransactionData []hexutil.Bytes `json:"transactions"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - Deposits types.Deposits `json:"depositRequests"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` + TransactionData []hexutil.Bytes `json:"transactions"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + Deposits types.Deposits `json:"depositRequests"` + WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests"` + ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests"` } // Client identifiers to support ClientVersionV1. diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 6460e4e41ba..f414eaf2a4a 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -53,22 +53,23 @@ 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"` - WithdrawalRequests types.WithdrawalRequests `json:"withdrawalRequests,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"` + ConsolidationRequests types.ConsolidationRequests `json:"consolidationRequests,omitempty"` } type ommer struct { @@ -357,9 +358,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } // Retrieve deposit and withdrawal requests var ( - depositRequests types.Deposits - withdrawalRequests types.WithdrawalRequests - requestsHash *common.Hash + depositRequests types.Deposits + withdrawalRequests types.WithdrawalRequests + consolidationRequests types.ConsolidationRequests + requestsHash *common.Hash ) if chainConfig.IsPrague(vmContext.BlockNumber, vmContext.Time) { // Parse deposit requests from the logs @@ -375,18 +377,25 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, vmenv := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) wxs := core.ProcessDequeueWithdrawalRequests(vmenv, statedb) requests = append(requests, wxs...) + // Process the consolidation requests contract execution + cxs := core.ProcessDequeueConsolidationRequests(vmenv, statedb) + requests = append(requests, cxs...) // Calculate the requests root h := types.DeriveSha(requests, trie.NewStackTrie(nil)) requestsHash = &h - // Get the deposits from the requests + + // Break out individual request types. depositRequests = make(types.Deposits, 0) withdrawalRequests = make(types.WithdrawalRequests, 0) + consolidationRequests = make(types.ConsolidationRequests, 0) for _, req := range requests { switch v := req.Inner().(type) { case *types.Deposit: depositRequests = append(depositRequests, v) case *types.WithdrawalRequest: withdrawalRequests = append(withdrawalRequests, v) + case *types.ConsolidationRequest: + consolidationRequests = append(consolidationRequests, v) } } } @@ -396,19 +405,20 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, 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), - RequestsHash: requestsHash, - DepositRequests: depositRequests, - WithdrawalRequests: withdrawalRequests, + 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, + ConsolidationRequests: consolidationRequests, } if pre.Env.Withdrawals != nil { h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index a4d418dab82..bb20e03b3a9 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4316,9 +4316,8 @@ 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) { +// TestRequests verifies that Prague requests are processed correctly. +func TestRequests(t *testing.T) { var ( engine = beacon.NewFaker() @@ -4330,8 +4329,9 @@ func TestEIP7002(t *testing.T) { gspec = &Genesis{ Config: &config, Alloc: types.GenesisAlloc{ - addr: {Balance: funds}, - params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")}, + addr: {Balance: funds}, + params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")}, + params.ConsolidationRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd")}, }, } ) @@ -4357,28 +4357,66 @@ func TestEIP7002(t *testing.T) { Amount: 1337, }, } + cxs := types.ConsolidationRequests{ + { + Source: addr, + SourcePublicKey: [48]byte{13, 37}, + TargetPublicKey: [48]byte{11, 11}, + }, + { + Source: addr, + SourcePublicKey: [48]byte{42, 42}, + TargetPublicKey: [48]byte{11, 11}, + }, + } - _, 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, + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 3, func(i int, b *BlockGen) { + switch i { + case 0: + // Block 1: submit withdrawal requests + for _, 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: b.TxNonce(addr), + 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) + } + case 1: + // Block 2: submit consolidation requests + for _, cx := range cxs { + data := make([]byte, 96) + copy(data, cx.SourcePublicKey[:]) + copy(data[48:], cx.TargetPublicKey[:]) + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: b.TxNonce(addr), + To: ¶ms.ConsolidationRequestsAddress, + 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) } - 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) @@ -4387,6 +4425,8 @@ func TestEIP7002(t *testing.T) { if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } + + // Verify the withdrawal requests match. block := chain.GetBlockByNumber(1) if block == nil { t.Fatalf("failed to retrieve block 1") @@ -4395,7 +4435,7 @@ func TestEIP7002(t *testing.T) { // Verify the withdrawal requests match. got := block.Requests() if len(got) != 2 { - t.Fatalf("wrong number of withdrawal requests: wanted 2, got %d", len(wxs)) + t.Fatalf("wrong number of withdrawal requests: wanted 2, got %d", len(got)) } for i, want := range wxs { got, ok := got[i].Inner().(*types.WithdrawalRequest) @@ -4412,4 +4452,30 @@ func TestEIP7002(t *testing.T) { t.Fatalf("wrong amount: want %d, got %d", want.Amount, got.Amount) } } + + // Verify the consolidation requests match. Even though both requests are sent + // in block two, only one is dequeued at a time. + for i, want := range cxs { + block := chain.GetBlockByNumber(uint64(i + 2)) + if block == nil { + t.Fatalf("failed to retrieve block") + } + requests := block.Requests() + if len(requests) != 1 { + t.Fatalf("wrong number of consolidation requests: wanted 1, got %d", len(got)) + } + got, ok := requests[0].Inner().(*types.ConsolidationRequest) + if !ok { + t.Fatalf("expected consolidation request") + } + if want.Source != got.Source { + t.Fatalf("wrong source address: want %s, got %s", want.Source, got.Source) + } + if want.SourcePublicKey != got.SourcePublicKey { + t.Fatalf("wrong source public key: want %s, got %s", common.Bytes2Hex(want.SourcePublicKey[:]), common.Bytes2Hex(got.SourcePublicKey[:])) + } + if want.TargetPublicKey != got.TargetPublicKey { + t.Fatalf("wrong target public key: want %s, got %s", common.Bytes2Hex(want.TargetPublicKey[:]), common.Bytes2Hex(got.TargetPublicKey[:])) + } + } } diff --git a/core/chain_makers.go b/core/chain_makers.go index fde7484d2b2..60e59e057c2 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -362,6 +362,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse ) wxs := ProcessDequeueWithdrawalRequests(vmenv, statedb) requests = append(requests, wxs...) + cxs := ProcessDequeueConsolidationRequests(vmenv, statedb) + requests = append(requests, cxs...) } 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 4e3e27dab96..b4e61f5fcfb 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -106,6 +106,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } wxs := ProcessDequeueWithdrawalRequests(vmenv, statedb) requests = append(requests, wxs...) + cxs := ProcessDequeueConsolidationRequests(vmenv, statedb) + requests = append(requests, cxs...) } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) @@ -310,3 +312,44 @@ func ProcessDequeueWithdrawalRequests(vmenv *vm.EVM, statedb *state.StateDB) typ } return reqs } + +// ProcessDequeueConsolidationRequests applies the EIP-7251 system call to the consolidation requests contract. +func ProcessDequeueConsolidationRequests(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.ConsolidationRequestsAddress, + } + vmenv.Reset(NewEVMTxContext(msg), statedb) + statedb.AddAddressToAccessList(params.ConsolidationRequestsAddress) + 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)/116; i++ { + start := i * 116 + var ( + sourcePubkey [48]byte + targetPubkey [48]byte + ) + copy(sourcePubkey[:], ret[start+20:start+20+48]) + copy(targetPubkey[:], ret[start+20+48:start+20+48+48]) + cx := &types.ConsolidationRequest{ + Source: common.BytesToAddress(ret[start : start+20]), + SourcePublicKey: sourcePubkey, + TargetPublicKey: targetPubkey, + } + reqs = append(reqs, types.NewRequest(cx)) + } + return reqs +} diff --git a/core/types/consolidation_request.go b/core/types/consolidation_request.go new file mode 100644 index 00000000000..4c3e8e49b22 --- /dev/null +++ b/core/types/consolidation_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" + + "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 ConsolidationRequest -field-override consolidationRequestMarshaling -out gen_consolidation_request_json.go + +// ConsolidationRequest represents an EIP-7251 consolidation request from source for +// the validator associated with the source public key to a target public key. +type ConsolidationRequest struct { + Source common.Address `json:"sourceAddress"` + SourcePublicKey [48]byte `json:"sourcePubkey"` + TargetPublicKey [48]byte `json:"targetPubkey"` +} + +// field type overrides for gencodec +type consolidationRequestMarshaling struct { + SourcePublicKey hexutil.Bytes + TargetPublicKey hexutil.Bytes +} + +func (c *ConsolidationRequest) Bytes() []byte { + out := make([]byte, 116) + copy(out, c.Source.Bytes()) + copy(out[20:], c.SourcePublicKey[:]) + copy(out[68:], c.TargetPublicKey[:]) + return out +} + +// ConsolidationRequests implements DerivableList for consolidation requests. +type ConsolidationRequests []*ConsolidationRequest + +// Len returns the length of s. +func (s ConsolidationRequests) Len() int { return len(s) } + +// EncodeIndex encodes the i'th consolidation request to c. +func (s ConsolidationRequests) EncodeIndex(i int, c *bytes.Buffer) { + rlp.Encode(c, s[i]) +} + +// Requests creates a deep copy of each deposit and returns a slice of the +// withdrwawal requests as Request objects. +func (s ConsolidationRequests) Requests() (reqs Requests) { + for _, d := range s { + reqs = append(reqs, NewRequest(d)) + } + return +} + +func (c *ConsolidationRequest) requestType() byte { return ConsolidationRequestType } +func (c *ConsolidationRequest) encode(b *bytes.Buffer) error { return rlp.Encode(b, c) } +func (c *ConsolidationRequest) decode(input []byte) error { return rlp.DecodeBytes(input, c) } +func (c *ConsolidationRequest) copy() RequestData { + return &ConsolidationRequest{ + Source: c.Source, + SourcePublicKey: c.SourcePublicKey, + TargetPublicKey: c.TargetPublicKey, + } +} diff --git a/core/types/gen_consolidation_request_json.go b/core/types/gen_consolidation_request_json.go new file mode 100644 index 00000000000..699abb4a7d9 --- /dev/null +++ b/core/types/gen_consolidation_request_json.go @@ -0,0 +1,56 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*consolidationRequestMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (c ConsolidationRequest) MarshalJSON() ([]byte, error) { + type ConsolidationRequest struct { + Source common.Address `json:"sourceAddress"` + SourcePublicKey hexutil.Bytes `json:"sourcePubkey"` + TargetPublicKey hexutil.Bytes `json:"targetPubkey"` + } + var enc ConsolidationRequest + enc.Source = c.Source + enc.SourcePublicKey = c.SourcePublicKey[:] + enc.TargetPublicKey = c.TargetPublicKey[:] + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (c *ConsolidationRequest) UnmarshalJSON(input []byte) error { + type ConsolidationRequest struct { + Source *common.Address `json:"sourceAddress"` + SourcePublicKey *hexutil.Bytes `json:"sourcePubkey"` + TargetPublicKey *hexutil.Bytes `json:"targetPubkey"` + } + var dec ConsolidationRequest + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Source != nil { + c.Source = *dec.Source + } + if dec.SourcePublicKey != nil { + if len(*dec.SourcePublicKey) != len(c.SourcePublicKey) { + return errors.New("field 'sourcePubkey' has wrong length, need 48 items") + } + copy(c.SourcePublicKey[:], *dec.SourcePublicKey) + } + if dec.TargetPublicKey != nil { + if len(*dec.TargetPublicKey) != len(c.TargetPublicKey) { + return errors.New("field 'targetPubkey' has wrong length, need 48 items") + } + copy(c.TargetPublicKey[:], *dec.TargetPublicKey) + } + return nil +} diff --git a/core/types/request.go b/core/types/request.go index c3f6417e49a..b8a0ef7afcc 100644 --- a/core/types/request.go +++ b/core/types/request.go @@ -32,8 +32,9 @@ var ( // Request types. const ( - DepositRequestType = 0x00 - WithdrawalRequestType = 0x01 + DepositRequestType = 0x00 + WithdrawalRequestType = 0x01 + ConsolidationRequestType = 0x02 ) // Request is an EIP-7685 request object. It represents execution layer @@ -152,6 +153,8 @@ func (r *Request) decode(b []byte) (RequestData, error) { inner = new(Deposit) case WithdrawalRequestType: inner = new(WithdrawalRequest) + case ConsolidationRequestType: + inner = new(ConsolidationRequest) default: return nil, ErrRequestTypeNotSupported } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 3515799854f..047b288de6c 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -877,6 +877,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin if body != nil { body.Deposits = nil bodies[i].WithdrawalRequests = nil + bodies[i].ConsolidationRequests = nil bodies[i] = body } } @@ -907,6 +908,7 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) if bodies[i] != nil { bodies[i].Deposits = nil bodies[i].WithdrawalRequests = nil + bodies[i].ConsolidationRequests = nil } } return bodies, nil @@ -945,11 +947,12 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody { } var ( - body = block.Body() - txs = make([]hexutil.Bytes, len(body.Transactions)) - withdrawals = body.Withdrawals - depositRequests types.Deposits - withdrawalRequests types.WithdrawalRequests + body = block.Body() + txs = make([]hexutil.Bytes, len(body.Transactions)) + withdrawals = body.Withdrawals + depositRequests types.Deposits + withdrawalRequests types.WithdrawalRequests + consolidationRequests types.ConsolidationRequests ) for j, tx := range body.Transactions { @@ -965,19 +968,26 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody { // TODO: this isn't future proof because we can't determine if a request // type has activated yet or if there are just no requests of that type from // only the block. + depositRequests = make(types.Deposits, 0) + withdrawalRequests = make(types.WithdrawalRequests, 0) + consolidationRequests = make(types.ConsolidationRequests, 0) + for _, req := range block.Requests() { switch v := req.Inner().(type) { case *types.Deposit: depositRequests = append(depositRequests, v) case *types.WithdrawalRequest: withdrawalRequests = append(withdrawalRequests, v) + case *types.ConsolidationRequest: + consolidationRequests = append(consolidationRequests, v) } } } return &engine.ExecutionPayloadBody{ - TransactionData: txs, - Withdrawals: withdrawals, - Deposits: depositRequests, - WithdrawalRequests: withdrawalRequests, + TransactionData: txs, + Withdrawals: withdrawals, + Deposits: depositRequests, + WithdrawalRequests: withdrawalRequests, + ConsolidationRequests: consolidationRequests, } } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 0a58e1eaee0..c7b5a17f535 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -80,6 +80,8 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { Nonce: 0, Balance: big.NewInt(0), }, + params.WithdrawalRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd")}, + params.ConsolidationRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd")}, }, ExtraData: []byte("test genesis"), Timestamp: 9000, @@ -1313,15 +1315,23 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) { // Each block, this callback will include two txs that generate body values like logs and requests. callback := func(parent *types.Header) { + if parent.Number.Cmp(big.NewInt(10)) == 0 { + // Make a block with empty requests to ensure nil / non-nil distinction is + // being maintained. + return + } var ( statedb, _ = ethservice.BlockChain().StateAt(parent.Root) // Create tx to trigger log generator. tx1, _ = types.SignTx(types.NewContractCreation(statedb.GetNonce(testAddr), new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) // Create tx to trigger deposit generator. tx2, _ = types.SignTx(types.NewTransaction(statedb.GetNonce(testAddr)+1, ethservice.APIBackend.ChainConfig().DepositContractAddress, new(big.Int), 500000, big.NewInt(2*params.InitialBaseFee), nil), types.LatestSigner(ethservice.BlockChain().Config()), testKey) + // Create tx to trigger withdrawal request. + tx3, _ = types.SignTx(types.NewTransaction(statedb.GetNonce(testAddr)+2, params.WithdrawalRequestsAddress, big.NewInt(42), 500000, big.NewInt(2*params.InitialBaseFee), make([]byte, 56)), types.LatestSigner(ethservice.BlockChain().Config()), testKey) + // Create tx to trigger consolidation request. + tx4, _ = types.SignTx(types.NewTransaction(statedb.GetNonce(testAddr)+3, params.ConsolidationRequestsAddress, big.NewInt(42), 500000, big.NewInt(2*params.InitialBaseFee), make([]byte, 96)), types.LatestSigner(ethservice.BlockChain().Config()), testKey) ) - ethservice.TxPool().Add([]*types.Transaction{tx1}, false, false) - ethservice.TxPool().Add([]*types.Transaction{tx2}, false, false) + ethservice.TxPool().Add([]*types.Transaction{tx1, tx2, tx3, tx4}, false, false) } // Make some withdrawals to include. @@ -1569,18 +1579,31 @@ func equalBody(a *types.Body, b *engine.ExecutionPayloadBody) bool { return false } - var deposits types.Deposits + var ( + dxs types.Deposits + wxs types.WithdrawalRequests + cxs types.ConsolidationRequests + ) if a.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. - deposits = make(types.Deposits, 0) - } - for _, r := range a.Requests { - if d, ok := r.Inner().(*types.Deposit); ok { - deposits = append(deposits, d) + dxs = make(types.Deposits, 0) + wxs = make(types.WithdrawalRequests, 0) + cxs = make(types.ConsolidationRequests, 0) + } + for _, req := range a.Requests { + switch v := req.Inner().(type) { + case *types.Deposit: + dxs = append(dxs, v) + case *types.WithdrawalRequest: + wxs = append(wxs, v) + case *types.ConsolidationRequest: + cxs = append(cxs, v) } } - return reflect.DeepEqual(deposits, b.Deposits) + return reflect.DeepEqual(dxs, b.Deposits) && + reflect.DeepEqual(wxs, b.WithdrawalRequests) && + reflect.DeepEqual(cxs, b.ConsolidationRequests) } func TestBlockToPayloadWithBlobs(t *testing.T) { diff --git a/miner/worker.go b/miner/worker.go index f0bdd027713..51e69898bec 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -125,6 +125,9 @@ func (miner *Miner) generateWork(params *generateParams) *newPayloadResult { vmenv := vm.NewEVM(context, vm.TxContext{}, work.state, miner.chainConfig, vm.Config{}) wxs := core.ProcessDequeueWithdrawalRequests(vmenv, work.state) requests = append(requests, wxs...) + // Process ConsolidationRequests + cxs := core.ProcessDequeueConsolidationRequests(vmenv, work.state) + requests = append(requests, cxs...) 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 7da77b18ebd..2e1ab388a86 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -193,6 +193,8 @@ var ( BeaconRootsCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500") // WithdrawalRequests is the address where the EIP-7002 withdrawal requests queue is maintained. WithdrawalRequestsAddress = common.HexToAddress("0x00A3ca265EBcb825B45F985A16CEFB49958cE017") + // ConsolidationRequests is the address where the EIP-7251 consolidation requests queue is maintained. + ConsolidationRequestsAddress = common.HexToAddress("0x00b42dbF2194e931E80326D950320f7d9Dbeac02") // SystemAddress is where the system-transaction is sent from as per EIP-4788 SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") // HistoryStorageAddress is where the historical block hashes are stored. From 9db178c799c9cd6558de2b466f97335a8ba1cc7b Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 26 Jun 2024 17:12:17 -0600 Subject: [PATCH 3/8] all: impl eip-7702 Co-authored-by: lightclient Co-authored-by: Mario Vega --- cmd/evm/internal/t8ntool/transaction.go | 2 +- cmd/evm/staterunner.go | 2 +- core/bench_test.go | 2 +- core/error.go | 3 + core/setcode_test.go | 131 ++++++++++++ core/state/statedb.go | 32 +++ core/state_processor_test.go | 4 +- core/state_transition.go | 76 ++++++- core/txpool/legacypool/legacypool.go | 5 +- core/txpool/validation.go | 2 +- core/types/gen_authorization.go | 74 +++++++ core/types/receipt.go | 4 +- core/types/transaction.go | 12 ++ core/types/transaction_marshalling.go | 125 ++++++++++-- core/types/transaction_signing.go | 75 +++++++ core/types/tx_setcode.go | 256 ++++++++++++++++++++++++ core/types/tx_setcode_test.go | 15 ++ core/vm/eips.go | 24 +++ core/vm/evm.go | 18 +- core/vm/instructions.go | 13 +- core/vm/interface.go | 3 + core/vm/jump_table.go | 1 + core/vm/operations_acl.go | 121 +++++++++++ params/protocol_params.go | 1 + tests/gen_stauthorization.go | 74 +++++++ tests/gen_sttransaction.go | 6 + tests/state_test.go | 1 + tests/state_test_util.go | 42 ++++ tests/transaction_test_util.go | 2 +- 29 files changed, 1078 insertions(+), 48 deletions(-) create mode 100644 core/setcode_test.go create mode 100644 core/types/gen_authorization.go create mode 100644 core/types/tx_setcode.go create mode 100644 core/types/tx_setcode_test.go create mode 100644 tests/gen_stauthorization.go diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 7f66ba4d85d..64e21b24fd6 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error { r.Address = sender } // Check intrinsic gas - if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, + if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.AuthList(), tx.To() == nil, chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil { r.Error = err results = append(results, r) diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index fc2bf8223f3..f7fe31337bf 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -91,7 +91,7 @@ func runStateTest(fname string, cfg vm.Config, dump bool) error { } var testsByName map[string]tests.StateTest if err := json.Unmarshal(src, &testsByName); err != nil { - return err + return fmt.Errorf("unable to read test file: %w", err) } // Iterate over all the tests, run them and aggregate the results diff --git a/core/bench_test.go b/core/bench_test.go index 639d36e9aea..aade2b36b9e 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -83,7 +83,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { return func(i int, gen *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) - gas, _ := IntrinsicGas(data, nil, false, false, false, false) + gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false) signer := gen.Signer() gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { diff --git a/core/error.go b/core/error.go index 161538fe432..f00a8c075a8 100644 --- a/core/error.go +++ b/core/error.go @@ -112,4 +112,7 @@ var ( // ErrBlobTxCreate is returned if a blob transaction has no explicit to field. ErrBlobTxCreate = errors.New("blob transaction of type create") + + // ErrEmptyAuthList is returned if a set code transaction has an empty auth list. + ErrEmptyAuthList = errors.New("set code transaction with empty auth list") ) diff --git a/core/setcode_test.go b/core/setcode_test.go new file mode 100644 index 00000000000..72a95f7859d --- /dev/null +++ b/core/setcode_test.go @@ -0,0 +1,131 @@ +package core + +import ( + "bytes" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +func TestEIP7702(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + engine = beacon.NewFaker() + + // A sender who makes transactions, has some funds + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + 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: &config, + Alloc: types.GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + // The address 0xAAAA sstores 1 into slot 2. + aa: { + Code: []byte{ + byte(vm.PC), // [0] + byte(vm.DUP1), // [0,0] + byte(vm.DUP1), // [0,0,0] + byte(vm.DUP1), // [0,0,0,0] + byte(vm.PUSH1), 0x01, // [0,0,0,0,1] (value) + byte(vm.PUSH20), addr2[0], addr2[1], addr2[2], addr2[3], addr2[4], addr2[5], addr2[6], addr2[7], addr2[8], addr2[9], addr2[10], addr2[11], addr2[12], addr2[13], addr2[14], addr2[15], addr2[16], addr2[17], addr2[18], addr2[19], + byte(vm.GAS), + byte(vm.CALL), + byte(vm.STOP), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + // The address 0xBBBB sstores 42 into slot 42. + bb: { + Code: []byte{ + byte(vm.PUSH1), 0x42, + byte(vm.DUP1), + byte(vm.SSTORE), + byte(vm.STOP), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + 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) + + auth1, _ := types.SignAuth(&types.Authorization{ + ChainID: new(big.Int).Set(gspec.Config.ChainID), + Address: aa, + Nonce: 1, + }, key1) + + auth2, _ := types.SignAuth(&types.Authorization{ + ChainID: new(big.Int), + Address: bb, + Nonce: 0, + }, key2) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(aa) + txdata := &types.SetCodeTx{ + ChainID: uint256.MustFromBig(gspec.Config.ChainID), + Nonce: 0, + To: addr1, + Gas: 500000, + GasFeeCap: uint256.MustFromBig(newGwei(5)), + GasTipCap: uint256.NewInt(2), + AuthList: []*types.Authorization{auth1, auth2}, + } + tx := types.NewTx(txdata) + tx, err := types.SignTx(tx, signer, key1) + if err != nil { + t.Fatalf("%s", err) + } + 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) + } + + var ( + state, _ = chain.State() + fortyTwo = common.BytesToHash([]byte{0x42}) + actual = state.GetState(addr2, fortyTwo) + ) + if code, want := state.GetCode(addr1), types.AddressToDelegation(auth1.Address); !bytes.Equal(code, want) { + t.Fatalf("addr1 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want)) + } + if code, want := state.GetCode(addr2), types.AddressToDelegation(auth2.Address); !bytes.Equal(code, want) { + t.Fatalf("addr2 code incorrect: got %s, want %s", common.Bytes2Hex(code), common.Bytes2Hex(want)) + } + if actual.Cmp(fortyTwo) != 0 { + t.Fatalf("addr2 storage wrong: expected %d, got %d", fortyTwo, actual) + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go index f81c8a055b0..65131de9d0a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -384,6 +384,22 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { return common.Hash{} } +func (s *StateDB) ResolveCode(addr common.Address) []byte { + stateObject := s.resolveStateObject(addr) + if stateObject != nil { + return stateObject.Code() + } + return nil +} + +func (s *StateDB) ResolveCodeHash(addr common.Address) common.Hash { + stateObject := s.resolveStateObject(addr) + if stateObject != nil { + return common.BytesToHash(stateObject.CodeHash()) + } + return common.Hash{} +} + // GetState retrieves the value associated with the specific key. func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) @@ -639,6 +655,18 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { return obj } +func (s *StateDB) resolveStateObject(addr common.Address) *stateObject { + obj := s.getStateObject(addr) + if obj == nil { + return nil + } + addr, ok := types.ParseDelegation(obj.Code()) + if !ok { + return obj + } + return s.getStateObject(addr) +} + func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } @@ -1381,6 +1409,10 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(sender) if dst != nil { al.AddAddress(*dst) + // TODO: is this right? + if addr, ok := types.ParseDelegation(s.GetCode(*dst)); ok { + al.AddAddress(addr) + } // If it's a create-tx, the destination will be added inside evm.create } for _, addr := range precompiles { diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 3ed0eb75799..15ed89a85ae 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -431,12 +431,12 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr var ( code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) - intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true) + intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true) // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness // will not contain that copied data. // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) - intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true) + intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true) ) func TestProcessVerkle(t *testing.T) { diff --git a/core/state_transition.go b/core/state_transition.go index 37807d3d2b6..1cc24329281 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "fmt" "math" "math/big" @@ -27,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -68,7 +70,7 @@ func (result *ExecutionResult) Revert() []byte { } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, authList types.AuthorizationList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) { // Set the starting gas for the raw transaction var gas uint64 if isContractCreation && isHomestead { @@ -114,6 +116,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas } + if authList != nil { + gas += uint64(len(authList)) * params.CallNewAccountGas + } return gas, nil } @@ -141,6 +146,7 @@ type Message struct { AccessList types.AccessList BlobGasFeeCap *big.Int BlobHashes []common.Hash + AuthList types.AuthorizationList // When SkipAccountChecks is true, the message nonce is not checked against the // account nonce in state. It also disables checking that the sender is an EOA. @@ -160,6 +166,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In Value: tx.Value(), Data: tx.Data(), AccessList: tx.AccessList(), + AuthList: tx.AuthList(), SkipAccountChecks: false, BlobHashes: tx.BlobHashes(), BlobGasFeeCap: tx.BlobGasFeeCap(), @@ -294,10 +301,10 @@ func (st *StateTransition) preCheck() error { msg.From.Hex(), stNonce) } // Make sure the sender is an EOA - codeHash := st.state.GetCodeHash(msg.From) - if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { + code := st.state.GetCode(msg.From) + if 0 < len(code) && !bytes.HasPrefix(code, []byte{0xef, 0x01, 0x00}) { return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, - msg.From.Hex(), codeHash) + msg.From.Hex(), st.state.GetCodeHash(msg.From)) } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) @@ -357,6 +364,27 @@ func (st *StateTransition) preCheck() error { } } } + + // Check that auth list isn't empty. + if msg.AuthList != nil && len(msg.AuthList) == 0 { + return fmt.Errorf("%w: address %v", ErrEmptyAuthList, msg.From.Hex()) + } + + // TODO: remove after this spec change is merged: + // https://github.com/ethereum/EIPs/pull/8845 + if msg.AuthList != nil { + var ( + secp256k1N = secp256k1.S256().Params().N + secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) + ) + for _, auth := range msg.AuthList { + if auth.V.Cmp(common.Big1) > 0 || auth.S.Cmp(secp256k1halfN) > 0 { + w := fmt.Errorf("set code transaction with invalid auth signature") + return fmt.Errorf("%w: address %v", w, msg.From.Hex()) + } + } + } + return st.buyGas() } @@ -394,7 +422,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ) // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.AuthList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) if err != nil { return nil, err } @@ -433,6 +461,42 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // - reset transient storage(eip 1153) st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + if !contractCreation { + // Increment the nonce for the next transaction + st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) + } + + // Check authorizations list validity. + if msg.AuthList != nil { + for _, auth := range msg.AuthList { + // Verify chain ID is 0 or equal to current chain ID. + if auth.ChainID.Sign() != 0 && st.evm.ChainConfig().ChainID.Cmp(auth.ChainID) != 0 { + continue + } + authority, err := auth.Authority() + if err != nil { + continue + } + // Check the authority account 1) doesn't have code or has exisiting + // delegation 2) matches the auth's nonce + st.state.AddAddressToAccessList(authority) + code := st.state.GetCode(authority) + if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok { + continue + } + if have := st.state.GetNonce(authority); have != auth.Nonce { + continue + } + // If the account already exists in state, refund the new account cost + // charged in the initrinsic calculation. + if exists := st.state.Exist(authority); exists { + st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) + } + st.state.SetNonce(authority, auth.Nonce+1) + st.state.SetCode(authority, types.AddressToDelegation(auth.Address)) + } + } + var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err @@ -440,8 +504,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if contractCreation { ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value) } else { - // Increment the nonce for the next transaction - st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value) } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 5506ecc31fc..8bc9007daf1 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -278,7 +278,7 @@ func New(config Config, chain BlockChain) *LegacyPool { // pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction. func (pool *LegacyPool) Filter(tx *types.Transaction) bool { switch tx.Type() { - case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType: + case types.SetCodeTxType, types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType: return true default: return false @@ -610,7 +610,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro Accept: 0 | 1< 0 { + tip.Set(tx.GasTipCap.ToBig()) + } + return tip.Add(tip, baseFee) +} + +func (tx *SetCodeTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V.ToBig(), tx.R.ToBig(), tx.S.ToBig() +} + +func (tx *SetCodeTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID.SetFromBig(chainID) + tx.V.SetFromBig(v) + tx.R.SetFromBig(r) + tx.S.SetFromBig(s) +} + +func (tx *SetCodeTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *SetCodeTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} diff --git a/core/types/tx_setcode_test.go b/core/types/tx_setcode_test.go new file mode 100644 index 00000000000..3016ecb45a6 --- /dev/null +++ b/core/types/tx_setcode_test.go @@ -0,0 +1,15 @@ +package types + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestParseDelegation(t *testing.T) { + addr := common.Address{0x42} + d := append(DelegationPrefix, addr.Bytes()...) + if got, ok := ParseDelegation(d); !ok || addr != got { + t.Fatalf("failed to parse, got %s %v", got.Hex(), ok) + } +} diff --git a/core/vm/eips.go b/core/vm/eips.go index edd6ec8d0a2..7eef4c59a3b 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -40,6 +40,7 @@ var activators = map[int]func(*JumpTable){ 1344: enable1344, 1153: enable1153, 4762: enable4762, + 7702: enable7702, } // EnableEIP enables the given EIP on the config. @@ -533,3 +534,26 @@ func enable4762(jt *JumpTable) { } } } + +func enable7702(jt *JumpTable) { + jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP7702 + + jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].dynamicGas = gasEip7702CodeCheck + + jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].dynamicGas = gasEip7702CodeCheck + + jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALL].dynamicGas = gasCallEIP7702 + + jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALLCODE].dynamicGas = gasCallCodeEIP7702 + + jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[STATICCALL].dynamicGas = gasStaticCallEIP7702 + + jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702 +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 7617d843c7f..1c7efe5c857 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -230,7 +230,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. - code := evm.StateDB.GetCode(addr) + code := evm.StateDB.ResolveCode(addr) if witness := evm.StateDB.Witness(); witness != nil { witness.AddCode(code) } @@ -241,7 +241,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -302,9 +302,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) if witness := evm.StateDB.Witness(); witness != nil { - witness.AddCode(evm.StateDB.GetCode(addrCopy)) + witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -352,9 +352,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() if witness := evm.StateDB.Witness(); witness != nil { - witness.AddCode(evm.StateDB.GetCode(addrCopy)) + witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -410,9 +410,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas) if witness := evm.StateDB.Witness(); witness != nil { - witness.AddCode(evm.StateDB.GetCode(addrCopy)) + witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. @@ -476,7 +476,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // - the nonce is non-zero // - the code is non-empty // - the storage is non-empty - contractHash := evm.StateDB.GetCodeHash(address) + contractHash := evm.StateDB.ResolveCodeHash(address) storageRoot := evm.StateDB.GetStorageRoot(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 35d6393fba0..b3ddad97dbd 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -342,9 +342,9 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) slot := scope.Stack.peek() address := slot.Bytes20() if witness := interpreter.evm.StateDB.Witness(); witness != nil { - witness.AddCode(interpreter.evm.StateDB.GetCode(address)) + witness.AddCode(interpreter.evm.StateDB.ResolveCode(address)) } - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) + slot.SetUint64(uint64(len(interpreter.evm.StateDB.ResolveCode(slot.Bytes20())))) return nil, nil } @@ -382,7 +382,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = math.MaxUint64 } addr := common.Address(a.Bytes20()) - code := interpreter.evm.StateDB.GetCode(addr) + code := interpreter.evm.StateDB.ResolveCode(addr) if witness := interpreter.evm.StateDB.Witness(); witness != nil { witness.AddCode(code) } @@ -394,7 +394,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // opExtCodeHash returns the code hash of a specified account. // There are several cases when the function is called, while we can relay everything -// to `state.GetCodeHash` function to ensure the correctness. +// to `state.ResolveCodeHash` function to ensure the correctness. // // 1. Caller tries to get the code hash of a normal contract account, state // should return the relative code hash and set it as the result. @@ -408,6 +408,9 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // 4. Caller tries to get the code hash of a precompiled account, the result should be // zero or emptyCodeHash. // +// 4. Caller tries to get the code hash of a delegated account, the result should be +// equal the result of calling extcodehash on the account directly. +// // It is worth noting that in order to avoid unnecessary create and clean, all precompile // accounts on mainnet have been transferred 1 wei, so the return here should be // emptyCodeHash. If the precompile account is not transferred any amount on a private or @@ -424,7 +427,7 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { - slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + slot.SetBytes(interpreter.evm.StateDB.ResolveCodeHash(address).Bytes()) } return nil, nil } diff --git a/core/vm/interface.go b/core/vm/interface.go index 5f426435650..6079ca7847e 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -45,6 +45,9 @@ type StateDB interface { SetCode(common.Address, []byte) GetCodeSize(common.Address) int + ResolveCodeHash(common.Address) common.Hash + ResolveCode(common.Address) []byte + AddRefund(uint64) SubRefund(uint64) GetRefund() uint64 diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 6b2950194d7..0b16f9fd496 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -94,6 +94,7 @@ func newCancunInstructionSet() JumpTable { enable1153(&instructionSet) // EIP-1153 "Transient Storage" enable5656(&instructionSet) // EIP-5656 (MCOPY opcode) enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction + enable7702(&instructionSet) return validate(instructionSet) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 289da44be3a..3be0dbf505c 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ) @@ -248,3 +249,123 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { } return gasFunc } + +var ( + gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall) + gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall) + gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall) + gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode) + gasSelfdestructEIP7702 = makeSelfdestructGasFn(true) +) + +func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.Back(1).Bytes20()) + // Check slot presence in the access list + warmAccess := evm.StateDB.AddressInAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !warmAccess { + evm.StateDB.AddAddressToAccessList(addr) + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + } + + // Check if code is a delegation and if so, charge for resolution. + if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + var cost uint64 + if evm.StateDB.AddressInAccessList(addr) { + cost += params.WarmStorageReadCostEIP2929 + } else { + evm.StateDB.AddAddressToAccessList(addr) + cost += params.ColdAccountAccessCostEIP2929 + } + if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + coldCost += cost + } + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if warmAccess || err != nil { + return gas, err + } + // In case of a cold access, we temporarily add the cold charge back, and also + // add it to the returned gas. By adding it to the return, it will be charged + // outside of this function, as part of the dynamic gas, and that will make it + // also become correctly reported to tracers. + contract.Gas += coldCost + + var overflow bool + if gas, overflow = math.SafeAdd(gas, coldCost); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } +} + +func gasEip7702CodeCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.peek().Bytes20()) + cost := uint64(0) + // fmt.Println("checking", addr, evm.StateDB.AddressInAccessList(addr)) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(addr) + // The warm storage read cost is already charged as constantGas + cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + } + // Check if code is a delegation and if so, charge for resolution + if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + if evm.StateDB.AddressInAccessList(addr) { + cost += params.WarmStorageReadCostEIP2929 + } else { + // fmt.Println("adding ", addr, "to acl") + evm.StateDB.AddAddressToAccessList(addr) + cost += params.ColdAccountAccessCostEIP2929 + } + } + // fmt.Println("cost is", cost) + return cost, nil +} + +func gasExtCodeCopyEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + } + // Check if code is a delegation and if so, charge for resolution + if addr, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + var overflow bool + if evm.StateDB.AddressInAccessList(addr) { + if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + } else { + evm.StateDB.AddAddressToAccessList(addr) + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + } + } + return gas, nil +} diff --git a/params/protocol_params.go b/params/protocol_params.go index 2e1ab388a86..273bb3161c4 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -94,6 +94,7 @@ const ( TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list + TxAuthTupleGas uint64 = 2500 // Per auth tuple code specified in EIP-7702 // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. diff --git a/tests/gen_stauthorization.go b/tests/gen_stauthorization.go new file mode 100644 index 00000000000..6254600b2bb --- /dev/null +++ b/tests/gen_stauthorization.go @@ -0,0 +1,74 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package tests + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ = (*stAuthorizationMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s stAuthorization) MarshalJSON() ([]byte, error) { + type stAuthorization struct { + ChainID *math.HexOrDecimal256 + Address common.Address `json:"address" gencodec:"required"` + Nonce math.HexOrDecimal64 `json:"nonce" gencodec:"required"` + V *math.HexOrDecimal256 `json:"v" gencodec:"required"` + R *math.HexOrDecimal256 `json:"r" gencodec:"required"` + S *math.HexOrDecimal256 `json:"s" gencodec:"required"` + } + var enc stAuthorization + enc.ChainID = (*math.HexOrDecimal256)(s.ChainID) + enc.Address = s.Address + enc.Nonce = math.HexOrDecimal64(s.Nonce) + enc.V = (*math.HexOrDecimal256)(s.V) + enc.R = (*math.HexOrDecimal256)(s.R) + enc.S = (*math.HexOrDecimal256)(s.S) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *stAuthorization) UnmarshalJSON(input []byte) error { + type stAuthorization struct { + ChainID *math.HexOrDecimal256 + Address *common.Address `json:"address" gencodec:"required"` + Nonce *math.HexOrDecimal64 `json:"nonce" gencodec:"required"` + V *math.HexOrDecimal256 `json:"v" gencodec:"required"` + R *math.HexOrDecimal256 `json:"r" gencodec:"required"` + S *math.HexOrDecimal256 `json:"s" gencodec:"required"` + } + var dec stAuthorization + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ChainID != nil { + s.ChainID = (*big.Int)(dec.ChainID) + } + if dec.Address == nil { + return errors.New("missing required field 'address' for stAuthorization") + } + s.Address = *dec.Address + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' for stAuthorization") + } + s.Nonce = uint64(*dec.Nonce) + if dec.V == nil { + return errors.New("missing required field 'v' for stAuthorization") + } + s.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' for stAuthorization") + } + s.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' for stAuthorization") + } + s.S = (*big.Int)(dec.S) + return nil +} diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go index 9b5aecbfe6b..b25ce76166a 100644 --- a/tests/gen_sttransaction.go +++ b/tests/gen_sttransaction.go @@ -30,6 +30,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { Sender *common.Address `json:"sender"` BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` + AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"` } var enc stTransaction enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) @@ -50,6 +51,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { enc.Sender = s.Sender enc.BlobVersionedHashes = s.BlobVersionedHashes enc.BlobGasFeeCap = (*math.HexOrDecimal256)(s.BlobGasFeeCap) + enc.AuthorizationList = s.AuthorizationList return json.Marshal(&enc) } @@ -69,6 +71,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { Sender *common.Address `json:"sender"` BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` + AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"` } var dec stTransaction if err := json.Unmarshal(input, &dec); err != nil { @@ -116,5 +119,8 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { if dec.BlobGasFeeCap != nil { s.BlobGasFeeCap = (*big.Int)(dec.BlobGasFeeCap) } + if dec.AuthorizationList != nil { + s.AuthorizationList = dec.AuthorizationList + } return nil } diff --git a/tests/state_test.go b/tests/state_test.go index 76fec97de0e..06e7c805851 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -96,6 +96,7 @@ func TestExecutionSpecState(t *testing.T) { t.Skipf("directory %s does not exist", executionSpecStateTestDir) } st := new(testMatcher) + st.runonly("eip7702") st.walk(t, executionSpecStateTestDir, func(t *testing.T, name string, test *StateTest) { execStateTest(t, st, test) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 2bf0056cbc9..b681986915d 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -123,6 +123,7 @@ type stTransaction struct { Sender *common.Address `json:"sender"` BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` BlobGasFeeCap *big.Int `json:"maxFeePerBlobGas,omitempty"` + AuthorizationList []*stAuthorization `json:"authorizationList,omitempty"` } type stTransactionMarshaling struct { @@ -135,6 +136,28 @@ type stTransactionMarshaling struct { BlobGasFeeCap *math.HexOrDecimal256 } +//go:generate go run github.com/fjl/gencodec -type stAuthorization -field-override stAuthorizationMarshaling -out gen_stauthorization.go + +// Authorization is an authorization from an account to deploy code at it's +// address. +type stAuthorization struct { + ChainID *big.Int + Address common.Address `json:"address" gencodec:"required"` + Nonce uint64 `json:"nonce" gencodec:"required"` + V *big.Int `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// field type overrides for gencodec +type stAuthorizationMarshaling struct { + ChainID *math.HexOrDecimal256 + Nonce math.HexOrDecimal64 + V *math.HexOrDecimal256 + R *math.HexOrDecimal256 + S *math.HexOrDecimal256 +} + // GetChainConfig takes a fork definition and returns a chain config. // The fork definition can be // - a plain forkname, e.g. `Byzantium`, @@ -417,6 +440,24 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess if gasPrice == nil { return nil, errors.New("no gas price provided") } + var authList types.AuthorizationList + if tx.AuthorizationList != nil { + authList = make(types.AuthorizationList, 0) + for _, auth := range tx.AuthorizationList { + chainID := auth.ChainID + if chainID == nil { + chainID = big.NewInt(0) + } + authList = append(authList, &types.Authorization{ + ChainID: chainID, + Address: auth.Address, + Nonce: auth.Nonce, + V: auth.V, + R: auth.R, + S: auth.S, + }) + } + } msg := &core.Message{ From: from, @@ -431,6 +472,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess AccessList: accessList, BlobHashes: tx.BlobVersionedHashes, BlobGasFeeCap: tx.BlobGasFeeCap, + AuthList: authList, } return msg, nil } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index d3dbbd5db29..4da27ff9433 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -59,7 +59,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { return nil, nil, err } // Intrinsic gas - requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false) + requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.AuthList(), tx.To() == nil, isHomestead, isIstanbul, false) if err != nil { return nil, nil, err } From 75a43aed6cefdc340d14cec64139f1102a912152 Mon Sep 17 00:00:00 2001 From: lightclient Date: Sat, 7 Sep 2024 08:52:32 -0600 Subject: [PATCH 4/8] add delegation designator to witness --- cmd/evm/blockrunner.go | 2 +- core/state_transition.go | 5 ++++- core/vm/evm.go | 4 ++++ core/vm/instructions.go | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index d5cd8d8e3de..8cc715c5a32 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -86,7 +86,7 @@ func blockTestCmd(ctx *cli.Context) error { continue } test := tests[name] - if err := test.Run(false, rawdb.HashScheme, false, tracer, func(res error, chain *core.BlockChain) { + if err := test.Run(false, rawdb.HashScheme, true, tracer, func(res error, chain *core.BlockChain) { if ctx.Bool(DumpFlag.Name) { if state, _ := chain.State(); state != nil { fmt.Println(string(state.Dump(nil))) diff --git a/core/state_transition.go b/core/state_transition.go index 1cc24329281..e3c519e1f90 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -479,8 +479,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Check the authority account 1) doesn't have code or has exisiting // delegation 2) matches the auth's nonce - st.state.AddAddressToAccessList(authority) code := st.state.GetCode(authority) + st.state.AddAddressToAccessList(authority) + if witness := st.state.Witness(); witness != nil { + witness.AddCode(code) + } if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok { continue } diff --git a/core/vm/evm.go b/core/vm/evm.go index 1c7efe5c857..164a3d91ea2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -232,6 +232,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // The contract is a scoped environment for this execution context only. code := evm.StateDB.ResolveCode(addr) if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(evm.StateDB.GetCode(addr)) witness.AddCode(code) } if len(code) == 0 { @@ -302,6 +303,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(evm.StateDB.GetCode(addrCopy)) witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) @@ -352,6 +354,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(evm.StateDB.GetCode(addrCopy)) witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) @@ -410,6 +413,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas) if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(evm.StateDB.GetCode(addrCopy)) witness.AddCode(evm.StateDB.ResolveCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.ResolveCodeHash(addrCopy), evm.StateDB.ResolveCode(addrCopy)) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b3ddad97dbd..da312e82607 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -342,6 +342,7 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) slot := scope.Stack.peek() address := slot.Bytes20() if witness := interpreter.evm.StateDB.Witness(); witness != nil { + witness.AddCode(interpreter.evm.StateDB.GetCode(address)) witness.AddCode(interpreter.evm.StateDB.ResolveCode(address)) } slot.SetUint64(uint64(len(interpreter.evm.StateDB.ResolveCode(slot.Bytes20())))) @@ -384,6 +385,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) addr := common.Address(a.Bytes20()) code := interpreter.evm.StateDB.ResolveCode(addr) if witness := interpreter.evm.StateDB.Witness(); witness != nil { + witness.AddCode(interpreter.evm.StateDB.GetCode(addr)) witness.AddCode(code) } codeCopy := getData(code, uint64CodeOffset, length.Uint64()) From 183e7b702a000406fc4d3200a27614bc3b20b09a Mon Sep 17 00:00:00 2001 From: lightclient Date: Sat, 7 Sep 2024 09:50:44 -0600 Subject: [PATCH 5/8] core: force add new delegation to accesslist --- core/state/statedb.go | 8 +++++--- core/state_transition.go | 6 ++++++ core/vm/operations_acl.go | 2 -- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 65131de9d0a..dd88ba9799d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1409,9 +1409,11 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(sender) if dst != nil { al.AddAddress(*dst) - // TODO: is this right? - if addr, ok := types.ParseDelegation(s.GetCode(*dst)); ok { - al.AddAddress(addr) + if rules.IsPrague { + // Add delegation target + if addr, ok := types.ParseDelegation(s.GetCode(*dst)); ok { + al.AddAddress(addr) + } } // If it's a create-tx, the destination will be added inside evm.create } diff --git a/core/state_transition.go b/core/state_transition.go index e3c519e1f90..31764f43569 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -495,6 +495,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if exists := st.state.Exist(authority); exists { st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) } + // Special case, when msg.To is to an authority being set in the tx, we + // need to add it to the access list. Usually this happens in StateDB + // Prepare(..). + if st.msg.To != nil && *st.msg.To == authority { + st.state.AddAddressToAccessList(auth.Address) + } st.state.SetNonce(authority, auth.Nonce+1) st.state.SetCode(authority, types.AddressToDelegation(auth.Address)) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 3be0dbf505c..f5ed13f55b2 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -328,12 +328,10 @@ func gasEip7702CodeCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory if evm.StateDB.AddressInAccessList(addr) { cost += params.WarmStorageReadCostEIP2929 } else { - // fmt.Println("adding ", addr, "to acl") evm.StateDB.AddAddressToAccessList(addr) cost += params.ColdAccountAccessCostEIP2929 } } - // fmt.Println("cost is", cost) return cost, nil } From 4e699844efcc711f276b8bae88fc49459e6b35e4 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 9 Sep 2024 11:05:44 +0200 Subject: [PATCH 6/8] eth/catalyst: fix unsupported fork error --- eth/catalyst/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 047b288de6c..a113f1eb7bc 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -449,7 +449,7 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu // GetPayloadV4 returns a cached payload by id. func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { - if !payloadID.Is(engine.PayloadV4) { + if !payloadID.Is(engine.PayloadV3) { // We don't have FCUV4, so FCUv3 will put payload version 3 in. return nil, engine.UnsupportedFork } return api.getPayload(payloadID, false) From 589c961ec3cfc83920e6d14e19565abc3c23961a Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 9 Sep 2024 11:30:44 +0200 Subject: [PATCH 7/8] internal/ethapi: add authorization list --- eth/catalyst/api.go | 2 +- internal/ethapi/api.go | 63 +++++++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index a113f1eb7bc..9fee0fed2c2 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -449,7 +449,7 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu // GetPayloadV4 returns a cached payload by id. func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { - if !payloadID.Is(engine.PayloadV3) { // We don't have FCUV4, so FCUv3 will put payload version 3 in. + if !payloadID.Is(engine.PayloadV3, engine.PayloadV4) { return nil, engine.UnsupportedFork } return api.getPayload(payloadID, false) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index aeb3e8adc28..eda37b483ba 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1332,28 +1332,29 @@ func (api *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, i // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction type RPCTransaction struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` - GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` - MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - Type hexutil.Uint64 `json:"type"` - Accesses *types.AccessList `json:"accessList,omitempty"` - ChainID *hexutil.Big `json:"chainId,omitempty"` - BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` - YParity *hexutil.Uint64 `json:"yParity,omitempty"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + Type hexutil.Uint64 `json:"type"` + Accesses *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + AuthorizationList types.AuthorizationList `json:"authorizationList,omitempty"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + YParity *hexutil.Uint64 `json:"yParity,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC @@ -1428,6 +1429,24 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber } result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap()) result.BlobVersionedHashes = tx.BlobHashes() + + case types.SetCodeTxType: + al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) + result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price + if baseFee != nil && blockHash != (common.Hash{}) { + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap()) + result.BlobVersionedHashes = tx.BlobHashes() + result.AuthorizationList = tx.AuthList() } return result } From 23ae355c422d19aa8eb326f91b0c7f63483a7c40 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 9 Sep 2024 12:57:31 +0200 Subject: [PATCH 8/8] core/types: ChainID -> chainId --- core/types/transaction_signing.go | 2 +- core/types/tx_setcode.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index d783ed6fec0..59a03e32946 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -97,7 +97,7 @@ func LatestSignerForChainID(chainID *big.Int) Signer { if chainID == nil { return HomesteadSigner{} } - return NewCancunSigner(chainID) + return NewPragueSigner(chainID) } // SignTx signs the transaction using the given signer and private key. diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go index 0a48fddccff..e0bf1756ab4 100644 --- a/core/types/tx_setcode.go +++ b/core/types/tx_setcode.go @@ -57,7 +57,7 @@ type SetCodeTx struct { // Authorization is an authorization from an account to deploy code at it's // address. type Authorization struct { - ChainID *big.Int + ChainID *big.Int `json:"chainId" gencodec:"required"` Address common.Address `json:"address" gencodec:"required"` Nonce uint64 `json:"nonce" gencodec:"required"` V *big.Int `json:"v" gencodec:"required"`