diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index b2eb1dc9822..ebab7c0cf8b 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -17,25 +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"` - 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 @@ -61,6 +63,8 @@ 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.ConsolidationRequests = e.ConsolidationRequests enc.ExecutionWitness = e.ExecutionWitness return json.Marshal(&enc) } @@ -68,25 +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"` - 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 { @@ -163,6 +169,12 @@ 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.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 19d2b2823e3..bb24f223df3 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -60,25 +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"` - 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. @@ -233,7 +235,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 +247,20 @@ 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 data.ConsolidationRequests != nil { + requests = append(requests, data.ConsolidationRequests.Requests()...) + } + if requests != nil { h := types.DeriveSha(requests, trie.NewStackTrie(nil)) requestsHash = &h } + header := &types.Header{ ParentHash: data.ParentHash, UncleHash: types.EmptyUncleHash, @@ -320,22 +335,31 @@ 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) + data.ConsolidationRequests = make(types.ConsolidationRequests, 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) + 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"` + 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/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/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index a0805722d66..f414eaf2a4a 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -53,21 +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"` + 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 { @@ -354,33 +356,15 @@ 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 + consolidationRequests types.ConsolidationRequests + 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 +373,60 @@ 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...) + // 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)) - execRs.RequestsHash = &h - // Get the deposits from the requests - deposits := make(types.Deposits, 0) + requestsHash = &h + + // Break out individual request types. + depositRequests = make(types.Deposits, 0) + withdrawalRequests = make(types.WithdrawalRequests, 0) + consolidationRequests = make(types.ConsolidationRequests, 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) + case *types.ConsolidationRequest: + consolidationRequests = append(consolidationRequests, 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, + ConsolidationRequests: consolidationRequests, + } + 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/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/blockchain_test.go b/core/blockchain_test.go index fe0c527a009..bb20e03b3a9 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,167 @@ func TestEIP6110(t *testing.T) { } } } + +// TestRequests verifies that Prague requests are processed correctly. +func TestRequests(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")}, + params.ConsolidationRequestsAddress: {Code: common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd")}, + }, + } + ) + 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, + }, + } + 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, 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) + } + } + }) + + 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) + } + + // Verify the withdrawal requests match. + 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(got)) + } + 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) + } + } + + // 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 dcaa36e08cc..60e59e057c2 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -355,6 +355,15 @@ 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...) + 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/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..dd88ba9799d 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,12 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(sender) if dst != nil { al.AddAddress(*dst) + 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 } for _, addr := range precompiles { diff --git a/core/state_processor.go b/core/state_processor.go index 2844693a9e5..b4e61f5fcfb 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,10 @@ 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...) + cxs := ProcessDequeueConsolidationRequests(vmenv, statedb) + requests = append(requests, cxs...) } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) @@ -270,3 +275,81 @@ 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 +} + +// 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/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..31764f43569 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,51 @@ 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 + 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 + } + 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) + } + // 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)) + } + } + var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err @@ -440,8 +513,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<. +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_authorization.go b/core/types/gen_authorization.go new file mode 100644 index 00000000000..c629a2a4d6f --- /dev/null +++ b/core/types/gen_authorization.go @@ -0,0 +1,74 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*authorizationMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (a Authorization) MarshalJSON() ([]byte, error) { + type Authorization struct { + ChainID *hexutil.Big + Address common.Address `json:"address" gencodec:"required"` + Nonce hexutil.Uint64 `json:"nonce" gencodec:"required"` + V *hexutil.Big `json:"v" gencodec:"required"` + R *hexutil.Big `json:"r" gencodec:"required"` + S *hexutil.Big `json:"s" gencodec:"required"` + } + var enc Authorization + enc.ChainID = (*hexutil.Big)(a.ChainID) + enc.Address = a.Address + enc.Nonce = hexutil.Uint64(a.Nonce) + enc.V = (*hexutil.Big)(a.V) + enc.R = (*hexutil.Big)(a.R) + enc.S = (*hexutil.Big)(a.S) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *Authorization) UnmarshalJSON(input []byte) error { + type Authorization struct { + ChainID *hexutil.Big + Address *common.Address `json:"address" gencodec:"required"` + Nonce *hexutil.Uint64 `json:"nonce" gencodec:"required"` + V *hexutil.Big `json:"v" gencodec:"required"` + R *hexutil.Big `json:"r" gencodec:"required"` + S *hexutil.Big `json:"s" gencodec:"required"` + } + var dec Authorization + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ChainID != nil { + a.ChainID = (*big.Int)(dec.ChainID) + } + if dec.Address == nil { + return errors.New("missing required field 'address' for Authorization") + } + a.Address = *dec.Address + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' for Authorization") + } + a.Nonce = uint64(*dec.Nonce) + if dec.V == nil { + return errors.New("missing required field 'v' for Authorization") + } + a.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' for Authorization") + } + a.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' for Authorization") + } + a.S = (*big.Int)(dec.S) + return nil +} 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/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/receipt.go b/core/types/receipt.go index 4f96fde59c4..47f16d950ae 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -204,7 +204,7 @@ func (r *Receipt) decodeTyped(b []byte) error { return errShortTypedReceipt } switch b[0] { - case DynamicFeeTxType, AccessListTxType, BlobTxType: + case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType: var data receiptRLP err := rlp.DecodeBytes(b[1:], &data) if err != nil { @@ -312,7 +312,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { } w.WriteByte(r.Type) switch r.Type { - case AccessListTxType, DynamicFeeTxType, BlobTxType: + case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType: rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for diff --git a/core/types/request.go b/core/types/request.go index 7b1cade26e7..b8a0ef7afcc 100644 --- a/core/types/request.go +++ b/core/types/request.go @@ -32,7 +32,9 @@ var ( // Request types. const ( - DepositRequestType = 0x00 + DepositRequestType = 0x00 + WithdrawalRequestType = 0x01 + ConsolidationRequestType = 0x02 ) // Request is an EIP-7685 request object. It represents execution layer @@ -149,6 +151,10 @@ func (r *Request) decode(b []byte) (RequestData, error) { switch b[0] { case DepositRequestType: inner = new(Deposit) + case WithdrawalRequestType: + inner = new(WithdrawalRequest) + case ConsolidationRequestType: + inner = new(ConsolidationRequest) default: return nil, ErrRequestTypeNotSupported } diff --git a/core/types/transaction.go b/core/types/transaction.go index 4ac9187bdbf..b1ea9332d89 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -49,6 +49,7 @@ const ( AccessListTxType = 0x01 DynamicFeeTxType = 0x02 BlobTxType = 0x03 + SetCodeTxType = 0x04 ) // Transaction is an Ethereum transaction. @@ -206,6 +207,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { inner = new(DynamicFeeTx) case BlobTxType: inner = new(BlobTx) + case SetCodeTxType: + inner = new(SetCodeTx) default: return nil, ErrTxTypeNotSupported } @@ -466,6 +469,15 @@ func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction { return cpy } +// AuthList returns the authorizations list of the transaction. +func (tx *Transaction) AuthList() AuthorizationList { + setcodetx, ok := tx.inner.(*SetCodeTx) + if !ok { + return nil + } + return setcodetx.AuthList +} + // SetTime sets the decoding time of a transaction. This is used by tests to set // arbitrary times and by persistent transaction pools when loading old txs from // disk. diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 4d5b2bcdd4c..7493b72712f 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -31,22 +31,23 @@ import ( type txJSON struct { Type hexutil.Uint64 `json:"type"` - ChainID *hexutil.Big `json:"chainId,omitempty"` - Nonce *hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` - MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` - MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` - Value *hexutil.Big `json:"value"` - Input *hexutil.Bytes `json:"input"` - AccessList *AccessList `json:"accessList,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"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + Nonce *hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` + MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + Value *hexutil.Big `json:"value"` + Input *hexutil.Bytes `json:"input"` + AccessList *AccessList `json:"accessList,omitempty"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + AuthorizationList *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"` // Blob transaction sidecar encoding: Blobs []kzg4844.Blob `json:"blobs,omitempty"` @@ -153,6 +154,23 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.Commitments = itx.Sidecar.Commitments enc.Proofs = itx.Sidecar.Proofs } + case *SetCodeTx: + enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig()) + enc.Nonce = (*hexutil.Uint64)(&itx.Nonce) + enc.To = tx.To() + enc.Gas = (*hexutil.Uint64)(&itx.Gas) + enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap.ToBig()) + enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap.ToBig()) + enc.Value = (*hexutil.Big)(itx.Value.ToBig()) + enc.Input = (*hexutil.Bytes)(&itx.Data) + enc.AccessList = &itx.AccessList + enc.AuthorizationList = &itx.AuthList + enc.V = (*hexutil.Big)(itx.V.ToBig()) + enc.R = (*hexutil.Big)(itx.R.ToBig()) + enc.S = (*hexutil.Big)(itx.S.ToBig()) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) + } return json.Marshal(&enc) } @@ -409,6 +427,81 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { } } + case SetCodeTxType: + var itx SetCodeTx + inner = &itx + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = uint256.MustFromBig((*big.Int)(dec.ChainID)) + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.To == nil { + return errors.New("missing required field 'to' in transaction") + } + itx.To = *dec.To + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.Gas = uint64(*dec.Gas) + if dec.MaxPriorityFeePerGas == nil { + return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") + } + itx.GasTipCap = uint256.MustFromBig((*big.Int)(dec.MaxPriorityFeePerGas)) + if dec.MaxFeePerGas == nil { + return errors.New("missing required field 'maxFeePerGas' for txdata") + } + itx.GasFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerGas)) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = uint256.MustFromBig((*big.Int)(dec.Value)) + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Input + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + if dec.AuthorizationList == nil { + return errors.New("missing required field 'authorizationList' in transaction") + } + itx.AuthList = *dec.AuthorizationList + + // signature R + var overflow bool + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R, overflow = uint256.FromBig((*big.Int)(dec.R)) + if overflow { + return errors.New("'r' value overflows uint256") + } + // signature S + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S, overflow = uint256.FromBig((*big.Int)(dec.S)) + if overflow { + return errors.New("'s' value overflows uint256") + } + // signature V + vbig, err := dec.yParityValue() + if err != nil { + return err + } + itx.V, overflow = uint256.FromBig(vbig) + if overflow { + return errors.New("'v' value overflows uint256") + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { + if err := sanityCheckSignature(vbig, itx.R.ToBig(), itx.S.ToBig(), false); err != nil { + return err + } + } + default: return ErrTxTypeNotSupported } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index dd25f081f79..59a03e32946 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -40,6 +40,8 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { var signer Signer switch { + case config.IsPrague(blockNumber, blockTime): + signer = NewPragueSigner(config.ChainID) case config.IsCancun(blockNumber, blockTime): signer = NewCancunSigner(config.ChainID) case config.IsLondon(blockNumber): @@ -65,6 +67,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint // have the current block number available, use MakeSigner instead. func LatestSigner(config *params.ChainConfig) Signer { if config.ChainID != nil { + if config.PragueTime != nil { + return NewPragueSigner(config.ChainID) + } if config.CancunTime != nil { return NewCancunSigner(config.ChainID) } @@ -92,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. @@ -168,6 +173,76 @@ type Signer interface { Equal(Signer) bool } +type pragueSigner struct{ cancunSigner } + +// NewPragueSigner returns a signer that accepts +// - EIP-7702 set code transactions +// - EIP-4844 blob transactions +// - EIP-1559 dynamic fee transactions +// - EIP-2930 access list transactions, +// - EIP-155 replay protected transactions, and +// - legacy Homestead transactions. +func NewPragueSigner(chainId *big.Int) Signer { + return pragueSigner{cancunSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}}} +} + +func (s pragueSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != SetCodeTxType { + return s.cancunSigner.Sender(tx) + } + V, R, S := tx.RawSignatureValues() + + // Set code txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s pragueSigner) Equal(s2 Signer) bool { + x, ok := s2.(pragueSigner) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s pragueSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + txdata, ok := tx.inner.(*SetCodeTx) + if !ok { + return s.cancunSigner.SignatureValues(tx, sig) + } + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.ChainID.Sign() != 0 && txdata.ChainID.ToBig().Cmp(s.chainId) != 0 { + return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId) + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s pragueSigner) Hash(tx *Transaction) common.Hash { + if tx.Type() != SetCodeTxType { + return s.cancunSigner.Hash(tx) + } + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + tx.AuthList(), + }) +} + type cancunSigner struct{ londonSigner } // NewCancunSigner returns a signer that accepts diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go new file mode 100644 index 00000000000..e0bf1756ab4 --- /dev/null +++ b/core/types/tx_setcode.go @@ -0,0 +1,256 @@ +package types + +import ( + "bytes" + "crypto/ecdsa" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" +) + +// DelegationPrefix is used by code to denote the account is delegating to +// another account. +var DelegationPrefix = []byte{0xef, 0x01, 0x00} + +// ParseDelegation tries to parse the address from a delegation slice. +func ParseDelegation(b []byte) (common.Address, bool) { + if len(b) != 23 || !bytes.HasPrefix(b, DelegationPrefix) { + return common.Address{}, false + } + var addr common.Address + copy(addr[:], b[len(DelegationPrefix):]) + return addr, true +} + +// AddressToDelegation adds the delegation prefix to the specified address. +func AddressToDelegation(addr common.Address) []byte { + return append(DelegationPrefix, addr.Bytes()...) +} + +// SetCodeTx implements the EIP-7702 transaction type which temporarily installs +// the code at the signer's address. +type SetCodeTx struct { + ChainID *uint256.Int + Nonce uint64 + GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *uint256.Int // a.k.a. maxFeePerGas + Gas uint64 + To common.Address + Value *uint256.Int + Data []byte + AccessList AccessList + AuthList AuthorizationList + + // Signature values + V *uint256.Int `json:"v" gencodec:"required"` + R *uint256.Int `json:"r" gencodec:"required"` + S *uint256.Int `json:"s" gencodec:"required"` +} + +//go:generate go run github.com/fjl/gencodec -type Authorization -field-override authorizationMarshaling -out gen_authorization.go + +// Authorization is an authorization from an account to deploy code at it's +// address. +type Authorization struct { + 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"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// field type overrides for gencodec +type authorizationMarshaling struct { + ChainID *hexutil.Big + Nonce hexutil.Uint64 + V *hexutil.Big + R *hexutil.Big + S *hexutil.Big +} + +func SignAuth(auth *Authorization, prv *ecdsa.PrivateKey) (*Authorization, error) { + h := prefixedRlpHash( + 0x05, + []interface{}{ + auth.ChainID, + auth.Address, + auth.Nonce, + }) + + sig, err := crypto.Sign(h[:], prv) + if err != nil { + return nil, err + } + return auth.WithSignature(sig), nil +} + +func (a *Authorization) WithSignature(sig []byte) *Authorization { + r, s, _ := decodeSignature(sig) + v := big.NewInt(int64(sig[64])) + cpy := Authorization{ + ChainID: a.ChainID, + Address: a.Address, + Nonce: a.Nonce, + V: v, + R: r, + S: s, + } + return &cpy +} + +type AuthorizationList []*Authorization + +func (a Authorization) Authority() (common.Address, error) { + sighash := prefixedRlpHash( + 0x05, + []interface{}{ + a.ChainID, + a.Address, + a.Nonce, + }) + + v := byte(a.V.Uint64()) + if !crypto.ValidateSignatureValues(v, a.R, a.S, true) { + return common.Address{}, ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := a.R.Bytes(), a.S.Bytes() + sig := make([]byte, crypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = v + // recover the public key from the signature + pub, err := crypto.Ecrecover(sighash[:], sig) + if err != nil { + return common.Address{}, err + } + if len(pub) == 0 || pub[0] != 4 { + return common.Address{}, errors.New("invalid public key") + } + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil +} + +type SetCodeDelegation struct { + From common.Address + Nonce uint64 + Target common.Address +} + +/* +func (a AuthorizationList) WithAddress() []AuthorizationTuple { + var ( + list = make([]AuthorizationTuple, len(a)) + sha = hasherPool.Get().(crypto.KeccakState) + h = common.Hash{} + ) + defer hasherPool.Put(sha) + for i, auth := range a { + sha.Reset() + sha.Write([]byte{0x05}) + sha.Write(auth.Code) + sha.Read(h[:]) + addr, err := recoverPlain(h, auth.R, auth.S, auth.V, false) + if err != nil { + continue + } + list[i].Address = addr + copy(list[i].Code, auth.Code) + } + return list +} +*/ + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *SetCodeTx) copy() TxData { + cpy := &SetCodeTx{ + Nonce: tx.Nonce, + To: tx.To, + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + AuthList: make(AuthorizationList, len(tx.AuthList)), + Value: new(uint256.Int), + ChainID: new(uint256.Int), + GasTipCap: new(uint256.Int), + GasFeeCap: new(uint256.Int), + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + } + copy(cpy.AccessList, tx.AccessList) + copy(cpy.AuthList, tx.AuthList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. +func (tx *SetCodeTx) txType() byte { return SetCodeTxType } +func (tx *SetCodeTx) chainID() *big.Int { return tx.ChainID.ToBig() } +func (tx *SetCodeTx) accessList() AccessList { return tx.AccessList } +func (tx *SetCodeTx) data() []byte { return tx.Data } +func (tx *SetCodeTx) gas() uint64 { return tx.Gas } +func (tx *SetCodeTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *SetCodeTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() } +func (tx *SetCodeTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *SetCodeTx) value() *big.Int { return tx.Value.ToBig() } +func (tx *SetCodeTx) nonce() uint64 { return tx.Nonce } +func (tx *SetCodeTx) to() *common.Address { tmp := tx.To; return &tmp } + +func (tx *SetCodeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap.ToBig()) + } + tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee) + if tip.Cmp(tx.GasTipCap.ToBig()) > 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/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/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..164a3d91ea2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -230,8 +230,9 @@ 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(evm.StateDB.GetCode(addr)) witness.AddCode(code) } if len(code) == 0 { @@ -241,7 +242,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 } @@ -303,8 +304,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, 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 } @@ -353,8 +355,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by 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 } @@ -411,8 +414,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte 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 +480,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..da312e82607 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -343,8 +343,9 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) 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,8 +383,9 @@ 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(interpreter.evm.StateDB.GetCode(addr)) witness.AddCode(code) } codeCopy := getData(code, uint64CodeOffset, length.Uint64()) @@ -394,7 +396,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 +410,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 +429,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..f5ed13f55b2 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,121 @@ 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 { + evm.StateDB.AddAddressToAccessList(addr) + cost += params.ColdAccountAccessCostEIP2929 + } + } + 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/eth/catalyst/api.go b/eth/catalyst/api.go index 7ea1c9ee076..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.PayloadV4) { + if !payloadID.Is(engine.PayloadV3, engine.PayloadV4) { return nil, engine.UnsupportedFork } return api.getPayload(payloadID, false) @@ -872,12 +872,14 @@ 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].ConsolidationRequests = nil + bodies[i] = body } - bodies[i] = body } return bodies } @@ -905,6 +907,8 @@ func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) for i := range bodies { if bodies[i] != nil { bodies[i].Deposits = nil + bodies[i].WithdrawalRequests = nil + bodies[i].ConsolidationRequests = nil } } return bodies, nil @@ -943,10 +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 + 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 { @@ -962,16 +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() { - 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) + case *types.ConsolidationRequest: + consolidationRequests = append(consolidationRequests, v) } } } - return &engine.ExecutionPayloadBody{ - TransactionData: txs, - Withdrawals: withdrawals, - Deposits: depositRequests, + 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/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 } diff --git a/miner/worker.go b/miner/worker.go index 1f49118c474..51e69898bec 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -113,10 +113,21 @@ 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...) + // 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 638f58a3399..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. @@ -189,10 +190,12 @@ 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") + // 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. 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 }