From 4bda873488939cdf2cc5920dc1858ca1761ece76 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 22 Mar 2023 08:57:08 +0100 Subject: [PATCH] Unified point cache (#180) * Unified point cache * Use cache for Try*Account * alter Trie interface to use caching for slots (#181) * alter Trie interface to use caching for slots * fix: use a lock to protect the point cache (#185) * use fastest non-master go-verkle version & pull trie/Verkle.go changes to use new api (#184) * mod: update to fastest go-verkle version today Signed-off-by: Ignacio Hagopian * trie/verkle: use new batch serialization api Signed-off-by: Ignacio Hagopian --------- Signed-off-by: Ignacio Hagopian --------- Signed-off-by: Ignacio Hagopian Co-authored-by: Ignacio Hagopian * fix: TryDelete signature in unit tests --------- Signed-off-by: Ignacio Hagopian Co-authored-by: Ignacio Hagopian --- core/chain_makers.go | 2 +- core/{types => state}/access_witness.go | 28 +---- core/state/database.go | 20 ++- core/state/state_object.go | 25 +--- core/state/statedb.go | 12 +- core/state/trie_prefetcher.go | 2 +- core/state_processor.go | 2 +- core/vm/evm.go | 12 +- core/vm/instructions.go | 5 +- core/vm/interface.go | 5 +- go.mod | 6 +- go.sum | 20 +-- light/trie.go | 10 +- trie/secure_trie.go | 16 +-- trie/trie.go | 4 +- trie/trie_test.go | 4 +- trie/util_test.go | 2 +- trie/utils/verkle.go | 34 +++++ trie/verkle.go | 158 ++++++++++-------------- 19 files changed, 175 insertions(+), 192 deletions(-) rename core/{types => state}/access_witness.go (94%) diff --git a/core/chain_makers.go b/core/chain_makers.go index 5b9bbec284d5..531079ccaeb2 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -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) } diff --git a/core/types/access_witness.go b/core/state/access_witness.go similarity index 94% rename from core/types/access_witness.go rename to core/state/access_witness.go index a0194a74b1ab..789bbbc27e3a 100644 --- a/core/types/access_witness.go +++ b/core/state/access_witness.go @@ -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 . -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 @@ -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, } } @@ -246,7 +243,6 @@ 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) @@ -254,20 +250,8 @@ func (aw *AccessWitness) Copy() *AccessWitness { 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 { diff --git a/core/state/database.go b/core/state/database.go index a74a1cd2e103..77c80f10dd9c 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -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" ) @@ -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) @@ -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 @@ -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{ @@ -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 { @@ -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. diff --git a/core/state/state_object.go b/core/state/state_object.go index 5a74765165bc..1b4f60a382ee 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -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) @@ -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 @@ -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), @@ -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) } @@ -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 } diff --git a/core/state/statedb.go b/core/state/statedb.go index 8bff1592f663..3171cdff3caa 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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. @@ -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, @@ -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 } @@ -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] diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 2e16f587ce56..ad2383103ab4 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -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{}{} } diff --git a/core/state_processor.go b/core/state_processor.go index d73b13e669e7..94045c742ce7 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -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) diff --git a/core/vm/evm.go b/core/vm/evm.go index 44e9d820bebe..4721751d163e 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -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" @@ -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 @@ -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, @@ -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 } @@ -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 diff --git a/core/vm/instructions.go b/core/vm/instructions.go index fe83beb1f55b..d1aec069873c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.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/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" @@ -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 @@ -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 diff --git a/core/vm/interface.go b/core/vm/interface.go index 37a7eab63912..db10b9af5ce5 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" ) @@ -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 diff --git a/go.mod b/go.mod index ed075c50f249..94f82043bf54 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.14.0 github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f - github.com/crate-crypto/go-ipa v0.0.0-20230202201618-2e6f5bfc5401 + github.com/crate-crypto/go-ipa v0.0.0-20230315201338-1643fdc2ead8 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.8.0 github.com/docker/docker v1.6.2 @@ -23,7 +23,7 @@ require ( github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/gballet/go-verkle v0.0.0-20230303104313-a4243d1136b3 + github.com/gballet/go-verkle v0.0.0-20230317174103-141354da6b11 github.com/go-stack/stack v1.8.0 github.com/golang-jwt/jwt/v4 v4.3.0 github.com/golang/protobuf v1.5.2 @@ -60,7 +60,7 @@ require ( github.com/urfave/cli/v2 v2.10.2 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + golang.org/x/sync v0.1.0 golang.org/x/sys v0.6.0 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 diff --git a/go.sum b/go.sum index ac5302ca67a8..4a3b92eff6df 100644 --- a/go.sum +++ b/go.sum @@ -86,10 +86,8 @@ github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-ipa v0.0.0-20221111143132-9aa5d42120bc h1:mtR7MuscVeP/s0/ERWA2uSr5QOrRYy1pdvZqG1USfXI= -github.com/crate-crypto/go-ipa v0.0.0-20221111143132-9aa5d42120bc/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI= -github.com/crate-crypto/go-ipa v0.0.0-20230202201618-2e6f5bfc5401 h1:TSXRL74LZ7R2xWOI1M0mz9E56PiPKGlSw0drgR8g7CE= -github.com/crate-crypto/go-ipa v0.0.0-20230202201618-2e6f5bfc5401/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI= +github.com/crate-crypto/go-ipa v0.0.0-20230315201338-1643fdc2ead8 h1:2EBbIwPDRqlCD2K34Eojyy0x9d3RhOuHAZfbQm508X8= +github.com/crate-crypto/go-ipa v0.0.0-20230315201338-1643fdc2ead8/go.mod h1:gzbVz57IDJgQ9rLQwfSk696JGWof8ftznEL9GoAv3NI= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -137,12 +135,8 @@ github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgx github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gballet/go-verkle v0.0.0-20221122140954-75ceda26b7db h1:YvtZfE11QEYWPjsQCyZLoZCGMsxJs9mTEbhF3MnM32Q= -github.com/gballet/go-verkle v0.0.0-20221122140954-75ceda26b7db/go.mod h1:DMDd04jjQgdynaAwbEgiRERIGpC8fDjx0+y06an7Psg= -github.com/gballet/go-verkle v0.0.0-20221129125207-513116151b28 h1:UbB7D2R1OQCkNFX+LYoo2pHZ0u5LhwR9ldUsY4ZbZqI= -github.com/gballet/go-verkle v0.0.0-20221129125207-513116151b28/go.mod h1:DMDd04jjQgdynaAwbEgiRERIGpC8fDjx0+y06an7Psg= -github.com/gballet/go-verkle v0.0.0-20230303104313-a4243d1136b3 h1:UfRrJQF6ohVQGdi3MTU/8dPnayaIR2RuulZ55gUYXIE= -github.com/gballet/go-verkle v0.0.0-20230303104313-a4243d1136b3/go.mod h1:NR+n/LUx+m5SyVTRObiuNdJ50q309MGPD0VrIMDZJuc= +github.com/gballet/go-verkle v0.0.0-20230317174103-141354da6b11 h1:x4hiQFgr1SlqR4IoAZiXLFZK4L7KbibqkORqa1fwKp8= +github.com/gballet/go-verkle v0.0.0-20230317174103-141354da6b11/go.mod h1:IyOnn1kujMWaT+wet/6Ix1BtvYwateOBy9puuWH/8sw= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -516,8 +510,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -558,10 +553,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc= golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= diff --git a/light/trie.go b/light/trie.go index abb7e6557732..83c9f6910661 100644 --- a/light/trie.go +++ b/light/trie.go @@ -105,7 +105,7 @@ type odrTrie struct { trie *trie.Trie } -func (t *odrTrie) TryGet(key []byte) ([]byte, error) { +func (t *odrTrie) TryGet(_, key []byte) ([]byte, error) { key = crypto.Keccak256(key) var res []byte err := t.do(key, func() (err error) { @@ -142,17 +142,17 @@ func (t *odrTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { }) } -func (t *odrTrie) TryUpdate(key, value []byte) error { +func (t *odrTrie) TryUpdate(_, key, value []byte) error { key = crypto.Keccak256(key) return t.do(key, func() error { return t.trie.TryUpdate(key, value) }) } -func (t *odrTrie) TryDelete(key []byte) error { +func (t *odrTrie) TryDelete(_, key []byte) error { key = crypto.Keccak256(key) return t.do(key, func() error { - return t.trie.TryDelete(key) + return t.trie.TryDelete(nil, key) }) } @@ -160,7 +160,7 @@ func (t *odrTrie) TryDelete(key []byte) error { func (t *odrTrie) TryDeleteAccount(key []byte) error { key = crypto.Keccak256(key) return t.do(key, func() error { - return t.trie.TryDelete(key) + return t.trie.TryDelete(nil, key) }) } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 68b0a07cbfa0..f48b3a6ca3b1 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -75,7 +75,7 @@ func NewStateTrie(id *ID, db *Database) (*StateTrie, error) { // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. func (t *StateTrie) Get(key []byte) []byte { - res, err := t.TryGet(key) + res, err := t.TryGet(nil, key) if err != nil { log.Error("Unhandled trie error in StateTrie.Get", "err", err) } @@ -86,7 +86,7 @@ func (t *StateTrie) Get(key []byte) []byte { // The value bytes must not be modified by the caller. // If the specified node is not in the trie, nil will be returned. // If a trie node is not found in the database, a MissingNodeError is returned. -func (t *StateTrie) TryGet(key []byte) ([]byte, error) { +func (t *StateTrie) TryGet(_, key []byte) ([]byte, error) { return t.trie.TryGet(t.hashKey(key)) } @@ -131,7 +131,7 @@ func (t *StateTrie) TryGetNode(path []byte) ([]byte, int, error) { // The value bytes must not be modified by the caller while they are // stored in the trie. func (t *StateTrie) Update(key, value []byte) { - if err := t.TryUpdate(key, value); err != nil { + if err := t.TryUpdate(nil, key, value); err != nil { log.Error("Unhandled trie error in StateTrie.Update", "err", err) } } @@ -144,7 +144,7 @@ func (t *StateTrie) Update(key, value []byte) { // stored in the trie. // // If a node is not found in the database, a MissingNodeError is returned. -func (t *StateTrie) TryUpdate(key, value []byte) error { +func (t *StateTrie) TryUpdate(_, key, value []byte) error { hk := t.hashKey(key) err := t.trie.TryUpdate(hk, value) if err != nil { @@ -171,7 +171,7 @@ func (t *StateTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error // Delete removes any existing value for key from the trie. func (t *StateTrie) Delete(key []byte) { - if err := t.TryDelete(key); err != nil { + if err := t.TryDelete(nil, key); err != nil { log.Error("Unhandled trie error in StateTrie.Delete", "err", err) } } @@ -179,17 +179,17 @@ func (t *StateTrie) Delete(key []byte) { // TryDelete removes any existing value for key from the trie. // If the specified trie node is not in the trie, nothing will be changed. // If a node is not found in the database, a MissingNodeError is returned. -func (t *StateTrie) TryDelete(key []byte) error { +func (t *StateTrie) TryDelete(_, key []byte) error { hk := t.hashKey(key) delete(t.getSecKeyCache(), string(hk)) - return t.trie.TryDelete(hk) + return t.trie.TryDelete(nil, hk) } // TryDeleteAccount abstracts an account deletion from the trie. func (t *StateTrie) TryDeleteAccount(key []byte) error { hk := t.hashKey(key) delete(t.getSecKeyCache(), string(hk)) - return t.trie.TryDelete(hk) + return t.trie.TryDelete(nil, hk) } // GetKey returns the sha3 preimage of a hashed key that was diff --git a/trie/trie.go b/trie/trie.go index bec6a1cc7891..49b1d9d8d4cd 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -389,14 +389,14 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error // Delete removes any existing value for key from the trie. func (t *Trie) Delete(key []byte) { - if err := t.TryDelete(key); err != nil { + if err := t.TryDelete(nil, key); err != nil { log.Error("Unhandled trie error in Trie.Delete", "err", err) } } // TryDelete removes any existing value for key from the trie. // If a node was not found in the database, a MissingNodeError is returned. -func (t *Trie) TryDelete(key []byte) error { +func (t *Trie) TryDelete(_, key []byte) error { t.unhashed++ k := keybytesToHex(key) _, n, err := t.delete(t.root, nil, k) diff --git a/trie/trie_test.go b/trie/trie_test.go index d2a599ffdd64..7594f388090b 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -111,7 +111,7 @@ func testMissingNode(t *testing.T, memonly bool) { t.Errorf("Unexpected error: %v", err) } trie, _ = New(TrieID(root), triedb) - err = trie.TryDelete([]byte("123456")) + err = trie.TryDelete(nil, []byte("123456")) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -144,7 +144,7 @@ func testMissingNode(t *testing.T, memonly bool) { t.Errorf("Wrong error: %v", err) } trie, _ = New(TrieID(root), triedb) - err = trie.TryDelete([]byte("123456")) + err = trie.TryDelete(nil, []byte("123456")) if _, ok := err.(*MissingNodeError); !ok { t.Errorf("Wrong error: %v", err) } diff --git a/trie/util_test.go b/trie/util_test.go index e0e314205035..ea9a6a3d5531 100644 --- a/trie/util_test.go +++ b/trie/util_test.go @@ -225,7 +225,7 @@ func TestTrieTracePrevValue(t *testing.T) { trie.resolveAndTrack(root.Bytes(), nil) for _, val := range vals { - trie.TryDelete([]byte(val.k)) + trie.TryDelete(nil, []byte(val.k)) } paths, blobs = trie.tracer.prevList() if len(paths) != len(seen) || len(blobs) != len(seen) { diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index e84c170f7f8b..13361e5bd553 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -17,6 +17,8 @@ package utils import ( + "sync" + "github.com/crate-crypto/go-ipa/bandersnatch/fr" "github.com/gballet/go-verkle" "github.com/holiman/uint256" @@ -41,6 +43,38 @@ var ( getTreePolyIndex0Point *verkle.Point ) +type PointCache struct { + cache map[string]*verkle.Point + lock sync.RWMutex +} + +func NewPointCache() *PointCache { + return &PointCache{ + cache: make(map[string]*verkle.Point), + } +} + +func (pc *PointCache) GetTreeKeyHeader(addr []byte) *verkle.Point { + pc.lock.RLock() + point, ok := pc.cache[string(addr)] + pc.lock.RUnlock() + if ok { + return point + } + + point = EvaluateAddressPoint(addr) + pc.lock.Lock() + pc.cache[string(addr)] = point + pc.lock.Unlock() + return point +} + +func (pc *PointCache) GetTreeKeyVersionCached(addr []byte) []byte { + p := pc.GetTreeKeyHeader(addr) + v := PointToHash(p, VersionLeafKey) + return v[:] +} + func init() { // The byte array is the Marshalled output of the point computed as such: //cfg, _ := verkle.GetConfig() diff --git a/trie/verkle.go b/trie/verkle.go index b4218eef6077..8d8fc089230a 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -28,23 +28,26 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie/utils" "github.com/gballet/go-verkle" + "github.com/holiman/uint256" ) // VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie // interface so that Verkle trees can be reused verbatim. type VerkleTrie struct { - root verkle.VerkleNode - db *Database + root verkle.VerkleNode + db *Database + pointCache *utils.PointCache } func (vt *VerkleTrie) ToDot() string { return verkle.ToDot(vt.root) } -func NewVerkleTrie(root verkle.VerkleNode, db *Database) *VerkleTrie { +func NewVerkleTrie(root verkle.VerkleNode, db *Database, pointCache *utils.PointCache) *VerkleTrie { return &VerkleTrie{ - root: root, - db: db, + root: root, + db: db, + pointCache: pointCache, } } @@ -59,55 +62,45 @@ func (trie *VerkleTrie) GetKey(key []byte) []byte { // 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. -func (trie *VerkleTrie) TryGet(key []byte) ([]byte, error) { +func (trie *VerkleTrie) TryGet(addr, key []byte) ([]byte, error) { + pointEval := trie.pointCache.GetTreeKeyHeader(key) + k := utils.GetTreeKeyStorageSlotWithEvaluatedAddress(pointEval, new(uint256.Int).SetBytes(key)) + return trie.root.Get(k, trie.db.diskdb.Get) +} + +// GetWithHashedKey returns the value, assuming that the key has already +// been hashed. +func (trie *VerkleTrie) GetWithHashedKey(key []byte) ([]byte, error) { return trie.root.Get(key, trie.db.diskdb.Get) } func (t *VerkleTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { var ( - err error - balancekey, cskey, ckkey, noncekey [32]byte - acc *types.StateAccount = &types.StateAccount{} + acc *types.StateAccount = &types.StateAccount{} + resolver = func(hash []byte) ([]byte, error) { + return t.db.diskdb.Get(hash) + } ) - - // Only evaluate the polynomial once - // TODO implement GetStem as well, so that the trie is only traversed once - // it's not as bad because the commitments aren't updated, but it could, in - // theory, have to deserialize some more nodes (if there is some sort of cache - // dump) - versionkey := utils.GetTreeKeyVersion(key[:]) - copy(balancekey[:], versionkey) - balancekey[31] = utils.BalanceLeafKey - copy(noncekey[:], versionkey) - noncekey[31] = utils.NonceLeafKey - copy(cskey[:], versionkey) - cskey[31] = utils.CodeSizeLeafKey - copy(ckkey[:], versionkey) - ckkey[31] = utils.CodeKeccakLeafKey - - nonce, err := t.TryGet(noncekey[:]) + versionkey := t.pointCache.GetTreeKeyVersionCached(key) + values, err := t.root.(*verkle.InternalNode).GetStem(versionkey[:31], resolver) if err != nil { return nil, fmt.Errorf("TryGetAccount (%x) error: %v", key, err) } - if len(nonce) > 0 { - acc.Nonce = binary.LittleEndian.Uint64(nonce) + + if values == nil { + return nil, nil } - balance, err := t.TryGet(balancekey[:]) - if err != nil { - return nil, fmt.Errorf("updateStateObject (%x) error: %v", key, err) + if len(values[utils.NonceLeafKey]) > 0 { + acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey]) } + balance := values[utils.BalanceLeafKey] if len(balance) > 0 { for i := 0; i < len(balance)/2; i++ { balance[len(balance)-i-1], balance[i] = balance[i], balance[len(balance)-i-1] } } acc.Balance = new(big.Int).SetBytes(balance[:]) - ck, err := t.TryGet(ckkey[:]) - if err != nil { - return nil, fmt.Errorf("updateStateObject (%x) error: %v", key, err) - } - acc.CodeHash = ck - + acc.CodeHash = values[utils.CodeKeccakLeafKey] // TODO fix the code size as well return acc, nil @@ -120,7 +113,7 @@ func (t *VerkleTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error err error nonce, balance [32]byte values = make([][]byte, verkle.NodeWidth) - stem = utils.GetTreeKeyVersion(key[:]) + stem = t.pointCache.GetTreeKeyVersionCached(key[:]) ) // Only evaluate the polynomial once @@ -156,10 +149,9 @@ func (t *VerkleTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error } func (trie *VerkleTrie) TryUpdateStem(key []byte, values [][]byte) error { - resolver := - func(h []byte) ([]byte, error) { - return trie.db.diskdb.Get(h) - } + resolver := func(h []byte) ([]byte, error) { + return trie.db.diskdb.Get(h) + } switch root := trie.root.(type) { case *verkle.InternalNode: return root.InsertStem(key, values, resolver) @@ -174,43 +166,38 @@ func (trie *VerkleTrie) TryUpdateStem(key []byte, values [][]byte) error { // 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. -func (trie *VerkleTrie) TryUpdate(key, value []byte) error { +func (trie *VerkleTrie) TryUpdate(address, key, value []byte) error { + k := utils.GetTreeKeyStorageSlotWithEvaluatedAddress(trie.pointCache.GetTreeKeyHeader(address), new(uint256.Int).SetBytes(key[:])) var v [32]byte copy(v[:], value[:]) - return trie.root.Insert(key, v[:], func(h []byte) ([]byte, error) { + return trie.root.Insert(k, v[:], func(h []byte) ([]byte, error) { return trie.db.diskdb.Get(h) }) } func (t *VerkleTrie) TryDeleteAccount(key []byte) error { var ( - err error - balancekey, cskey, ckkey, noncekey [32]byte + err error + values = make([][]byte, verkle.NodeWidth) + stem = t.pointCache.GetTreeKeyVersionCached(key[:]) ) - // Only evaluate the polynomial once - // TODO InsertStem with overwrite of values 0 - versionkey := utils.GetTreeKeyVersion(key[:]) - copy(balancekey[:], versionkey) - balancekey[31] = utils.BalanceLeafKey - copy(noncekey[:], versionkey) - noncekey[31] = utils.NonceLeafKey - copy(cskey[:], versionkey) - cskey[31] = utils.CodeSizeLeafKey - copy(ckkey[:], versionkey) - ckkey[31] = utils.CodeKeccakLeafKey - - if err = t.TryDelete(versionkey); err != nil { - return fmt.Errorf("updateStateObject (%x) error: %v", key, err) + for i := 0; i < verkle.NodeWidth; i++ { + values[i] = zero[:] } - if err = t.TryDelete(noncekey[:]); err != nil { - return fmt.Errorf("updateStateObject (%x) error: %v", key, err) + + resolver := func(hash []byte) ([]byte, error) { + return t.db.diskdb.Get(hash) } - if err = t.TryDelete(balancekey[:]); err != nil { - return fmt.Errorf("updateStateObject (%x) error: %v", key, err) + + switch root := t.root.(type) { + case *verkle.InternalNode: + err = root.InsertStem(stem, values, resolver) + case *verkle.StatelessNode: + err = root.InsertAtStem(stem, values, resolver, true) } - if err = t.TryDelete(ckkey[:]); err != nil { - return fmt.Errorf("updateStateObject (%x) error: %v", key, err) + if err != nil { + return fmt.Errorf("TryDeleteAccount (%x) error: %v", key, err) } // TODO figure out if the code size needs to be updated, too @@ -219,8 +206,10 @@ func (t *VerkleTrie) TryDeleteAccount(key []byte) 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. -func (trie *VerkleTrie) TryDelete(key []byte) error { - return trie.root.Delete(key, func(h []byte) ([]byte, error) { +func (trie *VerkleTrie) TryDelete(addr, key []byte) error { + pointEval := trie.pointCache.GetTreeKeyHeader(key) + k := utils.GetTreeKeyStorageSlotWithEvaluatedAddress(pointEval, new(uint256.Int).SetBytes(key)) + return trie.root.Delete(k, func(h []byte) ([]byte, error) { return trie.db.diskdb.Get(h) }) } @@ -239,33 +228,22 @@ func nodeToDBKey(n verkle.VerkleNode) []byte { // Commit writes all nodes to the trie's memory database, tracking the internal // and external (for account tries) references. func (trie *VerkleTrie) Commit(_ bool) (common.Hash, *NodeSet, error) { - flush := make(chan verkle.VerkleNode) - resolver := func(n verkle.VerkleNode) { - flush <- n + root, ok := trie.root.(*verkle.InternalNode) + if !ok { + return common.Hash{}, nil, errors.New("unexpected root node type") + } + nodes, err := root.BatchSerialize() + if err != nil { + return common.Hash{}, nil, fmt.Errorf("serializing tree nodes: %s", err) } - go func() { - switch root := trie.root.(type) { - case *verkle.InternalNode: - root.Flush(resolver) - case *verkle.StatelessNode: - root.Flush(resolver) - } - close(flush) - }() - var commitCount int - for n := range flush { - commitCount += 1 - value, err := n.Serialize() - if err != nil { - panic(err) - } - if err := trie.db.diskdb.Put(nodeToDBKey(n), value); err != nil { - return common.Hash{}, NewNodeSet(common.Hash{}), err + for _, node := range nodes { + if err := trie.db.diskdb.Put(node.CommitmentBytes[:], node.SerializedBytes); err != nil { + return common.Hash{}, nil, fmt.Errorf("put node to disk: %s", err) } } - return trie.Hash(), NewNodeSet(common.Hash{}), nil + return nodes[0].CommitmentBytes, NewNodeSet(common.Hash{}), nil } // NodeIterator returns an iterator that returns nodes of the trie. Iteration