Merge branch 'ethereum:master' into portal

This commit is contained in:
Chen Kai 2024-05-14 17:15:43 +08:00 committed by GitHub
commit e695713e50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 1453 additions and 391 deletions

View file

@ -91,7 +91,7 @@ func TestValidatedHead(t *testing.T) {
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4) ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
// finality should be requested from both servers // finality should be requested from both servers
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{}) 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) ht.ExpValidated(t, 4, nil)
chain.SetNextSyncPeriod(2) chain.SetNextSyncPeriod(2)

View file

@ -234,7 +234,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition { // Test post-merge transition
base: "./testdata/24", base: "./testdata/24",
input: t8nInput{ input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merge", "", "alloc.json", "txs.json", "env.json", "Paris", "",
}, },
output: t8nOutput{alloc: true, result: true}, output: t8nOutput{alloc: true, result: true},
expOut: "exp.json", expOut: "exp.json",
@ -242,7 +242,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition where input is missing random { // Test post-merge transition where input is missing random
base: "./testdata/24", base: "./testdata/24",
input: t8nInput{ input: t8nInput{
"alloc.json", "txs.json", "env-missingrandom.json", "Merge", "", "alloc.json", "txs.json", "env-missingrandom.json", "Paris", "",
}, },
output: t8nOutput{alloc: false, result: false}, output: t8nOutput{alloc: false, result: false},
expExitCode: 3, expExitCode: 3,
@ -250,7 +250,7 @@ func TestT8n(t *testing.T) {
{ // Test base fee calculation { // Test base fee calculation
base: "./testdata/25", base: "./testdata/25",
input: t8nInput{ input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merge", "", "alloc.json", "txs.json", "env.json", "Paris", "",
}, },
output: t8nOutput{alloc: true, result: true}, output: t8nOutput{alloc: true, result: true},
expOut: "exp.json", expOut: "exp.json",
@ -378,7 +378,7 @@ func TestT8nTracing(t *testing.T) {
{ {
base: "./testdata/32", base: "./testdata/32",
input: t8nInput{ input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merge", "", "alloc.json", "txs.json", "env.json", "Paris", "",
}, },
extraArgs: []string{"--trace", "--trace.callframes"}, extraArgs: []string{"--trace", "--trace.callframes"},
expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"}, expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"},

View file

@ -103,17 +103,17 @@ func TestAttachWelcome(t *testing.T) {
"--http", "--http.port", httpPort, "--http", "--http.port", httpPort,
"--ws", "--ws.port", wsPort) "--ws", "--ws.port", wsPort)
t.Run("ipc", func(t *testing.T) { 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) testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
}) })
t.Run("http", func(t *testing.T) { t.Run("http", func(t *testing.T) {
endpoint := "http://127.0.0.1:" + httpPort endpoint := "http://127.0.0.1:" + httpPort
waitForEndpoint(t, endpoint, 3*time.Second) waitForEndpoint(t, endpoint, 4*time.Second)
testAttachWelcome(t, geth, endpoint, httpAPIs) testAttachWelcome(t, geth, endpoint, httpAPIs)
}) })
t.Run("ws", func(t *testing.T) { t.Run("ws", func(t *testing.T) {
endpoint := "ws://127.0.0.1:" + wsPort endpoint := "ws://127.0.0.1:" + wsPort
waitForEndpoint(t, endpoint, 3*time.Second) waitForEndpoint(t, endpoint, 4*time.Second)
testAttachWelcome(t, geth, endpoint, httpAPIs) testAttachWelcome(t, geth, endpoint, httpAPIs)
}) })
geth.Kill() geth.Kill()

View file

@ -28,7 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/gballet/go-verkle" "github.com/ethereum/go-verkle"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )

View file

@ -1805,8 +1805,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
} }
statedb.SetLogger(bc.logger) statedb.SetLogger(bc.logger)
// Enable prefetching to pull in trie node paths while processing transactions // If we are past Byzantium, enable prefetching to pull in trie node paths
statedb.StartPrefetcher("chain") // 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 activeState = statedb
// If we have a followup block, run that against the current state to pre-cache // If we have a followup block, run that against the current state to pre-cache

View file

@ -32,7 +32,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/gballet/go-verkle" "github.com/ethereum/go-verkle"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )

View file

@ -64,6 +64,11 @@ var (
// than init code size limit. // than init code size limit.
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded") 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 // ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account. // is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")

320
core/state/access_events.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}

View file

@ -20,7 +20,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/crate-crypto/go-ipa/banderwagon"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -40,11 +39,8 @@ const (
// Cache size granted for caching clean code. // Cache size granted for caching clean code.
codeCacheSize = 64 * 1024 * 1024 codeCacheSize = 64 * 1024 * 1024
// commitmentSize is the size of commitment stored in cache. // Number of address->curve point associations to keep.
commitmentSize = banderwagon.UncompressedSize pointCacheSize = 4096
// Cache item granted for caching commitment results.
commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength)
) )
// Database wraps access to tries and contract code. // Database wraps access to tries and contract code.
@ -67,6 +63,9 @@ type Database interface {
// DiskDB returns the underlying key-value disk database. // DiskDB returns the underlying key-value disk database.
DiskDB() ethdb.KeyValueStore 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 returns the underlying trie database for managing trie nodes.
TrieDB() *triedb.Database TrieDB() *triedb.Database
} }
@ -139,6 +138,9 @@ type Trie interface {
// nodes of the longest existing prefix of the key (at least the root), ending // nodes of the longest existing prefix of the key (at least the root), ending
// with the node that proves the absence of the key. // with the node that proves the absence of the key.
Prove(key []byte, proofDb ethdb.KeyValueWriter) error 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 // 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), codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
triedb: triedb.NewDatabase(db, config), 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), codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
triedb: triedb, triedb: triedb,
pointCache: utils.NewPointCache(pointCacheSize),
} }
} }
@ -175,12 +179,13 @@ type cachingDB struct {
codeSizeCache *lru.Cache[common.Hash, int] codeSizeCache *lru.Cache[common.Hash, int]
codeCache *lru.SizeConstrainedCache[common.Hash, []byte] codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
triedb *triedb.Database triedb *triedb.Database
pointCache *utils.PointCache
} }
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() { 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) tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil { if err != nil {
@ -266,3 +271,8 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore {
func (db *cachingDB) TrieDB() *triedb.Database { func (db *cachingDB) TrieDB() *triedb.Database {
return db.triedb return db.triedb
} }
// PointCache returns the cache of evaluated curve points.
func (db *cachingDB) PointCache() *utils.PointCache {
return db.pointCache
}

View file

@ -131,7 +131,8 @@ type (
storageChange struct { storageChange struct {
account *common.Address account *common.Address
key common.Hash key common.Hash
prevvalue *common.Hash prevvalue common.Hash
origvalue common.Hash
} }
codeChange struct { codeChange struct {
account *common.Address account *common.Address
@ -278,7 +279,7 @@ func (ch codeChange) copy() journalEntry {
} }
func (ch storageChange) revert(s *StateDB) { 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 { func (ch storageChange) dirtied() *common.Address {

View file

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"maps" "maps"
"sync"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -33,6 +34,14 @@ import (
"github.com/holiman/uint256" "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 type Storage map[common.Hash]common.Hash
func (s Storage) Copy() Storage { func (s Storage) Copy() Storage {
@ -118,43 +127,54 @@ func (s *stateObject) touch() {
} }
} }
// getTrie returns the associated storage trie. The trie will be opened // getTrie returns the associated storage trie. The trie will be opened if it'
// if it's not loaded previously. An error will be returned if trie can't // not loaded previously. An error will be returned if trie can't be loaded.
// 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) { func (s *stateObject) getTrie() (Trie, error) {
if s.trie == nil { if s.trie == nil {
// Try fetching from prefetcher first tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil { if err != nil {
// When the miner is creating the pending state, there is no prefetcher return nil, err
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
} }
s.trie = tr
} }
return s.trie, nil 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. // GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(key common.Hash) common.Hash { func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key) value, _ := s.getState(key)
return value return value
} }
// getState retrieves a value from the account storage trie and also returns if // getState retrieves a value associated with the given storage key, along with
// the slot is already dirty or not. // its original value.
func (s *stateObject) getState(key common.Hash) (common.Hash, bool) { func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
// If we have a dirty value for this state entry, return it origin := s.GetCommittedState(key)
value, dirty := s.dirtyStorage[key] value, dirty := s.dirtyStorage[key]
if dirty { if dirty {
return value, true return value, origin
} }
// Otherwise return the entry's original value return origin, origin
return s.GetCommittedState(key), false
} }
// GetCommittedState retrieves a value from the committed account storage trie. // 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) { func (s *stateObject) SetState(key, value common.Hash) {
// If the new value is the same as old, don't set. Otherwise, track only the // 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. // dirty changes, supporting reverting all of it back to no change.
prev, dirty := s.getState(key) prev, origin := s.getState(key)
if prev == value { if prev == value {
return return
} }
var prevvalue *common.Hash
if dirty {
prevvalue = &prev
}
// New value is different, update and journal the change // New value is different, update and journal the change
s.db.journal.append(storageChange{ s.db.journal.append(storageChange{
account: &s.address, account: &s.address,
key: key, key: key,
prevvalue: prevvalue, prevvalue: prev,
origvalue: origin,
}) })
if s.db.logger != nil && s.db.logger.OnStorageChange != nil { if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value) 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 // setState updates a value in account dirty storage. The dirtiness will be
// nil (assuming journal revert), the dirtyness is removed. // removed if the value being set equals to the original value.
func (s *stateObject) setState(key common.Hash, value *common.Hash) { func (s *stateObject) setState(key common.Hash, value common.Hash, origin common.Hash) {
// If the first set is being reverted, undo the dirty marker // Storage slot is set back to its original value, undo the dirty marker
if value == nil { if value == origin {
delete(s.dirtyStorage, key) delete(s.dirtyStorage, key)
return 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 // 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. // 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)) slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage { for key, value := range s.dirtyStorage {
// If the slot is different from its original value, move it into the // 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 slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
} else { } else {
// Otherwise, the slot was reverted to its original value, remove it // 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) delete(s.pendingStorage, key)
} }
} }
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch) 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 { if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage) s.dirtyStorage = make(Storage)
@ -288,25 +306,43 @@ func (s *stateObject) finalise(prefetch bool) {
// storage change at all. // storage change at all.
func (s *stateObject) updateTrie() (Trie, error) { func (s *stateObject) updateTrie() (Trie, error) {
// Make sure all dirty slots are finalized into the pending storage area // 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 // Short circuit if nothing changed, don't bother with hashing anything
if len(s.pendingStorage) == 0 { if len(s.pendingStorage) == 0 {
return s.trie, nil 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 // The snapshot storage map for the object
var ( var (
storage map[common.Hash][]byte storage map[common.Hash][]byte
origin 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 // Insert all the pending storage updates into the trie
usedStorage := make([][]byte, 0, len(s.pendingStorage)) 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 // Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following: // in circumstances similar to the following:
// //
@ -335,26 +371,30 @@ func (s *stateObject) updateTrie() (Trie, error) {
s.db.setError(err) s.db.setError(err)
return nil, err return nil, err
} }
s.db.StorageUpdated += 1 s.db.StorageUpdated.Add(1)
} else { } else {
deletions = append(deletions, key) deletions = append(deletions, key)
} }
// Cache the mutated storage slots until commit // Cache the mutated storage slots until commit
if storage == nil { if storage == nil {
s.db.storagesLock.Lock()
if storage = s.db.storages[s.addrHash]; storage == nil { if storage = s.db.storages[s.addrHash]; storage == nil {
storage = make(map[common.Hash][]byte) storage = make(map[common.Hash][]byte)
s.db.storages[s.addrHash] = storage 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 storage[khash] = encoded // encoded will be nil if it's deleted
// Cache the original value of mutated storage slots // Cache the original value of mutated storage slots
if origin == nil { if origin == nil {
s.db.storagesLock.Lock()
if origin = s.db.storagesOrigin[s.address]; origin == nil { if origin = s.db.storagesOrigin[s.address]; origin == nil {
origin = make(map[common.Hash][]byte) origin = make(map[common.Hash][]byte)
s.db.storagesOrigin[s.address] = origin s.db.storagesOrigin[s.address] = origin
} }
s.db.storagesLock.Unlock()
} }
// Track the original value of slot only if it's mutated first time // Track the original value of slot only if it's mutated first time
if _, ok := origin[khash]; !ok { if _, ok := origin[khash]; !ok {
@ -374,7 +414,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
s.db.setError(err) s.db.setError(err)
return nil, 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 // If no slots were touched, issue a warning as we shouldn't have done all
// the above work in the first place // the above work in the first place

View file

@ -24,6 +24,7 @@ import (
"slices" "slices"
"sort" "sort"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -37,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@ -95,10 +97,12 @@ type StateDB struct {
// These maps hold the state changes (including the corresponding // These maps hold the state changes (including the corresponding
// original value) that occurred in this **block**. // 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 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 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 // This map holds 'live' objects, which will get modified while
// processing a state transition. // processing a state transition.
@ -164,9 +168,9 @@ type StateDB struct {
TrieDBCommits time.Duration TrieDBCommits time.Duration
AccountUpdated int AccountUpdated int
StorageUpdated int StorageUpdated atomic.Int64
AccountDeleted int AccountDeleted int
StorageDeleted int StorageDeleted atomic.Int64
// Testing hooks // Testing hooks
onCommit func(states *triestate.Set) // Hook invoked when commit is performed 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. // commit phase, most of the needed data is already hot.
func (s *StateDB) StartPrefetcher(namespace string) { func (s *StateDB) StartPrefetcher(namespace string) {
if s.prefetcher != nil { if s.prefetcher != nil {
s.prefetcher.close() s.prefetcher.terminate(false)
s.prefetcher.report()
s.prefetcher = nil s.prefetcher = nil
} }
if s.snap != nil { if s.snap != nil {
@ -225,7 +230,8 @@ func (s *StateDB) StartPrefetcher(namespace string) {
// from the gathered metrics. // from the gathered metrics.
func (s *StateDB) StopPrefetcher() { func (s *StateDB) StopPrefetcher() {
if s.prefetcher != nil { if s.prefetcher != nil {
s.prefetcher.close() s.prefetcher.terminate(false)
s.prefetcher.report()
s.prefetcher = nil 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. // updateStateObject writes the given object to the trie.
func (s *StateDB) updateStateObject(obj *stateObject) { 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 // Encode the account and update the account trie
addr := obj.Address() addr := obj.Address()
if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { 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. // deleteStateObject removes the given object from the state trie.
func (s *StateDB) deleteStateObject(addr common.Address) { 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 { if err := s.trie.DeleteAccount(addr); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) 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. // in the middle of a transaction.
state.accessList = s.accessList.Copy() state.accessList = s.accessList.Copy()
state.transientStorage = s.transientStorage.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 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.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) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
} else { } else {
obj.finalise(true) // Prefetch slots in the background obj.finalise()
s.markUpdate(addr) s.markUpdate(addr)
} }
// At this point, also ship the address off to the precacher. The precacher // 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 addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
} }
if s.prefetcher != nil && len(addressesToPrefetch) > 0 { 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. // Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund() 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 // Finalise all the dirty storage states and write them into the tries
s.Finalise(deleteEmptyObjects) s.Finalise(deleteEmptyObjects)
// If there was a trie prefetcher operating, it gets aborted and irrevocably // If there was a trie prefetcher operating, terminate it async so that the
// modified after we start retrieving tries. Remove it from the statedb after // individual storage tries can be updated as soon as the disk load finishes.
// 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 s.prefetcher != nil { if s.prefetcher != nil {
s.prefetcher.terminate(true)
defer func() { defer func() {
s.prefetcher.close() s.prefetcher.report()
s.prefetcher = nil s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
}() }()
} }
// Although naively it makes sense to retrieve the account trie and then do // Process all storage updates concurrently. The state object update root
// the contract storage and account updates sequentially, that short circuits // method will internally call a blocking trie fetch from the prefetcher,
// the account prefetcher. Instead, let's process all the storage updates // so there's no need to explicitly wait for the prefetchers to finish.
// first, giving the account prefetches just a few more milliseconds of time var (
// to pull useful data from disk. start = time.Now()
start := time.Now() workers errgroup.Group
for addr, op := range s.mutations { )
if op.applied { if s.db.TrieDB().IsVerkle() {
continue // Whilst MPT storage tries are independent, Verkle has one single trie
} // for all the accounts and all the storage slots merged together. The
if op.isDelete() { // former can thus be simply parallelized, but updating the latter will
continue // need concurrency support within the trie itself. That's a TODO for a
} // later time.
s.stateObjects[addr].updateRoot() 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) s.StorageUpdates += time.Since(start)
// Now we're about to start to write changes to the trie. The trie is so far // 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 // _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. // which has the same root, but also has some content loaded into it.
if prefetcher != nil { start = time.Now()
if trie := prefetcher.trie(common.Hash{}, s.originalRoot); trie != nil {
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 s.trie = trie
} }
} }
@ -912,8 +916,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.deleteStateObject(deletedAddr) s.deleteStateObject(deletedAddr)
s.AccountDeleted += 1 s.AccountDeleted += 1
} }
if prefetcher != nil { s.AccountUpdates += time.Since(start)
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
if s.prefetcher != nil {
s.prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
} }
// Track the amount of time wasted on hashing the account trie // Track the amount of time wasted on hashing the account trie
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) 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 return common.Hash{}, err
} }
accountUpdatedMeter.Mark(int64(s.AccountUpdated)) accountUpdatedMeter.Mark(int64(s.AccountUpdated))
storageUpdatedMeter.Mark(int64(s.StorageUpdated)) storageUpdatedMeter.Mark(s.StorageUpdated.Load())
accountDeletedMeter.Mark(int64(s.AccountDeleted)) accountDeletedMeter.Mark(int64(s.AccountDeleted))
storageDeletedMeter.Mark(int64(s.StorageDeleted)) storageDeletedMeter.Mark(s.StorageDeleted.Load())
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated)) accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
s.AccountUpdated, s.AccountDeleted = 0, 0 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 snapshotting is enabled, update the snapshot tree with this new version
if s.snap != nil { 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) // - Add coinbase to access list (EIP-3651)
// - Reset transient storage (EIP-1153) // - 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) { 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 // Clear out any leftover from previous executions
al := newAccessList() al := newAccessList()
s.accessList = al s.accessList = al
@ -1439,3 +1449,7 @@ func (s *StateDB) markUpdate(addr common.Address) {
s.mutations[addr].applied = false s.mutations[addr].applied = false
s.mutations[addr].typ = update s.mutations[addr].typ = update
} }
func (s *StateDB) PointCache() *utils.PointCache {
return s.db.PointCache()
}

View file

@ -1329,3 +1329,47 @@ func TestDeleteStorage(t *testing.T) {
t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes) 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)
}

View file

@ -17,6 +17,7 @@
package state package state
import ( import (
"errors"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -27,6 +28,10 @@ import (
var ( var (
// triePrefetchMetricsPrefix is the prefix under which to publish the metrics. // triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
triePrefetchMetricsPrefix = "trie/prefetch/" 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 // triePrefetcher is an active prefetcher, which receives accounts or storage
@ -37,160 +42,126 @@ var (
type triePrefetcher struct { type triePrefetcher struct {
db Database // Database to fetch trie nodes through db Database // Database to fetch trie nodes through
root common.Hash // Root hash of the account trie for metrics 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 fetchers map[string]*subfetcher // Subfetchers for each trie
term chan struct{} // Channel to signal interruption
deliveryMissMeter metrics.Meter deliveryMissMeter metrics.Meter
accountLoadMeter metrics.Meter accountLoadMeter metrics.Meter
accountDupMeter metrics.Meter accountDupMeter metrics.Meter
accountSkipMeter metrics.Meter
accountWasteMeter metrics.Meter accountWasteMeter metrics.Meter
storageLoadMeter metrics.Meter storageLoadMeter metrics.Meter
storageDupMeter metrics.Meter storageDupMeter metrics.Meter
storageSkipMeter metrics.Meter
storageWasteMeter metrics.Meter storageWasteMeter metrics.Meter
} }
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher { func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
prefix := triePrefetchMetricsPrefix + namespace prefix := triePrefetchMetricsPrefix + namespace
p := &triePrefetcher{ return &triePrefetcher{
db: db, db: db,
root: root, root: root,
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
term: make(chan struct{}),
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil), accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil), accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil), accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil), storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil), storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
} }
return p
} }
// close iterates over all the subfetchers, aborts any that were left spinning // terminate iterates over all the subfetchers and issues a terminateion request
// and reports the stats to the metrics subsystem. // to all of them. Depending on the async parameter, the method will either block
func (p *triePrefetcher) close() { // 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 { for _, fetcher := range p.fetchers {
fetcher.abort() // safe to do multiple times fetcher.terminate(async)
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)))
}
}
} }
// Clear out all fetchers (will crash on a second call, deliberate) close(p.term)
p.fetchers = nil
} }
// copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data // report aggregates the pre-fetching and usage metrics and reports them.
// already loaded will be copied over, but no goroutines will be started. This func (p *triePrefetcher) report() {
// is mostly used in the miner which creates a copy of it's actively mutated if !metrics.Enabled {
// 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 {
return 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) id := p.trieID(owner, root)
fetcher := p.fetchers[id] fetcher := p.fetchers[id]
if fetcher == nil { if fetcher == nil {
fetcher = newSubfetcher(p.db, p.root, owner, root, addr) fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
p.fetchers[id] = fetcher 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 // trie returns the trie matching the root hash, blocking until the fetcher of
// have it. // the given trie terminates. If no fetcher exists for the request, nil will be
func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie { // returned.
// If the prefetcher is inactive, return from existing deep copies func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) (Trie, error) {
id := p.trieID(owner, root) // Bail if no trie was prefetched for this root
if p.fetches != nil { fetcher := p.fetchers[p.trieID(owner, root)]
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]
if fetcher == nil { if fetcher == nil {
log.Error("Prefetcher missed to load trie", "owner", owner, "root", root)
p.deliveryMissMeter.Mark(1) p.deliveryMissMeter.Mark(1)
return nil return nil, nil
} }
// Interrupt the prefetcher if it's by any chance still running and return // Subfetcher exists, retrieve its trie
// a copy of any pre-loaded trie. return fetcher.peek(), nil
fetcher.abort() // safe to do multiple times
trie := fetcher.peek()
if trie == nil {
p.deliveryMissMeter.Mark(1)
return nil
}
return trie
} }
// used marks a batch of state items used to allow creating statistics as to // 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) { func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil { 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 fetcher.used = used
} }
} }
@ -218,10 +189,9 @@ type subfetcher struct {
tasks [][]byte // Items queued up for retrieval tasks [][]byte // Items queued up for retrieval
lock sync.Mutex // Lock protecting the task queue lock sync.Mutex // Lock protecting the task queue
wake chan struct{} // Wake channel if a new task is scheduled wake chan struct{} // Wake channel if a new task is scheduled
stop chan struct{} // Channel to interrupt processing stop chan struct{} // Channel to interrupt processing
term chan struct{} // Channel to signal interruption term chan struct{} // Channel to signal interruption
copy chan chan Trie // Channel to request a copy of the current trie
seen map[string]struct{} // Tracks the entries already loaded seen map[string]struct{} // Tracks the entries already loaded
dups int // Number of duplicate preload tasks 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), wake: make(chan struct{}, 1),
stop: make(chan struct{}), stop: make(chan struct{}),
term: make(chan struct{}), term: make(chan struct{}),
copy: make(chan chan Trie),
seen: make(map[string]struct{}), seen: make(map[string]struct{}),
} }
go sf.loop() 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. // 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 // Append the tasks to the current queue
sf.lock.Lock() sf.lock.Lock()
sf.tasks = append(sf.tasks, keys...) sf.tasks = append(sf.tasks, keys...)
sf.lock.Unlock() sf.lock.Unlock()
// Notify the prefetcher, it's fine if it's already terminated // Notify the background thread to execute scheduled tasks
select { select {
case sf.wake <- struct{}{}: case sf.wake <- struct{}{}:
// Wake signal sent
default: 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 // wait blocks until the subfetcher terminates. This method is used to block on
// is currently. // 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 { func (sf *subfetcher) peek() Trie {
ch := make(chan Trie) // Block until the fertcher terminates, then retrieve the trie
select { sf.wait()
case sf.copy <- ch: return sf.trie
// 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)
}
} }
// abort interrupts the subfetcher immediately. It is safe to call abort multiple // terminate requests the subfetcher to stop accepting new tasks and spin down
// times but it is not thread safe. // as soon as everything is loaded. Depending on the async parameter, the method
func (sf *subfetcher) abort() { // will either block until all disk loads finish or return immediately.
func (sf *subfetcher) terminate(async bool) {
select { select {
case <-sf.stop: case <-sf.stop:
default: default:
close(sf.stop) close(sf.stop)
} }
if async {
return
}
<-sf.term <-sf.term
} }
// loop waits for new tasks to be scheduled and keeps loading them until it runs // loop loads newly-scheduled trie tasks as they are received and loads them, stopping
// out of tasks or its underlying trie is retrieved for committing. // when requested.
func (sf *subfetcher) loop() { func (sf *subfetcher) loop() {
// No matter how the loop stops, signal anyone waiting that it's terminated // No matter how the loop stops, signal anyone waiting that it's terminated
defer close(sf.term) defer close(sf.term)
@ -305,8 +285,6 @@ func (sf *subfetcher) loop() {
} }
sf.trie = trie sf.trie = trie
} else { } 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) trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
if err != nil { if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
@ -318,48 +296,38 @@ func (sf *subfetcher) loop() {
for { for {
select { select {
case <-sf.wake: 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() sf.lock.Lock()
tasks := sf.tasks tasks := sf.tasks
sf.tasks = nil sf.tasks = nil
sf.lock.Unlock() sf.lock.Unlock()
// Prefetch any tasks until the loop is interrupted for _, task := range tasks {
for i, task := range tasks { if _, ok := sf.seen[string(task)]; ok {
select { sf.dups++
case <-sf.stop: continue
// 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{}{}
}
} }
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: case <-sf.stop:
// Termination is requested, abort and leave remaining tasks // Termination is requested, abort if no more tasks are pending. If
return // 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).
} }
} }
} }

View file

@ -19,7 +19,6 @@ package state
import ( import (
"math/big" "math/big"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -46,68 +45,20 @@ func filledStateDB() *StateDB {
return state return state
} }
func TestCopyAndClose(t *testing.T) { func TestUseAfterTerminate(t *testing.T) {
db := filledStateDB() db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "") prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa") 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) { if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err != nil {
db := filledStateDB() t.Errorf("Prefetch failed before terminate: %v", err)
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 b != nil { prefetcher.terminate(false)
t.Fatal("Trie after close should return nil")
}
}
func TestCopyClose(t *testing.T) { if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
db := filledStateDB() t.Errorf("Prefetch succeeded after terminate: %v", err)
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 b == nil { if _, err := prefetcher.trie(common.Hash{}, db.originalRoot); err != nil {
t.Fatal("Copy trie should return nil") t.Errorf("Trie retrieval failed after terminate: %v", err)
}
if c != nil {
t.Fatal("Trie after close should return nil")
}
if d == nil {
t.Fatal("Copy trie should not return nil")
} }
} }

View file

@ -482,7 +482,7 @@ func TestProcessVerkle(t *testing.T) {
txCost1 := params.TxGas txCost1 := params.TxGas
txCost2 := params.TxGas txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */) contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(293644 /* execution costs */) codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */)
blockGasUsagesExpected := []uint64{ blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2, txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,

View file

@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
} }
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // 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 // Set the starting gas for the raw transaction
var gas uint64 var gas uint64
if isContractCreation && isHomestead { if isContractCreation && isHomestead {
@ -405,6 +405,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} }
st.gasRemaining -= gas 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 // Check clause 6
value, overflow := uint256.FromBig(msg.Value) value, overflow := uint256.FromBig(msg.Value)
if overflow { if overflow {
@ -458,6 +466,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
fee := new(uint256.Int).SetUint64(st.gasUsed()) fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256) fee.Mul(fee, effectiveTipU256)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) 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{ return &ExecutionResult{

View file

@ -298,6 +298,12 @@ const (
GasChangeCallStorageColdAccess GasChangeReason = 13 GasChangeCallStorageColdAccess GasChangeReason = 13
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
GasChangeCallFailedExecution GasChangeReason = 14 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 // 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. // it will be "manually" tracked by a direct emit of the gas change event.

View file

@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
return common.RightPadBytes(data[start:end], int(size)) 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. // toWordSize returns the ceiled word size required for memory expansion.
func toWordSize(size uint64) uint64 { func toWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 { if size > math.MaxUint64-31 {

View file

@ -57,6 +57,9 @@ type Contract struct {
CodeAddr *common.Address CodeAddr *common.Address
Input []byte Input []byte
// is the execution frame represented by this object a contract deployment
IsDeployment bool
Gas uint64 Gas uint64
value *uint256.Int value *uint256.Int
} }

View file

@ -137,6 +137,8 @@ var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
var PrecompiledContractsBLS = PrecompiledContractsPrague var PrecompiledContractsBLS = PrecompiledContractsPrague
var PrecompiledContractsVerkle = PrecompiledContractsPrague
var ( var (
PrecompiledAddressesPrague []common.Address PrecompiledAddressesPrague []common.Address
PrecompiledAddressesCancun []common.Address PrecompiledAddressesCancun []common.Address

View file

@ -18,9 +18,11 @@ package vm
import ( import (
"fmt" "fmt"
"math"
"sort" "sort"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -37,6 +39,7 @@ var activators = map[int]func(*JumpTable){
1884: enable1884, 1884: enable1884,
1344: enable1344, 1344: enable1344,
1153: enable1153, 1153: enable1153,
4762: enable4762,
} }
// EnableEIP enables the given EIP on the config. // EnableEIP enables the given EIP on the config.
@ -319,3 +322,214 @@ func enable6780(jt *JumpTable) {
maxStack: maxStack(1, 0), 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),
}
}
}

View file

@ -22,6 +22,7 @@ import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "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/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -42,6 +43,8 @@ type (
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract var precompiles map[common.Address]PrecompiledContract
switch { switch {
case evm.chainRules.IsVerkle:
precompiles = PrecompiledContractsVerkle
case evm.chainRules.IsPrague: case evm.chainRules.IsPrague:
precompiles = PrecompiledContractsPrague precompiles = PrecompiledContractsPrague
case evm.chainRules.IsCancun: case evm.chainRules.IsCancun:
@ -85,10 +88,11 @@ type BlockContext struct {
// All fields can change between transactions. // All fields can change between transactions.
type TxContext struct { type TxContext struct {
// Message information // Message information
Origin common.Address // Provides information for ORIGIN 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) 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 BlobHashes []common.Hash // Provides information for BLOBHASH
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set 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 // 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 // Reset resets the EVM with a new transaction context.Reset
// This is not threadsafe and should only be done very cautiously. // This is not threadsafe and should only be done very cautiously.
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
if evm.chainRules.IsEIP4762 {
txCtx.AccessEvents = state.NewAccessEvents(statedb.PointCache())
}
evm.TxContext = txCtx evm.TxContext = txCtx
evm.StateDB = statedb evm.StateDB = statedb
} }
@ -200,6 +207,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
p, isPrecompile := evm.precompile(addr) p, isPrecompile := evm.precompile(addr)
if !evm.StateDB.Exist(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() { if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// Calling a non-existing account, don't do anything. // Calling a non-existing account, don't do anything.
return nil, gas, nil 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 // 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. // creation fails, the access-list change should not be rolled back.
if evm.chainRules.IsBerlin { if evm.chainRules.IsEIP2929 {
evm.StateDB.AddAddressToAccessList(address) evm.StateDB.AddAddressToAccessList(address)
} }
// Ensure there's no existing contract already at the designated 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(address), value, gas) contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash) 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. // Check whether the max code size has been exceeded, assign err if the case.
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { 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 // be stored due to not enough gas set an error and let it be handled
// by the error checking condition below. // by the error checking condition below.
if err == nil { if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas if !evm.chainRules.IsEIP4762 {
if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { createDataGas := uint64(len(ret)) * params.CreateDataGas
evm.StateDB.SetCode(address, ret) if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
err = ErrCodeStoreOutOfGas
}
} else { } 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)
} }
} }

View file

@ -383,7 +383,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
} else if !evm.StateDB.Exist(address) { } else if !evm.StateDB.Exist(address) {
gas += params.CallNewAccountGas gas += params.CallNewAccountGas
} }
if transfersValue { if transfersValue && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas gas += params.CallValueTransferGas
} }
memoryGas, err := memoryGasCost(mem, memorySize) 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 { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow 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)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err 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 { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -414,12 +422,22 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
gas uint64 gas uint64
overflow bool overflow bool
) )
if stack.Back(2).Sign() != 0 { if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas gas += params.CallValueTransferGas
} }
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow 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)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err

View file

@ -359,9 +359,9 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if overflow { if overflow {
uint64CodeOffset = math.MaxUint64 uint64CodeOffset = math.MaxUint64
} }
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
return nil, nil return nil, nil
} }
@ -434,6 +434,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
num.Clear() num.Clear()
return nil, nil return nil, nil
} }
var upper, lower uint64 var upper, lower uint64
upper = interpreter.evm.Context.BlockNumber.Uint64() upper = interpreter.evm.Context.BlockNumber.Uint64()
if upper < 257 { if upper < 257 {
@ -583,6 +584,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
if interpreter.evm.chainRules.IsEIP150 { if interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64 gas -= gas / 64
} }
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
@ -623,6 +625,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas gas = scope.Contract.Gas
) )
// Apply EIP150 // Apply EIP150
gas -= gas / 64 gas -= gas / 64
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) 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()) stackvalue.SetBytes(addr.Bytes())
} }
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
@ -896,6 +898,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
pushByteSize, pushByteSize,
)), )),
) )
*pc += size *pc += size
return nil, nil return nil, nil
} }

View file

@ -643,7 +643,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
} }
} }
func TestCreate2Addreses(t *testing.T) { func TestCreate2Addresses(t *testing.T) {
type testcase struct { type testcase struct {
origin string origin string
salt string salt string

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256" "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 // 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 // even if the feature/fork is not active yet
AddSlotToAccessList(addr common.Address, slot common.Hash) 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) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
RevertToSnapshot(int) RevertToSnapshot(int)

View file

@ -99,6 +99,9 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
// If jump table was not initialised we set the default one. // If jump table was not initialised we set the default one.
var table *JumpTable var table *JumpTable
switch { switch {
case evm.chainRules.IsVerkle:
// TODO replace with proper instruction set when fork is specified
table = &verkleInstructionSet
case evm.chainRules.IsCancun: case evm.chainRules.IsCancun:
table = &cancunInstructionSet table = &cancunInstructionSet
case evm.chainRules.IsShanghai: case evm.chainRules.IsShanghai:
@ -219,6 +222,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// Capture pre-execution values for tracing. // Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas 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 // Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation. // enough stack items available to perform the operation.
op = contract.GetOp(pc) op = contract.GetOp(pc)

View file

@ -57,6 +57,7 @@ var (
mergeInstructionSet = newMergeInstructionSet() mergeInstructionSet = newMergeInstructionSet()
shanghaiInstructionSet = newShanghaiInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet()
cancunInstructionSet = newCancunInstructionSet() cancunInstructionSet = newCancunInstructionSet()
verkleInstructionSet = newVerkleInstructionSet()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
@ -80,6 +81,12 @@ func validate(jt JumpTable) JumpTable {
return jt return jt
} }
func newVerkleInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet()
enable4762(&instructionSet)
return validate(instructionSet)
}
func newCancunInstructionSet() JumpTable { func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet() instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 := New()
db.Put([]byte("key"), []byte("value")) db.Put([]byte("key"), []byte("value"))
db.Close() db.Close()

View file

@ -17,7 +17,7 @@
package event package event
// JoinSubscriptions joins multiple subscriptions to be able to track them as // 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 { func JoinSubscriptions(subs ...Subscription) Subscription {
return NewSubscription(func(unsubbed <-chan struct{}) error { return NewSubscription(func(unsubbed <-chan struct{}) error {
// Unsubscribe all subscriptions before returning // Unsubscribe all subscriptions before returning

4
go.mod
View file

@ -15,20 +15,20 @@ require (
github.com/cloudflare/cloudflare-go v0.79.0 github.com/cloudflare/cloudflare-go v0.79.0
github.com/cockroachdb/pebble v1.1.0 github.com/cockroachdb/pebble v1.1.0
github.com/consensys/gnark-crypto v0.12.1 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/crate-crypto/go-kzg-4844 v1.0.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set/v2 v2.1.0 github.com/deckarep/golang-set/v2 v2.1.0
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
github.com/ethereum/c-kzg-4844 v1.0.0 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/fatih/color v1.13.0
github.com/ferranbt/fastssz v0.1.3 github.com/ferranbt/fastssz v0.1.3
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e
github.com/fjl/memsize v0.0.2 github.com/fjl/memsize v0.0.2
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff 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/gofrs/flock v0.8.1
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.4 github.com/golang/protobuf v1.5.4

8
go.sum
View file

@ -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/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 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I=
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/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 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= 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= 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/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 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= 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 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo= 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/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 h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= 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/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 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ=

View file

@ -581,6 +581,11 @@ func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) 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 // CheckCompatible checks whether scheduled fork transitions have been imported
// with a mismatching chain configuration. // with a mismatching chain configuration.
func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError { func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError {
@ -909,6 +914,7 @@ func (err *ConfigCompatError) Error() string {
type Rules struct { type Rules struct {
ChainID *big.Int ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsEIP2929, IsEIP4762 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon bool IsBerlin, IsLondon bool
IsMerge, IsShanghai, IsCancun, IsPrague 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 // disallow setting Merge out of order
isMerge = isMerge && c.IsLondon(num) isMerge = isMerge && c.IsLondon(num)
isVerkle := isMerge && c.IsVerkle(num, timestamp)
return Rules{ return Rules{
ChainID: new(big.Int).Set(chainID), ChainID: new(big.Int).Set(chainID),
IsHomestead: c.IsHomestead(num), IsHomestead: c.IsHomestead(num),
@ -934,11 +941,13 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsPetersburg: c.IsPetersburg(num), IsPetersburg: c.IsPetersburg(num),
IsIstanbul: c.IsIstanbul(num), IsIstanbul: c.IsIstanbul(num),
IsBerlin: c.IsBerlin(num), IsBerlin: c.IsBerlin(num),
IsEIP2929: c.IsBerlin(num) && !isVerkle,
IsLondon: c.IsLondon(num), IsLondon: c.IsLondon(num),
IsMerge: isMerge, IsMerge: isMerge,
IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsShanghai: isMerge && c.IsShanghai(num, timestamp),
IsCancun: isMerge && c.IsCancun(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp),
IsPrague: isMerge && c.IsPrague(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp),
IsVerkle: isMerge && c.IsVerkle(num, timestamp), IsVerkle: isVerkle,
IsEIP4762: isVerkle,
} }
} }

View file

@ -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. 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. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
Create2Gas uint64 = 32000 // Once per CREATE2 operation Create2Gas uint64 = 32000 // Once per CREATE2 operation
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. 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. 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.

36
params/verkle_params.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -212,7 +212,7 @@ var Forks = map[string]*params.ChainConfig{
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0),
}, },
"ArrowGlacierToMergeAtDiffC0000": { "ArrowGlacierToParisAtDiffC0000": {
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0), EIP150Block: big.NewInt(0),
@ -246,6 +246,23 @@ var Forks = map[string]*params.ChainConfig{
ArrowGlacierBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: 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": { "Merge": {
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
@ -281,7 +298,7 @@ var Forks = map[string]*params.ChainConfig{
TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0),
ShanghaiTime: u64(0), ShanghaiTime: u64(0),
}, },
"MergeToShanghaiAtTime15k": { "ParisToShanghaiAtTime15k": {
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0), HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0), EIP150Block: big.NewInt(0),

View file

@ -54,14 +54,6 @@ func initMatcher(st *testMatcher) {
// Uses 1GB RAM per tested fork // Uses 1GB RAM per tested fork
st.skipLoad(`^stStaticCall/static_Call1MB`) 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: // Broken tests:
// EOF is not part of cancun // EOF is not part of cancun
st.skipLoad(`^stEOF/`) st.skipLoad(`^stEOF/`)

@ -1 +1 @@
Subproject commit fa51c5c164f79140730ccb8fe26a46c3d3994338 Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2

View file

@ -284,3 +284,7 @@ func (t *StateTrie) getSecKeyCache() map[string][]byte {
} }
return t.secKeyCache return t.secKeyCache
} }
func (t *StateTrie) IsVerkle() bool {
return false
}

View file

@ -23,7 +23,7 @@ import (
"github.com/crate-crypto/go-ipa/bandersnatch/fr" "github.com/crate-crypto/go-ipa/bandersnatch/fr"
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/gballet/go-verkle" "github.com/ethereum/go-verkle"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -219,7 +219,7 @@ func CodeChunkKey(address []byte, chunk *uint256.Int) []byte {
return GetTreeKey(address, treeIndex, subIndex) 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. // If the storage slot is in the header, we need to add the header offset.
var key uint256.Int var key uint256.Int
key.SetBytes(bytes) 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 // StorageSlotKey returns the verkle tree key of the storage slot for the
// specified account. // specified account.
func StorageSlotKey(address []byte, storageKey []byte) []byte { func StorageSlotKey(address []byte, storageKey []byte) []byte {
treeIndex, subIndex := storageIndex(storageKey) treeIndex, subIndex := StorageIndex(storageKey)
return GetTreeKey(address, treeIndex, subIndex) 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 // slot for the specified account. The difference between StorageSlotKey is the
// address evaluation is already computed to minimize the computational overhead. // address evaluation is already computed to minimize the computational overhead.
func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte { func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte {
treeIndex, subIndex := storageIndex(storageKey) treeIndex, subIndex := StorageIndex(storageKey)
return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex)
} }

View file

@ -20,7 +20,7 @@ import (
"bytes" "bytes"
"testing" "testing"
"github.com/gballet/go-verkle" "github.com/ethereum/go-verkle"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )

View file

@ -27,7 +27,7 @@ import (
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb/database" "github.com/ethereum/go-ethereum/triedb/database"
"github.com/gballet/go-verkle" "github.com/ethereum/go-verkle"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )