mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 10:50:44 +00:00
Trie tracer is an auxiliary tool to capture all deleted nodes which can't be captured by trie.Committer. The deleted nodes can be removed from the disk later. Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
This commit is contained in:
parent
0c57a6c64c
commit
ee81b721fc
11 changed files with 408 additions and 30 deletions
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||||
|
|
@ -22,7 +23,8 @@ func TestDeriveSha(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for len(txs) < 1000 {
|
for len(txs) < 1000 {
|
||||||
exp := types.DeriveSha(txs, new(trie.Trie))
|
tr, _ := trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
|
exp := types.DeriveSha(txs, tr)
|
||||||
got := types.DeriveSha(txs, trie.NewStackTrie(nil))
|
got := types.DeriveSha(txs, trie.NewStackTrie(nil))
|
||||||
if !bytes.Equal(got[:], exp[:]) {
|
if !bytes.Equal(got[:], exp[:]) {
|
||||||
t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp)
|
t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp)
|
||||||
|
|
@ -69,7 +71,8 @@ func BenchmarkDeriveSha200(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
exp = types.DeriveSha(txs, new(trie.Trie))
|
tr, _ := trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
|
exp = types.DeriveSha(txs, tr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -90,7 +93,8 @@ func TestFuzzDeriveSha(t *testing.T) {
|
||||||
rndSeed := mrand.Int()
|
rndSeed := mrand.Int()
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
seed := rndSeed + i
|
seed := rndSeed + i
|
||||||
exp := types.DeriveSha(newDummy(i), new(trie.Trie))
|
tr, _ := trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
|
exp := types.DeriveSha(newDummy(i), tr)
|
||||||
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
|
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
|
||||||
if !bytes.Equal(got[:], exp[:]) {
|
if !bytes.Equal(got[:], exp[:]) {
|
||||||
printList(newDummy(seed))
|
printList(newDummy(seed))
|
||||||
|
|
@ -118,7 +122,8 @@ func TestDerivableList(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, tc := range tcs[1:] {
|
for i, tc := range tcs[1:] {
|
||||||
exp := types.DeriveSha(flatList(tc), new(trie.Trie))
|
tr, _ := trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
|
exp := types.DeriveSha(flatList(tc), tr)
|
||||||
got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil))
|
got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil))
|
||||||
if !bytes.Equal(got[:], exp[:]) {
|
if !bytes.Equal(got[:], exp[:]) {
|
||||||
t.Fatalf("case %d: got %x exp %x", i, got, exp)
|
t.Fatalf("case %d: got %x exp %x", i, got, exp)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func (c *committer) commit(n node, db *Database) (node, int, error) {
|
||||||
if hash != nil && !dirty {
|
if hash != nil && !dirty {
|
||||||
return hash, 0, nil
|
return hash, 0, nil
|
||||||
}
|
}
|
||||||
// Commit children, then parent, and remove remove the dirty flag.
|
// Commit children, then parent, and remove the dirty flag.
|
||||||
switch cn := n.(type) {
|
switch cn := n.(type) {
|
||||||
case *shortNode:
|
case *shortNode:
|
||||||
// Commit child
|
// Commit child
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||||
|
|
@ -297,7 +298,7 @@ func TestUnionIterator(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIteratorNoDups(t *testing.T) {
|
func TestIteratorNoDups(t *testing.T) {
|
||||||
var tr Trie
|
tr, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
for _, val := range testdata1 {
|
for _, val := range testdata1 {
|
||||||
tr.Update([]byte(val.k), []byte(val.v))
|
tr.Update([]byte(val.k), []byte(val.v))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb/memorydb"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -552,7 +551,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
|
||||||
}
|
}
|
||||||
// Rebuild the trie with the leaf stream, the shape of trie
|
// Rebuild the trie with the leaf stream, the shape of trie
|
||||||
// should be same with the original one.
|
// should be same with the original one.
|
||||||
tr := &Trie{root: root, Db: NewDatabase(memorydb.New())}
|
tr := newWithRootNode(root)
|
||||||
if empty {
|
if empty {
|
||||||
tr.root = nil
|
tr.root = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb/memorydb"
|
"github.com/XinFinOrg/XDPoSChain/ethdb/memorydb"
|
||||||
)
|
)
|
||||||
|
|
@ -74,7 +75,7 @@ func TestProof(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOneElementProof(t *testing.T) {
|
func TestOneElementProof(t *testing.T) {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
updateString(trie, "k", "v")
|
updateString(trie, "k", "v")
|
||||||
for i, prover := range makeProvers(trie) {
|
for i, prover := range makeProvers(trie) {
|
||||||
proof := prover([]byte("k"))
|
proof := prover([]byte("k"))
|
||||||
|
|
@ -125,7 +126,7 @@ func TestBadProof(t *testing.T) {
|
||||||
// Tests that missing keys can also be proven. The test explicitly uses a single
|
// Tests that missing keys can also be proven. The test explicitly uses a single
|
||||||
// entry trie and checks for missing keys both before and after the single entry.
|
// entry trie and checks for missing keys both before and after the single entry.
|
||||||
func TestMissingKeyProof(t *testing.T) {
|
func TestMissingKeyProof(t *testing.T) {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
updateString(trie, "k", "v")
|
updateString(trie, "k", "v")
|
||||||
|
|
||||||
for i, key := range []string{"a", "j", "l", "z"} {
|
for i, key := range []string{"a", "j", "l", "z"} {
|
||||||
|
|
@ -453,7 +454,7 @@ func TestAllElementsProof(t *testing.T) {
|
||||||
// TestSingleSideRangeProof tests the range starts from zero.
|
// TestSingleSideRangeProof tests the range starts from zero.
|
||||||
func TestSingleSideRangeProof(t *testing.T) {
|
func TestSingleSideRangeProof(t *testing.T) {
|
||||||
for i := 0; i < 64; i++ {
|
for i := 0; i < 64; i++ {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
var entries entrySlice
|
var entries entrySlice
|
||||||
for i := 0; i < 4096; i++ {
|
for i := 0; i < 4096; i++ {
|
||||||
value := &kv{randBytes(32), randBytes(20), false}
|
value := &kv{randBytes(32), randBytes(20), false}
|
||||||
|
|
@ -488,7 +489,7 @@ func TestSingleSideRangeProof(t *testing.T) {
|
||||||
// TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff.
|
// TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff.
|
||||||
func TestReverseSingleSideRangeProof(t *testing.T) {
|
func TestReverseSingleSideRangeProof(t *testing.T) {
|
||||||
for i := 0; i < 64; i++ {
|
for i := 0; i < 64; i++ {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
var entries entrySlice
|
var entries entrySlice
|
||||||
for i := 0; i < 4096; i++ {
|
for i := 0; i < 4096; i++ {
|
||||||
value := &kv{randBytes(32), randBytes(20), false}
|
value := &kv{randBytes(32), randBytes(20), false}
|
||||||
|
|
@ -595,7 +596,7 @@ func TestBadRangeProof(t *testing.T) {
|
||||||
// TestGappedRangeProof focuses on the small trie with embedded nodes.
|
// TestGappedRangeProof focuses on the small trie with embedded nodes.
|
||||||
// If the gapped node is embedded in the trie, it should be detected too.
|
// If the gapped node is embedded in the trie, it should be detected too.
|
||||||
func TestGappedRangeProof(t *testing.T) {
|
func TestGappedRangeProof(t *testing.T) {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
var entries []*kv // Sorted entries
|
var entries []*kv // Sorted entries
|
||||||
for i := byte(0); i < 10; i++ {
|
for i := byte(0); i < 10; i++ {
|
||||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||||
|
|
@ -669,7 +670,7 @@ func TestSameSideProofs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasRightElement(t *testing.T) {
|
func TestHasRightElement(t *testing.T) {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
var entries entrySlice
|
var entries entrySlice
|
||||||
for i := 0; i < 4096; i++ {
|
for i := 0; i < 4096; i++ {
|
||||||
value := &kv{randBytes(32), randBytes(20), false}
|
value := &kv{randBytes(32), randBytes(20), false}
|
||||||
|
|
@ -1022,7 +1023,7 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomTrie(n int) (*Trie, map[string]*kv) {
|
func randomTrie(n int) (*Trie, map[string]*kv) {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
vals := make(map[string]*kv)
|
vals := make(map[string]*kv)
|
||||||
for i := byte(0); i < 100; i++ {
|
for i := byte(0); i < 100; i++ {
|
||||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
||||||
|
|
@ -1047,7 +1048,7 @@ func randBytes(n int) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func nonRandomTrie(n int) (*Trie, map[string]*kv) {
|
func nonRandomTrie(n int) (*Trie, map[string]*kv) {
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
vals := make(map[string]*kv)
|
vals := make(map[string]*kv)
|
||||||
max := uint64(0xffffffffffffffff)
|
max := uint64(0xffffffffffffffff)
|
||||||
for i := uint64(0); i < uint64(n); i++ {
|
for i := uint64(0); i < uint64(n); i++ {
|
||||||
|
|
@ -1072,7 +1073,7 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) {
|
||||||
common.Hex2Bytes("02"),
|
common.Hex2Bytes("02"),
|
||||||
common.Hex2Bytes("03"),
|
common.Hex2Bytes("03"),
|
||||||
}
|
}
|
||||||
trie := new(Trie)
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
trie.Update(key, vals[i])
|
trie.Update(key, vals[i])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,8 +185,10 @@ func (t *SecureTrie) Hash() common.Hash {
|
||||||
|
|
||||||
// Copy returns a copy of SecureTrie.
|
// Copy returns a copy of SecureTrie.
|
||||||
func (t *SecureTrie) Copy() *SecureTrie {
|
func (t *SecureTrie) Copy() *SecureTrie {
|
||||||
cpy := *t
|
return &SecureTrie{
|
||||||
return &cpy
|
trie: *t.trie.Copy(),
|
||||||
|
secKeyCache: t.secKeyCache,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
|
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,7 @@ func TestSecureTrieConcurrency(t *testing.T) {
|
||||||
threads := runtime.NumCPU()
|
threads := runtime.NumCPU()
|
||||||
tries := make([]*SecureTrie, threads)
|
tries := make([]*SecureTrie, threads)
|
||||||
for i := 0; i < threads; i++ {
|
for i := 0; i < threads; i++ {
|
||||||
cpy := *trie
|
tries[i] = trie.Copy()
|
||||||
tries[i] = &cpy
|
|
||||||
}
|
}
|
||||||
// Start a batch of goroutines interactng with the trie
|
// Start a batch of goroutines interactng with the trie
|
||||||
pend := new(sync.WaitGroup)
|
pend := new(sync.WaitGroup)
|
||||||
|
|
|
||||||
60
trie/trie.go
60
trie/trie.go
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/log"
|
"github.com/XinFinOrg/XDPoSChain/log"
|
||||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||||
|
|
@ -53,10 +54,15 @@ type LeafCallback func(paths [][]byte, hexpath []byte, leaf []byte, parent commo
|
||||||
type Trie struct {
|
type Trie struct {
|
||||||
Db *Database
|
Db *Database
|
||||||
root node
|
root node
|
||||||
// Keep track of the number leafs which have been inserted since the last
|
|
||||||
|
// Keep track of the number leaves which have been inserted since the last
|
||||||
// hashing operation. This number will not directly map to the number of
|
// hashing operation. This number will not directly map to the number of
|
||||||
// actually unhashed nodes
|
// actually unhashed nodes
|
||||||
unhashed int
|
unhashed int
|
||||||
|
|
||||||
|
// tracer is the state diff tracer can be used to track newly added/deleted
|
||||||
|
// trie node. It will be reset after each commit operation.
|
||||||
|
tracer *tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFlag returns the Cache flag value for a newly created Node.
|
// newFlag returns the Cache flag value for a newly created Node.
|
||||||
|
|
@ -64,7 +70,17 @@ func (t *Trie) newFlag() nodeFlag {
|
||||||
return nodeFlag{dirty: true}
|
return nodeFlag{dirty: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a trie with an existing root Node from Db.
|
// Copy returns a copy of Trie.
|
||||||
|
func (t *Trie) Copy() *Trie {
|
||||||
|
return &Trie{
|
||||||
|
Db: t.Db,
|
||||||
|
root: t.root,
|
||||||
|
unhashed: t.unhashed,
|
||||||
|
tracer: t.tracer.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a trie with an existing root node from db.
|
||||||
//
|
//
|
||||||
// If root is the zero hash or the sha3 hash of an empty string, the
|
// 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,
|
// trie is initially empty and does not require a database. Otherwise,
|
||||||
|
|
@ -76,6 +92,7 @@ func New(root common.Hash, db *Database) (*Trie, error) {
|
||||||
}
|
}
|
||||||
trie := &Trie{
|
trie := &Trie{
|
||||||
Db: db,
|
Db: db,
|
||||||
|
//tracer: newTracer(),
|
||||||
}
|
}
|
||||||
if root != (common.Hash{}) && root != types.EmptyRootHash {
|
if root != (common.Hash{}) && root != types.EmptyRootHash {
|
||||||
rootnode, err := trie.resolveHash(root[:], nil)
|
rootnode, err := trie.resolveHash(root[:], nil)
|
||||||
|
|
@ -87,6 +104,16 @@ func New(root common.Hash, db *Database) (*Trie, error) {
|
||||||
return trie, nil
|
return trie, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newWithRootNode initializes the trie with the given root node.
|
||||||
|
// It's only used by range prover.
|
||||||
|
func newWithRootNode(root node) *Trie {
|
||||||
|
return &Trie{
|
||||||
|
root: root,
|
||||||
|
//tracer: newTracer(),
|
||||||
|
Db: NewDatabase(rawdb.NewMemoryDatabase()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at
|
// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at
|
||||||
// the key after the given start key.
|
// the key after the given start key.
|
||||||
func (t *Trie) NodeIterator(start []byte) NodeIterator {
|
func (t *Trie) NodeIterator(start []byte) NodeIterator {
|
||||||
|
|
@ -472,7 +499,12 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||||
if matchlen == 0 {
|
if matchlen == 0 {
|
||||||
return true, branch, nil
|
return true, branch, nil
|
||||||
}
|
}
|
||||||
// Otherwise, replace it with a short Node leading up to the branch.
|
// New branch node is created as a child of the original short node.
|
||||||
|
// Track the newly inserted node in the tracer. The node identifier
|
||||||
|
// passed is the path from the root node.
|
||||||
|
t.tracer.onInsert(append(prefix, key[:matchlen]...))
|
||||||
|
|
||||||
|
// Replace it with a short node leading up to the branch.
|
||||||
return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil
|
return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil
|
||||||
|
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
|
|
@ -486,6 +518,11 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||||
return true, n, nil
|
return true, n, nil
|
||||||
|
|
||||||
case nil:
|
case nil:
|
||||||
|
// New short node is created and track it in the tracer. The node identifier
|
||||||
|
// passed is the path from the root node. Note the valueNode won't be tracked
|
||||||
|
// since it's always embedded in its parent.
|
||||||
|
t.tracer.onInsert(prefix)
|
||||||
|
|
||||||
return true, &shortNode{key, value, t.newFlag()}, nil
|
return true, &shortNode{key, value, t.newFlag()}, nil
|
||||||
|
|
||||||
case hashNode:
|
case hashNode:
|
||||||
|
|
@ -538,6 +575,11 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||||
return false, n, nil // don't replace n on mismatch
|
return false, n, nil // don't replace n on mismatch
|
||||||
}
|
}
|
||||||
if matchlen == len(key) {
|
if matchlen == len(key) {
|
||||||
|
// The matched short node is deleted entirely and track
|
||||||
|
// it in the deletion set. The same the valueNode doesn't
|
||||||
|
// need to be tracked at all since it's always embedded.
|
||||||
|
t.tracer.onDelete(prefix)
|
||||||
|
|
||||||
return true, nil, nil // remove n entirely for whole matches
|
return true, nil, nil // remove n entirely for whole matches
|
||||||
}
|
}
|
||||||
// The key is longer than n.Key. Remove the remaining suffix
|
// The key is longer than n.Key. Remove the remaining suffix
|
||||||
|
|
@ -550,6 +592,10 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||||
}
|
}
|
||||||
switch child := child.(type) {
|
switch child := child.(type) {
|
||||||
case *shortNode:
|
case *shortNode:
|
||||||
|
// The child shortNode is merged into its parent, track
|
||||||
|
// is deleted as well.
|
||||||
|
t.tracer.onDelete(append(prefix, n.Key...))
|
||||||
|
|
||||||
// Deleting from the subtrie reduced it to another
|
// Deleting from the subtrie reduced it to another
|
||||||
// short Node. Merge the nodes to avoid creating a
|
// short Node. Merge the nodes to avoid creating a
|
||||||
// ShortNode{..., ShortNode{...}}. Use concat (which
|
// ShortNode{..., ShortNode{...}}. Use concat (which
|
||||||
|
|
@ -611,6 +657,11 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
if cnode, ok := cnode.(*shortNode); ok {
|
if cnode, ok := cnode.(*shortNode); ok {
|
||||||
|
// Replace the entire full node with the short node.
|
||||||
|
// Mark the original short node as deleted since the
|
||||||
|
// value is embedded into the parent now.
|
||||||
|
t.tracer.onDelete(append(prefix, byte(pos)))
|
||||||
|
|
||||||
k := append([]byte{byte(pos)}, cnode.Key...)
|
k := append([]byte{byte(pos)}, cnode.Key...)
|
||||||
return true, &shortNode{k, cnode.Val, t.newFlag()}, nil
|
return true, &shortNode{k, cnode.Val, t.newFlag()}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -692,6 +743,8 @@ func (t *Trie) Commit(onleaf LeafCallback) (common.Hash, int, error) {
|
||||||
if t.Db == nil {
|
if t.Db == nil {
|
||||||
panic("commit called on trie with nil database")
|
panic("commit called on trie with nil database")
|
||||||
}
|
}
|
||||||
|
defer t.tracer.reset()
|
||||||
|
|
||||||
if t.root == nil {
|
if t.root == nil {
|
||||||
return types.EmptyRootHash, 0, nil
|
return types.EmptyRootHash, 0, nil
|
||||||
}
|
}
|
||||||
|
|
@ -750,4 +803,5 @@ func (t *Trie) hashRoot() (node, node, error) {
|
||||||
func (t *Trie) Reset() {
|
func (t *Trie) Reset() {
|
||||||
t.root = nil
|
t.root = nil
|
||||||
t.unhashed = 0
|
t.unhashed = 0
|
||||||
|
t.tracer.reset()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"testing/quick"
|
"testing/quick"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||||
|
|
@ -52,7 +53,7 @@ func newEmpty() *Trie {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyTrie(t *testing.T) {
|
func TestEmptyTrie(t *testing.T) {
|
||||||
var trie Trie
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
res := trie.Hash()
|
res := trie.Hash()
|
||||||
exp := types.EmptyRootHash
|
exp := types.EmptyRootHash
|
||||||
if res != exp {
|
if res != exp {
|
||||||
|
|
@ -61,7 +62,7 @@ func TestEmptyTrie(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNull(t *testing.T) {
|
func TestNull(t *testing.T) {
|
||||||
var trie Trie
|
trie, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
|
||||||
key := make([]byte, 32)
|
key := make([]byte, 32)
|
||||||
value := []byte("test")
|
value := []byte("test")
|
||||||
trie.Update(key, value)
|
trie.Update(key, value)
|
||||||
|
|
@ -317,6 +318,7 @@ const (
|
||||||
opHash
|
opHash
|
||||||
opReset
|
opReset
|
||||||
opItercheckhash
|
opItercheckhash
|
||||||
|
opNodeDiff
|
||||||
opMax // boundary value, not an actual op
|
opMax // boundary value, not an actual op
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -368,10 +370,13 @@ func runRandTestBool(rt randTest) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRandTest(rt randTest) error {
|
func runRandTest(rt randTest) error {
|
||||||
triedb := NewDatabase(memorydb.New())
|
var (
|
||||||
|
triedb = NewDatabase(memorydb.New())
|
||||||
tr, _ := New(common.Hash{}, triedb)
|
tr, _ = New(common.Hash{}, triedb)
|
||||||
values := make(map[string]string) // tracks content of the trie
|
values = make(map[string]string) // tracks content of the trie
|
||||||
|
origTrie, _ = New(common.Hash{}, triedb)
|
||||||
|
)
|
||||||
|
tr.tracer = newTracer()
|
||||||
|
|
||||||
for i, step := range rt {
|
for i, step := range rt {
|
||||||
fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n",
|
fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n",
|
||||||
|
|
@ -391,6 +396,7 @@ func runRandTest(rt randTest) error {
|
||||||
}
|
}
|
||||||
case opCommit:
|
case opCommit:
|
||||||
_, _, rt[i].err = tr.Commit(nil)
|
_, _, rt[i].err = tr.Commit(nil)
|
||||||
|
origTrie = tr.Copy()
|
||||||
case opHash:
|
case opHash:
|
||||||
tr.Hash()
|
tr.Hash()
|
||||||
case opReset:
|
case opReset:
|
||||||
|
|
@ -405,6 +411,9 @@ func runRandTest(rt randTest) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tr = newtr
|
tr = newtr
|
||||||
|
tr.tracer = newTracer()
|
||||||
|
|
||||||
|
origTrie = tr.Copy()
|
||||||
case opItercheckhash:
|
case opItercheckhash:
|
||||||
checktr, _ := New(common.Hash{}, triedb)
|
checktr, _ := New(common.Hash{}, triedb)
|
||||||
it := NewIterator(tr.NodeIterator(nil))
|
it := NewIterator(tr.NodeIterator(nil))
|
||||||
|
|
@ -414,6 +423,59 @@ func runRandTest(rt randTest) error {
|
||||||
if tr.Hash() != checktr.Hash() {
|
if tr.Hash() != checktr.Hash() {
|
||||||
rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash")
|
rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash")
|
||||||
}
|
}
|
||||||
|
case opNodeDiff:
|
||||||
|
var (
|
||||||
|
inserted = tr.tracer.insertList()
|
||||||
|
deleted = tr.tracer.deleteList()
|
||||||
|
origIter = origTrie.NodeIterator(nil)
|
||||||
|
curIter = tr.NodeIterator(nil)
|
||||||
|
origSeen = make(map[string]struct{})
|
||||||
|
curSeen = make(map[string]struct{})
|
||||||
|
)
|
||||||
|
for origIter.Next(true) {
|
||||||
|
if origIter.Leaf() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
origSeen[string(origIter.Path())] = struct{}{}
|
||||||
|
}
|
||||||
|
for curIter.Next(true) {
|
||||||
|
if curIter.Leaf() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
curSeen[string(curIter.Path())] = struct{}{}
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
insertExp = make(map[string]struct{})
|
||||||
|
deleteExp = make(map[string]struct{})
|
||||||
|
)
|
||||||
|
for path := range curSeen {
|
||||||
|
_, present := origSeen[path]
|
||||||
|
if !present {
|
||||||
|
insertExp[path] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for path := range origSeen {
|
||||||
|
_, present := curSeen[path]
|
||||||
|
if !present {
|
||||||
|
deleteExp[path] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(insertExp) != len(inserted) {
|
||||||
|
rt[i].err = fmt.Errorf("insert set mismatch")
|
||||||
|
}
|
||||||
|
if len(deleteExp) != len(deleted) {
|
||||||
|
rt[i].err = fmt.Errorf("delete set mismatch")
|
||||||
|
}
|
||||||
|
for _, insert := range inserted {
|
||||||
|
if _, present := insertExp[string(insert)]; !present {
|
||||||
|
rt[i].err = fmt.Errorf("missing inserted node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, del := range deleted {
|
||||||
|
if _, present := deleteExp[string(del)]; !present {
|
||||||
|
rt[i].err = fmt.Errorf("missing deleted node")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Abort the test on error.
|
// Abort the test on error.
|
||||||
if rt[i].err != nil {
|
if rt[i].err != nil {
|
||||||
|
|
|
||||||
122
trie/util_test.go
Normal file
122
trie/util_test.go
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests if the trie diffs are tracked correctly.
|
||||||
|
func TestTrieTracer(t *testing.T) {
|
||||||
|
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||||
|
trie, _ := New(common.Hash{}, db)
|
||||||
|
trie.tracer = newTracer()
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
trie.Hash()
|
||||||
|
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
it := trie.NodeIterator(nil)
|
||||||
|
for it.Next(true) {
|
||||||
|
if it.Leaf() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[string(it.Path())] = struct{}{}
|
||||||
|
}
|
||||||
|
inserted := trie.tracer.insertList()
|
||||||
|
if len(inserted) != len(seen) {
|
||||||
|
t.Fatalf("Unexpected inserted node tracked want %d got %d", len(seen), len(inserted))
|
||||||
|
}
|
||||||
|
for _, k := range inserted {
|
||||||
|
_, ok := seen[string(k)]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unexpected inserted node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleted := trie.tracer.deleteList()
|
||||||
|
if len(deleted) != 0 {
|
||||||
|
t.Fatalf("Unexpected deleted node tracked %d", len(deleted))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the changes
|
||||||
|
trie.Commit(nil)
|
||||||
|
|
||||||
|
// Delete all the elements, check deletion set
|
||||||
|
for _, val := range vals {
|
||||||
|
trie.Delete([]byte(val.k))
|
||||||
|
}
|
||||||
|
trie.Hash()
|
||||||
|
|
||||||
|
inserted = trie.tracer.insertList()
|
||||||
|
if len(inserted) != 0 {
|
||||||
|
t.Fatalf("Unexpected inserted node tracked %d", len(inserted))
|
||||||
|
}
|
||||||
|
deleted = trie.tracer.deleteList()
|
||||||
|
if len(deleted) != len(seen) {
|
||||||
|
t.Fatalf("Unexpected deleted node tracked want %d got %d", len(seen), len(deleted))
|
||||||
|
}
|
||||||
|
for _, k := range deleted {
|
||||||
|
_, ok := seen[string(k)]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unexpected inserted node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrieTracerNoop(t *testing.T) {
|
||||||
|
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||||
|
trie, _ := New(common.Hash{}, db)
|
||||||
|
trie.tracer = newTracer()
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
for _, val := range vals {
|
||||||
|
trie.Delete([]byte(val.k))
|
||||||
|
}
|
||||||
|
if len(trie.tracer.insertList()) != 0 {
|
||||||
|
t.Fatalf("Unexpected inserted node tracked %d", len(trie.tracer.insertList()))
|
||||||
|
}
|
||||||
|
if len(trie.tracer.deleteList()) != 0 {
|
||||||
|
t.Fatalf("Unexpected deleted node tracked %d", len(trie.tracer.deleteList()))
|
||||||
|
}
|
||||||
|
}
|
||||||
133
trie/utils.go
Normal file
133
trie/utils.go
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// tracer tracks the changes of trie nodes. During the trie operations,
|
||||||
|
// some nodes can be deleted from the trie, while these deleted nodes
|
||||||
|
// won't be captured by trie.Hasher or trie.Committer. Thus, these deleted
|
||||||
|
// nodes won't be removed from the disk at all. Tracer is an auxiliary tool
|
||||||
|
// used to track all insert and delete operations of trie and capture all
|
||||||
|
// deleted nodes eventually.
|
||||||
|
//
|
||||||
|
// The changed nodes can be mainly divided into two categories: the leaf
|
||||||
|
// node and intermediate node. The former is inserted/deleted by callers
|
||||||
|
// while the latter is inserted/deleted in order to follow the rule of trie.
|
||||||
|
// This tool can track all of them no matter the node is embedded in its
|
||||||
|
// parent or not, but valueNode is never tracked.
|
||||||
|
//
|
||||||
|
// Note tracer is not thread-safe, callers should be responsible for handling
|
||||||
|
// the concurrency issues by themselves.
|
||||||
|
type tracer struct {
|
||||||
|
insert map[string]struct{}
|
||||||
|
delete map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTracer initializes trie node diff tracer.
|
||||||
|
func newTracer() *tracer {
|
||||||
|
return &tracer{
|
||||||
|
insert: make(map[string]struct{}),
|
||||||
|
delete: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 it's untouched.
|
||||||
|
func (t *tracer) onInsert(key []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))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.insert[string(key)] = 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) {
|
||||||
|
// 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))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.delete[string(key)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertList returns the tracked inserted trie nodes in list format.
|
||||||
|
func (t *tracer) insertList() [][]byte {
|
||||||
|
// Tracer isn't used right now, remove this check later.
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ret [][]byte
|
||||||
|
for key := range t.insert {
|
||||||
|
ret = append(ret, []byte(key))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteList returns the tracked deleted trie nodes in list format.
|
||||||
|
func (t *tracer) deleteList() [][]byte {
|
||||||
|
// Tracer isn't used right now, remove this check later.
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var ret [][]byte
|
||||||
|
for key := range t.delete {
|
||||||
|
ret = append(ret, []byte(key))
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset clears the content tracked by tracer.
|
||||||
|
func (t *tracer) reset() {
|
||||||
|
// Tracer isn't used right now, remove this check later.
|
||||||
|
if t == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.insert = make(map[string]struct{})
|
||||||
|
t.delete = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns a deep copied tracer instance.
|
||||||
|
func (t *tracer) copy() *tracer {
|
||||||
|
// Tracer isn't used right now, remove this check later.
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
insert = make(map[string]struct{})
|
||||||
|
delete = make(map[string]struct{})
|
||||||
|
)
|
||||||
|
for key := range t.insert {
|
||||||
|
insert[key] = struct{}{}
|
||||||
|
}
|
||||||
|
for key := range t.delete {
|
||||||
|
delete[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return &tracer{
|
||||||
|
insert: insert,
|
||||||
|
delete: delete,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue