mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
core/state: cache storage tries across blocks and pending state
This commit is contained in:
parent
1ff152f3a4
commit
ece9eaa1d4
3 changed files with 228 additions and 15 deletions
|
|
@ -259,8 +259,10 @@ func importChain(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
fmt.Println(ioStats)
|
fmt.Println(ioStats)
|
||||||
|
|
||||||
fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses())
|
fmt.Printf("State storage cache misses: %d\n", state.StorageTrieMisses())
|
||||||
fmt.Printf("Trie cache unloads: %d\n\n", trie.CacheUnloads())
|
fmt.Printf("State storage cache unloads: %d\n", state.StorageTrieUnloads())
|
||||||
|
fmt.Printf("Global trie cache misses: %d\n", trie.CacheMisses())
|
||||||
|
fmt.Printf("Global trie cache unloads: %d\n\n", trie.CacheUnloads())
|
||||||
|
|
||||||
// Print the memory statistics used by the importing
|
// Print the memory statistics used by the importing
|
||||||
mem := new(runtime.MemStats)
|
mem := new(runtime.MemStats)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
lru "github.com/hashicorp/golang-lru"
|
lru "github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
@ -30,14 +31,36 @@ import (
|
||||||
var MaxTrieCacheGen = uint16(120)
|
var MaxTrieCacheGen = uint16(120)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Number of past tries to keep. This value is chosen such that
|
// maxPastTries is the number of past tries to keep. This value is chosen such
|
||||||
// reasonable chain reorg depths will hit an existing trie.
|
// that reasonable chain reorg depths will hit an existing trie.
|
||||||
maxPastTries = 12
|
maxPastTries = 12
|
||||||
|
|
||||||
// Number of codehash->size associations to keep.
|
// maxPastStorageTries is the number of past storage tries to keep.
|
||||||
|
maxPastStorageTries = 4096
|
||||||
|
|
||||||
|
// codeSizeCacheSize is the number of codehash->size associations to keep.
|
||||||
codeSizeCacheSize = 100000
|
codeSizeCacheSize = 100000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
storageTrieMissCounter = metrics.NewRegisteredCounter("state/storage/miss", nil)
|
||||||
|
storageTrieUnloadCounter = metrics.NewRegisteredCounter("state/storage/unload", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageTrieMisses retrieves a global counter measuring the number of storage
|
||||||
|
// trie cache misses the state had since process startup. This isn't useful for
|
||||||
|
// anything apart from state debugging purposes.
|
||||||
|
func StorageTrieMisses() int64 {
|
||||||
|
return storageTrieMissCounter.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageTrieUnloads retrieves a global counter measuring the number of storage
|
||||||
|
// trie cache unloads the state had since process startup. This isn't useful for
|
||||||
|
// anything apart from state debugging purposes.
|
||||||
|
func StorageTrieUnloads() int64 {
|
||||||
|
return storageTrieUnloadCounter.Count()
|
||||||
|
}
|
||||||
|
|
||||||
// Database wraps access to tries and contract code.
|
// Database wraps access to tries and contract code.
|
||||||
type Database interface {
|
type Database interface {
|
||||||
// OpenTrie opens the main account trie.
|
// OpenTrie opens the main account trie.
|
||||||
|
|
@ -77,8 +100,13 @@ type Trie interface {
|
||||||
// high level trie abstraction.
|
// high level trie abstraction.
|
||||||
func NewDatabase(db ethdb.Database) Database {
|
func NewDatabase(db ethdb.Database) Database {
|
||||||
csc, _ := lru.New(codeSizeCacheSize)
|
csc, _ := lru.New(codeSizeCacheSize)
|
||||||
|
str, _ := lru.NewWithEvict(maxPastStorageTries, func(key interface{}, value interface{}) {
|
||||||
|
storageTrieUnloadCounter.Inc(1)
|
||||||
|
})
|
||||||
|
|
||||||
return &cachingDB{
|
return &cachingDB{
|
||||||
db: trie.NewDatabase(db),
|
db: trie.NewDatabase(db),
|
||||||
|
pastStorageTries: str,
|
||||||
codeSizeCache: csc,
|
codeSizeCache: csc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +115,7 @@ type cachingDB struct {
|
||||||
db *trie.Database
|
db *trie.Database
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
pastTries []*trie.SecureTrie
|
pastTries []*trie.SecureTrie
|
||||||
|
pastStorageTries *lru.Cache
|
||||||
codeSizeCache *lru.Cache
|
codeSizeCache *lru.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +150,36 @@ func (db *cachingDB) pushTrie(t *trie.SecureTrie) {
|
||||||
|
|
||||||
// OpenStorageTrie opens the storage trie of an account.
|
// OpenStorageTrie opens the storage trie of an account.
|
||||||
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
|
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
|
||||||
return trie.NewSecure(root, db.db, 0)
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
// Retrieve a storage trie from the cache if available
|
||||||
|
if t, ok := db.pastStorageTries.Get(root); ok {
|
||||||
|
return &cachedStorageTrie{reader: t.(*syncedTrie), db: db}, nil
|
||||||
|
}
|
||||||
|
// Trie not cached, construct a brand new one and cache it if non-empty
|
||||||
|
tr, err := trie.NewSecure(root, db.db, MaxTrieCacheGen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
str := &syncedTrie{tr: tr}
|
||||||
|
|
||||||
|
if root != emptyRoot {
|
||||||
|
db.pastStorageTries.Add(root, str)
|
||||||
|
storageTrieMissCounter.Inc(1)
|
||||||
|
}
|
||||||
|
return &cachedStorageTrie{reader: str, db: db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *cachingDB) pushStorageTrie(t *syncedTrie) {
|
||||||
|
// Refuse to cache the empty trie
|
||||||
|
if t.Hash() == emptyRoot {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
db.pastStorageTries.Add(t.Hash(), t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyTrie returns an independent copy of the given trie.
|
// CopyTrie returns an independent copy of the given trie.
|
||||||
|
|
@ -129,6 +187,11 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
|
||||||
switch t := t.(type) {
|
switch t := t.(type) {
|
||||||
case cachedTrie:
|
case cachedTrie:
|
||||||
return cachedTrie{t.SecureTrie.Copy(), db}
|
return cachedTrie{t.SecureTrie.Copy(), db}
|
||||||
|
case *cachedStorageTrie:
|
||||||
|
if t.writer != nil {
|
||||||
|
return &cachedStorageTrie{reader: t.writer.Copy(), db: db}
|
||||||
|
}
|
||||||
|
return &cachedStorageTrie{reader: t.reader.Copy(), db: db}
|
||||||
case *trie.SecureTrie:
|
case *trie.SecureTrie:
|
||||||
return t.Copy()
|
return t.Copy()
|
||||||
default:
|
default:
|
||||||
|
|
@ -176,3 +239,151 @@ func (m cachedTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) {
|
||||||
func (m cachedTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.Putter) error {
|
func (m cachedTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.Putter) error {
|
||||||
return m.SecureTrie.Prove(key, fromLevel, proofDb)
|
return m.SecureTrie.Prove(key, fromLevel, proofDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// syncedTrie is a synchronized wrapper around a trie to support multiple threads
|
||||||
|
// accessing and read-expanding the same trie (i.e. caching loaded trie nodes).
|
||||||
|
type syncedTrie struct {
|
||||||
|
tr *trie.SecureTrie
|
||||||
|
lock sync.Mutex // Any operation on a trie can expand it (RWMutex is not enough)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) Copy() *syncedTrie {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return &syncedTrie{tr: s.tr.Copy()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) TryGet(key []byte) ([]byte, error) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.TryGet(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) TryUpdate(key, value []byte) error {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.TryUpdate(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) TryDelete(key []byte) error {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.TryDelete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.Commit(onleaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) Hash() common.Hash {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) NodeIterator(startKey []byte) trie.NodeIterator {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.NodeIterator(startKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) GetKey(key []byte) []byte {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.GetKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *syncedTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.Putter) error {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
return s.tr.Prove(key, fromLevel, proofDb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachedStorageTrie is a wrapper around a read-only trie shared by possibly
|
||||||
|
// multiple goroutines and a write-enabled trie unique to each goroutine.
|
||||||
|
//
|
||||||
|
// Important! Reads always go through the expand-only disk-backed trie whereas
|
||||||
|
// writes go through the write-enabled one. This wrapper assumes that any trie
|
||||||
|
// item read and modified will not be read again, rather used from a higher cache.
|
||||||
|
type cachedStorageTrie struct {
|
||||||
|
reader *syncedTrie // Original trie for expand-only operations
|
||||||
|
writer *syncedTrie // Copy trie for update and delete operations
|
||||||
|
|
||||||
|
db *cachingDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) TryGet(key []byte) ([]byte, error) {
|
||||||
|
res, err := c.reader.TryGet(key)
|
||||||
|
if c.writer != nil {
|
||||||
|
return c.writer.TryGet(key)
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) TryUpdate(key, value []byte) error {
|
||||||
|
if c.writer == nil {
|
||||||
|
c.writer = c.reader.Copy()
|
||||||
|
}
|
||||||
|
return c.writer.TryUpdate(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) TryDelete(key []byte) error {
|
||||||
|
if c.writer == nil {
|
||||||
|
c.writer = c.reader.Copy()
|
||||||
|
}
|
||||||
|
return c.writer.TryDelete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) Hash() common.Hash {
|
||||||
|
if c.writer != nil {
|
||||||
|
return c.writer.Hash()
|
||||||
|
}
|
||||||
|
return c.reader.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) NodeIterator(startKey []byte) trie.NodeIterator {
|
||||||
|
if c.writer != nil {
|
||||||
|
return c.writer.NodeIterator(startKey)
|
||||||
|
}
|
||||||
|
return c.reader.NodeIterator(startKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) GetKey(key []byte) []byte {
|
||||||
|
if c.writer != nil {
|
||||||
|
return c.writer.GetKey(key)
|
||||||
|
}
|
||||||
|
return c.reader.GetKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.Putter) error {
|
||||||
|
if c.writer != nil {
|
||||||
|
return c.writer.Prove(key, fromLevel, proofDb)
|
||||||
|
}
|
||||||
|
return c.reader.Prove(key, fromLevel, proofDb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cachedStorageTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) {
|
||||||
|
// Retrieve the hash of the read-only trie (should be free)
|
||||||
|
origin, err := c.reader.Commit(nil)
|
||||||
|
|
||||||
|
// If there have been modifications made, push the writer into the cache
|
||||||
|
if c.writer != nil {
|
||||||
|
updated, err := c.writer.Commit(onleaf)
|
||||||
|
if err == nil && updated != origin {
|
||||||
|
c.db.pushStorageTrie(c.writer)
|
||||||
|
}
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
return origin, err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ type revision struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// emptyState is the known hash of an empty state trie entry.
|
// emptyRoot is the known root hash of an empty trie.
|
||||||
emptyState = crypto.Keccak256Hash(nil)
|
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||||
|
|
||||||
// emptyCode is the known hash of the empty EVM bytecode.
|
// emptyCode is the known hash of the empty EVM bytecode.
|
||||||
emptyCode = crypto.Keccak256Hash(nil)
|
emptyCode = crypto.Keccak256Hash(nil)
|
||||||
|
|
@ -653,7 +653,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
|
||||||
if err := rlp.DecodeBytes(leaf, &account); err != nil {
|
if err := rlp.DecodeBytes(leaf, &account); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if account.Root != emptyState {
|
if account.Root != emptyRoot {
|
||||||
s.db.TrieDB().Reference(account.Root, parent)
|
s.db.TrieDB().Reference(account.Root, parent)
|
||||||
}
|
}
|
||||||
code := common.BytesToHash(account.CodeHash)
|
code := common.BytesToHash(account.CodeHash)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue