This enables the following linters - typecheck - unused - staticcheck - bidichk - durationcheck - exportloopref - gosec WIth a few exceptions. - We use a deprecated protobuf in trezor. I didn't want to mess with that, since I cannot meaningfully test any changes there. - The deprecated TypeMux is used in a few places still, so the warning for it is silenced for now. - Using string type in context.WithValue is apparently wrong, one should use a custom type, to prevent collisions between different places in the hierarchy of callers. That should be fixed at some point, but may require some attention. - The warnings for using weak random generator are squashed, since we use a lot of random without need for cryptographic guarantees.
		
			
				
	
	
		
			1718 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1718 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2021 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 snap
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"crypto/rand"
 | |
| 	"encoding/binary"
 | |
| 	"fmt"
 | |
| 	"math/big"
 | |
| 	"sort"
 | |
| 	"sync"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"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/light"
 | |
| 	"github.com/ethereum/go-ethereum/log"
 | |
| 	"github.com/ethereum/go-ethereum/rlp"
 | |
| 	"github.com/ethereum/go-ethereum/trie"
 | |
| 	"golang.org/x/crypto/sha3"
 | |
| )
 | |
| 
 | |
| func TestHashing(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var bytecodes = make([][]byte, 10)
 | |
| 	for i := 0; i < len(bytecodes); i++ {
 | |
| 		buf := make([]byte, 100)
 | |
| 		rand.Read(buf)
 | |
| 		bytecodes[i] = buf
 | |
| 	}
 | |
| 	var want, got string
 | |
| 	var old = func() {
 | |
| 		hasher := sha3.NewLegacyKeccak256()
 | |
| 		for i := 0; i < len(bytecodes); i++ {
 | |
| 			hasher.Reset()
 | |
| 			hasher.Write(bytecodes[i])
 | |
| 			hash := hasher.Sum(nil)
 | |
| 			got = fmt.Sprintf("%v\n%v", got, hash)
 | |
| 		}
 | |
| 	}
 | |
| 	var new = func() {
 | |
| 		hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
 | |
| 		var hash = make([]byte, 32)
 | |
| 		for i := 0; i < len(bytecodes); i++ {
 | |
| 			hasher.Reset()
 | |
| 			hasher.Write(bytecodes[i])
 | |
| 			hasher.Read(hash)
 | |
| 			want = fmt.Sprintf("%v\n%v", want, hash)
 | |
| 		}
 | |
| 	}
 | |
| 	old()
 | |
| 	new()
 | |
| 	if want != got {
 | |
| 		t.Errorf("want\n%v\ngot\n%v\n", want, got)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func BenchmarkHashing(b *testing.B) {
 | |
| 	var bytecodes = make([][]byte, 10000)
 | |
| 	for i := 0; i < len(bytecodes); i++ {
 | |
| 		buf := make([]byte, 100)
 | |
| 		rand.Read(buf)
 | |
| 		bytecodes[i] = buf
 | |
| 	}
 | |
| 	var old = func() {
 | |
| 		hasher := sha3.NewLegacyKeccak256()
 | |
| 		for i := 0; i < len(bytecodes); i++ {
 | |
| 			hasher.Reset()
 | |
| 			hasher.Write(bytecodes[i])
 | |
| 			hasher.Sum(nil)
 | |
| 		}
 | |
| 	}
 | |
| 	var new = func() {
 | |
| 		hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
 | |
| 		var hash = make([]byte, 32)
 | |
| 		for i := 0; i < len(bytecodes); i++ {
 | |
| 			hasher.Reset()
 | |
| 			hasher.Write(bytecodes[i])
 | |
| 			hasher.Read(hash)
 | |
| 		}
 | |
| 	}
 | |
| 	b.Run("old", func(b *testing.B) {
 | |
| 		b.ReportAllocs()
 | |
| 		for i := 0; i < b.N; i++ {
 | |
| 			old()
 | |
| 		}
 | |
| 	})
 | |
| 	b.Run("new", func(b *testing.B) {
 | |
| 		b.ReportAllocs()
 | |
| 		for i := 0; i < b.N; i++ {
 | |
| 			new()
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| type (
 | |
| 	accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error
 | |
| 	storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error
 | |
| 	trieHandlerFunc    func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error
 | |
| 	codeHandlerFunc    func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error
 | |
| )
 | |
| 
 | |
| type testPeer struct {
 | |
| 	id            string
 | |
| 	test          *testing.T
 | |
| 	remote        *Syncer
 | |
| 	logger        log.Logger
 | |
| 	accountTrie   *trie.Trie
 | |
| 	accountValues entrySlice
 | |
| 	storageTries  map[common.Hash]*trie.Trie
 | |
| 	storageValues map[common.Hash]entrySlice
 | |
| 
 | |
| 	accountRequestHandler accountHandlerFunc
 | |
| 	storageRequestHandler storageHandlerFunc
 | |
| 	trieRequestHandler    trieHandlerFunc
 | |
| 	codeRequestHandler    codeHandlerFunc
 | |
| 	term                  func()
 | |
| 
 | |
| 	// counters
 | |
| 	nAccountRequests  int
 | |
| 	nStorageRequests  int
 | |
| 	nBytecodeRequests int
 | |
| 	nTrienodeRequests int
 | |
| }
 | |
| 
 | |
| func newTestPeer(id string, t *testing.T, term func()) *testPeer {
 | |
| 	peer := &testPeer{
 | |
| 		id:                    id,
 | |
| 		test:                  t,
 | |
| 		logger:                log.New("id", id),
 | |
| 		accountRequestHandler: defaultAccountRequestHandler,
 | |
| 		trieRequestHandler:    defaultTrieRequestHandler,
 | |
| 		storageRequestHandler: defaultStorageRequestHandler,
 | |
| 		codeRequestHandler:    defaultCodeRequestHandler,
 | |
| 		term:                  term,
 | |
| 	}
 | |
| 	//stderrHandler := log.StreamHandler(os.Stderr, log.TerminalFormat(true))
 | |
| 	//peer.logger.SetHandler(stderrHandler)
 | |
| 	return peer
 | |
| }
 | |
| 
 | |
| func (t *testPeer) ID() string      { return t.id }
 | |
| func (t *testPeer) Log() log.Logger { return t.logger }
 | |
| 
 | |
| func (t *testPeer) Stats() string {
 | |
| 	return fmt.Sprintf(`Account requests: %d
 | |
| Storage requests: %d
 | |
| Bytecode requests: %d
 | |
| Trienode requests: %d
 | |
| `, t.nAccountRequests, t.nStorageRequests, t.nBytecodeRequests, t.nTrienodeRequests)
 | |
| }
 | |
| 
 | |
| func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error {
 | |
| 	t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes))
 | |
| 	t.nAccountRequests++
 | |
| 	go t.accountRequestHandler(t, id, root, origin, limit, bytes)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (t *testPeer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error {
 | |
| 	t.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes))
 | |
| 	t.nTrienodeRequests++
 | |
| 	go t.trieRequestHandler(t, id, root, paths, bytes)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error {
 | |
| 	t.nStorageRequests++
 | |
| 	if len(accounts) == 1 && origin != nil {
 | |
| 		t.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes))
 | |
| 	} else {
 | |
| 		t.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes))
 | |
| 	}
 | |
| 	go t.storageRequestHandler(t, id, root, accounts, origin, limit, bytes)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error {
 | |
| 	t.nBytecodeRequests++
 | |
| 	t.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes))
 | |
| 	go t.codeRequestHandler(t, id, hashes, bytes)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // defaultTrieRequestHandler is a well-behaving handler for trie healing requests
 | |
| func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
 | |
| 	// Pass the response
 | |
| 	var nodes [][]byte
 | |
| 	for _, pathset := range paths {
 | |
| 		switch len(pathset) {
 | |
| 		case 1:
 | |
| 			blob, _, err := t.accountTrie.TryGetNode(pathset[0])
 | |
| 			if err != nil {
 | |
| 				t.logger.Info("Error handling req", "error", err)
 | |
| 				break
 | |
| 			}
 | |
| 			nodes = append(nodes, blob)
 | |
| 		default:
 | |
| 			account := t.storageTries[(common.BytesToHash(pathset[0]))]
 | |
| 			for _, path := range pathset[1:] {
 | |
| 				blob, _, err := account.TryGetNode(path)
 | |
| 				if err != nil {
 | |
| 					t.logger.Info("Error handling req", "error", err)
 | |
| 					break
 | |
| 				}
 | |
| 				nodes = append(nodes, blob)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	t.remote.OnTrieNodes(t, requestId, nodes)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // defaultAccountRequestHandler is a well-behaving handler for AccountRangeRequests
 | |
| func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
 | |
| 	keys, vals, proofs := createAccountRequestResponse(t, root, origin, limit, cap)
 | |
| 	if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil {
 | |
| 		t.test.Errorf("Remote side rejected our delivery: %v", err)
 | |
| 		t.term()
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) {
 | |
| 	var size uint64
 | |
| 	if limit == (common.Hash{}) {
 | |
| 		limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
 | |
| 	}
 | |
| 	for _, entry := range t.accountValues {
 | |
| 		if size > cap {
 | |
| 			break
 | |
| 		}
 | |
| 		if bytes.Compare(origin[:], entry.k) <= 0 {
 | |
| 			keys = append(keys, common.BytesToHash(entry.k))
 | |
| 			vals = append(vals, entry.v)
 | |
| 			size += uint64(32 + len(entry.v))
 | |
| 		}
 | |
| 		// If we've exceeded the request threshold, abort
 | |
| 		if bytes.Compare(entry.k, limit[:]) >= 0 {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	// Unless we send the entire trie, we need to supply proofs
 | |
| 	// Actually, we need to supply proofs either way! This seems to be an implementation
 | |
| 	// quirk in go-ethereum
 | |
| 	proof := light.NewNodeSet()
 | |
| 	if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil {
 | |
| 		t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err)
 | |
| 	}
 | |
| 	if len(keys) > 0 {
 | |
| 		lastK := (keys[len(keys)-1])[:]
 | |
| 		if err := t.accountTrie.Prove(lastK, 0, proof); err != nil {
 | |
| 			t.logger.Error("Could not prove last item", "error", err)
 | |
| 		}
 | |
| 	}
 | |
| 	for _, blob := range proof.NodeList() {
 | |
| 		proofs = append(proofs, blob)
 | |
| 	}
 | |
| 	return keys, vals, proofs
 | |
| }
 | |
| 
 | |
| // defaultStorageRequestHandler is a well-behaving storage request handler
 | |
| func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) error {
 | |
| 	hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, bOrigin, bLimit, max)
 | |
| 	if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
 | |
| 		t.test.Errorf("Remote side rejected our delivery: %v", err)
 | |
| 		t.term()
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
 | |
| 	var bytecodes [][]byte
 | |
| 	for _, h := range hashes {
 | |
| 		bytecodes = append(bytecodes, getCodeByHash(h))
 | |
| 	}
 | |
| 	if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
 | |
| 		t.test.Errorf("Remote side rejected our delivery: %v", err)
 | |
| 		t.term()
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) {
 | |
| 	var size uint64
 | |
| 	for _, account := range accounts {
 | |
| 		// The first account might start from a different origin and end sooner
 | |
| 		var originHash common.Hash
 | |
| 		if len(origin) > 0 {
 | |
| 			originHash = common.BytesToHash(origin)
 | |
| 		}
 | |
| 		var limitHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
 | |
| 		if len(limit) > 0 {
 | |
| 			limitHash = common.BytesToHash(limit)
 | |
| 		}
 | |
| 		var (
 | |
| 			keys  []common.Hash
 | |
| 			vals  [][]byte
 | |
| 			abort bool
 | |
| 		)
 | |
| 		for _, entry := range t.storageValues[account] {
 | |
| 			if size >= max {
 | |
| 				abort = true
 | |
| 				break
 | |
| 			}
 | |
| 			if bytes.Compare(entry.k, originHash[:]) < 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			keys = append(keys, common.BytesToHash(entry.k))
 | |
| 			vals = append(vals, entry.v)
 | |
| 			size += uint64(32 + len(entry.v))
 | |
| 			if bytes.Compare(entry.k, limitHash[:]) >= 0 {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if len(keys) > 0 {
 | |
| 			hashes = append(hashes, keys)
 | |
| 			slots = append(slots, vals)
 | |
| 		}
 | |
| 		// Generate the Merkle proofs for the first and last storage slot, but
 | |
| 		// only if the response was capped. If the entire storage trie included
 | |
| 		// in the response, no need for any proofs.
 | |
| 		if originHash != (common.Hash{}) || (abort && len(keys) > 0) {
 | |
| 			// If we're aborting, we need to prove the first and last item
 | |
| 			// This terminates the response (and thus the loop)
 | |
| 			proof := light.NewNodeSet()
 | |
| 			stTrie := t.storageTries[account]
 | |
| 
 | |
| 			// Here's a potential gotcha: when constructing the proof, we cannot
 | |
| 			// use the 'origin' slice directly, but must use the full 32-byte
 | |
| 			// hash form.
 | |
| 			if err := stTrie.Prove(originHash[:], 0, proof); err != nil {
 | |
| 				t.logger.Error("Could not prove inexistence of origin", "origin", originHash, "error", err)
 | |
| 			}
 | |
| 			if len(keys) > 0 {
 | |
| 				lastK := (keys[len(keys)-1])[:]
 | |
| 				if err := stTrie.Prove(lastK, 0, proof); err != nil {
 | |
| 					t.logger.Error("Could not prove last item", "error", err)
 | |
| 				}
 | |
| 			}
 | |
| 			for _, blob := range proof.NodeList() {
 | |
| 				proofs = append(proofs, blob)
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return hashes, slots, proofs
 | |
| }
 | |
| 
 | |
| //  the createStorageRequestResponseAlwaysProve tests a cornercase, where it always
 | |
| // supplies the proof for the last account, even if it is 'complete'.h
 | |
| func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) {
 | |
| 	var size uint64
 | |
| 	max = max * 3 / 4
 | |
| 
 | |
| 	var origin common.Hash
 | |
| 	if len(bOrigin) > 0 {
 | |
| 		origin = common.BytesToHash(bOrigin)
 | |
| 	}
 | |
| 	var exit bool
 | |
| 	for i, account := range accounts {
 | |
| 		var keys []common.Hash
 | |
| 		var vals [][]byte
 | |
| 		for _, entry := range t.storageValues[account] {
 | |
| 			if bytes.Compare(entry.k, origin[:]) < 0 {
 | |
| 				exit = true
 | |
| 			}
 | |
| 			keys = append(keys, common.BytesToHash(entry.k))
 | |
| 			vals = append(vals, entry.v)
 | |
| 			size += uint64(32 + len(entry.v))
 | |
| 			if size > max {
 | |
| 				exit = true
 | |
| 			}
 | |
| 		}
 | |
| 		if i == len(accounts)-1 {
 | |
| 			exit = true
 | |
| 		}
 | |
| 		hashes = append(hashes, keys)
 | |
| 		slots = append(slots, vals)
 | |
| 
 | |
| 		if exit {
 | |
| 			// If we're aborting, we need to prove the first and last item
 | |
| 			// This terminates the response (and thus the loop)
 | |
| 			proof := light.NewNodeSet()
 | |
| 			stTrie := t.storageTries[account]
 | |
| 
 | |
| 			// Here's a potential gotcha: when constructing the proof, we cannot
 | |
| 			// use the 'origin' slice directly, but must use the full 32-byte
 | |
| 			// hash form.
 | |
| 			if err := stTrie.Prove(origin[:], 0, proof); err != nil {
 | |
| 				t.logger.Error("Could not prove inexistence of origin", "origin", origin,
 | |
| 					"error", err)
 | |
| 			}
 | |
| 			if len(keys) > 0 {
 | |
| 				lastK := (keys[len(keys)-1])[:]
 | |
| 				if err := stTrie.Prove(lastK, 0, proof); err != nil {
 | |
| 					t.logger.Error("Could not prove last item", "error", err)
 | |
| 				}
 | |
| 			}
 | |
| 			for _, blob := range proof.NodeList() {
 | |
| 				proofs = append(proofs, blob)
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return hashes, slots, proofs
 | |
| }
 | |
| 
 | |
| // emptyRequestAccountRangeFn is a rejects AccountRangeRequests
 | |
| func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
 | |
| 	t.remote.OnAccounts(t, requestId, nil, nil, nil)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func emptyTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
 | |
| 	t.remote.OnTrieNodes(t, requestId, nil)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func nonResponsiveTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func emptyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
 | |
| 	t.remote.OnStorage(t, requestId, nil, nil, nil)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func nonResponsiveStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func proofHappyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
 | |
| 	hashes, slots, proofs := createStorageRequestResponseAlwaysProve(t, root, accounts, origin, limit, max)
 | |
| 	if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
 | |
| 		t.test.Errorf("Remote side rejected our delivery: %v", err)
 | |
| 		t.term()
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| //func emptyCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
 | |
| //	var bytecodes [][]byte
 | |
| //	t.remote.OnByteCodes(t, id, bytecodes)
 | |
| //	return nil
 | |
| //}
 | |
| 
 | |
| func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
 | |
| 	var bytecodes [][]byte
 | |
| 	for _, h := range hashes {
 | |
| 		// Send back the hashes
 | |
| 		bytecodes = append(bytecodes, h[:])
 | |
| 	}
 | |
| 	if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
 | |
| 		t.logger.Info("remote error on delivery (as expected)", "error", err)
 | |
| 		// Mimic the real-life handler, which drops a peer on errors
 | |
| 		t.remote.Unregister(t.id)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
 | |
| 	var bytecodes [][]byte
 | |
| 	for _, h := range hashes[:1] {
 | |
| 		bytecodes = append(bytecodes, getCodeByHash(h))
 | |
| 	}
 | |
| 	// Missing bytecode can be retrieved again, no error expected
 | |
| 	if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
 | |
| 		t.test.Errorf("Remote side rejected our delivery: %v", err)
 | |
| 		t.term()
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // starvingStorageRequestHandler is somewhat well-behaving storage handler, but it caps the returned results to be very small
 | |
| func starvingStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
 | |
| 	return defaultStorageRequestHandler(t, requestId, root, accounts, origin, limit, 500)
 | |
| }
 | |
| 
 | |
| func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
 | |
| 	return defaultAccountRequestHandler(t, requestId, root, origin, limit, 500)
 | |
| }
 | |
| 
 | |
| //func misdeliveringAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error {
 | |
| //	return defaultAccountRequestHandler(t, requestId-1, root, origin, 500)
 | |
| //}
 | |
| 
 | |
| func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
 | |
| 	hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, limit, cap)
 | |
| 	if len(proofs) > 0 {
 | |
| 		proofs = proofs[1:]
 | |
| 	}
 | |
| 	if err := t.remote.OnAccounts(t, requestId, hashes, accounts, proofs); err != nil {
 | |
| 		t.logger.Info("remote error on delivery (as expected)", "error", err)
 | |
| 		// Mimic the real-life handler, which drops a peer on errors
 | |
| 		t.remote.Unregister(t.id)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // corruptStorageRequestHandler doesn't provide good proofs
 | |
| func corruptStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
 | |
| 	hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, origin, limit, max)
 | |
| 	if len(proofs) > 0 {
 | |
| 		proofs = proofs[1:]
 | |
| 	}
 | |
| 	if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
 | |
| 		t.logger.Info("remote error on delivery (as expected)", "error", err)
 | |
| 		// Mimic the real-life handler, which drops a peer on errors
 | |
| 		t.remote.Unregister(t.id)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
 | |
| 	hashes, slots, _ := createStorageRequestResponse(t, root, accounts, origin, limit, max)
 | |
| 	if err := t.remote.OnStorage(t, requestId, hashes, slots, nil); err != nil {
 | |
| 		t.logger.Info("remote error on delivery (as expected)", "error", err)
 | |
| 		// Mimic the real-life handler, which drops a peer on errors
 | |
| 		t.remote.Unregister(t.id)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // TestSyncBloatedProof tests a scenario where we provide only _one_ value, but
 | |
| // also ship the entire trie inside the proof. If the attack is successful,
 | |
| // the remote side does not do any follow-up requests
 | |
| func TestSyncBloatedProof(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
 | |
| 	source := newTestPeer("source", t, term)
 | |
| 	source.accountTrie = sourceAccountTrie
 | |
| 	source.accountValues = elems
 | |
| 
 | |
| 	source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
 | |
| 		var (
 | |
| 			proofs [][]byte
 | |
| 			keys   []common.Hash
 | |
| 			vals   [][]byte
 | |
| 		)
 | |
| 		// The values
 | |
| 		for _, entry := range t.accountValues {
 | |
| 			if bytes.Compare(entry.k, origin[:]) < 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			if bytes.Compare(entry.k, limit[:]) > 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			keys = append(keys, common.BytesToHash(entry.k))
 | |
| 			vals = append(vals, entry.v)
 | |
| 		}
 | |
| 		// The proofs
 | |
| 		proof := light.NewNodeSet()
 | |
| 		if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil {
 | |
| 			t.logger.Error("Could not prove origin", "origin", origin, "error", err)
 | |
| 		}
 | |
| 		// The bloat: add proof of every single element
 | |
| 		for _, entry := range t.accountValues {
 | |
| 			if err := t.accountTrie.Prove(entry.k, 0, proof); err != nil {
 | |
| 				t.logger.Error("Could not prove item", "error", err)
 | |
| 			}
 | |
| 		}
 | |
| 		// And remove one item from the elements
 | |
| 		if len(keys) > 2 {
 | |
| 			keys = append(keys[:1], keys[2:]...)
 | |
| 			vals = append(vals[:1], vals[2:]...)
 | |
| 		}
 | |
| 		for _, blob := range proof.NodeList() {
 | |
| 			proofs = append(proofs, blob)
 | |
| 		}
 | |
| 		if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil {
 | |
| 			t.logger.Info("remote error on delivery (as expected)", "error", err)
 | |
| 			t.term()
 | |
| 			// This is actually correct, signal to exit the test successfully
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 	syncer := setupSyncer(source)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err == nil {
 | |
| 		t.Fatal("No error returned from incomplete/cancelled sync")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func setupSyncer(peers ...*testPeer) *Syncer {
 | |
| 	stateDb := rawdb.NewMemoryDatabase()
 | |
| 	syncer := NewSyncer(stateDb)
 | |
| 	for _, peer := range peers {
 | |
| 		syncer.Register(peer)
 | |
| 		peer.remote = syncer
 | |
| 	}
 | |
| 	return syncer
 | |
| }
 | |
| 
 | |
| // TestSync tests a basic sync with one peer
 | |
| func TestSync(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(mkSource("source"))
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a
 | |
| // panic within the prover
 | |
| func TestSyncTinyTriePanic(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(1)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(mkSource("source"))
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestMultiSync tests a basic sync with multiple peers
 | |
| func TestMultiSync(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(mkSource("sourceA"), mkSource("sourceB"))
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncWithStorage tests  basic sync using accounts + storage + code
 | |
| func TestSyncWithStorage(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(mkSource("sourceA"))
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all
 | |
| func TestMultiSyncManyUseless(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
 | |
| 
 | |
| 	mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 
 | |
| 		if !noAccount {
 | |
| 			source.accountRequestHandler = emptyRequestAccountRangeFn
 | |
| 		}
 | |
| 		if !noStorage {
 | |
| 			source.storageRequestHandler = emptyStorageRequestHandler
 | |
| 		}
 | |
| 		if !noTrieNode {
 | |
| 			source.trieRequestHandler = emptyTrieRequestHandler
 | |
| 		}
 | |
| 		return source
 | |
| 	}
 | |
| 
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("full", true, true, true),
 | |
| 		mkSource("noAccounts", false, true, true),
 | |
| 		mkSource("noStorage", true, false, true),
 | |
| 		mkSource("noTrie", true, true, false),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all
 | |
| func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) {
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
 | |
| 
 | |
| 	mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 
 | |
| 		if !noAccount {
 | |
| 			source.accountRequestHandler = emptyRequestAccountRangeFn
 | |
| 		}
 | |
| 		if !noStorage {
 | |
| 			source.storageRequestHandler = emptyStorageRequestHandler
 | |
| 		}
 | |
| 		if !noTrieNode {
 | |
| 			source.trieRequestHandler = emptyTrieRequestHandler
 | |
| 		}
 | |
| 		return source
 | |
| 	}
 | |
| 
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("full", true, true, true),
 | |
| 		mkSource("noAccounts", false, true, true),
 | |
| 		mkSource("noStorage", true, false, true),
 | |
| 		mkSource("noTrie", true, true, false),
 | |
| 	)
 | |
| 	// We're setting the timeout to very low, to increase the chance of the timeout
 | |
| 	// being triggered. This was previously a cause of panic, when a response
 | |
| 	// arrived simultaneously as a timeout was triggered.
 | |
| 	syncer.rates.OverrideTTLLimit = time.Millisecond
 | |
| 
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all
 | |
| func TestMultiSyncManyUnresponsive(t *testing.T) {
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
 | |
| 
 | |
| 	mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 
 | |
| 		if !noAccount {
 | |
| 			source.accountRequestHandler = nonResponsiveRequestAccountRangeFn
 | |
| 		}
 | |
| 		if !noStorage {
 | |
| 			source.storageRequestHandler = nonResponsiveStorageRequestHandler
 | |
| 		}
 | |
| 		if !noTrieNode {
 | |
| 			source.trieRequestHandler = nonResponsiveTrieRequestHandler
 | |
| 		}
 | |
| 		return source
 | |
| 	}
 | |
| 
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("full", true, true, true),
 | |
| 		mkSource("noAccounts", false, true, true),
 | |
| 		mkSource("noStorage", true, false, true),
 | |
| 		mkSource("noTrie", true, true, false),
 | |
| 	)
 | |
| 	// We're setting the timeout to very low, to make the test run a bit faster
 | |
| 	syncer.rates.OverrideTTLLimit = time.Millisecond
 | |
| 
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| func checkStall(t *testing.T, term func()) chan struct{} {
 | |
| 	testDone := make(chan struct{})
 | |
| 	go func() {
 | |
| 		select {
 | |
| 		case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much
 | |
| 			t.Log("Sync stalled")
 | |
| 			term()
 | |
| 		case <-testDone:
 | |
| 			return
 | |
| 		}
 | |
| 	}()
 | |
| 	return testDone
 | |
| }
 | |
| 
 | |
| // TestSyncBoundaryAccountTrie tests sync against a few normal peers, but the
 | |
| // account trie has a few boundary elements.
 | |
| func TestSyncBoundaryAccountTrie(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeBoundaryAccountTrie(3000)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("peer-a"),
 | |
| 		mkSource("peer-b"),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is
 | |
| // consistently returning very small results
 | |
| func TestSyncNoStorageAndOneCappedPeer(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
 | |
| 
 | |
| 	mkSource := func(name string, slow bool) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 
 | |
| 		if slow {
 | |
| 			source.accountRequestHandler = starvingAccountRequestHandler
 | |
| 		}
 | |
| 		return source
 | |
| 	}
 | |
| 
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("nice-a", false),
 | |
| 		mkSource("nice-b", false),
 | |
| 		mkSource("nice-c", false),
 | |
| 		mkSource("capped", true),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver
 | |
| // code requests properly.
 | |
| func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
 | |
| 
 | |
| 	mkSource := func(name string, codeFn codeHandlerFunc) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.codeRequestHandler = codeFn
 | |
| 		return source
 | |
| 	}
 | |
| 	// One is capped, one is corrupt. If we don't use a capped one, there's a 50%
 | |
| 	// chance that the full set of codes requested are sent only to the
 | |
| 	// non-corrupt peer, which delivers everything in one go, and makes the
 | |
| 	// test moot
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("capped", cappedCodeRequestHandler),
 | |
| 		mkSource("corrupt", corruptCodeRequestHandler),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
 | |
| 
 | |
| 	mkSource := func(name string, accFn accountHandlerFunc) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.accountRequestHandler = accFn
 | |
| 		return source
 | |
| 	}
 | |
| 	// One is capped, one is corrupt. If we don't use a capped one, there's a 50%
 | |
| 	// chance that the full set of codes requested are sent only to the
 | |
| 	// non-corrupt peer, which delivers everything in one go, and makes the
 | |
| 	// test moot
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("capped", defaultAccountRequestHandler),
 | |
| 		mkSource("corrupt", corruptAccountRequestHandler),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes
 | |
| // one by one
 | |
| func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
 | |
| 
 | |
| 	mkSource := func(name string, codeFn codeHandlerFunc) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.codeRequestHandler = codeFn
 | |
| 		return source
 | |
| 	}
 | |
| 	// Count how many times it's invoked. Remember, there are only 8 unique hashes,
 | |
| 	// so it shouldn't be more than that
 | |
| 	var counter int
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("capped", func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
 | |
| 			counter++
 | |
| 			return cappedCodeRequestHandler(t, id, hashes, max)
 | |
| 		}),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 
 | |
| 	// There are only 8 unique hashes, and 3K accounts. However, the code
 | |
| 	// deduplication is per request batch. If it were a perfect global dedup,
 | |
| 	// we would expect only 8 requests. If there were no dedup, there would be
 | |
| 	// 3k requests.
 | |
| 	// We expect somewhere below 100 requests for these 8 unique hashes. But
 | |
| 	// the number can be flaky, so don't limit it so strictly.
 | |
| 	if threshold := 100; counter > threshold {
 | |
| 		t.Logf("Error, expected < %d invocations, got %d", threshold, counter)
 | |
| 	}
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncBoundaryStorageTrie tests sync against a few normal peers, but the
 | |
| // storage trie has a few boundary elements.
 | |
| func TestSyncBoundaryStorageTrie(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("peer-a"),
 | |
| 		mkSource("peer-b"),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is
 | |
| // consistently returning very small results
 | |
| func TestSyncWithStorageAndOneCappedPeer(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false)
 | |
| 
 | |
| 	mkSource := func(name string, slow bool) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 
 | |
| 		if slow {
 | |
| 			source.storageRequestHandler = starvingStorageRequestHandler
 | |
| 		}
 | |
| 		return source
 | |
| 	}
 | |
| 
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("nice-a", false),
 | |
| 		mkSource("slow", true),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is
 | |
| // sometimes sending bad proofs
 | |
| func TestSyncWithStorageAndCorruptPeer(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
 | |
| 
 | |
| 	mkSource := func(name string, handler storageHandlerFunc) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 		source.storageRequestHandler = handler
 | |
| 		return source
 | |
| 	}
 | |
| 
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("nice-a", defaultStorageRequestHandler),
 | |
| 		mkSource("nice-b", defaultStorageRequestHandler),
 | |
| 		mkSource("nice-c", defaultStorageRequestHandler),
 | |
| 		mkSource("corrupt", corruptStorageRequestHandler),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| func TestSyncWithStorageAndNonProvingPeer(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
 | |
| 
 | |
| 	mkSource := func(name string, handler storageHandlerFunc) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 		source.storageRequestHandler = handler
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(
 | |
| 		mkSource("nice-a", defaultStorageRequestHandler),
 | |
| 		mkSource("nice-b", defaultStorageRequestHandler),
 | |
| 		mkSource("nice-c", defaultStorageRequestHandler),
 | |
| 		mkSource("corrupt", noProofStorageRequestHandler),
 | |
| 	)
 | |
| 	done := checkStall(t, term)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	close(done)
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| // TestSyncWithStorage tests  basic sync using accounts + storage + code, against
 | |
| // a peer who insists on delivering full storage sets _and_ proofs. This triggered
 | |
| // an error, where the recipient erroneously clipped the boundary nodes, but
 | |
| // did not mark the account for healing.
 | |
| func TestSyncWithStorageMisbehavingProve(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(10, 30, false)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		source.storageTries = storageTries
 | |
| 		source.storageValues = storageElems
 | |
| 		source.storageRequestHandler = proofHappyStorageRequestHandler
 | |
| 		return source
 | |
| 	}
 | |
| 	syncer := setupSyncer(mkSource("sourceA"))
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| }
 | |
| 
 | |
| type kv struct {
 | |
| 	k, v []byte
 | |
| }
 | |
| 
 | |
| // Some helpers for sorting
 | |
| type entrySlice []*kv
 | |
| 
 | |
| func (p entrySlice) Len() int           { return len(p) }
 | |
| func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 }
 | |
| func (p entrySlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
 | |
| 
 | |
| func key32(i uint64) []byte {
 | |
| 	key := make([]byte, 32)
 | |
| 	binary.LittleEndian.PutUint64(key, i)
 | |
| 	return key
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	codehashes = []common.Hash{
 | |
| 		crypto.Keccak256Hash([]byte{0}),
 | |
| 		crypto.Keccak256Hash([]byte{1}),
 | |
| 		crypto.Keccak256Hash([]byte{2}),
 | |
| 		crypto.Keccak256Hash([]byte{3}),
 | |
| 		crypto.Keccak256Hash([]byte{4}),
 | |
| 		crypto.Keccak256Hash([]byte{5}),
 | |
| 		crypto.Keccak256Hash([]byte{6}),
 | |
| 		crypto.Keccak256Hash([]byte{7}),
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // getCodeHash returns a pseudo-random code hash
 | |
| func getCodeHash(i uint64) []byte {
 | |
| 	h := codehashes[int(i)%len(codehashes)]
 | |
| 	return common.CopyBytes(h[:])
 | |
| }
 | |
| 
 | |
| // getCodeByHash convenience function to lookup the code from the code hash
 | |
| func getCodeByHash(hash common.Hash) []byte {
 | |
| 	if hash == emptyCode {
 | |
| 		return nil
 | |
| 	}
 | |
| 	for i, h := range codehashes {
 | |
| 		if h == hash {
 | |
| 			return []byte{byte(i)}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // makeAccountTrieNoStorage spits out a trie, along with the leafs
 | |
| func makeAccountTrieNoStorage(n int) (*trie.Trie, entrySlice) {
 | |
| 	db := trie.NewDatabase(rawdb.NewMemoryDatabase())
 | |
| 	accTrie := trie.NewEmpty(db)
 | |
| 	var entries entrySlice
 | |
| 	for i := uint64(1); i <= uint64(n); i++ {
 | |
| 		value, _ := rlp.EncodeToBytes(&types.StateAccount{
 | |
| 			Nonce:    i,
 | |
| 			Balance:  big.NewInt(int64(i)),
 | |
| 			Root:     emptyRoot,
 | |
| 			CodeHash: getCodeHash(i),
 | |
| 		})
 | |
| 		key := key32(i)
 | |
| 		elem := &kv{key, value}
 | |
| 		accTrie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 	}
 | |
| 	sort.Sort(entries)
 | |
| 	accTrie.Commit(nil)
 | |
| 	return accTrie, entries
 | |
| }
 | |
| 
 | |
| // makeBoundaryAccountTrie constructs an account trie. Instead of filling
 | |
| // accounts normally, this function will fill a few accounts which have
 | |
| // boundary hash.
 | |
| func makeBoundaryAccountTrie(n int) (*trie.Trie, entrySlice) {
 | |
| 	var (
 | |
| 		entries    entrySlice
 | |
| 		boundaries []common.Hash
 | |
| 
 | |
| 		db   = trie.NewDatabase(rawdb.NewMemoryDatabase())
 | |
| 		trie = trie.NewEmpty(db)
 | |
| 	)
 | |
| 	// Initialize boundaries
 | |
| 	var next common.Hash
 | |
| 	step := new(big.Int).Sub(
 | |
| 		new(big.Int).Div(
 | |
| 			new(big.Int).Exp(common.Big2, common.Big256, nil),
 | |
| 			big.NewInt(int64(accountConcurrency)),
 | |
| 		), common.Big1,
 | |
| 	)
 | |
| 	for i := 0; i < accountConcurrency; i++ {
 | |
| 		last := common.BigToHash(new(big.Int).Add(next.Big(), step))
 | |
| 		if i == accountConcurrency-1 {
 | |
| 			last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
 | |
| 		}
 | |
| 		boundaries = append(boundaries, last)
 | |
| 		next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
 | |
| 	}
 | |
| 	// Fill boundary accounts
 | |
| 	for i := 0; i < len(boundaries); i++ {
 | |
| 		value, _ := rlp.EncodeToBytes(&types.StateAccount{
 | |
| 			Nonce:    uint64(0),
 | |
| 			Balance:  big.NewInt(int64(i)),
 | |
| 			Root:     emptyRoot,
 | |
| 			CodeHash: getCodeHash(uint64(i)),
 | |
| 		})
 | |
| 		elem := &kv{boundaries[i].Bytes(), value}
 | |
| 		trie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 	}
 | |
| 	// Fill other accounts if required
 | |
| 	for i := uint64(1); i <= uint64(n); i++ {
 | |
| 		value, _ := rlp.EncodeToBytes(&types.StateAccount{
 | |
| 			Nonce:    i,
 | |
| 			Balance:  big.NewInt(int64(i)),
 | |
| 			Root:     emptyRoot,
 | |
| 			CodeHash: getCodeHash(i),
 | |
| 		})
 | |
| 		elem := &kv{key32(i), value}
 | |
| 		trie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 	}
 | |
| 	sort.Sort(entries)
 | |
| 	trie.Commit(nil)
 | |
| 	return trie, entries
 | |
| }
 | |
| 
 | |
| // makeAccountTrieWithStorageWithUniqueStorage creates an account trie where each accounts
 | |
| // has a unique storage set.
 | |
| func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) {
 | |
| 	var (
 | |
| 		db             = trie.NewDatabase(rawdb.NewMemoryDatabase())
 | |
| 		accTrie        = trie.NewEmpty(db)
 | |
| 		entries        entrySlice
 | |
| 		storageTries   = make(map[common.Hash]*trie.Trie)
 | |
| 		storageEntries = make(map[common.Hash]entrySlice)
 | |
| 	)
 | |
| 	// Create n accounts in the trie
 | |
| 	for i := uint64(1); i <= uint64(accounts); i++ {
 | |
| 		key := key32(i)
 | |
| 		codehash := emptyCode[:]
 | |
| 		if code {
 | |
| 			codehash = getCodeHash(i)
 | |
| 		}
 | |
| 		// Create a storage trie
 | |
| 		stTrie, stEntries := makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), i, db)
 | |
| 		stRoot := stTrie.Hash()
 | |
| 		stTrie.Commit(nil)
 | |
| 		value, _ := rlp.EncodeToBytes(&types.StateAccount{
 | |
| 			Nonce:    i,
 | |
| 			Balance:  big.NewInt(int64(i)),
 | |
| 			Root:     stRoot,
 | |
| 			CodeHash: codehash,
 | |
| 		})
 | |
| 		elem := &kv{key, value}
 | |
| 		accTrie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 
 | |
| 		storageTries[common.BytesToHash(key)] = stTrie
 | |
| 		storageEntries[common.BytesToHash(key)] = stEntries
 | |
| 	}
 | |
| 	sort.Sort(entries)
 | |
| 
 | |
| 	accTrie.Commit(nil)
 | |
| 	return accTrie, entries, storageTries, storageEntries
 | |
| }
 | |
| 
 | |
| // makeAccountTrieWithStorage spits out a trie, along with the leafs
 | |
| func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) {
 | |
| 	var (
 | |
| 		db             = trie.NewDatabase(rawdb.NewMemoryDatabase())
 | |
| 		accTrie        = trie.NewEmpty(db)
 | |
| 		entries        entrySlice
 | |
| 		storageTries   = make(map[common.Hash]*trie.Trie)
 | |
| 		storageEntries = make(map[common.Hash]entrySlice)
 | |
| 	)
 | |
| 	// Create n accounts in the trie
 | |
| 	for i := uint64(1); i <= uint64(accounts); i++ {
 | |
| 		key := key32(i)
 | |
| 		codehash := emptyCode[:]
 | |
| 		if code {
 | |
| 			codehash = getCodeHash(i)
 | |
| 		}
 | |
| 		// Make a storage trie
 | |
| 		var (
 | |
| 			stTrie    *trie.Trie
 | |
| 			stEntries entrySlice
 | |
| 		)
 | |
| 		if boundary {
 | |
| 			stTrie, stEntries = makeBoundaryStorageTrie(common.BytesToHash(key), slots, db)
 | |
| 		} else {
 | |
| 			stTrie, stEntries = makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), 0, db)
 | |
| 		}
 | |
| 		stRoot := stTrie.Hash()
 | |
| 		stTrie.Commit(nil)
 | |
| 
 | |
| 		value, _ := rlp.EncodeToBytes(&types.StateAccount{
 | |
| 			Nonce:    i,
 | |
| 			Balance:  big.NewInt(int64(i)),
 | |
| 			Root:     stRoot,
 | |
| 			CodeHash: codehash,
 | |
| 		})
 | |
| 		elem := &kv{key, value}
 | |
| 		accTrie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 		// we reuse the same one for all accounts
 | |
| 		storageTries[common.BytesToHash(key)] = stTrie
 | |
| 		storageEntries[common.BytesToHash(key)] = stEntries
 | |
| 	}
 | |
| 	sort.Sort(entries)
 | |
| 	accTrie.Commit(nil)
 | |
| 	return accTrie, entries, storageTries, storageEntries
 | |
| }
 | |
| 
 | |
| // makeStorageTrieWithSeed fills a storage trie with n items, returning the
 | |
| // not-yet-committed trie and the sorted entries. The seeds can be used to ensure
 | |
| // that tries are unique.
 | |
| func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Database) (*trie.Trie, entrySlice) {
 | |
| 	trie, _ := trie.New(owner, common.Hash{}, db)
 | |
| 	var entries entrySlice
 | |
| 	for i := uint64(1); i <= n; i++ {
 | |
| 		// store 'x' at slot 'x'
 | |
| 		slotValue := key32(i + seed)
 | |
| 		rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:]))
 | |
| 
 | |
| 		slotKey := key32(i)
 | |
| 		key := crypto.Keccak256Hash(slotKey[:])
 | |
| 
 | |
| 		elem := &kv{key[:], rlpSlotValue}
 | |
| 		trie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 	}
 | |
| 	sort.Sort(entries)
 | |
| 	trie.Commit(nil)
 | |
| 	return trie, entries
 | |
| }
 | |
| 
 | |
| // makeBoundaryStorageTrie constructs a storage trie. Instead of filling
 | |
| // storage slots normally, this function will fill a few slots which have
 | |
| // boundary hash.
 | |
| func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (*trie.Trie, entrySlice) {
 | |
| 	var (
 | |
| 		entries    entrySlice
 | |
| 		boundaries []common.Hash
 | |
| 		trie, _    = trie.New(owner, common.Hash{}, db)
 | |
| 	)
 | |
| 	// Initialize boundaries
 | |
| 	var next common.Hash
 | |
| 	step := new(big.Int).Sub(
 | |
| 		new(big.Int).Div(
 | |
| 			new(big.Int).Exp(common.Big2, common.Big256, nil),
 | |
| 			big.NewInt(int64(accountConcurrency)),
 | |
| 		), common.Big1,
 | |
| 	)
 | |
| 	for i := 0; i < accountConcurrency; i++ {
 | |
| 		last := common.BigToHash(new(big.Int).Add(next.Big(), step))
 | |
| 		if i == accountConcurrency-1 {
 | |
| 			last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
 | |
| 		}
 | |
| 		boundaries = append(boundaries, last)
 | |
| 		next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
 | |
| 	}
 | |
| 	// Fill boundary slots
 | |
| 	for i := 0; i < len(boundaries); i++ {
 | |
| 		key := boundaries[i]
 | |
| 		val := []byte{0xde, 0xad, 0xbe, 0xef}
 | |
| 
 | |
| 		elem := &kv{key[:], val}
 | |
| 		trie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 	}
 | |
| 	// Fill other slots if required
 | |
| 	for i := uint64(1); i <= uint64(n); i++ {
 | |
| 		slotKey := key32(i)
 | |
| 		key := crypto.Keccak256Hash(slotKey[:])
 | |
| 
 | |
| 		slotValue := key32(i)
 | |
| 		rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:]))
 | |
| 
 | |
| 		elem := &kv{key[:], rlpSlotValue}
 | |
| 		trie.Update(elem.k, elem.v)
 | |
| 		entries = append(entries, elem)
 | |
| 	}
 | |
| 	sort.Sort(entries)
 | |
| 	trie.Commit(nil)
 | |
| 	return trie, entries
 | |
| }
 | |
| 
 | |
| func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) {
 | |
| 	t.Helper()
 | |
| 	triedb := trie.NewDatabase(db)
 | |
| 	accTrie, err := trie.New(common.Hash{}, root, triedb)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	accounts, slots := 0, 0
 | |
| 	accIt := trie.NewIterator(accTrie.NodeIterator(nil))
 | |
| 	for accIt.Next() {
 | |
| 		var acc struct {
 | |
| 			Nonce    uint64
 | |
| 			Balance  *big.Int
 | |
| 			Root     common.Hash
 | |
| 			CodeHash []byte
 | |
| 		}
 | |
| 		if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil {
 | |
| 			log.Crit("Invalid account encountered during snapshot creation", "err", err)
 | |
| 		}
 | |
| 		accounts++
 | |
| 		if acc.Root != emptyRoot {
 | |
| 			storeTrie, err := trie.NewSecure(common.BytesToHash(accIt.Key), acc.Root, triedb)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			storeIt := trie.NewIterator(storeTrie.NodeIterator(nil))
 | |
| 			for storeIt.Next() {
 | |
| 				slots++
 | |
| 			}
 | |
| 			if err := storeIt.Err; err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if err := accIt.Err; err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	t.Logf("accounts: %d, slots: %d", accounts, slots)
 | |
| }
 | |
| 
 | |
| // TestSyncAccountPerformance tests how efficient the snap algo is at minimizing
 | |
| // state healing
 | |
| func TestSyncAccountPerformance(t *testing.T) {
 | |
| 	// Set the account concurrency to 1. This _should_ result in the
 | |
| 	// range root to become correct, and there should be no healing needed
 | |
| 	defer func(old int) { accountConcurrency = old }(accountConcurrency)
 | |
| 	accountConcurrency = 1
 | |
| 
 | |
| 	var (
 | |
| 		once   sync.Once
 | |
| 		cancel = make(chan struct{})
 | |
| 		term   = func() {
 | |
| 			once.Do(func() {
 | |
| 				close(cancel)
 | |
| 			})
 | |
| 		}
 | |
| 	)
 | |
| 	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
 | |
| 
 | |
| 	mkSource := func(name string) *testPeer {
 | |
| 		source := newTestPeer(name, t, term)
 | |
| 		source.accountTrie = sourceAccountTrie
 | |
| 		source.accountValues = elems
 | |
| 		return source
 | |
| 	}
 | |
| 	src := mkSource("source")
 | |
| 	syncer := setupSyncer(src)
 | |
| 	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
 | |
| 		t.Fatalf("sync failed: %v", err)
 | |
| 	}
 | |
| 	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
 | |
| 	// The trie root will always be requested, since it is added when the snap
 | |
| 	// sync cycle starts. When popping the queue, we do not look it up again.
 | |
| 	// Doing so would bring this number down to zero in this artificial testcase,
 | |
| 	// but only add extra IO for no reason in practice.
 | |
| 	if have, want := src.nTrienodeRequests, 1; have != want {
 | |
| 		fmt.Print(src.Stats())
 | |
| 		t.Errorf("trie node heal requests wrong, want %d, have %d", want, have)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSlotEstimation(t *testing.T) {
 | |
| 	for i, tc := range []struct {
 | |
| 		last  common.Hash
 | |
| 		count int
 | |
| 		want  uint64
 | |
| 	}{
 | |
| 		{
 | |
| 			// Half the space
 | |
| 			common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
 | |
| 			100,
 | |
| 			100,
 | |
| 		},
 | |
| 		{
 | |
| 			// 1 / 16th
 | |
| 			common.HexToHash("0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
 | |
| 			100,
 | |
| 			1500,
 | |
| 		},
 | |
| 		{
 | |
| 			// Bit more than 1 / 16th
 | |
| 			common.HexToHash("0x1000000000000000000000000000000000000000000000000000000000000000"),
 | |
| 			100,
 | |
| 			1499,
 | |
| 		},
 | |
| 		{
 | |
| 			// Almost everything
 | |
| 			common.HexToHash("0xF000000000000000000000000000000000000000000000000000000000000000"),
 | |
| 			100,
 | |
| 			6,
 | |
| 		},
 | |
| 		{
 | |
| 			// Almost nothing -- should lead to error
 | |
| 			common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
 | |
| 			1,
 | |
| 			0,
 | |
| 		},
 | |
| 		{
 | |
| 			// Nothing -- should lead to error
 | |
| 			common.Hash{},
 | |
| 			100,
 | |
| 			0,
 | |
| 		},
 | |
| 	} {
 | |
| 		have, _ := estimateRemainingSlots(tc.count, tc.last)
 | |
| 		if want := tc.want; have != want {
 | |
| 			t.Errorf("test %d: have %d want %d", i, have, want)
 | |
| 		}
 | |
| 	}
 | |
| }
 |