Skip to content

Commit

Permalink
core,trie,eth,cmd: rework preimage store (#533)
Browse files Browse the repository at this point in the history
* core,trie,eth,cmd: rework preimage store

* ci: trigger unittest path-base-implementing
  • Loading branch information
huyngopt1994 authored Aug 19, 2024
1 parent 0325851 commit ba5e4a4
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 88 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
pull_request:
branches:
- master
- path-base-implementing

concurrency:
group: ${{ github.head_ref || github.run_id }}
Expand Down
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
}

func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB {
sdb := state.NewDatabase(db)
sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true})
statedb, _ := state.New(common.Hash{}, sdb, nil)
for addr, a := range accounts {
statedb.SetCode(addr, a.Code)
Expand Down
3 changes: 2 additions & 1 deletion core/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
)

type stateTest struct {
Expand All @@ -40,7 +41,7 @@ func newStateTest() *stateTest {

func TestDump(t *testing.T) {
db := rawdb.NewMemoryDatabase()
sdb, _ := New(common.Hash{}, NewDatabaseWithConfig(db, nil), nil)
sdb, _ := New(common.Hash{}, NewDatabaseWithConfig(db, &trie.Config{Preimages: true}), nil)
s := &stateTest{db: db, state: sdb}

// generate a few entries
Expand Down
3 changes: 2 additions & 1 deletion eth/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/trie"
)

var dumper = spew.ConfigState{Indent: " "}
Expand Down Expand Up @@ -66,7 +67,7 @@ func TestAccountRange(t *testing.T) {
t.Parallel()

var (
statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil)
statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true})
state, _ = state.New(common.Hash{}, statedb, nil)
addrs = [AccountRangeMaxResults * 2]common.Address{}
m = map[common.Address]bool{}
Expand Down
97 changes: 20 additions & 77 deletions trie/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ type Database struct {
oldest common.Hash // Oldest tracked node, flush-list head
newest common.Hash // Newest tracked node, flush-list tail

preimages map[common.Hash][]byte // Preimages of nodes from the secure trie

gctime time.Duration // Time spent on garbage collection since last commit
gcnodes uint64 // Nodes garbage collected since last commit
gcsize common.StorageSize // Data storage garbage collected since last commit
Expand All @@ -84,11 +82,10 @@ type Database struct {
flushnodes uint64 // Nodes flushed since last commit
flushsize common.StorageSize // Data storage flushed since last commit

dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. metadata)
childrenSize common.StorageSize // Storage size of the external children tracking
preimagesSize common.StorageSize // Storage size of the preimages cache

lock sync.RWMutex
dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. metadata)
childrenSize common.StorageSize // Storage size of the external children tracking
preimages *preimageStore // Store for caching preimages of trie nodes
lock sync.RWMutex
}

// rawNode is a simple binary blob used to differentiate between collapsed trie
Expand Down Expand Up @@ -298,15 +295,18 @@ func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database
cleans = fastcache.LoadFromFileOrNew(config.Journal, config.Cache*1024*1024)
}
}
var preimage *preimageStore
if config != nil && config.Preimages {
preimage = newPreimageStore(diskdb)
}

db := &Database{
diskdb: diskdb,
cleans: cleans,
dirties: map[common.Hash]*cachedNode{{}: {
children: make(map[common.Hash]uint16),
}},
}
if config == nil || config.Preimages { // TODO(karalabe): Flip to default off in the future
db.preimages = make(map[common.Hash][]byte)
preimages: preimage,
}
return db
}
Expand Down Expand Up @@ -349,24 +349,6 @@ func (db *Database) insert(hash common.Hash, size int, node node) {
db.dirtiesSize += common.StorageSize(common.HashLength + entry.size)
}

// insertPreimage writes a new trie node pre-image to the memory database if it's
// yet unknown. The method will NOT make a copy of the slice,
// only use if the preimage will NOT be changed later on.
//
// Note, this method assumes that the database's lock is held!
func (db *Database) insertPreimage(hash common.Hash, preimage []byte) {
// Short circuit if preimage collection is disabled
if db.preimages == nil {
return
}
// Track the preimage if a yet unknown one
if _, ok := db.preimages[hash]; ok {
return
}
db.preimages[hash] = preimage
db.preimagesSize += common.StorageSize(common.HashLength + len(preimage))
}

// node retrieves a cached trie node from memory, or returns nil if none can be
// found in the memory cache.
func (db *Database) node(hash common.Hash) node {
Expand Down Expand Up @@ -443,24 +425,6 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
return nil, errors.New("not found")
}

// preimage retrieves a cached trie node pre-image from memory. If it cannot be
// found cached, the method queries the persistent database for the content.
func (db *Database) preimage(hash common.Hash) []byte {
// Short circuit if preimage collection is disabled
if db.preimages == nil {
return nil
}
// Retrieve the node from cache if available
db.lock.RLock()
preimage := db.preimages[hash]
db.lock.RUnlock()

if preimage != nil {
return preimage
}
return rawdb.ReadPreimage(db.diskdb, hash)
}

// Nodes retrieves the hashes of all the nodes cached within the memory database.
// This method is extremely expensive and should only be used to validate internal
// states in test code.
Expand Down Expand Up @@ -605,20 +569,10 @@ func (db *Database) Cap(limit common.StorageSize) error {

// If the preimage cache got large enough, push to disk. If it's still small
// leave for later to deduplicate writes.
flushPreimages := db.preimagesSize > 4*1024*1024
if flushPreimages {
if db.preimages == nil {
log.Error("Attempted to write preimages whilst disabled")
} else {
rawdb.WritePreimages(batch, db.preimages)
if batch.ValueSize() > ethdb.IdealBatchSize {
if err := batch.Write(); err != nil {
return err
}
batch.Reset()
}
}
if db.preimages != nil {
db.preimages.commit(false)
}

// Keep committing nodes from the flush-list until we're below allowance
oldest := db.oldest
for size > limit && oldest != (common.Hash{}) {
Expand Down Expand Up @@ -652,13 +606,6 @@ func (db *Database) Cap(limit common.StorageSize) error {
db.lock.Lock()
defer db.lock.Unlock()

if flushPreimages {
if db.preimages == nil {
log.Error("Attempted to reset preimage cache whilst disabled")
} else {
db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0
}
}
for db.oldest != oldest {
node := db.dirties[db.oldest]
delete(db.dirties, db.oldest)
Expand Down Expand Up @@ -702,13 +649,7 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H

// Move all of the accumulated preimages into a write batch
if db.preimages != nil {
rawdb.WritePreimages(batch, db.preimages)
// Since we're going to replay trie node writes into the clean cache, flush out
// any batched pre-images before continuing.
if err := batch.Write(); err != nil {
return err
}
batch.Reset()
db.preimages.commit(true)
}
// Move the trie itself into the batch, flushing if enough data is accumulated
nodes, storage := len(db.dirties), db.dirtiesSize
Expand All @@ -731,9 +672,7 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H
batch.Reset()

// Reset the storage counters and bumped metrics
if db.preimages != nil {
db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0
}

memcacheCommitTimeTimer.Update(time.Since(start))
memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize))
memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties)))
Expand Down Expand Up @@ -845,7 +784,11 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize) {
// counted.
var metadataSize = common.StorageSize((len(db.dirties) - 1) * cachedNodeSize)
var metarootRefs = common.StorageSize(len(db.dirties[common.Hash{}].children) * (common.HashLength + 2))
return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, db.preimagesSize
var preimageSize common.StorageSize
if db.preimages != nil {
preimageSize = db.preimages.size()
}
return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimageSize
}

// saveCache saves clean state cache to given directory path
Expand Down
91 changes: 91 additions & 0 deletions trie/preimages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package trie

import (
"sync"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
)

// preimageStore is the store for caching preimages of node key.
type preimageStore struct {
lock sync.RWMutex
disk ethdb.KeyValueStore
preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
preimagesSize common.StorageSize // Storage size of the preimages cache
}

// newPreimageStore initializes the store for caching preimages.
func newPreimageStore(disk ethdb.KeyValueStore) *preimageStore {
return &preimageStore{
disk: disk,
preimages: make(map[common.Hash][]byte),
}
}

func (store *preimageStore) insertPreimage(preimages map[common.Hash][]byte) {
store.lock.Lock()
defer store.lock.Unlock()

for hash, preimage := range preimages {
if _, ok := store.preimages[hash]; ok {
continue
}
store.preimages[hash] = preimage
store.preimagesSize += common.StorageSize(common.HashLength + len(preimage))
}
}

func (store *preimageStore) preimage(hash common.Hash) []byte {
// Lock the store for reading
store.lock.RLock()
preimage := store.preimages[hash]
store.lock.RUnlock()
if preimage != nil {
return preimage
}
// Incase preimage is not existed in memory, then read from disk.
return rawdb.ReadPreimage(store.disk, hash)
}

func (store *preimageStore) commit(force bool) error {
store.lock.Lock()
defer store.lock.Unlock()

// If preimages size is less than 4MB and not forced to commit, then return.
if store.preimagesSize <= 4*1024*1024 && !force {
return nil
}

batch := store.disk.NewBatch()
rawdb.WritePreimages(batch, store.preimages)
if err := batch.Write(); err != nil {
return err
}
store.preimages, store.preimagesSize = make(map[common.Hash][]byte), 0
return nil
}

func (store *preimageStore) size() common.StorageSize {
store.lock.RLock()
defer store.lock.RUnlock()

return store.preimagesSize
}
25 changes: 17 additions & 8 deletions trie/secure_trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
// SecureTrie is not safe for concurrent use.
type SecureTrie struct {
trie Trie
preimages *preimageStore
hashKeyBuf [common.HashLength]byte
secKeyCache map[string][]byte
secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch
Expand All @@ -61,7 +62,7 @@ func NewSecure(root common.Hash, db *Database) (*SecureTrie, error) {
if err != nil {
return nil, err
}
return &SecureTrie{trie: *trie}, nil
return &SecureTrie{trie: *trie, preimages: db.preimages}, nil
}

// Get returns the value for key stored in the trie.
Expand Down Expand Up @@ -153,7 +154,11 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
return key
}
return t.trie.db.preimage(common.BytesToHash(shaKey))
if t.preimages == nil {
return nil
}

return t.preimages.preimage(common.BytesToHash(shaKey))
}

// Commit writes all nodes and the secure hash pre-images to the trie's database.
Expand All @@ -164,12 +169,13 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
func (t *SecureTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) {
// Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
if t.trie.db.preimages != nil { // Ugly direct check but avoids the below write lock
t.trie.db.lock.Lock()
if t.preimages != nil { // Ugly direct check but avoids the below write lock
preimages := make(map[common.Hash][]byte)

for hk, key := range t.secKeyCache {
t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key)
preimages[common.BytesToHash([]byte(hk))] = key
}
t.trie.db.lock.Unlock()
t.preimages.insertPreimage(preimages)
}
t.secKeyCache = make(map[string][]byte)
}
Expand All @@ -185,8 +191,11 @@ func (t *SecureTrie) Hash() common.Hash {

// Copy returns a copy of SecureTrie.
func (t *SecureTrie) Copy() *SecureTrie {
cpy := *t
return &cpy
return &SecureTrie{
trie: *t.trie.Copy(),
preimages: t.preimages,
secKeyCache: t.secKeyCache,
}
}

// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
Expand Down
9 changes: 9 additions & 0 deletions trie/trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,3 +587,12 @@ func (t *Trie) Reset() {
t.root = nil
t.unhashed = 0
}

// Copy returns a copy of Trie.
func (t *Trie) Copy() *Trie {
return &Trie{
db: t.db,
root: t.root,
unhashed: t.unhashed,
}
}

0 comments on commit ba5e4a4

Please sign in to comment.