diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go
index 4d62206417..33d6d4bbc4 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/cmd/geth/verkle.go b/cmd/geth/verkle.go
index ff3931356e..9eb37fb5a8 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/core/chain_makers.go b/core/chain_makers.go
index 13d7cb86c0..58985347bb 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"
)
diff --git a/core/error.go b/core/error.go
index e6e6ba2f90..161538fe43 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_events.go b/core/state/access_events.go
new file mode 100644
index 0000000000..4b6c7c7e69
--- /dev/null
+++ b/core/state/access_events.go
@@ -0,0 +1,320 @@
+// 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 (
+ "maps"
+
+ "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
+
+// AccessEvents lists the locations of the state that are being accessed
+// during the production of a block.
+type AccessEvents struct {
+ branches map[branchAccessKey]mode
+ chunks map[chunkAccessKey]mode
+
+ pointCache *utils.PointCache
+}
+
+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 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 (ae *AccessEvents) Merge(other *AccessEvents) {
+ for k := range other.branches {
+ ae.branches[k] |= other.branches[k]
+ }
+ for k, chunk := range other.chunks {
+ ae.chunks[k] |= chunk
+ }
+}
+
+// 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).
+ 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 (ae *AccessEvents) Copy() *AccessEvents {
+ cpy := &AccessEvents{
+ branches: maps.Clone(ae.branches),
+ chunks: maps.Clone(ae.chunks),
+ pointCache: ae.pointCache,
+ }
+ return cpy
+}
+
+// AddAccount returns the gas to be charged for each of the currently cold
+// member fields of an account.
+func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
+ var gas uint64
+ 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 common.Address) uint64 {
+ var gas uint64
+ 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 (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)
+ return gas
+}
+
+// ContractCreateInitGas returns the access gas costs for the initialization of
+// a contract creation.
+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)
+ if createSendsValue {
+ gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
+ }
+ return gas
+}
+
+// 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 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(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 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 common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
+ stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.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 access event list.
+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)
+
+ // Read access.
+ var branchRead, chunkRead bool
+ if _, hasStem := ae.branches[branchKey]; !hasStem {
+ branchRead = true
+ ae.branches[branchKey] = AccessWitnessReadFlag
+ }
+ if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
+ chunkRead = true
+ ae.chunks[chunkKey] = AccessWitnessReadFlag
+ }
+
+ // Write access.
+ var branchWrite, chunkWrite, chunkFill bool
+ if isWrite {
+ if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
+ branchWrite = true
+ ae.branches[branchKey] |= AccessWitnessWriteFlag
+ }
+
+ chunkValue := ae.chunks[chunkKey]
+ if (chunkValue & AccessWitnessWriteFlag) == 0 {
+ 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
+}
+
+type branchAccessKey struct {
+ addr common.Address
+ treeIndex uint256.Int
+}
+
+func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey {
+ var sk branchAccessKey
+ sk.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
+}
+
+// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
+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
+ // 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 := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
+ var overflow bool
+ statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
+ if overflow {
+ panic("overflow when adding gas")
+ }
+ }
+ return statelessGasCharged
+}
+
+// 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 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 common.Address, isWrite bool) uint64 {
+ return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
+}
+
+// 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 common.Address, isWrite bool) uint64 {
+ return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
+}
+
+// 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 common.Address, isWrite bool) uint64 {
+ return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
+}
+
+// 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 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
new file mode 100644
index 0000000000..705033fe0b
--- /dev/null
+++ b/core/state/access_events_test.go
@@ -0,0 +1,153 @@
+// 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/state/database.go b/core/state/database.go
index 188ecf0c86..04d7c06687 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"
@@ -40,11 +39,8 @@ 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)
+ // Number of address->curve point associations to keep.
+ pointCacheSize = 4096
)
// Database wraps access to tries and contract code.
@@ -67,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
}
@@ -139,6 +138,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
@@ -157,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),
}
}
@@ -167,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),
}
}
@@ -175,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(commitmentCacheItems))
+ return trie.NewVerkleTrie(root, db.triedb, db.pointCache)
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
@@ -266,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 ac37d4ceeb..1b028a2844 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -37,6 +37,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"
"golang.org/x/sync/errgroup"
)
@@ -1327,7 +1328,10 @@ 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 && rules.IsEIP4762 {
+ panic("eip2929 and eip4762 are both activated")
+ }
+ if rules.IsEIP2929 {
// Clear out any leftover from previous executions
al := newAccessList()
s.accessList = al
@@ -1439,3 +1443,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_test.go b/core/state_processor_test.go
index e98d27eb92..af4d29b604 100644
--- a/core/state_processor_test.go
+++ b/core/state_processor_test.go
@@ -482,7 +482,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(57444 /* execution costs */)
blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
diff --git a/core/state_transition.go b/core/state_transition.go
index a52e24dc43..3b6a887fff 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -68,7 +68,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 {
@@ -405,6 +405,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
st.gasRemaining -= gas
+ if rules.IsEIP4762 {
+ st.evm.AccessEvents.AddTxOrigin(msg.From)
+
+ if targetAddr := msg.To; targetAddr != nil {
+ st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0)
+ }
+ }
+
// Check clause 6
value, overflow := uint256.FromBig(msg.Value)
if overflow {
@@ -458,6 +466,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.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true)
+ }
}
return &ExecutionResult{
diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go
index 41bae63d9f..9b08cffd45 100644
--- a/core/tracing/hooks.go
+++ b/core/tracing/hooks.go
@@ -298,6 +298,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/common.go b/core/vm/common.go
index 90ba4a4ad1..ba75950e37 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 4e28260a67..cfda75b27e 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/contracts.go b/core/vm/contracts.go
index 8b648062e9..8e0f846775 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 9f06b2818f..edd6ec8d0a 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -18,9 +18,11 @@ package vm
import (
"fmt"
+ "math"
"sort"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@@ -37,6 +39,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 +322,214 @@ func enable6780(jt *JumpTable) {
maxStack: maxStack(1, 0),
}
}
+
+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.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
+ }
+ scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
+
+ return nil, nil
+}
+
+// opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which
+// 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))
+ 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.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
+ }
+ }
+ } 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.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
+ }
+ }
+
+ *pc += size
+ return nil, nil
+ }
+}
+
+func enable4762(jt *JumpTable) {
+ 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: opCreate,
+ constantGas: params.CreateNGasEip4762,
+ dynamicGas: gasCreateEip3860,
+ minStack: minStack(3, 1),
+ maxStack: maxStack(3, 1),
+ memorySize: memoryCreate,
+ }
+
+ jt[CREATE2] = &operation{
+ execute: opCreate2,
+ 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)] = &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 c18353a973..26af0ea041 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"
@@ -42,6 +43,8 @@ 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:
@@ -85,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
@@ -156,6 +160,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 evm.chainRules.IsEIP4762 {
+ txCtx.AccessEvents = state.NewAccessEvents(statedb.PointCache())
+ }
evm.TxContext = txCtx
evm.StateDB = statedb
}
@@ -200,6 +207,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
p, isPrecompile := evm.precompile(addr)
if !evm.StateDB.Exist(addr) {
+ if !isPrecompile && evm.chainRules.IsEIP4762 {
+ // add proof of absence to witness
+ wgas := evm.AccessEvents.AddAccount(addr, 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
@@ -439,7 +456,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// 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 {
+ if evm.chainRules.IsEIP2929 {
evm.StateDB.AddAddressToAccessList(address)
}
// Ensure there's no existing contract already at the designated address.
@@ -479,8 +496,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.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
+ 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 +524,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.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) {
+ err = ErrCodeStoreOutOfGas
+ }
+
+ 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
+ }
+ }
+
+ if err == nil {
+ evm.StateDB.SetCode(address, ret)
}
}
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index fd5fa14cf5..d294324b08 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)
@@ -394,7 +394,14 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow
}
-
+ if evm.chainRules.IsEIP4762 {
+ if transfersValue {
+ gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
@@ -402,6 +409,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+
return gas, nil
}
@@ -414,12 +422,22 @@ 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 {
return 0, ErrGasUintOverflow
}
+ if evm.chainRules.IsEIP4762 {
+ address := common.Address(stack.Back(1).Bytes20())
+ transfersValue := !stack.Back(2).IsZero()
+ if transfersValue {
+ gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index f37ee004dc..10cdd72e0c 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -359,9 +359,9 @@ 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)
-
return nil, nil
}
@@ -434,6 +434,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 {
@@ -583,6 +584,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
if interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64
}
+
// reuse size int for stackvalue
stackvalue := size
@@ -623,6 +625,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)
+
// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
@@ -637,7 +640,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 {
@@ -896,6 +898,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
pushByteSize,
)),
)
+
*pc += size
return nil, nil
}
diff --git a/core/vm/interface.go b/core/vm/interface.go
index 774360a08e..8b2c58898e 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)
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 406927e321..66a20f434e 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 proper 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.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
// 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 65716f9442..5624f47ba7 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,12 @@ func validate(jt JumpTable) JumpTable {
return jt
}
+func newVerkleInstructionSet() JumpTable {
+ instructionSet := newCancunInstructionSet()
+ 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 0000000000..73eb05974d
--- /dev/null
+++ b/core/vm/operations_verkle.go
@@ -0,0 +1,159 @@
+// 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.AccessEvents.SlotGas(contract.Address(), 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.AccessEvents.SlotGas(contract.Address(), 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.AccessEvents.BalanceGas(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()
+ if _, isPrecompile := evm.precompile(address); isPrecompile {
+ return 0, nil
+ }
+ gas := evm.AccessEvents.VersionGas(address, false)
+ gas += evm.AccessEvents.CodeSizeGas(address, false)
+ if gas == 0 {
+ gas = params.WarmStorageReadCostEIP2929
+ }
+ return gas, 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
+ }
+ gas := evm.AccessEvents.CodeHashGas(address, false)
+ if gas == 0 {
+ gas = params.WarmStorageReadCostEIP2929
+ }
+ return gas, 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
+ }
+ if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile {
+ return gas, nil
+ }
+ witnessGas := evm.AccessEvents.MessageCallGas(contract.Address())
+ if witnessGas == 0 {
+ witnessGas = params.WarmStorageReadCostEIP2929
+ }
+ return witnessGas + 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())
+ if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile {
+ return 0, nil
+ }
+ contractAddr := contract.Address()
+ 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)
+ }
+ // Charge write costs if it transfers value
+ if evm.StateDB.GetBalance(contractAddr).Sign() != 0 {
+ statelessGas += evm.AccessEvents.BalanceGas(contractAddr, true)
+ if contractAddr != beneficiaryAddr {
+ statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, true)
+ }
+ }
+ 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.AccessEvents.CodeChunksRangeGas(contract.Address(), 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)
+ if err != nil {
+ return 0, err
+ }
+ addr := common.Address(stack.peek().Bytes20())
+ wgas := evm.AccessEvents.VersionGas(addr, false)
+ wgas += evm.AccessEvents.CodeSizeGas(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 3cd0d82bdf..cf76ae35eb 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 a7b4eb1c13..76e6feccdb 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 534e57831a..871782399d 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 {
@@ -909,6 +914,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
@@ -923,6 +929,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),
@@ -934,11 +941,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,
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,
}
}
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 863cf58ece..d375a96642 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 0000000000..93d4f7cd64
--- /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 efd4dfb5d3..e38d5ac4dc 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 328b2d2527..2a4a632d49 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/utils/verkle_test.go b/trie/utils/verkle_test.go
index 28b059c379..c29504a6d0 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"
)
diff --git a/trie/verkle.go b/trie/verkle.go
index 01d813d9ec..bb0c54857f 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"
)