core,trie,triedb: add getAccount/Storage in pathdb

This commit is contained in:
Fynn 2024-05-11 14:15:36 +08:00
parent 3e896c875a
commit 462e2bf71b
20 changed files with 620 additions and 18 deletions

View file

@ -18,6 +18,8 @@ package rawdb
import ( import (
"fmt" "fmt"
"math/big"
"strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -94,6 +96,58 @@ func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
} }
} }
func EncodeNibbles(bytes []byte) []byte {
nibbles := make([]byte, len(bytes)*2)
for i, b := range bytes {
nibbles[i*2] = b >> 4 // 取字节高4位
nibbles[i*2+1] = b & 0x0F // 取字节低4位
}
return nibbles
}
func ReadAccountFromTrieDirectly(db ethdb.Database, key []byte) ([]byte, []byte, common.Hash) {
it := db.NewIterator(TrieNodeAccountPrefix, []byte(""))
defer it.Release()
if it.Seek(accountTrieNodeKey(EncodeNibbles(key))) && it.Error() == nil {
dbKey := common.CopyBytes(it.Key())
if strings.HasPrefix(string(accountTrieNodeKey(EncodeNibbles(key))), string(dbKey)) {
data := common.CopyBytes(it.Value())
return data, dbKey[1:], common.Hash{}
} else {
log.Debug("ReadAccountFromTrieDirectly", "dbKey", common.Bytes2Hex(dbKey), "target key", common.Bytes2Hex(accountTrieNodeKey(EncodeNibbles(key))))
}
} else {
log.Error("ReadAccountFromTrieDirectly", "iterater error", it.Error())
}
return nil, nil, common.Hash{}
}
func ReadStorageFromTrieDirectly(db ethdb.Database, accountHash common.Hash, key []byte) ([]byte, []byte, common.Hash) {
it := db.NewIterator(append(TrieNodeStoragePrefix, accountHash.Bytes()...), []byte(""))
defer it.Release()
if it.Seek(storageTrieNodeKey(accountHash, EncodeNibbles(key))) && it.Error() == nil {
dbKey := common.CopyBytes(it.Key())
if strings.HasPrefix(string(storageTrieNodeKey(accountHash, EncodeNibbles(key))), string(dbKey)) {
data := common.CopyBytes(it.Value())
return data, dbKey[1:], common.Hash{}
}
}
return nil, nil, common.Hash{}
}
func DeleteStorageTrie(db ethdb.KeyValueWriter, accountHash common.Hash) {
nextAccountHash := common.BigToHash(accountHash.Big().Add(accountHash.Big(), big.NewInt(1)))
if err := db.DeleteRange(storageTrieNodeKey(accountHash, nil), storageTrieNodeKey(nextAccountHash, nil)); err != nil {
log.Crit("Failed to delete storage trie", "err", err)
}
}
func IterateStorageTrieNodes(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator {
return db.NewIterator(storageTrieNodeKey(accountHash, nil), nil)
}
// ReadStorageTrieNode retrieves the storage trie node with the specified node path. // ReadStorageTrieNode retrieves the storage trie node with the specified node path.
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte { func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte {
data, _ := db.Get(storageTrieNodeKey(accountHash, path)) data, _ := db.Get(storageTrieNodeKey(accountHash, path))

View file

@ -27,6 +27,11 @@ type table struct {
prefix string prefix string
} }
func (t *table) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// NewTable returns a database object that prefixes all keys with a given string. // NewTable returns a database object that prefixes all keys with a given string.
func NewTable(db ethdb.Database, prefix string) ethdb.Database { func NewTable(db ethdb.Database, prefix string) ethdb.Database {
return &table{ return &table{
@ -214,6 +219,11 @@ type tableBatch struct {
prefix string prefix string
} }
func (b *tableBatch) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// Put inserts the given value into the batch for later committing. // Put inserts the given value into the batch for later committing.
func (b *tableBatch) Put(key, value []byte) error { func (b *tableBatch) Put(key, value []byte) error {
return b.batch.Put(append([]byte(b.prefix), key...), value) return b.batch.Put(append([]byte(b.prefix), key...), value)
@ -246,6 +256,11 @@ type tableReplayer struct {
prefix string prefix string
} }
func (r *tableReplayer) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// Put implements the interface KeyValueWriter. // Put implements the interface KeyValueWriter.
func (r *tableReplayer) Put(key []byte, value []byte) error { func (r *tableReplayer) Put(key []byte, value []byte) error {
trimmed := key[len(r.prefix):] trimmed := key[len(r.prefix):]
@ -270,6 +285,11 @@ type tableIterator struct {
prefix string prefix string
} }
func (iter *tableIterator) Seek(key []byte) bool {
// TODO implement me
panic("implement me")
}
// Next moves the iterator to the next key/value pair. It returns whether the // Next moves the iterator to the next key/value pair. It returns whether the
// iterator is exhausted. // iterator is exhausted.
func (iter *tableIterator) Next() bool { func (iter *tableIterator) Next() bool {

View file

@ -51,6 +51,11 @@ type stateBloom struct {
bloom *bloomfilter.Filter bloom *bloomfilter.Filter
} }
func (bloom *stateBloom) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// newStateBloomWithSize creates a brand new state bloom for state generation. // newStateBloomWithSize creates a brand new state bloom for state generation.
// The bloom filter will be created by the passing bloom filter size. According // The bloom filter will be created by the passing bloom filter size. According
// to the https://hur.st/bloomfilter/?n=600000000&p=&m=2048MB&k=4, the parameters // to the https://hur.st/bloomfilter/?n=600000000&p=&m=2048MB&k=4, the parameters

View file

@ -18,6 +18,7 @@ package types
import ( import (
"bytes" "bytes"
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -119,3 +120,24 @@ func FullAccountRLP(data []byte) ([]byte, error) {
} }
return rlp.EncodeToBytes(account) return rlp.EncodeToBytes(account)
} }
func MustFullAccountRLP(data []byte) []byte {
if data == nil {
return nil
}
val, err := FullAccountRLP(data)
if err != nil {
panic(fmt.Sprintf("must full account rlp faield, err %v", err))
}
return val
}
func FullToSlimAccountRLP(data []byte) []byte {
if data == nil {
return nil
}
var account StateAccount
if err := rlp.DecodeBytes(data, &account); err != nil {
panic(fmt.Sprintf("full to slim account rlp failed, err %v", err))
}
return SlimAccountRLP(account)
}

View file

@ -35,6 +35,9 @@ type KeyValueWriter interface {
// Delete removes the key from the key-value data store. // Delete removes the key from the key-value data store.
Delete(key []byte) error Delete(key []byte) error
// DeleteRange deletes all of the keys (and values) in the range [start,end)
DeleteRange(start, end []byte) error
} }
// KeyValueStater wraps the Stat method of a backing data store. // KeyValueStater wraps the Stat method of a backing data store.

View file

@ -44,6 +44,9 @@ type Iterator interface {
// may change on the next call to Next. // may change on the next call to Next.
Value() []byte Value() []byte
// Seek moves the iterator to the target key/value pair. Only support Lower-bound
Seek(key []byte) bool
// Release releases associated resources. Release should always succeed and can // Release releases associated resources. Release should always succeed and can
// be called multiple times without causing error. // be called multiple times without causing error.
Release() Release()

View file

@ -84,6 +84,11 @@ type Database struct {
log log.Logger // Contextual logger tracking the database path log log.Logger // Contextual logger tracking the database path
} }
func (db *Database) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// New returns a wrapped LevelDB object. The namespace is the prefix that the // New returns a wrapped LevelDB object. The namespace is the prefix that the
// metrics reporting should use for surfacing internal stats. // metrics reporting should use for surfacing internal stats.
func New(file string, cache int, handles int, namespace string, readonly bool) (*Database, error) { func New(file string, cache int, handles int, namespace string, readonly bool) (*Database, error) {
@ -393,6 +398,11 @@ type batch struct {
size int size int
} }
func (b *batch) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// Put inserts the given value into the batch for later committing. // Put inserts the given value into the batch for later committing.
func (b *batch) Put(key, value []byte) error { func (b *batch) Put(key, value []byte) error {
b.b.Put(key, value) b.b.Put(key, value)

View file

@ -49,6 +49,11 @@ type Database struct {
lock sync.RWMutex lock sync.RWMutex
} }
func (db *Database) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// New returns a wrapped map with all the required database interface methods // New returns a wrapped map with all the required database interface methods
// implemented. // implemented.
func New() *Database { func New() *Database {
@ -220,6 +225,11 @@ type batch struct {
size int size int
} }
func (b *batch) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// Put inserts the given value into the batch for later committing. // Put inserts the given value into the batch for later committing.
func (b *batch) Put(key, value []byte) error { func (b *batch) Put(key, value []byte) error {
b.writes = append(b.writes, keyvalue{string(key), common.CopyBytes(value), false}) b.writes = append(b.writes, keyvalue{string(key), common.CopyBytes(value), false})
@ -288,6 +298,11 @@ type iterator struct {
values [][]byte values [][]byte
} }
func (it *iterator) Seek(key []byte) bool {
// TODO implement me
panic("implement me")
}
// Next moves the iterator to the next key/value pair. It returns whether the // Next moves the iterator to the next key/value pair. It returns whether the
// iterator is exhausted. // iterator is exhausted.
func (it *iterator) Next() bool { func (it *iterator) Next() bool {

View file

@ -334,6 +334,17 @@ func (d *Database) Delete(key []byte) error {
return d.db.Delete(key, nil) return d.db.Delete(key, nil)
} }
// DeleteRange deletes all of the keys (and values) in the range [start,end)
// (inclusive on start, exclusive on end).
func (d *Database) DeleteRange(start, end []byte) error {
d.quitLock.RLock()
defer d.quitLock.RUnlock()
if d.closed {
return pebble.ErrClosed
}
return d.db.DeleteRange(start, end, nil)
}
// NewBatch creates a write-only key-value store that buffers changes to its host // NewBatch creates a write-only key-value store that buffers changes to its host
// database until a final write is called. // database until a final write is called.
func (d *Database) NewBatch() ethdb.Batch { func (d *Database) NewBatch() ethdb.Batch {
@ -603,6 +614,13 @@ func (b *batch) Reset() {
b.size = 0 b.size = 0
} }
func (b *batch) DeleteRange(start, end []byte) error {
b.b.DeleteRange(start, end, nil)
b.size += len(start)
b.size += len(end)
return nil
}
// Replay replays the batch contents. // Replay replays the batch contents.
func (b *batch) Replay(w ethdb.KeyValueWriter) error { func (b *batch) Replay(w ethdb.KeyValueWriter) error {
reader := b.b.Reader() reader := b.b.Reader()
@ -634,6 +652,10 @@ type pebbleIterator struct {
released bool released bool
} }
func (iter *pebbleIterator) Seek(key []byte) bool {
return iter.iter.SeekLT(key)
}
// NewIterator creates a binary-alphabetical iterator over a subset // NewIterator creates a binary-alphabetical iterator over a subset
// of database content with a particular key prefix, starting at a particular // of database content with a particular key prefix, starting at a particular
// initial key (or after, if it does not exist). // initial key (or after, if it does not exist).

View file

@ -32,6 +32,11 @@ type Database struct {
remote *rpc.Client remote *rpc.Client
} }
func (db *Database) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
func (db *Database) Has(key []byte) (bool, error) { func (db *Database) Has(key []byte) (bool, error) {
if _, err := db.Get(key); err != nil { if _, err := db.Get(key); err != nil {
return false, nil return false, nil

View file

@ -694,6 +694,11 @@ type StorageResult struct {
// hex-strings for delivery to rpc-caller. // hex-strings for delivery to rpc-caller.
type proofList []string type proofList []string
func (n *proofList) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
func (n *proofList) Put(key []byte, value []byte) error { func (n *proofList) Put(key []byte, value []byte) error {
*n = append(*n, hexutil.Encode(value)) *n = append(*n, hexutil.Encode(value))
return nil return nil

View file

@ -231,6 +231,23 @@ func decodeRef(buf []byte) (node, []byte, error) {
} }
} }
// DecodeLeafNode return the Key and Val part of the shorNode
func DecodeLeafNode(hash, path, value []byte) ([]byte, []byte) {
n := mustDecodeNode(hash, value)
switch sn := n.(type) {
case *shortNode:
if val, ok := sn.Val.(valueNode); ok {
// remove the prefix key of path
key := append(path, sn.Key...)
if hasTerm(key) {
key = key[:len(key)-1]
}
return val, hexToKeybytes(append(path, sn.Key...))
}
}
return nil, nil
}
// wraps a decoding error with information about the path to the // wraps a decoding error with information about the path to the
// invalid child node (for debugging encoding issues). // invalid child node (for debugging encoding issues).
type decodeError struct { type decodeError struct {

View file

@ -36,6 +36,11 @@ type ProofSet struct {
lock sync.RWMutex lock sync.RWMutex
} }
func (db *ProofSet) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// NewProofSet creates an empty node set // NewProofSet creates an empty node set
func NewProofSet() *ProofSet { func NewProofSet() *ProofSet {
return &ProofSet{ return &ProofSet{

View file

@ -58,6 +58,10 @@ type TrieLoader interface {
// The value refers to the original content of state before the transition // The value refers to the original content of state before the transition
// is made. Nil means that the state was not present previously. // is made. Nil means that the state was not present previously.
type Set struct { type Set struct {
LatestAccounts map[common.Hash][]byte
LatestStorages map[common.Hash]map[common.Hash][]byte
DestructSet map[common.Hash]struct{}
Accounts map[common.Address][]byte // Mutated account set, nil means the account was not present Accounts map[common.Address][]byte // Mutated account set, nil means the account was not present
Storages map[common.Address]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present Storages map[common.Address]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present
size common.StorageSize // Approximate size of set size common.StorageSize // Approximate size of set

View file

@ -493,6 +493,11 @@ type cleaner struct {
db *Database db *Database
} }
func (c *cleaner) DeleteRange(start, end []byte) error {
// TODO implement me
panic("implement me")
}
// Put reacts to database writes and implements dirty data uncaching. This is the // Put reacts to database writes and implements dirty data uncaching. This is the
// post-processing step of a commit operation where the already persisted trie is // post-processing step of a commit operation where the already persisted trie is
// removed from the dirty cache and moved into the clean cache. The reason behind // removed from the dirty cache and moved into the clean cache. The reason behind

View file

@ -67,6 +67,13 @@ type layer interface {
// Note, no error will be returned if the requested node is not found in database. // Note, no error will be returned if the requested node is not found in database.
node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error)
// Account directly retrieves the account data associated with a particular hash
Account(hash common.Hash) ([]byte, error)
// Storage directly retrieves the storage data associated with a particular hash,
// within a particular account.
Storage(accountHash, storageHash common.Hash) ([]byte, error)
// rootHash returns the root hash for which this layer was made. // rootHash returns the root hash for which this layer was made.
rootHash() common.Hash rootHash() common.Hash

View file

@ -17,15 +17,95 @@
package pathdb package pathdb
import ( import (
"encoding/binary"
"fmt" "fmt"
"math"
"math/rand"
"sync" "sync"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
bloomfilter "github.com/holiman/bloomfilter/v2"
) )
var (
// aggregatorMemoryLimit is the maximum size of the bottom-most diff layer
// that aggregates the writes from above until it's flushed into the disk
// layer.
//
// Note, bumping this up might drastically increase the size of the bloom
// filters that's stored in every diff layer. Don't do that without fully
// understanding all the implications.
aggregatorMemoryLimit = uint64(4 * 1024 * 1024)
// aggregatorItemLimit is an approximate number of items that will end up
// in the aggregator layer before it's flushed out to disk. A plain account
// weighs around 14B (+hash), a storage slot 32B (+hash), a deleted slot
// 0B (+hash). Slots are mostly set/unset in lockstep, so that average at
// 16B (+hash). All in all, the average entry seems to be 15+32=47B. Use a
// smaller number to be on the safe side.
aggregatorItemLimit = aggregatorMemoryLimit / 42
// bloomTargetError is the target false positive rate when the aggregator
// layer is at its fullest. The actual value will probably move around up
// and down from this number, it's mostly a ballpark figure.
//
// Note, dropping this down might drastically increase the size of the bloom
// filters that's stored in every diff layer. Don't do that without fully
// understanding all the implications.
bloomTargetError = 0.02
// bloomSize is the ideal bloom filter size given the maximum number of items
// it's expected to hold and the target false positive error rate.
bloomSize = math.Ceil(float64(aggregatorItemLimit) * math.Log(bloomTargetError) / math.Log(1/math.Pow(2, math.Log(2))))
// bloomFuncs is the ideal number of bits a single entry should set in the
// bloom filter to keep its size to a minimum (given it's size and maximum
// entry count).
bloomFuncs = math.Round((bloomSize / float64(aggregatorItemLimit)) * math.Log(2))
// the bloom offsets are runtime constants which determines which part of the
// account/storage hash the hasher functions looks at, to determine the
// bloom key for an account/slot. This is randomized at init(), so that the
// global population of nodes do not all display the exact same behaviour with
// regards to bloom content
bloomDestructHasherOffset = 0
bloomAccountHasherOffset = 0
bloomStorageHasherOffset = 0
)
func init() {
// Init the bloom offsets in the range [0:24] (requires 8 bytes)
bloomDestructHasherOffset = rand.Intn(25)
bloomAccountHasherOffset = rand.Intn(25)
bloomStorageHasherOffset = rand.Intn(25)
// The destruct and account blooms must be different, as the storage slots
// will check for destruction too for every bloom miss. It should not collide
// with modified accounts.
for bloomAccountHasherOffset == bloomDestructHasherOffset {
bloomAccountHasherOffset = rand.Intn(25)
}
}
// destructBloomHash is used to convert a destruct event into a 64 bit mini hash.
func destructBloomHash(h common.Hash) uint64 {
return binary.BigEndian.Uint64(h[bloomDestructHasherOffset : bloomDestructHasherOffset+8])
}
// accountBloomHash is used to convert an account hash into a 64 bit mini hash.
func accountBloomHash(h common.Hash) uint64 {
return binary.BigEndian.Uint64(h[bloomAccountHasherOffset : bloomAccountHasherOffset+8])
}
// storageBloomHash is used to convert an account hash and a storage hash into a 64 bit mini hash.
func storageBloomHash(h0, h1 common.Hash) uint64 {
return binary.BigEndian.Uint64(h0[bloomStorageHasherOffset:bloomStorageHasherOffset+8]) ^
binary.BigEndian.Uint64(h1[bloomStorageHasherOffset:bloomStorageHasherOffset+8])
}
// diffLayer represents a collection of modifications made to the in-memory tries // diffLayer represents a collection of modifications made to the in-memory tries
// along with associated state changes after running a block on top. // along with associated state changes after running a block on top.
// //
@ -42,6 +122,10 @@ type diffLayer struct {
parent layer // Parent layer modified by this one, never nil, **can be changed** parent layer // Parent layer modified by this one, never nil, **can be changed**
lock sync.RWMutex // Lock used to protect parent lock sync.RWMutex // Lock used to protect parent
diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer
selfDiffed *bloomfilter.Filter // Bloom filter tracking all the diffed items of the diff layer
origin *diskLayer // Base disk layer to directly use on bloom misses
} }
// newDiffLayer creates a new diff layer on top of an existing layer. // newDiffLayer creates a new diff layer on top of an existing layer.
@ -58,6 +142,15 @@ func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes
states: states, states: states,
parent: parent, parent: parent,
} }
switch l := parent.(type) {
case *diskLayer:
dl.rebloom(l)
case *diffLayer:
dl.rebloom(l.origin)
default:
panic("unknown parent type")
}
for _, subset := range nodes { for _, subset := range nodes {
for path, n := range subset { for path, n := range subset {
dl.memory += uint64(n.Size() + len(path)) dl.memory += uint64(n.Size() + len(path))
@ -75,6 +168,61 @@ func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes
return dl return dl
} }
// rebloom discards the layer's current bloom and rebuilds it from scratch based
// on the parent's and the local diffs.
func (dl *diffLayer) rebloom(origin *diskLayer) {
dl.lock.Lock()
defer dl.lock.Unlock()
defer func(start time.Time) {
bloomIndexTimer.Update(time.Since(start))
}(time.Now())
// Inject the new origin that triggered the rebloom
dl.origin = origin
// Retrieve the parent bloom or create a fresh empty one
if parent, ok := dl.parent.(*diffLayer); ok {
parent.lock.RLock()
dl.diffed, _ = parent.diffed.Copy()
parent.lock.RUnlock()
} else {
if dl.selfDiffed == nil {
dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs))
} else {
dl.diffed, _ = dl.selfDiffed.NewCompatible()
}
}
if dl.selfDiffed == nil {
dl.selfDiffed, _ = dl.diffed.NewCompatible()
// Iterate over all the accounts and storage slots and index them
for h := range dl.states.DestructSet {
dl.selfDiffed.AddHash(destructBloomHash(h))
}
for h := range dl.states.LatestAccounts {
dl.selfDiffed.AddHash(accountBloomHash(h))
}
for accountHash, slots := range dl.states.LatestStorages {
for storageHash := range slots {
dl.selfDiffed.AddHash(storageBloomHash(accountHash, storageHash))
}
}
}
err := dl.diffed.UnionInPlace(dl.selfDiffed)
if err != nil {
log.Error("diff layer bloom filter failed to union in place", "id", dl.id, "err", err)
}
// Calculate the current false positive rate and update the error rate meter.
// This is a bit cheating because subsequent layers will overwrite it, but it
// should be fine, we're only interested in ballpark figures.
k := float64(dl.diffed.K())
n := float64(dl.diffed.N())
m := float64(dl.diffed.M())
bloomErrorGauge.Update(math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k))
}
// rootHash implements the layer interface, returning the root hash of // rootHash implements the layer interface, returning the root hash of
// corresponding state. // corresponding state.
func (dl *diffLayer) rootHash() common.Hash { func (dl *diffLayer) rootHash() common.Hash {
@ -95,6 +243,85 @@ func (dl *diffLayer) parentLayer() layer {
return dl.parent return dl.parent
} }
func (dl *diffLayer) Account(hash common.Hash) ([]byte, error) {
dl.lock.RLock()
defer dl.lock.RUnlock()
// Check the bloom filter first whether there's even a point in reaching into
// all the maps in all the layers below
hit := dl.diffed.ContainsHash(accountBloomHash(hash))
if !hit {
hit = dl.diffed.ContainsHash(destructBloomHash(hash))
}
var origin *diskLayer
if !hit {
origin = dl.origin // extract origin while holding the lock
}
// If the bloom filter misses, don't even bother with traversing the memory
// diff layers, reach straight into the bottom persistent disk layer
if origin != nil {
return origin.Account(hash)
}
return dl.account(hash)
}
func (dl *diffLayer) account(hash common.Hash) ([]byte, error) {
dl.lock.RLock()
defer dl.lock.RUnlock()
if data, ok := dl.states.LatestAccounts[hash]; ok {
return data, nil
}
if _, ok := dl.states.DestructSet[hash]; ok {
return nil, nil
}
if diff, ok := dl.parent.(*diffLayer); ok {
return diff.account(hash)
}
return dl.parent.Account(hash)
}
func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
dl.lock.RLock()
defer dl.lock.RUnlock()
hit := dl.diffed.ContainsHash(storageBloomHash(accountHash, storageHash))
if !hit {
hit = dl.diffed.ContainsHash(destructBloomHash(accountHash))
}
var origin *diskLayer
if !hit {
origin = dl.origin // extract origin while holding the lock
}
if origin != nil {
return origin.Storage(accountHash, storageHash)
}
return dl.storage(accountHash, storageHash)
}
func (dl *diffLayer) storage(accountHash, storageHash common.Hash) ([]byte, error) {
dl.lock.RLock()
defer dl.lock.RUnlock()
if storage, ok := dl.states.LatestStorages[accountHash]; ok {
if data, ok := storage[storageHash]; ok {
return data, nil
}
}
if _, ok := dl.states.DestructSet[accountHash]; ok {
return nil, nil
}
// Storage slot unknown to this diff, resolve from parent
if diff, ok := dl.parent.(*diffLayer); ok {
return diff.storage(accountHash, storageHash)
}
return dl.parent.Storage(accountHash, storageHash)
}
// node implements the layer interface, retrieving the trie node blob with the // node implements the layer interface, retrieving the trie node blob with the
// provided node information. No error will be returned if the node is not found. // provided node information. No error will be returned if the node is not found.
func (dl *diffLayer) node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) { func (dl *diffLayer) node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) {

View file

@ -17,14 +17,18 @@
package pathdb package pathdb
import ( import (
"bytes"
"encoding/hex"
"fmt" "fmt"
"sync" "sync"
"github.com/VictoriaMetrics/fastcache" "github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
) )
@ -34,19 +38,39 @@ type diskLayer struct {
root common.Hash // Immutable, root hash to which this layer was made for root common.Hash // Immutable, root hash to which this layer was made for
id uint64 // Immutable, corresponding state id id uint64 // Immutable, corresponding state id
db *Database // Path-based trie database db *Database // Path-based trie database
cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs cleans *cleanCache // GC friendly memory cache of clean node RLPs
buffer *nodebuffer // Node buffer to aggregate writes buffer *nodebuffer // Node buffer to aggregate writes
stale bool // Signals that the layer became stale (state progressed) stale bool // Signals that the layer became stale (state progressed)
lock sync.RWMutex // Lock used to protect stale flag lock sync.RWMutex // Lock used to protect stale flag
} }
type cleanCache struct {
nodes *fastcache.Cache
plainStates *fastcache.Cache
}
func (cc *cleanCache) reset() {
if cc.nodes != nil {
cc.nodes.Reset()
}
if cc.plainStates != nil {
cc.plainStates.Reset()
}
}
// newDiskLayer creates a new disk layer based on the passing arguments. // newDiskLayer creates a new disk layer based on the passing arguments.
func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *fastcache.Cache, buffer *nodebuffer) *diskLayer { func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *cleanCache, buffer *nodebuffer) *diskLayer {
// Initialize a clean cache if the memory allowance is not zero // Initialize a clean cache if the memory allowance is not zero
// or reuse the provided cache if it is not nil (inherited from // or reuse the provided cache if it is not nil (inherited from
// the original disk layer). // the original disk layer).
if cleans == nil && db.config.CleanCacheSize != 0 { if cleans == nil && db.config.CleanCacheSize != 0 {
cleans = fastcache.New(db.config.CleanCacheSize) cleans = &cleanCache{}
nodeCacheSize := db.config.CleanCacheSize * 43 / 100
plainStatesCacheSize := db.config.CleanCacheSize * 57 / 100
cleans.nodes = fastcache.New(nodeCacheSize)
cleans.plainStates = fastcache.New(plainStatesCacheSize)
log.Info("Allocate clean cache in disklayer", "nodes", common.StorageSize(nodeCacheSize),
"plainStates", common.StorageSize(plainStatesCacheSize))
} }
return &diskLayer{ return &diskLayer{
root: root, root: root,
@ -93,6 +117,82 @@ func (dl *diskLayer) markStale() {
dl.stale = true dl.stale = true
} }
func (dl *diskLayer) Account(hash common.Hash) ([]byte, error) {
// Hold the lock, ensure the parent won't be changed during the
// state accessing.
dl.lock.RLock()
defer dl.lock.RUnlock()
if dl.stale {
return nil, errSnapshotStale
}
if data, exist := dl.buffer.account(hash); exist {
return data, nil
}
if data, ok := dl.cleans.plainStates.HasGet(nil, hash.Bytes()); ok {
return types.MustFullAccountRLP(data), nil
}
blob := dl.readAccountTrie(hash)
dl.cleans.plainStates.Set(hash.Bytes(), types.FullToSlimAccountRLP(blob))
return blob, nil
}
// readAccountTrie return value of the account leaf node directly from the db
func (dl *diskLayer) readAccountTrie(hash common.Hash) []byte {
nBlob, path, nHash := rawdb.ReadAccountFromTrieDirectly(dl.db.diskdb, hash.Bytes())
if nBlob == nil {
return nil
}
dl.cleans.nodes.Set(cacheKey(common.Hash{}, path[:]), nBlob)
val, key := trie.DecodeLeafNode(nHash.Bytes(), path, nBlob)
if bytes.Compare(key, hash.Bytes()) == 0 {
return val
} else {
log.Debug("account short node info ", "account hash", hash.String(), "gotten key", hex.EncodeToString(key), "path", common.Bytes2Hex(path))
}
return nil
}
func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
// Hold the lock, ensure the parent won't be changed during the
// state accessing.
dl.lock.RLock()
defer dl.lock.RUnlock()
if dl.stale {
return nil, errSnapshotStale
}
if data, exist := dl.buffer.storage(accountHash, storageHash); exist {
return data, nil
}
if data, ok := dl.cleans.plainStates.HasGet(nil, append(accountHash.Bytes(), storageHash.Bytes()...)); ok {
return data, nil
}
blob := dl.readStorageTrie(accountHash, storageHash)
dl.cleans.plainStates.Set(append(accountHash.Bytes(), storageHash.Bytes()...), blob)
return blob, nil
}
// readStorageTrie return value of the storage leaf node directly from the db
func (dl *diskLayer) readStorageTrie(accountHash, storageHash common.Hash) []byte {
key := storageHash.Bytes()
nBlob, path, nHash := rawdb.ReadStorageFromTrieDirectly(dl.db.diskdb, accountHash, key)
if nBlob == nil {
return nil
}
dl.cleans.nodes.Set(cacheKey(accountHash, path[common.HashLength:]), nBlob)
val, key := trie.DecodeLeafNode(nHash.Bytes(), path[common.HashLength:], nBlob)
if bytes.Compare(storageHash.Bytes(), key) == 0 {
return val
}
return nil
}
// node implements the layer interface, retrieving the trie node with the // node implements the layer interface, retrieving the trie node with the
// provided node info. No error will be returned if the node is not found. // provided node info. No error will be returned if the node is not found.
func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) { func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) {
@ -121,7 +221,7 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
key := cacheKey(owner, path) key := cacheKey(owner, path)
if dl.cleans != nil { if dl.cleans != nil {
if blob := dl.cleans.Get(nil, key); len(blob) > 0 { if blob := dl.cleans.nodes.Get(nil, key); len(blob) > 0 {
cleanHitMeter.Mark(1) cleanHitMeter.Mark(1)
cleanReadMeter.Mark(int64(len(blob))) cleanReadMeter.Mark(int64(len(blob)))
return blob, h.hash(blob), &nodeLoc{loc: locCleanCache, depth: depth}, nil return blob, h.hash(blob), &nodeLoc{loc: locCleanCache, depth: depth}, nil
@ -136,7 +236,7 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path) blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
} }
if dl.cleans != nil && len(blob) > 0 { if dl.cleans != nil && len(blob) > 0 {
dl.cleans.Set(key, blob) dl.cleans.nodes.Set(key, blob)
cleanWriteMeter.Mark(int64(len(blob))) cleanWriteMeter.Mark(int64(len(blob)))
} }
@ -292,7 +392,7 @@ func (dl *diskLayer) resetCache() {
return return
} }
if dl.cleans != nil { if dl.cleans != nil {
dl.cleans.Reset() dl.cleans.reset()
} }
} }

View file

@ -48,4 +48,7 @@ var (
historyBuildTimeMeter = metrics.NewRegisteredTimer("pathdb/history/time", nil) historyBuildTimeMeter = metrics.NewRegisteredTimer("pathdb/history/time", nil)
historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil) historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil)
historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil) historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil)
bloomIndexTimer = metrics.NewRegisteredResettingTimer("pathdb/bloom/index", nil)
bloomErrorGauge = metrics.NewRegisteredGaugeFloat64("pathdb/bloom/error", nil)
) )

View file

@ -19,14 +19,16 @@ package pathdb
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"sync"
"time" "time"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
) )
@ -38,6 +40,11 @@ type nodebuffer struct {
size uint64 // The size of aggregated writes size uint64 // The size of aggregated writes
limit uint64 // The maximum memory allowance in bytes limit uint64 // The maximum memory allowance in bytes
nodes map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path nodes map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path
// latest account and storage
LatestAccounts map[common.Hash][]byte
LatestStorages map[common.Hash]map[common.Hash][]byte
DestructSet map[common.Hash]struct{}
} }
// newNodeBuffer initializes the node buffer with the provided nodes. // newNodeBuffer initializes the node buffer with the provided nodes.
@ -59,6 +66,30 @@ func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, l
} }
} }
func (b *nodebuffer) account(hash common.Hash) ([]byte, bool) {
if data, ok := b.LatestAccounts[hash]; ok {
return data, true
}
if _, ok := b.DestructSet[hash]; ok {
return nil, true
}
return nil, false
}
func (b *nodebuffer) storage(accountHash, storageHash common.Hash) ([]byte, bool) {
if storage, ok := b.LatestStorages[accountHash]; ok {
if data, ok := storage[storageHash]; ok {
return data, true
}
}
if _, ok := b.DestructSet[accountHash]; ok {
return nil, true
}
return nil, false
}
// node retrieves the trie node with given node info. // node retrieves the trie node with given node info.
func (b *nodebuffer) node(owner common.Hash, path []byte) (*trienode.Node, bool) { func (b *nodebuffer) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
subset, ok := b.nodes[owner] subset, ok := b.nodes[owner]
@ -195,7 +226,7 @@ func (b *nodebuffer) empty() bool {
// setSize sets the buffer size to the provided number, and invokes a flush // setSize sets the buffer size to the provided number, and invokes a flush
// operation if the current memory usage exceeds the new limit. // operation if the current memory usage exceeds the new limit.
func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64) error { func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *cleanCache, id uint64) error {
b.limit = uint64(size) b.limit = uint64(size)
return b.flush(db, clean, id, false) return b.flush(db, clean, id, false)
} }
@ -215,7 +246,7 @@ func (b *nodebuffer) allocBatch(db ethdb.KeyValueStore) ethdb.Batch {
// flush persists the in-memory dirty trie node into the disk if the configured // flush persists the in-memory dirty trie node into the disk if the configured
// memory threshold is reached. Note, all data must be written atomically. // memory threshold is reached. Note, all data must be written atomically.
func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64, force bool) error { func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *cleanCache, id uint64, force bool) error {
if b.size <= b.limit && !force { if b.size <= b.limit && !force {
return nil return nil
} }
@ -228,6 +259,45 @@ func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id ui
start = time.Now() start = time.Now()
batch = b.allocBatch(db) batch = b.allocBatch(db)
) )
// delete all kv for destructSet first to keep latest for disk nodes
for h, _ := range b.DestructSet {
rawdb.DeleteStorageTrie(batch, h)
clean.plainStates.Set(h.Bytes(), nil)
}
var wg sync.WaitGroup
if len(b.DestructSet) != 0 {
wg.Add(1)
go func() {
st := time.Now()
nums := 0
for h := range b.DestructSet {
// delete from the clean cache
it := rawdb.IterateStorageTrieNodes(db, h)
for it.Next() {
if it.Value() != nil {
h := newHasher()
_, key := trie.DecodeLeafNode(h.hash(it.Value()).Bytes(), it.Key()[1+common.HashLength:], it.Value())
if key != nil {
nums++
clean.plainStates.Del(key)
}
h.release()
}
}
it.Release()
}
log.Info("handle deletion of plain storage", "elapsed", time.Since(st).String(), "deleted nums", nums)
for h, acc := range b.LatestAccounts {
clean.plainStates.Set(h.Bytes(), types.FullToSlimAccountRLP(acc))
}
for h, storages := range b.LatestStorages {
for k, v := range storages {
clean.plainStates.Set(append(h.Bytes(), k.Bytes()...), v)
}
}
wg.Done()
}()
}
nodes := writeNodes(batch, b.nodes, clean) nodes := writeNodes(batch, b.nodes, clean)
rawdb.WritePersistentStateID(batch, id) rawdb.WritePersistentStateID(batch, id)
@ -247,7 +317,7 @@ func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id ui
// writeNodes writes the trie nodes into the provided database batch. // writeNodes writes the trie nodes into the provided database batch.
// Note this function will also inject all the newly written nodes // Note this function will also inject all the newly written nodes
// into clean cache. // into clean cache.
func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) { func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *cleanCache) (total int) {
for owner, subset := range nodes { for owner, subset := range nodes {
for path, n := range subset { for path, n := range subset {
if n.IsDeleted() { if n.IsDeleted() {
@ -257,7 +327,7 @@ func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.No
rawdb.DeleteStorageTrieNode(batch, owner, []byte(path)) rawdb.DeleteStorageTrieNode(batch, owner, []byte(path))
} }
if clean != nil { if clean != nil {
clean.Del(cacheKey(owner, []byte(path))) clean.nodes.Del(cacheKey(owner, []byte(path)))
} }
} else { } else {
if owner == (common.Hash{}) { if owner == (common.Hash{}) {
@ -266,7 +336,7 @@ func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.No
rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob) rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob)
} }
if clean != nil { if clean != nil {
clean.Set(cacheKey(owner, []byte(path)), n.Blob) clean.nodes.Set(cacheKey(owner, []byte(path)), n.Blob)
} }
} }
} }