diff --git a/beacon/light/sync/head_sync_test.go b/beacon/light/sync/head_sync_test.go
index cd7dacf7fe..d095d6a446 100644
--- a/beacon/light/sync/head_sync_test.go
+++ b/beacon/light/sync/head_sync_test.go
@@ -91,7 +91,7 @@ func TestValidatedHead(t *testing.T) {
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
// finality should be requested from both servers
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
- // future period annonced heads should be queued
+ // future period announced heads should be queued
ht.ExpValidated(t, 4, nil)
chain.SetNextSyncPeriod(2)
diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go
index 5a74491c3b..76ebc420ec 100644
--- a/cmd/evm/t8n_test.go
+++ b/cmd/evm/t8n_test.go
@@ -234,7 +234,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition
base: "./testdata/24",
input: t8nInput{
- "alloc.json", "txs.json", "env.json", "Merge", "",
+ "alloc.json", "txs.json", "env.json", "Paris", "",
},
output: t8nOutput{alloc: true, result: true},
expOut: "exp.json",
@@ -242,7 +242,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition where input is missing random
base: "./testdata/24",
input: t8nInput{
- "alloc.json", "txs.json", "env-missingrandom.json", "Merge", "",
+ "alloc.json", "txs.json", "env-missingrandom.json", "Paris", "",
},
output: t8nOutput{alloc: false, result: false},
expExitCode: 3,
@@ -250,7 +250,7 @@ func TestT8n(t *testing.T) {
{ // Test base fee calculation
base: "./testdata/25",
input: t8nInput{
- "alloc.json", "txs.json", "env.json", "Merge", "",
+ "alloc.json", "txs.json", "env.json", "Paris", "",
},
output: t8nOutput{alloc: true, result: true},
expOut: "exp.json",
@@ -378,7 +378,7 @@ func TestT8nTracing(t *testing.T) {
{
base: "./testdata/32",
input: t8nInput{
- "alloc.json", "txs.json", "env.json", "Merge", "",
+ "alloc.json", "txs.json", "env.json", "Paris", "",
},
extraArgs: []string{"--trace", "--trace.callframes"},
expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"},
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/blockchain.go b/core/blockchain.go
index 56e00e85b6..7c8ab3abc4 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -1805,8 +1805,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
}
statedb.SetLogger(bc.logger)
- // Enable prefetching to pull in trie node paths while processing transactions
- statedb.StartPrefetcher("chain")
+ // If we are past Byzantium, enable prefetching to pull in trie node paths
+ // while processing transactions. Before Byzantium the prefetcher is mostly
+ // useless due to the intermediate root hashing after each transaction.
+ if bc.chainConfig.IsByzantium(block.Number()) {
+ statedb.StartPrefetcher("chain")
+ }
activeState = statedb
// If we have a followup block, run that against the current state to pre-cache
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/journal.go b/core/state/journal.go
index c0f5615c98..ad4a654fc6 100644
--- a/core/state/journal.go
+++ b/core/state/journal.go
@@ -131,7 +131,8 @@ type (
storageChange struct {
account *common.Address
key common.Hash
- prevvalue *common.Hash
+ prevvalue common.Hash
+ origvalue common.Hash
}
codeChange struct {
account *common.Address
@@ -278,7 +279,7 @@ func (ch codeChange) copy() journalEntry {
}
func (ch storageChange) revert(s *StateDB) {
- s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue)
+ s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
}
func (ch storageChange) dirtied() *common.Address {
diff --git a/core/state/state_object.go b/core/state/state_object.go
index d75ba01376..3239be368c 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -21,6 +21,7 @@ import (
"fmt"
"io"
"maps"
+ "sync"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -33,6 +34,14 @@ import (
"github.com/holiman/uint256"
)
+// hasherPool holds a pool of hashers used by state objects during concurrent
+// trie updates.
+var hasherPool = sync.Pool{
+ New: func() interface{} {
+ return crypto.NewKeccakState()
+ },
+}
+
type Storage map[common.Hash]common.Hash
func (s Storage) Copy() Storage {
@@ -118,43 +127,54 @@ func (s *stateObject) touch() {
}
}
-// getTrie returns the associated storage trie. The trie will be opened
-// if it's not loaded previously. An error will be returned if trie can't
-// be loaded.
+// getTrie returns the associated storage trie. The trie will be opened if it'
+// not loaded previously. An error will be returned if trie can't be loaded.
+//
+// If a new trie is opened, it will be cached within the state object to allow
+// subsequent reads to expand the same trie instead of reloading from disk.
func (s *stateObject) getTrie() (Trie, error) {
if s.trie == nil {
- // Try fetching from prefetcher first
- if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil {
- // When the miner is creating the pending state, there is no prefetcher
- s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
- }
- if s.trie == nil {
- tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
- if err != nil {
- return nil, err
- }
- s.trie = tr
+ tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
+ if err != nil {
+ return nil, err
}
+ s.trie = tr
}
return s.trie, nil
}
+// getPrefetchedTrie returns the associated trie, as populated by the prefetcher
+// if it's available.
+//
+// Note, opposed to getTrie, this method will *NOT* blindly cache the resulting
+// trie in the state object. The caller might want to do that, but it's cleaner
+// to break the hidden interdependency between retrieving tries from the db or
+// from the prefetcher.
+func (s *stateObject) getPrefetchedTrie() (Trie, error) {
+ // If there's nothing to meaningfully return, let the user figure it out by
+ // pulling the trie from disk.
+ if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil {
+ return nil, nil
+ }
+ // Attempt to retrieve the trie from the pretecher
+ return s.db.prefetcher.trie(s.addrHash, s.data.Root)
+}
+
// GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key)
return value
}
-// getState retrieves a value from the account storage trie and also returns if
-// the slot is already dirty or not.
-func (s *stateObject) getState(key common.Hash) (common.Hash, bool) {
- // If we have a dirty value for this state entry, return it
+// getState retrieves a value associated with the given storage key, along with
+// its original value.
+func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
+ origin := s.GetCommittedState(key)
value, dirty := s.dirtyStorage[key]
if dirty {
- return value, true
+ return value, origin
}
- // Otherwise return the entry's original value
- return s.GetCommittedState(key), false
+ return origin, origin
}
// GetCommittedState retrieves a value from the committed account storage trie.
@@ -219,41 +239,37 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
func (s *stateObject) SetState(key, value common.Hash) {
// If the new value is the same as old, don't set. Otherwise, track only the
// dirty changes, supporting reverting all of it back to no change.
- prev, dirty := s.getState(key)
+ prev, origin := s.getState(key)
if prev == value {
return
}
- var prevvalue *common.Hash
- if dirty {
- prevvalue = &prev
- }
// New value is different, update and journal the change
s.db.journal.append(storageChange{
account: &s.address,
key: key,
- prevvalue: prevvalue,
+ prevvalue: prev,
+ origvalue: origin,
})
if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value)
}
- s.setState(key, &value)
+ s.setState(key, value, origin)
}
-// setState updates a value in account dirty storage. If the value being set is
-// nil (assuming journal revert), the dirtyness is removed.
-func (s *stateObject) setState(key common.Hash, value *common.Hash) {
- // If the first set is being reverted, undo the dirty marker
- if value == nil {
+// setState updates a value in account dirty storage. The dirtiness will be
+// removed if the value being set equals to the original value.
+func (s *stateObject) setState(key common.Hash, value common.Hash, origin common.Hash) {
+ // Storage slot is set back to its original value, undo the dirty marker
+ if value == origin {
delete(s.dirtyStorage, key)
return
}
- // Otherwise restore the previous value
- s.dirtyStorage[key] = *value
+ s.dirtyStorage[key] = value
}
// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
-func (s *stateObject) finalise(prefetch bool) {
+func (s *stateObject) finalise() {
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage {
// If the slot is different from its original value, move it into the
@@ -264,12 +280,14 @@ func (s *stateObject) finalise(prefetch bool) {
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
} else {
// Otherwise, the slot was reverted to its original value, remove it
- // from the pending area to avoid thrashing the data strutures.
+ // from the pending area to avoid thrashing the data structure.
delete(s.pendingStorage, key)
}
}
- if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
- s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch)
+ if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
+ if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
+ log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
+ }
}
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
@@ -288,25 +306,43 @@ func (s *stateObject) finalise(prefetch bool) {
// storage change at all.
func (s *stateObject) updateTrie() (Trie, error) {
// Make sure all dirty slots are finalized into the pending storage area
- s.finalise(false)
+ s.finalise()
// Short circuit if nothing changed, don't bother with hashing anything
if len(s.pendingStorage) == 0 {
return s.trie, nil
}
+ // Retrieve a pretecher populated trie, or fall back to the database
+ tr, err := s.getPrefetchedTrie()
+ switch {
+ case err != nil:
+ // Fetcher retrieval failed, something's very wrong, abort
+ s.db.setError(err)
+ return nil, err
+
+ case tr == nil:
+ // Fetcher not running or empty trie, fallback to the database trie
+ tr, err = s.getTrie()
+ if err != nil {
+ s.db.setError(err)
+ return nil, err
+ }
+
+ default:
+ // Prefetcher returned a live trie, swap it out for the current one
+ s.trie = tr
+ }
// The snapshot storage map for the object
var (
storage map[common.Hash][]byte
origin map[common.Hash][]byte
)
- tr, err := s.getTrie()
- if err != nil {
- s.db.setError(err)
- return nil, err
- }
// Insert all the pending storage updates into the trie
usedStorage := make([][]byte, 0, len(s.pendingStorage))
+ hasher := hasherPool.Get().(crypto.KeccakState)
+ defer hasherPool.Put(hasher)
+
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following:
//
@@ -335,26 +371,30 @@ func (s *stateObject) updateTrie() (Trie, error) {
s.db.setError(err)
return nil, err
}
- s.db.StorageUpdated += 1
+ s.db.StorageUpdated.Add(1)
} else {
deletions = append(deletions, key)
}
// Cache the mutated storage slots until commit
if storage == nil {
+ s.db.storagesLock.Lock()
if storage = s.db.storages[s.addrHash]; storage == nil {
storage = make(map[common.Hash][]byte)
s.db.storages[s.addrHash] = storage
}
+ s.db.storagesLock.Unlock()
}
- khash := crypto.HashData(s.db.hasher, key[:])
+ khash := crypto.HashData(hasher, key[:])
storage[khash] = encoded // encoded will be nil if it's deleted
// Cache the original value of mutated storage slots
if origin == nil {
+ s.db.storagesLock.Lock()
if origin = s.db.storagesOrigin[s.address]; origin == nil {
origin = make(map[common.Hash][]byte)
s.db.storagesOrigin[s.address] = origin
}
+ s.db.storagesLock.Unlock()
}
// Track the original value of slot only if it's mutated first time
if _, ok := origin[khash]; !ok {
@@ -374,7 +414,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
s.db.setError(err)
return nil, err
}
- s.db.StorageDeleted += 1
+ s.db.StorageDeleted.Add(1)
}
// If no slots were touched, issue a warning as we shouldn't have done all
// the above work in the first place
diff --git a/core/state/statedb.go b/core/state/statedb.go
index ac37d4ceeb..0ef52a88f6 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -24,6 +24,7 @@ import (
"slices"
"sort"
"sync"
+ "sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
@@ -37,6 +38,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"
)
@@ -95,10 +97,12 @@ type StateDB struct {
// These maps hold the state changes (including the corresponding
// original value) that occurred in this **block**.
- accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding
+ accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding
+ accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
+
storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format
- accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
+ storagesLock sync.Mutex // Mutex protecting the maps during concurrent updates/commits
// This map holds 'live' objects, which will get modified while
// processing a state transition.
@@ -164,9 +168,9 @@ type StateDB struct {
TrieDBCommits time.Duration
AccountUpdated int
- StorageUpdated int
+ StorageUpdated atomic.Int64
AccountDeleted int
- StorageDeleted int
+ StorageDeleted atomic.Int64
// Testing hooks
onCommit func(states *triestate.Set) // Hook invoked when commit is performed
@@ -213,7 +217,8 @@ func (s *StateDB) SetLogger(l *tracing.Hooks) {
// commit phase, most of the needed data is already hot.
func (s *StateDB) StartPrefetcher(namespace string) {
if s.prefetcher != nil {
- s.prefetcher.close()
+ s.prefetcher.terminate(false)
+ s.prefetcher.report()
s.prefetcher = nil
}
if s.snap != nil {
@@ -225,7 +230,8 @@ func (s *StateDB) StartPrefetcher(namespace string) {
// from the gathered metrics.
func (s *StateDB) StopPrefetcher() {
if s.prefetcher != nil {
- s.prefetcher.close()
+ s.prefetcher.terminate(false)
+ s.prefetcher.report()
s.prefetcher = nil
}
}
@@ -543,9 +549,6 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
// updateStateObject writes the given object to the trie.
func (s *StateDB) updateStateObject(obj *stateObject) {
- // Track the amount of time wasted on updating the account from the trie
- defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
-
// Encode the account and update the account trie
addr := obj.Address()
if err := s.trie.UpdateAccount(addr, &obj.data); err != nil {
@@ -574,10 +577,6 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
// deleteStateObject removes the given object from the state trie.
func (s *StateDB) deleteStateObject(addr common.Address) {
- // Track the amount of time wasted on deleting the account from the trie
- defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
-
- // Delete the account from the trie
if err := s.trie.DeleteAccount(addr); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
}
@@ -742,13 +741,6 @@ func (s *StateDB) Copy() *StateDB {
// in the middle of a transaction.
state.accessList = s.accessList.Copy()
state.transientStorage = s.transientStorage.Copy()
-
- // If there's a prefetcher running, make an inactive copy of it that can
- // only access data but does not actively preload (since the user will not
- // know that they need to explicitly terminate an active copy).
- if s.prefetcher != nil {
- state.prefetcher = s.prefetcher.copy()
- }
return state
}
@@ -819,7 +811,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect)
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
} else {
- obj.finalise(true) // Prefetch slots in the background
+ obj.finalise()
s.markUpdate(addr)
}
// At this point, also ship the address off to the precacher. The precacher
@@ -828,7 +820,9 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
}
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
- s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch)
+ if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch); err != nil {
+ log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
+ }
}
// Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund()
@@ -841,42 +835,52 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Finalise all the dirty storage states and write them into the tries
s.Finalise(deleteEmptyObjects)
- // If there was a trie prefetcher operating, it gets aborted and irrevocably
- // modified after we start retrieving tries. Remove it from the statedb after
- // this round of use.
- //
- // This is weird pre-byzantium since the first tx runs with a prefetcher and
- // the remainder without, but pre-byzantium even the initial prefetcher is
- // useless, so no sleep lost.
- prefetcher := s.prefetcher
+ // If there was a trie prefetcher operating, terminate it async so that the
+ // individual storage tries can be updated as soon as the disk load finishes.
if s.prefetcher != nil {
+ s.prefetcher.terminate(true)
defer func() {
- s.prefetcher.close()
- s.prefetcher = nil
+ s.prefetcher.report()
+ s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
}()
}
- // Although naively it makes sense to retrieve the account trie and then do
- // the contract storage and account updates sequentially, that short circuits
- // the account prefetcher. Instead, let's process all the storage updates
- // first, giving the account prefetches just a few more milliseconds of time
- // to pull useful data from disk.
- start := time.Now()
- for addr, op := range s.mutations {
- if op.applied {
- continue
- }
- if op.isDelete() {
- continue
- }
- s.stateObjects[addr].updateRoot()
+ // Process all storage updates concurrently. The state object update root
+ // method will internally call a blocking trie fetch from the prefetcher,
+ // so there's no need to explicitly wait for the prefetchers to finish.
+ var (
+ start = time.Now()
+ workers errgroup.Group
+ )
+ if s.db.TrieDB().IsVerkle() {
+ // Whilst MPT storage tries are independent, Verkle has one single trie
+ // for all the accounts and all the storage slots merged together. The
+ // former can thus be simply parallelized, but updating the latter will
+ // need concurrency support within the trie itself. That's a TODO for a
+ // later time.
+ workers.SetLimit(1)
}
+ for addr, op := range s.mutations {
+ if op.applied || op.isDelete() {
+ continue
+ }
+ obj := s.stateObjects[addr] // closure for the task runner below
+ workers.Go(func() error {
+ obj.updateRoot()
+ return nil
+ })
+ }
+ workers.Wait()
s.StorageUpdates += time.Since(start)
// Now we're about to start to write changes to the trie. The trie is so far
// _untouched_. We can check with the prefetcher, if it can give us a trie
// which has the same root, but also has some content loaded into it.
- if prefetcher != nil {
- if trie := prefetcher.trie(common.Hash{}, s.originalRoot); trie != nil {
+ start = time.Now()
+
+ if s.prefetcher != nil {
+ if trie, err := s.prefetcher.trie(common.Hash{}, s.originalRoot); err != nil {
+ log.Error("Failed to retrieve account pre-fetcher trie", "err", err)
+ } else if trie != nil {
s.trie = trie
}
}
@@ -912,8 +916,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.deleteStateObject(deletedAddr)
s.AccountDeleted += 1
}
- if prefetcher != nil {
- prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
+ s.AccountUpdates += time.Since(start)
+
+ if s.prefetcher != nil {
+ s.prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
}
// Track the amount of time wasted on hashing the account trie
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
@@ -1254,15 +1260,16 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
return common.Hash{}, err
}
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
- storageUpdatedMeter.Mark(int64(s.StorageUpdated))
+ storageUpdatedMeter.Mark(s.StorageUpdated.Load())
accountDeletedMeter.Mark(int64(s.AccountDeleted))
- storageDeletedMeter.Mark(int64(s.StorageDeleted))
+ storageDeletedMeter.Mark(s.StorageDeleted.Load())
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
s.AccountUpdated, s.AccountDeleted = 0, 0
- s.StorageUpdated, s.StorageDeleted = 0, 0
+ s.StorageUpdated.Store(0)
+ s.StorageDeleted.Store(0)
// If snapshotting is enabled, update the snapshot tree with this new version
if s.snap != nil {
@@ -1327,7 +1334,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 +1449,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/statedb_test.go b/core/state/statedb_test.go
index 71d64f5628..2ce2b868fa 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -1329,3 +1329,47 @@ func TestDeleteStorage(t *testing.T) {
t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes)
}
}
+
+func TestStorageDirtiness(t *testing.T) {
+ var (
+ disk = rawdb.NewMemoryDatabase()
+ tdb = triedb.NewDatabase(disk, nil)
+ db = NewDatabaseWithNodeDB(disk, tdb)
+ state, _ = New(types.EmptyRootHash, db, nil)
+ addr = common.HexToAddress("0x1")
+ checkDirty = func(key common.Hash, value common.Hash, dirty bool) {
+ obj := state.getStateObject(addr)
+ v, exist := obj.dirtyStorage[key]
+ if exist != dirty {
+ t.Fatalf("Unexpected dirty marker, want: %t, got: %t", dirty, exist)
+ }
+ if v != value {
+ t.Fatalf("Unexpected storage slot, want: %t, got: %t", value, v)
+ }
+ }
+ )
+ state.CreateAccount(addr)
+
+ // the storage change is noop, no dirty marker
+ state.SetState(addr, common.Hash{0x1}, common.Hash{})
+ checkDirty(common.Hash{0x1}, common.Hash{}, false)
+
+ // the storage change is valid, dirty marker is expected
+ snap := state.Snapshot()
+ state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
+ checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
+
+ // the storage change is reverted, dirtiness should be revoked
+ state.RevertToSnapshot(snap)
+ checkDirty(common.Hash{0x1}, common.Hash{}, false)
+
+ // the storage is reset back to its original value, dirtiness should be revoked
+ state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
+ snap = state.Snapshot()
+ state.SetState(addr, common.Hash{0x1}, common.Hash{})
+ checkDirty(common.Hash{0x1}, common.Hash{}, false)
+
+ // the storage change is reverted, dirty value should be set back
+ state.RevertToSnapshot(snap)
+ checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
+}
diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go
index c2a49417d4..7e08964e41 100644
--- a/core/state/trie_prefetcher.go
+++ b/core/state/trie_prefetcher.go
@@ -17,6 +17,7 @@
package state
import (
+ "errors"
"sync"
"github.com/ethereum/go-ethereum/common"
@@ -27,6 +28,10 @@ import (
var (
// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
triePrefetchMetricsPrefix = "trie/prefetch/"
+
+ // errTerminated is returned if a fetcher is attempted to be operated after it
+ // has already terminated.
+ errTerminated = errors.New("fetcher is already terminated")
)
// triePrefetcher is an active prefetcher, which receives accounts or storage
@@ -37,160 +42,126 @@ var (
type triePrefetcher struct {
db Database // Database to fetch trie nodes through
root common.Hash // Root hash of the account trie for metrics
- fetches map[string]Trie // Partially or fully fetched tries. Only populated for inactive copies.
fetchers map[string]*subfetcher // Subfetchers for each trie
+ term chan struct{} // Channel to signal interruption
deliveryMissMeter metrics.Meter
accountLoadMeter metrics.Meter
accountDupMeter metrics.Meter
- accountSkipMeter metrics.Meter
accountWasteMeter metrics.Meter
storageLoadMeter metrics.Meter
storageDupMeter metrics.Meter
- storageSkipMeter metrics.Meter
storageWasteMeter metrics.Meter
}
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
prefix := triePrefetchMetricsPrefix + namespace
- p := &triePrefetcher{
+ return &triePrefetcher{
db: db,
root: root,
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
+ term: make(chan struct{}),
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
- accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
- storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
}
- return p
}
-// close iterates over all the subfetchers, aborts any that were left spinning
-// and reports the stats to the metrics subsystem.
-func (p *triePrefetcher) close() {
+// terminate iterates over all the subfetchers and issues a terminateion request
+// to all of them. Depending on the async parameter, the method will either block
+// until all subfetchers spin down, or return immediately.
+func (p *triePrefetcher) terminate(async bool) {
+ // Short circuit if the fetcher is already closed
+ select {
+ case <-p.term:
+ return
+ default:
+ }
+ // Termiante all sub-fetchers, sync or async, depending on the request
for _, fetcher := range p.fetchers {
- fetcher.abort() // safe to do multiple times
-
- if metrics.Enabled {
- if fetcher.root == p.root {
- p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
- p.accountDupMeter.Mark(int64(fetcher.dups))
- p.accountSkipMeter.Mark(int64(len(fetcher.tasks)))
-
- for _, key := range fetcher.used {
- delete(fetcher.seen, string(key))
- }
- p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
- } else {
- p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
- p.storageDupMeter.Mark(int64(fetcher.dups))
- p.storageSkipMeter.Mark(int64(len(fetcher.tasks)))
-
- for _, key := range fetcher.used {
- delete(fetcher.seen, string(key))
- }
- p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
- }
- }
+ fetcher.terminate(async)
}
- // Clear out all fetchers (will crash on a second call, deliberate)
- p.fetchers = nil
+ close(p.term)
}
-// copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data
-// already loaded will be copied over, but no goroutines will be started. This
-// is mostly used in the miner which creates a copy of it's actively mutated
-// state to be sealed while it may further mutate the state.
-func (p *triePrefetcher) copy() *triePrefetcher {
- copy := &triePrefetcher{
- db: p.db,
- root: p.root,
- fetches: make(map[string]Trie), // Active prefetchers use the fetches map
-
- deliveryMissMeter: p.deliveryMissMeter,
- accountLoadMeter: p.accountLoadMeter,
- accountDupMeter: p.accountDupMeter,
- accountSkipMeter: p.accountSkipMeter,
- accountWasteMeter: p.accountWasteMeter,
- storageLoadMeter: p.storageLoadMeter,
- storageDupMeter: p.storageDupMeter,
- storageSkipMeter: p.storageSkipMeter,
- storageWasteMeter: p.storageWasteMeter,
- }
- // If the prefetcher is already a copy, duplicate the data
- if p.fetches != nil {
- for root, fetch := range p.fetches {
- if fetch == nil {
- continue
- }
- copy.fetches[root] = p.db.CopyTrie(fetch)
- }
- return copy
- }
- // Otherwise we're copying an active fetcher, retrieve the current states
- for id, fetcher := range p.fetchers {
- copy.fetches[id] = fetcher.peek()
- }
- return copy
-}
-
-// prefetch schedules a batch of trie items to prefetch.
-func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) {
- // If the prefetcher is an inactive one, bail out
- if p.fetches != nil {
+// report aggregates the pre-fetching and usage metrics and reports them.
+func (p *triePrefetcher) report() {
+ if !metrics.Enabled {
return
}
- // Active fetcher, schedule the retrievals
+ for _, fetcher := range p.fetchers {
+ fetcher.wait() // ensure the fetcher's idle before poking in its internals
+
+ if fetcher.root == p.root {
+ p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
+ p.accountDupMeter.Mark(int64(fetcher.dups))
+ for _, key := range fetcher.used {
+ delete(fetcher.seen, string(key))
+ }
+ p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
+ } else {
+ p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
+ p.storageDupMeter.Mark(int64(fetcher.dups))
+ for _, key := range fetcher.used {
+ delete(fetcher.seen, string(key))
+ }
+ p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
+ }
+ }
+}
+
+// prefetch schedules a batch of trie items to prefetch. After the prefetcher is
+// closed, all the following tasks scheduled will not be executed and an error
+// will be returned.
+//
+// prefetch is called from two locations:
+//
+// 1. Finalize of the state-objects storage roots. This happens at the end
+// of every transaction, meaning that if several transactions touches
+// upon the same contract, the parameters invoking this method may be
+// repeated.
+// 2. Finalize of the main account trie. This happens only once per block.
+func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) error {
+ // Ensure the subfetcher is still alive
+ select {
+ case <-p.term:
+ return errTerminated
+ default:
+ }
id := p.trieID(owner, root)
fetcher := p.fetchers[id]
if fetcher == nil {
fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
p.fetchers[id] = fetcher
}
- fetcher.schedule(keys)
+ return fetcher.schedule(keys)
}
-// trie returns the trie matching the root hash, or nil if the prefetcher doesn't
-// have it.
-func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
- // If the prefetcher is inactive, return from existing deep copies
- id := p.trieID(owner, root)
- if p.fetches != nil {
- trie := p.fetches[id]
- if trie == nil {
- p.deliveryMissMeter.Mark(1)
- return nil
- }
- return p.db.CopyTrie(trie)
- }
- // Otherwise the prefetcher is active, bail if no trie was prefetched for this root
- fetcher := p.fetchers[id]
+// trie returns the trie matching the root hash, blocking until the fetcher of
+// the given trie terminates. If no fetcher exists for the request, nil will be
+// returned.
+func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) (Trie, error) {
+ // Bail if no trie was prefetched for this root
+ fetcher := p.fetchers[p.trieID(owner, root)]
if fetcher == nil {
+ log.Error("Prefetcher missed to load trie", "owner", owner, "root", root)
p.deliveryMissMeter.Mark(1)
- return nil
+ return nil, nil
}
- // Interrupt the prefetcher if it's by any chance still running and return
- // a copy of any pre-loaded trie.
- fetcher.abort() // safe to do multiple times
-
- trie := fetcher.peek()
- if trie == nil {
- p.deliveryMissMeter.Mark(1)
- return nil
- }
- return trie
+ // Subfetcher exists, retrieve its trie
+ return fetcher.peek(), nil
}
// used marks a batch of state items used to allow creating statistics as to
-// how useful or wasteful the prefetcher is.
+// how useful or wasteful the fetcher is.
func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
+ fetcher.wait() // ensure the fetcher's idle before poking in its internals
fetcher.used = used
}
}
@@ -218,10 +189,9 @@ type subfetcher struct {
tasks [][]byte // Items queued up for retrieval
lock sync.Mutex // Lock protecting the task queue
- wake chan struct{} // Wake channel if a new task is scheduled
- stop chan struct{} // Channel to interrupt processing
- term chan struct{} // Channel to signal interruption
- copy chan chan Trie // Channel to request a copy of the current trie
+ wake chan struct{} // Wake channel if a new task is scheduled
+ stop chan struct{} // Channel to interrupt processing
+ term chan struct{} // Channel to signal interruption
seen map[string]struct{} // Tracks the entries already loaded
dups int // Number of duplicate preload tasks
@@ -240,7 +210,6 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
wake: make(chan struct{}, 1),
stop: make(chan struct{}),
term: make(chan struct{}),
- copy: make(chan chan Trie),
seen: make(map[string]struct{}),
}
go sf.loop()
@@ -248,50 +217,61 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
}
// schedule adds a batch of trie keys to the queue to prefetch.
-func (sf *subfetcher) schedule(keys [][]byte) {
+func (sf *subfetcher) schedule(keys [][]byte) error {
+ // Ensure the subfetcher is still alive
+ select {
+ case <-sf.term:
+ return errTerminated
+ default:
+ }
// Append the tasks to the current queue
sf.lock.Lock()
sf.tasks = append(sf.tasks, keys...)
sf.lock.Unlock()
- // Notify the prefetcher, it's fine if it's already terminated
+ // Notify the background thread to execute scheduled tasks
select {
case sf.wake <- struct{}{}:
+ // Wake signal sent
default:
+ // Wake signal not sent as a previous is already queued
}
+ return nil
}
-// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
-// is currently.
+// wait blocks until the subfetcher terminates. This method is used to block on
+// an async termination before accessing internal fields from the fetcher.
+func (sf *subfetcher) wait() {
+ <-sf.term
+}
+
+// peek retrieves the fetcher's trie, populated with any pre-fetched data. The
+// returned trie will be a shallow copy, so modifying it will break subsequent
+// peeks for the original data. The method will block until all the scheduled
+// data has been loaded and the fethcer terminated.
func (sf *subfetcher) peek() Trie {
- ch := make(chan Trie)
- select {
- case sf.copy <- ch:
- // Subfetcher still alive, return copy from it
- return <-ch
-
- case <-sf.term:
- // Subfetcher already terminated, return a copy directly
- if sf.trie == nil {
- return nil
- }
- return sf.db.CopyTrie(sf.trie)
- }
+ // Block until the fertcher terminates, then retrieve the trie
+ sf.wait()
+ return sf.trie
}
-// abort interrupts the subfetcher immediately. It is safe to call abort multiple
-// times but it is not thread safe.
-func (sf *subfetcher) abort() {
+// terminate requests the subfetcher to stop accepting new tasks and spin down
+// as soon as everything is loaded. Depending on the async parameter, the method
+// will either block until all disk loads finish or return immediately.
+func (sf *subfetcher) terminate(async bool) {
select {
case <-sf.stop:
default:
close(sf.stop)
}
+ if async {
+ return
+ }
<-sf.term
}
-// loop waits for new tasks to be scheduled and keeps loading them until it runs
-// out of tasks or its underlying trie is retrieved for committing.
+// loop loads newly-scheduled trie tasks as they are received and loads them, stopping
+// when requested.
func (sf *subfetcher) loop() {
// No matter how the loop stops, signal anyone waiting that it's terminated
defer close(sf.term)
@@ -305,8 +285,6 @@ func (sf *subfetcher) loop() {
}
sf.trie = trie
} else {
- // The trie argument can be nil as verkle doesn't support prefetching
- // yet. TODO FIX IT(rjl493456442), otherwise code will panic here.
trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
@@ -318,48 +296,38 @@ func (sf *subfetcher) loop() {
for {
select {
case <-sf.wake:
- // Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
+ // Execute all remaining tasks in single run
sf.lock.Lock()
tasks := sf.tasks
sf.tasks = nil
sf.lock.Unlock()
- // Prefetch any tasks until the loop is interrupted
- for i, task := range tasks {
- select {
- case <-sf.stop:
- // If termination is requested, add any leftover back and return
- sf.lock.Lock()
- sf.tasks = append(sf.tasks, tasks[i:]...)
- sf.lock.Unlock()
- return
-
- case ch := <-sf.copy:
- // Somebody wants a copy of the current trie, grant them
- ch <- sf.db.CopyTrie(sf.trie)
-
- default:
- // No termination request yet, prefetch the next entry
- if _, ok := sf.seen[string(task)]; ok {
- sf.dups++
- } else {
- if len(task) == common.AddressLength {
- sf.trie.GetAccount(common.BytesToAddress(task))
- } else {
- sf.trie.GetStorage(sf.addr, task)
- }
- sf.seen[string(task)] = struct{}{}
- }
+ for _, task := range tasks {
+ if _, ok := sf.seen[string(task)]; ok {
+ sf.dups++
+ continue
}
+ if len(task) == common.AddressLength {
+ sf.trie.GetAccount(common.BytesToAddress(task))
+ } else {
+ sf.trie.GetStorage(sf.addr, task)
+ }
+ sf.seen[string(task)] = struct{}{}
}
- case ch := <-sf.copy:
- // Somebody wants a copy of the current trie, grant them
- ch <- sf.db.CopyTrie(sf.trie)
-
case <-sf.stop:
- // Termination is requested, abort and leave remaining tasks
- return
+ // Termination is requested, abort if no more tasks are pending. If
+ // there are some, exhaust them first.
+ sf.lock.Lock()
+ done := sf.tasks == nil
+ sf.lock.Unlock()
+
+ if done {
+ return
+ }
+ // Some tasks are pending, loop and pick them up (that wake branch
+ // will be selected eventually, whilst stop remains closed to this
+ // branch will also run afterwards).
}
}
}
diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go
index a616adf98f..d6788fad99 100644
--- a/core/state/trie_prefetcher_test.go
+++ b/core/state/trie_prefetcher_test.go
@@ -19,7 +19,6 @@ package state
import (
"math/big"
"testing"
- "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
@@ -46,68 +45,20 @@ func filledStateDB() *StateDB {
return state
}
-func TestCopyAndClose(t *testing.T) {
+func TestUseAfterTerminate(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa")
- prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- time.Sleep(1 * time.Second)
- a := prefetcher.trie(common.Hash{}, db.originalRoot)
- prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- b := prefetcher.trie(common.Hash{}, db.originalRoot)
- cpy := prefetcher.copy()
- cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- c := cpy.trie(common.Hash{}, db.originalRoot)
- prefetcher.close()
- cpy2 := cpy.copy()
- cpy2.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- d := cpy2.trie(common.Hash{}, db.originalRoot)
- cpy.close()
- cpy2.close()
- if a.Hash() != b.Hash() || a.Hash() != c.Hash() || a.Hash() != d.Hash() {
- t.Fatalf("Invalid trie, hashes should be equal: %v %v %v %v", a.Hash(), b.Hash(), c.Hash(), d.Hash())
- }
-}
-func TestUseAfterClose(t *testing.T) {
- db := filledStateDB()
- prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
- skey := common.HexToHash("aaa")
- prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- a := prefetcher.trie(common.Hash{}, db.originalRoot)
- prefetcher.close()
- b := prefetcher.trie(common.Hash{}, db.originalRoot)
- if a == nil {
- t.Fatal("Prefetching before close should not return nil")
+ if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err != nil {
+ t.Errorf("Prefetch failed before terminate: %v", err)
}
- if b != nil {
- t.Fatal("Trie after close should return nil")
- }
-}
+ prefetcher.terminate(false)
-func TestCopyClose(t *testing.T) {
- db := filledStateDB()
- prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
- skey := common.HexToHash("aaa")
- prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
- cpy := prefetcher.copy()
- a := prefetcher.trie(common.Hash{}, db.originalRoot)
- b := cpy.trie(common.Hash{}, db.originalRoot)
- prefetcher.close()
- c := prefetcher.trie(common.Hash{}, db.originalRoot)
- d := cpy.trie(common.Hash{}, db.originalRoot)
- if a == nil {
- t.Fatal("Prefetching before close should not return nil")
+ if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
+ t.Errorf("Prefetch succeeded after terminate: %v", err)
}
- if b == nil {
- t.Fatal("Copy trie should return nil")
- }
- if c != nil {
- t.Fatal("Trie after close should return nil")
- }
- if d == nil {
- t.Fatal("Copy trie should not return nil")
+ if _, err := prefetcher.trie(common.Hash{}, db.originalRoot); err != nil {
+ t.Errorf("Trie retrieval failed after terminate: %v", err)
}
}
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/instructions_test.go b/core/vm/instructions_test.go
index 8653864d11..e17e913aa3 100644
--- a/core/vm/instructions_test.go
+++ b/core/vm/instructions_test.go
@@ -643,7 +643,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
}
}
-func TestCreate2Addreses(t *testing.T) {
+func TestCreate2Addresses(t *testing.T) {
type testcase struct {
origin string
salt string
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/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go
index 83a13c8cff..7137d29396 100644
--- a/ethdb/dbtest/testsuite.go
+++ b/ethdb/dbtest/testsuite.go
@@ -381,7 +381,7 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
}
})
- t.Run("OperatonsAfterClose", func(t *testing.T) {
+ t.Run("OperationsAfterClose", func(t *testing.T) {
db := New()
db.Put([]byte("key"), []byte("value"))
db.Close()
diff --git a/event/multisub.go b/event/multisub.go
index 5c8d2df48c..1f0af2a292 100644
--- a/event/multisub.go
+++ b/event/multisub.go
@@ -17,7 +17,7 @@
package event
// JoinSubscriptions joins multiple subscriptions to be able to track them as
-// one entity and collectively cancel them of consume any errors from them.
+// one entity and collectively cancel them or consume any errors from them.
func JoinSubscriptions(subs ...Subscription) Subscription {
return NewSubscription(func(unsubbed <-chan struct{}) error {
// Unsubscribe all subscriptions before returning
diff --git a/go.mod b/go.mod
index 4437149e23..57abc1e0e8 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.3
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 b28d64c628..98985cb56a 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.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo=
@@ -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/tests/init.go b/tests/init.go
index e333587a07..c85e714c00 100644
--- a/tests/init.go
+++ b/tests/init.go
@@ -212,7 +212,7 @@ var Forks = map[string]*params.ChainConfig{
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
},
- "ArrowGlacierToMergeAtDiffC0000": {
+ "ArrowGlacierToParisAtDiffC0000": {
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
@@ -246,6 +246,23 @@ var Forks = map[string]*params.ChainConfig{
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
},
+ "Paris": {
+ ChainID: big.NewInt(1),
+ HomesteadBlock: big.NewInt(0),
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ MergeNetsplitBlock: big.NewInt(0),
+ TerminalTotalDifficulty: big.NewInt(0),
+ },
"Merge": {
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
@@ -281,7 +298,7 @@ var Forks = map[string]*params.ChainConfig{
TerminalTotalDifficulty: big.NewInt(0),
ShanghaiTime: u64(0),
},
- "MergeToShanghaiAtTime15k": {
+ "ParisToShanghaiAtTime15k": {
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
diff --git a/tests/state_test.go b/tests/state_test.go
index 6f53b88722..76fec97de0 100644
--- a/tests/state_test.go
+++ b/tests/state_test.go
@@ -54,14 +54,6 @@ func initMatcher(st *testMatcher) {
// Uses 1GB RAM per tested fork
st.skipLoad(`^stStaticCall/static_Call1MB`)
- // These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we
- // no longer delete "leftover storage" when deploying a contract.
- st.skipLoad(`^stSStoreTest/InitCollision\.json`)
- st.skipLoad(`^stRevertTest/RevertInCreateInInit\.json`)
- st.skipLoad(`^stExtCodeHash/dynamicAccountOverwriteEmpty\.json`)
- st.skipLoad(`^stCreate2/create2collisionStorage\.json`)
- st.skipLoad(`^stCreate2/RevertInCreateInInitCreate2\.json`)
-
// Broken tests:
// EOF is not part of cancun
st.skipLoad(`^stEOF/`)
diff --git a/tests/testdata b/tests/testdata
index fa51c5c164..faf33b4714 160000
--- a/tests/testdata
+++ b/tests/testdata
@@ -1 +1 @@
-Subproject commit fa51c5c164f79140730ccb8fe26a46c3d3994338
+Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2
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"
)