consensus/beacon,core, params, trie: verkle-compatible access list

Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>
This commit is contained in:
Guillaume Ballet 2024-03-12 12:01:14 +01:00
parent ebf9e11af2
commit 361a1a6686
23 changed files with 874 additions and 194 deletions

View file

@ -348,9 +348,9 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H
} }
// Finalize implements consensus.Engine and processes withdrawals on top. // Finalize implements consensus.Engine and processes withdrawals on top.
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
if !beacon.IsPoSHeader(header) { if !beacon.IsPoSHeader(header) {
beacon.ethone.Finalize(chain, header, state, txs, uncles, nil) beacon.ethone.Finalize(chain, header, statedb, txs, uncles, nil)
return return
} }
// Withdrawals processing. // Withdrawals processing.
@ -358,7 +358,11 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// Convert amount from gwei to wei. // Convert amount from gwei to wei.
amount := new(uint256.Int).SetUint64(w.Amount) amount := new(uint256.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, uint256.NewInt(params.GWei)) amount = amount.Mul(amount, uint256.NewInt(params.GWei))
state.AddBalance(w.Address, amount) statedb.AddBalance(w.Address, amount)
if chain.Config().IsVerkle(header.Number, header.Time) {
statedb.Witness().AddAddress(w.Address, state.ALAllItems, state.AccessListWrite)
}
} }
// No block reward which is issued by consensus layer instead. // No block reward which is issued by consensus layer instead.
} }

View file

@ -67,6 +67,10 @@ 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 does not
// have enough funds to cover the witness access costs.
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")

View file

@ -18,22 +18,62 @@ package state
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
) )
type accessList struct { type ALAccessMode bool
var (
AccessListRead = ALAccessMode(false)
AccessListWrite = ALAccessMode(true)
)
type ALAccountItem uint64
const (
ALVersion = ALAccountItem(1 << iota)
ALBalance
ALNonce
ALCodeHash
ALCodeSize
ALLastHeaderItem
)
const ALAllItems = ALVersion | ALBalance | ALNonce | ALCodeSize | ALCodeHash
type AccessList interface {
ContainsAddress(address common.Address) bool
Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool)
Copy() AccessList
AddAddress(address common.Address, items ALAccountItem, isWrite ALAccessMode) uint64
AddSlot(address common.Address, slot common.Hash, isWrite ALAccessMode) uint64
DeleteSlot(address common.Address, slot common.Hash)
DeleteAddress(address common.Address)
TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64
TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64
TouchTxOriginAndComputeGas(originAddr []byte) uint64
TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64
TouchAddressOnReadAndComputeGas(addr []byte, index uint256.Int, suffix byte) uint64
Merge(AccessList)
Keys() [][]byte
}
type accessList2929 struct {
addresses map[common.Address]int addresses map[common.Address]int
slots []map[common.Hash]struct{} slots []map[common.Hash]struct{}
} }
// ContainsAddress returns true if the address is in the access list. // ContainsAddress returns true if the address is in the access list.
func (al *accessList) ContainsAddress(address common.Address) bool { func (al *accessList2929) ContainsAddress(address common.Address) bool {
_, ok := al.addresses[address] _, ok := al.addresses[address]
return ok return ok
} }
// Contains checks if a slot within an account is present in the access list, returning // Contains checks if a slot within an account is present in the access list, returning
// separate flags for the presence of the account and the slot respectively. // separate flags for the presence of the account and the slot respectively.
func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { func (al *accessList2929) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
idx, ok := al.addresses[address] idx, ok := al.addresses[address]
if !ok { if !ok {
// no such address (and hence zero slots) // no such address (and hence zero slots)
@ -48,15 +88,15 @@ func (al *accessList) Contains(address common.Address, slot common.Hash) (addres
} }
// newAccessList creates a new accessList. // newAccessList creates a new accessList.
func newAccessList() *accessList { func newAccessList() AccessList {
return &accessList{ return &accessList2929{
addresses: make(map[common.Address]int), addresses: make(map[common.Address]int),
} }
} }
// Copy creates an independent copy of an accessList. // Copy creates an independent copy of an accessList.
func (a *accessList) Copy() *accessList { func (a *accessList2929) Copy() AccessList {
cp := newAccessList() cp := newAccessList().(*accessList2929)
for k, v := range a.addresses { for k, v := range a.addresses {
cp.addresses[k] = v cp.addresses[k] = v
} }
@ -73,12 +113,12 @@ func (a *accessList) Copy() *accessList {
// AddAddress adds an address to the access list, and returns 'true' if the operation // AddAddress adds an address to the access list, and returns 'true' if the operation
// caused a change (addr was not previously in the list). // caused a change (addr was not previously in the list).
func (al *accessList) AddAddress(address common.Address) bool { func (al *accessList2929) AddAddress(address common.Address, _ ALAccountItem, _ ALAccessMode) uint64 {
if _, present := al.addresses[address]; present { if _, present := al.addresses[address]; present {
return false return params.WarmStorageReadCostEIP2929
} }
al.addresses[address] = -1 al.addresses[address] = -1
return true return params.ColdAccountAccessCostEIP2929
} }
// AddSlot adds the specified (addr, slot) combo to the access list. // AddSlot adds the specified (addr, slot) combo to the access list.
@ -86,31 +126,31 @@ func (al *accessList) AddAddress(address common.Address) bool {
// - address added // - address added
// - slot added // - slot added
// For any 'true' value returned, a corresponding journal entry must be made. // For any 'true' value returned, a corresponding journal entry must be made.
func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { func (al *accessList2929) AddSlot(address common.Address, slot common.Hash, _ ALAccessMode) (gas uint64) {
idx, addrPresent := al.addresses[address] idx, addrPresent := al.addresses[address]
if !addrPresent || idx == -1 { if !addrPresent || idx == -1 {
// Address not present, or addr present but no slots there // Address not present, or addr present but no slots there
al.addresses[address] = len(al.slots) al.addresses[address] = len(al.slots)
slotmap := map[common.Hash]struct{}{slot: {}} slotmap := map[common.Hash]struct{}{slot: {}}
al.slots = append(al.slots, slotmap) al.slots = append(al.slots, slotmap)
return !addrPresent, true return params.WarmStorageReadCostEIP2929
} }
// There is already an (address,slot) mapping // There is already an (address,slot) mapping
slotmap := al.slots[idx] slotmap := al.slots[idx]
if _, ok := slotmap[slot]; !ok { if _, ok := slotmap[slot]; !ok {
slotmap[slot] = struct{}{} slotmap[slot] = struct{}{}
// Journal add slot change // Journal add slot change
return false, true return params.ColdSloadCostEIP2929
} }
// No changes required // No changes required
return false, false return params.WarmStorageReadCostEIP2929
} }
// DeleteSlot removes an (address, slot)-tuple from the access list. // DeleteSlot removes an (address, slot)-tuple from the access list.
// This operation needs to be performed in the same order as the addition happened. // This operation needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of // This method is meant to be used by the journal, which maintains ordering of
// operations. // operations.
func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { func (al *accessList2929) DeleteSlot(address common.Address, slot common.Hash) {
idx, addrOk := al.addresses[address] idx, addrOk := al.addresses[address]
// There are two ways this can fail // There are two ways this can fail
if !addrOk { if !addrOk {
@ -131,6 +171,29 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
// needs to be performed in the same order as the addition happened. // needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of // This method is meant to be used by the journal, which maintains ordering of
// operations. // operations.
func (al *accessList) DeleteAddress(address common.Address) { func (al *accessList2929) DeleteAddress(address common.Address) {
delete(al.addresses, address) delete(al.addresses, address)
} }
func (al *accessList2929) TouchAndChargeValueTransfer(callerAddr []byte, targetAddr []byte) uint64 {
return 0
}
func (al *accessList2929) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 {
return 0
}
func (al *accessList2929) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
return 0
}
func (al *accessList2929) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 {
return 0
}
func (al *accessList2929) TouchAddressOnReadAndComputeGas(addr []byte, index uint256.Int, subIndex byte) uint64 {
return 0
}
func (al *accessList2929) Merge(other AccessList) {}
func (al *accessList2929) Keys() [][]byte { return nil }

View file

@ -0,0 +1,260 @@
// 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 (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
// mode specifies how a tree location has been accessed
// for the byte value:
// * the first bit is set if the branch has been edited
// * the second bit is set if the branch has been read
type mode byte
const (
AccessWitnessReadFlag = mode(1)
AccessWitnessWriteFlag = mode(2)
)
var zeroTreeIndex uint256.Int
// AccessWitness lists the locations of the state that are being accessed
// during the production of a block.
type AccessWitness struct {
branches map[branchAccessKey]mode
chunks map[chunkAccessKey]mode
pointCache *utils.PointCache
}
func NewAccessWitness() *AccessWitness {
return &AccessWitness{
branches: make(map[branchAccessKey]mode),
chunks: make(map[chunkAccessKey]mode),
pointCache: utils.NewPointCache(),
}
}
// Merge is used to merge the witness that got generated during the execution
// of a tx, with the accumulation of witnesses that were generated during the
// execution of all the txs preceding this one in a given block.
func (aw *AccessWitness) Merge(o AccessList) {
other := o.(*AccessWitness)
for k := range other.branches {
aw.branches[k] |= other.branches[k]
}
for k, chunk := range other.chunks {
aw.chunks[k] |= chunk
}
}
// Key returns, predictably, the list of keys that were touched during the
// buildup of the access witness.
func (aw *AccessWitness) Keys() [][]byte {
// TODO: consider if parallelizing this is worth it, probably depending on len(aw.chunks).
keys := make([][]byte, 0, len(aw.chunks))
for chunk := range aw.chunks {
basePoint := aw.pointCache.GetTreeKeyHeader(chunk.addr[:])
key := utils.GetTreeKeyWithEvaluatedAddess(basePoint, &chunk.treeIndex, chunk.leafKey)
keys = append(keys, key)
}
return keys
}
func (aw *AccessWitness) Copy() AccessList {
naw := &AccessWitness{
branches: make(map[branchAccessKey]mode),
chunks: make(map[chunkAccessKey]mode),
pointCache: aw.pointCache,
}
naw.Merge(aw)
return naw
}
func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, AccessListWrite)
gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, AccessListWrite)
return gas
}
// TouchAndChargeContractCreateInit charges access costs to initiate
// a contract creation
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, AccessListWrite)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, AccessListWrite)
// FIXME note that this is incompatible with the spec
if createSendsValue {
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, AccessListWrite)
}
return gas
}
func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, AccessListRead)
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, AccessListRead)
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, AccessListRead)
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, AccessListWrite)
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, AccessListWrite)
// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
// so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling.
// This is the reason why we return 0 instead of `gas`.
// Note that we still have to touch the addresses to make sure the witness is correct.
return 0
}
func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, AccessListRead)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, AccessListRead)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, AccessListRead)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, AccessListRead)
if sendsValue {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, AccessListWrite)
} else {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, AccessListRead)
}
// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
// so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling.
// This is the reason why we return 0 instead of `gas`.
// Note that we still have to touch the addresses to make sure the witness is correct.
return 0
}
func (aw *AccessWitness) TouchAddressOnReadAndComputeGas(addr []byte, treeIndex uint256.Int, subIndex byte) uint64 {
return aw.touchAddressAndChargeGas(addr, treeIndex, subIndex, AccessListRead)
}
func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite ALAccessMode) uint64 {
stemRead, suffixRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite)
var gas uint64
if stemRead {
gas += params.WitnessBranchReadCost
}
if suffixRead {
gas += params.WitnessChunkReadCost
}
if stemWrite {
gas += params.WitnessBranchWriteCost
}
if selectorWrite {
gas += params.WitnessChunkWriteCost
}
if selectorFill {
gas += params.WitnessChunkFillCost
}
return gas
}
// touchAddress adds any missing access event to the witness.
func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite ALAccessMode) (bool, bool, bool, bool, bool) {
branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex)
// Read access.
var branchRead, chunkRead bool
if _, hasStem := aw.branches[branchKey]; !hasStem {
branchRead = true
aw.branches[branchKey] = AccessWitnessReadFlag
}
if _, hasSelector := aw.chunks[chunkKey]; !hasSelector {
chunkRead = true
aw.chunks[chunkKey] = AccessWitnessReadFlag
}
// Write access.
var branchWrite, chunkWrite, chunkFill bool
if isWrite {
if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
branchWrite = true
aw.branches[branchKey] |= AccessWitnessWriteFlag
}
chunkValue := aw.chunks[chunkKey]
if (chunkValue & AccessWitnessWriteFlag) == 0 {
chunkWrite = true
aw.chunks[chunkKey] |= AccessWitnessWriteFlag
}
// TODO: charge chunk filling costs if the leaf was previously empty in the state
}
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
}
type branchAccessKey struct {
addr common.Address
treeIndex uint256.Int
}
func newBranchAccessKey(addr []byte, treeIndex uint256.Int) branchAccessKey {
var sk branchAccessKey
copy(sk.addr[20-len(addr):], addr)
sk.treeIndex = treeIndex
return sk
}
type chunkAccessKey struct {
branchAccessKey
leafKey byte
}
func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
var lk chunkAccessKey
lk.branchAccessKey = branchKey
lk.leafKey = leafKey
return lk
}
func (aw *AccessWitness) AddAddress(addr common.Address, items ALAccountItem, isWrite ALAccessMode) uint64 {
var gas uint64
for index := utils.VersionLeafKey; index <= utils.CodeSizeLeafKey; index++ {
if (1<<index)&items != 0 {
gas += aw.touchAddressAndChargeGas(addr[:], zeroTreeIndex, byte(index), isWrite)
}
}
return gas
}
func (aw *AccessWitness) AddSlot(address common.Address, slot common.Hash, isWrite ALAccessMode) uint64 {
treeIndex, suffix := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes())
return aw.touchAddressAndChargeGas(address[:], *treeIndex, suffix, isWrite)
}
func (aw *AccessWitness) DeleteSlot(address common.Address, slot common.Hash) {
// Should remain in the witness
}
func (aw *AccessWitness) DeleteAddress(address common.Address) {
// Should remain in the witness
}
func (aw *AccessWitness) ContainsAddress(address common.Address) bool {
panic("not implemented") // TODO: Implement
}
func (aw *AccessWitness) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
panic("not implemented") // TODO: Implement
}

View file

@ -180,7 +180,7 @@ type cachingDB struct {
// 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, utils.NewPointCache())
} }
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil { if err != nil {

View file

@ -99,11 +99,14 @@ type StateDB struct {
preimages map[common.Hash][]byte preimages map[common.Hash][]byte
// Per-transaction access list // Per-transaction access list
accessList *accessList accessList AccessList
// Transient storage // Transient storage
transientStorage transientStorage transientStorage transientStorage
// Global witness
witness AccessList
// Journal of state modifications. This is the backbone of // Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot. // Snapshot and RevertToSnapshot.
journal *journal journal *journal
@ -1275,26 +1278,30 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
// - 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.IsBerlin {
if !rules.IsVerkle {
s.accessList = newAccessList()
} else {
s.accessList = NewAccessWitness()
}
// Clear out any leftover from previous executions // Clear out any leftover from previous executions
al := newAccessList() al := s.accessList
s.accessList = al
al.AddAddress(sender) al.AddAddress(sender, ALAllItems, AccessListRead)
if dst != nil { if dst != nil {
al.AddAddress(*dst) al.AddAddress(*dst, ALAllItems, AccessListRead)
// If it's a create-tx, the destination will be added inside evm.create // If it's a create-tx, the destination will be added inside evm.create
} }
for _, addr := range precompiles { for _, addr := range precompiles {
al.AddAddress(addr) al.AddAddress(addr, ALAllItems, AccessListRead)
} }
for _, el := range list { for _, el := range list {
al.AddAddress(el.Address) al.AddAddress(el.Address, 0, AccessListRead)
for _, key := range el.StorageKeys { for _, key := range el.StorageKeys {
al.AddSlot(el.Address, key) al.AddSlot(el.Address, key, AccessListRead)
} }
} }
if rules.IsShanghai { // EIP-3651: warm coinbase if rules.IsShanghai { // EIP-3651: warm coinbase
al.AddAddress(coinbase) al.AddAddress(coinbase, ALAllItems, AccessListRead)
} }
} }
// Reset transient storage at the beginning of transaction execution // Reset transient storage at the beginning of transaction execution
@ -1302,38 +1309,25 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d
} }
// AddAddressToAccessList adds the given address to the access list // AddAddressToAccessList adds the given address to the access list
func (s *StateDB) AddAddressToAccessList(addr common.Address) { func (s *StateDB) AddAddressToAccessList(addr common.Address, mode ALAccountItem, isWrite ALAccessMode) uint64 {
if s.accessList.AddAddress(addr) { gas := s.accessList.AddAddress(addr, mode, isWrite)
if gas > 0 {
s.journal.append(accessListAddAccountChange{&addr}) s.journal.append(accessListAddAccountChange{&addr})
} }
return gas
} }
// AddSlotToAccessList adds the given (address, slot)-tuple to the access list // AddSlotToAccessList adds the given (address, slot)-tuple to the access list
func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash, isWrite ALAccessMode) uint64 {
addrMod, slotMod := s.accessList.AddSlot(addr, slot) gas := s.accessList.AddSlot(addr, slot, isWrite)
if addrMod {
// In practice, this should not happen, since there is no way to enter the if gas > params.WarmStorageReadCostEIP2929 {
// scope of 'address' without having the 'address' become already added
// to the access list (via call-variant, create, etc).
// Better safe than sorry, though
s.journal.append(accessListAddAccountChange{&addr})
}
if slotMod {
s.journal.append(accessListAddSlotChange{ s.journal.append(accessListAddSlotChange{
address: &addr, address: &addr,
slot: &slot, slot: &slot,
}) })
} }
} return gas
// AddressInAccessList returns true if the given address is in the access list.
func (s *StateDB) AddressInAccessList(addr common.Address) bool {
return s.accessList.ContainsAddress(addr)
}
// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list.
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
return s.accessList.Contains(addr, slot)
} }
// convertAccountSet converts a provided account set from address keyed to hash keyed. // convertAccountSet converts a provided account set from address keyed to hash keyed.
@ -1370,3 +1364,45 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.
} }
return copied return copied
} }
func (s *StateDB) TouchTxOriginAndComputeGas(addr common.Address) uint64 {
return s.accessList.TouchTxOriginAndComputeGas(addr.Bytes())
}
func (s *StateDB) TouchTxExistingAndComputeGas(addr common.Address, sendsValue bool) uint64 {
return s.accessList.TouchTxExistingAndComputeGas(addr.Bytes(), sendsValue)
}
func (s *StateDB) TouchAndChargeContractCreateInit(addr common.Address, sendsValue bool) uint64 {
return s.accessList.TouchAndChargeContractCreateInit(addr.Bytes(), sendsValue)
}
func (s *StateDB) TouchAndChargeValueTransfer(from, to common.Address) uint64 {
return s.accessList.TouchAndChargeValueTransfer(from.Bytes(), to.Bytes())
}
func (s *StateDB) TouchAddressOnReadAndComputeGas(from common.Address, index uint64) uint64 {
treeIndex := uint256.NewInt((index + 128) / 256)
subIndex := byte((index + 128) % 256)
return s.accessList.TouchAddressOnReadAndComputeGas(from.Bytes(), *treeIndex, subIndex)
}
func (s *StateDB) MergeTxAccessListIntoBlockWitness() {
if _, ok := s.accessList.(*AccessWitness); ok {
s.witness.Merge(s.accessList)
}
}
// addressInAccessList returns true if the given address is in the access list.
func (s *StateDB) addressInAccessList(addr common.Address) bool {
return s.accessList.ContainsAddress(addr)
}
// slotInAccessList returns true if the given (address, slot)-tuple is in the access list.
func (s *StateDB) slotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
return s.accessList.Contains(addr, slot)
}
func (s *StateDB) Witness() AccessList {
return s.witness
}

View file

@ -345,14 +345,14 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
{ {
name: "AddAddressToAccessList", name: "AddAddressToAccessList",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.AddAddressToAccessList(addr) s.AddAddressToAccessList(addr, 0, false)
}, },
}, },
{ {
name: "AddSlotToAccessList", name: "AddSlotToAccessList",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.AddSlotToAccessList(addr, s.AddSlotToAccessList(addr,
common.Hash{byte(a.args[0])}) common.Hash{byte(a.args[0])}, false)
}, },
args: make([]int64, 1), args: make([]int64, 1),
}, },
@ -879,19 +879,19 @@ func TestStateDBAccessList(t *testing.T) {
} }
// Check that the given addresses are in the access list // Check that the given addresses are in the access list
for _, address := range addresses { for _, address := range addresses {
if !state.AddressInAccessList(address) { if !state.addressInAccessList(address) {
t.Fatalf("expected %x to be in access list", address) t.Fatalf("expected %x to be in access list", address)
} }
} }
// Check that only the expected addresses are present in the access list // Check that only the expected addresses are present in the access list
for address := range state.accessList.addresses { for address := range state.accessList.(*accessList2929).addresses {
if _, exist := addressMap[address]; !exist { if _, exist := addressMap[address]; !exist {
t.Fatalf("extra address %x in access list", address) t.Fatalf("extra address %x in access list", address)
} }
} }
} }
verifySlots := func(addrString string, slotStrings ...string) { verifySlots := func(addrString string, slotStrings ...string) {
if !state.AddressInAccessList(addr(addrString)) { if !state.addressInAccessList(addr(addrString)) {
t.Fatalf("scope missing address/slots %v", addrString) t.Fatalf("scope missing address/slots %v", addrString)
} }
var address = addr(addrString) var address = addr(addrString)
@ -905,14 +905,14 @@ func TestStateDBAccessList(t *testing.T) {
} }
// Check that the expected items are in the access list // Check that the expected items are in the access list
for i, s := range slots { for i, s := range slots {
if _, slotPresent := state.SlotInAccessList(address, s); !slotPresent { if _, slotPresent := state.slotInAccessList(address, s); !slotPresent {
t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString) t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString)
} }
} }
// Check that no extra elements are in the access list // Check that no extra elements are in the access list
index := state.accessList.addresses[address] index := state.accessList.(*accessList2929).addresses[address]
if index >= 0 { if index >= 0 {
stateSlots := state.accessList.slots[index] stateSlots := state.accessList.(*accessList2929).slots[index]
for s := range stateSlots { for s := range stateSlots {
if _, slotPresent := slotMap[s]; !slotPresent { if _, slotPresent := slotMap[s]; !slotPresent {
t.Fatalf("scope has extra slot %v (address %v)", s, addrString) t.Fatalf("scope has extra slot %v (address %v)", s, addrString)
@ -921,9 +921,9 @@ func TestStateDBAccessList(t *testing.T) {
} }
} }
state.AddAddressToAccessList(addr("aa")) // 1 state.AddAddressToAccessList(addr("aa"), 0, false) // 1
state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3 state.AddSlotToAccessList(addr("bb"), slot("01"), false) // 2,3
state.AddSlotToAccessList(addr("bb"), slot("02")) // 4 state.AddSlotToAccessList(addr("bb"), slot("02"), false) // 4
verifyAddrs("aa", "bb") verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02") verifySlots("bb", "01", "02")
@ -934,17 +934,17 @@ func TestStateDBAccessList(t *testing.T) {
} }
// same again, should cause no journal entries // same again, should cause no journal entries
state.AddSlotToAccessList(addr("bb"), slot("01")) state.AddSlotToAccessList(addr("bb"), slot("01"), false)
state.AddSlotToAccessList(addr("bb"), slot("02")) state.AddSlotToAccessList(addr("bb"), slot("02"), false)
state.AddAddressToAccessList(addr("aa")) state.AddAddressToAccessList(addr("aa"), 0, false)
if exp, got := 4, state.journal.length(); exp != got { if exp, got := 4, state.journal.length(); exp != got {
t.Fatalf("journal length mismatch: have %d, want %d", got, exp) t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
} }
// some new ones // some new ones
state.AddSlotToAccessList(addr("bb"), slot("03")) // 5 state.AddSlotToAccessList(addr("bb"), slot("03"), false) // 5
state.AddSlotToAccessList(addr("aa"), slot("01")) // 6 state.AddSlotToAccessList(addr("aa"), slot("01"), false) // 6
state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8 state.AddSlotToAccessList(addr("cc"), slot("01"), false) // 7,8
state.AddAddressToAccessList(addr("cc")) state.AddAddressToAccessList(addr("cc"), 0, false)
if exp, got := 8, state.journal.length(); exp != got { if exp, got := 8, state.journal.length(); exp != got {
t.Fatalf("journal length mismatch: have %d, want %d", got, exp) t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
} }
@ -956,7 +956,7 @@ func TestStateDBAccessList(t *testing.T) {
// now start rolling back changes // now start rolling back changes
state.journal.revert(state, 7) state.journal.revert(state, 7)
if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok { if _, ok := state.slotInAccessList(addr("cc"), slot("01")); ok {
t.Fatalf("slot present, expected missing") t.Fatalf("slot present, expected missing")
} }
verifyAddrs("aa", "bb", "cc") verifyAddrs("aa", "bb", "cc")
@ -964,7 +964,7 @@ func TestStateDBAccessList(t *testing.T) {
verifySlots("bb", "01", "02", "03") verifySlots("bb", "01", "02", "03")
state.journal.revert(state, 6) state.journal.revert(state, 6)
if state.AddressInAccessList(addr("cc")) { if state.addressInAccessList(addr("cc")) {
t.Fatalf("addr present, expected missing") t.Fatalf("addr present, expected missing")
} }
verifyAddrs("aa", "bb") verifyAddrs("aa", "bb")
@ -972,46 +972,46 @@ func TestStateDBAccessList(t *testing.T) {
verifySlots("bb", "01", "02", "03") verifySlots("bb", "01", "02", "03")
state.journal.revert(state, 5) state.journal.revert(state, 5)
if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok { if _, ok := state.slotInAccessList(addr("aa"), slot("01")); ok {
t.Fatalf("slot present, expected missing") t.Fatalf("slot present, expected missing")
} }
verifyAddrs("aa", "bb") verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02", "03") verifySlots("bb", "01", "02", "03")
state.journal.revert(state, 4) state.journal.revert(state, 4)
if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok { if _, ok := state.slotInAccessList(addr("bb"), slot("03")); ok {
t.Fatalf("slot present, expected missing") t.Fatalf("slot present, expected missing")
} }
verifyAddrs("aa", "bb") verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02") verifySlots("bb", "01", "02")
state.journal.revert(state, 3) state.journal.revert(state, 3)
if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok { if _, ok := state.slotInAccessList(addr("bb"), slot("02")); ok {
t.Fatalf("slot present, expected missing") t.Fatalf("slot present, expected missing")
} }
verifyAddrs("aa", "bb") verifyAddrs("aa", "bb")
verifySlots("bb", "01") verifySlots("bb", "01")
state.journal.revert(state, 2) state.journal.revert(state, 2)
if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok { if _, ok := state.slotInAccessList(addr("bb"), slot("01")); ok {
t.Fatalf("slot present, expected missing") t.Fatalf("slot present, expected missing")
} }
verifyAddrs("aa", "bb") verifyAddrs("aa", "bb")
state.journal.revert(state, 1) state.journal.revert(state, 1)
if state.AddressInAccessList(addr("bb")) { if state.addressInAccessList(addr("bb")) {
t.Fatalf("addr present, expected missing") t.Fatalf("addr present, expected missing")
} }
verifyAddrs("aa") verifyAddrs("aa")
state.journal.revert(state, 0) state.journal.revert(state, 0)
if state.AddressInAccessList(addr("aa")) { if state.addressInAccessList(addr("aa")) {
t.Fatalf("addr present, expected missing") t.Fatalf("addr present, expected missing")
} }
if got, exp := len(state.accessList.addresses), 0; got != exp { if got, exp := len(state.accessList.(*accessList2929).addresses), 0; got != exp {
t.Fatalf("expected empty, got %d", got) t.Fatalf("expected empty, got %d", got)
} }
if got, exp := len(state.accessList.slots), 0; got != exp { if got, exp := len(state.accessList.(*accessList2929).slots), 0; got != exp {
t.Fatalf("expected empty, got %d", got) t.Fatalf("expected empty, got %d", got)
} }
// Check the copy // Check the copy
@ -1019,10 +1019,10 @@ func TestStateDBAccessList(t *testing.T) {
state = stateCopy1 state = stateCopy1
verifyAddrs("aa", "bb") verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02") verifySlots("bb", "01", "02")
if got, exp := len(state.accessList.addresses), 2; got != exp { if got, exp := len(state.accessList.(*accessList2929).addresses), 2; got != exp {
t.Fatalf("expected empty, got %d", got) t.Fatalf("expected empty, got %d", got)
} }
if got, exp := len(state.accessList.slots), 1; got != exp { if got, exp := len(state.accessList.(*accessList2929).slots), 1; got != exp {
t.Fatalf("expected empty, got %d", got) t.Fatalf("expected empty, got %d", got)
} }
} }

View file

@ -145,6 +145,8 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
} }
statedb.MergeTxAccessListIntoBlockWitness()
// Set the receipt logs and create the bloom filter. // Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash)
receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
@ -185,7 +187,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
Data: beaconRoot[:], Data: beaconRoot[:],
} }
vmenv.Reset(NewEVMTxContext(msg), statedb) vmenv.Reset(NewEVMTxContext(msg), statedb)
statedb.AddAddressToAccessList(params.BeaconRootsStorageAddress) statedb.AddAddressToAccessList(params.BeaconRootsStorageAddress, state.ALAllItems, state.AccessListWrite)
_, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
statedb.Finalise(true) statedb.Finalise(true)
} }

View file

@ -23,8 +23,10 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math" cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -354,6 +356,19 @@ func (st *StateTransition) preCheck() error {
return st.buyGas() return st.buyGas()
} }
// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
// otherwise, do the subtraction setting the result in gasPool and return true
func tryConsumeGas(gasPool *uint64, gas uint64) bool {
if *gasPool < gas {
*gasPool = 0
return false
}
*gasPool -= gas
return true
}
// TransitionDb will transition the state by applying the current message and // TransitionDb will transition the state by applying the current message and
// returning the evm execution result with following fields. // returning the evm execution result with following fields.
// //
@ -404,6 +419,28 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} }
st.gasRemaining -= gas st.gasRemaining -= gas
if rules.IsVerkle {
statelessGasOrigin := st.evm.StateDB.TouchTxOriginAndComputeGas(msg.From)
if !tryConsumeGas(&st.gasRemaining, statelessGasOrigin) {
return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas)
}
originNonce := st.evm.StateDB.GetNonce(msg.From)
if msg.To != nil {
statelessGasDest := st.evm.StateDB.TouchTxExistingAndComputeGas(*msg.To, msg.Value.Sign() != 0)
if !tryConsumeGas(&st.gasRemaining, statelessGasDest) {
return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas)
}
// ensure the code size ends up in the access witness
st.evm.StateDB.GetCodeSize(*msg.To)
} else {
contractAddr := crypto.CreateAddress(msg.From, originNonce)
if !tryConsumeGas(&st.gasRemaining, st.evm.StateDB.TouchAndChargeContractCreateInit(contractAddr, msg.Value.Sign() != 0)) {
return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas)
}
}
}
// Check clause 6 // Check clause 6
value, overflow := uint256.FromBig(msg.Value) value, overflow := uint256.FromBig(msg.Value)
if overflow { if overflow {
@ -457,6 +494,10 @@ 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) st.state.AddBalance(st.evm.Context.Coinbase, fee)
if rules.IsVerkle && fee.Sign() != 0 {
st.evm.StateDB.AddAddressToAccessList(st.evm.Context.Coinbase, state.ALAllItems, state.AccessListWrite)
}
} }
return &ExecutionResult{ return &ExecutionResult{

View file

@ -56,6 +56,8 @@ type Contract struct {
CodeAddr *common.Address CodeAddr *common.Address
Input []byte Input []byte
IsDeployment bool
Gas uint64 Gas uint64
value *uint256.Int value *uint256.Int
} }

View file

@ -37,6 +37,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.
@ -123,28 +124,28 @@ func enable2929(jt *JumpTable) {
jt[SLOAD].constantGas = 0 jt[SLOAD].constantGas = 0
jt[SLOAD].dynamicGas = gasSLoadEIP2929 jt[SLOAD].dynamicGas = gasSLoadEIP2929
jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 jt[EXTCODECOPY].constantGas = 0
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929 jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929
jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929 jt[EXTCODESIZE].constantGas = 0
jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck
jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929 jt[EXTCODEHASH].constantGas = 0
jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck
jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929 jt[BALANCE].constantGas = 0
jt[BALANCE].dynamicGas = gasEip2929AccountCheck jt[BALANCE].dynamicGas = gasEip2929AccountCheck
jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 jt[CALL].constantGas = 0
jt[CALL].dynamicGas = gasCallEIP2929 jt[CALL].dynamicGas = gasCallEIP2929
jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 jt[CALLCODE].constantGas = 0
jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 jt[CALLCODE].dynamicGas = gasCallCodeEIP2929
jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 jt[STATICCALL].constantGas = 0
jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 jt[STATICCALL].dynamicGas = gasStaticCallEIP2929
jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 jt[DELEGATECALL].constantGas = 0
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929 jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929
// This was previously part of the dynamic cost, but we're using it as a constantGas // This was previously part of the dynamic cost, but we're using it as a constantGas
@ -319,3 +320,14 @@ func enable6780(jt *JumpTable) {
maxStack: maxStack(1, 0), maxStack: maxStack(1, 0),
} }
} }
func enable4762(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStore4762
jt[BALANCE].dynamicGas = gasBalance4762
jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762
jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762
jt[CALL].dynamicGas = nil
jt[CALLCODE].dynamicGas = nil
jt[DELEGATECALL].dynamicGas = nil
jt[STATICCALL].dynamicGas = nil
}

View file

@ -21,6 +21,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/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -189,6 +190,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
p, isPrecompile := evm.precompile(addr) p, isPrecompile := evm.precompile(addr)
debug := evm.Config.Tracer != nil debug := evm.Config.Tracer != nil
var creation bool
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
@ -201,9 +203,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
evm.Config.Tracer.CaptureExit(ret, 0, nil) evm.Config.Tracer.CaptureExit(ret, 0, nil)
} }
} }
if evm.chainRules.IsVerkle {
tryConsumeGas(&gas, evm.StateDB.AddAddressToAccessList(addr, state.ALAllItems, state.AccessListRead))
}
return nil, gas, nil return nil, gas, nil
} }
evm.StateDB.CreateAccount(addr) evm.StateDB.CreateAccount(addr)
creation = true
} }
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
@ -237,6 +244,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// The depth-check is already done, and precompiles handled above // The depth-check is already done, and precompiles handled above
contract := NewContract(caller, AccountRef(addrCopy), value, gas) contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
contract.IsDeployment = creation
ret, err = evm.interpreter.Run(contract, input, false) ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
@ -418,6 +426,20 @@ func (c *codeAndHash) Hash() common.Hash {
return c.hash return c.hash
} }
// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
// otherwise, do the subtraction setting the result in gasPool and return true
func tryConsumeGas(gasPool *uint64, gas uint64) bool {
// TODO remove this function, it should no longer be needed
if *gasPool < gas {
*gasPool = 0
return false
}
*gasPool -= gas
return true
}
// create creates a new contract using code as deployment code. // create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
@ -436,7 +458,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// We add this to the access list _before_ taking a snapshot. Even if the creation fails, // 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 // the access-list change should not be rolled back
if evm.chainRules.IsBerlin { if evm.chainRules.IsBerlin {
evm.StateDB.AddAddressToAccessList(address) evm.StateDB.AddAddressToAccessList(address, state.ALVersion|state.ALNonce, state.AccessListWrite)
if value.Sign() != 0 {
evm.StateDB.AddAddressToAccessList(address, state.ALBalance, state.AccessListWrite)
}
} }
// Ensure there's no existing contract already at the designated address // Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
@ -455,6 +480,7 @@ 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
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
if evm.depth == 0 { if evm.depth == 0 {
@ -489,6 +515,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
} }
if err == nil && evm.chainRules.IsVerkle {
if len(ret) > 0 {
touchCodeChunksRangeOnReadAndChargeGas(address, 0, uint64(len(ret)), uint64(len(ret)), evm.StateDB)
}
if !contract.UseGas(evm.StateDB.AddAddressToAccessList(address, state.ALAllItems, state.AccessListWrite)) {
err = ErrOutOfGas
}
}
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.

View file

@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -395,6 +396,20 @@ 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
} }
if evm.chainRules.IsVerkle {
if _, isPrecompile := evm.precompile(address); !isPrecompile {
gas, overflow = math.SafeAdd(gas, evm.StateDB.AddAddressToAccessList(address, state.ALVersion|state.ALCodeSize, state.AccessListRead))
if overflow {
return 0, ErrGasUintOverflow
}
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.StateDB.AddAddressToAccessList(address, state.ALBalance, state.AccessListRead))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
}
return gas, nil return gas, nil
} }
@ -420,6 +435,17 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
if evm.chainRules.IsVerkle {
if _, isPrecompile := evm.precompile(contract.Address()); !isPrecompile {
gas, overflow = math.SafeAdd(gas, evm.StateDB.AddAddressToAccessList(contract.Address(), state.ALVersion|state.ALCodeSize, state.AccessListRead))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
return gas, nil return gas, nil
} }
@ -436,6 +462,14 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsVerkle {
if _, isPrecompile := evm.precompile(contract.Address()); !isPrecompile {
gas, overflow = math.SafeAdd(gas, evm.StateDB.AddAddressToAccessList(contract.Address(), state.ALVersion|state.ALCodeSize, state.AccessListRead))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
return gas, nil return gas, nil
} }
@ -452,6 +486,14 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsVerkle {
if _, isPrecompile := evm.precompile(contract.Address()); !isPrecompile {
gas, overflow = math.SafeAdd(gas, evm.StateDB.AddAddressToAccessList(contract.Address(), state.ALVersion|state.ALCodeSize, state.AccessListRead))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
return gas, nil return gas, nil
} }
@ -477,3 +519,40 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
} }
return gas, nil return gas, nil
} }
// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func touchCodeChunksRangeOnReadAndChargeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, sdb StateDB) 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++ {
gas := sdb.TouchAddressOnReadAndComputeGas(contractAddr, chunkNumber)
var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
if overflow {
panic("overflow when adding gas")
}
}
return statelessGasCharged
}

View file

@ -584,6 +584,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
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
) )
if interpreter.evm.chainRules.IsVerkle {
contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address()))
statelessGas := interpreter.evm.StateDB.TouchAndChargeContractCreateInit(contractAddress, value.Sign() != 0)
if !tryConsumeGas(&gas, statelessGas) {
return nil, ErrOutOfGas
}
}
if interpreter.evm.chainRules.IsEIP150 { if interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64 gas -= gas / 64
} }
@ -626,6 +633,14 @@ 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
) )
if interpreter.evm.chainRules.IsVerkle {
codeAndHash := &codeAndHash{code: input}
contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
statelessGas := interpreter.evm.StateDB.TouchAndChargeContractCreateInit(contractAddress, endowment.Sign() != 0)
if !tryConsumeGas(&gas, statelessGas) {
return nil, ErrOutOfGas
}
}
// Apply EIP150 // Apply EIP150
gas -= gas / 64 gas -= gas / 64
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas)
@ -866,6 +881,17 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
*pc += 1 *pc += 1
if *pc < codeLen { if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsVerkle && *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 := touchCodeChunksRangeOnReadAndChargeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), interpreter.evm.StateDB)
if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
} else { } else {
scope.Stack.push(integer.Clear()) scope.Stack.push(integer.Clear())
} }
@ -887,6 +913,17 @@ func makePush(size uint64, pushByteSize int) executionFunc {
endMin = startMin + pushByteSize endMin = startMin + pushByteSize
} }
if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsVerkle && *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 := touchCodeChunksRangeOnReadAndChargeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), interpreter.evm.StateDB)
if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
integer := new(uint256.Int) integer := new(uint256.Int)
scope.Stack.push(integer.SetBytes(common.RightPadBytes( scope.Stack.push(integer.SetBytes(common.RightPadBytes(
scope.Contract.Code[startMin:endMin], pushByteSize))) scope.Contract.Code[startMin:endMin], pushByteSize)))

View file

@ -20,6 +20,7 @@ import (
"math/big" "math/big"
"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/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -64,14 +65,18 @@ type StateDB interface {
// is defined according to EIP161 (balance = nonce = code = 0). // is defined according to EIP161 (balance = nonce = code = 0).
Empty(common.Address) bool Empty(common.Address) bool
AddressInAccessList(addr common.Address) bool
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform // AddAddressToAccessList adds the given address 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
AddAddressToAccessList(addr common.Address) AddAddressToAccessList(addr common.Address, item state.ALAccountItem, isWrite state.ALAccessMode) uint64
// 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, write state.ALAccessMode) uint64
TouchTxOriginAndComputeGas(addr common.Address) uint64
TouchTxExistingAndComputeGas(addr common.Address, sendsValue bool) uint64
TouchAndChargeContractCreateInit(addr common.Address, sendsValue bool) uint64
TouchAndChargeValueTransfer(from, to common.Address) uint64
TouchAddressOnReadAndComputeGas(from common.Address, chunk uint64) uint64
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)
@ -79,6 +84,8 @@ type StateDB interface {
AddLog(*types.Log) AddLog(*types.Log)
AddPreimage(common.Hash, []byte) AddPreimage(common.Hash, []byte)
Witness() state.AccessList
} }
// CallContext provides a basic interface for the EVM calling conventions. The EVM // CallContext provides a basic interface for the EVM calling conventions. The EVM

View file

@ -56,6 +56,11 @@ 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:
table = &cancunInstructionSet
if err := EnableEIP(4762, table); err != nil {
log.Error("EIP activation failed", "eip", 4762, "error", err)
}
case evm.chainRules.IsCancun: case evm.chainRules.IsCancun:
table = &cancunInstructionSet table = &cancunInstructionSet
case evm.chainRules.IsShanghai: case evm.chainRules.IsShanghai:
@ -174,6 +179,12 @@ 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.IsVerkle && !contract.IsDeployment {
// if the PC ends up in a new "chunk" of verkleized code, charge the
// associated costs.
contractAddr := contract.Address()
contract.Gas -= touchCodeChunksRangeOnReadAndChargeGas(contractAddr, pc, 1, uint64(len(contract.Code)), in.evm.StateDB)
}
// 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

@ -80,6 +80,12 @@ func validate(jt JumpTable) JumpTable {
return jt return jt
} }
func newVerkleInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet()
EnableEIP(4762, &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

@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -35,26 +36,15 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
y, x = stack.Back(1), stack.peek() y, x = stack.Back(1), stack.peek()
slot = common.Hash(x.Bytes32()) slot = common.Hash(x.Bytes32())
current = evm.StateDB.GetState(contract.Address(), slot) current = evm.StateDB.GetState(contract.Address(), slot)
cost = uint64(0) cost = evm.StateDB.AddSlotToAccessList(contract.Address(), slot, state.AccessListWrite)
) )
// Check slot presence in the access list // Check slot presence in the access list
if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
cost = params.ColdSloadCostEIP2929
// If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
if !addrPresent {
// Once we're done with YOLOv2 and schedule this for mainnet, might
// be good to remove this panic here, which is just really a
// canary to have during testing
panic("impossible case: address was not present in access list during sstore op")
}
}
value := common.Hash(y.Bytes32()) value := common.Hash(y.Bytes32())
if current == value { // noop (1) if current == value { // noop (1)
// EIP 2200 original clause: // EIP 2200 original clause:
// return params.SloadGasEIP2200, nil // return params.SloadGasEIP2200, nil
return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS return cost, nil // SLOAD_GAS
} }
original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
if original == current { if original == current {
@ -91,10 +81,27 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
} }
// EIP-2200 original clause: // EIP-2200 original clause:
//return params.SloadGasEIP2200, nil // dirty update (2.2) //return params.SloadGasEIP2200, nil // dirty update (2.2)
return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) return cost, nil // dirty update (2.2)
} }
} }
func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas := evm.StateDB.AddSlotToAccessList(contract.Address(), common.Hash(stack.peek().Bytes32()), state.AccessListWrite)
return gas, nil
}
func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return evm.StateDB.AddAddressToAccessList(contract.Address(), state.ALBalance, state.AccessListRead), nil
}
func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return evm.StateDB.AddAddressToAccessList(contract.Address(), state.ALCodeSize, state.AccessListRead), nil
}
func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return evm.StateDB.AddAddressToAccessList(contract.Address(), state.ALCodeHash, state.AccessListRead), nil
}
// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929 // gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929
// For SLOAD, if the (address, storage_key) pair (where address is the address of the contract // For SLOAD, if the (address, storage_key) pair (where address is the address of the contract
// whose storage is being read) is not yet in accessed_storage_keys, // whose storage is being read) is not yet in accessed_storage_keys,
@ -104,13 +111,7 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
loc := stack.peek() loc := stack.peek()
slot := common.Hash(loc.Bytes32()) slot := common.Hash(loc.Bytes32())
// Check slot presence in the access list // Check slot presence in the access list
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { return evm.StateDB.AddSlotToAccessList(contract.Address(), slot, false), nil
// If the caller cannot afford the cost, this change will be rolled back
// If he does afford it, we can skip checking the same thing later on, during execution
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
return params.ColdSloadCostEIP2929, nil
}
return params.WarmStorageReadCostEIP2929, nil
} }
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 // gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
@ -126,15 +127,13 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo
} }
addr := common.Address(stack.peek().Bytes20()) addr := common.Address(stack.peek().Bytes20())
// Check slot presence in the access list // Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) { algas := evm.StateDB.AddAddressToAccessList(addr, state.ALCodeSize, state.AccessListRead)
evm.StateDB.AddAddressToAccessList(addr)
var overflow bool var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas // We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { if gas, overflow = math.SafeAdd(gas, algas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil
}
return gas, nil return gas, nil
} }
@ -148,28 +147,22 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo
func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
addr := common.Address(stack.peek().Bytes20()) addr := common.Address(stack.peek().Bytes20())
// Check slot presence in the access list // Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) {
// If the caller cannot afford the cost, this change will be rolled back // If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(addr) return evm.StateDB.AddAddressToAccessList(addr, state.ALCodeSize|state.ALCodeHash|state.ALBalance, state.AccessListRead), nil
// The warm storage read cost is already charged as constantGas
return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil
}
return 0, nil
} }
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
addr := common.Address(stack.Back(1).Bytes20()) addr := common.Address(stack.Back(1).Bytes20())
// Check slot presence in the access list // Check slot presence in the access list
warmAccess := evm.StateDB.AddressInAccessList(addr) cost := evm.StateDB.AddAddressToAccessList(addr, 0, state.AccessListRead)
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
// the cost to charge for cold access, if any, is Cold - Warm // the cost to charge for cold access, if any, is Cold - Warm
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 if cost == params.WarmStorageReadCostEIP2929 {
if !warmAccess {
evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available // Charge the remaining difference here already, to correctly calculate available
// gas for call // gas for call
if !contract.UseGas(coldCost) { if !contract.UseGas(cost) {
return 0, ErrOutOfGas return 0, ErrOutOfGas
} }
} }
@ -179,17 +172,17 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
// - memory expansion // - memory expansion
// - 63/64ths rule // - 63/64ths rule
gas, err := oldCalculator(evm, contract, stack, mem, memorySize) gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
if warmAccess || err != nil { if cost == params.WarmStorageReadCostEIP2929 || err != nil {
return gas, err return gas, err
} }
// In case of a cold access, we temporarily add the cold charge back, and also // In case of a cold access, we temporarily add the cold charge back, and also
// add it to the returned gas. By adding it to the return, it will be charged // add it to the returned gas. By adding it to the return, it will be charged
// outside of this function, as part of the dynamic gas, and that will make it // outside of this function, as part of the dynamic gas, and that will make it
// also become correctly reported to tracers. // also become correctly reported to tracers.
contract.Gas += coldCost contract.Gas += cost
var overflow bool var overflow bool
if gas, overflow = math.SafeAdd(gas, coldCost); overflow { if gas, overflow = math.SafeAdd(gas, cost); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
@ -231,11 +224,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
gas uint64 gas uint64
address = common.Address(stack.peek().Bytes20()) address = common.Address(stack.peek().Bytes20())
) )
if !evm.StateDB.AddressInAccessList(address) { gas = evm.StateDB.AddAddressToAccessList(address, 0, state.AccessListWrite)
// If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(address)
gas = params.ColdAccountAccessCostEIP2929
}
// if empty and transfers value // if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas gas += params.CreateBySelfdestructGas

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

@ -18,7 +18,6 @@ package utils
import ( import (
"encoding/binary" "encoding/binary"
"sync"
"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"
@ -35,6 +34,8 @@ const (
NonceLeafKey = 2 NonceLeafKey = 2
CodeKeccakLeafKey = 3 CodeKeccakLeafKey = 3
CodeSizeLeafKey = 4 CodeSizeLeafKey = 4
maxPointCacheByteSize = 100 << 20
) )
var ( var (
@ -46,7 +47,7 @@ var (
verkleNodeWidth = uint256.NewInt(256) verkleNodeWidth = uint256.NewInt(256)
codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset) codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset)
index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64] getTreePolyIndex0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64]
// cacheHitGauge is the metric to track how many cache hit occurred. // cacheHitGauge is the metric to track how many cache hit occurred.
cacheHitGauge = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil) cacheHitGauge = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil)
@ -57,15 +58,11 @@ var (
func init() { func init() {
// The byte array is the Marshalled output of the point computed as such: // The byte array is the Marshalled output of the point computed as such:
// //cfg, _ := verkle.GetConfig()
// var ( //verkle.FromLEBytes(&getTreePolyIndex0Fr[0], []byte{2, 64})
// config = verkle.GetConfig() //= cfg.CommitToPoly(getTreePolyIndex0Fr[:], 1)
// fr verkle.Fr getTreePolyIndex0Point = new(verkle.Point)
// ) err := getTreePolyIndex0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191})
// verkle.FromLEBytes(&fr, []byte{2, 64})
// point := config.CommitToPoly([]verkle.Fr{fr}, 1)
index0Point = new(verkle.Point)
err := index0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -73,39 +70,36 @@ func init() {
// PointCache is the LRU cache for storing evaluated address commitment. // PointCache is the LRU cache for storing evaluated address commitment.
type PointCache struct { type PointCache struct {
lru lru.BasicLRU[string, *verkle.Point] cache *lru.Cache[string, *verkle.Point]
lock sync.RWMutex
} }
// NewPointCache returns the cache with specified size. // NewPointCache returns the cache with specified size.
func NewPointCache(maxItems int) *PointCache { func NewPointCache() *PointCache {
// Each verkle.Point is 96 bytes.
verklePointSize := 96
capacity := maxPointCacheByteSize / verklePointSize
return &PointCache{ return &PointCache{
lru: lru.NewBasicLRU[string, *verkle.Point](maxItems), cache: lru.NewCache[string, *verkle.Point](capacity),
} }
} }
// Get returns the cached commitment for the specified address, or computing // GetTreeKeyHeader returns the cached commitment for the specified address,
// it on the flight. // or computing it on the fly.
func (c *PointCache) Get(addr []byte) *verkle.Point { func (pc *PointCache) GetTreeKeyHeader(addr []byte) *verkle.Point {
c.lock.Lock() point, ok := pc.cache.Get(string(addr))
defer c.lock.Unlock()
p, ok := c.lru.Get(string(addr))
if ok { if ok {
cacheHitGauge.Inc(1) return point
return p
} }
cacheMissGauge.Inc(1)
p = evaluateAddressPoint(addr) point = EvaluateAddressPoint(addr)
c.lru.Add(string(addr), p) pc.cache.Add(string(addr), point)
return p return point
} }
// GetStem returns the first 31 bytes of the tree key as the tree stem. It only func (pc *PointCache) GetTreeKeyVersionCached(addr []byte) []byte {
// works for the account metadata whose treeIndex is 0. p := pc.GetTreeKeyHeader(addr)
func (c *PointCache) GetStem(addr []byte) []byte { v := PointToHash(p, VersionLeafKey)
p := c.Get(addr) return v[:]
return pointToHash(p, 0)[:31]
} }
// GetTreeKey performs both the work of the spec's get_tree_key function, and that // GetTreeKey performs both the work of the spec's get_tree_key function, and that
@ -143,9 +137,9 @@ func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte {
ret := cfg.CommitToPoly(poly[:], 0) ret := cfg.CommitToPoly(poly[:], 0)
// add a constant point corresponding to poly[0]=[2+256*64]. // add a constant point corresponding to poly[0]=[2+256*64].
ret.Add(ret, index0Point) ret.Add(ret, getTreePolyIndex0Point)
return pointToHash(ret, subIndex) return PointToHash(ret, subIndex)
} }
// GetTreeKeyWithEvaluatedAddress is basically identical to GetTreeKey, the only // GetTreeKeyWithEvaluatedAddress is basically identical to GetTreeKey, the only
@ -174,7 +168,7 @@ func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.
// add the pre-evaluated address // add the pre-evaluated address
ret.Add(ret, evaluated) ret.Add(ret, evaluated)
return pointToHash(ret, subIndex) return PointToHash(ret, subIndex)
} }
// VersionKey returns the verkle tree key of the version field for the specified account. // VersionKey returns the verkle tree key of the version field for the specified account.
@ -305,7 +299,7 @@ func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []by
return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex) return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex)
} }
func pointToHash(evaluated *verkle.Point, suffix byte) []byte { func PointToHash(evaluated *verkle.Point, suffix byte) []byte {
// The output of Byte() is big endian for banderwagon. This // The output of Byte() is big endian for banderwagon. This
// introduces an imbalance in the tree, because hashes are // introduces an imbalance in the tree, because hashes are
// elements of a 253-bit field. This means more than half the // elements of a 253-bit field. This means more than half the
@ -319,7 +313,31 @@ func pointToHash(evaluated *verkle.Point, suffix byte) []byte {
return bytes[:] return bytes[:]
} }
func evaluateAddressPoint(address []byte) *verkle.Point { func GetTreeKeyWithEvaluatedAddess(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte {
var poly [5]fr.Element
poly[0].SetZero()
poly[1].SetZero()
poly[2].SetZero()
// little-endian, 32-byte aligned treeIndex
var index [32]byte
for i := 0; i < len(treeIndex); i++ {
binary.LittleEndian.PutUint64(index[i*8:(i+1)*8], treeIndex[i])
}
verkle.FromLEBytes(&poly[3], index[:16])
verkle.FromLEBytes(&poly[4], index[16:])
cfg := verkle.GetConfig()
ret := cfg.CommitToPoly(poly[:], 0)
// add the pre-evaluated address
ret.Add(ret, evaluated)
return PointToHash(ret, subIndex)
}
func EvaluateAddressPoint(address []byte) *verkle.Point {
if len(address) < 32 { if len(address) < 32 {
var aligned [32]byte var aligned [32]byte
address = append(aligned[:32-len(address)], address...) address = append(aligned[:32-len(address)], address...)
@ -337,6 +355,43 @@ func evaluateAddressPoint(address []byte) *verkle.Point {
ret := cfg.CommitToPoly(poly[:], 0) ret := cfg.CommitToPoly(poly[:], 0)
// add a constant point // add a constant point
ret.Add(ret, index0Point) ret.Add(ret, getTreePolyIndex0Point)
return ret return ret
} }
func GetTreeKeyStorageSlotTreeIndexes(storageKey []byte) (*uint256.Int, byte) {
var pos uint256.Int
pos.SetBytes(storageKey)
// If the storage slot is in the header, we need to add the header offset.
if pos.Cmp(codeStorageDelta) < 0 {
// This addition is always safe; it can't ever overflow since pos<codeStorageDelta.
pos.Add(headerStorageOffset, &pos)
// In this branch, the tree-index is zero since we're in the account header,
// and the sub-index is the LSB of the modified storage key.
return zero, byte(pos[0] & 0xFF)
}
// If the storage slot is in the main storage, we need to add the main storage offset.
// The first MAIN_STORAGE_OFFSET group will see its
// first 64 slots unreachable. This is either a typo in the
// spec or intended to conserve the 256-u256
// aligment. If we decide to ever access these 64
// slots, uncomment this.
// // Get the new offset since we now know that we are above 64.
// pos.Sub(&pos, codeStorageDelta)
// suffix := byte(pos[0] & 0xFF)
suffix := storageKey[len(storageKey)-1]
// We first divide by VerkleNodeWidth to create room to avoid an overflow next.
pos.Rsh(&pos, uint(verkleNodeWidthLog2))
// We add mainStorageOffset/VerkleNodeWidth which can't overflow.
pos.Add(&pos, mainStorageOffsetLshVerkleNodeWidth)
// The sub-index is the LSB of the original storage key, since mainStorageOffset
// doesn't affect this byte, so we can avoid masks or shifts.
return &pos, suffix
}

View file

@ -27,7 +27,7 @@ import (
func TestTreeKey(t *testing.T) { func TestTreeKey(t *testing.T) {
var ( var (
address = []byte{0x01} address = []byte{0x01}
addressEval = evaluateAddressPoint(address) addressEval = EvaluateAddressPoint(address)
smallIndex = uint256.NewInt(1) smallIndex = uint256.NewInt(1)
largeIndex = uint256.NewInt(10000) largeIndex = uint256.NewInt(10000)
smallStorage = []byte{0x1} smallStorage = []byte{0x1}
@ -91,7 +91,7 @@ func BenchmarkTreeKeyWithEvaluation(b *testing.B) {
verkle.GetConfig() verkle.GetConfig()
addr := []byte{0x01} addr := []byte{0x01}
eval := evaluateAddressPoint(addr) eval := EvaluateAddressPoint(addr)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
@ -129,7 +129,7 @@ func BenchmarkStorageKeyWithEvaluation(b *testing.B) {
verkle.GetConfig() verkle.GetConfig()
addr := []byte{0x01} addr := []byte{0x01}
eval := evaluateAddressPoint(addr) eval := EvaluateAddressPoint(addr)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()

View file

@ -79,6 +79,7 @@ func (t *VerkleTrie) GetKey(key []byte) []byte {
// account address. If the specified account is not in the verkle tree, nil will // account address. If the specified account is not in the verkle tree, nil will
// be returned. If the tree is corrupted, an error will be returned. // be returned. If the tree is corrupted, an error will be returned.
func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
versionkey := t.cache.GetTreeKeyVersionCached(addr[:])
var ( var (
acc = &types.StateAccount{} acc = &types.StateAccount{}
values [][]byte values [][]byte
@ -86,7 +87,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error
) )
switch n := t.root.(type) { switch n := t.root.(type) {
case *verkle.InternalNode: case *verkle.InternalNode:
values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver) values, err = n.GetValuesAtStem(versionkey, t.nodeResolver)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err) return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err)
} }
@ -119,7 +120,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error
// account address and storage key. If the specified slot is not in the verkle tree, // account address and storage key. If the specified slot is not in the verkle tree,
// nil will be returned. If the tree is corrupted, an error will be returned. // nil will be returned. If the tree is corrupted, an error will be returned.
func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.GetTreeKeyHeader(addr.Bytes()), key)
val, err := t.root.Get(k, t.nodeResolver) val, err := t.root.Get(k, t.nodeResolver)
if err != nil { if err != nil {
return nil, err return nil, err
@ -153,7 +154,7 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount)
switch n := t.root.(type) { switch n := t.root.(type) {
case *verkle.InternalNode: case *verkle.InternalNode:
err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver) err = n.InsertValuesAtStem(t.cache.GetTreeKeyVersionCached(addr[:]), values, t.nodeResolver)
if err != nil { if err != nil {
return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err) return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err)
} }
@ -174,7 +175,7 @@ func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) er
} else { } else {
copy(v[32-len(value):], value[:]) copy(v[32-len(value):], value[:])
} }
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(address.Bytes()), key) k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.GetTreeKeyHeader(address.Bytes()), key)
return t.root.Insert(k, v[:], t.nodeResolver) return t.root.Insert(k, v[:], t.nodeResolver)
} }
@ -191,7 +192,7 @@ func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
} }
switch n := t.root.(type) { switch n := t.root.(type) {
case *verkle.InternalNode: case *verkle.InternalNode:
err = n.InsertValuesAtStem(t.cache.GetStem(addr.Bytes()), values, t.nodeResolver) err = n.InsertValuesAtStem(t.cache.GetTreeKeyVersionCached(addr.Bytes()), values, t.nodeResolver)
if err != nil { if err != nil {
return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err) return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err)
} }
@ -206,7 +207,7 @@ func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
// returned. If the trie is corrupted, an error will be returned. // returned. If the trie is corrupted, an error will be returned.
func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error { func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error {
var zero [32]byte var zero [32]byte
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.GetTreeKeyHeader(addr.Bytes()), key)
return t.root.Insert(k, zero[:], t.nodeResolver) return t.root.Insert(k, zero[:], t.nodeResolver)
} }
@ -338,7 +339,7 @@ func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Has
groupOffset := (chunknr + 128) % 256 groupOffset := (chunknr + 128) % 256
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ { if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
values = make([][]byte, verkle.NodeWidth) values = make([][]byte, verkle.NodeWidth)
key = utils.CodeChunkKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), uint256.NewInt(chunknr)) key = utils.CodeChunkKeyWithEvaluatedAddress(t.cache.GetTreeKeyHeader(addr.Bytes()), uint256.NewInt(chunknr))
} }
values[groupOffset] = chunks[i : i+32] values[groupOffset] = chunks[i : i+32]

View file

@ -57,7 +57,7 @@ var (
func TestVerkleTreeReadWrite(t *testing.T) { func TestVerkleTreeReadWrite(t *testing.T) {
db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)
tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100)) tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache())
for addr, acct := range accounts { for addr, acct := range accounts {
if err := tr.UpdateAccount(addr, acct); err != nil { if err := tr.UpdateAccount(addr, acct); err != nil {