forked from cerc-io/plugeth
cmd, core, eth, les, light: track deleted nodes (#25757)
* cmd, core, eth, les, light: track deleted nodes * trie: add docs * trie: address comments * cmd, core, eth, les, light, trie: trie id * trie: add tests * trie, core: updates * trie: fix imports * trie: add utility print-method for nodeset * trie: import err * trie: fix go vet warnings Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
co-authored by
Martin Holst Swende
parent
fc3e6d0162
commit
bff84a99fe
+30
-2
@@ -33,13 +33,15 @@ type leaf struct {
|
||||
// insertion order.
|
||||
type committer struct {
|
||||
nodes *NodeSet
|
||||
tracer *tracer
|
||||
collectLeaf bool
|
||||
}
|
||||
|
||||
// newCommitter creates a new committer or picks one from the pool.
|
||||
func newCommitter(owner common.Hash, collectLeaf bool) *committer {
|
||||
func newCommitter(owner common.Hash, tracer *tracer, collectLeaf bool) *committer {
|
||||
return &committer{
|
||||
nodes: NewNodeSet(owner),
|
||||
tracer: tracer,
|
||||
collectLeaf: collectLeaf,
|
||||
}
|
||||
}
|
||||
@@ -51,6 +53,20 @@ func (c *committer) Commit(n node) (hashNode, *NodeSet, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Some nodes can be deleted from trie which can't be captured by committer
|
||||
// itself. Iterate all deleted nodes tracked by tracer and marked them as
|
||||
// deleted only if they are present in database previously.
|
||||
for _, path := range c.tracer.deleteList() {
|
||||
// There are a few possibilities for this scenario(the node is deleted
|
||||
// but not present in database previously), for example the node was
|
||||
// embedded in the parent and now deleted from the trie. In this case
|
||||
// it's noop from database's perspective.
|
||||
val := c.tracer.getPrev(path)
|
||||
if len(val) == 0 {
|
||||
continue
|
||||
}
|
||||
c.nodes.markDeleted(path, val)
|
||||
}
|
||||
return h.(hashNode), c.nodes, nil
|
||||
}
|
||||
|
||||
@@ -83,6 +99,12 @@ func (c *committer) commit(path []byte, n node) (node, error) {
|
||||
if hn, ok := hashedNode.(hashNode); ok {
|
||||
return hn, nil
|
||||
}
|
||||
// The short node now is embedded in its parent. Mark the node as
|
||||
// deleted if it's present in database previously. It's equivalent
|
||||
// as deletion from database's perspective.
|
||||
if prev := c.tracer.getPrev(path); len(prev) != 0 {
|
||||
c.nodes.markDeleted(path, prev)
|
||||
}
|
||||
return collapsed, nil
|
||||
case *fullNode:
|
||||
hashedKids, err := c.commitChildren(path, cn)
|
||||
@@ -96,6 +118,12 @@ func (c *committer) commit(path []byte, n node) (node, error) {
|
||||
if hn, ok := hashedNode.(hashNode); ok {
|
||||
return hn, nil
|
||||
}
|
||||
// The full node now is embedded in its parent. Mark the node as
|
||||
// deleted if it's present in database previously. It's equivalent
|
||||
// as deletion from database's perspective.
|
||||
if prev := c.tracer.getPrev(path); len(prev) != 0 {
|
||||
c.nodes.markDeleted(path, prev)
|
||||
}
|
||||
return collapsed, nil
|
||||
case hashNode:
|
||||
return cn, nil
|
||||
@@ -161,7 +189,7 @@ func (c *committer) store(path []byte, n node) node {
|
||||
}
|
||||
)
|
||||
// Collect the dirty node to nodeset for return.
|
||||
c.nodes.add(string(path), mnode)
|
||||
c.nodes.markUpdated(path, mnode, c.tracer.getPrev(path))
|
||||
|
||||
// Collect the corresponding leaf node if it's required. We don't check
|
||||
// full node since it's impossible to store value in fullNode. The key
|
||||
|
||||
+30
-2
@@ -795,8 +795,8 @@ func (db *Database) Update(nodes *MergedNodeSet) error {
|
||||
}
|
||||
for _, owner := range order {
|
||||
subset := nodes.sets[owner]
|
||||
for _, path := range subset.paths {
|
||||
n, ok := subset.nodes[path]
|
||||
for _, path := range subset.updates.order {
|
||||
n, ok := subset.updates.nodes[path]
|
||||
if !ok {
|
||||
return fmt.Errorf("missing node %x %v", owner, path)
|
||||
}
|
||||
@@ -837,6 +837,34 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize) {
|
||||
return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimageSize
|
||||
}
|
||||
|
||||
// GetReader retrieves a node reader belonging to the given state root.
|
||||
func (db *Database) GetReader(root common.Hash) Reader {
|
||||
return newHashReader(db)
|
||||
}
|
||||
|
||||
// hashReader is reader of hashDatabase which implements the Reader interface.
|
||||
type hashReader struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
// newHashReader initializes the hash reader.
|
||||
func newHashReader(db *Database) *hashReader {
|
||||
return &hashReader{db: db}
|
||||
}
|
||||
|
||||
// Node retrieves the trie node with the given node hash.
|
||||
// No error will be returned if the node is not found.
|
||||
func (reader *hashReader) Node(_ common.Hash, _ []byte, hash common.Hash) (node, error) {
|
||||
return reader.db.node(hash), nil
|
||||
}
|
||||
|
||||
// NodeBlob retrieves the RLP-encoded trie node blob with the given node hash.
|
||||
// No error will be returned if the node is not found.
|
||||
func (reader *hashReader) NodeBlob(_ common.Hash, _ []byte, hash common.Hash) ([]byte, error) {
|
||||
blob, _ := reader.db.Node(hash)
|
||||
return blob, nil
|
||||
}
|
||||
|
||||
// saveCache saves clean state cache to given directory path
|
||||
// using specified CPU cores.
|
||||
func (db *Database) saveCache(dir string, threads int) error {
|
||||
|
||||
+12
-2
@@ -375,7 +375,12 @@ func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return it.trie.resolveHash(hash, path)
|
||||
// Retrieve the specified node from the underlying node reader.
|
||||
// it.trie.resolveAndTrack is not used since in that function the
|
||||
// loaded blob will be tracked, while it's not required here since
|
||||
// all loaded nodes won't be linked to trie at all and track nodes
|
||||
// may lead to out-of-memory issue.
|
||||
return it.trie.reader.node(path, common.BytesToHash(hash))
|
||||
}
|
||||
|
||||
func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) {
|
||||
@@ -384,7 +389,12 @@ func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error)
|
||||
return blob, nil
|
||||
}
|
||||
}
|
||||
return it.trie.resolveBlob(hash, path)
|
||||
// Retrieve the specified node from the underlying node reader.
|
||||
// it.trie.resolveAndTrack is not used since in that function the
|
||||
// loaded blob will be tracked, while it's not required here since
|
||||
// all loaded nodes won't be linked to trie at all and track nodes
|
||||
// may lead to out-of-memory issue.
|
||||
return it.trie.reader.nodeBlob(path, common.BytesToHash(hash))
|
||||
}
|
||||
|
||||
func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestIterator(t *testing.T) {
|
||||
}
|
||||
db.Update(NewWithNodeSet(nodes))
|
||||
|
||||
trie, _ = New(common.Hash{}, root, db)
|
||||
trie, _ = New(TrieID(root), db)
|
||||
found := make(map[string]string)
|
||||
it := NewIterator(trie.NodeIterator(nil))
|
||||
for it.Next() {
|
||||
@@ -227,7 +227,7 @@ func TestDifferenceIterator(t *testing.T) {
|
||||
}
|
||||
rootA, nodesA, _ := triea.Commit(false)
|
||||
dba.Update(NewWithNodeSet(nodesA))
|
||||
triea, _ = New(common.Hash{}, rootA, dba)
|
||||
triea, _ = New(TrieID(rootA), dba)
|
||||
|
||||
dbb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
trieb := NewEmpty(dbb)
|
||||
@@ -236,7 +236,7 @@ func TestDifferenceIterator(t *testing.T) {
|
||||
}
|
||||
rootB, nodesB, _ := trieb.Commit(false)
|
||||
dbb.Update(NewWithNodeSet(nodesB))
|
||||
trieb, _ = New(common.Hash{}, rootB, dbb)
|
||||
trieb, _ = New(TrieID(rootB), dbb)
|
||||
|
||||
found := make(map[string]string)
|
||||
di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil))
|
||||
@@ -269,7 +269,7 @@ func TestUnionIterator(t *testing.T) {
|
||||
}
|
||||
rootA, nodesA, _ := triea.Commit(false)
|
||||
dba.Update(NewWithNodeSet(nodesA))
|
||||
triea, _ = New(common.Hash{}, rootA, dba)
|
||||
triea, _ = New(TrieID(rootA), dba)
|
||||
|
||||
dbb := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
trieb := NewEmpty(dbb)
|
||||
@@ -278,7 +278,7 @@ func TestUnionIterator(t *testing.T) {
|
||||
}
|
||||
rootB, nodesB, _ := trieb.Commit(false)
|
||||
dbb.Update(NewWithNodeSet(nodesB))
|
||||
trieb, _ = New(common.Hash{}, rootB, dbb)
|
||||
trieb, _ = New(TrieID(rootB), dbb)
|
||||
|
||||
di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)})
|
||||
it := NewIterator(di)
|
||||
@@ -356,7 +356,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
|
||||
}
|
||||
for i := 0; i < 20; i++ {
|
||||
// Create trie that will load all nodes from DB.
|
||||
tr, _ := New(common.Hash{}, tr.Hash(), triedb)
|
||||
tr, _ := New(TrieID(tr.Hash()), triedb)
|
||||
|
||||
// Remove a random node from the database. It can't be the root node
|
||||
// because that one is already loaded.
|
||||
@@ -445,7 +445,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
|
||||
}
|
||||
// Create a new iterator that seeks to "bars". Seeking can't proceed because
|
||||
// the node is missing.
|
||||
tr, _ := New(common.Hash{}, root, triedb)
|
||||
tr, _ := New(TrieID(root), triedb)
|
||||
it := tr.NodeIterator([]byte("bars"))
|
||||
missing, ok := it.Error().(*MissingNodeError)
|
||||
if !ok {
|
||||
@@ -533,7 +533,7 @@ func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) {
|
||||
// Create an empty trie
|
||||
logDb := &loggingDb{0, memorydb.New()}
|
||||
triedb := NewDatabase(logDb)
|
||||
trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb)
|
||||
trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
for i := 0; i < 10000; i++ {
|
||||
|
||||
+131
-16
@@ -18,6 +18,8 @@ package trie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
@@ -25,18 +27,77 @@ import (
|
||||
// memoryNode is all the information we know about a single cached trie node
|
||||
// in the memory.
|
||||
type memoryNode struct {
|
||||
hash common.Hash // Node hash, computed by hashing rlp value
|
||||
size uint16 // Byte size of the useful cached data
|
||||
node node // Cached collapsed trie node, or raw rlp data
|
||||
hash common.Hash // Node hash, computed by hashing rlp value, empty for deleted nodes
|
||||
size uint16 // Byte size of the useful cached data, 0 for deleted nodes
|
||||
node node // Cached collapsed trie node, or raw rlp data, nil for deleted nodes
|
||||
}
|
||||
|
||||
// memoryNodeSize is the raw size of a memoryNode data structure without any
|
||||
// node data included. It's an approximate size, but should be a lot better
|
||||
// than not counting them.
|
||||
// nolint:unused
|
||||
var memoryNodeSize = int(reflect.TypeOf(memoryNode{}).Size())
|
||||
|
||||
// memorySize returns the total memory size used by this node.
|
||||
// nolint:unused
|
||||
func (n *memoryNode) memorySize(key int) int {
|
||||
return int(n.size) + memoryNodeSize + key
|
||||
}
|
||||
|
||||
// rlp returns the raw rlp encoded blob of the cached trie node, either directly
|
||||
// from the cache, or by regenerating it from the collapsed node.
|
||||
// nolint:unused
|
||||
func (n *memoryNode) rlp() []byte {
|
||||
if node, ok := n.node.(rawNode); ok {
|
||||
return node
|
||||
}
|
||||
return nodeToBytes(n.node)
|
||||
}
|
||||
|
||||
// obj returns the decoded and expanded trie node, either directly from the cache,
|
||||
// or by regenerating it from the rlp encoded blob.
|
||||
// nolint:unused
|
||||
func (n *memoryNode) obj() node {
|
||||
if node, ok := n.node.(rawNode); ok {
|
||||
return mustDecodeNode(n.hash[:], node)
|
||||
}
|
||||
return expandNode(n.hash[:], n.node)
|
||||
}
|
||||
|
||||
// nodeWithPrev wraps the memoryNode with the previous node value.
|
||||
type nodeWithPrev struct {
|
||||
*memoryNode
|
||||
prev []byte // RLP-encoded previous value, nil means it's non-existent
|
||||
}
|
||||
|
||||
// unwrap returns the internal memoryNode object.
|
||||
// nolint:unused
|
||||
func (n *nodeWithPrev) unwrap() *memoryNode {
|
||||
return n.memoryNode
|
||||
}
|
||||
|
||||
// memorySize returns the total memory size used by this node. It overloads
|
||||
// the function in memoryNode by counting the size of previous value as well.
|
||||
// nolint: unused
|
||||
func (n *nodeWithPrev) memorySize(key int) int {
|
||||
return n.memoryNode.memorySize(key) + len(n.prev)
|
||||
}
|
||||
|
||||
// nodesWithOrder represents a collection of dirty nodes which includes
|
||||
// newly-inserted and updated nodes. The modification order of all nodes
|
||||
// is represented by order list.
|
||||
type nodesWithOrder struct {
|
||||
order []string // the path list of dirty nodes, sort by insertion order
|
||||
nodes map[string]*nodeWithPrev // the map of dirty nodes, keyed by node path
|
||||
}
|
||||
|
||||
// NodeSet contains all dirty nodes collected during the commit operation.
|
||||
// Each node is keyed by path. It's not thread-safe to use.
|
||||
type NodeSet struct {
|
||||
owner common.Hash // the identifier of the trie
|
||||
paths []string // the path of dirty nodes, sort by insertion order
|
||||
nodes map[string]*memoryNode // the map of dirty nodes, keyed by node path
|
||||
leaves []*leaf // the list of dirty leaves
|
||||
owner common.Hash // the identifier of the trie
|
||||
updates *nodesWithOrder // the set of updated nodes(newly inserted, updated)
|
||||
deletes map[string][]byte // the map of deleted nodes, keyed by node
|
||||
leaves []*leaf // the list of dirty leaves
|
||||
}
|
||||
|
||||
// NewNodeSet initializes an empty node set to be used for tracking dirty nodes
|
||||
@@ -45,24 +106,78 @@ type NodeSet struct {
|
||||
func NewNodeSet(owner common.Hash) *NodeSet {
|
||||
return &NodeSet{
|
||||
owner: owner,
|
||||
nodes: make(map[string]*memoryNode),
|
||||
updates: &nodesWithOrder{
|
||||
nodes: make(map[string]*nodeWithPrev),
|
||||
},
|
||||
deletes: make(map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// add caches node with provided path and node object.
|
||||
func (set *NodeSet) add(path string, node *memoryNode) {
|
||||
set.paths = append(set.paths, path)
|
||||
set.nodes[path] = node
|
||||
// NewNodeSetWithDeletion initializes the nodeset with provided deletion set.
|
||||
func NewNodeSetWithDeletion(owner common.Hash, paths [][]byte, prev [][]byte) *NodeSet {
|
||||
set := NewNodeSet(owner)
|
||||
for i, path := range paths {
|
||||
set.markDeleted(path, prev[i])
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// addLeaf caches the provided leaf node.
|
||||
// markUpdated marks the node as dirty(newly-inserted or updated) with provided
|
||||
// node path, node object along with its previous value.
|
||||
func (set *NodeSet) markUpdated(path []byte, node *memoryNode, prev []byte) {
|
||||
set.updates.order = append(set.updates.order, string(path))
|
||||
set.updates.nodes[string(path)] = &nodeWithPrev{
|
||||
memoryNode: node,
|
||||
prev: prev,
|
||||
}
|
||||
}
|
||||
|
||||
// markDeleted marks the node as deleted with provided path and previous value.
|
||||
func (set *NodeSet) markDeleted(path []byte, prev []byte) {
|
||||
set.deletes[string(path)] = prev
|
||||
}
|
||||
|
||||
// addLeaf collects the provided leaf node into set.
|
||||
func (set *NodeSet) addLeaf(node *leaf) {
|
||||
set.leaves = append(set.leaves, node)
|
||||
}
|
||||
|
||||
// Len returns the number of dirty nodes contained in the set.
|
||||
func (set *NodeSet) Len() int {
|
||||
return len(set.nodes)
|
||||
// Size returns the number of updated and deleted nodes contained in the set.
|
||||
func (set *NodeSet) Size() (int, int) {
|
||||
return len(set.updates.order), len(set.deletes)
|
||||
}
|
||||
|
||||
// Hashes returns the hashes of all updated nodes. TODO(rjl493456442) how can
|
||||
// we get rid of it?
|
||||
func (set *NodeSet) Hashes() []common.Hash {
|
||||
var ret []common.Hash
|
||||
for _, node := range set.updates.nodes {
|
||||
ret = append(ret, node.hash)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Summary returns a string-representation of the NodeSet.
|
||||
func (set *NodeSet) Summary() string {
|
||||
var out = new(strings.Builder)
|
||||
fmt.Fprintf(out, "nodeset owner: %v\n", set.owner)
|
||||
if set.updates != nil {
|
||||
for _, key := range set.updates.order {
|
||||
updated := set.updates.nodes[key]
|
||||
if updated.prev != nil {
|
||||
fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", key, updated.hash, updated.prev)
|
||||
} else {
|
||||
fmt.Fprintf(out, " [+]: %x -> %v\n", key, updated.hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, n := range set.deletes {
|
||||
fmt.Fprintf(out, " [-]: %x -> %x\n", k, n)
|
||||
}
|
||||
for _, n := range set.leaves {
|
||||
fmt.Fprintf(out, "[leaf]: %v\n", n)
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
// MergedNodeSet represents a merged dirty node set for a group of tries.
|
||||
|
||||
+7
-3
@@ -22,7 +22,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
@@ -60,8 +59,13 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
|
||||
key = key[1:]
|
||||
nodes = append(nodes, n)
|
||||
case hashNode:
|
||||
// Retrieve the specified node from the underlying node reader.
|
||||
// trie.resolveAndTrack is not used since in that function the
|
||||
// loaded blob will be tracked, while it's not required here since
|
||||
// all loaded nodes won't be linked to trie at all and track nodes
|
||||
// may lead to out-of-memory issue.
|
||||
var err error
|
||||
tn, err = t.resolveHash(n, prefix)
|
||||
tn, err = t.reader.node(prefix, common.BytesToHash(n))
|
||||
if err != nil {
|
||||
log.Error("Unhandled trie error in Trie.Prove", "err", err)
|
||||
return err
|
||||
@@ -559,7 +563,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
|
||||
}
|
||||
// Rebuild the trie with the leaf stream, the shape of trie
|
||||
// should be same with the original one.
|
||||
tr := &Trie{root: root, db: NewDatabase(rawdb.NewMemoryDatabase())}
|
||||
tr := &Trie{root: root, reader: newEmptyReader()}
|
||||
if empty {
|
||||
tr.root = nil
|
||||
}
|
||||
|
||||
+9
-4
@@ -29,8 +29,13 @@ type SecureTrie = StateTrie
|
||||
|
||||
// NewSecure creates a new StateTrie.
|
||||
// Deprecated: use NewStateTrie.
|
||||
func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) {
|
||||
return NewStateTrie(owner, root, db)
|
||||
func NewSecure(stateRoot common.Hash, owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) {
|
||||
id := &ID{
|
||||
StateRoot: stateRoot,
|
||||
Owner: owner,
|
||||
Root: root,
|
||||
}
|
||||
return NewStateTrie(id, db)
|
||||
}
|
||||
|
||||
// StateTrie wraps a trie with key hashing. In a stateTrie trie, all
|
||||
@@ -56,11 +61,11 @@ type StateTrie struct {
|
||||
// If root is the zero hash or the sha3 hash of an empty string, the
|
||||
// trie is initially empty. Otherwise, New will panic if db is nil
|
||||
// and returns MissingNodeError if the root node cannot be found.
|
||||
func NewStateTrie(owner common.Hash, root common.Hash, db *Database) (*StateTrie, error) {
|
||||
func NewStateTrie(id *ID, db *Database) (*StateTrie, error) {
|
||||
if db == nil {
|
||||
panic("trie.NewStateTrie called without a database")
|
||||
}
|
||||
trie, err := New(owner, root, db)
|
||||
trie, err := New(id, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
)
|
||||
|
||||
func newEmptySecure() *StateTrie {
|
||||
trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New()))
|
||||
trie, _ := NewStateTrie(TrieID(common.Hash{}), NewDatabase(memorydb.New()))
|
||||
return trie
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func newEmptySecure() *StateTrie {
|
||||
func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) {
|
||||
// Create an empty trie
|
||||
triedb := NewDatabase(memorydb.New())
|
||||
trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb)
|
||||
trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
content := make(map[string][]byte)
|
||||
@@ -66,7 +66,7 @@ func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) {
|
||||
panic(fmt.Errorf("failed to commit db %v", err))
|
||||
}
|
||||
// Re-create the trie based on the new state
|
||||
trie, _ = NewSecure(common.Hash{}, root, triedb)
|
||||
trie, _ = NewStateTrie(TrieID(root), triedb)
|
||||
return triedb, trie, content
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -30,7 +30,7 @@ import (
|
||||
func makeTestTrie() (*Database, *StateTrie, map[string][]byte) {
|
||||
// Create an empty trie
|
||||
triedb := NewDatabase(memorydb.New())
|
||||
trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb)
|
||||
trie, _ := NewStateTrie(TrieID(common.Hash{}), triedb)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
content := make(map[string][]byte)
|
||||
@@ -59,7 +59,7 @@ func makeTestTrie() (*Database, *StateTrie, map[string][]byte) {
|
||||
panic(fmt.Errorf("failed to commit db %v", err))
|
||||
}
|
||||
// Re-create the trie based on the new state
|
||||
trie, _ = NewSecure(common.Hash{}, root, triedb)
|
||||
trie, _ = NewStateTrie(TrieID(root), triedb)
|
||||
return triedb, trie, content
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func makeTestTrie() (*Database, *StateTrie, map[string][]byte) {
|
||||
// content map.
|
||||
func checkTrieContents(t *testing.T, db *Database, root []byte, content map[string][]byte) {
|
||||
// Check root availability and trie contents
|
||||
trie, err := NewStateTrie(common.Hash{}, common.BytesToHash(root), db)
|
||||
trie, err := NewStateTrie(TrieID(common.BytesToHash(root)), db)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create trie at %x: %v", root, err)
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func checkTrieContents(t *testing.T, db *Database, root []byte, content map[stri
|
||||
// checkTrieConsistency checks that all nodes in a trie are indeed present.
|
||||
func checkTrieConsistency(db *Database, root common.Hash) error {
|
||||
// Create and iterate a trie rooted in a subnode
|
||||
trie, err := NewStateTrie(common.Hash{}, root, db)
|
||||
trie, err := NewStateTrie(TrieID(root), db)
|
||||
if err != nil {
|
||||
return nil // Consider a non existent state consistent
|
||||
}
|
||||
@@ -105,8 +105,8 @@ type trieElement struct {
|
||||
func TestEmptySync(t *testing.T) {
|
||||
dbA := NewDatabase(memorydb.New())
|
||||
dbB := NewDatabase(memorydb.New())
|
||||
emptyA := NewEmpty(dbA)
|
||||
emptyB, _ := New(common.Hash{}, emptyRoot, dbB)
|
||||
emptyA, _ := New(TrieID(common.Hash{}), dbA)
|
||||
emptyB, _ := New(TrieID(emptyRoot), dbB)
|
||||
|
||||
for i, trie := range []*Trie{emptyA, emptyB} {
|
||||
sync := NewSync(trie.Hash(), memorydb.New(), nil)
|
||||
|
||||
+36
-43
@@ -67,9 +67,8 @@ type Trie struct {
|
||||
// actually unhashed nodes.
|
||||
unhashed int
|
||||
|
||||
// db is the handler trie can retrieve nodes from. It's
|
||||
// only for reading purpose and not available for writing.
|
||||
db *Database
|
||||
// reader is the handler trie can retrieve nodes from.
|
||||
reader *trieReader
|
||||
|
||||
// tracer is the tool to track the trie changes.
|
||||
// It will be reset after each commit operation.
|
||||
@@ -87,26 +86,29 @@ func (t *Trie) Copy() *Trie {
|
||||
root: t.root,
|
||||
owner: t.owner,
|
||||
unhashed: t.unhashed,
|
||||
db: t.db,
|
||||
reader: t.reader,
|
||||
tracer: t.tracer.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a trie with an existing root node from db and an assigned
|
||||
// owner for storage proximity.
|
||||
//
|
||||
// If root is the zero hash or the sha3 hash of an empty string, the
|
||||
// trie is initially empty and does not require a database. Otherwise,
|
||||
// New will panic if db is nil and returns a MissingNodeError if root does
|
||||
// not exist in the database. Accessing the trie loads nodes from db on demand.
|
||||
func New(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
|
||||
// New creates the trie instance with provided trie id and the read-only
|
||||
// database. The state specified by trie id must be available, otherwise
|
||||
// an error will be returned. The trie root specified by trie id can be
|
||||
// zero hash or the sha3 hash of an empty string, then trie is initially
|
||||
// empty, otherwise, the root node must be present in database or returns
|
||||
// a MissingNodeError if not.
|
||||
func New(id *ID, db NodeReader) (*Trie, error) {
|
||||
reader, err := newTrieReader(id.StateRoot, id.Owner, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trie := &Trie{
|
||||
owner: owner,
|
||||
db: db,
|
||||
owner: id.Owner,
|
||||
reader: reader,
|
||||
//tracer: newTracer(),
|
||||
}
|
||||
if root != (common.Hash{}) && root != emptyRoot {
|
||||
rootnode, err := trie.resolveHash(root[:], nil)
|
||||
if id.Root != (common.Hash{}) && id.Root != emptyRoot {
|
||||
rootnode, err := trie.resolveAndTrack(id.Root[:], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -117,7 +119,7 @@ func New(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
|
||||
|
||||
// NewEmpty is a shortcut to create empty tree. It's mostly used in tests.
|
||||
func NewEmpty(db *Database) *Trie {
|
||||
tr, _ := New(common.Hash{}, common.Hash{}, db)
|
||||
tr, _ := New(TrieID(common.Hash{}), db)
|
||||
return tr
|
||||
}
|
||||
|
||||
@@ -173,7 +175,7 @@ func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode
|
||||
}
|
||||
return value, n, didResolve, err
|
||||
case hashNode:
|
||||
child, err := t.resolveHash(n, key[:pos])
|
||||
child, err := t.resolveAndTrack(n, key[:pos])
|
||||
if err != nil {
|
||||
return nil, n, true, err
|
||||
}
|
||||
@@ -219,7 +221,7 @@ func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, new
|
||||
if hash == nil {
|
||||
return nil, origNode, 0, errors.New("non-consensus node")
|
||||
}
|
||||
blob, err := t.db.Node(common.BytesToHash(hash))
|
||||
blob, err := t.reader.nodeBlob(path, common.BytesToHash(hash))
|
||||
return blob, origNode, 1, err
|
||||
}
|
||||
// Path still needs to be traversed, descend into children
|
||||
@@ -249,7 +251,7 @@ func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, new
|
||||
return item, n, resolved, err
|
||||
|
||||
case hashNode:
|
||||
child, err := t.resolveHash(n, path[:pos])
|
||||
child, err := t.resolveAndTrack(n, path[:pos])
|
||||
if err != nil {
|
||||
return nil, n, 1, err
|
||||
}
|
||||
@@ -370,7 +372,7 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||
// We've hit a part of the trie that isn't loaded yet. Load
|
||||
// the node and insert into it. This leaves all child nodes on
|
||||
// the path to the value in the trie.
|
||||
rn, err := t.resolveHash(n, prefix)
|
||||
rn, err := t.resolveAndTrack(n, prefix)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
@@ -524,7 +526,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||
// We've hit a part of the trie that isn't loaded yet. Load
|
||||
// the node and delete from it. This leaves all child nodes on
|
||||
// the path to the value in the trie.
|
||||
rn, err := t.resolveHash(n, prefix)
|
||||
rn, err := t.resolveAndTrack(n, prefix)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
@@ -548,30 +550,22 @@ func concat(s1 []byte, s2 ...byte) []byte {
|
||||
|
||||
func (t *Trie) resolve(n node, prefix []byte) (node, error) {
|
||||
if n, ok := n.(hashNode); ok {
|
||||
return t.resolveHash(n, prefix)
|
||||
return t.resolveAndTrack(n, prefix)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// resolveHash loads node from the underlying database with the provided
|
||||
// node hash and path prefix.
|
||||
func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
|
||||
hash := common.BytesToHash(n)
|
||||
if node := t.db.node(hash); node != nil {
|
||||
return node, nil
|
||||
// resolveAndTrack loads node from the underlying store with the given node hash
|
||||
// and path prefix and also tracks the loaded node blob in tracer treated as the
|
||||
// node's original value. The rlp-encoded blob is preferred to be loaded from
|
||||
// database because it's easy to decode node while complex to encode node to blob.
|
||||
func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
|
||||
blob, err := t.reader.nodeBlob(prefix, common.BytesToHash(n))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix}
|
||||
}
|
||||
|
||||
// resolveHash loads rlp-encoded node blob from the underlying database
|
||||
// with the provided node hash and path prefix.
|
||||
func (t *Trie) resolveBlob(n hashNode, prefix []byte) ([]byte, error) {
|
||||
hash := common.BytesToHash(n)
|
||||
blob, _ := t.db.Node(hash)
|
||||
if len(blob) != 0 {
|
||||
return blob, nil
|
||||
}
|
||||
return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix}
|
||||
t.tracer.onRead(prefix, blob)
|
||||
return mustDecodeNode(n, blob), nil
|
||||
}
|
||||
|
||||
// Hash returns the root hash of the trie. It does not write to the
|
||||
@@ -606,7 +600,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
|
||||
t.root = hashedNode
|
||||
return rootHash, nil, nil
|
||||
}
|
||||
h := newCommitter(t.owner, collectLeaf)
|
||||
h := newCommitter(t.owner, t.tracer, collectLeaf)
|
||||
newRoot, nodes, err := h.Commit(t.root)
|
||||
if err != nil {
|
||||
return common.Hash{}, nil, err
|
||||
@@ -633,6 +627,5 @@ func (t *Trie) Reset() {
|
||||
t.root = nil
|
||||
t.owner = common.Hash{}
|
||||
t.unhashed = 0
|
||||
//t.db = nil
|
||||
t.tracer.reset()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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 "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
// ID is the identifier for uniquely identifying a trie.
|
||||
type ID struct {
|
||||
StateRoot common.Hash // The root of the corresponding state(block.root)
|
||||
Owner common.Hash // The contract address hash which the trie belongs to
|
||||
Root common.Hash // The root hash of trie
|
||||
}
|
||||
|
||||
// StateTrieID constructs an identifier for state trie with the provided state root.
|
||||
func StateTrieID(root common.Hash) *ID {
|
||||
return &ID{
|
||||
StateRoot: root,
|
||||
Owner: common.Hash{},
|
||||
Root: root,
|
||||
}
|
||||
}
|
||||
|
||||
// StorageTrieID constructs an identifier for storage trie which belongs to a certain
|
||||
// state and contract specified by the stateRoot and owner.
|
||||
func StorageTrieID(stateRoot common.Hash, owner common.Hash, root common.Hash) *ID {
|
||||
return &ID{
|
||||
StateRoot: stateRoot,
|
||||
Owner: owner,
|
||||
Root: root,
|
||||
}
|
||||
}
|
||||
|
||||
// TrieID constructs an identifier for a standard trie(not a second-layer trie)
|
||||
// with provided root. It's mostly used in tests and some other tries like CHT trie.
|
||||
func TrieID(root common.Hash) *ID {
|
||||
return &ID{
|
||||
StateRoot: root,
|
||||
Owner: common.Hash{},
|
||||
Root: root,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// Reader wraps the Node and NodeBlob method of a backing trie store.
|
||||
type Reader interface {
|
||||
// Node retrieves the trie node with the provided trie identifier, hexary
|
||||
// node path and the corresponding node hash.
|
||||
// No error will be returned if the node is not found.
|
||||
Node(owner common.Hash, path []byte, hash common.Hash) (node, error)
|
||||
|
||||
// NodeBlob retrieves the RLP-encoded trie node blob with the provided trie
|
||||
// identifier, hexary node path and the corresponding node hash.
|
||||
// No error will be returned if the node is not found.
|
||||
NodeBlob(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
||||
}
|
||||
|
||||
// NodeReader wraps all the necessary functions for accessing trie node.
|
||||
type NodeReader interface {
|
||||
// GetReader returns a reader for accessing all trie nodes with provided
|
||||
// state root. Nil is returned in case the state is not available.
|
||||
GetReader(root common.Hash) Reader
|
||||
}
|
||||
|
||||
// trieReader is a wrapper of the underlying node reader. It's not safe
|
||||
// for concurrent usage.
|
||||
type trieReader struct {
|
||||
owner common.Hash
|
||||
reader Reader
|
||||
banned map[string]struct{} // Marker to prevent node from being accessed, for tests
|
||||
}
|
||||
|
||||
// newTrieReader initializes the trie reader with the given node reader.
|
||||
func newTrieReader(stateRoot, owner common.Hash, db NodeReader) (*trieReader, error) {
|
||||
reader := db.GetReader(stateRoot)
|
||||
if reader == nil {
|
||||
return nil, fmt.Errorf("state not found #%x", stateRoot)
|
||||
}
|
||||
return &trieReader{owner: owner, reader: reader}, nil
|
||||
}
|
||||
|
||||
// newEmptyReader initializes the pure in-memory reader. All read operations
|
||||
// should be forbidden and returns the MissingNodeError.
|
||||
func newEmptyReader() *trieReader {
|
||||
return &trieReader{}
|
||||
}
|
||||
|
||||
// node retrieves the trie node with the provided trie node information.
|
||||
// An MissingNodeError will be returned in case the node is not found or
|
||||
// any error is encountered.
|
||||
func (r *trieReader) node(path []byte, hash common.Hash) (node, error) {
|
||||
// Perform the logics in tests for preventing trie node access.
|
||||
if r.banned != nil {
|
||||
if _, ok := r.banned[string(path)]; ok {
|
||||
return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path}
|
||||
}
|
||||
}
|
||||
if r.reader == nil {
|
||||
return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path}
|
||||
}
|
||||
node, err := r.reader.Node(r.owner, path, hash)
|
||||
if err != nil || node == nil {
|
||||
return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path, err: err}
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// node retrieves the rlp-encoded trie node with the provided trie node
|
||||
// information. An MissingNodeError will be returned in case the node is
|
||||
// not found or any error is encountered.
|
||||
func (r *trieReader) nodeBlob(path []byte, hash common.Hash) ([]byte, error) {
|
||||
// Perform the logics in tests for preventing trie node access.
|
||||
if r.banned != nil {
|
||||
if _, ok := r.banned[string(path)]; ok {
|
||||
return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path}
|
||||
}
|
||||
}
|
||||
if r.reader == nil {
|
||||
return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path}
|
||||
}
|
||||
blob, err := r.reader.NodeBlob(r.owner, path, hash)
|
||||
if err != nil || len(blob) == 0 {
|
||||
return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path, err: err}
|
||||
}
|
||||
return blob, nil
|
||||
}
|
||||
+55
-17
@@ -64,7 +64,8 @@ func TestNull(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMissingRoot(t *testing.T) {
|
||||
trie, err := New(common.Hash{}, common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(memorydb.New()))
|
||||
root := common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")
|
||||
trie, err := New(TrieID(root), NewDatabase(memorydb.New()))
|
||||
if trie != nil {
|
||||
t.Error("New returned non-nil trie for invalid root")
|
||||
}
|
||||
@@ -89,27 +90,27 @@ func testMissingNode(t *testing.T, memonly bool) {
|
||||
triedb.Commit(root, true, nil)
|
||||
}
|
||||
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
_, err := trie.TryGet([]byte("120000"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
_, err = trie.TryGet([]byte("120099"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
_, err = trie.TryGet([]byte("123456"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
err = trie.TryDelete([]byte("123456"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
@@ -122,27 +123,27 @@ func testMissingNode(t *testing.T, memonly bool) {
|
||||
diskdb.Delete(hash[:])
|
||||
}
|
||||
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
_, err = trie.TryGet([]byte("120000"))
|
||||
if _, ok := err.(*MissingNodeError); !ok {
|
||||
t.Errorf("Wrong error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
_, err = trie.TryGet([]byte("120099"))
|
||||
if _, ok := err.(*MissingNodeError); !ok {
|
||||
t.Errorf("Wrong error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
_, err = trie.TryGet([]byte("123456"))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
err = trie.TryUpdate([]byte("120099"), []byte("zxcv"))
|
||||
if _, ok := err.(*MissingNodeError); !ok {
|
||||
t.Errorf("Wrong error: %v", err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, triedb)
|
||||
trie, _ = New(TrieID(root), triedb)
|
||||
err = trie.TryDelete([]byte("123456"))
|
||||
if _, ok := err.(*MissingNodeError); !ok {
|
||||
t.Errorf("Wrong error: %v", err)
|
||||
@@ -196,7 +197,7 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
root, nodes, _ := trie.Commit(false)
|
||||
db.Update(NewWithNodeSet(nodes))
|
||||
trie, _ = New(common.Hash{}, root, db)
|
||||
trie, _ = New(TrieID(root), db)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +274,7 @@ func TestReplication(t *testing.T) {
|
||||
triedb.Update(NewWithNodeSet(nodes))
|
||||
|
||||
// create a new trie on top of the database and check that lookups work.
|
||||
trie2, err := New(common.Hash{}, exp, triedb)
|
||||
trie2, err := New(TrieID(exp), triedb)
|
||||
if err != nil {
|
||||
t.Fatalf("can't recreate trie at %x: %v", exp, err)
|
||||
}
|
||||
@@ -294,7 +295,7 @@ func TestReplication(t *testing.T) {
|
||||
if nodes != nil {
|
||||
triedb.Update(NewWithNodeSet(nodes))
|
||||
}
|
||||
trie2, err = New(common.Hash{}, hash, triedb)
|
||||
trie2, err = New(TrieID(hash), triedb)
|
||||
if err != nil {
|
||||
t.Fatalf("can't recreate trie at %x: %v", exp, err)
|
||||
}
|
||||
@@ -377,6 +378,7 @@ const (
|
||||
opCommit
|
||||
opItercheckhash
|
||||
opNodeDiff
|
||||
opProve
|
||||
opMax // boundary value, not an actual op
|
||||
)
|
||||
|
||||
@@ -402,7 +404,7 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
|
||||
step.key = genKey()
|
||||
step.value = make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(step.value, uint64(i))
|
||||
case opGet, opDelete:
|
||||
case opGet, opDelete, opProve:
|
||||
step.key = genKey()
|
||||
}
|
||||
steps = append(steps, step)
|
||||
@@ -436,24 +438,60 @@ func runRandTest(rt randTest) bool {
|
||||
if string(v) != want {
|
||||
rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want)
|
||||
}
|
||||
case opProve:
|
||||
hash := tr.Hash()
|
||||
if hash == emptyRoot {
|
||||
continue
|
||||
}
|
||||
proofDb := rawdb.NewMemoryDatabase()
|
||||
err := tr.Prove(step.key, 0, proofDb)
|
||||
if err != nil {
|
||||
rt[i].err = fmt.Errorf("failed for proving key %#x, %v", step.key, err)
|
||||
}
|
||||
_, err = VerifyProof(hash, step.key, proofDb)
|
||||
if err != nil {
|
||||
rt[i].err = fmt.Errorf("failed for verifying key %#x, %v", step.key, err)
|
||||
}
|
||||
case opHash:
|
||||
tr.Hash()
|
||||
case opCommit:
|
||||
hash, nodes, err := tr.Commit(false)
|
||||
root, nodes, err := tr.Commit(true)
|
||||
if err != nil {
|
||||
rt[i].err = err
|
||||
return false
|
||||
}
|
||||
// Validity the returned nodeset
|
||||
if nodes != nil {
|
||||
for path, node := range nodes.updates.nodes {
|
||||
blob, _, _ := origTrie.TryGetNode(hexToCompact([]byte(path)))
|
||||
got := node.prev
|
||||
if !bytes.Equal(blob, got) {
|
||||
rt[i].err = fmt.Errorf("prevalue mismatch for 0x%x, got 0x%x want 0x%x", path, got, blob)
|
||||
panic(rt[i].err)
|
||||
}
|
||||
}
|
||||
for path, prev := range nodes.deletes {
|
||||
blob, _, _ := origTrie.TryGetNode(hexToCompact([]byte(path)))
|
||||
if !bytes.Equal(blob, prev) {
|
||||
rt[i].err = fmt.Errorf("prevalue mismatch for 0x%x, got 0x%x want 0x%x", path, prev, blob)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if nodes != nil {
|
||||
triedb.Update(NewWithNodeSet(nodes))
|
||||
}
|
||||
newtr, err := New(common.Hash{}, hash, triedb)
|
||||
newtr, err := New(TrieID(root), triedb)
|
||||
if err != nil {
|
||||
rt[i].err = err
|
||||
return false
|
||||
}
|
||||
tr = newtr
|
||||
|
||||
// Enable node tracing. Resolve the root node again explicitly
|
||||
// since it's not captured at the beginning.
|
||||
tr.tracer = newTracer()
|
||||
tr.resolveAndTrack(root.Bytes(), nil)
|
||||
|
||||
origTrie = tr.Copy()
|
||||
case opItercheckhash:
|
||||
|
||||
+119
-1
@@ -17,6 +17,7 @@
|
||||
package trie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -72,7 +73,7 @@ func TestTrieTracer(t *testing.T) {
|
||||
if err := db.Update(NewWithNodeSet(nodes)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
trie, _ = New(common.Hash{}, root, db)
|
||||
trie, _ = New(TrieID(root), db)
|
||||
trie.tracer = newTracer()
|
||||
|
||||
// Delete all the elements, check deletion set
|
||||
@@ -124,3 +125,120 @@ func TestTrieTracerNoop(t *testing.T) {
|
||||
t.Fatalf("Unexpected deleted node tracked %d", len(trie.tracer.deleteList()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrieTracePrevValue(t *testing.T) {
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
trie := NewEmpty(db)
|
||||
trie.tracer = newTracer()
|
||||
|
||||
paths, blobs := trie.tracer.prevList()
|
||||
if len(paths) != 0 || len(blobs) != 0 {
|
||||
t.Fatalf("Nothing should be tracked")
|
||||
}
|
||||
// Insert a batch of entries, all the nodes should be marked as inserted
|
||||
vals := []struct{ k, v string }{
|
||||
{"do", "verb"},
|
||||
{"ether", "wookiedoo"},
|
||||
{"horse", "stallion"},
|
||||
{"shaman", "horse"},
|
||||
{"doge", "coin"},
|
||||
{"dog", "puppy"},
|
||||
{"somethingveryoddindeedthis is", "myothernodedata"},
|
||||
}
|
||||
for _, val := range vals {
|
||||
trie.Update([]byte(val.k), []byte(val.v))
|
||||
}
|
||||
paths, blobs = trie.tracer.prevList()
|
||||
if len(paths) != 0 || len(blobs) != 0 {
|
||||
t.Fatalf("Nothing should be tracked")
|
||||
}
|
||||
|
||||
// Commit the changes and re-create with new root
|
||||
root, nodes, _ := trie.Commit(false)
|
||||
if err := db.Update(NewWithNodeSet(nodes)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
trie, _ = New(TrieID(root), db)
|
||||
trie.tracer = newTracer()
|
||||
trie.resolveAndTrack(root.Bytes(), nil)
|
||||
|
||||
// Load all nodes in trie
|
||||
for _, val := range vals {
|
||||
trie.TryGet([]byte(val.k))
|
||||
}
|
||||
|
||||
// Ensure all nodes are tracked by tracer with correct prev-values
|
||||
iter := trie.NodeIterator(nil)
|
||||
seen := make(map[string][]byte)
|
||||
for iter.Next(true) {
|
||||
// Embedded nodes are ignored since they are not present in
|
||||
// database.
|
||||
if iter.Hash() == (common.Hash{}) {
|
||||
continue
|
||||
}
|
||||
seen[string(iter.Path())] = common.CopyBytes(iter.NodeBlob())
|
||||
}
|
||||
|
||||
paths, blobs = trie.tracer.prevList()
|
||||
if len(paths) != len(seen) || len(blobs) != len(seen) {
|
||||
t.Fatalf("Unexpected tracked values")
|
||||
}
|
||||
for i, path := range paths {
|
||||
blob := blobs[i]
|
||||
prev, ok := seen[string(path)]
|
||||
if !ok {
|
||||
t.Fatalf("Missing node %v", path)
|
||||
}
|
||||
if !bytes.Equal(blob, prev) {
|
||||
t.Fatalf("Unexpected value path: %v, want: %v, got: %v", path, prev, blob)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-open the trie and iterate the trie, ensure nothing will be tracked.
|
||||
// Iterator will not link any loaded nodes to trie.
|
||||
trie, _ = New(TrieID(root), db)
|
||||
trie.tracer = newTracer()
|
||||
|
||||
iter = trie.NodeIterator(nil)
|
||||
for iter.Next(true) {
|
||||
}
|
||||
paths, blobs = trie.tracer.prevList()
|
||||
if len(paths) != 0 || len(blobs) != 0 {
|
||||
t.Fatalf("Nothing should be tracked")
|
||||
}
|
||||
|
||||
// Re-open the trie and generate proof for entries, ensure nothing will
|
||||
// be tracked. Prover will not link any loaded nodes to trie.
|
||||
trie, _ = New(TrieID(root), db)
|
||||
trie.tracer = newTracer()
|
||||
for _, val := range vals {
|
||||
trie.Prove([]byte(val.k), 0, rawdb.NewMemoryDatabase())
|
||||
}
|
||||
paths, blobs = trie.tracer.prevList()
|
||||
if len(paths) != 0 || len(blobs) != 0 {
|
||||
t.Fatalf("Nothing should be tracked")
|
||||
}
|
||||
|
||||
// Delete entries from trie, ensure all previous values are correct.
|
||||
trie, _ = New(TrieID(root), db)
|
||||
trie.tracer = newTracer()
|
||||
trie.resolveAndTrack(root.Bytes(), nil)
|
||||
|
||||
for _, val := range vals {
|
||||
trie.TryDelete([]byte(val.k))
|
||||
}
|
||||
paths, blobs = trie.tracer.prevList()
|
||||
if len(paths) != len(seen) || len(blobs) != len(seen) {
|
||||
t.Fatalf("Unexpected tracked values")
|
||||
}
|
||||
for i, path := range paths {
|
||||
blob := blobs[i]
|
||||
prev, ok := seen[string(path)]
|
||||
if !ok {
|
||||
t.Fatalf("Missing node %v", path)
|
||||
}
|
||||
if !bytes.Equal(blob, prev) {
|
||||
t.Fatalf("Unexpected value path: %v, want: %v, got: %v", path, prev, blob)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-21
@@ -50,45 +50,43 @@ func newTracer() *tracer {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// onRead tracks the newly loaded trie node and caches the rlp-encoded blob internally.
|
||||
// Don't change the value outside of function since it's not deep-copied.
|
||||
func (t *tracer) onRead(key []byte, val []byte) {
|
||||
func (t *tracer) onRead(path []byte, val []byte) {
|
||||
// Tracer isn't used right now, remove this check later.
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
t.origin[string(key)] = val
|
||||
t.origin[string(path)] = val
|
||||
}
|
||||
*/
|
||||
|
||||
// onInsert tracks the newly inserted trie node. If it's already in the deletion set
|
||||
// (resurrected node), then just wipe it from the deletion set as the "untouched".
|
||||
func (t *tracer) onInsert(key []byte) {
|
||||
func (t *tracer) onInsert(path []byte) {
|
||||
// Tracer isn't used right now, remove this check later.
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if _, present := t.delete[string(key)]; present {
|
||||
delete(t.delete, string(key))
|
||||
if _, present := t.delete[string(path)]; present {
|
||||
delete(t.delete, string(path))
|
||||
return
|
||||
}
|
||||
t.insert[string(key)] = struct{}{}
|
||||
t.insert[string(path)] = struct{}{}
|
||||
}
|
||||
|
||||
// onDelete tracks the newly deleted trie node. If it's already
|
||||
// in the addition set, then just wipe it from the addition set
|
||||
// as it's untouched.
|
||||
func (t *tracer) onDelete(key []byte) {
|
||||
func (t *tracer) onDelete(path []byte) {
|
||||
// Tracer isn't used right now, remove this check later.
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if _, present := t.insert[string(key)]; present {
|
||||
delete(t.insert, string(key))
|
||||
if _, present := t.insert[string(path)]; present {
|
||||
delete(t.insert, string(path))
|
||||
return
|
||||
}
|
||||
t.delete[string(key)] = struct{}{}
|
||||
t.delete[string(path)] = struct{}{}
|
||||
}
|
||||
|
||||
// insertList returns the tracked inserted trie nodes in list format.
|
||||
@@ -98,8 +96,8 @@ func (t *tracer) insertList() [][]byte {
|
||||
return nil
|
||||
}
|
||||
var ret [][]byte
|
||||
for key := range t.insert {
|
||||
ret = append(ret, []byte(key))
|
||||
for path := range t.insert {
|
||||
ret = append(ret, []byte(path))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -111,22 +109,37 @@ func (t *tracer) deleteList() [][]byte {
|
||||
return nil
|
||||
}
|
||||
var ret [][]byte
|
||||
for key := range t.delete {
|
||||
ret = append(ret, []byte(key))
|
||||
for path := range t.delete {
|
||||
ret = append(ret, []byte(path))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
/*
|
||||
// prevList returns the tracked node blobs in list format.
|
||||
func (t *tracer) prevList() ([][]byte, [][]byte) {
|
||||
// Tracer isn't used right now, remove this check later.
|
||||
if t == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var (
|
||||
paths [][]byte
|
||||
blobs [][]byte
|
||||
)
|
||||
for path, blob := range t.origin {
|
||||
paths = append(paths, []byte(path))
|
||||
blobs = append(blobs, blob)
|
||||
}
|
||||
return paths, blobs
|
||||
}
|
||||
|
||||
// getPrev returns the cached original value of the specified node.
|
||||
func (t *tracer) getPrev(key []byte) []byte {
|
||||
// Don't panic on uninitialized tracer, it's possible in testing.
|
||||
func (t *tracer) getPrev(path []byte) []byte {
|
||||
// Tracer isn't used right now, remove this check later.
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return t.origin[string(key)]
|
||||
return t.origin[string(path)]
|
||||
}
|
||||
*/
|
||||
|
||||
// reset clears the content tracked by tracer.
|
||||
func (t *tracer) reset() {
|
||||
|
||||
Reference in New Issue
Block a user