Skip to content

Commit

Permalink
Unified point cache (ethereum#180)
Browse files Browse the repository at this point in the history
* Unified point cache

* Use cache for Try*Account

* alter Trie interface to use caching for slots (ethereum#181)

* alter Trie interface to use caching for slots

* fix: use a lock to protect the point cache (ethereum#185)

* use fastest non-master go-verkle version & pull trie/Verkle.go changes to use new api (ethereum#184)

* mod: update to fastest go-verkle version today

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* trie/verkle: use new batch serialization api

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix: TryDelete signature in unit tests

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>
  • Loading branch information
gballet and jsign authored Mar 22, 2023
1 parent 92ab166 commit 4bda873
Show file tree
Hide file tree
Showing 19 changed files with 175 additions and 192 deletions.
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
kvs := make(map[string][]byte)
keys := statedb.Witness().Keys()
for _, key := range keys {
v, err := vtr.TryGet(key)
v, err := vtr.GetWithHashedKey(key)
if err != nil {
panic(err)
}
Expand Down
28 changes: 6 additions & 22 deletions core/types/access_witness.go → core/state/access_witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@
// 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 types
package state

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/gballet/go-verkle"
)

type VerkleStem [31]byte
Expand Down Expand Up @@ -51,23 +50,21 @@ type AccessWitness struct {
// block.
InitialValue map[string][]byte

// Caches all the points that correspond to an address,
// so they are not recalculated.
addrToPoint map[string]*verkle.Point

// Caches which code chunks have been accessed, in order
// to reduce the number of times that GetTreeKeyCodeChunk
// is called.
CodeLocations map[string]map[uint64]struct{}

statedb *StateDB
}

func NewAccessWitness() *AccessWitness {
func NewAccessWitness(statedb *StateDB) *AccessWitness {
return &AccessWitness{
Branches: make(map[VerkleStem]Mode),
Chunks: make(map[common.Hash]Mode),
InitialValue: make(map[string][]byte),
addrToPoint: make(map[string]*verkle.Point),
CodeLocations: make(map[string]map[uint64]struct{}),
statedb: statedb,
}
}

Expand Down Expand Up @@ -246,28 +243,15 @@ func (aw *AccessWitness) Copy() *AccessWitness {
Branches: make(map[VerkleStem]Mode),
Chunks: make(map[common.Hash]Mode),
InitialValue: make(map[string][]byte),
addrToPoint: make(map[string]*verkle.Point),
}

naw.Merge(aw)

return naw
}

func (aw *AccessWitness) getTreeKeyHeader(addr []byte) *verkle.Point {
if point, ok := aw.addrToPoint[string(addr)]; ok {
return point
}

point := utils.EvaluateAddressPoint(addr)
aw.addrToPoint[string(addr)] = point
return point
}

func (aw *AccessWitness) GetTreeKeyVersionCached(addr []byte) []byte {
p := aw.getTreeKeyHeader(addr)
v := utils.PointToHash(p, utils.VersionLeafKey)
return v[:]
return aw.statedb.db.(*VerkleDB).addrToPoint.GetTreeKeyVersionCached(addr)
}

func (aw *AccessWitness) TouchAndChargeProofOfAbsence(addr []byte) uint64 {
Expand Down
20 changes: 15 additions & 5 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/gballet/go-verkle"
lru "github.com/hashicorp/golang-lru"
)
Expand Down Expand Up @@ -73,7 +74,7 @@ type Trie interface {
// TryGet returns the value for key stored in the trie. The value bytes must
// not be modified by the caller. If a node was not found in the database, a
// trie.MissingNodeError is returned.
TryGet(key []byte) ([]byte, error)
TryGet(address, key []byte) ([]byte, error)

// TryGetAccount abstract an account read from the trie.
TryGetAccount(key []byte) (*types.StateAccount, error)
Expand All @@ -82,14 +83,14 @@ type Trie interface {
// existing value is deleted from the trie. The value bytes must not be modified
// by the caller while they are stored in the trie. If a node was not found in the
// database, a trie.MissingNodeError is returned.
TryUpdate(key, value []byte) error
TryUpdate(address, key, value []byte) error

// TryUpdateAccount abstract an account write to the trie.
TryUpdateAccount(key []byte, account *types.StateAccount) error

// TryDelete removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned.
TryDelete(key []byte) error
TryDelete(address, key []byte) error

// TryDeleteAccount abstracts an account deletion from the trie.
TryDeleteAccount(key []byte) error
Expand Down Expand Up @@ -141,6 +142,7 @@ func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
diskdb: db,
codeSizeCache: csc,
codeCache: fastcache.New(codeCacheSize),
addrToPoint: utils.NewPointCache(),
}
}
return &cachingDB{
Expand Down Expand Up @@ -241,12 +243,20 @@ type VerkleDB struct {
diskdb ethdb.KeyValueStore
codeSizeCache *lru.Cache
codeCache *fastcache.Cache

// Caches all the points that correspond to an address,
// so they are not recalculated.
addrToPoint *utils.PointCache
}

func (db *VerkleDB) GetTreeKeyHeader(addr []byte) *verkle.Point {
return db.addrToPoint.GetTreeKeyHeader(addr)
}

// OpenTrie opens the main account trie.
func (db *VerkleDB) OpenTrie(root common.Hash) (Trie, error) {
if root == (common.Hash{}) || root == emptyRoot {
return trie.NewVerkleTrie(verkle.New(), db.db), nil
return trie.NewVerkleTrie(verkle.New(), db.db, db.addrToPoint), nil
}
payload, err := db.DiskDB().Get(root[:])
if err != nil {
Expand All @@ -257,7 +267,7 @@ func (db *VerkleDB) OpenTrie(root common.Hash) (Trie, error) {
if err != nil {
panic(err)
}
return trie.NewVerkleTrie(r, db.db), err
return trie.NewVerkleTrie(r, db.db, db.addrToPoint), err
}

// OpenStorageTrie opens the storage trie of an account.
Expand Down
25 changes: 4 additions & 21 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/gballet/go-verkle"
"github.com/holiman/uint256"
)

var emptyCodeHash = crypto.Keccak256(nil)
Expand Down Expand Up @@ -73,8 +70,6 @@ type stateObject struct {
data types.StateAccount
db *StateDB

pointEval *verkle.Point

// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
Expand Down Expand Up @@ -115,16 +110,11 @@ func newObject(db *StateDB, address common.Address, data types.StateAccount) *st
if data.Root == (common.Hash{}) {
data.Root = emptyRoot
}
var pointEval *verkle.Point
if db.GetTrie().IsVerkle() {
pointEval = trieUtils.EvaluateAddressPoint(address.Bytes())
}

return &stateObject{
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
pointEval: pointEval,
data: data,
originStorage: make(Storage),
pendingStorage: make(Storage),
Expand Down Expand Up @@ -235,7 +225,7 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
panic("verkle trees use the snapshot")
}
start := time.Now()
enc, err = s.getTrie(db).TryGet(key.Bytes())
enc, err = s.getTrie(db).TryGet(s.address[:], key.Bytes())
if metrics.EnabledExpensive {
s.db.StorageReads += time.Since(start)
}
Expand Down Expand Up @@ -351,23 +341,16 @@ func (s *stateObject) updateTrie(db Database) Trie {

var v []byte
if (value == common.Hash{}) {
if tr.IsVerkle() {
k := trieUtils.GetTreeKeyStorageSlotWithEvaluatedAddress(s.pointEval, new(uint256.Int).SetBytes(key[:]))
s.setError(tr.TryDelete(k))
//s.db.db.TrieDB().DiskDB().Delete(append(s.address[:], key[:]...))
} else {
s.setError(tr.TryDelete(key[:]))
}
s.setError(tr.TryDelete(s.address[:], key[:]))
s.db.StorageDeleted += 1
} else {
// Encoding []byte cannot fail, ok to ignore the error.
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
if !tr.IsVerkle() {
s.setError(tr.TryUpdate(key[:], v))
s.setError(tr.TryUpdate(s.address[:], key[:], v))
} else {
k := trieUtils.GetTreeKeyStorageSlotWithEvaluatedAddress(s.pointEval, new(uint256.Int).SetBytes(key[:]))
// Update the trie, with v as a value
s.setError(tr.TryUpdate(k, value[:]))
s.setError(tr.TryUpdate(s.address[:], key[:], value[:]))
}
s.db.StorageUpdated += 1
}
Expand Down
12 changes: 6 additions & 6 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ type StateDB struct {
// Per-transaction access list
accessList *accessList

witness *types.AccessWitness
witness *AccessWitness

// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
Expand Down Expand Up @@ -155,7 +155,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
hasher: crypto.NewKeccakState(),
}
if tr.IsVerkle() {
sdb.witness = types.NewAccessWitness()
sdb.witness = NewAccessWitness(sdb)
if sdb.snaps == nil {
snapconfig := snapshot.Config{
CacheSize: 256,
Expand Down Expand Up @@ -184,14 +184,14 @@ func (s *StateDB) Snaps() *snapshot.Tree {
return s.snaps
}

func (s *StateDB) Witness() *types.AccessWitness {
func (s *StateDB) Witness() *AccessWitness {
if s.witness == nil {
s.witness = types.NewAccessWitness()
s.witness = NewAccessWitness(s)
}
return s.witness
}

func (s *StateDB) SetWitness(aw *types.AccessWitness) {
func (s *StateDB) SetWitness(aw *AccessWitness) {
s.witness = aw
}

Expand Down Expand Up @@ -532,7 +532,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
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.GetTreeKeyCodeChunkWithEvaluatedAddress(obj.pointEval, uint256.NewInt(chunknr))
key = utils.GetTreeKeyCodeChunkWithEvaluatedAddress(obj.db.db.(*VerkleDB).GetTreeKeyHeader(obj.address[:]), uint256.NewInt(chunknr))
}
values[groupOffset] = chunks[i : i+32]

Expand Down
2 changes: 1 addition & 1 deletion core/state/trie_prefetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ func (sf *subfetcher) loop() {
if len(task) == len(common.Address{}) {
sf.trie.TryGetAccount(task)
} else {
sf.trie.TryGet(task)
sf.trie.TryGet(nil, task)
}
sf.seen[string(task)] = struct{}{}
}
Expand Down
2 changes: 1 addition & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, author *com
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
if config.IsCancun(blockNumber) {
txContext.Accesses = types.NewAccessWitness()
txContext.Accesses = state.NewAccessWitness(statedb)
}
evm.Reset(txContext, statedb)

Expand Down
12 changes: 6 additions & 6 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
Expand Down Expand Up @@ -86,7 +86,7 @@ type TxContext struct {
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE

Accesses *types.AccessWitness
Accesses *state.AccessWitness
}

// EVM is the Ethereum Virtual Machine base object and provides
Expand Down Expand Up @@ -129,9 +129,6 @@ type EVM struct {
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*.
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM {
if txCtx.Accesses == nil && chainConfig.IsCancun(blockCtx.BlockNumber) {
txCtx.Accesses = types.NewAccessWitness()
}
evm := &EVM{
Context: blockCtx,
TxContext: txCtx,
Expand All @@ -140,6 +137,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil),
}
if txCtx.Accesses == nil && chainConfig.IsCancun(blockCtx.BlockNumber) {
txCtx.Accesses = state.NewAccessWitness(evm.StateDB.(*state.StateDB))
}
evm.interpreter = NewEVMInterpreter(evm, config)
return evm
}
Expand All @@ -148,7 +148,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
// This is not threadsafe and should only be done very cautiously.
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
if txCtx.Accesses == nil && evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
txCtx.Accesses = types.NewAccessWitness()
txCtx.Accesses = state.NewAccessWitness(evm.StateDB.(*state.StateDB))
}
evm.TxContext = txCtx
evm.StateDB = statedb
Expand Down
5 changes: 3 additions & 2 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
Expand Down Expand Up @@ -383,7 +384,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
}

// touchChunkOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func touchChunkOnReadAndChargeGas(chunks trie.ChunkedCode, offset uint64, evals [][]byte, code []byte, accesses *types.AccessWitness, deployment bool) uint64 {
func touchChunkOnReadAndChargeGas(chunks trie.ChunkedCode, offset uint64, evals [][]byte, code []byte, accesses *state.AccessWitness, deployment bool) uint64 {
// note that in the case where the executed 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
Expand Down Expand Up @@ -413,7 +414,7 @@ func touchChunkOnReadAndChargeGas(chunks trie.ChunkedCode, offset uint64, evals
}

// touchEachChunksOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func touchEachChunksOnReadAndChargeGas(offset, size uint64, contract *Contract, code []byte, accesses *types.AccessWitness, deployment bool) uint64 {
func touchEachChunksOnReadAndChargeGas(offset, size uint64, contract *Contract, code []byte, accesses *state.AccessWitness, deployment bool) uint64 {
// note that in the case where the copied code is outside the range of the
// contract code but touches the last leaf with contract code in it,
// we don't include the last leaf of code in the AccessWitness. The
Expand Down
5 changes: 3 additions & 2 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -78,8 +79,8 @@ type StateDB interface {

ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error

Witness() *types.AccessWitness
SetWitness(*types.AccessWitness)
Witness() *state.AccessWitness
SetWitness(*state.AccessWitness)
}

// CallContext provides a basic interface for the EVM calling conventions. The EVM
Expand Down
Loading

0 comments on commit 4bda873

Please sign in to comment.