From 9ad03c90b78f0a07399b2b71b01a19e275d1f82a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:19:20 +0100 Subject: [PATCH 01/30] cmd, core, params, trie: Add verkle access witness Co-authored-by: Ignacio Hagopian --- cmd/geth/verkle.go | 2 +- consensus/beacon/consensus.go | 4 + core/error.go | 5 + core/state/access_witness.go | 297 ++++++++++++++++++++++++++++++++++ core/state/database.go | 5 +- core/state/statedb.go | 24 ++- core/state_processor.go | 5 + core/state_transition.go | 47 +++++- core/vm/common.go | 12 ++ core/vm/contract.go | 3 + core/vm/eips.go | 27 ++++ core/vm/evm.go | 70 ++++++-- core/vm/gas_table.go | 46 +++++- core/vm/instructions.go | 72 ++++++++- core/vm/interpreter.go | 11 ++ core/vm/jump_table.go | 8 + core/vm/operations_verkle.go | 129 +++++++++++++++ go.mod | 4 +- go.sum | 8 +- params/config.go | 8 + params/protocol_params.go | 1 + params/verkle_params.go | 36 +++++ trie/secure_trie.go | 4 + trie/utils/verkle.go | 8 +- trie/verkle.go | 2 +- 25 files changed, 806 insertions(+), 32 deletions(-) create mode 100644 core/state/access_witness.go create mode 100644 core/vm/operations_verkle.go create mode 100644 params/verkle_params.go diff --git a/cmd/geth/verkle.go b/cmd/geth/verkle.go index ff3931356e8f5..9eb37fb5a8751 100644 --- a/cmd/geth/verkle.go +++ b/cmd/geth/verkle.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" - "github.com/gballet/go-verkle" + "github.com/ethereum/go-verkle" "github.com/urfave/cli/v2" ) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 4e3fbeb09a7c9..dd273a6f59a89 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -360,6 +360,10 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. amount := new(uint256.Int).SetUint64(w.Amount) amount = amount.Mul(amount, uint256.NewInt(params.GWei)) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + + if chain.Config().IsEIP4762(header.Number, header.Time) { + state.Witness().TouchFullAccount(w.Address[:], true) + } } // No block reward which is issued by consensus layer instead. } diff --git a/core/error.go b/core/error.go index e6e6ba2f90c38..161538fe4323d 100644 --- a/core/error.go +++ b/core/error.go @@ -64,6 +64,11 @@ var ( // than init code size limit. ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded") + // ErrInsufficientBalanceWitness is returned if the transaction sender has enough + // funds to cover the transfer, but not enough to pay for witness access/modification + // costs for the transaction + ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction") + // ErrInsufficientFunds is returned if the total cost of executing a transaction // is higher than the balance of the user's account. ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") diff --git a/core/state/access_witness.go b/core/state/access_witness.go new file mode 100644 index 0000000000000..10db53992e236 --- /dev/null +++ b/core/state/access_witness.go @@ -0,0 +1,297 @@ +// Copyright 2021 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 state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/holiman/uint256" +) + +// mode specifies how a tree location has been accessed +// for the byte value: +// * the first bit is set if the branch has been edited +// * the second bit is set if the branch has been read +type mode byte + +const ( + AccessWitnessReadFlag = mode(1) + AccessWitnessWriteFlag = mode(2) +) + +var zeroTreeIndex uint256.Int + +// AccessWitness lists the locations of the state that are being accessed +// during the production of a block. +type AccessWitness struct { + branches map[branchAccessKey]mode + chunks map[chunkAccessKey]mode + + pointCache *utils.PointCache +} + +func NewAccessWitness(pointCache *utils.PointCache) *AccessWitness { + return &AccessWitness{ + branches: make(map[branchAccessKey]mode), + chunks: make(map[chunkAccessKey]mode), + pointCache: pointCache, + } +} + +// Merge is used to merge the witness that got generated during the execution +// of a tx, with the accumulation of witnesses that were generated during the +// execution of all the txs preceding this one in a given block. +func (aw *AccessWitness) Merge(other *AccessWitness) { + for k := range other.branches { + aw.branches[k] |= other.branches[k] + } + for k, chunk := range other.chunks { + aw.chunks[k] |= chunk + } +} + +// Key returns, predictably, the list of keys that were touched during the +// buildup of the access witness. +func (aw *AccessWitness) Keys() [][]byte { + // TODO: consider if parallelizing this is worth it, probably depending on len(aw.chunks). + keys := make([][]byte, 0, len(aw.chunks)) + for chunk := range aw.chunks { + basePoint := aw.pointCache.Get(chunk.addr[:]) + key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey) + keys = append(keys, key) + } + return keys +} + +func (aw *AccessWitness) Copy() *AccessWitness { + naw := &AccessWitness{ + branches: make(map[branchAccessKey]mode), + chunks: make(map[chunkAccessKey]mode), + pointCache: aw.pointCache, + } + naw.Merge(aw) + return naw +} + +func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { + var gas uint64 + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) + } + return gas +} + +func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 { + var gas uint64 + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false) + return gas +} + +func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { + var gas uint64 + gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + return gas +} + +// TouchAndChargeContractCreateInit charges access costs to initiate +// a contract creation +func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 { + var gas uint64 + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) + if createSendsValue { + gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true) + } + return gas +} + +func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) + } + + // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address + // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling. + // This is the reason why we return 0 instead of `gas`. + // Note that we still have to touch the addresses to make sure the witness is correct. + return 0 +} + +func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 { + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false) + if sendsValue { + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + } else { + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false) + } + + // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address + // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling. + // This is the reason why we return 0 instead of `gas`. + // Note that we still have to touch the addresses to make sure the witness is correct. + return 0 +} + +func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 { + treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) + return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) +} + +func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { + stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite) + + var gas uint64 + if stemRead { + gas += params.WitnessBranchReadCost + } + if selectorRead { + gas += params.WitnessChunkReadCost + } + if stemWrite { + gas += params.WitnessBranchWriteCost + } + if selectorWrite { + gas += params.WitnessChunkWriteCost + } + if selectorFill { + gas += params.WitnessChunkFillCost + } + + return gas +} + +// touchAddress adds any missing access event to the witness. +func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { + branchKey := newBranchAccessKey(addr, treeIndex) + chunkKey := newChunkAccessKey(branchKey, subIndex) + + // Read access. + var branchRead, chunkRead bool + if _, hasStem := aw.branches[branchKey]; !hasStem { + branchRead = true + aw.branches[branchKey] = AccessWitnessReadFlag + } + if _, hasSelector := aw.chunks[chunkKey]; !hasSelector { + chunkRead = true + aw.chunks[chunkKey] = AccessWitnessReadFlag + } + + // Write access. + var branchWrite, chunkWrite, chunkFill bool + if isWrite { + if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 { + branchWrite = true + aw.branches[branchKey] |= AccessWitnessWriteFlag + } + + chunkValue := aw.chunks[chunkKey] + if (chunkValue & AccessWitnessWriteFlag) == 0 { + chunkWrite = true + aw.chunks[chunkKey] |= AccessWitnessWriteFlag + } + + // TODO: charge chunk filling costs if the leaf was previously empty in the state + } + + return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill +} + +type branchAccessKey struct { + addr common.Address + treeIndex uint256.Int +} + +func newBranchAccessKey(addr []byte, treeIndex uint256.Int) branchAccessKey { + var sk branchAccessKey + copy(sk.addr[20-len(addr):], addr) + sk.treeIndex = treeIndex + return sk +} + +type chunkAccessKey struct { + branchAccessKey + leafKey byte +} + +func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { + var lk chunkAccessKey + lk.branchAccessKey = branchKey + lk.leafKey = leafKey + return lk +} + +// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs +func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { + // note that in the case where the copied code is outside the range of the + // contract code but touches the last leaf with contract code in it, + // we don't include the last leaf of code in the AccessWitness. The + // reason that we do not need the last leaf is the account's code size + // is already in the AccessWitness so a stateless verifier can see that + // the code from the last leaf is not needed. + if (codeLen == 0 && size == 0) || startPC > codeLen { + return 0 + } + + endPC := startPC + size + if endPC > codeLen { + endPC = codeLen + } + if endPC > 0 { + endPC -= 1 // endPC is the last bytecode that will be touched. + } + + var statelessGasCharged uint64 + for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { + treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) + subIndex := byte((chunkNumber + 128) % 256) + gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) + var overflow bool + statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) + if overflow { + panic("overflow when adding gas") + } + } + + return statelessGasCharged +} + +func (aw *AccessWitness) TouchVersion(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchBalance(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchNonce(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchCodeSize(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) +} + +func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 { + return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) +} diff --git a/core/state/database.go b/core/state/database.go index 188ecf0c86a5e..365a28d900a14 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -139,6 +139,9 @@ type Trie interface { // nodes of the longest existing prefix of the key (at least the root), ending // with the node that proves the absence of the key. Prove(key []byte, proofDb ethdb.KeyValueWriter) error + + // IsVerkle returns true if the trie is verkle-tree based + IsVerkle() bool } // NewDatabase creates a backing store for state. The returned database is safe for @@ -180,7 +183,7 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { - return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems)) + return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(100)) } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { diff --git a/core/state/statedb.go b/core/state/statedb.go index 4a934fe82cdf5..42adf401c04a1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/triestate" + "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" ) @@ -139,6 +140,9 @@ type StateDB struct { // Transient storage transientStorage transientStorage + // Verkle witness + witness *AccessWitness + // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. journal *journal @@ -193,12 +197,30 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), } + if tr.IsVerkle() { + sdb.witness = sdb.NewAccessWitness() + } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) } return sdb, nil } +func (s *StateDB) NewAccessWitness() *AccessWitness { + return NewAccessWitness(utils.NewPointCache(100)) +} + +func (s *StateDB) Witness() *AccessWitness { + if s.witness == nil { + s.witness = s.NewAccessWitness() + } + return s.witness +} + +func (s *StateDB) SetWitness(aw *AccessWitness) { + s.witness = aw +} + // SetLogger sets the logger for account update hooks. func (s *StateDB) SetLogger(l *tracing.Hooks) { s.logger = l @@ -1275,7 +1297,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - Add coinbase to access list (EIP-3651) // - Reset transient storage (EIP-1153) func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { - if rules.IsBerlin { + if rules.IsEIP2929 { // Clear out any leftover from previous executions al := newAccessList() s.accessList = al diff --git a/core/state_processor.go b/core/state_processor.go index b1a8938f677a1..7d69718c034cd 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -120,6 +120,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo } // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) + txContext.Accesses = statedb.NewAccessWitness() evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env). @@ -158,6 +159,10 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } + if statedb.Witness() != nil { + statedb.Witness().Merge(txContext.Accesses) + } + // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) diff --git a/core/state_transition.go b/core/state_transition.go index a52e24dc43957..1e4bab6559ee4 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "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/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" @@ -68,7 +69,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 bool, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) { // Set the starting gas for the raw transaction var gas uint64 if isContractCreation && isHomestead { @@ -359,6 +360,19 @@ func (st *StateTransition) preCheck() error { return st.buyGas() } +// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool +// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false +// otherwise, do the subtraction setting the result in gasPool and return true +func tryConsumeGas(gasPool *uint64, gas uint64) bool { + if *gasPool < gas { + *gasPool = 0 + return false + } + + *gasPool -= gas + return true +} + // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // @@ -405,6 +419,32 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } st.gasRemaining -= gas + if rules.IsEIP4762 { + targetAddr := msg.To + originAddr := msg.From + + statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndComputeGas(originAddr.Bytes()) + if !tryConsumeGas(&st.gasRemaining, statelessGasOrigin) { + return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) + } + originNonce := st.evm.StateDB.GetNonce(originAddr) + + if msg.To != nil { + statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0) + if !tryConsumeGas(&st.gasRemaining, statelessGasDest) { + return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) + } + + // ensure the code size ends up in the access witness + st.evm.StateDB.GetCodeSize(*targetAddr) + } else { + contractAddr := crypto.CreateAddress(originAddr, originNonce) + if !tryConsumeGas(&st.gasRemaining, st.evm.Accesses.TouchAndChargeContractCreateInit(contractAddr.Bytes(), msg.Value.Sign() != 0)) { + return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) + } + } + } + // Check clause 6 value, overflow := uint256.FromBig(msg.Value) if overflow { @@ -458,6 +498,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { fee := new(uint256.Int).SetUint64(st.gasUsed()) fee.Mul(fee, effectiveTipU256) st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) + + // add the coinbase to the witness iff the fee is greater than 0 + if rules.IsEIP4762 && fee.Sign() != 0 { + st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true) + } } return &ExecutionResult{ diff --git a/core/vm/common.go b/core/vm/common.go index 90ba4a4ad15bc..ba75950e370be 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte { return common.RightPadBytes(data[start:end], int(size)) } +func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) { + length := uint64(len(data)) + if start > length { + start = length + } + end := start + size + if end > length { + end = length + } + return common.RightPadBytes(data[start:end], int(size)), start, end - start +} + // toWordSize returns the ceiled word size required for memory expansion. func toWordSize(size uint64) uint64 { if size > math.MaxUint64-31 { diff --git a/core/vm/contract.go b/core/vm/contract.go index 4e28260a67b73..cfda75b27e11f 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -57,6 +57,9 @@ type Contract struct { CodeAddr *common.Address Input []byte + // is the execution frame represented by this object a contract deployment + IsDeployment bool + Gas uint64 value *uint256.Int } diff --git a/core/vm/eips.go b/core/vm/eips.go index 9f06b2818feee..ff01a67e54c2f 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -37,6 +37,7 @@ var activators = map[int]func(*JumpTable){ 1884: enable1884, 1344: enable1344, 1153: enable1153, + 4762: enable4762, } // EnableEIP enables the given EIP on the config. @@ -319,3 +320,29 @@ func enable6780(jt *JumpTable) { maxStack: maxStack(1, 0), } } + +func enable4762(jt *JumpTable) { + jt[SSTORE].constantGas = 0 + jt[SSTORE].dynamicGas = gasSStore4762 + jt[SLOAD].constantGas = 0 + jt[SLOAD].dynamicGas = gasSLoad4762 + jt[BALANCE].dynamicGas = gasBalance4762 + jt[BALANCE].constantGas = 0 + jt[EXTCODESIZE].constantGas = 0 + jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762 + jt[EXTCODEHASH].constantGas = 0 + jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 + jt[EXTCODECOPY].constantGas = 0 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 + jt[CREATE].constantGas = params.CreateNGasEip4762 + jt[CREATE2].constantGas = params.CreateNGasEip4762 + jt[CALL].constantGas = 0 + jt[CALL].dynamicGas = gasCallEIP4762 + jt[CALLCODE].constantGas = 0 + jt[CALLCODE].dynamicGas = gasCallCodeEIP4762 + jt[STATICCALL].constantGas = 0 + jt[STATICCALL].dynamicGas = gasStaticCallEIP4762 + jt[DELEGATECALL].constantGas = 0 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762 +} diff --git a/core/vm/evm.go b/core/vm/evm.go index c18353a97354e..b8be848e68415 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -22,6 +22,7 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -44,7 +45,7 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { switch { case evm.chainRules.IsPrague: precompiles = PrecompiledContractsPrague - case evm.chainRules.IsCancun: + case evm.chainRules.IsCancun, evm.chainRules.IsVerkle: precompiles = PrecompiledContractsCancun case evm.chainRules.IsBerlin: precompiles = PrecompiledContractsBerlin @@ -85,10 +86,11 @@ type BlockContext struct { // All fields can change between transactions. type TxContext struct { // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) - BlobHashes []common.Hash // Provides information for BLOBHASH - BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) + BlobHashes []common.Hash // Provides information for BLOBHASH + BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set + Accesses *state.AccessWitness // Capture all state accesses for this tx } // EVM is the Ethereum Virtual Machine base object and provides @@ -149,6 +151,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } + if txCtx.Accesses == nil && chainConfig.IsPrague(blockCtx.BlockNumber, blockCtx.Time) { + evm.Accesses = evm.StateDB.(*state.StateDB).NewAccessWitness() + } evm.interpreter = NewEVMInterpreter(evm) return evm } @@ -156,6 +161,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { + if txCtx.Accesses == nil && evm.chainRules.IsPrague { + txCtx.Accesses = evm.StateDB.(*state.StateDB).NewAccessWitness() + } evm.TxContext = txCtx evm.StateDB = statedb } @@ -199,12 +207,24 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas snapshot := evm.StateDB.Snapshot() p, isPrecompile := evm.precompile(addr) + var creation bool if !evm.StateDB.Exist(addr) { + if !isPrecompile && evm.chainRules.IsEIP4762 { + // add proof of absence to witness + wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), false) + if gas < wgas { + evm.StateDB.RevertToSnapshot(snapshot) + return nil, 0, ErrOutOfGas + } + gas -= wgas + } + if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { // Calling a non-existing account, don't do anything. return nil, gas, nil } evm.StateDB.CreateAccount(addr) + creation = true } evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) @@ -222,6 +242,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // 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.IsDeployment = creation ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -436,10 +457,16 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, gas, ErrNonceUintOverflow } evm.StateDB.SetNonce(caller.Address(), nonce+1) +<<<<<<< HEAD // We add this to the access list _before_ taking a snapshot. Even if the // creation fails, the access-list change should not be rolled back. if evm.chainRules.IsBerlin { +======= + // We add this to the access list _before_ taking a snapshot. Even if the creation fails, + // the access-list change should not be rolled back + if evm.chainRules.IsEIP2929 { +>>>>>>> fc62f50eb (cmd, core, params, trie: Add verkle access witness) evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address. @@ -479,8 +506,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(address), value, gas) contract.SetCodeOptionalHash(&address, codeAndHash) + contract.IsDeployment = true - ret, err = evm.interpreter.Run(contract, nil, false) + // Charge the contract creation init gas in verkle mode + if evm.chainRules.IsEIP4762 { + if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeUnspecified) { + err = ErrOutOfGas + } + } + + if err == nil { + ret, err = evm.interpreter.Run(contract, nil, false) + } // Check whether the max code size has been exceeded, assign err if the case. if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { @@ -497,11 +534,24 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // be stored due to not enough gas set an error and let it be handled // by the error checking condition below. if err == nil { - createDataGas := uint64(len(ret)) * params.CreateDataGas - if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { - evm.StateDB.SetCode(address, ret) + if !evm.chainRules.IsEIP4762 { + createDataGas := uint64(len(ret)) * params.CreateDataGas + if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + err = ErrCodeStoreOutOfGas + } } else { - err = ErrCodeStoreOutOfGas + // Contract creation completed, touch the missing fields in the contract + if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeUnspecified) { + err = ErrOutOfGas + } + + if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeUnspecified) { + err = ErrOutOfGas + } + } + + if err == nil { + evm.StateDB.SetCode(address, ret) } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index fd5fa14cf5d7a..ca50ff9281503 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -383,7 +383,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } else if !evm.StateDB.Exist(address) { gas += params.CallNewAccountGas } - if transfersValue { + if transfersValue && !evm.chainRules.IsEIP4762 { gas += params.CallValueTransferGas } memoryGas, err := memoryGasCost(mem, memorySize) @@ -402,6 +402,21 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + if evm.chainRules.IsEIP4762 { + if _, isPrecompile := evm.precompile(address); !isPrecompile { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:])) + if overflow { + return 0, ErrGasUintOverflow + } + } + if transfersValue { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) + if overflow { + return 0, ErrGasUintOverflow + } + } + } + return gas, nil } @@ -414,7 +429,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory gas uint64 overflow bool ) - if stack.Back(2).Sign() != 0 { + if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { @@ -427,6 +442,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + if evm.chainRules.IsEIP4762 { + address := common.Address(stack.Back(1).Bytes20()) + if _, isPrecompile := evm.precompile(address); !isPrecompile { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) + if overflow { + return 0, ErrGasUintOverflow + } + } + } return gas, nil } @@ -443,6 +467,15 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + if evm.chainRules.IsEIP4762 { + address := common.Address(stack.Back(1).Bytes20()) + if _, isPrecompile := evm.precompile(address); !isPrecompile { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) + if overflow { + return 0, ErrGasUintOverflow + } + } + } return gas, nil } @@ -459,6 +492,15 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + if evm.chainRules.IsEIP4762 { + address := common.Address(stack.Back(1).Bytes20()) + if _, isPrecompile := evm.precompile(address); !isPrecompile { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) + if overflow { + return 0, ErrGasUintOverflow + } + } + } return gas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index a062bb15ff5c3..8603366514cdd 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -363,9 +363,17 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ if overflow { uint64CodeOffset = math.MaxUint64 } - codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + contractAddr := scope.Contract.Address() + paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } + scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) return nil, nil } @@ -382,8 +390,23 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = math.MaxUint64 } addr := common.Address(a.Bytes20()) - codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + if interpreter.evm.chainRules.IsEIP4762 { + code := interpreter.evm.StateDB.GetCode(addr) + contract := &Contract{ + Code: code, + self: AccountRef(addr), + } + paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) + } else { + codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + } return nil, nil } @@ -438,6 +461,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( num.Clear() return nil, nil } + var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber.Uint64() if upper < 257 { @@ -587,6 +611,15 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } + + if interpreter.evm.chainRules.IsEIP4762 { + contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) + statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + return nil, ErrExecutionReverted + } + } + // reuse size int for stackvalue stackvalue := size @@ -627,6 +660,15 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = scope.Contract.Gas ) + if interpreter.evm.chainRules.IsEIP4762 { + codeAndHash := &codeAndHash{code: input} + contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + return nil, ErrExecutionReverted + } + } + // Apply EIP150 gas -= gas / 64 scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) @@ -641,7 +683,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] stackvalue.SetBytes(addr.Bytes()) } scope.Stack.push(&stackvalue) - scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) if suberr == ErrExecutionReverted { @@ -880,6 +921,17 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by *pc += 1 if *pc < codeLen { scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + + if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsEIP4762 && *pc%31 == 0 { + // touch next chunk if PUSH1 is at the boundary. if so, *pc has + // advanced past this boundary. + contractAddr := scope.Contract.Address() + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } } else { scope.Stack.push(integer.Clear()) } @@ -900,6 +952,16 @@ func makePush(size uint64, pushByteSize int) executionFunc { pushByteSize, )), ) + + if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsEIP4762 { + contractAddr := scope.Contract.Address() + statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } + *pc += size return nil, nil } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 406927e32158e..a10363b7f3be2 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -99,6 +99,9 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // If jump table was not initialised we set the default one. var table *JumpTable switch { + case evm.chainRules.IsVerkle: + // TODO replace with prooper instruction set when fork is specified + table = &verkleInstructionSet case evm.chainRules.IsCancun: table = &cancunInstructionSet case evm.chainRules.IsShanghai: @@ -219,6 +222,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // Capture pre-execution values for tracing. logged, pcCopy, gasCopy = false, pc, contract.Gas } + + if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment { + // if the PC ends up in a new "chunk" of verkleized code, charge the + // associated costs. + contractAddr := contract.Address() + contract.Gas -= in.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) + } + // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. op = contract.GetOp(pc) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 65716f9442af0..37e63eaab64c4 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -57,6 +57,7 @@ var ( mergeInstructionSet = newMergeInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() cancunInstructionSet = newCancunInstructionSet() + verkleInstructionSet = newVerkleInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -80,6 +81,13 @@ func validate(jt JumpTable) JumpTable { return jt } +func newVerkleInstructionSet() JumpTable { + instructionSet := newShanghaiInstructionSet() + enable6780(&instructionSet) + enable4762(&instructionSet) + return validate(instructionSet) +} + func newCancunInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go new file mode 100644 index 0000000000000..a342622cf5626 --- /dev/null +++ b/core/vm/operations_verkle.go @@ -0,0 +1,129 @@ +// 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 vm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + gas := evm.Accesses.TouchBalance(address[:], false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + wgas := evm.Accesses.TouchVersion(address[:], false) + wgas += evm.Accesses.TouchCodeSize(address[:], false) + if wgas == 0 { + wgas = params.WarmStorageReadCostEIP2929 + } + + return wgas, nil +} + +func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + codehashgas := evm.Accesses.TouchCodeHash(address[:], false) + if codehashgas == 0 { + codehashgas = params.WarmStorageReadCostEIP2929 + } + return codehashgas, nil +} + +func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + wgas := evm.Accesses.TouchCodeSize(contract.Address().Bytes(), false) + wgas += evm.Accesses.TouchCodeHash(contract.Address().Bytes(), false) + if wgas == 0 { + wgas = params.WarmStorageReadCostEIP2929 + } + return wgas + gas, nil + } +} + +var ( + gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall) + gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode) + gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall) + gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) +) + +func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + beneficiaryAddr := common.Address(stack.peek().Bytes20()) + contractAddr := contract.Address() + statelessGas := evm.Accesses.TouchVersion(contractAddr[:], false) + statelessGas += evm.Accesses.TouchCodeSize(contractAddr[:], false) + statelessGas += evm.Accesses.TouchBalance(contractAddr[:], false) + if contractAddr != beneficiaryAddr { + statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], false) + } + // Charge write costs if it transfers value + if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { + statelessGas += evm.Accesses.TouchBalance(contractAddr[:], true) + if contractAddr != beneficiaryAddr { + statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], true) + } + } + return statelessGas, nil +} + +func gasExtCodeCopyEIP4762(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()) + wgas := evm.Accesses.TouchVersion(addr[:], false) + wgas += evm.Accesses.TouchCodeSize(addr[:], false) + if wgas == 0 { + wgas = params.WarmStorageReadCostEIP2929 + } + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, wgas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} diff --git a/go.mod b/go.mod index 3cd0d82bdf7cc..cf76ae35eb059 100644 --- a/go.mod +++ b/go.mod @@ -15,20 +15,20 @@ require ( github.com/cloudflare/cloudflare-go v0.79.0 github.com/cockroachdb/pebble v1.1.0 github.com/consensys/gnark-crypto v0.12.1 - github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 + github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c github.com/crate-crypto/go-kzg-4844 v1.0.0 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set/v2 v2.1.0 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 github.com/ethereum/c-kzg-4844 v1.0.0 + github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 github.com/fatih/color v1.13.0 github.com/ferranbt/fastssz v0.1.2 github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e github.com/fjl/memsize v0.0.2 github.com/fsnotify/fsnotify v1.6.0 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/protobuf v1.5.4 diff --git a/go.sum b/go.sum index a7b4eb1c138a7..76e6feccdb13a 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJ github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -169,6 +169,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= @@ -185,8 +187,6 @@ github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgx github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= diff --git a/params/config.go b/params/config.go index 439e882189240..207cbb9b6b489 100644 --- a/params/config.go +++ b/params/config.go @@ -581,6 +581,11 @@ func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) } +// IsEIP4762 returns whether eip 4762 has been activated at given block. +func (c *ChainConfig) IsEIP4762(num *big.Int, time uint64) bool { + return c.IsVerkle(num, time) +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError { @@ -901,6 +906,7 @@ func (err *ConfigCompatError) Error() string { type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool + IsEIP2929, IsEIP4762 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague bool @@ -926,6 +932,8 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), + IsEIP2929: c.IsBerlin(num) && !c.IsPrague(num, timestamp), + IsEIP4762: c.IsPrague(num, timestamp), IsLondon: c.IsLondon(num), IsMerge: isMerge, IsShanghai: isMerge && c.IsShanghai(num, timestamp), diff --git a/params/protocol_params.go b/params/protocol_params.go index 863cf58ece461..d375a96642732 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -86,6 +86,7 @@ const ( LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. Create2Gas uint64 = 32000 // Once per CREATE2 operation + CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. diff --git a/params/verkle_params.go b/params/verkle_params.go new file mode 100644 index 0000000000000..93d4f7cd64760 --- /dev/null +++ b/params/verkle_params.go @@ -0,0 +1,36 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package params + +// Verkle tree EIP: costs associated to witness accesses +var ( + WitnessBranchReadCost uint64 = 1900 + WitnessChunkReadCost uint64 = 200 + WitnessBranchWriteCost uint64 = 3000 + WitnessChunkWriteCost uint64 = 500 + WitnessChunkFillCost uint64 = 6200 +) + +// ClearVerkleWitnessCosts sets all witness costs to 0, which is necessary +// for historical block replay simulations. +func ClearVerkleWitnessCosts() { + WitnessBranchReadCost = 0 + WitnessChunkReadCost = 0 + WitnessBranchWriteCost = 0 + WitnessChunkWriteCost = 0 + WitnessChunkFillCost = 0 +} diff --git a/trie/secure_trie.go b/trie/secure_trie.go index efd4dfb5d33fb..e38d5ac4dc360 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -284,3 +284,7 @@ func (t *StateTrie) getSecKeyCache() map[string][]byte { } return t.secKeyCache } + +func (t *StateTrie) IsVerkle() bool { + return false +} diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index 328b2d2527615..2a4a632d49262 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -23,7 +23,7 @@ import ( "github.com/crate-crypto/go-ipa/bandersnatch/fr" "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/metrics" - "github.com/gballet/go-verkle" + "github.com/ethereum/go-verkle" "github.com/holiman/uint256" ) @@ -219,7 +219,7 @@ func CodeChunkKey(address []byte, chunk *uint256.Int) []byte { return GetTreeKey(address, treeIndex, subIndex) } -func storageIndex(bytes []byte) (*uint256.Int, byte) { +func StorageIndex(bytes []byte) (*uint256.Int, byte) { // If the storage slot is in the header, we need to add the header offset. var key uint256.Int key.SetBytes(bytes) @@ -245,7 +245,7 @@ func storageIndex(bytes []byte) (*uint256.Int, byte) { // StorageSlotKey returns the verkle tree key of the storage slot for the // specified account. func StorageSlotKey(address []byte, storageKey []byte) []byte { - treeIndex, subIndex := storageIndex(storageKey) + treeIndex, subIndex := StorageIndex(storageKey) return GetTreeKey(address, treeIndex, subIndex) } @@ -296,7 +296,7 @@ func CodeChunkKeyWithEvaluatedAddress(addressPoint *verkle.Point, chunk *uint256 // slot for the specified account. The difference between StorageSlotKey is the // address evaluation is already computed to minimize the computational overhead. func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte { - treeIndex, subIndex := storageIndex(storageKey) + treeIndex, subIndex := StorageIndex(storageKey) return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) } diff --git a/trie/verkle.go b/trie/verkle.go index 01d813d9ec9ba..bb0c54857f6da 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/triedb/database" - "github.com/gballet/go-verkle" + "github.com/ethereum/go-verkle" "github.com/holiman/uint256" ) From bcc6da85cca523c09f2763d570c54f6da506f4b8 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:13:35 +0100 Subject: [PATCH 02/30] fix failing tests --- cmd/geth/consolecmd_test.go | 6 +++--- trie/utils/verkle_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 4d62206417035..33d6d4bbc4211 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -103,17 +103,17 @@ func TestAttachWelcome(t *testing.T) { "--http", "--http.port", httpPort, "--ws", "--ws.port", wsPort) t.Run("ipc", func(t *testing.T) { - waitForEndpoint(t, ipc, 3*time.Second) + waitForEndpoint(t, ipc, 4*time.Second) testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) }) t.Run("http", func(t *testing.T) { endpoint := "http://127.0.0.1:" + httpPort - waitForEndpoint(t, endpoint, 3*time.Second) + waitForEndpoint(t, endpoint, 4*time.Second) testAttachWelcome(t, geth, endpoint, httpAPIs) }) t.Run("ws", func(t *testing.T) { endpoint := "ws://127.0.0.1:" + wsPort - waitForEndpoint(t, endpoint, 3*time.Second) + waitForEndpoint(t, endpoint, 4*time.Second) testAttachWelcome(t, geth, endpoint, httpAPIs) }) geth.Kill() diff --git a/trie/utils/verkle_test.go b/trie/utils/verkle_test.go index 28b059c3794ed..c29504a6d0cb9 100644 --- a/trie/utils/verkle_test.go +++ b/trie/utils/verkle_test.go @@ -20,7 +20,7 @@ import ( "bytes" "testing" - "github.com/gballet/go-verkle" + "github.com/ethereum/go-verkle" "github.com/holiman/uint256" ) From 2b3391ecb3eebd59ea169e5a581c027141f05aea Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:55:58 +0100 Subject: [PATCH 03/30] fix linter issue --- core/state/database.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 365a28d900a14..8ae2c525461a1 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" - "github.com/crate-crypto/go-ipa/banderwagon" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core/rawdb" @@ -39,12 +38,6 @@ const ( // Cache size granted for caching clean code. codeCacheSize = 64 * 1024 * 1024 - - // commitmentSize is the size of commitment stored in cache. - commitmentSize = banderwagon.UncompressedSize - - // Cache item granted for caching commitment results. - commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength) ) // Database wraps access to tries and contract code. From a21c021b51bf7e238d01f52240512d564946c9da Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:09:51 +0100 Subject: [PATCH 04/30] add beacon block hash to the witness --- core/chain_makers.go | 2 +- core/state_processor.go | 14 ++++++++++---- miner/worker.go | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/chain_makers.go b/core/chain_makers.go index 13d7cb86c043c..1656984558515 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -102,7 +102,7 @@ func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{}) ) - ProcessBeaconBlockRoot(root, vmenv, b.statedb) + ProcessBeaconBlockRoot(root, vmenv, b.statedb, b.Number(), b.Timestamp()) } // addTx adds a transaction to the generated block. If no coinbase has diff --git a/core/state_processor.go b/core/state_processor.go index 7d69718c034cd..15b8703b6e895 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -78,7 +78,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg signer = types.MakeSigner(p.config, header.Number, header.Time) ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { - ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, header.Number, header.Time) } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { @@ -190,7 +190,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // contract. This method is exported to be used in tests. -func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) { +func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB, num *big.Int, time uint64) { // If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with // the new root msg := &Message{ @@ -202,8 +202,14 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat To: ¶ms.BeaconRootsAddress, Data: beaconRoot[:], } - vmenv.Reset(NewEVMTxContext(msg), statedb) - statedb.AddAddressToAccessList(params.BeaconRootsAddress) + txctx := NewEVMTxContext(msg) + vmenv.Reset(txctx, statedb) + if vmenv.ChainConfig().Rules(num, true, time).IsEIP2929 { + statedb.AddAddressToAccessList(params.BeaconRootsAddress) + } _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) + if vmenv.ChainConfig().Rules(num, true, time).IsEIP4762 { + statedb.Witness().Merge(txctx.Accesses) + } statedb.Finalise(true) } diff --git a/miner/worker.go b/miner/worker.go index 5dc3e2056b81b..a9c53ae7c12bb 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -198,7 +198,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error) if header.ParentBeaconRoot != nil { context := core.NewEVMBlockContext(header, miner.chain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{}) - core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) + core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state, env.header.Number, env.header.Time) } return env, nil } From e34ac0ffa80e410e84a1772e6a0e0a34ea1e2d68 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:31:53 +0200 Subject: [PATCH 05/30] fix reference error --- core/chain_makers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chain_makers.go b/core/chain_makers.go index 1656984558515..20614a0c0d212 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/triedb" - "github.com/gballet/go-verkle" + "github.com/ethereum/go-verkle" "github.com/holiman/uint256" ) From 54bdfb42bd0cd473a3106e99214feb9b04b10b4a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:05:37 +0200 Subject: [PATCH 06/30] Update core/state/access_witness.go Co-authored-by: Martin HS --- core/state/access_witness.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 10db53992e236..7fd836d3631ba 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -97,7 +97,7 @@ func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { return gas } -func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 { +func (aw *AccessWitness) TouchAndChargeMessageCall(destination []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false) From f534a4dc35a6e6a1ad6be209a419aba52ec2753c Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:05:26 +0200 Subject: [PATCH 07/30] partial review feedback --- cmd/evm/internal/t8ntool/execution.go | 2 +- consensus/beacon/consensus.go | 4 ++- core/state/access_witness.go | 44 ++++++++++++--------------- core/state_transition.go | 12 ++------ core/tracing/hooks.go | 6 ++++ core/vm/eips.go | 1 + core/vm/evm.go | 6 ++-- core/vm/instructions.go | 12 ++------ core/vm/operations_verkle.go | 20 ++++++++++++ 9 files changed, 59 insertions(+), 48 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 3c09229e1c5c5..00c996c447b2e 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -194,7 +194,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) - core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) + core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb, big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) } for i := 0; txIt.Next(); i++ { diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index dd273a6f59a89..59bbba9fd105f 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -361,8 +361,10 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. amount = amount.Mul(amount, uint256.NewInt(params.GWei)) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + // Add the balance of each withdrawal to the witness, no gas will + // be charged. if chain.Config().IsEIP4762(header.Number, header.Time) { - state.Witness().TouchFullAccount(w.Address[:], true) + state.Witness().TouchBalance(w.Address[:], true) } } // No block reward which is issued by consensus layer instead. diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 7fd836d3631ba..efb6ce8adb4b3 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -89,6 +89,8 @@ func (aw *AccessWitness) Copy() *AccessWitness { return naw } +// TouchFullAccount returns the gas to be charged for each of the currently cold +// member fields of an account. func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { var gas uint64 for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { @@ -97,6 +99,9 @@ func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { return gas } +// TouchAndChargeMessageCall returns the gas to be charged for each of the currently +// cold member fields of an account, that need to be touched when making a message +// call to that account. func (aw *AccessWitness) TouchAndChargeMessageCall(destination []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) @@ -104,6 +109,8 @@ func (aw *AccessWitness) TouchAndChargeMessageCall(destination []byte) uint64 { return gas } +// TouchAndChargeValueTransfer returns the gas to be charged for each of the currently +// cold balance member fields of the caller and the callee accounts. func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) @@ -111,8 +118,8 @@ func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []by return gas } -// TouchAndChargeContractCreateInit charges access costs to initiate -// a contract creation +// TouchAndChargeContractCreateInit returns the access gas costs for the initialization of +// a contract creation. func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) @@ -123,41 +130,30 @@ func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSen return gas } -func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { +// TouchTxOrigin adds the member fields of the sender account to the witness, +// so that cold accesses are not charged, since they are covered by the 21000 gas. +func (aw *AccessWitness) TouchTxOrigin(originAddr []byte) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) } - - // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address - // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling. - // This is the reason why we return 0 instead of `gas`. - // Note that we still have to touch the addresses to make sure the witness is correct. - return 0 } -func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false) - if sendsValue { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) - } else { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false) +// TouchTxDestination adds the member fields of the sender account to the witness, +// so that cold accesses are not charged, since they are covered by the 21000 gas. +func (aw *AccessWitness) TouchTxDestination(targetAddr []byte, sendsValue bool) { + for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { + aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.VersionLeafKey && sendsValue) } - - // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address - // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling. - // This is the reason why we return 0 instead of `gas`. - // Note that we still have to touch the addresses to make sure the witness is correct. - return 0 } +// TouchSlotAndChargeGas returns the amount of gas to be charged for a cold storage access. func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 { treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } +// touchAddressAndChargeGas adds any missing access event to the witness, and returns the cold +// access cost to be charged, if need be. func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite) diff --git a/core/state_transition.go b/core/state_transition.go index 1e4bab6559ee4..14f71be5fbdb3 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -423,17 +423,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { targetAddr := msg.To originAddr := msg.From - statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndComputeGas(originAddr.Bytes()) - if !tryConsumeGas(&st.gasRemaining, statelessGasOrigin) { - return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) - } + st.evm.Accesses.TouchTxOrigin(originAddr.Bytes()) originNonce := st.evm.StateDB.GetNonce(originAddr) if msg.To != nil { - statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0) - if !tryConsumeGas(&st.gasRemaining, statelessGasDest) { - return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) - } + st.evm.Accesses.TouchTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) @@ -501,7 +495,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true) + st.evm.Accesses.TouchBalance(st.evm.Context.Coinbase[:], true) } } diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 9ca6ee39fbe73..33ce4c45b809f 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -272,6 +272,12 @@ const ( GasChangeCallStorageColdAccess GasChangeReason = 13 // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. GasChangeCallFailedExecution GasChangeReason = 14 + // GasChangeWitnessContractInit is the amount charged for adding to the witness during the contract creation initialization step + GasChangeWitnessContractInit GasChangeReason = 15 + // GasChangeWitnessContractCreation is the amount charged for adding to the witness during the contract creation finalization step + GasChangeWitnessContractCreation GasChangeReason = 16 + // GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks + GasChangeWitnessCodeChunk GasChangeReason = 17 // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as // it will be "manually" tracked by a direct emit of the gas change event. diff --git a/core/vm/eips.go b/core/vm/eips.go index ff01a67e54c2f..ccf6e8f9b4d19 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -334,6 +334,7 @@ func enable4762(jt *JumpTable) { jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 jt[EXTCODECOPY].constantGas = 0 jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 + jt[CODECOPY].dynamicGas = gasCodeCopyEip4762 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 jt[CREATE].constantGas = params.CreateNGasEip4762 jt[CREATE2].constantGas = params.CreateNGasEip4762 diff --git a/core/vm/evm.go b/core/vm/evm.go index b8be848e68415..48871e8d32b53 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -510,7 +510,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeUnspecified) { + if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { err = ErrOutOfGas } } @@ -541,11 +541,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } else { // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeUnspecified) { + if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { err = ErrOutOfGas } - if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeUnspecified) { + if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { err = ErrOutOfGas } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 8603366514cdd..7246a363fd718 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -364,16 +364,8 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ uint64CodeOffset = math.MaxUint64 } - contractAddr := scope.Contract.Address() - paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) - if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment { - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } - scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index a342622cf5626..bb768a9e9a2b1 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -108,6 +108,26 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return statelessGas, nil } +func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + var ( + codeOffset = stack.Back(1) + length = stack.Back(2) + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) + if !contract.IsDeployment { + gas += evm.Accesses.TouchCodeChunksRangeAndChargeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + } + return gas, nil +} + func gasExtCodeCopyEIP4762(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) From 1bdae104a44a0cd87ed449f7204928b5a66baa21 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:26:46 +0200 Subject: [PATCH 08/30] update based on fixes added to the main branch --- core/state/access_witness.go | 4 ++-- core/state_transition.go | 7 ------- core/vm/gas_table.go | 29 +++-------------------------- core/vm/operations_verkle.go | 16 +++++++++++++--- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/core/state/access_witness.go b/core/state/access_witness.go index efb6ce8adb4b3..2ad89b21c20e4 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -104,8 +104,8 @@ func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { // call to that account. func (aw *AccessWitness) TouchAndChargeMessageCall(destination []byte) uint64 { var gas uint64 - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false) + gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) + gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) return gas } diff --git a/core/state_transition.go b/core/state_transition.go index 14f71be5fbdb3..eb81509423c68 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -26,7 +26,6 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "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/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" @@ -424,18 +423,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { originAddr := msg.From st.evm.Accesses.TouchTxOrigin(originAddr.Bytes()) - originNonce := st.evm.StateDB.GetNonce(originAddr) if msg.To != nil { st.evm.Accesses.TouchTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) - } else { - contractAddr := crypto.CreateAddress(originAddr, originNonce) - if !tryConsumeGas(&st.gasRemaining, st.evm.Accesses.TouchAndChargeContractCreateInit(contractAddr.Bytes(), msg.Value.Sign() != 0)) { - return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas) - } } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index ca50ff9281503..28bde5121bcd5 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -403,12 +403,6 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, ErrGasUintOverflow } if evm.chainRules.IsEIP4762 { - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:])) - if overflow { - return 0, ErrGasUintOverflow - } - } if transfersValue { gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { @@ -444,8 +438,9 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory } if evm.chainRules.IsEIP4762 { address := common.Address(stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) + transfersValue := !stack.Back(2).IsZero() + if transfersValue { + gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } @@ -467,15 +462,6 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsEIP4762 { - address := common.Address(stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) - if overflow { - return 0, ErrGasUintOverflow - } - } - } return gas, nil } @@ -492,15 +478,6 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - if evm.chainRules.IsEIP4762 { - address := common.Address(stack.Back(1).Bytes20()) - if _, isPrecompile := evm.precompile(address); !isPrecompile { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) - if overflow { - return 0, ErrGasUintOverflow - } - } - } return gas, nil } diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index bb768a9e9a2b1..f8dbd6de402ce 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -49,17 +49,22 @@ func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() + if _, isPrecompile := evm.precompile(address); isPrecompile { + return 0, nil + } wgas := evm.Accesses.TouchVersion(address[:], false) wgas += evm.Accesses.TouchCodeSize(address[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } - return wgas, nil } func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() + if _, isPrecompile := evm.precompile(address); isPrecompile { + return 0, nil + } codehashgas := evm.Accesses.TouchCodeHash(address[:], false) if codehashgas == 0 { codehashgas = params.WarmStorageReadCostEIP2929 @@ -73,8 +78,10 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if err != nil { return 0, err } - wgas := evm.Accesses.TouchCodeSize(contract.Address().Bytes(), false) - wgas += evm.Accesses.TouchCodeHash(contract.Address().Bytes(), false) + if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { + return gas, nil + } + wgas := evm.Accesses.TouchAndChargeMessageCall(contract.Address().Bytes()) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -91,6 +98,9 @@ var ( func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { beneficiaryAddr := common.Address(stack.peek().Bytes20()) + if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile { + return 0, nil + } contractAddr := contract.Address() statelessGas := evm.Accesses.TouchVersion(contractAddr[:], false) statelessGas += evm.Accesses.TouchCodeSize(contractAddr[:], false) From fea85534ecd989df85fb659c1158293c32806ada Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:28:09 +0200 Subject: [PATCH 09/30] add more fixes --- core/vm/evm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 48871e8d32b53..c6195c2f35a48 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -542,11 +542,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } else { // Contract creation completed, touch the missing fields in the contract if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { - err = ErrOutOfGas + err = ErrCodeStoreOutOfGas } - if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { - err = ErrOutOfGas + if err == nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { + err = ErrCodeStoreOutOfGas } } From 2334cb7275ec49d190d0a95077ff9ff9c2b2a166 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 15 Apr 2024 21:30:17 +0200 Subject: [PATCH 10/30] rename access witness functions --- consensus/beacon/consensus.go | 2 +- core/state/access_witness.go | 40 +++++++++++++++++------------------ core/state_transition.go | 6 +++--- core/vm/evm.go | 8 +++---- core/vm/gas_table.go | 4 ++-- core/vm/instructions.go | 10 ++++----- core/vm/interpreter.go | 2 +- core/vm/operations_verkle.go | 32 ++++++++++++++-------------- 8 files changed, 52 insertions(+), 52 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 59bbba9fd105f..4f518529f78ca 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -364,7 +364,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // Add the balance of each withdrawal to the witness, no gas will // be charged. if chain.Config().IsEIP4762(header.Number, header.Time) { - state.Witness().TouchBalance(w.Address[:], true) + state.Witness().BalanceGas(w.Address[:], true) } } // No block reward which is issued by consensus layer instead. diff --git a/core/state/access_witness.go b/core/state/access_witness.go index 2ad89b21c20e4..88fbdf3a589bb 100644 --- a/core/state/access_witness.go +++ b/core/state/access_witness.go @@ -89,9 +89,9 @@ func (aw *AccessWitness) Copy() *AccessWitness { return naw } -// TouchFullAccount returns the gas to be charged for each of the currently cold +// AddAccount returns the gas to be charged for each of the currently cold // member fields of an account. -func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { +func (aw *AccessWitness) AddAccount(addr []byte, isWrite bool) uint64 { var gas uint64 for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) @@ -99,28 +99,28 @@ func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 { return gas } -// TouchAndChargeMessageCall returns the gas to be charged for each of the currently +// MessageCallGas returns the gas to be charged for each of the currently // cold member fields of an account, that need to be touched when making a message // call to that account. -func (aw *AccessWitness) TouchAndChargeMessageCall(destination []byte) uint64 { +func (aw *AccessWitness) MessageCallGas(destination []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) return gas } -// TouchAndChargeValueTransfer returns the gas to be charged for each of the currently +// ValueTransferGas returns the gas to be charged for each of the currently // cold balance member fields of the caller and the callee accounts. -func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { +func (aw *AccessWitness) ValueTransferGas(callerAddr, targetAddr []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) return gas } -// TouchAndChargeContractCreateInit returns the access gas costs for the initialization of +// ContractCreateInitGas returns the access gas costs for the initialization of // a contract creation. -func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 { +func (aw *AccessWitness) ContractCreateInitGas(addr []byte, createSendsValue bool) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) @@ -130,24 +130,24 @@ func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSen return gas } -// TouchTxOrigin adds the member fields of the sender account to the witness, +// AddTxOrigin adds the member fields of the sender account to the witness, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (aw *AccessWitness) TouchTxOrigin(originAddr []byte) { +func (aw *AccessWitness) AddTxOrigin(originAddr []byte) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) } } -// TouchTxDestination adds the member fields of the sender account to the witness, +// AddTxDestination adds the member fields of the sender account to the witness, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (aw *AccessWitness) TouchTxDestination(targetAddr []byte, sendsValue bool) { +func (aw *AccessWitness) AddTxDestination(targetAddr []byte, sendsValue bool) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.VersionLeafKey && sendsValue) } } -// TouchSlotAndChargeGas returns the amount of gas to be charged for a cold storage access. -func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 { +// SlotGas returns the amount of gas to be charged for a cold storage access. +func (aw *AccessWitness) SlotGas(addr []byte, slot common.Hash, isWrite bool) uint64 { treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } @@ -238,7 +238,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { } // touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { +func (aw *AccessWitness) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { // note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, // we don't include the last leaf of code in the AccessWitness. The @@ -272,22 +272,22 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s return statelessGasCharged } -func (aw *AccessWitness) TouchVersion(addr []byte, isWrite bool) uint64 { +func (aw *AccessWitness) VersionGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) } -func (aw *AccessWitness) TouchBalance(addr []byte, isWrite bool) uint64 { +func (aw *AccessWitness) BalanceGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) } -func (aw *AccessWitness) TouchNonce(addr []byte, isWrite bool) uint64 { +func (aw *AccessWitness) NonceGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) } -func (aw *AccessWitness) TouchCodeSize(addr []byte, isWrite bool) uint64 { +func (aw *AccessWitness) CodeSizeGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) } -func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 { +func (aw *AccessWitness) CodeHashGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) } diff --git a/core/state_transition.go b/core/state_transition.go index eb81509423c68..c923d1c7d7ba0 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -422,10 +422,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { targetAddr := msg.To originAddr := msg.From - st.evm.Accesses.TouchTxOrigin(originAddr.Bytes()) + st.evm.Accesses.AddTxOrigin(originAddr.Bytes()) if msg.To != nil { - st.evm.Accesses.TouchTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) + st.evm.Accesses.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) @@ -488,7 +488,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.Accesses.TouchBalance(st.evm.Context.Coinbase[:], true) + st.evm.Accesses.BalanceGas(st.evm.Context.Coinbase[:], true) } } diff --git a/core/vm/evm.go b/core/vm/evm.go index c6195c2f35a48..85474da08ad2f 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -211,7 +211,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 { // add proof of absence to witness - wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), false) + wgas := evm.Accesses.AddAccount(addr.Bytes(), false) if gas < wgas { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas @@ -510,7 +510,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + if !contract.UseGas(evm.Accesses.ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { err = ErrOutOfGas } } @@ -541,11 +541,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } else { // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { + if !contract.UseGas(evm.Accesses.AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { err = ErrCodeStoreOutOfGas } - if err == nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { + if err == nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { err = ErrCodeStoreOutOfGas } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 28bde5121bcd5..dc9a7f34ee2d9 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -404,7 +404,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } if evm.chainRules.IsEIP4762 { if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.Accesses.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } @@ -440,7 +440,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory address := common.Address(stack.Back(1).Bytes20()) transfersValue := !stack.Back(2).IsZero() if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.Accesses.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7246a363fd718..0771c5f052438 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -389,7 +389,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -606,7 +606,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b if interpreter.evm.chainRules.IsEIP4762 { contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) - statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0) + statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], value.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -655,7 +655,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] if interpreter.evm.chainRules.IsEIP4762 { codeAndHash := &codeAndHash{code: input} contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0) + statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -918,7 +918,7 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -947,7 +947,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsEIP4762 { contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index a10363b7f3be2..105ef860fc9a2 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -227,7 +227,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - contract.Gas -= in.evm.TxContext.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) + contract.Gas -= in.evm.TxContext.Accesses.CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) } // Get the operation from the jump table and validate the stack to ensure there are diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index f8dbd6de402ce..3ba6411bc0223 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -23,7 +23,7 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) + gas := evm.Accesses.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -31,7 +31,7 @@ func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) + gas := evm.Accesses.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -40,7 +40,7 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - gas := evm.Accesses.TouchBalance(address[:], false) + gas := evm.Accesses.BalanceGas(address[:], false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -52,8 +52,8 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - wgas := evm.Accesses.TouchVersion(address[:], false) - wgas += evm.Accesses.TouchCodeSize(address[:], false) + wgas := evm.Accesses.VersionGas(address[:], false) + wgas += evm.Accesses.CodeSizeGas(address[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -65,7 +65,7 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - codehashgas := evm.Accesses.TouchCodeHash(address[:], false) + codehashgas := evm.Accesses.CodeHashGas(address[:], false) if codehashgas == 0 { codehashgas = params.WarmStorageReadCostEIP2929 } @@ -81,7 +81,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { return gas, nil } - wgas := evm.Accesses.TouchAndChargeMessageCall(contract.Address().Bytes()) + wgas := evm.Accesses.MessageCallGas(contract.Address().Bytes()) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -102,17 +102,17 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } contractAddr := contract.Address() - statelessGas := evm.Accesses.TouchVersion(contractAddr[:], false) - statelessGas += evm.Accesses.TouchCodeSize(contractAddr[:], false) - statelessGas += evm.Accesses.TouchBalance(contractAddr[:], false) + statelessGas := evm.Accesses.VersionGas(contractAddr[:], false) + statelessGas += evm.Accesses.CodeSizeGas(contractAddr[:], false) + statelessGas += evm.Accesses.BalanceGas(contractAddr[:], false) if contractAddr != beneficiaryAddr { - statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], false) + statelessGas += evm.Accesses.BalanceGas(beneficiaryAddr[:], false) } // Charge write costs if it transfers value if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { - statelessGas += evm.Accesses.TouchBalance(contractAddr[:], true) + statelessGas += evm.Accesses.BalanceGas(contractAddr[:], true) if contractAddr != beneficiaryAddr { - statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], true) + statelessGas += evm.Accesses.BalanceGas(beneficiaryAddr[:], true) } } return statelessGas, nil @@ -133,7 +133,7 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) if !contract.IsDeployment { - gas += evm.Accesses.TouchCodeChunksRangeAndChargeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + gas += evm.Accesses.CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) } return gas, nil } @@ -145,8 +145,8 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo return 0, err } addr := common.Address(stack.peek().Bytes20()) - wgas := evm.Accesses.TouchVersion(addr[:], false) - wgas += evm.Accesses.TouchCodeSize(addr[:], false) + wgas := evm.Accesses.VersionGas(addr[:], false) + wgas += evm.Accesses.CodeSizeGas(addr[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } From 811ebfeb083b8bb83261bccea7f6ba1d3ea220c3 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:44:31 +0200 Subject: [PATCH 11/30] fix rebase issues --- core/state_processor_test.go | 2 +- core/vm/evm.go | 6 ------ eth/state_accessor.go | 2 +- eth/tracers/api.go | 8 ++++---- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index dc9cb203bcece..1c29ef39dc2c2 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -481,7 +481,7 @@ func TestProcessVerkle(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */) - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(293644 /* execution costs */) + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(179444 /* execution costs */) blockGasUsagesExpected := []uint64{ txCost1*2 + txCost2, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, diff --git a/core/vm/evm.go b/core/vm/evm.go index 85474da08ad2f..742ea8623607d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -457,16 +457,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, gas, ErrNonceUintOverflow } evm.StateDB.SetNonce(caller.Address(), nonce+1) -<<<<<<< HEAD // We add this to the access list _before_ taking a snapshot. Even if the // creation fails, the access-list change should not be rolled back. - if evm.chainRules.IsBerlin { -======= - // We add this to the access list _before_ taking a snapshot. Even if the creation fails, - // the access-list change should not be rolled back if evm.chainRules.IsEIP2929 { ->>>>>>> fc62f50eb (cmd, core, params, trie: Add verkle access witness) evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address. diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 372c76f496924..9d43973429888 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -237,7 +237,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, if beaconRoot := block.BeaconRoot(); beaconRoot != nil { context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, block.Number(), block.Time()) } if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.BlockContext{}, statedb, release, nil diff --git a/eth/tracers/api.go b/eth/tracers/api.go index d99531d48fc9a..93f43a278a4e2 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -381,7 +381,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed if beaconRoot := next.BeaconRoot(); beaconRoot != nil { context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) } // Clean out any pending release functions of trace state. Note this // step must be done after constructing tracing state, because the @@ -533,7 +533,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) } for i, tx := range block.Transactions() { if err := ctx.Err(); err != nil { @@ -612,7 +612,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { vmenv := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) } for i, tx := range txs { // Generate the next state snapshot fast without tracing @@ -770,7 +770,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block } if beaconRoot := block.BeaconRoot(); beaconRoot != nil { vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) } for i, tx := range block.Transactions() { // Prepare the transaction for un-traced execution From d4f27686b81181c176db7c618d189e6390ef8369 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:43:57 +0200 Subject: [PATCH 12/30] review feedback from @holiman --- cmd/evm/internal/t8ntool/execution.go | 2 +- core/chain_makers.go | 2 +- core/state_processor.go | 8 ++++---- eth/state_accessor.go | 2 +- eth/tracers/api.go | 8 ++++---- miner/worker.go | 2 +- params/config.go | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 00c996c447b2e..3c09229e1c5c5 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -194,7 +194,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) - core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb, big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) + core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) } for i := 0; txIt.Next(); i++ { diff --git a/core/chain_makers.go b/core/chain_makers.go index 20614a0c0d212..58985347bb317 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -102,7 +102,7 @@ func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{}) ) - ProcessBeaconBlockRoot(root, vmenv, b.statedb, b.Number(), b.Timestamp()) + ProcessBeaconBlockRoot(root, vmenv, b.statedb) } // addTx adds a transaction to the generated block. If no coinbase has diff --git a/core/state_processor.go b/core/state_processor.go index 15b8703b6e895..4880419167383 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -78,7 +78,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg signer = types.MakeSigner(p.config, header.Number, header.Time) ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { - ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, header.Number, header.Time) + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { @@ -190,7 +190,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // contract. This method is exported to be used in tests. -func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB, num *big.Int, time uint64) { +func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) { // If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with // the new root msg := &Message{ @@ -204,11 +204,11 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat } txctx := NewEVMTxContext(msg) vmenv.Reset(txctx, statedb) - if vmenv.ChainConfig().Rules(num, true, time).IsEIP2929 { + if vmenv.ChainConfig().Rules(vmenv.Context.BlockNumber, true, vmenv.Context.Time).IsEIP2929 { statedb.AddAddressToAccessList(params.BeaconRootsAddress) } _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) - if vmenv.ChainConfig().Rules(num, true, time).IsEIP4762 { + if vmenv.ChainConfig().Rules(vmenv.Context.BlockNumber, true, vmenv.Context.Time).IsEIP4762 { statedb.Witness().Merge(txctx.Accesses) } statedb.Finalise(true) diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 9d43973429888..372c76f496924 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -237,7 +237,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, if beaconRoot := block.BeaconRoot(); beaconRoot != nil { context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, block.Number(), block.Time()) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.BlockContext{}, statedb, release, nil diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 93f43a278a4e2..d99531d48fc9a 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -381,7 +381,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed if beaconRoot := next.BeaconRoot(); beaconRoot != nil { context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } // Clean out any pending release functions of trace state. Note this // step must be done after constructing tracing state, because the @@ -533,7 +533,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } for i, tx := range block.Transactions() { if err := ctx.Err(); err != nil { @@ -612,7 +612,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { vmenv := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } for i, tx := range txs { // Generate the next state snapshot fast without tracing @@ -770,7 +770,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block } if beaconRoot := block.BeaconRoot(); beaconRoot != nil { vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) - core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, vmenv.Context.BlockNumber, vmenv.Context.Time) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } for i, tx := range block.Transactions() { // Prepare the transaction for un-traced execution diff --git a/miner/worker.go b/miner/worker.go index a9c53ae7c12bb..5dc3e2056b81b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -198,7 +198,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error) if header.ParentBeaconRoot != nil { context := core.NewEVMBlockContext(header, miner.chain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{}) - core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state, env.header.Number, env.header.Time) + core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) } return env, nil } diff --git a/params/config.go b/params/config.go index 207cbb9b6b489..176738b86826f 100644 --- a/params/config.go +++ b/params/config.go @@ -932,8 +932,8 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), - IsEIP2929: c.IsBerlin(num) && !c.IsPrague(num, timestamp), - IsEIP4762: c.IsPrague(num, timestamp), + IsEIP2929: c.IsBerlin(num) && !c.IsVerkle(num, timestamp), + IsEIP4762: c.IsVerkle(num, timestamp), IsLondon: c.IsLondon(num), IsMerge: isMerge, IsShanghai: isMerge && c.IsShanghai(num, timestamp), From c65d117a921b7dc070409297c3e63ab82a76b48e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:35:36 +0200 Subject: [PATCH 13/30] add verkle op handlers --- core/state_processor_test.go | 2 +- core/vm/eips.go | 131 ++++++++++++++++++++++++++++++++++- core/vm/instructions.go | 55 +-------------- 3 files changed, 133 insertions(+), 55 deletions(-) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 1c29ef39dc2c2..3beffa002b7ca 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -481,7 +481,7 @@ func TestProcessVerkle(t *testing.T) { txCost1 := params.TxGas txCost2 := params.TxGas contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */) - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(179444 /* execution costs */) + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */) blockGasUsagesExpected := []uint64{ txCost1*2 + txCost2, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, diff --git a/core/vm/eips.go b/core/vm/eips.go index ccf6e8f9b4d19..5037740b57eac 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -18,9 +18,12 @@ package vm import ( "fmt" + "math" "sort" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -321,6 +324,125 @@ func enable6780(jt *JumpTable) { } } +func opCreateEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var endowment = scope.Stack.peek() + + contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) + statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + return nil, ErrExecutionReverted + } + + return opCreate(pc, interpreter, scope) +} + +func opCreate2EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + endowment = scope.Stack.Back(0) + offset, size = scope.Stack.Back(1), scope.Stack.Back(2) + salt = scope.Stack.Back(3) + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + ) + + codeAndHash := &codeAndHash{code: input} + contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + return nil, ErrExecutionReverted + } + + return opCreate2(pc, interpreter, scope) +} + +func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + stack = scope.Stack + a = stack.pop() + memOffset = stack.pop() + codeOffset = stack.pop() + length = stack.pop() + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + addr := common.Address(a.Bytes20()) + code := interpreter.evm.StateDB.GetCode(addr) + contract := &Contract{ + Code: code, + self: AccountRef(addr), + } + paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) + statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) + + return nil, nil +} + +// opPush1EIP4762 is a specialized version of pushN +func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + integer = new(uint256.Int) + ) + *pc += 1 + if *pc < codeLen { + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + + if !scope.Contract.IsDeployment && *pc%31 == 0 { + // touch next chunk if PUSH1 is at the boundary. if so, *pc has + // advanced past this boundary. + contractAddr := scope.Contract.Address() + statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } + } else { + scope.Stack.push(integer.Clear()) + } + return nil, nil +} + +func makePushEIP4762(size uint64, pushByteSize int) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = len(scope.Contract.Code) + start = min(codeLen, int(*pc+1)) + end = min(codeLen, start+pushByteSize) + ) + scope.Stack.push(new(uint256.Int).SetBytes( + common.RightPadBytes( + scope.Contract.Code[start:end], + pushByteSize, + )), + ) + + if !scope.Contract.IsDeployment { + contractAddr := scope.Contract.Address() + statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } + + *pc += size + return nil, nil + } +} + func enable4762(jt *JumpTable) { jt[SSTORE].constantGas = 0 jt[SSTORE].dynamicGas = gasSStore4762 @@ -334,16 +456,23 @@ func enable4762(jt *JumpTable) { jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 jt[EXTCODECOPY].constantGas = 0 jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 + jt[EXTCODECOPY].execute = opExtCodeCopyEIP4762 jt[CODECOPY].dynamicGas = gasCodeCopyEip4762 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 jt[CREATE].constantGas = params.CreateNGasEip4762 + jt[CREATE].execute = opCreateEIP4762 jt[CREATE2].constantGas = params.CreateNGasEip4762 + jt[CREATE2].execute = opCreate2EIP4762 jt[CALL].constantGas = 0 jt[CALL].dynamicGas = gasCallEIP4762 jt[CALLCODE].constantGas = 0 - jt[CALLCODE].dynamicGas = gasCallCodeEIP4762 + jt[CALLCODE].dynamicGas = gasDelegateCallEIP4762 jt[STATICCALL].constantGas = 0 jt[STATICCALL].dynamicGas = gasStaticCallEIP4762 jt[DELEGATECALL].constantGas = 0 jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762 + jt[PUSH1].execute = opPush1EIP4762 + for i := 1; i < 32; i++ { + jt[PUSH1+OpCode(i)].execute = makePushEIP4762(uint64(i+1), i+1) + } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 0771c5f052438..95b48fbe6016d 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -382,23 +382,8 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = math.MaxUint64 } addr := common.Address(a.Bytes20()) - if interpreter.evm.chainRules.IsEIP4762 { - code := interpreter.evm.StateDB.GetCode(addr) - contract := &Contract{ - Code: code, - self: AccountRef(addr), - } - paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) - } else { - codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - } + codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil } @@ -604,14 +589,6 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b gas -= gas / 64 } - if interpreter.evm.chainRules.IsEIP4762 { - contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) - statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], value.Sign() != 0) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - return nil, ErrExecutionReverted - } - } - // reuse size int for stackvalue stackvalue := size @@ -652,14 +629,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = scope.Contract.Gas ) - if interpreter.evm.chainRules.IsEIP4762 { - codeAndHash := &codeAndHash{code: input} - contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - return nil, ErrExecutionReverted - } - } // Apply EIP150 gas -= gas / 64 @@ -913,17 +882,6 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by *pc += 1 if *pc < codeLen { scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) - - if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsEIP4762 && *pc%31 == 0 { - // touch next chunk if PUSH1 is at the boundary. if so, *pc has - // advanced past this boundary. - contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } } else { scope.Stack.push(integer.Clear()) } @@ -945,15 +903,6 @@ func makePush(size uint64, pushByteSize int) executionFunc { )), ) - if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsEIP4762 { - contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - scope.Contract.Gas = 0 - return nil, ErrOutOfGas - } - } - *pc += size return nil, nil } From 4030cdd81f90b30c85cb87f7bbe239907c166aa1 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:24:40 +0200 Subject: [PATCH 14/30] rename AccessWitness to AccessEvents --- consensus/beacon/consensus.go | 2 +- .../{access_witness.go => access_events.go} | 52 +++++++++---------- core/state/statedb.go | 22 ++++---- core/state_processor.go | 8 +-- core/state_transition.go | 6 +-- core/vm/eips.go | 10 ++-- core/vm/evm.go | 26 +++++----- core/vm/gas_table.go | 4 +- core/vm/interpreter.go | 2 +- core/vm/operations_verkle.go | 32 ++++++------ 10 files changed, 82 insertions(+), 82 deletions(-) rename core/state/{access_witness.go => access_events.go} (82%) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 4f518529f78ca..7d5007f241d1a 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -364,7 +364,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // Add the balance of each withdrawal to the witness, no gas will // be charged. if chain.Config().IsEIP4762(header.Number, header.Time) { - state.Witness().BalanceGas(w.Address[:], true) + state.AccessEvents().BalanceGas(w.Address[:], true) } } // No block reward which is issued by consensus layer instead. diff --git a/core/state/access_witness.go b/core/state/access_events.go similarity index 82% rename from core/state/access_witness.go rename to core/state/access_events.go index 88fbdf3a589bb..b845fba303b08 100644 --- a/core/state/access_witness.go +++ b/core/state/access_events.go @@ -37,27 +37,27 @@ const ( var zeroTreeIndex uint256.Int -// AccessWitness lists the locations of the state that are being accessed +// AccessEvents lists the locations of the state that are being accessed // during the production of a block. -type AccessWitness struct { +type AccessEvents struct { branches map[branchAccessKey]mode chunks map[chunkAccessKey]mode pointCache *utils.PointCache } -func NewAccessWitness(pointCache *utils.PointCache) *AccessWitness { - return &AccessWitness{ +func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents { + return &AccessEvents{ branches: make(map[branchAccessKey]mode), chunks: make(map[chunkAccessKey]mode), pointCache: pointCache, } } -// Merge is used to merge the witness that got generated during the execution -// of a tx, with the accumulation of witnesses that were generated during the -// execution of all the txs preceding this one in a given block. -func (aw *AccessWitness) Merge(other *AccessWitness) { +// Merge is used to merge the access events that were generated during the +// execution of a tx, with the accumulation of all access events that were +// generated during the execution of all txs preceding this one in a block. +func (aw *AccessEvents) Merge(other *AccessEvents) { for k := range other.branches { aw.branches[k] |= other.branches[k] } @@ -68,7 +68,7 @@ func (aw *AccessWitness) Merge(other *AccessWitness) { // Key returns, predictably, the list of keys that were touched during the // buildup of the access witness. -func (aw *AccessWitness) Keys() [][]byte { +func (aw *AccessEvents) Keys() [][]byte { // TODO: consider if parallelizing this is worth it, probably depending on len(aw.chunks). keys := make([][]byte, 0, len(aw.chunks)) for chunk := range aw.chunks { @@ -79,8 +79,8 @@ func (aw *AccessWitness) Keys() [][]byte { return keys } -func (aw *AccessWitness) Copy() *AccessWitness { - naw := &AccessWitness{ +func (aw *AccessEvents) Copy() *AccessEvents { + naw := &AccessEvents{ branches: make(map[branchAccessKey]mode), chunks: make(map[chunkAccessKey]mode), pointCache: aw.pointCache, @@ -91,7 +91,7 @@ func (aw *AccessWitness) Copy() *AccessWitness { // AddAccount returns the gas to be charged for each of the currently cold // member fields of an account. -func (aw *AccessWitness) AddAccount(addr []byte, isWrite bool) uint64 { +func (aw *AccessEvents) AddAccount(addr []byte, isWrite bool) uint64 { var gas uint64 for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) @@ -102,7 +102,7 @@ func (aw *AccessWitness) AddAccount(addr []byte, isWrite bool) uint64 { // MessageCallGas returns the gas to be charged for each of the currently // cold member fields of an account, that need to be touched when making a message // call to that account. -func (aw *AccessWitness) MessageCallGas(destination []byte) uint64 { +func (aw *AccessEvents) MessageCallGas(destination []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) @@ -111,7 +111,7 @@ func (aw *AccessWitness) MessageCallGas(destination []byte) uint64 { // ValueTransferGas returns the gas to be charged for each of the currently // cold balance member fields of the caller and the callee accounts. -func (aw *AccessWitness) ValueTransferGas(callerAddr, targetAddr []byte) uint64 { +func (aw *AccessEvents) ValueTransferGas(callerAddr, targetAddr []byte) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) @@ -120,7 +120,7 @@ func (aw *AccessWitness) ValueTransferGas(callerAddr, targetAddr []byte) uint64 // ContractCreateInitGas returns the access gas costs for the initialization of // a contract creation. -func (aw *AccessWitness) ContractCreateInitGas(addr []byte, createSendsValue bool) uint64 { +func (aw *AccessEvents) ContractCreateInitGas(addr []byte, createSendsValue bool) uint64 { var gas uint64 gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) @@ -132,7 +132,7 @@ func (aw *AccessWitness) ContractCreateInitGas(addr []byte, createSendsValue boo // AddTxOrigin adds the member fields of the sender account to the witness, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (aw *AccessWitness) AddTxOrigin(originAddr []byte) { +func (aw *AccessEvents) AddTxOrigin(originAddr []byte) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) } @@ -140,21 +140,21 @@ func (aw *AccessWitness) AddTxOrigin(originAddr []byte) { // AddTxDestination adds the member fields of the sender account to the witness, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (aw *AccessWitness) AddTxDestination(targetAddr []byte, sendsValue bool) { +func (aw *AccessEvents) AddTxDestination(targetAddr []byte, sendsValue bool) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.VersionLeafKey && sendsValue) } } // SlotGas returns the amount of gas to be charged for a cold storage access. -func (aw *AccessWitness) SlotGas(addr []byte, slot common.Hash, isWrite bool) uint64 { +func (aw *AccessEvents) SlotGas(addr []byte, slot common.Hash, isWrite bool) uint64 { treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } // touchAddressAndChargeGas adds any missing access event to the witness, and returns the cold // access cost to be charged, if need be. -func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { +func (aw *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite) var gas uint64 @@ -178,7 +178,7 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256 } // touchAddress adds any missing access event to the witness. -func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { +func (aw *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { branchKey := newBranchAccessKey(addr, treeIndex) chunkKey := newChunkAccessKey(branchKey, subIndex) @@ -238,7 +238,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { } // touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func (aw *AccessWitness) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { +func (aw *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { // note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, // we don't include the last leaf of code in the AccessWitness. The @@ -272,22 +272,22 @@ func (aw *AccessWitness) CodeChunksRangeGas(contractAddr []byte, startPC, size u return statelessGasCharged } -func (aw *AccessWitness) VersionGas(addr []byte, isWrite bool) uint64 { +func (aw *AccessEvents) VersionGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) } -func (aw *AccessWitness) BalanceGas(addr []byte, isWrite bool) uint64 { +func (aw *AccessEvents) BalanceGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) } -func (aw *AccessWitness) NonceGas(addr []byte, isWrite bool) uint64 { +func (aw *AccessEvents) NonceGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) } -func (aw *AccessWitness) CodeSizeGas(addr []byte, isWrite bool) uint64 { +func (aw *AccessEvents) CodeSizeGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) } -func (aw *AccessWitness) CodeHashGas(addr []byte, isWrite bool) uint64 { +func (aw *AccessEvents) CodeHashGas(addr []byte, isWrite bool) uint64 { return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) } diff --git a/core/state/statedb.go b/core/state/statedb.go index 42adf401c04a1..008de7994efee 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -140,8 +140,8 @@ type StateDB struct { // Transient storage transientStorage transientStorage - // Verkle witness - witness *AccessWitness + // State access events, used for Verkle tries/EIP4762 + accessEvents *AccessEvents // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -198,7 +198,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) hasher: crypto.NewKeccakState(), } if tr.IsVerkle() { - sdb.witness = sdb.NewAccessWitness() + sdb.accessEvents = sdb.NewAccessEvents() } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) @@ -206,19 +206,19 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } -func (s *StateDB) NewAccessWitness() *AccessWitness { - return NewAccessWitness(utils.NewPointCache(100)) +func (s *StateDB) NewAccessEvents() *AccessEvents { + return NewAccessEvents(utils.NewPointCache(100)) } -func (s *StateDB) Witness() *AccessWitness { - if s.witness == nil { - s.witness = s.NewAccessWitness() +func (s *StateDB) AccessEvents() *AccessEvents { + if s.accessEvents == nil { + s.accessEvents = s.NewAccessEvents() } - return s.witness + return s.accessEvents } -func (s *StateDB) SetWitness(aw *AccessWitness) { - s.witness = aw +func (s *StateDB) SetAccessEvents(ae *AccessEvents) { + s.accessEvents = ae } // SetLogger sets the logger for account update hooks. diff --git a/core/state_processor.go b/core/state_processor.go index 4880419167383..66de9595434b0 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -120,7 +120,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo } // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) - txContext.Accesses = statedb.NewAccessWitness() + txContext.AccessEvents = statedb.NewAccessEvents() evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env). @@ -159,8 +159,8 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - if statedb.Witness() != nil { - statedb.Witness().Merge(txContext.Accesses) + if statedb.AccessEvents() != nil { + statedb.AccessEvents().Merge(txContext.AccessEvents) } // Set the receipt logs and create the bloom filter. @@ -209,7 +209,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat } _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) if vmenv.ChainConfig().Rules(vmenv.Context.BlockNumber, true, vmenv.Context.Time).IsEIP4762 { - statedb.Witness().Merge(txctx.Accesses) + statedb.AccessEvents().Merge(txctx.AccessEvents) } statedb.Finalise(true) } diff --git a/core/state_transition.go b/core/state_transition.go index c923d1c7d7ba0..8a5e6329d1ac2 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -422,10 +422,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { targetAddr := msg.To originAddr := msg.From - st.evm.Accesses.AddTxOrigin(originAddr.Bytes()) + st.evm.AccessEvents.AddTxOrigin(originAddr.Bytes()) if msg.To != nil { - st.evm.Accesses.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) + st.evm.AccessEvents.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) @@ -488,7 +488,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.Accesses.BalanceGas(st.evm.Context.Coinbase[:], true) + st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase[:], true) } } diff --git a/core/vm/eips.go b/core/vm/eips.go index 5037740b57eac..69b736acd2b39 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -331,7 +331,7 @@ func opCreateEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContex var endowment = scope.Stack.peek() contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) - statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -352,7 +352,7 @@ func opCreate2EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte codeAndHash := &codeAndHash{code: input} contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - statelessGas := interpreter.evm.Accesses.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -379,7 +379,7 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -403,7 +403,7 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -431,7 +431,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment { contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.Accesses.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/evm.go b/core/vm/evm.go index 742ea8623607d..c00bf8ee531e2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -86,11 +86,11 @@ type BlockContext struct { // All fields can change between transactions. type TxContext struct { // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) - BlobHashes []common.Hash // Provides information for BLOBHASH - BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set - Accesses *state.AccessWitness // Capture all state accesses for this tx + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) + BlobHashes []common.Hash // Provides information for BLOBHASH + BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set + AccessEvents *state.AccessEvents // Capture all state accesses for this tx } // EVM is the Ethereum Virtual Machine base object and provides @@ -151,8 +151,8 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } - if txCtx.Accesses == nil && chainConfig.IsPrague(blockCtx.BlockNumber, blockCtx.Time) { - evm.Accesses = evm.StateDB.(*state.StateDB).NewAccessWitness() + if txCtx.AccessEvents == nil && chainConfig.IsPrague(blockCtx.BlockNumber, blockCtx.Time) { + evm.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() } evm.interpreter = NewEVMInterpreter(evm) return evm @@ -161,8 +161,8 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { - if txCtx.Accesses == nil && evm.chainRules.IsPrague { - txCtx.Accesses = evm.StateDB.(*state.StateDB).NewAccessWitness() + if txCtx.AccessEvents == nil && evm.chainRules.IsPrague { + txCtx.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() } evm.TxContext = txCtx evm.StateDB = statedb @@ -211,7 +211,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 { // add proof of absence to witness - wgas := evm.Accesses.AddAccount(addr.Bytes(), false) + wgas := evm.AccessEvents.AddAccount(addr.Bytes(), false) if gas < wgas { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas @@ -504,7 +504,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.Accesses.ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { err = ErrOutOfGas } } @@ -535,11 +535,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } else { // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.Accesses.AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { + if !contract.UseGas(evm.AccessEvents.AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { err = ErrCodeStoreOutOfGas } - if err == nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { + if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { err = ErrCodeStoreOutOfGas } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index dc9a7f34ee2d9..b3cedfd09e2af 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -404,7 +404,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } if evm.chainRules.IsEIP4762 { if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.Accesses.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } @@ -440,7 +440,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory address := common.Address(stack.Back(1).Bytes20()) transfersValue := !stack.Back(2).IsZero() if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.Accesses.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 105ef860fc9a2..7761790f43b91 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -227,7 +227,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - contract.Gas -= in.evm.TxContext.Accesses.CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) + contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) } // Get the operation from the jump table and validate the stack to ensure there are diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 3ba6411bc0223..2ded65950b5bd 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -23,7 +23,7 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.Accesses.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) + gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -31,7 +31,7 @@ func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.Accesses.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) + gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -40,7 +40,7 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - gas := evm.Accesses.BalanceGas(address[:], false) + gas := evm.AccessEvents.BalanceGas(address[:], false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -52,8 +52,8 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - wgas := evm.Accesses.VersionGas(address[:], false) - wgas += evm.Accesses.CodeSizeGas(address[:], false) + wgas := evm.AccessEvents.VersionGas(address[:], false) + wgas += evm.AccessEvents.CodeSizeGas(address[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -65,7 +65,7 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - codehashgas := evm.Accesses.CodeHashGas(address[:], false) + codehashgas := evm.AccessEvents.CodeHashGas(address[:], false) if codehashgas == 0 { codehashgas = params.WarmStorageReadCostEIP2929 } @@ -81,7 +81,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { return gas, nil } - wgas := evm.Accesses.MessageCallGas(contract.Address().Bytes()) + wgas := evm.AccessEvents.MessageCallGas(contract.Address().Bytes()) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -102,17 +102,17 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } contractAddr := contract.Address() - statelessGas := evm.Accesses.VersionGas(contractAddr[:], false) - statelessGas += evm.Accesses.CodeSizeGas(contractAddr[:], false) - statelessGas += evm.Accesses.BalanceGas(contractAddr[:], false) + statelessGas := evm.AccessEvents.VersionGas(contractAddr[:], false) + statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr[:], false) + statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], false) if contractAddr != beneficiaryAddr { - statelessGas += evm.Accesses.BalanceGas(beneficiaryAddr[:], false) + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], false) } // Charge write costs if it transfers value if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { - statelessGas += evm.Accesses.BalanceGas(contractAddr[:], true) + statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], true) if contractAddr != beneficiaryAddr { - statelessGas += evm.Accesses.BalanceGas(beneficiaryAddr[:], true) + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], true) } } return statelessGas, nil @@ -133,7 +133,7 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) if !contract.IsDeployment { - gas += evm.Accesses.CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) } return gas, nil } @@ -145,8 +145,8 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo return 0, err } addr := common.Address(stack.peek().Bytes20()) - wgas := evm.Accesses.VersionGas(addr[:], false) - wgas += evm.Accesses.CodeSizeGas(addr[:], false) + wgas := evm.AccessEvents.VersionGas(addr[:], false) + wgas += evm.AccessEvents.CodeSizeGas(addr[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } From 3a5e0410f83cc11966fa4c414c4471468641ab58 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:33:52 +0200 Subject: [PATCH 15/30] improve comment for eip4762 PUSH1 handler --- core/vm/eips.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 69b736acd2b39..f9208e033108c 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -389,7 +389,8 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC return nil, nil } -// opPush1EIP4762 is a specialized version of pushN +// opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which +// need not worry about the adjusted bound logic. func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( codeLen = uint64(len(scope.Contract.Code)) From c04b64c8bef8f6997fe4e7f63d7ec339e4312483 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:07:49 +0200 Subject: [PATCH 16/30] remove unnecessary creation field --- core/vm/evm.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index c00bf8ee531e2..47c877c0fe22a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -207,7 +207,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas snapshot := evm.StateDB.Snapshot() p, isPrecompile := evm.precompile(addr) - var creation bool if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 { // add proof of absence to witness @@ -224,7 +223,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas return nil, gas, nil } evm.StateDB.CreateAccount(addr) - creation = true } evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) @@ -242,7 +240,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // 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.IsDeployment = creation ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } From 0620c5cc795c9d0d416133e93af565d0d40e2456 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:42:02 +0200 Subject: [PATCH 17/30] address last review comments --- core/vm/contracts.go | 2 + core/vm/eips.go | 153 ++++++++++++++++++++++++++++++++++--------- core/vm/evm.go | 4 +- 3 files changed, 128 insertions(+), 31 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 5f7de8007b519..efa8d5221258c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -137,6 +137,8 @@ var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{ var PrecompiledContractsBLS = PrecompiledContractsPrague +var PrecompiledContractsVerkle = PrecompiledContractsPrague + var ( PrecompiledAddressesPrague []common.Address PrecompiledAddressesCancun []common.Address diff --git a/core/vm/eips.go b/core/vm/eips.go index f9208e033108c..2af1b17672fa2 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -390,7 +390,8 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC } // opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which -// need not worry about the adjusted bound logic. +// need not worry about the adjusted bound logic when adding the PUSHDATA to +// the list of access events. func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( codeLen = uint64(len(scope.Contract.Code)) @@ -445,35 +446,127 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { } func enable4762(jt *JumpTable) { - jt[SSTORE].constantGas = 0 - jt[SSTORE].dynamicGas = gasSStore4762 - jt[SLOAD].constantGas = 0 - jt[SLOAD].dynamicGas = gasSLoad4762 - jt[BALANCE].dynamicGas = gasBalance4762 - jt[BALANCE].constantGas = 0 - jt[EXTCODESIZE].constantGas = 0 - jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762 - jt[EXTCODEHASH].constantGas = 0 - jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762 - jt[EXTCODECOPY].constantGas = 0 - jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762 - jt[EXTCODECOPY].execute = opExtCodeCopyEIP4762 - jt[CODECOPY].dynamicGas = gasCodeCopyEip4762 - jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762 - jt[CREATE].constantGas = params.CreateNGasEip4762 - jt[CREATE].execute = opCreateEIP4762 - jt[CREATE2].constantGas = params.CreateNGasEip4762 - jt[CREATE2].execute = opCreate2EIP4762 - jt[CALL].constantGas = 0 - jt[CALL].dynamicGas = gasCallEIP4762 - jt[CALLCODE].constantGas = 0 - jt[CALLCODE].dynamicGas = gasDelegateCallEIP4762 - jt[STATICCALL].constantGas = 0 - jt[STATICCALL].dynamicGas = gasStaticCallEIP4762 - jt[DELEGATECALL].constantGas = 0 - jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762 - jt[PUSH1].execute = opPush1EIP4762 + jt[SSTORE] = &operation{ + dynamicGas: gasSStore4762, + execute: opSstore, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } + jt[SLOAD] = &operation{ + dynamicGas: gasSLoad4762, + execute: opSload, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[BALANCE] = &operation{ + execute: opBalance, + dynamicGas: gasBalance4762, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[EXTCODESIZE] = &operation{ + execute: opExtCodeSize, + dynamicGas: gasExtCodeSize4762, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[EXTCODEHASH] = &operation{ + execute: opExtCodeHash, + dynamicGas: gasExtCodeHash4762, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[EXTCODECOPY] = &operation{ + execute: opExtCodeCopyEIP4762, + dynamicGas: gasExtCodeCopyEIP4762, + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + memorySize: memoryExtCodeCopy, + } + + jt[CODECOPY] = &operation{ + execute: opCodeCopy, + constantGas: GasFastestStep, + dynamicGas: gasCodeCopyEip4762, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryCodeCopy, + } + + jt[SELFDESTRUCT] = &operation{ + execute: opSelfdestruct6780, + dynamicGas: gasSelfdestructEIP4762, + constantGas: params.SelfdestructGasEIP150, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } + + jt[CREATE] = &operation{ + execute: opCreateEIP4762, + constantGas: params.CreateNGasEip4762, + dynamicGas: gasCreateEip3860, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryCreate, + } + + jt[CREATE2] = &operation{ + execute: opCreate2EIP4762, + constantGas: params.CreateNGasEip4762, + dynamicGas: gasCreate2Eip3860, + minStack: minStack(4, 1), + maxStack: maxStack(4, 1), + memorySize: memoryCreate2, + } + + jt[CALL] = &operation{ + execute: opCall, + dynamicGas: gasCallEIP4762, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + } + + jt[CALLCODE] = &operation{ + execute: opCallCode, + dynamicGas: gasCallCodeEIP4762, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + } + + jt[STATICCALL] = &operation{ + execute: opStaticCall, + dynamicGas: gasStaticCallEIP4762, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryStaticCall, + } + + jt[DELEGATECALL] = &operation{ + execute: opDelegateCall, + dynamicGas: gasDelegateCallEIP4762, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryDelegateCall, + } + + jt[PUSH1] = &operation{ + execute: opPush1EIP4762, + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } for i := 1; i < 32; i++ { - jt[PUSH1+OpCode(i)].execute = makePushEIP4762(uint64(i+1), i+1) + jt[PUSH1+OpCode(i)] = &operation{ + execute: makePushEIP4762(uint64(i+1), i+1), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } } } diff --git a/core/vm/evm.go b/core/vm/evm.go index 47c877c0fe22a..9393c732b24ca 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -43,9 +43,11 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { + case evm.chainRules.IsVerkle: + precompiles = PrecompiledContractsVerkle case evm.chainRules.IsPrague: precompiles = PrecompiledContractsPrague - case evm.chainRules.IsCancun, evm.chainRules.IsVerkle: + case evm.chainRules.IsCancun: precompiles = PrecompiledContractsCancun case evm.chainRules.IsBerlin: precompiles = PrecompiledContractsBerlin From 026ebc93b4df1db9c95997c3d4f9bf91e8e99085 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:18:56 +0200 Subject: [PATCH 18/30] Apply suggestions from code review Co-authored-by: Martin HS --- core/state/access_events.go | 2 +- core/state_transition.go | 20 ++------------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/core/state/access_events.go b/core/state/access_events.go index b845fba303b08..bc6064e96d56b 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -237,7 +237,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { return lk } -// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs +// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs func (aw *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { // note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, diff --git a/core/state_transition.go b/core/state_transition.go index 8a5e6329d1ac2..fba41762ad202 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -359,19 +359,6 @@ func (st *StateTransition) preCheck() error { return st.buyGas() } -// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool -// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false -// otherwise, do the subtraction setting the result in gasPool and return true -func tryConsumeGas(gasPool *uint64, gas uint64) bool { - if *gasPool < gas { - *gasPool = 0 - return false - } - - *gasPool -= gas - return true -} - // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // @@ -419,12 +406,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.gasRemaining -= gas if rules.IsEIP4762 { - targetAddr := msg.To - originAddr := msg.From - - st.evm.AccessEvents.AddTxOrigin(originAddr.Bytes()) + st.evm.AccessEvents.AddTxOrigin(msg.From.Bytes()) - if msg.To != nil { + if targetAddr := msg.To; targetAddr != nil { st.evm.AccessEvents.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness From aa96a9c7d620834714c95e87448060930a520672 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Thu, 2 May 2024 15:25:42 +0800 Subject: [PATCH 19/30] Several fixes (#425) * params: fix chainconfig rules * core, core/state: init accessEvent for each transactin * core/state: reuse point cache between blocks --- consensus/beacon/consensus.go | 6 ------ core/state/access_events.go | 6 +----- core/state/database.go | 16 +++++++++++++++- core/state/statedb.go | 28 +++++++++++----------------- core/state_processor.go | 9 --------- core/state_transition.go | 6 +++--- core/vm/eips.go | 10 +++++----- core/vm/evm.go | 24 ++++++++---------------- core/vm/gas_table.go | 4 ++-- core/vm/interface.go | 4 ++++ core/vm/interpreter.go | 2 +- core/vm/operations_verkle.go | 32 ++++++++++++++++---------------- params/config.go | 7 ++++--- 13 files changed, 70 insertions(+), 84 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 7d5007f241d1a..4e3fbeb09a7c9 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -360,12 +360,6 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. amount := new(uint256.Int).SetUint64(w.Amount) amount = amount.Mul(amount, uint256.NewInt(params.GWei)) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) - - // Add the balance of each withdrawal to the witness, no gas will - // be charged. - if chain.Config().IsEIP4762(header.Number, header.Time) { - state.AccessEvents().BalanceGas(w.Address[:], true) - } } // No block reward which is issued by consensus layer instead. } diff --git a/core/state/access_events.go b/core/state/access_events.go index bc6064e96d56b..3ad5ef28d2a16 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -66,7 +66,7 @@ func (aw *AccessEvents) Merge(other *AccessEvents) { } } -// Key returns, predictably, the list of keys that were touched during the +// Keys returns, predictably, the list of keys that were touched during the // buildup of the access witness. func (aw *AccessEvents) Keys() [][]byte { // TODO: consider if parallelizing this is worth it, probably depending on len(aw.chunks). @@ -173,7 +173,6 @@ func (aw *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256. if selectorFill { gas += params.WitnessChunkFillCost } - return gas } @@ -206,10 +205,8 @@ func (aw *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subInde chunkWrite = true aw.chunks[chunkKey] |= AccessWitnessWriteFlag } - // TODO: charge chunk filling costs if the leaf was previously empty in the state } - return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill } @@ -268,7 +265,6 @@ func (aw *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size ui panic("overflow when adding gas") } } - return statelessGasCharged } diff --git a/core/state/database.go b/core/state/database.go index 8ae2c525461a1..15479de985b52 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -38,6 +38,9 @@ const ( // Cache size granted for caching clean code. codeCacheSize = 64 * 1024 * 1024 + + // Number of address->curve point associations to keep. + pointCacheSize = 4096 ) // Database wraps access to tries and contract code. @@ -62,6 +65,9 @@ type Database interface { // TrieDB returns the underlying trie database for managing trie nodes. TrieDB() *triedb.Database + + // PointCache returns the cache of evaluated curve points. + PointCache() *utils.PointCache } // Trie is a Ethereum Merkle Patricia trie. @@ -153,6 +159,7 @@ func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database { codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: triedb.NewDatabase(db, config), + pointCache: utils.NewPointCache(pointCacheSize), } } @@ -163,6 +170,7 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: triedb, + pointCache: utils.NewPointCache(pointCacheSize), } } @@ -171,12 +179,13 @@ type cachingDB struct { codeSizeCache *lru.Cache[common.Hash, int] codeCache *lru.SizeConstrainedCache[common.Hash, []byte] triedb *triedb.Database + pointCache *utils.PointCache } // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { - return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(100)) + return trie.NewVerkleTrie(root, db.triedb, db.pointCache) } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { @@ -262,3 +271,8 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore { func (db *cachingDB) TrieDB() *triedb.Database { return db.triedb } + +// PointCache returns the cache of evaluated curve points. +func (db *cachingDB) PointCache() *utils.PointCache { + return db.pointCache +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 008de7994efee..6cdef8f2a64f8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -36,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/triestate" - "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" ) @@ -140,8 +139,8 @@ type StateDB struct { // Transient storage transientStorage transientStorage - // State access events, used for Verkle tries/EIP4762 - accessEvents *AccessEvents + // State access events, used for EIP4762 + accessEvents *AccessEvents // reset for each transaction // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -197,30 +196,16 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), } - if tr.IsVerkle() { - sdb.accessEvents = sdb.NewAccessEvents() - } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) } return sdb, nil } -func (s *StateDB) NewAccessEvents() *AccessEvents { - return NewAccessEvents(utils.NewPointCache(100)) -} - func (s *StateDB) AccessEvents() *AccessEvents { - if s.accessEvents == nil { - s.accessEvents = s.NewAccessEvents() - } return s.accessEvents } -func (s *StateDB) SetAccessEvents(ae *AccessEvents) { - s.accessEvents = ae -} - // SetLogger sets the logger for account update hooks. func (s *StateDB) SetLogger(l *tracing.Hooks) { s.logger = l @@ -767,6 +752,9 @@ func (s *StateDB) Copy() *StateDB { if s.prefetcher != nil { state.prefetcher = s.prefetcher.copy() } + if s.accessEvents != nil { + state.accessEvents = s.accessEvents.Copy() + } return state } @@ -1297,6 +1285,9 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - Add coinbase to access list (EIP-3651) // - Reset transient storage (EIP-1153) func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + if rules.IsEIP2929 && rules.IsEIP4762 { + panic("eip2929 and eip4762 are both activated") + } if rules.IsEIP2929 { // Clear out any leftover from previous executions al := newAccessList() @@ -1320,6 +1311,9 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(coinbase) } } + if rules.IsEIP4762 { + s.accessEvents = NewAccessEvents(s.db.PointCache()) + } // Reset transient storage at the beginning of transaction execution s.transientStorage = newTransientStorage() } diff --git a/core/state_processor.go b/core/state_processor.go index 66de9595434b0..b891db249bdf1 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -120,7 +120,6 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo } // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) - txContext.AccessEvents = statedb.NewAccessEvents() evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env). @@ -158,11 +157,6 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo if msg.To == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - - if statedb.AccessEvents() != nil { - statedb.AccessEvents().Merge(txContext.AccessEvents) - } - // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) @@ -208,8 +202,5 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat statedb.AddAddressToAccessList(params.BeaconRootsAddress) } _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) - if vmenv.ChainConfig().Rules(vmenv.Context.BlockNumber, true, vmenv.Context.Time).IsEIP4762 { - statedb.AccessEvents().Merge(txctx.AccessEvents) - } statedb.Finalise(true) } diff --git a/core/state_transition.go b/core/state_transition.go index fba41762ad202..5fb5639212e18 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -406,10 +406,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.gasRemaining -= gas if rules.IsEIP4762 { - st.evm.AccessEvents.AddTxOrigin(msg.From.Bytes()) + st.evm.StateDB.AccessEvents().AddTxOrigin(msg.From.Bytes()) if targetAddr := msg.To; targetAddr != nil { - st.evm.AccessEvents.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) + st.evm.StateDB.AccessEvents().AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) @@ -472,7 +472,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase[:], true) + st.evm.StateDB.AccessEvents().BalanceGas(st.evm.Context.Coinbase[:], true) } } diff --git a/core/vm/eips.go b/core/vm/eips.go index 2af1b17672fa2..947539f58fa1d 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -331,7 +331,7 @@ func opCreateEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContex var endowment = scope.Stack.peek() contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) - statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + statelessGas := interpreter.evm.StateDB.AccessEvents().ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -352,7 +352,7 @@ func opCreate2EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte codeAndHash := &codeAndHash{code: input} contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + statelessGas := interpreter.evm.StateDB.AccessEvents().ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -379,7 +379,7 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + statelessGas := interpreter.evm.StateDB.AccessEvents().CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -405,7 +405,7 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.StateDB.AccessEvents().CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -433,7 +433,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment { contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.StateDB.AccessEvents().CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/evm.go b/core/vm/evm.go index 9393c732b24ca..541c49ce45311 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -22,7 +22,6 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -88,11 +87,10 @@ type BlockContext struct { // All fields can change between transactions. type TxContext struct { // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) - BlobHashes []common.Hash // Provides information for BLOBHASH - BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set - AccessEvents *state.AccessEvents // Capture all state accesses for this tx + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) + BlobHashes []common.Hash // Provides information for BLOBHASH + BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set } // EVM is the Ethereum Virtual Machine base object and provides @@ -153,9 +151,6 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } - if txCtx.AccessEvents == nil && chainConfig.IsPrague(blockCtx.BlockNumber, blockCtx.Time) { - evm.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() - } evm.interpreter = NewEVMInterpreter(evm) return evm } @@ -163,9 +158,6 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { - if txCtx.AccessEvents == nil && evm.chainRules.IsPrague { - txCtx.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() - } evm.TxContext = txCtx evm.StateDB = statedb } @@ -212,7 +204,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 { // add proof of absence to witness - wgas := evm.AccessEvents.AddAccount(addr.Bytes(), false) + wgas := evm.StateDB.AccessEvents().AddAccount(addr.Bytes(), false) if gas < wgas { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas @@ -503,7 +495,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + if !contract.UseGas(evm.StateDB.AccessEvents().ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { err = ErrOutOfGas } } @@ -534,11 +526,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } else { // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.AccessEvents.AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { + if !contract.UseGas(evm.StateDB.AccessEvents().AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { err = ErrCodeStoreOutOfGas } - if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { + if err == nil && len(ret) > 0 && !contract.UseGas(evm.StateDB.AccessEvents().CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { err = ErrCodeStoreOutOfGas } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index b3cedfd09e2af..8f74287718975 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -404,7 +404,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } if evm.chainRules.IsEIP4762 { if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.StateDB.AccessEvents().ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } @@ -440,7 +440,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory address := common.Address(stack.Back(1).Bytes20()) transfersValue := !stack.Back(2).IsZero() if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.StateDB.AccessEvents().ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } diff --git a/core/vm/interface.go b/core/vm/interface.go index 774360a08ef9c..a54375572b5a3 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -20,6 +20,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -75,6 +76,9 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) + + AccessEvents() *state.AccessEvents + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) RevertToSnapshot(int) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 7761790f43b91..196eb840d977d 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -227,7 +227,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) + contract.Gas -= in.evm.StateDB.AccessEvents().CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) } // Get the operation from the jump table and validate the stack to ensure there are diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 2ded65950b5bd..5167e744e73b3 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -23,7 +23,7 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) + gas := evm.StateDB.AccessEvents().SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -31,7 +31,7 @@ func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) + gas := evm.StateDB.AccessEvents().SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -40,7 +40,7 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - gas := evm.AccessEvents.BalanceGas(address[:], false) + gas := evm.StateDB.AccessEvents().BalanceGas(address[:], false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -52,8 +52,8 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - wgas := evm.AccessEvents.VersionGas(address[:], false) - wgas += evm.AccessEvents.CodeSizeGas(address[:], false) + wgas := evm.StateDB.AccessEvents().VersionGas(address[:], false) + wgas += evm.StateDB.AccessEvents().CodeSizeGas(address[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -65,7 +65,7 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - codehashgas := evm.AccessEvents.CodeHashGas(address[:], false) + codehashgas := evm.StateDB.AccessEvents().CodeHashGas(address[:], false) if codehashgas == 0 { codehashgas = params.WarmStorageReadCostEIP2929 } @@ -81,7 +81,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { return gas, nil } - wgas := evm.AccessEvents.MessageCallGas(contract.Address().Bytes()) + wgas := evm.StateDB.AccessEvents().MessageCallGas(contract.Address().Bytes()) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -102,17 +102,17 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } contractAddr := contract.Address() - statelessGas := evm.AccessEvents.VersionGas(contractAddr[:], false) - statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr[:], false) - statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], false) + statelessGas := evm.StateDB.AccessEvents().VersionGas(contractAddr[:], false) + statelessGas += evm.StateDB.AccessEvents().CodeSizeGas(contractAddr[:], false) + statelessGas += evm.StateDB.AccessEvents().BalanceGas(contractAddr[:], false) if contractAddr != beneficiaryAddr { - statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], false) + statelessGas += evm.StateDB.AccessEvents().BalanceGas(beneficiaryAddr[:], false) } // Charge write costs if it transfers value if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { - statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], true) + statelessGas += evm.StateDB.AccessEvents().BalanceGas(contractAddr[:], true) if contractAddr != beneficiaryAddr { - statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], true) + statelessGas += evm.StateDB.AccessEvents().BalanceGas(beneficiaryAddr[:], true) } } return statelessGas, nil @@ -133,7 +133,7 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) if !contract.IsDeployment { - gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + gas += evm.StateDB.AccessEvents().CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) } return gas, nil } @@ -145,8 +145,8 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo return 0, err } addr := common.Address(stack.peek().Bytes20()) - wgas := evm.AccessEvents.VersionGas(addr[:], false) - wgas += evm.AccessEvents.CodeSizeGas(addr[:], false) + wgas := evm.StateDB.AccessEvents().VersionGas(addr[:], false) + wgas += evm.StateDB.AccessEvents().CodeSizeGas(addr[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } diff --git a/params/config.go b/params/config.go index 176738b86826f..5fedfd3519b46 100644 --- a/params/config.go +++ b/params/config.go @@ -921,6 +921,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules } // disallow setting Merge out of order isMerge = isMerge && c.IsLondon(num) + isVerkle := isMerge && c.IsVerkle(num, timestamp) return Rules{ ChainID: new(big.Int).Set(chainID), IsHomestead: c.IsHomestead(num), @@ -932,13 +933,13 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), - IsEIP2929: c.IsBerlin(num) && !c.IsVerkle(num, timestamp), - IsEIP4762: c.IsVerkle(num, timestamp), + IsEIP2929: c.IsBerlin(num) && !isVerkle, IsLondon: c.IsLondon(num), IsMerge: isMerge, IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), - IsVerkle: isMerge && c.IsVerkle(num, timestamp), + IsVerkle: isVerkle, + IsEIP4762: isVerkle, } } From d5699fbb2ebe45feac67713878dc550c36f72307 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 2 May 2024 16:36:30 +0200 Subject: [PATCH 20/30] Revert "Several fixes (#425)" This reverts commit aa96a9c7d620834714c95e87448060930a520672. --- consensus/beacon/consensus.go | 6 ++++++ core/state/access_events.go | 6 +++++- core/state/database.go | 16 +--------------- core/state/statedb.go | 28 +++++++++++++++++----------- core/state_processor.go | 9 +++++++++ core/state_transition.go | 6 +++--- core/vm/eips.go | 10 +++++----- core/vm/evm.go | 24 ++++++++++++++++-------- core/vm/gas_table.go | 4 ++-- core/vm/interface.go | 4 ---- core/vm/interpreter.go | 2 +- core/vm/operations_verkle.go | 32 ++++++++++++++++---------------- params/config.go | 7 +++---- 13 files changed, 84 insertions(+), 70 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 4e3fbeb09a7c9..7d5007f241d1a 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -360,6 +360,12 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. amount := new(uint256.Int).SetUint64(w.Amount) amount = amount.Mul(amount, uint256.NewInt(params.GWei)) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + + // Add the balance of each withdrawal to the witness, no gas will + // be charged. + if chain.Config().IsEIP4762(header.Number, header.Time) { + state.AccessEvents().BalanceGas(w.Address[:], true) + } } // No block reward which is issued by consensus layer instead. } diff --git a/core/state/access_events.go b/core/state/access_events.go index 3ad5ef28d2a16..bc6064e96d56b 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -66,7 +66,7 @@ func (aw *AccessEvents) Merge(other *AccessEvents) { } } -// Keys returns, predictably, the list of keys that were touched during the +// Key returns, predictably, the list of keys that were touched during the // buildup of the access witness. func (aw *AccessEvents) Keys() [][]byte { // TODO: consider if parallelizing this is worth it, probably depending on len(aw.chunks). @@ -173,6 +173,7 @@ func (aw *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256. if selectorFill { gas += params.WitnessChunkFillCost } + return gas } @@ -205,8 +206,10 @@ func (aw *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subInde chunkWrite = true aw.chunks[chunkKey] |= AccessWitnessWriteFlag } + // TODO: charge chunk filling costs if the leaf was previously empty in the state } + return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill } @@ -265,6 +268,7 @@ func (aw *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size ui panic("overflow when adding gas") } } + return statelessGasCharged } diff --git a/core/state/database.go b/core/state/database.go index 15479de985b52..8ae2c525461a1 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -38,9 +38,6 @@ const ( // Cache size granted for caching clean code. codeCacheSize = 64 * 1024 * 1024 - - // Number of address->curve point associations to keep. - pointCacheSize = 4096 ) // Database wraps access to tries and contract code. @@ -65,9 +62,6 @@ type Database interface { // TrieDB returns the underlying trie database for managing trie nodes. TrieDB() *triedb.Database - - // PointCache returns the cache of evaluated curve points. - PointCache() *utils.PointCache } // Trie is a Ethereum Merkle Patricia trie. @@ -159,7 +153,6 @@ func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database { codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: triedb.NewDatabase(db, config), - pointCache: utils.NewPointCache(pointCacheSize), } } @@ -170,7 +163,6 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: triedb, - pointCache: utils.NewPointCache(pointCacheSize), } } @@ -179,13 +171,12 @@ type cachingDB struct { codeSizeCache *lru.Cache[common.Hash, int] codeCache *lru.SizeConstrainedCache[common.Hash, []byte] triedb *triedb.Database - pointCache *utils.PointCache } // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { - return trie.NewVerkleTrie(root, db.triedb, db.pointCache) + return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(100)) } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { @@ -271,8 +262,3 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore { func (db *cachingDB) TrieDB() *triedb.Database { return db.triedb } - -// PointCache returns the cache of evaluated curve points. -func (db *cachingDB) PointCache() *utils.PointCache { - return db.pointCache -} diff --git a/core/state/statedb.go b/core/state/statedb.go index 6cdef8f2a64f8..008de7994efee 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/triestate" + "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" ) @@ -139,8 +140,8 @@ type StateDB struct { // Transient storage transientStorage transientStorage - // State access events, used for EIP4762 - accessEvents *AccessEvents // reset for each transaction + // State access events, used for Verkle tries/EIP4762 + accessEvents *AccessEvents // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -196,16 +197,30 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), } + if tr.IsVerkle() { + sdb.accessEvents = sdb.NewAccessEvents() + } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) } return sdb, nil } +func (s *StateDB) NewAccessEvents() *AccessEvents { + return NewAccessEvents(utils.NewPointCache(100)) +} + func (s *StateDB) AccessEvents() *AccessEvents { + if s.accessEvents == nil { + s.accessEvents = s.NewAccessEvents() + } return s.accessEvents } +func (s *StateDB) SetAccessEvents(ae *AccessEvents) { + s.accessEvents = ae +} + // SetLogger sets the logger for account update hooks. func (s *StateDB) SetLogger(l *tracing.Hooks) { s.logger = l @@ -752,9 +767,6 @@ func (s *StateDB) Copy() *StateDB { if s.prefetcher != nil { state.prefetcher = s.prefetcher.copy() } - if s.accessEvents != nil { - state.accessEvents = s.accessEvents.Copy() - } return state } @@ -1285,9 +1297,6 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - Add coinbase to access list (EIP-3651) // - Reset transient storage (EIP-1153) func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { - if rules.IsEIP2929 && rules.IsEIP4762 { - panic("eip2929 and eip4762 are both activated") - } if rules.IsEIP2929 { // Clear out any leftover from previous executions al := newAccessList() @@ -1311,9 +1320,6 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d al.AddAddress(coinbase) } } - if rules.IsEIP4762 { - s.accessEvents = NewAccessEvents(s.db.PointCache()) - } // Reset transient storage at the beginning of transaction execution s.transientStorage = newTransientStorage() } diff --git a/core/state_processor.go b/core/state_processor.go index b891db249bdf1..66de9595434b0 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -120,6 +120,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo } // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) + txContext.AccessEvents = statedb.NewAccessEvents() evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env). @@ -157,6 +158,11 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo if msg.To == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } + + if statedb.AccessEvents() != nil { + statedb.AccessEvents().Merge(txContext.AccessEvents) + } + // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) @@ -202,5 +208,8 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat statedb.AddAddressToAccessList(params.BeaconRootsAddress) } _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) + if vmenv.ChainConfig().Rules(vmenv.Context.BlockNumber, true, vmenv.Context.Time).IsEIP4762 { + statedb.AccessEvents().Merge(txctx.AccessEvents) + } statedb.Finalise(true) } diff --git a/core/state_transition.go b/core/state_transition.go index 5fb5639212e18..fba41762ad202 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -406,10 +406,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.gasRemaining -= gas if rules.IsEIP4762 { - st.evm.StateDB.AccessEvents().AddTxOrigin(msg.From.Bytes()) + st.evm.AccessEvents.AddTxOrigin(msg.From.Bytes()) if targetAddr := msg.To; targetAddr != nil { - st.evm.StateDB.AccessEvents().AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) + st.evm.AccessEvents.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) // ensure the code size ends up in the access witness st.evm.StateDB.GetCodeSize(*targetAddr) @@ -472,7 +472,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.StateDB.AccessEvents().BalanceGas(st.evm.Context.Coinbase[:], true) + st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase[:], true) } } diff --git a/core/vm/eips.go b/core/vm/eips.go index 947539f58fa1d..2af1b17672fa2 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -331,7 +331,7 @@ func opCreateEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContex var endowment = scope.Stack.peek() contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) - statelessGas := interpreter.evm.StateDB.AccessEvents().ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -352,7 +352,7 @@ func opCreate2EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte codeAndHash := &codeAndHash{code: input} contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - statelessGas := interpreter.evm.StateDB.AccessEvents().ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) + statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { return nil, ErrExecutionReverted } @@ -379,7 +379,7 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.StateDB.AccessEvents().CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -405,7 +405,7 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.StateDB.AccessEvents().CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -433,7 +433,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment { contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.StateDB.AccessEvents().CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/evm.go b/core/vm/evm.go index 541c49ce45311..9393c732b24ca 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -22,6 +22,7 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -87,10 +88,11 @@ type BlockContext struct { // All fields can change between transactions. type TxContext struct { // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) - BlobHashes []common.Hash // Provides information for BLOBHASH - BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) + BlobHashes []common.Hash // Provides information for BLOBHASH + BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set + AccessEvents *state.AccessEvents // Capture all state accesses for this tx } // EVM is the Ethereum Virtual Machine base object and provides @@ -151,6 +153,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } + if txCtx.AccessEvents == nil && chainConfig.IsPrague(blockCtx.BlockNumber, blockCtx.Time) { + evm.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() + } evm.interpreter = NewEVMInterpreter(evm) return evm } @@ -158,6 +163,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { + if txCtx.AccessEvents == nil && evm.chainRules.IsPrague { + txCtx.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() + } evm.TxContext = txCtx evm.StateDB = statedb } @@ -204,7 +212,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 { // add proof of absence to witness - wgas := evm.StateDB.AccessEvents().AddAccount(addr.Bytes(), false) + wgas := evm.AccessEvents.AddAccount(addr.Bytes(), false) if gas < wgas { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas @@ -495,7 +503,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.StateDB.AccessEvents().ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { err = ErrOutOfGas } } @@ -526,11 +534,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } else { // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.StateDB.AccessEvents().AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { + if !contract.UseGas(evm.AccessEvents.AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { err = ErrCodeStoreOutOfGas } - if err == nil && len(ret) > 0 && !contract.UseGas(evm.StateDB.AccessEvents().CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { + if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { err = ErrCodeStoreOutOfGas } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 8f74287718975..b3cedfd09e2af 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -404,7 +404,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } if evm.chainRules.IsEIP4762 { if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.StateDB.AccessEvents().ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } @@ -440,7 +440,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory address := common.Address(stack.Back(1).Bytes20()) transfersValue := !stack.Back(2).IsZero() if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.StateDB.AccessEvents().ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) if overflow { return 0, ErrGasUintOverflow } diff --git a/core/vm/interface.go b/core/vm/interface.go index a54375572b5a3..774360a08ef9c 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -20,7 +20,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -76,9 +75,6 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) - - AccessEvents() *state.AccessEvents - Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) RevertToSnapshot(int) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 196eb840d977d..7761790f43b91 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -227,7 +227,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - contract.Gas -= in.evm.StateDB.AccessEvents().CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) + contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) } // Get the operation from the jump table and validate the stack to ensure there are diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 5167e744e73b3..2ded65950b5bd 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -23,7 +23,7 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.StateDB.AccessEvents().SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) + gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -31,7 +31,7 @@ func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.StateDB.AccessEvents().SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) + gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -40,7 +40,7 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - gas := evm.StateDB.AccessEvents().BalanceGas(address[:], false) + gas := evm.AccessEvents.BalanceGas(address[:], false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -52,8 +52,8 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - wgas := evm.StateDB.AccessEvents().VersionGas(address[:], false) - wgas += evm.StateDB.AccessEvents().CodeSizeGas(address[:], false) + wgas := evm.AccessEvents.VersionGas(address[:], false) + wgas += evm.AccessEvents.CodeSizeGas(address[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -65,7 +65,7 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - codehashgas := evm.StateDB.AccessEvents().CodeHashGas(address[:], false) + codehashgas := evm.AccessEvents.CodeHashGas(address[:], false) if codehashgas == 0 { codehashgas = params.WarmStorageReadCostEIP2929 } @@ -81,7 +81,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { return gas, nil } - wgas := evm.StateDB.AccessEvents().MessageCallGas(contract.Address().Bytes()) + wgas := evm.AccessEvents.MessageCallGas(contract.Address().Bytes()) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } @@ -102,17 +102,17 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } contractAddr := contract.Address() - statelessGas := evm.StateDB.AccessEvents().VersionGas(contractAddr[:], false) - statelessGas += evm.StateDB.AccessEvents().CodeSizeGas(contractAddr[:], false) - statelessGas += evm.StateDB.AccessEvents().BalanceGas(contractAddr[:], false) + statelessGas := evm.AccessEvents.VersionGas(contractAddr[:], false) + statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr[:], false) + statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], false) if contractAddr != beneficiaryAddr { - statelessGas += evm.StateDB.AccessEvents().BalanceGas(beneficiaryAddr[:], false) + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], false) } // Charge write costs if it transfers value if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { - statelessGas += evm.StateDB.AccessEvents().BalanceGas(contractAddr[:], true) + statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], true) if contractAddr != beneficiaryAddr { - statelessGas += evm.StateDB.AccessEvents().BalanceGas(beneficiaryAddr[:], true) + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], true) } } return statelessGas, nil @@ -133,7 +133,7 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) if !contract.IsDeployment { - gas += evm.StateDB.AccessEvents().CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) } return gas, nil } @@ -145,8 +145,8 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo return 0, err } addr := common.Address(stack.peek().Bytes20()) - wgas := evm.StateDB.AccessEvents().VersionGas(addr[:], false) - wgas += evm.StateDB.AccessEvents().CodeSizeGas(addr[:], false) + wgas := evm.AccessEvents.VersionGas(addr[:], false) + wgas += evm.AccessEvents.CodeSizeGas(addr[:], false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } diff --git a/params/config.go b/params/config.go index 5fedfd3519b46..176738b86826f 100644 --- a/params/config.go +++ b/params/config.go @@ -921,7 +921,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules } // disallow setting Merge out of order isMerge = isMerge && c.IsLondon(num) - isVerkle := isMerge && c.IsVerkle(num, timestamp) return Rules{ ChainID: new(big.Int).Set(chainID), IsHomestead: c.IsHomestead(num), @@ -933,13 +932,13 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), - IsEIP2929: c.IsBerlin(num) && !isVerkle, + IsEIP2929: c.IsBerlin(num) && !c.IsVerkle(num, timestamp), + IsEIP4762: c.IsVerkle(num, timestamp), IsLondon: c.IsLondon(num), IsMerge: isMerge, IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), - IsVerkle: isVerkle, - IsEIP4762: isVerkle, + IsVerkle: isMerge && c.IsVerkle(num, timestamp), } } From 04a4fe14d9e4166b0fcddf2c4cc4f38c9a30e077 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 2 May 2024 16:51:10 +0200 Subject: [PATCH 21/30] more review feedback --- core/state/access_events.go | 133 +++++++++++++++----------- core/state/access_events_test.go | 154 +++++++++++++++++++++++++++++++ core/vm/operations_verkle.go | 26 +++--- 3 files changed, 246 insertions(+), 67 deletions(-) create mode 100644 core/state/access_events_test.go diff --git a/core/state/access_events.go b/core/state/access_events.go index bc6064e96d56b..025237b339d8e 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -57,44 +57,44 @@ func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents { // Merge is used to merge the access events that were generated during the // execution of a tx, with the accumulation of all access events that were // generated during the execution of all txs preceding this one in a block. -func (aw *AccessEvents) Merge(other *AccessEvents) { +func (ae *AccessEvents) Merge(other *AccessEvents) { for k := range other.branches { - aw.branches[k] |= other.branches[k] + ae.branches[k] |= other.branches[k] } for k, chunk := range other.chunks { - aw.chunks[k] |= chunk + ae.chunks[k] |= chunk } } // Key returns, predictably, the list of keys that were touched during the // buildup of the access witness. -func (aw *AccessEvents) Keys() [][]byte { - // TODO: consider if parallelizing this is worth it, probably depending on len(aw.chunks). - keys := make([][]byte, 0, len(aw.chunks)) - for chunk := range aw.chunks { - basePoint := aw.pointCache.Get(chunk.addr[:]) +func (ae *AccessEvents) Keys() [][]byte { + // TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks). + keys := make([][]byte, 0, len(ae.chunks)) + for chunk := range ae.chunks { + basePoint := ae.pointCache.Get(chunk.addr[:]) key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey) keys = append(keys, key) } return keys } -func (aw *AccessEvents) Copy() *AccessEvents { - naw := &AccessEvents{ +func (ae *AccessEvents) Copy() *AccessEvents { + cpy := &AccessEvents{ branches: make(map[branchAccessKey]mode), chunks: make(map[chunkAccessKey]mode), - pointCache: aw.pointCache, + pointCache: ae.pointCache, } - naw.Merge(aw) - return naw + cpy.Merge(ae) + return cpy } // AddAccount returns the gas to be charged for each of the currently cold // member fields of an account. -func (aw *AccessEvents) AddAccount(addr []byte, isWrite bool) uint64 { +func (ae *AccessEvents) AddAccount(addr []byte, isWrite bool) uint64 { var gas uint64 for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) } return gas } @@ -102,60 +102,60 @@ func (aw *AccessEvents) AddAccount(addr []byte, isWrite bool) uint64 { // MessageCallGas returns the gas to be charged for each of the currently // cold member fields of an account, that need to be touched when making a message // call to that account. -func (aw *AccessEvents) MessageCallGas(destination []byte) uint64 { +func (ae *AccessEvents) MessageCallGas(destination []byte) uint64 { var gas uint64 - gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) - gas += aw.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) + gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) + gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) return gas } // ValueTransferGas returns the gas to be charged for each of the currently // cold balance member fields of the caller and the callee accounts. -func (aw *AccessEvents) ValueTransferGas(callerAddr, targetAddr []byte) uint64 { +func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr []byte) uint64 { var gas uint64 - gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) - gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) return gas } // ContractCreateInitGas returns the access gas costs for the initialization of // a contract creation. -func (aw *AccessEvents) ContractCreateInitGas(addr []byte, createSendsValue bool) uint64 { +func (ae *AccessEvents) ContractCreateInitGas(addr []byte, createSendsValue bool) uint64 { var gas uint64 - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) if createSendsValue { - gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true) } return gas } // AddTxOrigin adds the member fields of the sender account to the witness, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (aw *AccessEvents) AddTxOrigin(originAddr []byte) { +func (ae *AccessEvents) AddTxOrigin(originAddr []byte) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) } } // AddTxDestination adds the member fields of the sender account to the witness, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (aw *AccessEvents) AddTxDestination(targetAddr []byte, sendsValue bool) { +func (ae *AccessEvents) AddTxDestination(targetAddr []byte, sendsValue bool) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.VersionLeafKey && sendsValue) + ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.VersionLeafKey && sendsValue) } } // SlotGas returns the amount of gas to be charged for a cold storage access. -func (aw *AccessEvents) SlotGas(addr []byte, slot common.Hash, isWrite bool) uint64 { +func (ae *AccessEvents) SlotGas(addr []byte, slot common.Hash, isWrite bool) uint64 { treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) - return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) + return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } // touchAddressAndChargeGas adds any missing access event to the witness, and returns the cold // access cost to be charged, if need be. -func (aw *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { - stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite) +func (ae *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { + stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite) var gas uint64 if stemRead { @@ -178,33 +178,33 @@ func (aw *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256. } // touchAddress adds any missing access event to the witness. -func (aw *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { +func (ae *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { branchKey := newBranchAccessKey(addr, treeIndex) chunkKey := newChunkAccessKey(branchKey, subIndex) // Read access. var branchRead, chunkRead bool - if _, hasStem := aw.branches[branchKey]; !hasStem { + if _, hasStem := ae.branches[branchKey]; !hasStem { branchRead = true - aw.branches[branchKey] = AccessWitnessReadFlag + ae.branches[branchKey] = AccessWitnessReadFlag } - if _, hasSelector := aw.chunks[chunkKey]; !hasSelector { + if _, hasSelector := ae.chunks[chunkKey]; !hasSelector { chunkRead = true - aw.chunks[chunkKey] = AccessWitnessReadFlag + ae.chunks[chunkKey] = AccessWitnessReadFlag } // Write access. var branchWrite, chunkWrite, chunkFill bool if isWrite { - if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 { + if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 { branchWrite = true - aw.branches[branchKey] |= AccessWitnessWriteFlag + ae.branches[branchKey] |= AccessWitnessWriteFlag } - chunkValue := aw.chunks[chunkKey] + chunkValue := ae.chunks[chunkKey] if (chunkValue & AccessWitnessWriteFlag) == 0 { chunkWrite = true - aw.chunks[chunkKey] |= AccessWitnessWriteFlag + ae.chunks[chunkKey] |= AccessWitnessWriteFlag } // TODO: charge chunk filling costs if the leaf was previously empty in the state @@ -238,7 +238,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { } // CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func (aw *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { +func (ae *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { // note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, // we don't include the last leaf of code in the AccessWitness. The @@ -261,7 +261,7 @@ func (aw *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size ui for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) subIndex := byte((chunkNumber + 128) % 256) - gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) + gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) var overflow bool statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) if overflow { @@ -272,22 +272,47 @@ func (aw *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size ui return statelessGasCharged } -func (aw *AccessEvents) VersionGas(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) +// VersionGas returns the amount of gas to be charged if the account's version hasn't been +// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) VersionGas(addr []byte, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) } -func (aw *AccessEvents) BalanceGas(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) +// BalanceGas returns the amount of gas to be charged if the account's balance hasn't been +// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) BalanceGas(addr []byte, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) } -func (aw *AccessEvents) NonceGas(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) +// NonceGas returns the amount of gas to be charged if the account's nonce hasn't been +// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) NonceGas(addr []byte, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) } -func (aw *AccessEvents) CodeSizeGas(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) +// CodeSizeGas returns the amount of gas to be charged if the account's code size hasn't been +// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) CodeSizeGas(addr []byte, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) } -func (aw *AccessEvents) CodeHashGas(addr []byte, isWrite bool) uint64 { - return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) +// CodeHashGas returns the amount of gas to be charged if the account's code hash hasn't been +// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) CodeHashGas(addr []byte, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) } diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go new file mode 100644 index 0000000000000..e8cba9c0b9672 --- /dev/null +++ b/core/state/access_events_test.go @@ -0,0 +1,154 @@ +// Copyright 2021 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 state + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" +) + +var ( + testAddr [20]byte + testAddr2 [20]byte +) + +func init() { + for i := byte(0); i < 20; i++ { + testAddr[i] = i + testAddr[2] = 2 * i + } +} + +func TestAccountHeaderGas(t *testing.T) { + ae := NewAccessEvents(utils.NewPointCache(1024)) + + // Check cold read cost + gas := ae.VersionGas(testAddr[:], false) + if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost) + } + + // Check warm read cost + gas = ae.VersionGas(testAddr[:], false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check cold read costs in the same group no longer incur the branch read cost + gas = ae.BalanceGas(testAddr[:], false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + gas = ae.NonceGas(testAddr[:], false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + gas = ae.CodeSizeGas(testAddr[:], false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + gas = ae.CodeHashGas(testAddr[:], false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + + // Check cold write cost + gas = ae.VersionGas(testAddr[:], true) + if gas != params.WitnessBranchWriteCost+params.WitnessChunkWriteCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost) + } + + // Check warm write cost + gas = ae.VersionGas(testAddr[:], true) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check a write without a read charges both read and write costs + gas = ae.BalanceGas(testAddr2[:], true) + if gas != params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost) + + } + + // Check that a write followed by a read charges nothing + gas = ae.BalanceGas(testAddr2[:], false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check that reading a slot from the account header only charges the + // chunk read cost. + gas = ae.SlotGas(testAddr[:], common.Hash{}, false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } +} + +// TestContractCreateInitGas checks that the gas cost of contract creation is correctly +// calculated. +func TestContractCreateInitGas(t *testing.T) { + ae := NewAccessEvents(utils.NewPointCache(1024)) + + var testAddr [20]byte + for i := byte(0); i < 20; i++ { + testAddr[i] = i + } + + // Check cold read cost, without a value + gas := ae.ContractCreateInitGas(testAddr[:], false) + if gas != params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*2+params.WitnessChunkReadCost*2 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*3) + } + + // Check warm read cost + gas = ae.ContractCreateInitGas(testAddr[:], false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } +} + +// TestMessageCallGas checks that the gas cost of message calls is correctly +// calculated. +func TestMessageCallGas(t *testing.T) { + ae := NewAccessEvents(utils.NewPointCache(1024)) + + // Check cold read cost, without a value + gas := ae.MessageCallGas(testAddr[:]) + if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost*2 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost*2) + } + + // Check that reading the version and code size of the same account does not incur the branch read cost + gas = ae.VersionGas(testAddr[:], false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + gas = ae.CodeSizeGas(testAddr[:], false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check warm read cost + gas = ae.MessageCallGas(testAddr[:]) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } +} diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 2ded65950b5bd..37932522f28f9 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -52,12 +52,12 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - wgas := evm.AccessEvents.VersionGas(address[:], false) - wgas += evm.AccessEvents.CodeSizeGas(address[:], false) - if wgas == 0 { - wgas = params.WarmStorageReadCostEIP2929 + gas := evm.AccessEvents.VersionGas(address[:], false) + gas += evm.AccessEvents.CodeSizeGas(address[:], false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 } - return wgas, nil + return gas, nil } func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { @@ -65,11 +65,11 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - codehashgas := evm.AccessEvents.CodeHashGas(address[:], false) - if codehashgas == 0 { - codehashgas = params.WarmStorageReadCostEIP2929 + gas := evm.AccessEvents.CodeHashGas(address[:], false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 } - return codehashgas, nil + return gas, nil } func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { @@ -81,11 +81,11 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { return gas, nil } - wgas := evm.AccessEvents.MessageCallGas(contract.Address().Bytes()) - if wgas == 0 { - wgas = params.WarmStorageReadCostEIP2929 + witnessGas := evm.AccessEvents.MessageCallGas(contract.Address().Bytes()) + if witnessGas == 0 { + witnessGas = params.WarmStorageReadCostEIP2929 } - return wgas + gas, nil + return witnessGas + gas, nil } } From 3777222b865d0b289cd0ae779fb0d41be361b5af Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 2 May 2024 17:00:44 +0200 Subject: [PATCH 22/30] Focus on gas charges and remove witness-building parts Co-authored-by: Gary Rong --- consensus/beacon/consensus.go | 6 ------ core/state/access_events.go | 6 +----- core/state/statedb.go | 3 +++ core/state_processor.go | 3 --- params/config.go | 7 ++++--- 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 7d5007f241d1a..4e3fbeb09a7c9 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -360,12 +360,6 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. amount := new(uint256.Int).SetUint64(w.Amount) amount = amount.Mul(amount, uint256.NewInt(params.GWei)) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) - - // Add the balance of each withdrawal to the witness, no gas will - // be charged. - if chain.Config().IsEIP4762(header.Number, header.Time) { - state.AccessEvents().BalanceGas(w.Address[:], true) - } } // No block reward which is issued by consensus layer instead. } diff --git a/core/state/access_events.go b/core/state/access_events.go index 025237b339d8e..e8f58318affc7 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -66,7 +66,7 @@ func (ae *AccessEvents) Merge(other *AccessEvents) { } } -// Key returns, predictably, the list of keys that were touched during the +// Keys returns, predictably, the list of keys that were touched during the // buildup of the access witness. func (ae *AccessEvents) Keys() [][]byte { // TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks). @@ -173,7 +173,6 @@ func (ae *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256. if selectorFill { gas += params.WitnessChunkFillCost } - return gas } @@ -206,10 +205,8 @@ func (ae *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subInde chunkWrite = true ae.chunks[chunkKey] |= AccessWitnessWriteFlag } - // TODO: charge chunk filling costs if the leaf was previously empty in the state } - return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill } @@ -268,7 +265,6 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size ui panic("overflow when adding gas") } } - return statelessGasCharged } diff --git a/core/state/statedb.go b/core/state/statedb.go index 008de7994efee..7345b1348b542 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1297,6 +1297,9 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - Add coinbase to access list (EIP-3651) // - Reset transient storage (EIP-1153) func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + if rules.IsEIP2929 && rules.IsEIP4762 { + panic("eip2929 and eip4762 are both activated") + } if rules.IsEIP2929 { // Clear out any leftover from previous executions al := newAccessList() diff --git a/core/state_processor.go b/core/state_processor.go index 66de9595434b0..f73a908ad27b0 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -208,8 +208,5 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat statedb.AddAddressToAccessList(params.BeaconRootsAddress) } _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) - if vmenv.ChainConfig().Rules(vmenv.Context.BlockNumber, true, vmenv.Context.Time).IsEIP4762 { - statedb.AccessEvents().Merge(txctx.AccessEvents) - } statedb.Finalise(true) } diff --git a/params/config.go b/params/config.go index 176738b86826f..5fedfd3519b46 100644 --- a/params/config.go +++ b/params/config.go @@ -921,6 +921,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules } // disallow setting Merge out of order isMerge = isMerge && c.IsLondon(num) + isVerkle := isMerge && c.IsVerkle(num, timestamp) return Rules{ ChainID: new(big.Int).Set(chainID), IsHomestead: c.IsHomestead(num), @@ -932,13 +933,13 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), - IsEIP2929: c.IsBerlin(num) && !c.IsVerkle(num, timestamp), - IsEIP4762: c.IsVerkle(num, timestamp), + IsEIP2929: c.IsBerlin(num) && !isVerkle, IsLondon: c.IsLondon(num), IsMerge: isMerge, IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), - IsVerkle: isMerge && c.IsVerkle(num, timestamp), + IsVerkle: isVerkle, + IsEIP4762: isVerkle, } } From 72efd7198199324b91afbea5f237924b5e32e92e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 3 May 2024 18:27:35 +0200 Subject: [PATCH 23/30] move point cache to cachingDB and remove more witness stuff Co-authored-by: Gary Rong --- core/state/database.go | 16 +++++++++++++++- core/state/statedb.go | 25 ++++--------------------- core/state_processor.go | 5 ----- core/vm/evm.go | 7 ++----- core/vm/interface.go | 5 +++++ 5 files changed, 26 insertions(+), 32 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 8ae2c525461a1..04d7c06687c0a 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -38,6 +38,9 @@ const ( // Cache size granted for caching clean code. codeCacheSize = 64 * 1024 * 1024 + + // Number of address->curve point associations to keep. + pointCacheSize = 4096 ) // Database wraps access to tries and contract code. @@ -60,6 +63,9 @@ type Database interface { // DiskDB returns the underlying key-value disk database. DiskDB() ethdb.KeyValueStore + // PointCache returns the cache holding points used in verkle tree key computation + PointCache() *utils.PointCache + // TrieDB returns the underlying trie database for managing trie nodes. TrieDB() *triedb.Database } @@ -153,6 +159,7 @@ func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database { codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: triedb.NewDatabase(db, config), + pointCache: utils.NewPointCache(pointCacheSize), } } @@ -163,6 +170,7 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), triedb: triedb, + pointCache: utils.NewPointCache(pointCacheSize), } } @@ -171,12 +179,13 @@ type cachingDB struct { codeSizeCache *lru.Cache[common.Hash, int] codeCache *lru.SizeConstrainedCache[common.Hash, []byte] triedb *triedb.Database + pointCache *utils.PointCache } // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { - return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(100)) + return trie.NewVerkleTrie(root, db.triedb, db.pointCache) } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { @@ -262,3 +271,8 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore { func (db *cachingDB) TrieDB() *triedb.Database { return db.triedb } + +// PointCache returns the cache of evaluated curve points. +func (db *cachingDB) PointCache() *utils.PointCache { + return db.pointCache +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 7345b1348b542..2d94bf34d7025 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -140,9 +140,6 @@ type StateDB struct { // Transient storage transientStorage transientStorage - // State access events, used for Verkle tries/EIP4762 - accessEvents *AccessEvents - // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. journal *journal @@ -197,30 +194,12 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), } - if tr.IsVerkle() { - sdb.accessEvents = sdb.NewAccessEvents() - } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) } return sdb, nil } -func (s *StateDB) NewAccessEvents() *AccessEvents { - return NewAccessEvents(utils.NewPointCache(100)) -} - -func (s *StateDB) AccessEvents() *AccessEvents { - if s.accessEvents == nil { - s.accessEvents = s.NewAccessEvents() - } - return s.accessEvents -} - -func (s *StateDB) SetAccessEvents(ae *AccessEvents) { - s.accessEvents = ae -} - // SetLogger sets the logger for account update hooks. func (s *StateDB) SetLogger(l *tracing.Hooks) { s.logger = l @@ -1412,3 +1391,7 @@ func (s *StateDB) markUpdate(addr common.Address) { s.mutations[addr].applied = false s.mutations[addr].typ = update } + +func (s *StateDB) PointCache() *utils.PointCache { + return s.db.PointCache() +} diff --git a/core/state_processor.go b/core/state_processor.go index f73a908ad27b0..19389f07977aa 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -120,7 +120,6 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo } // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) - txContext.AccessEvents = statedb.NewAccessEvents() evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env). @@ -159,10 +158,6 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - if statedb.AccessEvents() != nil { - statedb.AccessEvents().Merge(txContext.AccessEvents) - } - // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) diff --git a/core/vm/evm.go b/core/vm/evm.go index 9393c732b24ca..a360baff9c904 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -153,9 +153,6 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } - if txCtx.AccessEvents == nil && chainConfig.IsPrague(blockCtx.BlockNumber, blockCtx.Time) { - evm.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() - } evm.interpreter = NewEVMInterpreter(evm) return evm } @@ -163,8 +160,8 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { - if txCtx.AccessEvents == nil && evm.chainRules.IsPrague { - txCtx.AccessEvents = evm.StateDB.(*state.StateDB).NewAccessEvents() + if evm.chainRules.IsEIP4762 { + txCtx.AccessEvents = state.NewAccessEvents(statedb.PointCache()) } evm.TxContext = txCtx evm.StateDB = statedb diff --git a/core/vm/interface.go b/core/vm/interface.go index 774360a08ef9c..8b2c58898ec65 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" ) @@ -75,6 +76,10 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) + + // PointCache returns the point cache used in computations + PointCache() *utils.PointCache + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) RevertToSnapshot(int) From 7fa2327931d4662d9872db2485235842ad44d951 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 6 May 2024 14:22:32 +0200 Subject: [PATCH 24/30] a few bug fixes and feedback from Gary Co-authored-by: Gary Rong --- core/state/access_events.go | 10 ++++----- core/state_processor.go | 3 --- core/vm/eips.go | 41 ++----------------------------------- core/vm/gas_table.go | 29 +++++++++++++------------- core/vm/interpreter.go | 2 +- core/vm/jump_table.go | 2 +- 6 files changed, 23 insertions(+), 64 deletions(-) diff --git a/core/state/access_events.go b/core/state/access_events.go index e8f58318affc7..10b533636f0d4 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -130,7 +130,7 @@ func (ae *AccessEvents) ContractCreateInitGas(addr []byte, createSendsValue bool return gas } -// AddTxOrigin adds the member fields of the sender account to the witness, +// AddTxOrigin adds the member fields of the sender account to the access event list, // so that cold accesses are not charged, since they are covered by the 21000 gas. func (ae *AccessEvents) AddTxOrigin(originAddr []byte) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { @@ -138,11 +138,11 @@ func (ae *AccessEvents) AddTxOrigin(originAddr []byte) { } } -// AddTxDestination adds the member fields of the sender account to the witness, +// AddTxDestination adds the member fields of the sender account to the access event list, // so that cold accesses are not charged, since they are covered by the 21000 gas. func (ae *AccessEvents) AddTxDestination(targetAddr []byte, sendsValue bool) { for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.VersionLeafKey && sendsValue) + ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey && sendsValue) } } @@ -152,7 +152,7 @@ func (ae *AccessEvents) SlotGas(addr []byte, slot common.Hash, isWrite bool) uin return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } -// touchAddressAndChargeGas adds any missing access event to the witness, and returns the cold +// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold // access cost to be charged, if need be. func (ae *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite) @@ -176,7 +176,7 @@ func (ae *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256. return gas } -// touchAddress adds any missing access event to the witness. +// touchAddress adds any missing access event to the access event list. func (ae *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { branchKey := newBranchAccessKey(addr, treeIndex) chunkKey := newChunkAccessKey(branchKey, subIndex) diff --git a/core/state_processor.go b/core/state_processor.go index 19389f07977aa..114844c0a8810 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -199,9 +199,6 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat } txctx := NewEVMTxContext(msg) vmenv.Reset(txctx, statedb) - if vmenv.ChainConfig().Rules(vmenv.Context.BlockNumber, true, vmenv.Context.Time).IsEIP2929 { - statedb.AddAddressToAccessList(params.BeaconRootsAddress) - } _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) statedb.Finalise(true) } diff --git a/core/vm/eips.go b/core/vm/eips.go index 2af1b17672fa2..a18af46ab7a4f 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -324,42 +323,6 @@ func enable6780(jt *JumpTable) { } } -func opCreateEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - if interpreter.readOnly { - return nil, ErrWriteProtection - } - var endowment = scope.Stack.peek() - - contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) - statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - return nil, ErrExecutionReverted - } - - return opCreate(pc, interpreter, scope) -} - -func opCreate2EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - if interpreter.readOnly { - return nil, ErrWriteProtection - } - var ( - endowment = scope.Stack.Back(0) - offset, size = scope.Stack.Back(1), scope.Stack.Back(2) - salt = scope.Stack.Back(3) - input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - ) - - codeAndHash := &codeAndHash{code: input} - contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) - statelessGas := interpreter.evm.AccessEvents.ContractCreateInitGas(contractAddress.Bytes()[:], endowment.Sign() != 0) - if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { - return nil, ErrExecutionReverted - } - - return opCreate2(pc, interpreter, scope) -} - func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( stack = scope.Stack @@ -506,7 +469,7 @@ func enable4762(jt *JumpTable) { } jt[CREATE] = &operation{ - execute: opCreateEIP4762, + execute: opCreate, constantGas: params.CreateNGasEip4762, dynamicGas: gasCreateEip3860, minStack: minStack(3, 1), @@ -515,7 +478,7 @@ func enable4762(jt *JumpTable) { } jt[CREATE2] = &operation{ - execute: opCreate2EIP4762, + execute: opCreate2, constantGas: params.CreateNGasEip4762, dynamicGas: gasCreate2Eip3860, minStack: minStack(4, 1), diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index b3cedfd09e2af..65a2c8826d82b 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -394,14 +394,6 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } - - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) - if err != nil { - return 0, err - } - if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow - } if evm.chainRules.IsEIP4762 { if transfersValue { gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) @@ -410,6 +402,13 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } } } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } return gas, nil } @@ -429,13 +428,6 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) - if err != nil { - return 0, err - } - if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow - } if evm.chainRules.IsEIP4762 { address := common.Address(stack.Back(1).Bytes20()) transfersValue := !stack.Back(2).IsZero() @@ -446,6 +438,13 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory } } } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } return gas, nil } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 7761790f43b91..e12362110b25d 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -100,7 +100,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { var table *JumpTable switch { case evm.chainRules.IsVerkle: - // TODO replace with prooper instruction set when fork is specified + // TODO replace with proper instruction set when fork is specified table = &verkleInstructionSet case evm.chainRules.IsCancun: table = &cancunInstructionSet diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 37e63eaab64c4..e0e86418addfd 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -82,7 +82,7 @@ func validate(jt JumpTable) JumpTable { } func newVerkleInstructionSet() JumpTable { - instructionSet := newShanghaiInstructionSet() + instructionSet := newCancunInstructionSet() enable6780(&instructionSet) enable4762(&instructionSet) return validate(instructionSet) From d804ab92cd736dbc2ae7158a2db4a979aae42499 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 6 May 2024 15:05:58 +0200 Subject: [PATCH 25/30] fix broken test --- core/state_processor.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 114844c0a8810..b1a8938f677a1 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -197,8 +197,8 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat To: ¶ms.BeaconRootsAddress, Data: beaconRoot[:], } - txctx := NewEVMTxContext(msg) - vmenv.Reset(txctx, statedb) + vmenv.Reset(NewEVMTxContext(msg), statedb) + statedb.AddAddressToAccessList(params.BeaconRootsAddress) _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) statedb.Finalise(true) } From 37952f606326df77e5819bbe3ab7398d74a669c0 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 6 May 2024 18:13:34 +0200 Subject: [PATCH 26/30] remove unused line --- core/state_transition.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index fba41762ad202..16beff8efd245 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -410,9 +410,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if targetAddr := msg.To; targetAddr != nil { st.evm.AccessEvents.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) - - // ensure the code size ends up in the access witness - st.evm.StateDB.GetCodeSize(*targetAddr) } } From dbca8bb5d46e8137a5017d8032b73df522e0ef83 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 7 May 2024 10:52:30 +0200 Subject: [PATCH 27/30] Apply suggestions from code review Co-authored-by: Martin HS --- core/state/access_events.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/state/access_events.go b/core/state/access_events.go index 10b533636f0d4..bda337e3d4e8e 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -81,11 +81,10 @@ func (ae *AccessEvents) Keys() [][]byte { func (ae *AccessEvents) Copy() *AccessEvents { cpy := &AccessEvents{ - branches: make(map[branchAccessKey]mode), - chunks: make(map[chunkAccessKey]mode), + branches: maps.Clone(ae.branches), + chunks: maps.Clone(ae.chunks), pointCache: ae.pointCache, } - cpy.Merge(ae) return cpy } @@ -93,9 +92,11 @@ func (ae *AccessEvents) Copy() *AccessEvents { // member fields of an account. func (ae *AccessEvents) AddAccount(addr []byte, isWrite bool) uint64 { var gas uint64 - for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite) - } + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, VersionLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, BalanceLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, NonceLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, CodeKeccakLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, CodeSizeLeafKey, isWrite) return gas } @@ -268,9 +269,8 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size ui return statelessGasCharged } -// VersionGas returns the amount of gas to be charged if the account's version hasn't been -// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access -// in write mode. If false, the charged gas corresponds to an access in read mode. +// VersionGas adds the accounts version to the accessed data, and returns the +// amount of gas that it costs. // Note that an access in write mode implies an access in read mode, whereas an access in // read mode does not imply an access in write mode. func (ae *AccessEvents) VersionGas(addr []byte, isWrite bool) uint64 { From 3800a708e147365ba1892f0ba18444c521acc5d3 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 7 May 2024 11:37:10 +0200 Subject: [PATCH 28/30] fix some formatting --- core/state/access_events.go | 86 +++++++++++++++++--------------- core/state/access_events_test.go | 34 ++++++------- core/state_transition.go | 6 +-- core/vm/eips.go | 6 +-- core/vm/evm.go | 8 +-- core/vm/gas_table.go | 4 +- core/vm/interpreter.go | 2 +- core/vm/operations_verkle.go | 32 ++++++------ 8 files changed, 92 insertions(+), 86 deletions(-) diff --git a/core/state/access_events.go b/core/state/access_events.go index bda337e3d4e8e..4b6c7c7e69bb0 100644 --- a/core/state/access_events.go +++ b/core/state/access_events.go @@ -17,6 +17,8 @@ package state import ( + "maps" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" @@ -90,20 +92,20 @@ func (ae *AccessEvents) Copy() *AccessEvents { // AddAccount returns the gas to be charged for each of the currently cold // member fields of an account. -func (ae *AccessEvents) AddAccount(addr []byte, isWrite bool) uint64 { +func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { var gas uint64 - gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, VersionLeafKey, isWrite) - gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, BalanceLeafKey, isWrite) - gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, NonceLeafKey, isWrite) - gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, CodeKeccakLeafKey, isWrite) - gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, CodeSizeLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) return gas } // MessageCallGas returns the gas to be charged for each of the currently // cold member fields of an account, that need to be touched when making a message // call to that account. -func (ae *AccessEvents) MessageCallGas(destination []byte) uint64 { +func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { var gas uint64 gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) @@ -112,7 +114,7 @@ func (ae *AccessEvents) MessageCallGas(destination []byte) uint64 { // ValueTransferGas returns the gas to be charged for each of the currently // cold balance member fields of the caller and the callee accounts. -func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr []byte) uint64 { +func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { var gas uint64 gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) @@ -121,7 +123,7 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr []byte) uint64 { // ContractCreateInitGas returns the access gas costs for the initialization of // a contract creation. -func (ae *AccessEvents) ContractCreateInitGas(addr []byte, createSendsValue bool) uint64 { +func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 { var gas uint64 gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) @@ -133,29 +135,33 @@ func (ae *AccessEvents) ContractCreateInitGas(addr []byte, createSendsValue bool // AddTxOrigin adds the member fields of the sender account to the access event list, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (ae *AccessEvents) AddTxOrigin(originAddr []byte) { - for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey) - } +func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) } // AddTxDestination adds the member fields of the sender account to the access event list, // so that cold accesses are not charged, since they are covered by the 21000 gas. -func (ae *AccessEvents) AddTxDestination(targetAddr []byte, sendsValue bool) { - for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ { - ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey && sendsValue) - } +func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false) } // SlotGas returns the amount of gas to be charged for a cold storage access. -func (ae *AccessEvents) SlotGas(addr []byte, slot common.Hash, isWrite bool) uint64 { +func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 { treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) } // touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold // access cost to be charged, if need be. -func (ae *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { +func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite) var gas uint64 @@ -178,7 +184,7 @@ func (ae *AccessEvents) touchAddressAndChargeGas(addr []byte, treeIndex uint256. } // touchAddress adds any missing access event to the access event list. -func (ae *AccessEvents) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { +func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { branchKey := newBranchAccessKey(addr, treeIndex) chunkKey := newChunkAccessKey(branchKey, subIndex) @@ -216,9 +222,9 @@ type branchAccessKey struct { treeIndex uint256.Int } -func newBranchAccessKey(addr []byte, treeIndex uint256.Int) branchAccessKey { +func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey { var sk branchAccessKey - copy(sk.addr[20-len(addr):], addr) + sk.addr = addr sk.treeIndex = treeIndex return sk } @@ -236,7 +242,7 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { } // CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func (ae *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { +func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { // note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, // we don't include the last leaf of code in the AccessWitness. The @@ -269,46 +275,46 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr []byte, startPC, size ui return statelessGasCharged } -// VersionGas adds the accounts version to the accessed data, and returns the -// amount of gas that it costs. -// Note that an access in write mode implies an access in read mode, whereas an access in -// read mode does not imply an access in write mode. -func (ae *AccessEvents) VersionGas(addr []byte, isWrite bool) uint64 { +// VersionGas adds the account's version to the accessed data, and returns the +// amount of gas that it costs. +// Note that an access in write mode implies an access in read mode, whereas an +// access in read mode does not imply an access in write mode. +func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 { return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) } -// BalanceGas returns the amount of gas to be charged if the account's balance hasn't been -// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// BalanceGas adds the account's balance to the accessed data, and returns the +// amount of gas that it costs. // in write mode. If false, the charged gas corresponds to an access in read mode. // Note that an access in write mode implies an access in read mode, whereas an access in // read mode does not imply an access in write mode. -func (ae *AccessEvents) BalanceGas(addr []byte, isWrite bool) uint64 { +func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 { return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) } -// NonceGas returns the amount of gas to be charged if the account's nonce hasn't been -// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// NonceGas adds the account's nonce to the accessed data, and returns the +// amount of gas that it costs. // in write mode. If false, the charged gas corresponds to an access in read mode. // Note that an access in write mode implies an access in read mode, whereas an access in // read mode does not imply an access in write mode. -func (ae *AccessEvents) NonceGas(addr []byte, isWrite bool) uint64 { +func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 { return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) } -// CodeSizeGas returns the amount of gas to be charged if the account's code size hasn't been -// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// CodeSizeGas adds the account's code size to the accessed data, and returns the +// amount of gas that it costs. // in write mode. If false, the charged gas corresponds to an access in read mode. // Note that an access in write mode implies an access in read mode, whereas an access in // read mode does not imply an access in write mode. -func (ae *AccessEvents) CodeSizeGas(addr []byte, isWrite bool) uint64 { +func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 { return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) } -// CodeHashGas returns the amount of gas to be charged if the account's code hash hasn't been -// touched in the proper mode. If `isWrite` is `true` then the charged gas is for an access +// CodeHashGas adds the account's code hash to the accessed data, and returns the +// amount of gas that it costs. // in write mode. If false, the charged gas corresponds to an access in read mode. // Note that an access in write mode implies an access in read mode, whereas an access in // read mode does not imply an access in write mode. -func (ae *AccessEvents) CodeHashGas(addr []byte, isWrite bool) uint64 { +func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) } diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go index e8cba9c0b9672..a7bb156db732b 100644 --- a/core/state/access_events_test.go +++ b/core/state/access_events_test.go @@ -40,63 +40,63 @@ func TestAccountHeaderGas(t *testing.T) { ae := NewAccessEvents(utils.NewPointCache(1024)) // Check cold read cost - gas := ae.VersionGas(testAddr[:], false) + gas := ae.VersionGas(testAddr, false) if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost) } // Check warm read cost - gas = ae.VersionGas(testAddr[:], false) + gas = ae.VersionGas(testAddr, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check cold read costs in the same group no longer incur the branch read cost - gas = ae.BalanceGas(testAddr[:], false) + gas = ae.BalanceGas(testAddr, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } - gas = ae.NonceGas(testAddr[:], false) + gas = ae.NonceGas(testAddr, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } - gas = ae.CodeSizeGas(testAddr[:], false) + gas = ae.CodeSizeGas(testAddr, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } - gas = ae.CodeHashGas(testAddr[:], false) + gas = ae.CodeHashGas(testAddr, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } // Check cold write cost - gas = ae.VersionGas(testAddr[:], true) + gas = ae.VersionGas(testAddr, true) if gas != params.WitnessBranchWriteCost+params.WitnessChunkWriteCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost) } // Check warm write cost - gas = ae.VersionGas(testAddr[:], true) + gas = ae.VersionGas(testAddr, true) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check a write without a read charges both read and write costs - gas = ae.BalanceGas(testAddr2[:], true) + gas = ae.BalanceGas(testAddr2, true) if gas != params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost) } // Check that a write followed by a read charges nothing - gas = ae.BalanceGas(testAddr2[:], false) + gas = ae.BalanceGas(testAddr2, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check that reading a slot from the account header only charges the // chunk read cost. - gas = ae.SlotGas(testAddr[:], common.Hash{}, false) + gas = ae.SlotGas(testAddr, common.Hash{}, false) if gas != params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } @@ -113,13 +113,13 @@ func TestContractCreateInitGas(t *testing.T) { } // Check cold read cost, without a value - gas := ae.ContractCreateInitGas(testAddr[:], false) + gas := ae.ContractCreateInitGas(testAddr, false) if gas != params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*2+params.WitnessChunkReadCost*2 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*3) } // Check warm read cost - gas = ae.ContractCreateInitGas(testAddr[:], false) + gas = ae.ContractCreateInitGas(testAddr, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } @@ -131,23 +131,23 @@ func TestMessageCallGas(t *testing.T) { ae := NewAccessEvents(utils.NewPointCache(1024)) // Check cold read cost, without a value - gas := ae.MessageCallGas(testAddr[:]) + gas := ae.MessageCallGas(testAddr) if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost*2 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost*2) } // Check that reading the version and code size of the same account does not incur the branch read cost - gas = ae.VersionGas(testAddr[:], false) + gas = ae.VersionGas(testAddr, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } - gas = ae.CodeSizeGas(testAddr[:], false) + gas = ae.CodeSizeGas(testAddr, false) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } // Check warm read cost - gas = ae.MessageCallGas(testAddr[:]) + gas = ae.MessageCallGas(testAddr) if gas != 0 { t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) } diff --git a/core/state_transition.go b/core/state_transition.go index 16beff8efd245..3b6a887fff524 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -406,10 +406,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.gasRemaining -= gas if rules.IsEIP4762 { - st.evm.AccessEvents.AddTxOrigin(msg.From.Bytes()) + st.evm.AccessEvents.AddTxOrigin(msg.From) if targetAddr := msg.To; targetAddr != nil { - st.evm.AccessEvents.AddTxDestination(targetAddr.Bytes(), msg.Value.Sign() != 0) + st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0) } } @@ -469,7 +469,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // add the coinbase to the witness iff the fee is greater than 0 if rules.IsEIP4762 && fee.Sign() != 0 { - st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase[:], true) + st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true) } } diff --git a/core/vm/eips.go b/core/vm/eips.go index a18af46ab7a4f..edd6ec8d0a2cc 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -342,7 +342,7 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC self: AccountRef(addr), } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -368,7 +368,7 @@ func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // touch next chunk if PUSH1 is at the boundary. if so, *pc has // advanced past this boundary. contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas @@ -396,7 +396,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc { if !scope.Contract.IsDeployment { contractAddr := scope.Contract.Address() - statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr[:], uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { scope.Contract.Gas = 0 return nil, ErrOutOfGas diff --git a/core/vm/evm.go b/core/vm/evm.go index a360baff9c904..26af0ea041b8e 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -209,7 +209,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP4762 { // add proof of absence to witness - wgas := evm.AccessEvents.AddAccount(addr.Bytes(), false) + wgas := evm.AccessEvents.AddAccount(addr, false) if gas < wgas { evm.StateDB.RevertToSnapshot(snapshot) return nil, 0, ErrOutOfGas @@ -500,7 +500,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { - if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { err = ErrOutOfGas } } @@ -531,11 +531,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } else { // Contract creation completed, touch the missing fields in the contract - if !contract.UseGas(evm.AccessEvents.AddAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { + if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { err = ErrCodeStoreOutOfGas } - if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { + if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { err = ErrCodeStoreOutOfGas } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 65a2c8826d82b..d294324b08c28 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -396,7 +396,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } if evm.chainRules.IsEIP4762 { if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) if overflow { return 0, ErrGasUintOverflow } @@ -432,7 +432,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory address := common.Address(stack.Back(1).Bytes20()) transfersValue := !stack.Back(2).IsZero() if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address().Bytes()[:], address.Bytes()[:])) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) if overflow { return 0, ErrGasUintOverflow } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index e12362110b25d..66a20f434e852 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -227,7 +227,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), false) + contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false) } // Get the operation from the jump table and validate the stack to ensure there are diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 37932522f28f9..73eb05974dc00 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -23,7 +23,7 @@ import ( ) func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), true) + gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -31,7 +31,7 @@ func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo } func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.AccessEvents.SlotGas(contract.Address().Bytes(), common.Hash(stack.peek().Bytes32()), false) + gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -40,7 +40,7 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() - gas := evm.AccessEvents.BalanceGas(address[:], false) + gas := evm.AccessEvents.BalanceGas(address, false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -52,8 +52,8 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - gas := evm.AccessEvents.VersionGas(address[:], false) - gas += evm.AccessEvents.CodeSizeGas(address[:], false) + gas := evm.AccessEvents.VersionGas(address, false) + gas += evm.AccessEvents.CodeSizeGas(address, false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -65,7 +65,7 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil } - gas := evm.AccessEvents.CodeHashGas(address[:], false) + gas := evm.AccessEvents.CodeHashGas(address, false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } @@ -81,7 +81,7 @@ func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { return gas, nil } - witnessGas := evm.AccessEvents.MessageCallGas(contract.Address().Bytes()) + witnessGas := evm.AccessEvents.MessageCallGas(contract.Address()) if witnessGas == 0 { witnessGas = params.WarmStorageReadCostEIP2929 } @@ -102,17 +102,17 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } contractAddr := contract.Address() - statelessGas := evm.AccessEvents.VersionGas(contractAddr[:], false) - statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr[:], false) - statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], false) + statelessGas := evm.AccessEvents.VersionGas(contractAddr, false) + statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr, false) + statelessGas += evm.AccessEvents.BalanceGas(contractAddr, false) if contractAddr != beneficiaryAddr { - statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], false) + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, false) } // Charge write costs if it transfers value if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { - statelessGas += evm.AccessEvents.BalanceGas(contractAddr[:], true) + statelessGas += evm.AccessEvents.BalanceGas(contractAddr, true) if contractAddr != beneficiaryAddr { - statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr[:], true) + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, true) } } return statelessGas, nil @@ -133,7 +133,7 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) if !contract.IsDeployment { - gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address().Bytes(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) } return gas, nil } @@ -145,8 +145,8 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo return 0, err } addr := common.Address(stack.peek().Bytes20()) - wgas := evm.AccessEvents.VersionGas(addr[:], false) - wgas += evm.AccessEvents.CodeSizeGas(addr[:], false) + wgas := evm.AccessEvents.VersionGas(addr, false) + wgas += evm.AccessEvents.CodeSizeGas(addr, false) if wgas == 0 { wgas = params.WarmStorageReadCostEIP2929 } From 1a661dcabfc0620fea498e7d25d954e88010a93f Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 9 May 2024 15:43:38 +0800 Subject: [PATCH 29/30] core/vm: remove duplicated EIP initialization --- core/vm/jump_table.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index e0e86418addfd..5624f47ba72cc 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -83,7 +83,6 @@ func validate(jt JumpTable) JumpTable { func newVerkleInstructionSet() JumpTable { instructionSet := newCancunInstructionSet() - enable6780(&instructionSet) enable4762(&instructionSet) return validate(instructionSet) } From 88da3ce370925106c92675d4f68361c7ca9c9343 Mon Sep 17 00:00:00 2001 From: Martin HS Date: Fri, 10 May 2024 19:37:47 +0200 Subject: [PATCH 30/30] Update core/state/access_events_test.go --- core/state/access_events_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go index a7bb156db732b..705033fe0be39 100644 --- a/core/state/access_events_test.go +++ b/core/state/access_events_test.go @@ -85,7 +85,6 @@ func TestAccountHeaderGas(t *testing.T) { gas = ae.BalanceGas(testAddr2, true) if gas != params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost { t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost) - } // Check that a write followed by a read charges nothing