forked from cerc-io/plugeth
core/state/snapshot, true: reuse dirty data instead of hitting disk when generating (#22667)
* core/state/snapshot: reuse memory data instead of hitting disk when generating * trie: minor nitpicks wrt the resolver optimization * core/state/snapshot, trie: use key/value store for resolver * trie: fix linter Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
parent
ea54c58d4f
commit
49281ab84f
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"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/ethdb/memorydb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
@ -434,6 +435,20 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string,
|
|||||||
}
|
}
|
||||||
meter.Mark(1)
|
meter.Mark(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We use the snap data to build up a cache which can be used by the
|
||||||
|
// main account trie as a primary lookup when resolving hashes
|
||||||
|
var snapNodeCache ethdb.KeyValueStore
|
||||||
|
if len(result.keys) > 0 {
|
||||||
|
snapNodeCache = memorydb.New()
|
||||||
|
snapTrieDb := trie.NewDatabase(snapNodeCache)
|
||||||
|
snapTrie, _ := trie.New(common.Hash{}, snapTrieDb)
|
||||||
|
for i, key := range result.keys {
|
||||||
|
snapTrie.Update(key, result.vals[i])
|
||||||
|
}
|
||||||
|
root, _ := snapTrie.Commit(nil)
|
||||||
|
snapTrieDb.Commit(root, false, nil)
|
||||||
|
}
|
||||||
tr := result.tr
|
tr := result.tr
|
||||||
if tr == nil {
|
if tr == nil {
|
||||||
tr, err = trie.New(root, dl.triedb)
|
tr, err = trie.New(root, dl.triedb)
|
||||||
@ -442,9 +457,11 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string,
|
|||||||
return false, nil, errMissingTrie
|
return false, nil, errMissingTrie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
trieMore bool
|
trieMore bool
|
||||||
iter = trie.NewIterator(tr.NodeIterator(origin))
|
nodeIt = tr.NodeIterator(origin)
|
||||||
|
iter = trie.NewIterator(nodeIt)
|
||||||
kvkeys, kvvals = result.keys, result.vals
|
kvkeys, kvvals = result.keys, result.vals
|
||||||
|
|
||||||
// counters
|
// counters
|
||||||
@ -458,6 +475,7 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string,
|
|||||||
start = time.Now()
|
start = time.Now()
|
||||||
internal time.Duration
|
internal time.Duration
|
||||||
)
|
)
|
||||||
|
nodeIt.AddResolver(snapNodeCache)
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
if last != nil && bytes.Compare(iter.Key, last) > 0 {
|
if last != nil && bytes.Compare(iter.Key, last) > 0 {
|
||||||
trieMore = true
|
trieMore = true
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -102,6 +103,19 @@ type NodeIterator interface {
|
|||||||
// iterator is not positioned at a leaf. Callers must not retain references
|
// iterator is not positioned at a leaf. Callers must not retain references
|
||||||
// to the value after calling Next.
|
// to the value after calling Next.
|
||||||
LeafProof() [][]byte
|
LeafProof() [][]byte
|
||||||
|
|
||||||
|
// AddResolver sets an intermediate database to use for looking up trie nodes
|
||||||
|
// before reaching into the real persistent layer.
|
||||||
|
//
|
||||||
|
// This is not required for normal operation, rather is an optimization for
|
||||||
|
// cases where trie nodes can be recovered from some external mechanism without
|
||||||
|
// reading from disk. In those cases, this resolver allows short circuiting
|
||||||
|
// accesses and returning them from memory.
|
||||||
|
//
|
||||||
|
// Before adding a similar mechanism to any other place in Geth, consider
|
||||||
|
// making trie.Database an interface and wrapping at that level. It's a huge
|
||||||
|
// refactor, but it could be worth it if another occurrence arises.
|
||||||
|
AddResolver(ethdb.KeyValueStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeIteratorState represents the iteration state at one particular node of the
|
// nodeIteratorState represents the iteration state at one particular node of the
|
||||||
@ -119,6 +133,8 @@ type nodeIterator struct {
|
|||||||
stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state
|
stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state
|
||||||
path []byte // Path to the current node
|
path []byte // Path to the current node
|
||||||
err error // Failure set in case of an internal error in the iterator
|
err error // Failure set in case of an internal error in the iterator
|
||||||
|
|
||||||
|
resolver ethdb.KeyValueStore // Optional intermediate resolver above the disk layer
|
||||||
}
|
}
|
||||||
|
|
||||||
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
|
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
|
||||||
@ -143,6 +159,10 @@ func newNodeIterator(trie *Trie, start []byte) NodeIterator {
|
|||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *nodeIterator) AddResolver(resolver ethdb.KeyValueStore) {
|
||||||
|
it.resolver = resolver
|
||||||
|
}
|
||||||
|
|
||||||
func (it *nodeIterator) Hash() common.Hash {
|
func (it *nodeIterator) Hash() common.Hash {
|
||||||
if len(it.stack) == 0 {
|
if len(it.stack) == 0 {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
@ -262,7 +282,7 @@ func (it *nodeIterator) init() (*nodeIteratorState, error) {
|
|||||||
if root != emptyRoot {
|
if root != emptyRoot {
|
||||||
state.hash = root
|
state.hash = root
|
||||||
}
|
}
|
||||||
return state, state.resolve(it.trie, nil)
|
return state, state.resolve(it, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// peek creates the next state of the iterator.
|
// peek creates the next state of the iterator.
|
||||||
@ -286,7 +306,7 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er
|
|||||||
}
|
}
|
||||||
state, path, ok := it.nextChild(parent, ancestor)
|
state, path, ok := it.nextChild(parent, ancestor)
|
||||||
if ok {
|
if ok {
|
||||||
if err := state.resolve(it.trie, path); err != nil {
|
if err := state.resolve(it, path); err != nil {
|
||||||
return parent, &parent.index, path, err
|
return parent, &parent.index, path, err
|
||||||
}
|
}
|
||||||
return state, &parent.index, path, nil
|
return state, &parent.index, path, nil
|
||||||
@ -319,7 +339,7 @@ func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []by
|
|||||||
}
|
}
|
||||||
state, path, ok := it.nextChildAt(parent, ancestor, seekKey)
|
state, path, ok := it.nextChildAt(parent, ancestor, seekKey)
|
||||||
if ok {
|
if ok {
|
||||||
if err := state.resolve(it.trie, path); err != nil {
|
if err := state.resolve(it, path); err != nil {
|
||||||
return parent, &parent.index, path, err
|
return parent, &parent.index, path, err
|
||||||
}
|
}
|
||||||
return state, &parent.index, path, nil
|
return state, &parent.index, path, nil
|
||||||
@ -330,9 +350,21 @@ func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []by
|
|||||||
return nil, nil, nil, errIteratorEnd
|
return nil, nil, nil, errIteratorEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error {
|
func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
|
||||||
|
if it.resolver != nil {
|
||||||
|
if blob, err := it.resolver.Get(hash); err == nil && len(blob) > 0 {
|
||||||
|
if resolved, err := decodeNode(hash, blob); err == nil {
|
||||||
|
return resolved, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolved, err := it.trie.resolveHash(hash, path)
|
||||||
|
return resolved, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
|
||||||
if hash, ok := st.node.(hashNode); ok {
|
if hash, ok := st.node.(hashNode); ok {
|
||||||
resolved, err := tr.resolveHash(hash, path)
|
resolved, err := it.resolveHash(hash, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -517,6 +549,10 @@ func (it *differenceIterator) Path() []byte {
|
|||||||
return it.b.Path()
|
return it.b.Path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *differenceIterator) AddResolver(resolver ethdb.KeyValueStore) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (it *differenceIterator) Next(bool) bool {
|
func (it *differenceIterator) Next(bool) bool {
|
||||||
// Invariants:
|
// Invariants:
|
||||||
// - We always advance at least one element in b.
|
// - We always advance at least one element in b.
|
||||||
@ -624,6 +660,10 @@ func (it *unionIterator) Path() []byte {
|
|||||||
return (*it.items)[0].Path()
|
return (*it.items)[0].Path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *unionIterator) AddResolver(resolver ethdb.KeyValueStore) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// Next returns the next node in the union of tries being iterated over.
|
// Next returns the next node in the union of tries being iterated over.
|
||||||
//
|
//
|
||||||
// It does this by maintaining a heap of iterators, sorted by the iteration
|
// It does this by maintaining a heap of iterators, sorted by the iteration
|
||||||
|
Loading…
Reference in New Issue
Block a user