Roy Crihfield
b8fec4b571
Adds a method to perform full-state snapshots by diffing against an empty state trie. This replicates the functionality of `ipld-eth-state-snapshot`, so that code can use this as a library; see: cerc-io/ipld-eth-state-snapshot#1 Note that due to how incremental diffs are processed (updates are processed after the trie has been traversed) the iterator state doesn't fully capture the progress of the diff, so it's not currently feasible to state diffs this way. Full snapshots don't have to worry about updated accounts, so we can support them. Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com> Reviewed-on: #15
222 lines
6.0 KiB
Go
222 lines
6.0 KiB
Go
package utils_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/cerc-io/eth-testing/chaindata/mainnet"
|
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
"github.com/ethereum/go-ethereum/trie"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/cerc-io/plugeth-statediff/test_helpers"
|
|
"github.com/cerc-io/plugeth-statediff/utils"
|
|
)
|
|
|
|
type kvs struct{ k, v string }
|
|
|
|
var (
|
|
testdata1 = []kvs{
|
|
{"barb", "ba"},
|
|
{"bard", "bc"},
|
|
{"bars", "bb"},
|
|
{"bar", "b"},
|
|
{"fab", "z"},
|
|
{"food", "ab"},
|
|
{"foo", "a"},
|
|
}
|
|
|
|
testdata2 = []kvs{
|
|
{"aardvark", "c"},
|
|
{"bar", "b"},
|
|
{"barb", "bd"},
|
|
{"bars", "be"},
|
|
{"fab", "z"},
|
|
{"foo", "a"},
|
|
{"foos", "aa"},
|
|
{"jars", "d"},
|
|
}
|
|
)
|
|
|
|
func TestSymmetricDifferenceIterator(t *testing.T) {
|
|
t.Run("with no difference", func(t *testing.T) {
|
|
db := trie.NewDatabase(rawdb.NewMemoryDatabase())
|
|
triea := trie.NewEmpty(db)
|
|
di := utils.NewSymmetricDifferenceIterator(triea.NodeIterator(nil), triea.NodeIterator(nil))
|
|
for di.Next(true) {
|
|
t.Errorf("iterator should not yield any elements")
|
|
}
|
|
assert.Equal(t, 0, di.Count())
|
|
|
|
triea.MustUpdate([]byte("foo"), []byte("bar"))
|
|
di = utils.NewSymmetricDifferenceIterator(triea.NodeIterator(nil), triea.NodeIterator(nil))
|
|
for di.Next(true) {
|
|
t.Errorf("iterator should not yield any elements")
|
|
}
|
|
// two nodes visited: the leaf (value) and its parent
|
|
assert.Equal(t, 2, di.Count())
|
|
|
|
trieb := trie.NewEmpty(db)
|
|
di = utils.NewSymmetricDifferenceIterator(triea.NodeIterator([]byte("jars")), trieb.NodeIterator(nil))
|
|
for di.Next(true) {
|
|
t.Errorf("iterator should not yield any elements")
|
|
}
|
|
assert.Equal(t, 0, di.Count())
|
|
|
|
// TODO will fail until merged: https://github.com/ethereum/go-ethereum/pull/27838
|
|
// di, aux = utils.NewSymmetricDifferenceIterator(triea.NodeIterator([]byte("food")), trieb.NodeIterator(nil))
|
|
// for di.Next(true) {
|
|
// t.Errorf("iterator should not yield any elements")
|
|
// }
|
|
// assert.Equal(t, 0, di.Count())
|
|
})
|
|
|
|
t.Run("small difference", func(t *testing.T) {
|
|
dba := trie.NewDatabase(rawdb.NewMemoryDatabase())
|
|
triea := trie.NewEmpty(dba)
|
|
|
|
dbb := trie.NewDatabase(rawdb.NewMemoryDatabase())
|
|
trieb := trie.NewEmpty(dbb)
|
|
trieb.MustUpdate([]byte("foo"), []byte("bar"))
|
|
|
|
di := utils.NewSymmetricDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil))
|
|
leaves := 0
|
|
for di.Next(true) {
|
|
if di.Leaf() {
|
|
assert.False(t, di.CommonPath())
|
|
assert.Equal(t, "foo", string(di.LeafKey()))
|
|
assert.Equal(t, "bar", string(di.LeafBlob()))
|
|
leaves++
|
|
}
|
|
}
|
|
assert.Equal(t, 1, leaves)
|
|
assert.Equal(t, 2, di.Count())
|
|
|
|
trieb.MustUpdate([]byte("quux"), []byte("bars"))
|
|
di = utils.NewSymmetricDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator([]byte("quux")))
|
|
leaves = 0
|
|
for di.Next(true) {
|
|
if di.Leaf() {
|
|
assert.False(t, di.CommonPath())
|
|
assert.Equal(t, "quux", string(di.LeafKey()))
|
|
assert.Equal(t, "bars", string(di.LeafBlob()))
|
|
leaves++
|
|
}
|
|
}
|
|
assert.Equal(t, 1, leaves)
|
|
assert.Equal(t, 1, di.Count())
|
|
})
|
|
|
|
dba := trie.NewDatabase(rawdb.NewMemoryDatabase())
|
|
triea := trie.NewEmpty(dba)
|
|
for _, val := range testdata1 {
|
|
triea.MustUpdate([]byte(val.k), []byte(val.v))
|
|
}
|
|
dbb := trie.NewDatabase(rawdb.NewMemoryDatabase())
|
|
trieb := trie.NewEmpty(dbb)
|
|
for _, val := range testdata2 {
|
|
trieb.MustUpdate([]byte(val.k), []byte(val.v))
|
|
}
|
|
|
|
onlyA := make(map[string]string)
|
|
onlyB := make(map[string]string)
|
|
var deletions, creations []string
|
|
it := utils.NewSymmetricDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil))
|
|
for it.Next(true) {
|
|
if !it.Leaf() {
|
|
continue
|
|
}
|
|
key, value := string(it.LeafKey()), string(it.LeafBlob())
|
|
if it.FromA() {
|
|
onlyA[key] = value
|
|
if !it.CommonPath() {
|
|
deletions = append(deletions, key)
|
|
}
|
|
} else {
|
|
onlyB[key] = value
|
|
if !it.CommonPath() {
|
|
creations = append(creations, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
expectedOnlyA := map[string]string{
|
|
"barb": "ba",
|
|
"bard": "bc",
|
|
"bars": "bb",
|
|
"food": "ab",
|
|
}
|
|
expectedOnlyB := map[string]string{
|
|
"aardvark": "c",
|
|
"barb": "bd",
|
|
"bars": "be",
|
|
"foos": "aa",
|
|
"jars": "d",
|
|
}
|
|
expectedDeletions := []string{
|
|
"bard",
|
|
"food",
|
|
}
|
|
expectedCreations := []string{
|
|
"aardvark",
|
|
"foos",
|
|
"jars",
|
|
}
|
|
assert.Equal(t, expectedOnlyA, onlyA)
|
|
assert.Equal(t, expectedOnlyB, onlyB)
|
|
assert.Equal(t, expectedDeletions, deletions)
|
|
assert.Equal(t, expectedCreations, creations)
|
|
}
|
|
|
|
// compare the paths traversed by the geth difference iterator and symmetric difference iterator
|
|
// within a sample of mainnet data.
|
|
func TestCompareDifferenceIterators(t *testing.T) {
|
|
test_helpers.QuietLogs()
|
|
|
|
db := rawdb.NewMemoryDatabase()
|
|
core.DefaultGenesisBlock().MustCommit(db)
|
|
blocks := mainnet.GetBlocks()
|
|
chain, _ := core.NewBlockChain(db, nil, nil, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
|
_, err := chain.InsertChain(blocks[1:])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
treeA, err := chain.StateCache().OpenTrie(blocks[1].Root())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
treeB, err := chain.StateCache().OpenTrie(blocks[2].Root())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// collect the paths of nodes exclusive to A and B separately, then make sure the symmetric
|
|
// iterator produces the same sets
|
|
var pathsA, pathsB [][]byte
|
|
itBonly, _ := trie.NewDifferenceIterator(treeA.NodeIterator(nil), treeB.NodeIterator(nil))
|
|
for itBonly.Next(true) {
|
|
pathsB = append(pathsB, itBonly.Path())
|
|
}
|
|
itAonly, _ := trie.NewDifferenceIterator(treeB.NodeIterator(nil), treeA.NodeIterator(nil))
|
|
for itAonly.Next(true) {
|
|
pathsA = append(pathsA, itAonly.Path())
|
|
}
|
|
|
|
itSym := utils.NewSymmetricDifferenceIterator(treeA.NodeIterator(nil), treeB.NodeIterator(nil))
|
|
var idxA, idxB int
|
|
for itSym.Next(true) {
|
|
if itSym.FromA() {
|
|
require.Equal(t, pathsA[idxA], itSym.Path())
|
|
idxA++
|
|
} else {
|
|
require.Equal(t, pathsB[idxB], itSym.Path())
|
|
idxB++
|
|
}
|
|
}
|
|
require.Equal(t, len(pathsA), idxA)
|
|
require.Equal(t, len(pathsB), idxB)
|
|
}
|