diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go
index a350e383a2..a99959e2ae 100644
--- a/consensus/beacon/consensus.go
+++ b/consensus/beacon/consensus.go
@@ -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.
}
diff --git a/core/error.go b/core/error.go
index 72cacf8c78..e8b7cc4c78 100644
--- a/core/error.go
+++ b/core/error.go
@@ -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")
diff --git a/core/state/access_list.go b/core/state/access_list.go
index 4194691345..30e32ecfea 100644
--- a/core/state/access_list.go
+++ b/core/state/access_list.go
@@ -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 }
diff --git a/core/state/access_witness.go b/core/state/access_witness.go
new file mode 100644
index 0000000000..07795952b2
--- /dev/null
+++ b/core/state/access_witness.go
@@ -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 .
+
+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< 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
+}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 3649b0ac58..58f1eff165 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -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)
}
}
diff --git a/core/state_processor.go b/core/state_processor.go
index 9e32ab4e56..520490a1f5 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -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)
}
diff --git a/core/state_transition.go b/core/state_transition.go
index 8fcf4c093d..378382c9c8 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -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{
diff --git a/core/vm/contract.go b/core/vm/contract.go
index 16b669ebca..10a2126457 100644
--- a/core/vm/contract.go
+++ b/core/vm/contract.go
@@ -56,6 +56,8 @@ type Contract struct {
CodeAddr *common.Address
Input []byte
+ IsDeployment bool
+
Gas uint64
value *uint256.Int
}
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 9f06b2818f..9ccd8a19f5 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -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
+}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 16cc854908..e993405421 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -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.
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index 4b141d8f9a..4a505b43e2 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -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
+}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index ac3ea4bcd6..13b187849c 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -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)))
diff --git a/core/vm/interface.go b/core/vm/interface.go
index 25bfa06720..2dfa610c23 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -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
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 1968289f4e..c3a2327d18 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -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)
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index 65716f9442..b357f4c5f8 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -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)
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index f420a24105..451fe4dbc3 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -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
diff --git a/params/verkle_params.go b/params/verkle_params.go
new file mode 100644
index 0000000000..93d4f7cd64
--- /dev/null
+++ b/params/verkle_params.go
@@ -0,0 +1,36 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package params
+
+// Verkle tree EIP: costs associated to witness accesses
+var (
+ WitnessBranchReadCost uint64 = 1900
+ WitnessChunkReadCost uint64 = 200
+ WitnessBranchWriteCost uint64 = 3000
+ WitnessChunkWriteCost uint64 = 500
+ WitnessChunkFillCost uint64 = 6200
+)
+
+// ClearVerkleWitnessCosts sets all witness costs to 0, which is necessary
+// for historical block replay simulations.
+func ClearVerkleWitnessCosts() {
+ WitnessBranchReadCost = 0
+ WitnessChunkReadCost = 0
+ WitnessBranchWriteCost = 0
+ WitnessChunkWriteCost = 0
+ WitnessChunkFillCost = 0
+}
diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go
index ce059edc64..d836166d9b 100644
--- a/trie/utils/verkle.go
+++ b/trie/utils/verkle.go
@@ -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