go-ethereum/core/state/sync_test.go
Guillaume Ballet ac152143e1 Verkle tree-based state with overlay transition
Squash the main verkle PR ahead of rebase

don't call Bytes() in GetTreeKey (#137)

trie: avoid endianness conversion in GetTreeKey (#140)

* trie/utils: add concrete expected value in trie key generation test

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* mod: update to latest go-verkle

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* trie/utils: avoid endianness conversions

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* apply review changes & update to official go-verkle version

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

upgrade go-verkle to CoW version and get TestProcessVerkle to build (#138)

updating ci to use self-hosted machine (#143)

fix: storage offset in non-header group + reuse of value buffer (#145)

dedup call to ChunkifyCode, same as replay branch (#156)

* dedup call to ChunkifyCode, same as replay branch

* fix some linter issues

fix code offset in tree update (#157)

fix REVERT in state processor test execution (#158)

* fix code offset in tree update

* fix REVERT in test execution

save on key hashing: lump code size update with first code chunk group (#159)

fix code chunk key calculation and storage key calculation (#161)

* fix codeKey calculation

* Remove

* fix storageOffset

* fix the fix to the fix to the offset fix

* Remove copy/pasted, unused code in test

* fix linter

---------

Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>

fix: infinite loop when calling extcodecopy on empty code (#151)

upgrade to latest go-verkle

fix: only update code in the tree if it's dirty (#174)

fix: read-touch the code size and Keccak of the origin (#175)

List of changes for converting a sepolia database (#182)

* naive conversion rebased on top of beverly hills

* changes for the sepolia shadow fork conversion

* fixes to please the linter

* fixes to please the linter

Unified point cache (#180)

* Unified point cache

* Use cache for Try*Account

* alter Trie interface to use caching for slots (#181)

* alter Trie interface to use caching for slots

* fix: use a lock to protect the point cache (#185)

* use fastest non-master go-verkle version & pull trie/Verkle.go changes to use new api (#184)

* mod: update to fastest go-verkle version today

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* trie/verkle: use new batch serialization api

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix: TryDelete signature in unit tests

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

trie/utils: fix potential overflow (#191)

* trie/utils: fix potential overflow

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* trie/utils: receive storage key as a byte slice

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* revert formatter changes

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* trie/utils: fix mod 256

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

trie/utils: fix incorrect bigint assignment (#193)

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

upgrade precomp link to fix CI

fix: add missing code size&keccak leaves in empty accounts (#192)

fixes to use the latest go-verkle@master (#197)

* fixes to use the latest go-verkle@master

* linter fixes

* linter fixes for tests

* fix: use jsign's go-verkle fix

refactor: remove unused (*StateDB).GetXLittleEndian methods (#204)

fix gas accounting issue in state_processor_test.go (#207)

update go-verkle not to use StatelessNode anymore (#206)

* update go-verkle not to use StatelessNode anymore

* update go-verkle to latest

refactor: move verkle gas accounting to its own block in TransitionDB (#208)

fix a panic in deserializeVerkleProof if GetProofItems returns a nil ProofElements

use the cachingDB instead of a custom VerkleDB (#209)

* use the cachingDB instead of a custom VerkleDB

* fix stack trace in LES

remove holiman from CODEOWNERS as he gets too many emails

read from tree in state object if the snapshot is nil (#205)

add missing error checks for the root node type (#214)

implement OpenStorageTrie for verkle trees (#210)

* implement OpenStorageTrie for verkle trees

* add a few comments for future maintenance

* fix linter issue

fix: copy balance leaf to new buffer in TryGetAccount (#217)

implement some heretofore unimplemented iterator methods (#219)

params: move verkle params to their own file (#228)

fix: proper number of chunk evals (#215)

overlay transition (#244)

* overlay transition

Fix some bugs identified in the code review

Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

Include base -> overlay key-values migration logic (#199)

* mod: add go-verkle version with key-value migration new apis

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* core/stateprocessor: use constant for max number of migrated key-values

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* core: add base->overlay key-values migration logic

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* core: fix some compiler errors

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* trie: consider removing transition trie api in the future

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* mod: use latest go-verkle

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

fix some unit tests errors

get convresion block from file

fix compilation issues

fix initialization issue in migrator

fix: changes needed to run the first 28 blocks

important sutff: fix the banner

fix: use nonce instead of balance in nonce leaf (#202)

fixes for performing the overlay transition (#203)

* fixes for performing the overlay transition

* fixes for the full replay

* fix: deletion-and-recreation of EoA

* fixes to replay 2M+ blocks

* upgrade to go-verkle@master

* fix: proper number of chunk evals

* rewrite conversion loop to fix known issues

changes to make replay work with the overlay method (#216)

* fixes for performing the overlay transition

fixes for the full replay

fix: deletion-and-recreation of EoA

fixes to replay 2M+ blocks

upgrade to go-verkle@master

fix: proper number of chunk evals

rewrite conversion loop to fix known issues

changes to make replay work with the overlay method

fixes to replay 2M+ blocks

update to latest go-verkle@master

* use a PBSS-like scheme for internal nodes (#221)

* use a PBSS-like scheme for internal nodes

* a couple of fixes coming from debugging replay

* fix: use an error to notify the transition tree that a deleted account was found in the overlay tree (#222)

* fixes for pbss replay (#227)

* fixes for pbss replay

* trie/verkle: use capped batch size (#229)

* trie/verkle: use capped batch size

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* trie/verkle: avoid path variable allocation per db.Put

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* don't keep more than 32 state root conversions in RAM (#230)

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>

* cleanup some code

* mod: update go-verkle

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* re-enable snapshot (#231)

* re-enable cancun block / snapshot (#226)

* clear storage conversion key upon translating account (#234)

* clear storage conversion key upon translating account

* mod: use latest go-verkle

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix: self-deadlock with translated root map mutex (#236)

* return compressed commitment as root commitment (#237)

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

fix first panic in *TransitionTrie.Copy()

upgrade go-verkle to latest master

mod: update go-verkle (#239)

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

core: print state root every 100 blocks (#240)

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

fix: only Commit the account trie (#242)

fixes to get TestProcessVerkle to work with the overlay branch (#238)

* fixes to get TestProcessVerkle to work with the overlay branch

* fix all panics in verkle state processor test

* fix proof verification

move transition management to cachingDB

* fix: mark the verkle transition as started if it's ended without being started

* fix the verkle state processing test

* fix linter errors

* Add a function to clear verkle params for replay

* fix: handle TransitionTrie in OpenStorageTrie

* fix linter issue

* fix the deleted account error (#247)

* code cleanup (#248)

* fix: don't error on a missing conversion.txt (#249)

* Overlay Tree preimages exporting and usage (#246)

* export overlay preimages tool

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* use preimages flat file in overlay tree migration logic

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* cmd/geth: add --roothash to overlay tree preimage exporting command

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* cleanup

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* review feedback

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix: reduce the PR footprint (#250)

* fix: don't fail when preimages.bin is missing (#251)

* fix: don't fail when preimages.bin is missing

* fix: don't open the preimages file when outside of transition

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

review changes

remove replay-specific code
2024-05-08 13:24:03 +02:00

686 lines
22 KiB
Go

// Copyright 2015 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 state
import (
"bytes"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
// testAccount is the data associated with an account used by the state tests.
type testAccount struct {
address common.Address
balance *big.Int
nonce uint64
code []byte
}
// makeTestState create a sample test state to test node-wise reconstruction.
func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) {
// Create an empty state
db := rawdb.NewMemoryDatabase()
sdb := NewDatabaseWithConfig(db, &trie.Config{Preimages: true})
state, _ := New(types.EmptyRootHash, sdb, nil)
// Fill it with some arbitrary data
var accounts []*testAccount
for i := byte(0); i < 96; i++ {
obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
acc := &testAccount{address: common.BytesToAddress([]byte{i})}
obj.AddBalance(big.NewInt(int64(11 * i)))
acc.balance = big.NewInt(int64(11 * i))
obj.SetNonce(uint64(42 * i))
acc.nonce = uint64(42 * i)
if i%3 == 0 {
obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i})
acc.code = []byte{i, i, i, i, i}
}
if i%5 == 0 {
for j := byte(0); j < 5; j++ {
hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j})
obj.SetState(hash, hash)
}
}
state.updateStateObject(obj)
accounts = append(accounts, acc)
}
root, err := state.Commit(0, false)
if err != nil {
panic(err)
}
// Return the generated state
return db, sdb, root, accounts
}
// checkStateAccounts cross references a reconstructed state with an expected
// account array.
func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) {
// Check root availability and state contents
state, err := New(root, NewDatabase(db), nil)
if err != nil {
t.Fatalf("failed to create state trie at %x: %v", root, err)
}
if err := checkStateConsistency(db, root); err != nil {
t.Fatalf("inconsistent state trie at %x: %v", root, err)
}
for i, acc := range accounts {
if balance := state.GetBalance(acc.address); balance.Cmp(acc.balance) != 0 {
t.Errorf("account %d: balance mismatch: have %v, want %v", i, balance, acc.balance)
}
if nonce := state.GetNonce(acc.address); nonce != acc.nonce {
t.Errorf("account %d: nonce mismatch: have %v, want %v", i, nonce, acc.nonce)
}
if code := state.GetCode(acc.address); !bytes.Equal(code, acc.code) {
t.Errorf("account %d: code mismatch: have %x, want %x", i, code, acc.code)
}
}
}
// checkStateConsistency checks that all data of a state root is present.
func checkStateConsistency(db ethdb.Database, root common.Hash) error {
state, err := New(root, NewDatabaseWithConfig(db, &trie.Config{Preimages: true}), nil)
if err != nil {
return err
}
it := newNodeIterator(state)
for it.Next() {
}
return it.Error
}
// Tests that an empty state is not scheduled for syncing.
func TestEmptyStateSync(t *testing.T) {
db := trie.NewDatabase(rawdb.NewMemoryDatabase())
sync := NewStateSync(types.EmptyRootHash, rawdb.NewMemoryDatabase(), nil, db.Scheme())
if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 {
t.Errorf("content requested for empty state: %v, %v, %v", nodes, paths, codes)
}
}
// Tests that given a root hash, a state can sync iteratively on a single thread,
// requesting retrieval tasks and returning all of them in one go.
func TestIterativeStateSyncIndividual(t *testing.T) {
testIterativeStateSync(t, 1, false, false)
}
func TestIterativeStateSyncBatched(t *testing.T) {
testIterativeStateSync(t, 100, false, false)
}
func TestIterativeStateSyncIndividualFromDisk(t *testing.T) {
testIterativeStateSync(t, 1, true, false)
}
func TestIterativeStateSyncBatchedFromDisk(t *testing.T) {
testIterativeStateSync(t, 100, true, false)
}
func TestIterativeStateSyncIndividualByPath(t *testing.T) {
testIterativeStateSync(t, 1, false, true)
}
func TestIterativeStateSyncBatchedByPath(t *testing.T) {
testIterativeStateSync(t, 100, false, true)
}
// stateElement represents the element in the state trie(bytecode or trie node).
type stateElement struct {
path string
hash common.Hash
code common.Hash
syncPath trie.SyncPath
}
func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {
// Create a random state to copy
srcDisk, srcDb, srcRoot, srcAccounts := makeTestState()
if commit {
srcDb.TrieDB().Commit(srcRoot, false)
}
srcTrie, _ := trie.New(trie.StateTrieID(srcRoot), srcDb.TrieDB())
// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
var (
nodeElements []stateElement
codeElements []stateElement
)
paths, nodes, codes := sched.Missing(count)
for i := 0; i < len(paths); i++ {
nodeElements = append(nodeElements, stateElement{
path: paths[i],
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(paths[i])),
})
}
for i := 0; i < len(codes); i++ {
codeElements = append(codeElements, stateElement{
code: codes[i],
})
}
for len(nodeElements)+len(codeElements) > 0 {
var (
nodeResults = make([]trie.NodeSyncResult, len(nodeElements))
codeResults = make([]trie.CodeSyncResult, len(codeElements))
)
for i, element := range codeElements {
data, err := srcDb.ContractCode(common.Address{}, element.code)
if err != nil {
t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
}
for i, node := range nodeElements {
if bypath {
if len(node.syncPath) == 1 {
data, _, err := srcTrie.GetNode(node.syncPath[0])
if err != nil {
t.Fatalf("failed to retrieve node data for path %x: %v", node.syncPath[0], err)
}
nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data}
} else {
var acc types.StateAccount
if err := rlp.DecodeBytes(srcTrie.MustGet(node.syncPath[0]), &acc); err != nil {
t.Fatalf("failed to decode account on path %x: %v", node.syncPath[0], err)
}
id := trie.StorageTrieID(srcRoot, common.BytesToHash(node.syncPath[0]), acc.Root)
stTrie, err := trie.New(id, srcDb.TrieDB())
if err != nil {
t.Fatalf("failed to retriev storage trie for path %x: %v", node.syncPath[1], err)
}
data, _, err := stTrie.GetNode(node.syncPath[1])
if err != nil {
t.Fatalf("failed to retrieve node data for path %x: %v", node.syncPath[1], err)
}
nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data}
}
} else {
data, err := srcDb.TrieDB().Node(node.hash)
if err != nil {
t.Fatalf("failed to retrieve node data for key %v", []byte(node.path))
}
nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data}
}
}
for _, result := range codeResults {
if err := sched.ProcessCode(result); err != nil {
t.Errorf("failed to process result %v", err)
}
}
for _, result := range nodeResults {
if err := sched.ProcessNode(result); err != nil {
t.Errorf("failed to process result %v", err)
}
}
batch := dstDb.NewBatch()
if err := sched.Commit(batch); err != nil {
t.Fatalf("failed to commit data: %v", err)
}
batch.Write()
paths, nodes, codes = sched.Missing(count)
nodeElements = nodeElements[:0]
for i := 0; i < len(paths); i++ {
nodeElements = append(nodeElements, stateElement{
path: paths[i],
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(paths[i])),
})
}
codeElements = codeElements[:0]
for i := 0; i < len(codes); i++ {
codeElements = append(codeElements, stateElement{
code: codes[i],
})
}
}
// Copy the preimages from source db in order to traverse the state.
srcDb.TrieDB().WritePreimages()
copyPreimages(srcDisk, dstDb)
// Cross check that the two states are in sync
checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
}
// Tests that the trie scheduler can correctly reconstruct the state even if only
// partial results are returned, and the others sent only later.
func TestIterativeDelayedStateSync(t *testing.T) {
// Create a random state to copy
srcDisk, srcDb, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
var (
nodeElements []stateElement
codeElements []stateElement
)
paths, nodes, codes := sched.Missing(0)
for i := 0; i < len(paths); i++ {
nodeElements = append(nodeElements, stateElement{
path: paths[i],
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(paths[i])),
})
}
for i := 0; i < len(codes); i++ {
codeElements = append(codeElements, stateElement{
code: codes[i],
})
}
for len(nodeElements)+len(codeElements) > 0 {
// Sync only half of the scheduled nodes
var nodeProcessed int
var codeProcessed int
if len(codeElements) > 0 {
codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
for i, element := range codeElements[:len(codeResults)] {
data, err := srcDb.ContractCode(common.Address{}, element.code)
if err != nil {
t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
}
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
}
for _, result := range codeResults {
if err := sched.ProcessCode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
codeProcessed = len(codeResults)
}
if len(nodeElements) > 0 {
nodeResults := make([]trie.NodeSyncResult, len(nodeElements)/2+1)
for i, element := range nodeElements[:len(nodeResults)] {
data, err := srcDb.TrieDB().Node(element.hash)
if err != nil {
t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
}
nodeResults[i] = trie.NodeSyncResult{Path: element.path, Data: data}
}
for _, result := range nodeResults {
if err := sched.ProcessNode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
nodeProcessed = len(nodeResults)
}
batch := dstDb.NewBatch()
if err := sched.Commit(batch); err != nil {
t.Fatalf("failed to commit data: %v", err)
}
batch.Write()
paths, nodes, codes = sched.Missing(0)
nodeElements = nodeElements[nodeProcessed:]
for i := 0; i < len(paths); i++ {
nodeElements = append(nodeElements, stateElement{
path: paths[i],
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(paths[i])),
})
}
codeElements = codeElements[codeProcessed:]
for i := 0; i < len(codes); i++ {
codeElements = append(codeElements, stateElement{
code: codes[i],
})
}
}
// Copy the preimages from source db in order to traverse the state.
srcDb.TrieDB().WritePreimages()
copyPreimages(srcDisk, dstDb)
// Cross check that the two states are in sync
checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
}
// Tests that given a root hash, a trie can sync iteratively on a single thread,
// requesting retrieval tasks and returning all of them in one go, however in a
// random order.
func TestIterativeRandomStateSyncIndividual(t *testing.T) { testIterativeRandomStateSync(t, 1) }
func TestIterativeRandomStateSyncBatched(t *testing.T) { testIterativeRandomStateSync(t, 100) }
func testIterativeRandomStateSync(t *testing.T, count int) {
// Create a random state to copy
srcDisk, srcDb, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
nodeQueue := make(map[string]stateElement)
codeQueue := make(map[common.Hash]struct{})
paths, nodes, codes := sched.Missing(count)
for i, path := range paths {
nodeQueue[path] = stateElement{
path: path,
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(path)),
}
}
for _, hash := range codes {
codeQueue[hash] = struct{}{}
}
for len(nodeQueue)+len(codeQueue) > 0 {
// Fetch all the queued nodes in a random order
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
data, err := srcDb.ContractCode(common.Address{}, hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
}
for _, result := range results {
if err := sched.ProcessCode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
}
if len(nodeQueue) > 0 {
results := make([]trie.NodeSyncResult, 0, len(nodeQueue))
for path, element := range nodeQueue {
data, err := srcDb.TrieDB().Node(element.hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x %v %v", element.hash, []byte(element.path), element.path)
}
results = append(results, trie.NodeSyncResult{Path: path, Data: data})
}
for _, result := range results {
if err := sched.ProcessNode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
}
// Feed the retrieved results back and queue new tasks
batch := dstDb.NewBatch()
if err := sched.Commit(batch); err != nil {
t.Fatalf("failed to commit data: %v", err)
}
batch.Write()
nodeQueue = make(map[string]stateElement)
codeQueue = make(map[common.Hash]struct{})
paths, nodes, codes := sched.Missing(count)
for i, path := range paths {
nodeQueue[path] = stateElement{
path: path,
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(path)),
}
}
for _, hash := range codes {
codeQueue[hash] = struct{}{}
}
}
// Copy the preimages from source db in order to traverse the state.
srcDb.TrieDB().WritePreimages()
copyPreimages(srcDisk, dstDb)
// Cross check that the two states are in sync
checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
}
// Tests that the trie scheduler can correctly reconstruct the state even if only
// partial results are returned (Even those randomly), others sent only later.
func TestIterativeRandomDelayedStateSync(t *testing.T) {
// Create a random state to copy
srcDisk, srcDb, srcRoot, srcAccounts := makeTestState()
// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
nodeQueue := make(map[string]stateElement)
codeQueue := make(map[common.Hash]struct{})
paths, nodes, codes := sched.Missing(0)
for i, path := range paths {
nodeQueue[path] = stateElement{
path: path,
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(path)),
}
}
for _, hash := range codes {
codeQueue[hash] = struct{}{}
}
for len(nodeQueue)+len(codeQueue) > 0 {
// Sync only half of the scheduled nodes, even those in random order
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue)/2+1)
for hash := range codeQueue {
delete(codeQueue, hash)
data, err := srcDb.ContractCode(common.Address{}, hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
if len(results) >= cap(results) {
break
}
}
for _, result := range results {
if err := sched.ProcessCode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
}
if len(nodeQueue) > 0 {
results := make([]trie.NodeSyncResult, 0, len(nodeQueue)/2+1)
for path, element := range nodeQueue {
delete(nodeQueue, path)
data, err := srcDb.TrieDB().Node(element.hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", element.hash)
}
results = append(results, trie.NodeSyncResult{Path: path, Data: data})
if len(results) >= cap(results) {
break
}
}
// Feed the retrieved results back and queue new tasks
for _, result := range results {
if err := sched.ProcessNode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
}
batch := dstDb.NewBatch()
if err := sched.Commit(batch); err != nil {
t.Fatalf("failed to commit data: %v", err)
}
batch.Write()
paths, nodes, codes := sched.Missing(0)
for i, path := range paths {
nodeQueue[path] = stateElement{
path: path,
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(path)),
}
}
for _, hash := range codes {
codeQueue[hash] = struct{}{}
}
}
// Copy the preimages from source db in order to traverse the state.
srcDb.TrieDB().WritePreimages()
copyPreimages(srcDisk, dstDb)
// Cross check that the two states are in sync
checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
}
// Tests that at any point in time during a sync, only complete sub-tries are in
// the database.
func TestIncompleteStateSync(t *testing.T) {
// Create a random state to copy
db, srcDb, srcRoot, srcAccounts := makeTestState()
// isCodeLookup to save some hashing
var isCode = make(map[common.Hash]struct{})
for _, acc := range srcAccounts {
if len(acc.code) > 0 {
isCode[crypto.Keccak256Hash(acc.code)] = struct{}{}
}
}
isCode[types.EmptyCodeHash] = struct{}{}
// Create a destination state and sync with the scheduler
dstDb := rawdb.NewMemoryDatabase()
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
var (
addedCodes []common.Hash
addedPaths []string
addedHashes []common.Hash
)
reader, err := srcDb.TrieDB().Reader(srcRoot)
if err != nil {
t.Fatalf("state is not available %x", srcRoot)
}
nodeQueue := make(map[string]stateElement)
codeQueue := make(map[common.Hash]struct{})
paths, nodes, codes := sched.Missing(1)
for i, path := range paths {
nodeQueue[path] = stateElement{
path: path,
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(path)),
}
}
for _, hash := range codes {
codeQueue[hash] = struct{}{}
}
for len(nodeQueue)+len(codeQueue) > 0 {
// Fetch a batch of state nodes
if len(codeQueue) > 0 {
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
for hash := range codeQueue {
data, err := srcDb.ContractCode(common.Address{}, hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", hash)
}
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
addedCodes = append(addedCodes, hash)
}
// Process each of the state nodes
for _, result := range results {
if err := sched.ProcessCode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
}
if len(nodeQueue) > 0 {
results := make([]trie.NodeSyncResult, 0, len(nodeQueue))
for path, element := range nodeQueue {
owner, inner := trie.ResolvePath([]byte(element.path))
data, err := reader.Node(owner, inner, element.hash)
if err != nil {
t.Fatalf("failed to retrieve node data for %x", element.hash)
}
results = append(results, trie.NodeSyncResult{Path: path, Data: data})
if element.hash != srcRoot {
addedPaths = append(addedPaths, element.path)
addedHashes = append(addedHashes, element.hash)
}
}
// Process each of the state nodes
for _, result := range results {
if err := sched.ProcessNode(result); err != nil {
t.Fatalf("failed to process result %v", err)
}
}
}
batch := dstDb.NewBatch()
if err := sched.Commit(batch); err != nil {
t.Fatalf("failed to commit data: %v", err)
}
batch.Write()
// Fetch the next batch to retrieve
nodeQueue = make(map[string]stateElement)
codeQueue = make(map[common.Hash]struct{})
paths, nodes, codes := sched.Missing(1)
for i, path := range paths {
nodeQueue[path] = stateElement{
path: path,
hash: nodes[i],
syncPath: trie.NewSyncPath([]byte(path)),
}
}
for _, hash := range codes {
codeQueue[hash] = struct{}{}
}
}
// Copy the preimages from source db in order to traverse the state.
srcDb.TrieDB().WritePreimages()
copyPreimages(db, dstDb)
// Sanity check that removing any node from the database is detected
for _, node := range addedCodes {
val := rawdb.ReadCode(dstDb, node)
rawdb.DeleteCode(dstDb, node)
if err := checkStateConsistency(dstDb, srcRoot); err == nil {
t.Errorf("trie inconsistency not caught, missing: %x", node)
}
rawdb.WriteCode(dstDb, node, val)
}
scheme := srcDb.TrieDB().Scheme()
for i, path := range addedPaths {
owner, inner := trie.ResolvePath([]byte(path))
hash := addedHashes[i]
val := rawdb.ReadTrieNode(dstDb, owner, inner, hash, scheme)
if val == nil {
t.Error("missing trie node")
}
rawdb.DeleteTrieNode(dstDb, owner, inner, hash, scheme)
if err := checkStateConsistency(dstDb, srcRoot); err == nil {
t.Errorf("trie inconsistency not caught, missing: %v", path)
}
rawdb.WriteTrieNode(dstDb, owner, inner, hash, val, scheme)
}
}
func copyPreimages(srcDb, dstDb ethdb.Database) {
it := srcDb.NewIterator(rawdb.PreimagePrefix, nil)
defer it.Release()
preimages := make(map[common.Hash][]byte)
for it.Next() {
hash := it.Key()[len(rawdb.PreimagePrefix):]
preimages[common.BytesToHash(hash)] = common.CopyBytes(it.Value())
}
rawdb.WritePreimages(dstDb, preimages)
}