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.
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) {
beacon.ethone.Finalize(chain, header, state, txs, uncles, nil)
beacon.ethone.Finalize(chain, header, statedb, txs, uncles, nil)
return
}
// Withdrawals processing.
@ -358,7 +358,11 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// Convert amount from gwei to wei.
amount := new(uint256.Int).SetUint64(w.Amount)
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.
}

View file

@ -67,6 +67,10 @@ var (
// than init code size limit.
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
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")

View file

@ -18,22 +18,62 @@ package state
import (
"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
slots []map[common.Hash]struct{}
}
// 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]
return ok
}
// 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.
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]
if !ok {
// 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.
func newAccessList() *accessList {
return &accessList{
func newAccessList() AccessList {
return &accessList2929{
addresses: make(map[common.Address]int),
}
}
// Copy creates an independent copy of an accessList.
func (a *accessList) Copy() *accessList {
cp := newAccessList()
func (a *accessList2929) Copy() AccessList {
cp := newAccessList().(*accessList2929)
for k, v := range a.addresses {
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
// 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 {
return false
return params.WarmStorageReadCostEIP2929
}
al.addresses[address] = -1
return true
return params.ColdAccountAccessCostEIP2929
}
// 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
// - slot added
// 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]
if !addrPresent || idx == -1 {
// Address not present, or addr present but no slots there
al.addresses[address] = len(al.slots)
slotmap := map[common.Hash]struct{}{slot: {}}
al.slots = append(al.slots, slotmap)
return !addrPresent, true
return params.WarmStorageReadCostEIP2929
}
// There is already an (address,slot) mapping
slotmap := al.slots[idx]
if _, ok := slotmap[slot]; !ok {
slotmap[slot] = struct{}{}
// Journal add slot change
return false, true
return params.ColdSloadCostEIP2929
}
// No changes required
return false, false
return params.WarmStorageReadCostEIP2929
}
// 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 method is meant to be used by the journal, which maintains ordering of
// 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]
// There are two ways this can fail
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.
// This method is meant to be used by the journal, which maintains ordering of
// operations.
func (al *accessList) DeleteAddress(address common.Address) {
func (al *accessList2929) DeleteAddress(address common.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.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() {
return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems))
return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache())
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {

View file

@ -99,11 +99,14 @@ type StateDB struct {
preimages map[common.Hash][]byte
// Per-transaction access list
accessList *accessList
accessList AccessList
// Transient storage
transientStorage transientStorage
// Global witness
witness AccessList
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
@ -1275,26 +1278,30 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
// - Reset transient storage (EIP-1153)
func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
if rules.IsBerlin {
if !rules.IsVerkle {
s.accessList = newAccessList()
} else {
s.accessList = NewAccessWitness()
}
// Clear out any leftover from previous executions
al := newAccessList()
s.accessList = al
al := s.accessList
al.AddAddress(sender)
al.AddAddress(sender, ALAllItems, AccessListRead)
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
}
for _, addr := range precompiles {
al.AddAddress(addr)
al.AddAddress(addr, ALAllItems, AccessListRead)
}
for _, el := range list {
al.AddAddress(el.Address)
al.AddAddress(el.Address, 0, AccessListRead)
for _, key := range el.StorageKeys {
al.AddSlot(el.Address, key)
al.AddSlot(el.Address, key, AccessListRead)
}
}
if rules.IsShanghai { // EIP-3651: warm coinbase
al.AddAddress(coinbase)
al.AddAddress(coinbase, ALAllItems, AccessListRead)
}
}
// 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
func (s *StateDB) AddAddressToAccessList(addr common.Address) {
if s.accessList.AddAddress(addr) {
func (s *StateDB) AddAddressToAccessList(addr common.Address, mode ALAccountItem, isWrite ALAccessMode) uint64 {
gas := s.accessList.AddAddress(addr, mode, isWrite)
if gas > 0 {
s.journal.append(accessListAddAccountChange{&addr})
}
return gas
}
// AddSlotToAccessList adds the given (address, slot)-tuple to the access list
func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
addrMod, slotMod := s.accessList.AddSlot(addr, slot)
if addrMod {
// In practice, this should not happen, since there is no way to enter the
// 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 {
func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash, isWrite ALAccessMode) uint64 {
gas := s.accessList.AddSlot(addr, slot, isWrite)
if gas > params.WarmStorageReadCostEIP2929 {
s.journal.append(accessListAddSlotChange{
address: &addr,
slot: &slot,
})
}
}
// 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)
return gas
}
// 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
}
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",
fn: func(a testAction, s *StateDB) {
s.AddAddressToAccessList(addr)
s.AddAddressToAccessList(addr, 0, false)
},
},
{
name: "AddSlotToAccessList",
fn: func(a testAction, s *StateDB) {
s.AddSlotToAccessList(addr,
common.Hash{byte(a.args[0])})
common.Hash{byte(a.args[0])}, false)
},
args: make([]int64, 1),
},
@ -879,19 +879,19 @@ func TestStateDBAccessList(t *testing.T) {
}
// Check that the given addresses are in the access list
for _, address := range addresses {
if !state.AddressInAccessList(address) {
if !state.addressInAccessList(address) {
t.Fatalf("expected %x to be in access list", address)
}
}
// 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 {
t.Fatalf("extra address %x in access list", address)
}
}
}
verifySlots := func(addrString string, slotStrings ...string) {
if !state.AddressInAccessList(addr(addrString)) {
if !state.addressInAccessList(addr(addrString)) {
t.Fatalf("scope missing address/slots %v", addrString)
}
var address = addr(addrString)
@ -905,14 +905,14 @@ func TestStateDBAccessList(t *testing.T) {
}
// Check that the expected items are in the access list
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)
}
}
// Check that no extra elements are in the access list
index := state.accessList.addresses[address]
index := state.accessList.(*accessList2929).addresses[address]
if index >= 0 {
stateSlots := state.accessList.slots[index]
stateSlots := state.accessList.(*accessList2929).slots[index]
for s := range stateSlots {
if _, slotPresent := slotMap[s]; !slotPresent {
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.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3
state.AddSlotToAccessList(addr("bb"), slot("02")) // 4
state.AddAddressToAccessList(addr("aa"), 0, false) // 1
state.AddSlotToAccessList(addr("bb"), slot("01"), false) // 2,3
state.AddSlotToAccessList(addr("bb"), slot("02"), false) // 4
verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02")
@ -934,17 +934,17 @@ func TestStateDBAccessList(t *testing.T) {
}
// same again, should cause no journal entries
state.AddSlotToAccessList(addr("bb"), slot("01"))
state.AddSlotToAccessList(addr("bb"), slot("02"))
state.AddAddressToAccessList(addr("aa"))
state.AddSlotToAccessList(addr("bb"), slot("01"), false)
state.AddSlotToAccessList(addr("bb"), slot("02"), false)
state.AddAddressToAccessList(addr("aa"), 0, false)
if exp, got := 4, state.journal.length(); exp != got {
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
}
// some new ones
state.AddSlotToAccessList(addr("bb"), slot("03")) // 5
state.AddSlotToAccessList(addr("aa"), slot("01")) // 6
state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8
state.AddAddressToAccessList(addr("cc"))
state.AddSlotToAccessList(addr("bb"), slot("03"), false) // 5
state.AddSlotToAccessList(addr("aa"), slot("01"), false) // 6
state.AddSlotToAccessList(addr("cc"), slot("01"), false) // 7,8
state.AddAddressToAccessList(addr("cc"), 0, false)
if exp, got := 8, state.journal.length(); exp != got {
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
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")
}
verifyAddrs("aa", "bb", "cc")
@ -964,7 +964,7 @@ func TestStateDBAccessList(t *testing.T) {
verifySlots("bb", "01", "02", "03")
state.journal.revert(state, 6)
if state.AddressInAccessList(addr("cc")) {
if state.addressInAccessList(addr("cc")) {
t.Fatalf("addr present, expected missing")
}
verifyAddrs("aa", "bb")
@ -972,46 +972,46 @@ func TestStateDBAccessList(t *testing.T) {
verifySlots("bb", "01", "02", "03")
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")
}
verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02", "03")
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")
}
verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02")
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")
}
verifyAddrs("aa", "bb")
verifySlots("bb", "01")
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")
}
verifyAddrs("aa", "bb")
state.journal.revert(state, 1)
if state.AddressInAccessList(addr("bb")) {
if state.addressInAccessList(addr("bb")) {
t.Fatalf("addr present, expected missing")
}
verifyAddrs("aa")
state.journal.revert(state, 0)
if state.AddressInAccessList(addr("aa")) {
if state.addressInAccessList(addr("aa")) {
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)
}
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)
}
// Check the copy
@ -1019,10 +1019,10 @@ func TestStateDBAccessList(t *testing.T) {
state = stateCopy1
verifyAddrs("aa", "bb")
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)
}
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)
}
}

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())
}
statedb.MergeTxAccessListIntoBlockWitness()
// Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash)
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
@ -185,7 +187,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
Data: beaconRoot[:],
}
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)
statedb.Finalise(true)
}

View file

@ -23,8 +23,10 @@ import (
"github.com/ethereum/go-ethereum/common"
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/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@ -354,6 +356,19 @@ func (st *StateTransition) preCheck() error {
return st.buyGas()
}
// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
// otherwise, do the subtraction setting the result in gasPool and return true
func tryConsumeGas(gasPool *uint64, gas uint64) bool {
if *gasPool < gas {
*gasPool = 0
return false
}
*gasPool -= gas
return true
}
// TransitionDb will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
@ -404,6 +419,28 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
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
value, overflow := uint256.FromBig(msg.Value)
if overflow {
@ -457,6 +494,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256)
st.state.AddBalance(st.evm.Context.Coinbase, fee)
if rules.IsVerkle && fee.Sign() != 0 {
st.evm.StateDB.AddAddressToAccessList(st.evm.Context.Coinbase, state.ALAllItems, state.AccessListWrite)
}
}
return &ExecutionResult{

View file

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

View file

@ -37,6 +37,7 @@ var activators = map[int]func(*JumpTable){
1884: enable1884,
1344: enable1344,
1153: enable1153,
4762: enable4762,
}
// EnableEIP enables the given EIP on the config.
@ -123,28 +124,28 @@ func enable2929(jt *JumpTable) {
jt[SLOAD].constantGas = 0
jt[SLOAD].dynamicGas = gasSLoadEIP2929
jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODECOPY].constantGas = 0
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929
jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODESIZE].constantGas = 0
jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck
jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODEHASH].constantGas = 0
jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck
jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929
jt[BALANCE].constantGas = 0
jt[BALANCE].dynamicGas = gasEip2929AccountCheck
jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
jt[CALL].constantGas = 0
jt[CALL].dynamicGas = gasCallEIP2929
jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929
jt[CALLCODE].constantGas = 0
jt[CALLCODE].dynamicGas = gasCallCodeEIP2929
jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929
jt[STATICCALL].constantGas = 0
jt[STATICCALL].dynamicGas = gasStaticCallEIP2929
jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
jt[DELEGATECALL].constantGas = 0
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929
// 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),
}
}
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"
"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/crypto"
"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)
debug := evm.Config.Tracer != nil
var creation bool
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// 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)
}
}
if evm.chainRules.IsVerkle {
tryConsumeGas(&gas, evm.StateDB.AddAddressToAccessList(addr, state.ALAllItems, state.AccessListRead))
}
return nil, gas, nil
}
evm.StateDB.CreateAccount(addr)
creation = true
}
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
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
contract.IsDeployment = creation
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@ -418,6 +426,20 @@ func (c *codeAndHash) Hash() common.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.
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
@ -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,
// the access-list change should not be rolled back
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
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.
contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash)
contract.IsDeployment = true
if evm.Config.Tracer != nil {
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
// 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.

View file

@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state"
"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 {
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
}
@ -420,6 +435,17 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
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
}
@ -436,6 +462,14 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
if evm.chainRules.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
}
@ -452,6 +486,14 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
if evm.chainRules.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
}
@ -477,3 +519,40 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
}
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()))
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 {
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()))
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
gas -= gas / 64
scope.Contract.UseGas(gas)
@ -866,6 +881,17 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
if !scope.Contract.IsDeployment && interpreter.evm.chainRules.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 {
scope.Stack.push(integer.Clear())
}
@ -887,6 +913,17 @@ func makePush(size uint64, pushByteSize int) executionFunc {
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)
scope.Stack.push(integer.SetBytes(common.RightPadBytes(
scope.Contract.Code[startMin:endMin], pushByteSize)))

View file

@ -20,6 +20,7 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@ -64,14 +65,18 @@ type StateDB interface {
// is defined according to EIP161 (balance = nonce = code = 0).
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
// 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
// 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)
RevertToSnapshot(int)
@ -79,6 +84,8 @@ type StateDB interface {
AddLog(*types.Log)
AddPreimage(common.Hash, []byte)
Witness() state.AccessList
}
// 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.
var table *JumpTable
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:
table = &cancunInstructionSet
case evm.chainRules.IsShanghai:
@ -174,6 +179,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
if in.evm.chainRules.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
// enough stack items available to perform the operation.
op = contract.GetOp(pc)

View file

@ -80,6 +80,12 @@ func validate(jt JumpTable) JumpTable {
return jt
}
func newVerkleInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet()
EnableEIP(4762, &instructionSet)
return validate(instructionSet)
}
func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
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/math"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/params"
)
@ -35,26 +36,15 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
y, x = stack.Back(1), stack.peek()
slot = common.Hash(x.Bytes32())
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
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())
if current == value { // noop (1)
// EIP 2200 original clause:
// return params.SloadGasEIP2200, nil
return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS
return cost, nil // SLOAD_GAS
}
original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
if original == current {
@ -91,10 +81,27 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
}
// EIP-2200 original clause:
//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
// 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,
@ -104,13 +111,7 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
loc := stack.peek()
slot := common.Hash(loc.Bytes32())
// Check slot presence in the access list
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
// 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
return evm.StateDB.AddSlotToAccessList(contract.Address(), slot, false), nil
}
// 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())
// Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) {
evm.StateDB.AddAddressToAccessList(addr)
var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
algas := evm.StateDB.AddAddressToAccessList(addr, state.ALCodeSize, state.AccessListRead)
var overflow bool
// We charge (cold-warm), since 'warm' is already charged as constantGas
if gas, overflow = math.SafeAdd(gas, algas); overflow {
return 0, ErrGasUintOverflow
}
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) {
addr := common.Address(stack.peek().Bytes20())
// 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
evm.StateDB.AddAddressToAccessList(addr)
// The warm storage read cost is already charged as constantGas
return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil
}
return 0, nil
// If the caller cannot afford the cost, this change will be rolled back
return evm.StateDB.AddAddressToAccessList(addr, state.ALCodeSize|state.ALCodeHash|state.ALBalance, state.AccessListRead), nil
}
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
addr := common.Address(stack.Back(1).Bytes20())
// 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 cost to charge for cold access, if any, is Cold - Warm
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
if !warmAccess {
evm.StateDB.AddAddressToAccessList(addr)
if cost == params.WarmStorageReadCostEIP2929 {
// Charge the remaining difference here already, to correctly calculate available
// gas for call
if !contract.UseGas(coldCost) {
if !contract.UseGas(cost) {
return 0, ErrOutOfGas
}
}
@ -179,17 +172,17 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
// - memory expansion
// - 63/64ths rule
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
if warmAccess || err != nil {
if cost == params.WarmStorageReadCostEIP2929 || err != nil {
return gas, err
}
// 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
// outside of this function, as part of the dynamic gas, and that will make it
// also become correctly reported to tracers.
contract.Gas += coldCost
contract.Gas += cost
var overflow bool
if gas, overflow = math.SafeAdd(gas, coldCost); overflow {
if gas, overflow = math.SafeAdd(gas, cost); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
@ -231,11 +224,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
gas uint64
address = common.Address(stack.peek().Bytes20())
)
if !evm.StateDB.AddressInAccessList(address) {
// If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(address)
gas = params.ColdAccountAccessCostEIP2929
}
gas = evm.StateDB.AddAddressToAccessList(address, 0, state.AccessListWrite)
// if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
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 (
"encoding/binary"
"sync"
"github.com/crate-crypto/go-ipa/bandersnatch/fr"
"github.com/ethereum/go-ethereum/common/lru"
@ -35,6 +34,8 @@ const (
NonceLeafKey = 2
CodeKeccakLeafKey = 3
CodeSizeLeafKey = 4
maxPointCacheByteSize = 100 << 20
)
var (
@ -46,7 +47,7 @@ var (
verkleNodeWidth = uint256.NewInt(256)
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 = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil)
@ -57,15 +58,11 @@ var (
func init() {
// The byte array is the Marshalled output of the point computed as such:
//
// var (
// config = verkle.GetConfig()
// fr verkle.Fr
// )
// 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})
//cfg, _ := verkle.GetConfig()
//verkle.FromLEBytes(&getTreePolyIndex0Fr[0], []byte{2, 64})
//= cfg.CommitToPoly(getTreePolyIndex0Fr[:], 1)
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})
if err != nil {
panic(err)
}
@ -73,39 +70,36 @@ func init() {
// PointCache is the LRU cache for storing evaluated address commitment.
type PointCache struct {
lru lru.BasicLRU[string, *verkle.Point]
lock sync.RWMutex
cache *lru.Cache[string, *verkle.Point]
}
// 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{
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
// it on the flight.
func (c *PointCache) Get(addr []byte) *verkle.Point {
c.lock.Lock()
defer c.lock.Unlock()
p, ok := c.lru.Get(string(addr))
// GetTreeKeyHeader returns the cached commitment for the specified address,
// or computing it on the fly.
func (pc *PointCache) GetTreeKeyHeader(addr []byte) *verkle.Point {
point, ok := pc.cache.Get(string(addr))
if ok {
cacheHitGauge.Inc(1)
return p
return point
}
cacheMissGauge.Inc(1)
p = evaluateAddressPoint(addr)
c.lru.Add(string(addr), p)
return p
point = EvaluateAddressPoint(addr)
pc.cache.Add(string(addr), point)
return point
}
// GetStem returns the first 31 bytes of the tree key as the tree stem. It only
// works for the account metadata whose treeIndex is 0.
func (c *PointCache) GetStem(addr []byte) []byte {
p := c.Get(addr)
return pointToHash(p, 0)[:31]
func (pc *PointCache) GetTreeKeyVersionCached(addr []byte) []byte {
p := pc.GetTreeKeyHeader(addr)
v := PointToHash(p, VersionLeafKey)
return v[:]
}
// 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)
// 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
@ -174,7 +168,7 @@ func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.
// add the pre-evaluated address
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.
@ -305,7 +299,7 @@ func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []by
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
// introduces an imbalance in the tree, because hashes are
// 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[:]
}
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 {
var aligned [32]byte
address = append(aligned[:32-len(address)], address...)
@ -337,6 +355,43 @@ func evaluateAddressPoint(address []byte) *verkle.Point {
ret := cfg.CommitToPoly(poly[:], 0)
// add a constant point
ret.Add(ret, index0Point)
ret.Add(ret, getTreePolyIndex0Point)
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) {
var (
address = []byte{0x01}
addressEval = evaluateAddressPoint(address)
addressEval = EvaluateAddressPoint(address)
smallIndex = uint256.NewInt(1)
largeIndex = uint256.NewInt(10000)
smallStorage = []byte{0x1}
@ -91,7 +91,7 @@ func BenchmarkTreeKeyWithEvaluation(b *testing.B) {
verkle.GetConfig()
addr := []byte{0x01}
eval := evaluateAddressPoint(addr)
eval := EvaluateAddressPoint(addr)
b.ReportAllocs()
b.ResetTimer()
@ -129,7 +129,7 @@ func BenchmarkStorageKeyWithEvaluation(b *testing.B) {
verkle.GetConfig()
addr := []byte{0x01}
eval := evaluateAddressPoint(addr)
eval := EvaluateAddressPoint(addr)
b.ReportAllocs()
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
// be returned. If the tree is corrupted, an error will be returned.
func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
versionkey := t.cache.GetTreeKeyVersionCached(addr[:])
var (
acc = &types.StateAccount{}
values [][]byte
@ -86,7 +87,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error
)
switch n := t.root.(type) {
case *verkle.InternalNode:
values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver)
values, err = n.GetValuesAtStem(versionkey, t.nodeResolver)
if err != nil {
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,
// 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) {
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)
if err != nil {
return nil, err
@ -153,7 +154,7 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount)
switch n := t.root.(type) {
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 {
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 {
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)
}
@ -191,7 +192,7 @@ func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
}
switch n := t.root.(type) {
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 {
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.
func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error {
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)
}
@ -338,7 +339,7 @@ func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Has
groupOffset := (chunknr + 128) % 256
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
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]

View file

@ -57,7 +57,7 @@ var (
func TestVerkleTreeReadWrite(t *testing.T) {
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 {
if err := tr.UpdateAccount(addr, acct); err != nil {