mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
eth, trie: implement fake-peer leaf-sync
This commit is contained in:
parent
f78a3770a1
commit
3befea7801
16 changed files with 502 additions and 178 deletions
|
|
@ -309,7 +309,7 @@ func copyDb(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
peer := downloader.NewFakePeer("local", db, hc, dl)
|
peer := downloader.NewFakePeer("local", db, hc, dl)
|
||||||
if err = dl.RegisterPeer("local", 63, peer); err != nil {
|
if err = dl.RegisterPeer("local", 64, peer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Synchronise with the simulated peer
|
// Synchronise with the simulated peer
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package state
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -146,17 +147,24 @@ func testIterativeStateSync(t *testing.T, batch int) {
|
||||||
|
|
||||||
queue := append([]common.Hash{}, sched.Missing(batch)...)
|
queue := append([]common.Hash{}, sched.Missing(batch)...)
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
results := make([]trie.SyncResult, len(queue))
|
results := make([]*trie.SyncResult, len(queue))
|
||||||
for i, hash := range queue {
|
for i, hash := range queue {
|
||||||
|
if rand.Int()%2 == 0 {
|
||||||
data, err := srcMem.Get(hash.Bytes())
|
data, err := srcMem.Get(hash.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
||||||
}
|
}
|
||||||
results[i] = trie.SyncResult{Hash: hash, Data: data}
|
results[i] = &trie.SyncResult{Data: data}
|
||||||
|
} else {
|
||||||
|
trie, _ := trie.New(common.Hash{}, srcMem)
|
||||||
|
results[i], _, _ = trie.FetchData(hash, 8192)
|
||||||
}
|
}
|
||||||
if _, index, err := sched.Process(results); err != nil {
|
}
|
||||||
|
for index, result := range results {
|
||||||
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if index, err := sched.Commit(dstDb); err != nil {
|
if index, err := sched.Commit(dstDb); err != nil {
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err)
|
t.Fatalf("failed to commit data #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
|
@ -179,17 +187,24 @@ func TestIterativeDelayedStateSync(t *testing.T) {
|
||||||
queue := append([]common.Hash{}, sched.Missing(0)...)
|
queue := append([]common.Hash{}, sched.Missing(0)...)
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
// Sync only half of the scheduled nodes
|
// Sync only half of the scheduled nodes
|
||||||
results := make([]trie.SyncResult, len(queue)/2+1)
|
results := make([]*trie.SyncResult, len(queue)/2+1)
|
||||||
for i, hash := range queue[:len(results)] {
|
for i, hash := range queue[:len(results)] {
|
||||||
|
if rand.Int()%2 == 0 {
|
||||||
data, err := srcMem.Get(hash.Bytes())
|
data, err := srcMem.Get(hash.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
||||||
}
|
}
|
||||||
results[i] = trie.SyncResult{Hash: hash, Data: data}
|
results[i] = &trie.SyncResult{Data: data}
|
||||||
|
} else {
|
||||||
|
trie, _ := trie.New(common.Hash{}, srcMem)
|
||||||
|
results[i], _, _ = trie.FetchData(hash, 8192)
|
||||||
}
|
}
|
||||||
if _, index, err := sched.Process(results); err != nil {
|
}
|
||||||
|
for index, result := range results {
|
||||||
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if index, err := sched.Commit(dstDb); err != nil {
|
if index, err := sched.Commit(dstDb); err != nil {
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err)
|
t.Fatalf("failed to commit data #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
|
@ -219,18 +234,26 @@ func testIterativeRandomStateSync(t *testing.T, batch int) {
|
||||||
}
|
}
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
// Fetch all the queued nodes in a random order
|
// Fetch all the queued nodes in a random order
|
||||||
results := make([]trie.SyncResult, 0, len(queue))
|
results := make([]*trie.SyncResult, 0, len(queue))
|
||||||
for hash := range queue {
|
for hash := range queue {
|
||||||
|
if rand.Int()%2 == 0 {
|
||||||
data, err := srcMem.Get(hash.Bytes())
|
data, err := srcMem.Get(hash.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
||||||
}
|
}
|
||||||
results = append(results, trie.SyncResult{Hash: hash, Data: data})
|
results = append(results, &trie.SyncResult{Data: data})
|
||||||
|
} else {
|
||||||
|
trie, _ := trie.New(common.Hash{}, srcMem)
|
||||||
|
request, _, _ := trie.FetchData(hash, 8192)
|
||||||
|
results = append(results, request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Feed the retrieved results back and queue new tasks
|
// Feed the retrieved results back and queue new tasks
|
||||||
if _, index, err := sched.Process(results); err != nil {
|
for index, result := range results {
|
||||||
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if index, err := sched.Commit(dstDb); err != nil {
|
if index, err := sched.Commit(dstDb); err != nil {
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err)
|
t.Fatalf("failed to commit data #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +282,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
// Sync only half of the scheduled nodes, even those in random order
|
// Sync only half of the scheduled nodes, even those in random order
|
||||||
results := make([]trie.SyncResult, 0, len(queue)/2+1)
|
results := make([]*trie.SyncResult, 0, len(queue)/2+1)
|
||||||
for hash := range queue {
|
for hash := range queue {
|
||||||
delete(queue, hash)
|
delete(queue, hash)
|
||||||
|
|
||||||
|
|
@ -267,16 +290,18 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
||||||
}
|
}
|
||||||
results = append(results, trie.SyncResult{Hash: hash, Data: data})
|
results = append(results, &trie.SyncResult{Data: data})
|
||||||
|
|
||||||
if len(results) >= cap(results) {
|
if len(results) >= cap(results) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Feed the retrieved results back and queue new tasks
|
// Feed the retrieved results back and queue new tasks
|
||||||
if _, index, err := sched.Process(results); err != nil {
|
for index, result := range results {
|
||||||
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if index, err := sched.Commit(dstDb); err != nil {
|
if index, err := sched.Commit(dstDb); err != nil {
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err)
|
t.Fatalf("failed to commit data #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
|
@ -304,24 +329,25 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||||
queue := append([]common.Hash{}, sched.Missing(1)...)
|
queue := append([]common.Hash{}, sched.Missing(1)...)
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
// Fetch a batch of state nodes
|
// Fetch a batch of state nodes
|
||||||
results := make([]trie.SyncResult, len(queue))
|
results := make([]*trie.SyncResult, len(queue))
|
||||||
for i, hash := range queue {
|
for i, hash := range queue {
|
||||||
data, err := srcMem.Get(hash.Bytes())
|
data, err := srcMem.Get(hash.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
|
||||||
}
|
}
|
||||||
results[i] = trie.SyncResult{Hash: hash, Data: data}
|
results[i] = &trie.SyncResult{Data: data}
|
||||||
}
|
}
|
||||||
// Process each of the state nodes
|
// Process each of the state nodes
|
||||||
if _, index, err := sched.Process(results); err != nil {
|
for index, result := range results {
|
||||||
|
_, _, hash, err := sched.Process(result)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
added = append(added, hash)
|
||||||
|
}
|
||||||
if index, err := sched.Commit(dstDb); err != nil {
|
if index, err := sched.Commit(dstDb); err != nil {
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err)
|
t.Fatalf("failed to commit data #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
for _, result := range results {
|
|
||||||
added = append(added, result.Hash)
|
|
||||||
}
|
|
||||||
// Check that all known sub-tries added so far are complete or missing entirely.
|
// Check that all known sub-tries added so far are complete or missing entirely.
|
||||||
checkSubtries:
|
checkSubtries:
|
||||||
for _, hash := range added {
|
for _, hash := range added {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ var dumper = spew.ConfigState{Indent: " "}
|
||||||
func TestStorageRangeAt(t *testing.T) {
|
func TestStorageRangeAt(t *testing.T) {
|
||||||
// Create a state where account 0x010000... has a few storage entries.
|
// Create a state where account 0x010000... has a few storage entries.
|
||||||
var (
|
var (
|
||||||
db, _ = ethdb.NewMemDatabase()
|
db = ethdb.NewMemDatabase()
|
||||||
state, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
state, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
addr = common.Address{0x01}
|
addr = common.Address{0x01}
|
||||||
keys = []common.Hash{ // hashes of Keys of storage
|
keys = []common.Hash{ // hashes of Keys of storage
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,6 +46,8 @@ var (
|
||||||
MaxBodyFetch = 128 // Amount of block bodies to be fetched per retrieval request
|
MaxBodyFetch = 128 // Amount of block bodies to be fetched per retrieval request
|
||||||
MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request
|
MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request
|
||||||
MaxStateFetch = 384 // Amount of node state values to allow fetching per request
|
MaxStateFetch = 384 // Amount of node state values to allow fetching per request
|
||||||
|
MinTrieFetch = 100 * 1024 // Amount of state leaf data to fetch at minimum per request
|
||||||
|
MaxTrieFetch = 2048 * 1024 // Amount of state leaf data to allow fetching per request
|
||||||
|
|
||||||
MaxForkAncestry = 3 * params.EpochDuration // Maximum chain reorganisation
|
MaxForkAncestry = 3 * params.EpochDuration // Maximum chain reorganisation
|
||||||
rttMinEstimate = 2 * time.Second // Minimum round-trip time to target for download requests
|
rttMinEstimate = 2 * time.Second // Minimum round-trip time to target for download requests
|
||||||
|
|
@ -57,7 +60,7 @@ var (
|
||||||
qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence
|
qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence
|
||||||
qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value
|
qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value
|
||||||
|
|
||||||
maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection)
|
maxQueuedHeaders = 32 * 1024 // Maximum number of headers to queue for import (DOS protection)
|
||||||
maxHeadersProcess = 2048 // Number of header download results to import at once into the chain
|
maxHeadersProcess = 2048 // Number of header download results to import at once into the chain
|
||||||
maxResultsProcess = 2048 // Number of content download results to import at once into the chain
|
maxResultsProcess = 2048 // Number of content download results to import at once into the chain
|
||||||
|
|
||||||
|
|
@ -137,6 +140,7 @@ type Downloader struct {
|
||||||
stateSyncStart chan *stateSync
|
stateSyncStart chan *stateSync
|
||||||
trackStateReq chan *stateReq
|
trackStateReq chan *stateReq
|
||||||
stateCh chan dataPack // [eth/63] Channel receiving inbound node state data
|
stateCh chan dataPack // [eth/63] Channel receiving inbound node state data
|
||||||
|
trieCh chan dataPack // [eth/64] Channel receiving inbound state trie data
|
||||||
|
|
||||||
// Cancellation and termination
|
// Cancellation and termination
|
||||||
cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop)
|
cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop)
|
||||||
|
|
@ -225,6 +229,7 @@ func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockC
|
||||||
headerProcCh: make(chan []*types.Header, 1),
|
headerProcCh: make(chan []*types.Header, 1),
|
||||||
quitCh: make(chan struct{}),
|
quitCh: make(chan struct{}),
|
||||||
stateCh: make(chan dataPack),
|
stateCh: make(chan dataPack),
|
||||||
|
trieCh: make(chan dataPack),
|
||||||
stateSyncStart: make(chan *stateSync),
|
stateSyncStart: make(chan *stateSync),
|
||||||
trackStateReq: make(chan *stateReq),
|
trackStateReq: make(chan *stateReq),
|
||||||
}
|
}
|
||||||
|
|
@ -471,9 +476,9 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchers := []func() error{
|
fetchers := []func() error{
|
||||||
func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved
|
//func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved
|
||||||
func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync
|
//func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync
|
||||||
func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync
|
//func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync
|
||||||
func() error { return d.processHeaders(origin+1, td) },
|
func() error { return d.processHeaders(origin+1, td) },
|
||||||
}
|
}
|
||||||
if d.mode == FastSync {
|
if d.mode == FastSync {
|
||||||
|
|
@ -1470,25 +1475,30 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error {
|
||||||
|
|
||||||
// DeliverHeaders injects a new batch of block headers received from a remote
|
// DeliverHeaders injects a new batch of block headers received from a remote
|
||||||
// node into the download schedule.
|
// node into the download schedule.
|
||||||
func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) {
|
func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) error {
|
||||||
return d.deliver(id, d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter)
|
return d.deliver(id, d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverBodies injects a new batch of block bodies received from a remote node.
|
// DeliverBodies injects a new batch of block bodies received from a remote node.
|
||||||
func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) (err error) {
|
func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) error {
|
||||||
return d.deliver(id, d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter)
|
return d.deliver(id, d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverReceipts injects a new batch of receipts received from a remote node.
|
// DeliverReceipts injects a new batch of receipts received from a remote node.
|
||||||
func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) (err error) {
|
func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) error {
|
||||||
return d.deliver(id, d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter)
|
return d.deliver(id, d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverNodeData injects a new batch of node state data received from a remote node.
|
// DeliverNodeData injects a new batch of node state data received from a remote node.
|
||||||
func (d *Downloader) DeliverNodeData(id string, data [][]byte) (err error) {
|
func (d *Downloader) DeliverNodeData(id string, data [][]byte) error {
|
||||||
return d.deliver(id, d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter)
|
return d.deliver(id, d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeliverTrie injects a (possibly partial) sub-trie received from a remote node.
|
||||||
|
func (d *Downloader) DeliverTries(id string, res []*trie.SyncResult) error {
|
||||||
|
return d.deliver(id, d.trieCh, &triePack{id, res}, trieInMeter, trieDropMeter)
|
||||||
|
}
|
||||||
|
|
||||||
// deliver injects a new batch of data received from a remote node.
|
// deliver injects a new batch of data received from a remote node.
|
||||||
func (d *Downloader) deliver(id string, destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) {
|
func (d *Downloader) deliver(id string, destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) {
|
||||||
// Update the delivery metrics for both good and failed deliveries
|
// Update the delivery metrics for both good and failed deliveries
|
||||||
|
|
|
||||||
|
|
@ -608,6 +608,23 @@ func (dlp *downloadTesterPeer) RequestNodeData(hashes []common.Hash) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestTries constructs a getNodeData method associated with a particular
|
||||||
|
// peer in the download tester. The returned function can be used to retrieve
|
||||||
|
// batches of node state data from the particularly requested peer.
|
||||||
|
func (dlp *downloadTesterPeer) RequestTries(roots []common.Hash, limit common.StorageSize) error {
|
||||||
|
dlp.waitDelay()
|
||||||
|
|
||||||
|
dlp.dl.lock.RLock()
|
||||||
|
defer dlp.dl.lock.RUnlock()
|
||||||
|
|
||||||
|
if data, err := dlp.dl.peerDb.Get(roots[0].Bytes()); err == nil {
|
||||||
|
if !dlp.dl.peerMissingStates[dlp.id][roots[0]] {
|
||||||
|
go dlp.dl.downloader.DeliverTries(dlp.id, []*trie.SyncResult{{Data: data}})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// assertOwnChain checks if the local chain contains the correct number of items
|
// assertOwnChain checks if the local chain contains the correct number of items
|
||||||
// of the various chain components.
|
// of the various chain components.
|
||||||
func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
|
func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
|
||||||
|
|
@ -1690,6 +1707,9 @@ func (ftp *floodingTestPeer) RequestReceipts(hashes []common.Hash) error {
|
||||||
func (ftp *floodingTestPeer) RequestNodeData(hashes []common.Hash) error {
|
func (ftp *floodingTestPeer) RequestNodeData(hashes []common.Hash) error {
|
||||||
return ftp.peer.RequestNodeData(hashes)
|
return ftp.peer.RequestNodeData(hashes)
|
||||||
}
|
}
|
||||||
|
func (ftp *floodingTestPeer) RequestTries(roots []common.Hash, limit common.StorageSize) error {
|
||||||
|
return ftp.peer.RequestTries(roots, limit)
|
||||||
|
}
|
||||||
|
|
||||||
func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int, reverse bool) error {
|
func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int, reverse bool) error {
|
||||||
deliveriesDone := make(chan struct{}, 500)
|
deliveriesDone := make(chan struct{}, 500)
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,18 @@
|
||||||
package downloader
|
package downloader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/golang/snappy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FakePeer is a mock downloader peer that operates on a local database instance
|
// FakePeer is a mock downloader peer that operates on a local database instance
|
||||||
|
|
@ -146,6 +152,18 @@ func (p *FakePeer) RequestReceipts(hashes []common.Hash) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cnt int32
|
||||||
|
var siz int32
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
//fmt.Println("Packets:", atomic.LoadInt32(&cnt), "Bytes (snappy):", atomic.LoadInt32(&siz))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// RequestNodeData implements downloader.Peer, returning a batch of state trie
|
// RequestNodeData implements downloader.Peer, returning a batch of state trie
|
||||||
// nodes corresponding to the specified trie hashes.
|
// nodes corresponding to the specified trie hashes.
|
||||||
func (p *FakePeer) RequestNodeData(hashes []common.Hash) error {
|
func (p *FakePeer) RequestNodeData(hashes []common.Hash) error {
|
||||||
|
|
@ -155,6 +173,45 @@ func (p *FakePeer) RequestNodeData(hashes []common.Hash) error {
|
||||||
data = append(data, entry)
|
data = append(data, entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
atomic.AddInt32(&cnt, 1)
|
||||||
|
blob, _ := rlp.EncodeToBytes(data)
|
||||||
|
atomic.AddInt32(&siz, int32(len(snappy.Encode(nil, blob))))
|
||||||
|
|
||||||
p.dl.DeliverNodeData(p.id, data)
|
p.dl.DeliverNodeData(p.id, data)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestTries implements downloader.Peer, returning the (potentially partial)
|
||||||
|
// leaves of a state sub-trie.
|
||||||
|
func (p *FakePeer) RequestTries(roots []common.Hash, limit common.StorageSize) error {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
t, err := trie.New(common.Hash{}, p.db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
results := make([]*trie.SyncResult, 0, len(roots))
|
||||||
|
for _, root := range roots {
|
||||||
|
res, size, err := t.FetchData(root, limit, time.Second-time.Since(start))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
results = append(results, res)
|
||||||
|
|
||||||
|
if size >= limit || time.Since(start) > time.Second {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
limit -= size
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddInt32(&cnt, 1)
|
||||||
|
blob, _ := rlp.EncodeToBytes(results)
|
||||||
|
size := len(snappy.Encode(nil, blob))
|
||||||
|
atomic.AddInt32(&siz, int32(size))
|
||||||
|
|
||||||
|
fmt.Printf("Packet #%d: %d bytes (%d total) in %v\n", atomic.LoadInt32(&cnt), size, atomic.LoadInt32(&siz), time.Since(start))
|
||||||
|
|
||||||
|
p.dl.DeliverTries(p.id, results)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,6 @@ var (
|
||||||
|
|
||||||
stateInMeter = metrics.NewMeter("eth/downloader/states/in")
|
stateInMeter = metrics.NewMeter("eth/downloader/states/in")
|
||||||
stateDropMeter = metrics.NewMeter("eth/downloader/states/drop")
|
stateDropMeter = metrics.NewMeter("eth/downloader/states/drop")
|
||||||
|
trieInMeter = metrics.NewMeter("eth/downloader/tries/in")
|
||||||
|
trieDropMeter = metrics.NewMeter("eth/downloader/tries/drop")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,13 @@ type peerConnection struct {
|
||||||
blockIdle int32 // Current block activity state of the peer (idle = 0, active = 1)
|
blockIdle int32 // Current block activity state of the peer (idle = 0, active = 1)
|
||||||
receiptIdle int32 // Current receipt activity state of the peer (idle = 0, active = 1)
|
receiptIdle int32 // Current receipt activity state of the peer (idle = 0, active = 1)
|
||||||
stateIdle int32 // Current node data activity state of the peer (idle = 0, active = 1)
|
stateIdle int32 // Current node data activity state of the peer (idle = 0, active = 1)
|
||||||
|
trieIdle int32 // Current trie activity state of the peer (idle = 0, active = 1)
|
||||||
|
|
||||||
headerThroughput float64 // Number of headers measured to be retrievable per second
|
headerThroughput float64 // Number of headers measured to be retrievable per second
|
||||||
blockThroughput float64 // Number of blocks (bodies) measured to be retrievable per second
|
blockThroughput float64 // Number of blocks (bodies) measured to be retrievable per second
|
||||||
receiptThroughput float64 // Number of receipts measured to be retrievable per second
|
receiptThroughput float64 // Number of receipts measured to be retrievable per second
|
||||||
stateThroughput float64 // Number of node data pieces measured to be retrievable per second
|
stateThroughput float64 // Number of node data pieces measured to be retrievable per second
|
||||||
|
trieThroughput float64 // Amount of trie leaves measured to be retrievable per second
|
||||||
|
|
||||||
rtt time.Duration // Request round trip time to track responsiveness (QoS)
|
rtt time.Duration // Request round trip time to track responsiveness (QoS)
|
||||||
|
|
||||||
|
|
@ -65,6 +67,7 @@ type peerConnection struct {
|
||||||
blockStarted time.Time // Time instance when the last block (body) fetch was started
|
blockStarted time.Time // Time instance when the last block (body) fetch was started
|
||||||
receiptStarted time.Time // Time instance when the last receipt fetch was started
|
receiptStarted time.Time // Time instance when the last receipt fetch was started
|
||||||
stateStarted time.Time // Time instance when the last node data fetch was started
|
stateStarted time.Time // Time instance when the last node data fetch was started
|
||||||
|
trieStarted time.Time // Time instance when the last node data fetch was started
|
||||||
|
|
||||||
lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously)
|
lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously)
|
||||||
|
|
||||||
|
|
@ -88,6 +91,7 @@ type Peer interface {
|
||||||
RequestBodies([]common.Hash) error
|
RequestBodies([]common.Hash) error
|
||||||
RequestReceipts([]common.Hash) error
|
RequestReceipts([]common.Hash) error
|
||||||
RequestNodeData([]common.Hash) error
|
RequestNodeData([]common.Hash) error
|
||||||
|
RequestTries([]common.Hash, common.StorageSize) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// lightPeerWrapper wraps a LightPeer struct, stubbing out the Peer-only methods.
|
// lightPeerWrapper wraps a LightPeer struct, stubbing out the Peer-only methods.
|
||||||
|
|
@ -111,6 +115,9 @@ func (w *lightPeerWrapper) RequestReceipts([]common.Hash) error {
|
||||||
func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error {
|
func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error {
|
||||||
panic("RequestNodeData not supported in light client mode sync")
|
panic("RequestNodeData not supported in light client mode sync")
|
||||||
}
|
}
|
||||||
|
func (w *lightPeerWrapper) RequestTries([]common.Hash, common.StorageSize) error {
|
||||||
|
panic("RequestTries not supported in light client mode sync")
|
||||||
|
}
|
||||||
|
|
||||||
// newPeerConnection creates a new downloader peer.
|
// newPeerConnection creates a new downloader peer.
|
||||||
func newPeerConnection(id string, version int, peer Peer, logger log.Logger) *peerConnection {
|
func newPeerConnection(id string, version int, peer Peer, logger log.Logger) *peerConnection {
|
||||||
|
|
@ -134,11 +141,13 @@ func (p *peerConnection) Reset() {
|
||||||
atomic.StoreInt32(&p.blockIdle, 0)
|
atomic.StoreInt32(&p.blockIdle, 0)
|
||||||
atomic.StoreInt32(&p.receiptIdle, 0)
|
atomic.StoreInt32(&p.receiptIdle, 0)
|
||||||
atomic.StoreInt32(&p.stateIdle, 0)
|
atomic.StoreInt32(&p.stateIdle, 0)
|
||||||
|
atomic.StoreInt32(&p.trieIdle, 0)
|
||||||
|
|
||||||
p.headerThroughput = 0
|
p.headerThroughput = 0
|
||||||
p.blockThroughput = 0
|
p.blockThroughput = 0
|
||||||
p.receiptThroughput = 0
|
p.receiptThroughput = 0
|
||||||
p.stateThroughput = 0
|
p.stateThroughput = 0
|
||||||
|
p.trieThroughput = 0
|
||||||
|
|
||||||
p.lacking = make(map[common.Hash]struct{})
|
p.lacking = make(map[common.Hash]struct{})
|
||||||
}
|
}
|
||||||
|
|
@ -222,6 +231,23 @@ func (p *peerConnection) FetchNodeData(hashes []common.Hash) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FetchTries sends a trie retrieval request to the remote peer.
|
||||||
|
func (p *peerConnection) FetchTries(roots []common.Hash, limit common.StorageSize) error {
|
||||||
|
// Sanity check the protocol version
|
||||||
|
if p.version < 64 {
|
||||||
|
panic(fmt.Sprintf("trie fetch [eth/64+] requested on eth/%d", p.version))
|
||||||
|
}
|
||||||
|
// Short circuit if the peer is already fetching
|
||||||
|
if !atomic.CompareAndSwapInt32(&p.trieIdle, 0, 1) {
|
||||||
|
return errAlreadyFetching
|
||||||
|
}
|
||||||
|
p.trieStarted = time.Now()
|
||||||
|
|
||||||
|
go p.peer.RequestTries(roots, limit)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetHeadersIdle sets the peer to idle, allowing it to execute new header retrieval
|
// SetHeadersIdle sets the peer to idle, allowing it to execute new header retrieval
|
||||||
// requests. Its estimated header retrieval throughput is updated with that measured
|
// requests. Its estimated header retrieval throughput is updated with that measured
|
||||||
// just now.
|
// just now.
|
||||||
|
|
@ -257,6 +283,13 @@ func (p *peerConnection) SetNodeDataIdle(delivered int) {
|
||||||
p.setIdle(p.stateStarted, delivered, &p.stateThroughput, &p.stateIdle)
|
p.setIdle(p.stateStarted, delivered, &p.stateThroughput, &p.stateIdle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTrieIdle sets the peer to idle, allowing it to execute new trie retrieval
|
||||||
|
// requests. Its estimated state retrieval throughput is updated with that
|
||||||
|
// measured just now.
|
||||||
|
func (p *peerConnection) SetTrieIdle(delivered int) {
|
||||||
|
p.setIdle(p.trieStarted, delivered, &p.trieThroughput, &p.trieIdle)
|
||||||
|
}
|
||||||
|
|
||||||
// setIdle sets the peer to idle, allowing it to execute new retrieval requests.
|
// setIdle sets the peer to idle, allowing it to execute new retrieval requests.
|
||||||
// Its estimated retrieval throughput is updated with that measured just now.
|
// Its estimated retrieval throughput is updated with that measured just now.
|
||||||
func (p *peerConnection) setIdle(started time.Time, delivered int, throughput *float64, idle *int32) {
|
func (p *peerConnection) setIdle(started time.Time, delivered int, throughput *float64, idle *int32) {
|
||||||
|
|
@ -278,10 +311,17 @@ func (p *peerConnection) setIdle(started time.Time, delivered int, throughput *f
|
||||||
*throughput = (1-measurementImpact)*(*throughput) + measurementImpact*measured
|
*throughput = (1-measurementImpact)*(*throughput) + measurementImpact*measured
|
||||||
p.rtt = time.Duration((1-measurementImpact)*float64(p.rtt) + measurementImpact*float64(elapsed))
|
p.rtt = time.Duration((1-measurementImpact)*float64(p.rtt) + measurementImpact*float64(elapsed))
|
||||||
|
|
||||||
|
if p.version <= 63 {
|
||||||
p.log.Trace("Peer throughput measurements updated",
|
p.log.Trace("Peer throughput measurements updated",
|
||||||
"hps", p.headerThroughput, "bps", p.blockThroughput,
|
"hps", p.headerThroughput, "bps", p.blockThroughput,
|
||||||
"rps", p.receiptThroughput, "sps", p.stateThroughput,
|
"rps", p.receiptThroughput, "sps", p.stateThroughput,
|
||||||
"miss", len(p.lacking), "rtt", p.rtt)
|
"miss", len(p.lacking), "rtt", p.rtt)
|
||||||
|
} else {
|
||||||
|
p.log.Trace("Peer throughput measurements updated",
|
||||||
|
"hps", p.headerThroughput, "bps", p.blockThroughput,
|
||||||
|
"rps", p.receiptThroughput, "tps", p.trieThroughput,
|
||||||
|
"miss", len(p.lacking), "rtt", p.rtt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeaderCapacity retrieves the peers header download allowance based on its
|
// HeaderCapacity retrieves the peers header download allowance based on its
|
||||||
|
|
@ -320,6 +360,15 @@ func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int {
|
||||||
return int(math.Min(1+math.Max(1, p.stateThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxStateFetch)))
|
return int(math.Min(1+math.Max(1, p.stateThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxStateFetch)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrieCapacity retrieves the peers trie download allowance based on its
|
||||||
|
// previously discovered throughput.
|
||||||
|
func (p *peerConnection) TrieCapacity(targetRTT time.Duration) int {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
|
return int(math.Min(1+math.Max(1, p.trieThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxTrieFetch)))
|
||||||
|
}
|
||||||
|
|
||||||
// MarkLacking appends a new entity to the set of items (blocks, receipts, states)
|
// MarkLacking appends a new entity to the set of items (blocks, receipts, states)
|
||||||
// that a peer is known not to have (i.e. have been requested before). If the
|
// that a peer is known not to have (i.e. have been requested before). If the
|
||||||
// set reaches its maximum allowed capacity, items are randomly dropped off.
|
// set reaches its maximum allowed capacity, items are randomly dropped off.
|
||||||
|
|
@ -400,7 +449,7 @@ func (ps *peerSet) Register(p *peerConnection) error {
|
||||||
return errAlreadyRegistered
|
return errAlreadyRegistered
|
||||||
}
|
}
|
||||||
if len(ps.peers) > 0 {
|
if len(ps.peers) > 0 {
|
||||||
p.headerThroughput, p.blockThroughput, p.receiptThroughput, p.stateThroughput = 0, 0, 0, 0
|
p.headerThroughput, p.blockThroughput, p.receiptThroughput, p.stateThroughput, p.trieThroughput = 0, 0, 0, 0, 0
|
||||||
|
|
||||||
for _, peer := range ps.peers {
|
for _, peer := range ps.peers {
|
||||||
peer.lock.RLock()
|
peer.lock.RLock()
|
||||||
|
|
@ -408,12 +457,14 @@ func (ps *peerSet) Register(p *peerConnection) error {
|
||||||
p.blockThroughput += peer.blockThroughput
|
p.blockThroughput += peer.blockThroughput
|
||||||
p.receiptThroughput += peer.receiptThroughput
|
p.receiptThroughput += peer.receiptThroughput
|
||||||
p.stateThroughput += peer.stateThroughput
|
p.stateThroughput += peer.stateThroughput
|
||||||
|
p.trieThroughput += peer.trieThroughput
|
||||||
peer.lock.RUnlock()
|
peer.lock.RUnlock()
|
||||||
}
|
}
|
||||||
p.headerThroughput /= float64(len(ps.peers))
|
p.headerThroughput /= float64(len(ps.peers))
|
||||||
p.blockThroughput /= float64(len(ps.peers))
|
p.blockThroughput /= float64(len(ps.peers))
|
||||||
p.receiptThroughput /= float64(len(ps.peers))
|
p.receiptThroughput /= float64(len(ps.peers))
|
||||||
p.stateThroughput /= float64(len(ps.peers))
|
p.stateThroughput /= float64(len(ps.peers))
|
||||||
|
p.trieThroughput /= float64(len(ps.peers))
|
||||||
}
|
}
|
||||||
ps.peers[p.id] = p
|
ps.peers[p.id] = p
|
||||||
ps.lock.Unlock()
|
ps.lock.Unlock()
|
||||||
|
|
@ -519,7 +570,21 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
|
||||||
defer p.lock.RUnlock()
|
defer p.lock.RUnlock()
|
||||||
return p.stateThroughput
|
return p.stateThroughput
|
||||||
}
|
}
|
||||||
return ps.idlePeers(63, 64, idle, throughput)
|
return ps.idlePeers(63, 63, idle, throughput)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrieIdlePeers retrieves a flat list of all the currently trie-idle
|
||||||
|
// peers within the active peer set, ordered by their reputation.
|
||||||
|
func (ps *peerSet) TrieIdlePeers() ([]*peerConnection, int) {
|
||||||
|
idle := func(p *peerConnection) bool {
|
||||||
|
return atomic.LoadInt32(&p.trieIdle) == 0
|
||||||
|
}
|
||||||
|
throughput := func(p *peerConnection) float64 {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
|
return p.trieThroughput
|
||||||
|
}
|
||||||
|
return ps.idlePeers(64, 64, idle, throughput)
|
||||||
}
|
}
|
||||||
|
|
||||||
// idlePeers retrieves a flat list of all currently idle peers satisfying the
|
// idlePeers retrieves a flat list of all currently idle peers satisfying the
|
||||||
|
|
|
||||||
|
|
@ -32,18 +32,23 @@ import (
|
||||||
// stateReq represents a batch of state fetch requests groupped together into
|
// stateReq represents a batch of state fetch requests groupped together into
|
||||||
// a single data retrieval network packet.
|
// a single data retrieval network packet.
|
||||||
type stateReq struct {
|
type stateReq struct {
|
||||||
items []common.Hash // Hashes of the state items to download
|
items []common.Hash // Hashes of the state items to download (single one in leaf-mode)
|
||||||
|
limit common.StorageSize // Amount of trie-leaf data to download in leaf-mode
|
||||||
|
|
||||||
tasks map[common.Hash]*stateTask // Download tasks to track previous attempts
|
tasks map[common.Hash]*stateTask // Download tasks to track previous attempts
|
||||||
timeout time.Duration // Maximum round trip time for this to complete
|
timeout time.Duration // Maximum round trip time for this to complete
|
||||||
timer *time.Timer // Timer to fire when the RTT timeout expires
|
timer *time.Timer // Timer to fire when the RTT timeout expires
|
||||||
peer *peerConnection // Peer that we're requesting from
|
peer *peerConnection // Peer that we're requesting from
|
||||||
response [][]byte // Response data of the peer (nil for timeouts)
|
|
||||||
|
nodeRes [][]byte // Response data of the node-mode peer (nil for timeouts)
|
||||||
|
trieRes []*trie.SyncResult // Response data of the leaf-mode peer (nil for timeouts)
|
||||||
|
|
||||||
dropped bool // Flag whether the peer dropped off early
|
dropped bool // Flag whether the peer dropped off early
|
||||||
}
|
}
|
||||||
|
|
||||||
// timedOut returns if this request timed out.
|
// timedOut returns if this request timed out.
|
||||||
func (req *stateReq) timedOut() bool {
|
func (req *stateReq) timedOut() bool {
|
||||||
return req.response == nil
|
return req.nodeRes == nil && req.trieRes == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// stateSyncStats is a collection of progress stats to report during a state trie
|
// stateSyncStats is a collection of progress stats to report during a state trie
|
||||||
|
|
@ -142,7 +147,21 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
||||||
}
|
}
|
||||||
// Finalize the request and queue up for processing
|
// Finalize the request and queue up for processing
|
||||||
req.timer.Stop()
|
req.timer.Stop()
|
||||||
req.response = pack.(*statePack).states
|
req.nodeRes = pack.(*statePack).states
|
||||||
|
|
||||||
|
finished = append(finished, req)
|
||||||
|
delete(active, pack.PeerId())
|
||||||
|
|
||||||
|
case pack := <-d.trieCh:
|
||||||
|
// Discard any data not requested (or previsouly timed out)
|
||||||
|
req := active[pack.PeerId()]
|
||||||
|
if req == nil {
|
||||||
|
log.Debug("Unrequested trie data", "peer", pack.PeerId(), "len", pack.Items())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Finalize the request and queue up for processing
|
||||||
|
req.timer.Stop()
|
||||||
|
req.trieRes = pack.(*triePack).results
|
||||||
|
|
||||||
finished = append(finished, req)
|
finished = append(finished, req)
|
||||||
delete(active, pack.PeerId())
|
delete(active, pack.PeerId())
|
||||||
|
|
@ -288,8 +307,11 @@ func (s *stateSync) loop() error {
|
||||||
return errCancelStateFetch
|
return errCancelStateFetch
|
||||||
|
|
||||||
case req := <-s.deliver:
|
case req := <-s.deliver:
|
||||||
|
switch req.limit {
|
||||||
|
// If the request leaf limit is 0, we're in node-sync mode
|
||||||
|
case 0:
|
||||||
// Response, disconnect or timeout triggered, drop the peer if stalling
|
// Response, disconnect or timeout triggered, drop the peer if stalling
|
||||||
log.Trace("Received node data response", "peer", req.peer.id, "count", len(req.response), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut())
|
log.Trace("Received node data response", "peer", req.peer.id, "count", len(req.nodeRes), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut())
|
||||||
if len(req.items) <= 2 && !req.dropped && req.timedOut() {
|
if len(req.items) <= 2 && !req.dropped && req.timedOut() {
|
||||||
// 2 items are the minimum requested, if even that times out, we've no use of
|
// 2 items are the minimum requested, if even that times out, we've no use of
|
||||||
// this peer at the moment.
|
// this peer at the moment.
|
||||||
|
|
@ -304,7 +326,29 @@ func (s *stateSync) loop() error {
|
||||||
}
|
}
|
||||||
// The the delivery contains requested data, mark the node idle (otherwise it's a timed out delivery)
|
// The the delivery contains requested data, mark the node idle (otherwise it's a timed out delivery)
|
||||||
if !stale {
|
if !stale {
|
||||||
req.peer.SetNodeDataIdle(len(req.response))
|
req.peer.SetNodeDataIdle(len(req.nodeRes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we're in leaf-sync mode
|
||||||
|
default:
|
||||||
|
// Response, disconnect or timeout triggered, drop the peer if stalling
|
||||||
|
log.Trace("Received trie response", "peer", req.peer.id, "count", len(req.trieRes), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut())
|
||||||
|
if len(req.items) <= 2 && !req.dropped && req.timedOut() {
|
||||||
|
// 2 bytes are the minimum requested, if even that times out, we've no use of
|
||||||
|
// this peer at the moment.
|
||||||
|
log.Warn("Stalling trie sync, dropping peer", "peer", req.peer.id)
|
||||||
|
s.d.dropPeer(req.peer.id)
|
||||||
|
}
|
||||||
|
// Process all the received data and check for stale delivery
|
||||||
|
stale, err := s.process(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Trie write error", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// The the delivery contains requested data, mark the node idle (otherwise it's a timed out delivery)
|
||||||
|
if !stale {
|
||||||
|
req.peer.SetTrieIdle(int(req.limit))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -321,7 +365,7 @@ func (s *stateSync) commit(force bool) error {
|
||||||
if err := b.Write(); err != nil {
|
if err := b.Write(); err != nil {
|
||||||
return fmt.Errorf("DB write error: %v", err)
|
return fmt.Errorf("DB write error: %v", err)
|
||||||
}
|
}
|
||||||
s.updateStats(s.numUncommitted, 0, 0, time.Since(start))
|
s.updateStats(s.numUncommitted, s.bytesUncommitted, 0, 0, time.Since(start))
|
||||||
s.numUncommitted = 0
|
s.numUncommitted = 0
|
||||||
s.bytesUncommitted = 0
|
s.bytesUncommitted = 0
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -330,8 +374,25 @@ func (s *stateSync) commit(force bool) error {
|
||||||
// assignTasks attempts to assing new tasks to all idle peers, either from the
|
// assignTasks attempts to assing new tasks to all idle peers, either from the
|
||||||
// batch currently being retried, or fetching new data from the trie sync itself.
|
// batch currently being retried, or fetching new data from the trie sync itself.
|
||||||
func (s *stateSync) assignTasks() {
|
func (s *stateSync) assignTasks() {
|
||||||
|
// Iterate over all idle trie-sync capable peers and try to assign them state fetches
|
||||||
|
peers, _ := s.d.peers.TrieIdlePeers()
|
||||||
|
for _, p := range peers {
|
||||||
|
// Assign a batch of fetches proportional to the estimated latency/bandwidth
|
||||||
|
req := &stateReq{peer: p, timeout: s.d.requestTTL(), limit: common.StorageSize(p.TrieCapacity(s.d.requestRTT()))}
|
||||||
|
s.fillTasks(MaxStateFetch, req)
|
||||||
|
|
||||||
|
// If the peer was assigned tasks to fetch, send the network request
|
||||||
|
if len(req.items) > 0 {
|
||||||
|
req.peer.log.Trace("Requesting new batch of data", "type", "trie", "limit", req.limit)
|
||||||
|
select {
|
||||||
|
case s.d.trackStateReq <- req:
|
||||||
|
req.peer.FetchTries(req.items, req.limit)
|
||||||
|
case <-s.cancel:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Iterate over all idle peers and try to assign them state fetches
|
// Iterate over all idle peers and try to assign them state fetches
|
||||||
peers, _ := s.d.peers.NodeDataIdlePeers()
|
peers, _ = s.d.peers.NodeDataIdlePeers()
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
// Assign a batch of fetches proportional to the estimated latency/bandwidth
|
// Assign a batch of fetches proportional to the estimated latency/bandwidth
|
||||||
cap := p.NodeDataCapacity(s.d.requestRTT())
|
cap := p.NodeDataCapacity(s.d.requestRTT())
|
||||||
|
|
@ -389,20 +450,31 @@ func (s *stateSync) process(req *stateReq) (bool, error) {
|
||||||
|
|
||||||
defer func(start time.Time) {
|
defer func(start time.Time) {
|
||||||
if duplicate > 0 || unexpected > 0 {
|
if duplicate > 0 || unexpected > 0 {
|
||||||
s.updateStats(0, duplicate, unexpected, time.Since(start))
|
s.updateStats(0, 0, duplicate, unexpected, time.Since(start))
|
||||||
}
|
}
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
// Iterate over all the delivered data and inject one-by-one into the trie
|
// Iterate over all the delivered data and inject into the trie
|
||||||
progress, stale := false, len(req.response) > 0
|
results := make([]*trie.SyncResult, 0, len(req.nodeRes)+1)
|
||||||
|
if req.trieRes != nil {
|
||||||
|
for _, res := range req.trieRes {
|
||||||
|
results = append(results, res)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, blob := range req.nodeRes {
|
||||||
|
results = append(results, &trie.SyncResult{Data: blob})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progress, stale := false, len(req.nodeRes) > 0 || len(req.trieRes) > 0
|
||||||
|
for _, res := range results {
|
||||||
|
items, bytes, hash, err := s.sched.Process(res)
|
||||||
|
|
||||||
|
s.numUncommitted += items
|
||||||
|
s.bytesUncommitted += int(bytes)
|
||||||
|
|
||||||
for _, blob := range req.response {
|
|
||||||
prog, hash, err := s.sched.Process(&trie.SyncResult{Data: blob})
|
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
s.numUncommitted++
|
progress = progress || items > 0
|
||||||
s.bytesUncommitted += len(blob)
|
|
||||||
progress = progress || prog
|
|
||||||
case trie.ErrNotRequested:
|
case trie.ErrNotRequested:
|
||||||
unexpected++
|
unexpected++
|
||||||
case trie.ErrAlreadyProcessed:
|
case trie.ErrAlreadyProcessed:
|
||||||
|
|
@ -428,7 +500,7 @@ func (s *stateSync) process(req *stateReq) (bool, error) {
|
||||||
// If the node did deliver something, missing items may be due to a protocol
|
// If the node did deliver something, missing items may be due to a protocol
|
||||||
// limit or a previous timeout + delayed delivery. Both cases should permit
|
// limit or a previous timeout + delayed delivery. Both cases should permit
|
||||||
// the node to retry the missing items (to avoid single-peer stalls).
|
// the node to retry the missing items (to avoid single-peer stalls).
|
||||||
if len(req.response) > 0 || req.timedOut() {
|
if len(req.nodeRes) > 0 || len(req.trieRes) > 0 || req.timedOut() {
|
||||||
delete(task.attempts, req.peer.id)
|
delete(task.attempts, req.peer.id)
|
||||||
}
|
}
|
||||||
// If we've requested the node too many times already, it may be a malicious
|
// If we've requested the node too many times already, it may be a malicious
|
||||||
|
|
@ -444,7 +516,7 @@ func (s *stateSync) process(req *stateReq) (bool, error) {
|
||||||
|
|
||||||
// updateStats bumps the various state sync progress counters and displays a log
|
// updateStats bumps the various state sync progress counters and displays a log
|
||||||
// message for the user to see.
|
// message for the user to see.
|
||||||
func (s *stateSync) updateStats(written, duplicate, unexpected int, duration time.Duration) {
|
func (s *stateSync) updateStats(written, bytes, duplicate, unexpected int, duration time.Duration) {
|
||||||
s.d.syncStatsLock.Lock()
|
s.d.syncStatsLock.Lock()
|
||||||
defer s.d.syncStatsLock.Unlock()
|
defer s.d.syncStatsLock.Unlock()
|
||||||
|
|
||||||
|
|
@ -454,6 +526,6 @@ func (s *stateSync) updateStats(written, duplicate, unexpected int, duration tim
|
||||||
s.d.syncStatsState.unexpected += uint64(unexpected)
|
s.d.syncStatsState.unexpected += uint64(unexpected)
|
||||||
|
|
||||||
if written > 0 || duplicate > 0 || unexpected > 0 {
|
if written > 0 || duplicate > 0 || unexpected > 0 {
|
||||||
log.Info("Imported new state entries", "count", written, "elapsed", common.PrettyDuration(duration), "processed", s.d.syncStatsState.processed, "pending", s.d.syncStatsState.pending, "retry", len(s.tasks), "duplicate", s.d.syncStatsState.duplicate, "unexpected", s.d.syncStatsState.unexpected)
|
log.Info("Imported new state entries", "count", written, "bytes", bytes, "elapsed", common.PrettyDuration(duration), "processed", s.d.syncStatsState.processed, "pending", s.d.syncStatsState.pending, "retry", len(s.tasks), "duplicate", s.d.syncStatsState.duplicate, "unexpected", s.d.syncStatsState.unexpected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
// peerDropFn is a callback type for dropping a peer detected as malicious.
|
// peerDropFn is a callback type for dropping a peer detected as malicious.
|
||||||
|
|
@ -77,3 +78,14 @@ type statePack struct {
|
||||||
func (p *statePack) PeerId() string { return p.peerId }
|
func (p *statePack) PeerId() string { return p.peerId }
|
||||||
func (p *statePack) Items() int { return len(p.states) }
|
func (p *statePack) Items() int { return len(p.states) }
|
||||||
func (p *statePack) Stats() string { return fmt.Sprintf("%d", len(p.states)) }
|
func (p *statePack) Stats() string { return fmt.Sprintf("%d", len(p.states)) }
|
||||||
|
|
||||||
|
// triePack is a subtrie returned by a peer (or a single node if the hash does
|
||||||
|
// not denote or properly define a state trie).
|
||||||
|
type triePack struct {
|
||||||
|
peerId string
|
||||||
|
results []*trie.SyncResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *triePack) PeerId() string { return p.peerId }
|
||||||
|
func (p *triePack) Items() int { return len(p.results) }
|
||||||
|
func (p *triePack) Stats() string { return fmt.Sprintf("%d", len(p.results)) }
|
||||||
|
|
|
||||||
|
|
@ -470,7 +470,7 @@ func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool
|
||||||
var (
|
var (
|
||||||
evmux = new(event.TypeMux)
|
evmux = new(event.TypeMux)
|
||||||
pow = ethash.NewFaker()
|
pow = ethash.NewFaker()
|
||||||
db, _ = ethdb.NewMemDatabase()
|
db = ethdb.NewMemDatabase()
|
||||||
config = ¶ms.ChainConfig{DAOForkBlock: big.NewInt(1), DAOForkSupport: localForked}
|
config = ¶ms.ChainConfig{DAOForkBlock: big.NewInt(1), DAOForkSupport: localForked}
|
||||||
gspec = &core.Genesis{Config: config}
|
gspec = &core.Genesis{Config: config}
|
||||||
genesis = gspec.MustCommit(db)
|
genesis = gspec.MustCommit(db)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
|
||||||
var (
|
var (
|
||||||
evmux = new(event.TypeMux)
|
evmux = new(event.TypeMux)
|
||||||
engine = ethash.NewFaker()
|
engine = ethash.NewFaker()
|
||||||
db, _ = ethdb.NewMemDatabase()
|
db = ethdb.NewMemDatabase()
|
||||||
gspec = &core.Genesis{
|
gspec = &core.Genesis{
|
||||||
Config: params.TestChainConfig,
|
Config: params.TestChainConfig,
|
||||||
Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}},
|
Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}},
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,14 @@ func (p *peer) RequestNodeData(hashes []common.Hash) error {
|
||||||
return p2p.Send(p.rw, GetNodeDataMsg, hashes)
|
return p2p.Send(p.rw, GetNodeDataMsg, hashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestTries fetches a sub-trie from a node's known state data, corresponding
|
||||||
|
// to the specified root hash.
|
||||||
|
func (p *peer) RequestTries(roots []common.Hash, limit common.StorageSize) error {
|
||||||
|
p.Log().Debug("Fetching sub-trie", "roots", roots, "limit", limit)
|
||||||
|
panic("not implemented")
|
||||||
|
//return p2p.Send(p.rw, GetNodeDataMsg, hashes)
|
||||||
|
}
|
||||||
|
|
||||||
// RequestReceipts fetches a batch of transaction receipts from a remote node.
|
// RequestReceipts fetches a batch of transaction receipts from a remote node.
|
||||||
func (p *peer) RequestReceipts(hashes []common.Hash) error {
|
func (p *peer) RequestReceipts(hashes []common.Hash) error {
|
||||||
p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
|
p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
|
||||||
|
|
|
||||||
156
trie/sync.go
156
trie/sync.go
|
|
@ -21,6 +21,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
|
|
@ -89,6 +92,8 @@ type TrieSync struct {
|
||||||
requests map[common.Hash]*request // Pending requests pertaining to a key hash
|
requests map[common.Hash]*request // Pending requests pertaining to a key hash
|
||||||
queue *prque.Prque // Priority queue with the pending requests
|
queue *prque.Prque // Priority queue with the pending requests
|
||||||
keccak hash.Hash // Keccak256 hasher to verify deliveries with
|
keccak hash.Hash // Keccak256 hasher to verify deliveries with
|
||||||
|
|
||||||
|
nextId uint64 // Identifier component for the priority queue to split between same depths
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTrieSync creates a new trie data download scheduler.
|
// NewTrieSync creates a new trie data download scheduler.
|
||||||
|
|
@ -133,7 +138,7 @@ func (s *TrieSync) AddSubTrie(root common.Hash, depth int, parent common.Hash, c
|
||||||
ancestor.deps++
|
ancestor.deps++
|
||||||
req.parents = append(req.parents, ancestor)
|
req.parents = append(req.parents, ancestor)
|
||||||
}
|
}
|
||||||
s.schedule(req)
|
s.schedule(req, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRawEntry schedules the direct retrieval of a state entry that should not be
|
// AddRawEntry schedules the direct retrieval of a state entry that should not be
|
||||||
|
|
@ -166,7 +171,7 @@ func (s *TrieSync) AddRawEntry(hash common.Hash, depth int, parent common.Hash)
|
||||||
ancestor.deps++
|
ancestor.deps++
|
||||||
req.parents = append(req.parents, ancestor)
|
req.parents = append(req.parents, ancestor)
|
||||||
}
|
}
|
||||||
s.schedule(req)
|
s.schedule(req, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Missing retrieves the known missing nodes from the trie for retrieval.
|
// Missing retrieves the known missing nodes from the trie for retrieval.
|
||||||
|
|
@ -174,20 +179,21 @@ func (s *TrieSync) Missing(max int) []common.Hash {
|
||||||
requests := []common.Hash{}
|
requests := []common.Hash{}
|
||||||
for !s.queue.Empty() && (max == 0 || len(requests) < max) {
|
for !s.queue.Empty() && (max == 0 || len(requests) < max) {
|
||||||
hash := s.queue.PopItem().(common.Hash)
|
hash := s.queue.PopItem().(common.Hash)
|
||||||
if s.requests[hash] != nil {
|
if req := s.requests[hash]; req != nil && req.data == nil {
|
||||||
requests = append(requests, hash)
|
requests = append(requests, hash)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(".")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return requests
|
return requests
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process injects a batch of retrieved trie nodes data, returning if something
|
// Process injects a batch of retrieved trie data, returning the number of nodes
|
||||||
// was committed to the database and also the index of an entry if processing of
|
// and bytes written, along with the hash of the node or sub-trie just processed.
|
||||||
// it failed.
|
func (s *TrieSync) Process(result *SyncResult) (int, common.StorageSize, common.Hash, error) {
|
||||||
func (s *TrieSync) Process(result *SyncResult) (bool, common.Hash, error) {
|
|
||||||
// If it's a plain or full sub-trie delivery, inject and return
|
// If it's a plain or full sub-trie delivery, inject and return
|
||||||
if len(result.Keys) == 0 && len(result.Proof) == 0 {
|
if len(result.Keys) == 0 && len(result.Proof) == 0 {
|
||||||
return s.processNode(result.Data)
|
return s.processNode(common.Hash{}, result.Data, false)
|
||||||
}
|
}
|
||||||
if len(result.Proof) == 0 {
|
if len(result.Proof) == 0 {
|
||||||
return s.processLeaves(result.Keys, result.Values)
|
return s.processLeaves(result.Keys, result.Values)
|
||||||
|
|
@ -201,93 +207,101 @@ func (s *TrieSync) Process(result *SyncResult) (bool, common.Hash, error) {
|
||||||
|
|
||||||
// processNode verifies and processes a trie node, returning if anything was
|
// processNode verifies and processes a trie node, returning if anything was
|
||||||
// committed and the hash of the node injected.
|
// committed and the hash of the node injected.
|
||||||
func (s *TrieSync) processNode(blob []byte) (bool, common.Hash, error) {
|
func (s *TrieSync) processNode(hash common.Hash, blob []byte, ready bool) (int, common.StorageSize, common.Hash, error) {
|
||||||
// Derive the hash of the result based on its content
|
// Derive the hash of the result based on its content
|
||||||
var hash common.Hash
|
if hash == (common.Hash{}) {
|
||||||
|
|
||||||
s.keccak.Reset()
|
s.keccak.Reset()
|
||||||
s.keccak.Write(blob)
|
s.keccak.Write(blob)
|
||||||
s.keccak.Sum(hash[:0])
|
s.keccak.Sum(hash[:0])
|
||||||
|
}
|
||||||
// If the item was not requested, bail out
|
// If the item was not requested, bail out
|
||||||
request := s.requests[hash]
|
request := s.requests[hash]
|
||||||
if request == nil {
|
if request == nil {
|
||||||
return false, hash, ErrNotRequested
|
return 0, 0, hash, nil //ErrNotRequested
|
||||||
}
|
}
|
||||||
if request.data != nil {
|
if request.data != nil {
|
||||||
return false, hash, nil // TODO(karalabe): Why not ErrAlreadyProcessed
|
return 0, 0, hash, ErrAlreadyProcessed
|
||||||
}
|
}
|
||||||
// If the item is a raw entry request, commit directly
|
// If the item is a raw entry request, commit directly
|
||||||
if request.raw {
|
if request.raw {
|
||||||
request.data = blob
|
request.data = blob
|
||||||
s.commit(request)
|
items, bytes := s.commit(request)
|
||||||
return true, hash, nil
|
return items, bytes, hash, nil
|
||||||
}
|
}
|
||||||
// Decode and inject into the trie
|
// Decode and inject into the trie
|
||||||
node, err := decodeNode(hash[:], blob, 0)
|
node, err := decodeNode(hash[:], blob, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, hash, err
|
return 0, 0, hash, err
|
||||||
}
|
}
|
||||||
request.data = blob
|
request.data = blob
|
||||||
|
|
||||||
// Create and schedule a request for all the children nodes
|
// Create and schedule a request for all the children nodes
|
||||||
requests, err := s.children(request, node)
|
requests, err := s.children(request, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, hash, err
|
return 0, 0, hash, err
|
||||||
}
|
}
|
||||||
if len(requests) == 0 && request.deps == 0 {
|
if len(requests) == 0 && request.deps == 0 {
|
||||||
s.commit(request)
|
items, bytes := s.commit(request)
|
||||||
return true, hash, nil
|
return items, bytes, hash, nil
|
||||||
}
|
}
|
||||||
request.deps += len(requests)
|
request.deps += len(requests)
|
||||||
for _, child := range requests {
|
for _, child := range requests {
|
||||||
s.schedule(child)
|
s.schedule(child, ready)
|
||||||
}
|
}
|
||||||
return false, hash, nil
|
return 0, 0, hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processLeaves reconstructs a sub-trie from the given key-value pairs, returning
|
// processLeaves reconstructs a sub-trie from the given key-value pairs, returning
|
||||||
// the root of the sub-trie or an error on failure.
|
// the number of nodes and bytes written, along with the hash of the sub-trie just
|
||||||
func (s *TrieSync) processLeaves(keys [][]byte, values [][]byte) (bool, common.Hash, error) {
|
// processed.
|
||||||
|
func (s *TrieSync) processLeaves(keys [][]byte, values [][]byte) (int, common.StorageSize, common.Hash, error) {
|
||||||
// Inject all the leaves into a fresh trie and derive it's root hash
|
// Inject all the leaves into a fresh trie and derive it's root hash
|
||||||
db := ethdb.NewMemDatabase()
|
db := ethdb.NewMemDatabase()
|
||||||
trie, err := New(common.Hash{}, db)
|
trie, err := New(common.Hash{}, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, common.Hash{}, err
|
return 0, 0, common.Hash{}, err
|
||||||
}
|
}
|
||||||
for j := 0; j < len(keys); j++ {
|
for j := 0; j < len(keys); j++ {
|
||||||
trie.Update(keys[j], values[j])
|
trie.Update(keys[j], values[j])
|
||||||
}
|
}
|
||||||
root, err := trie.Commit()
|
root, err := trie.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, common.Hash{}, err
|
return 0, 0, common.Hash{}, err
|
||||||
}
|
}
|
||||||
// If the item was not requested, bail out
|
// If the item was not requested, bail out
|
||||||
request := s.requests[root]
|
request := s.requests[root]
|
||||||
if request == nil {
|
if request == nil {
|
||||||
return false, root, ErrNotRequested
|
return 0, 0, root, ErrNotRequested
|
||||||
}
|
}
|
||||||
if request.data != nil {
|
if request.data != nil {
|
||||||
return false, root, ErrAlreadyProcessed
|
return 0, 0, root, ErrAlreadyProcessed
|
||||||
}
|
}
|
||||||
// Inject all key-values as is and complete the root
|
// Inject all key-values as is and complete the root
|
||||||
for _, key := range db.Keys() {
|
var (
|
||||||
value, _ := db.Get(key)
|
items int
|
||||||
if hash := common.BytesToHash(key); hash != root {
|
bytes common.StorageSize
|
||||||
s.commitEntry(hash, value)
|
)
|
||||||
} else {
|
it := trie.NodeIterator(nil)
|
||||||
request.data = value
|
for it.Next(true) {
|
||||||
}
|
if hash := it.Hash(); hash != (common.Hash{}) {
|
||||||
}
|
blob, _ := db.Get(hash[:])
|
||||||
s.commit(request)
|
count, size, _, err := s.processNode(hash, blob, true)
|
||||||
|
|
||||||
return true, root, nil
|
items += count
|
||||||
|
bytes += size
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return items, bytes, root, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items, bytes, root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processPartialLeaves reconstructs a sub-trie from the Merkle proof and the
|
// processPartialLeaves reconstructs a sub-trie from the Merkle proof and the
|
||||||
// available key-value pairs, commiting the available parts and scheduling the
|
// available key-value pairs, commiting the available parts and scheduling the
|
||||||
// missing items for future retrival.
|
// missing items for future retrival.
|
||||||
func (s *TrieSync) processPartialLeaves(keys [][]byte, values [][]byte, proof [][]byte) (bool, common.Hash, error) {
|
func (s *TrieSync) processPartialLeaves(keys [][]byte, values [][]byte, proof [][]byte) (int, common.StorageSize, common.Hash, error) {
|
||||||
// Derive the hash of the topmost proof
|
// Derive the hash of the topmost proof
|
||||||
var root common.Hash
|
var root common.Hash
|
||||||
|
|
||||||
|
|
@ -298,29 +312,29 @@ func (s *TrieSync) processPartialLeaves(keys [][]byte, values [][]byte, proof []
|
||||||
// If the item was not requested, bail out
|
// If the item was not requested, bail out
|
||||||
request := s.requests[root]
|
request := s.requests[root]
|
||||||
if request == nil {
|
if request == nil {
|
||||||
return false, root, ErrNotRequested
|
return 0, 0, root, ErrNotRequested
|
||||||
}
|
}
|
||||||
if request.data != nil {
|
if request.data != nil {
|
||||||
return false, root, ErrAlreadyProcessed
|
return 0, 0, root, ErrAlreadyProcessed
|
||||||
}
|
}
|
||||||
// Decode the root node and schedule missing children
|
// Decode the root node and schedule missing children
|
||||||
node, err := decodeNode(root[:], proof[0], 0)
|
node, err := decodeNode(root[:], proof[0], 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, root, err
|
return 0, 0, root, err
|
||||||
}
|
}
|
||||||
request.data = proof[0]
|
request.data = proof[0]
|
||||||
|
|
||||||
requests, err := s.children(request, node)
|
requests, err := s.children(request, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, root, err
|
return 0, 0, root, err
|
||||||
}
|
}
|
||||||
if len(requests) == 0 && request.deps == 0 {
|
if len(requests) == 0 && request.deps == 0 {
|
||||||
s.commit(request)
|
items, bytes := s.commit(request)
|
||||||
return true, root, nil
|
return items, bytes, root, nil
|
||||||
}
|
}
|
||||||
request.deps += len(requests)
|
request.deps += len(requests)
|
||||||
for _, child := range requests {
|
for _, child := range requests {
|
||||||
s.schedule(child)
|
s.schedule(child, false)
|
||||||
}
|
}
|
||||||
// Fulfill any children satisfied by the key-value pairs
|
// Fulfill any children satisfied by the key-value pairs
|
||||||
switch node := (node).(type) {
|
switch node := (node).(type) {
|
||||||
|
|
@ -328,15 +342,20 @@ func (s *TrieSync) processPartialLeaves(keys [][]byte, values [][]byte, proof []
|
||||||
// All keys must have the short node's path as a prefix
|
// All keys must have the short node's path as a prefix
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
if !bytes.HasPrefix(key, node.Key) {
|
if !bytes.HasPrefix(key, node.Key) {
|
||||||
return false, root, fmt.Errorf("key mismatch at proof %x", proof[0])
|
return 0, 0, root, fmt.Errorf("key mismatch at proof %x", proof[0])
|
||||||
}
|
}
|
||||||
keys[i] = key[len(node.Key):]
|
keys[i] = key[len(node.Key):]
|
||||||
}
|
}
|
||||||
// Recurse into the subtrie of the short node
|
// Recurse into the subtrie of the short node
|
||||||
commit, _, err := s.processPartialLeaves(keys, values, proof[1:])
|
items, bytes, _, err := s.processPartialLeaves(keys, values, proof[1:])
|
||||||
return commit, root, err
|
return items, bytes, root, err
|
||||||
|
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
|
// Track the number of items and bytes written
|
||||||
|
var (
|
||||||
|
items int
|
||||||
|
bytes common.StorageSize
|
||||||
|
)
|
||||||
// Split up the keyspace between the full node's children
|
// Split up the keyspace between the full node's children
|
||||||
for i := 0; i < 17; i++ {
|
for i := 0; i < 17; i++ {
|
||||||
if node.Children[i] != nil {
|
if node.Children[i] != nil {
|
||||||
|
|
@ -350,21 +369,26 @@ func (s *TrieSync) processPartialLeaves(keys [][]byte, values [][]byte, proof []
|
||||||
if _, ok := node.Children[i].(hashNode); !ok {
|
if _, ok := node.Children[i].(hashNode); !ok {
|
||||||
// If we're at the last node, process it as a partial trie
|
// If we're at the last node, process it as a partial trie
|
||||||
if split == len(keys) && len(proof) != 1 {
|
if split == len(keys) && len(proof) != 1 {
|
||||||
commit, _, err := s.processPartialLeaves(keys[:split], values[:split], proof[1:])
|
count, size, _, err := s.processPartialLeaves(keys[:split], values[:split], proof[1:])
|
||||||
return commit, root, err
|
return items + count, bytes + size, root, err
|
||||||
}
|
}
|
||||||
// Otherwise we have a full sub-trie, parse in its entirety (if not already contained within the full node)
|
// Otherwise we have a full sub-trie, parse in its entirety (if not already contained within the full node)
|
||||||
commit, _, err := s.processLeaves(keys[:split], values[:split])
|
count, size, _, err := s.processLeaves(keys[:split], values[:split])
|
||||||
|
|
||||||
|
items += count
|
||||||
|
bytes += size
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return commit, root, err
|
return items, bytes, root, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keys = keys[split:]
|
keys = keys[split:]
|
||||||
values = values[split:]
|
values = values[split:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return items, bytes, root, nil
|
||||||
}
|
}
|
||||||
return false, root, nil
|
return 0, 0, root, fmt.Errorf("unexpected node type: %v", reflect.TypeOf(node))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit flushes the data stored in the internal membatch out to persistent
|
// Commit flushes the data stored in the internal membatch out to persistent
|
||||||
|
|
@ -391,14 +415,16 @@ func (s *TrieSync) Pending() int {
|
||||||
// schedule inserts a new state retrieval request into the fetch queue. If there
|
// schedule inserts a new state retrieval request into the fetch queue. If there
|
||||||
// is already a pending request for this node, the new request will be discarded
|
// is already a pending request for this node, the new request will be discarded
|
||||||
// and only a parent reference added to the old one.
|
// and only a parent reference added to the old one.
|
||||||
func (s *TrieSync) schedule(req *request) {
|
func (s *TrieSync) schedule(req *request, ready bool) {
|
||||||
// If we're already requesting this node, add a new reference and stop
|
// If we're already requesting this node, add a new reference and stop
|
||||||
if old, ok := s.requests[req.hash]; ok {
|
if old, ok := s.requests[req.hash]; ok {
|
||||||
old.parents = append(old.parents, req.parents...)
|
old.parents = append(old.parents, req.parents...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Schedule the request for future retrieval
|
// Schedule the request for future retrieval
|
||||||
s.queue.Push(req.hash, float32(req.depth))
|
if !ready {
|
||||||
|
s.queue.Push(req.hash, float32(req.depth)*math.MaxUint64+float32(math.MaxUint64-atomic.AddUint64(&s.nextId, 1)))
|
||||||
|
}
|
||||||
s.requests[req.hash] = req
|
s.requests[req.hash] = req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -465,8 +491,13 @@ func (s *TrieSync) children(req *request, object node) ([]*request, error) {
|
||||||
|
|
||||||
// commit finalizes a retrieval request and stores it into the membatch. If any
|
// commit finalizes a retrieval request and stores it into the membatch. If any
|
||||||
// of the referencing parent requests complete due to this commit, they are also
|
// of the referencing parent requests complete due to this commit, they are also
|
||||||
// committed themselves.
|
// committed themselves. The method returns the number of state items written to
|
||||||
func (s *TrieSync) commit(req *request) (err error) {
|
// the membatch as well as their total data size.
|
||||||
|
func (s *TrieSync) commit(req *request) (int, common.StorageSize) {
|
||||||
|
var (
|
||||||
|
items = 1
|
||||||
|
bytes = common.StorageSize(len(req.data))
|
||||||
|
)
|
||||||
// Write the node content to the membatch
|
// Write the node content to the membatch
|
||||||
s.commitEntry(req.hash, req.data)
|
s.commitEntry(req.hash, req.data)
|
||||||
delete(s.requests, req.hash)
|
delete(s.requests, req.hash)
|
||||||
|
|
@ -475,12 +506,13 @@ func (s *TrieSync) commit(req *request) (err error) {
|
||||||
for _, parent := range req.parents {
|
for _, parent := range req.parents {
|
||||||
parent.deps--
|
parent.deps--
|
||||||
if parent.deps == 0 {
|
if parent.deps == 0 {
|
||||||
if err := s.commit(parent); err != nil {
|
count, size := s.commit(parent)
|
||||||
return err
|
|
||||||
|
items += count
|
||||||
|
bytes += size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return items, bytes
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitEntry injects a raw database entry into the memory batch to be flushed
|
// commitEntry injects a raw database entry into the memory batch to be flushed
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
|
@ -124,11 +125,11 @@ func testIterativeTrieSync(t *testing.T, batch int) {
|
||||||
}
|
}
|
||||||
results[i] = &SyncResult{Data: data}
|
results[i] = &SyncResult{Data: data}
|
||||||
} else {
|
} else {
|
||||||
results[i], _ = srcTrie.FetchData(hash, 8192)
|
results[i], _, _ = srcTrie.FetchData(hash, 8192, 250*time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index, result := range results {
|
for index, result := range results {
|
||||||
if _, _, err := sched.Process(result); err != nil {
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,11 +164,11 @@ func TestIterativeDelayedTrieSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
results[i] = &SyncResult{Data: data}
|
results[i] = &SyncResult{Data: data}
|
||||||
} else {
|
} else {
|
||||||
results[i], _ = srcTrie.FetchData(hash, 8192)
|
results[i], _, _ = srcTrie.FetchData(hash, 8192, 250*time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index, result := range results {
|
for index, result := range results {
|
||||||
if _, _, err := sched.Process(result); err != nil {
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,13 +210,13 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) {
|
||||||
}
|
}
|
||||||
results = append(results, &SyncResult{Data: data})
|
results = append(results, &SyncResult{Data: data})
|
||||||
} else {
|
} else {
|
||||||
request, _ := srcTrie.FetchData(hash, 8192)
|
request, _, _ := srcTrie.FetchData(hash, 8192, 250*time.Millisecond)
|
||||||
results = append(results, request)
|
results = append(results, request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Feed the retrieved results back and queue new tasks
|
// Feed the retrieved results back and queue new tasks
|
||||||
for index, result := range results {
|
for index, result := range results {
|
||||||
if _, _, err := sched.Process(result); err != nil {
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +257,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
results = append(results, &SyncResult{Data: data})
|
results = append(results, &SyncResult{Data: data})
|
||||||
} else {
|
} else {
|
||||||
request, _ := srcTrie.FetchData(hash, 8192)
|
request, _, _ := srcTrie.FetchData(hash, 8192, 250*time.Millisecond)
|
||||||
results = append(results, request)
|
results = append(results, request)
|
||||||
}
|
}
|
||||||
if len(results) >= cap(results) {
|
if len(results) >= cap(results) {
|
||||||
|
|
@ -265,7 +266,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Feed the retrieved results back and queue new tasks
|
// Feed the retrieved results back and queue new tasks
|
||||||
for index, result := range results {
|
for index, result := range results {
|
||||||
_, hash, err := sched.Process(result)
|
_, _, hash, err := sched.Process(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
|
@ -310,11 +311,11 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
results[i] = &SyncResult{Data: data}
|
results[i] = &SyncResult{Data: data}
|
||||||
} else {
|
} else {
|
||||||
results[i], _ = srcTrie.FetchData(hash, 8192)
|
results[i], _, _ = srcTrie.FetchData(hash, 8192, 250*time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index, result := range results {
|
for index, result := range results {
|
||||||
if _, _, err := sched.Process(result); err != nil {
|
if _, _, _, err := sched.Process(result); err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -350,12 +351,12 @@ func TestIncompleteTrieSync(t *testing.T) {
|
||||||
}
|
}
|
||||||
results[i] = &SyncResult{Data: data}
|
results[i] = &SyncResult{Data: data}
|
||||||
} else {
|
} else {
|
||||||
results[i], _ = srcTrie.FetchData(hash, 8192)
|
results[i], _, _ = srcTrie.FetchData(hash, 8192, 250*time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Process each of the trie nodes
|
// Process each of the trie nodes
|
||||||
for index, result := range results {
|
for index, result := range results {
|
||||||
_, hash, err := sched.Process(result)
|
_, _, hash, err := sched.Process(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err)
|
t.Fatalf("failed to process result #%d: %v", index, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
trie/trie.go
31
trie/trie.go
|
|
@ -20,10 +20,12 @@ package trie
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -506,7 +508,17 @@ func (t *Trie) hashRoot(db DatabaseWriter) (node, node, error) {
|
||||||
|
|
||||||
// FetchData retrieves synchronization data from the trie to send to remote
|
// FetchData retrieves synchronization data from the trie to send to remote
|
||||||
// nodes.
|
// nodes.
|
||||||
func (t *Trie) FetchData(hash common.Hash, limit common.StorageSize) (res *SyncResult, fail error) {
|
func (t *Trie) FetchData(hash common.Hash, limit common.StorageSize, timeout time.Duration) (res *SyncResult, size common.StorageSize, fail error) {
|
||||||
|
// If we have a snapshot of this exact data, return without iteration
|
||||||
|
if blob, _ := t.db.Get(append([]byte("S"), hash[:]...)); blob != nil {
|
||||||
|
result := new(SyncResult)
|
||||||
|
if err := rlp.DecodeBytes(blob, result); err == nil {
|
||||||
|
return result, common.StorageSize(len(blob)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Start measuring the time to allow aborting if the storage limit is too generous
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
// If this method panics, hash points to a non-iterable trie; return individual node
|
// If this method panics, hash points to a non-iterable trie; return individual node
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
|
@ -514,7 +526,7 @@ func (t *Trie) FetchData(hash common.Hash, limit common.StorageSize) (res *SyncR
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail = err
|
fail = err
|
||||||
} else {
|
} else {
|
||||||
res = &SyncResult{Data: blob}
|
res, size = &SyncResult{Data: blob}, common.StorageSize(len(blob))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -524,19 +536,26 @@ func (t *Trie) FetchData(hash common.Hash, limit common.StorageSize) (res *SyncR
|
||||||
trie, _ := New(hash, t.db)
|
trie, _ := New(hash, t.db)
|
||||||
it := NewIterator(trie.NodeIterator(nil))
|
it := NewIterator(trie.NodeIterator(nil))
|
||||||
|
|
||||||
size := common.StorageSize(0)
|
size = common.StorageSize(0)
|
||||||
for size < limit && it.Next() {
|
for size < limit && time.Since(start) < timeout && it.Next() {
|
||||||
result.Keys = append(result.Keys, common.CopyBytes(it.Key))
|
result.Keys = append(result.Keys, common.CopyBytes(it.Key))
|
||||||
result.Values = append(result.Values, common.CopyBytes(it.Value))
|
result.Values = append(result.Values, common.CopyBytes(it.Value))
|
||||||
|
|
||||||
size += common.StorageSize(len(it.Key) + len(it.Value))
|
size += common.StorageSize(len(it.Key) + len(it.Value))
|
||||||
}
|
}
|
||||||
// If we've went past our data allowance, prove the partial data
|
// If we've went past our data allowance, prove the partial data
|
||||||
if size >= limit {
|
if size >= limit || time.Since(start) >= timeout {
|
||||||
result.Proof = it.Prove()
|
result.Proof = it.Prove()
|
||||||
if !it.Next() {
|
if !it.Next() {
|
||||||
result.Proof = nil // Overflowing item was the last after all
|
result.Proof = nil // Overflowing item was the last after all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
// If we have the entire storage-trie in memory, create a snapshot of it
|
||||||
|
if result.Proof == nil && len(result.Keys) > 0 && len(result.Keys[0]) == 32 {
|
||||||
|
if blob, err := rlp.EncodeToBytes(result); err == nil {
|
||||||
|
fmt.Printf(".")
|
||||||
|
t.db.Put(append([]byte("S"), hash[:]...), blob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, size, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue