dht_bootstrap.go 4.3 KB
Newer Older
1 2 3
package dht

import (
Jeromy's avatar
Jeromy committed
4
	"context"
5 6 7 8
	"crypto/rand"
	"fmt"
	"time"

9
	u "github.com/ipfs/go-ipfs-util"
10
	peer "github.com/libp2p/go-libp2p-peer"
Matt Joiner's avatar
Matt Joiner committed
11
	peerstore "github.com/libp2p/go-libp2p-peerstore"
George Antoniadis's avatar
George Antoniadis committed
12
	routing "github.com/libp2p/go-libp2p-routing"
13 14
)

15
// BootstrapConfig specifies parameters used bootstrapping the DHT.
16
//
17 18 19 20 21
// Note there is a tradeoff between the bootstrap period and the
// number of queries. We could support a higher period with less
// queries.
type BootstrapConfig struct {
	Queries int           // how many queries to run per period
22 23
	Period  time.Duration // how often to run periodic bootstrap.
	Timeout time.Duration // how long to wait for a bootstrap query to run
24
}
25

26 27 28 29 30 31
var DefaultBootstrapConfig = BootstrapConfig{
	// For now, this is set to 1 query.
	// We are currently more interested in ensuring we have a properly formed
	// DHT than making sure our dht minimizes traffic. Once we are more certain
	// of our implementation's robustness, we should lower this down to 8 or 4.
	Queries: 1,
32

Cole Brown's avatar
Cole Brown committed
33
	// For now, this is set to 5 minutes, which is a medium period. We are
34
	// We are currently more interested in ensuring we have a properly formed
35 36
	// DHT than making sure our dht minimizes traffic.
	Period: time.Duration(5 * time.Minute),
37

38
	Timeout: time.Duration(10 * time.Second),
39 40
}

Matt Joiner's avatar
Matt Joiner committed
41 42
// A method in the IpfsRouting interface. It calls BootstrapWithConfig with
// the default bootstrap config.
43
func (dht *IpfsDHT) Bootstrap(ctx context.Context) error {
Matt Joiner's avatar
Matt Joiner committed
44 45
	return dht.BootstrapWithConfig(ctx, DefaultBootstrapConfig)
}
46

Matt Joiner's avatar
Matt Joiner committed
47 48 49 50 51 52
// Runs cfg.Queries bootstrap queries every cfg.Period.
func (dht *IpfsDHT) BootstrapWithConfig(ctx context.Context, cfg BootstrapConfig) error {
	if cfg.Queries <= 0 {
		return fmt.Errorf("invalid number of queries: %d", cfg.Queries)
	}
	go func() {
53
		for {
Matt Joiner's avatar
Matt Joiner committed
54 55 56 57
			err := dht.runBootstrap(ctx, cfg)
			if err != nil {
				log.Warningf("error bootstrapping: %s", err)
			}
58 59
			select {
			case <-time.After(cfg.Period):
Matt Joiner's avatar
Matt Joiner committed
60
			case <-ctx.Done():
61 62 63
				return
			}
		}
Matt Joiner's avatar
Matt Joiner committed
64 65 66
	}()
	return nil
}
vyzo's avatar
vyzo committed
67

Matt Joiner's avatar
Matt Joiner committed
68 69 70 71 72 73 74 75 76
// This is a synchronous bootstrap. cfg.Queries queries will run each with a
// timeout of cfg.Timeout. cfg.Period is not used.
func (dht *IpfsDHT) BootstrapOnce(ctx context.Context, cfg BootstrapConfig) error {
	if cfg.Queries <= 0 {
		return fmt.Errorf("invalid number of queries: %d", cfg.Queries)
	}
	return dht.runBootstrap(ctx, cfg)
}

Matt Joiner's avatar
Matt Joiner committed
77 78 79 80 81
func newRandomPeerId() peer.ID {
	id := make([]byte, 32) // SHA256 is the default. TODO: Use a more canonical way to generate random IDs.
	rand.Read(id)
	id = u.Hash(id) // TODO: Feed this directly into the multihash instead of hashing it.
	return peer.ID(id)
82 83
}

Matt Joiner's avatar
Matt Joiner committed
84 85 86 87 88 89 90
// Traverse the DHT toward the given ID.
func (dht *IpfsDHT) walk(ctx context.Context, target peer.ID) (peerstore.PeerInfo, error) {
	// TODO: Extract the query action (traversal logic?) inside FindPeer,
	// don't actually call through the FindPeer machinery, which can return
	// things out of the peer store etc.
	return dht.FindPeer(ctx, target)
}
91

Matt Joiner's avatar
Matt Joiner committed
92 93 94 95 96 97 98 99
// Traverse the DHT toward a random ID.
func (dht *IpfsDHT) randomWalk(ctx context.Context) error {
	id := newRandomPeerId()
	p, err := dht.walk(ctx, id)
	switch err {
	case routing.ErrNotFound:
		return nil
	case nil:
Matt Joiner's avatar
Matt Joiner committed
100 101 102
		// We found a peer from a randomly generated ID. This should be very
		// unlikely.
		log.Warningf("random walk toward %s actually found peer: %s", id, p)
Matt Joiner's avatar
Matt Joiner committed
103 104 105
		return nil
	default:
		return err
106
	}
107 108 109
}

// runBootstrap builds up list of peers by requesting random peer IDs
110
func (dht *IpfsDHT) runBootstrap(ctx context.Context, cfg BootstrapConfig) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
111 112 113 114 115 116
	bslog := func(msg string) {
		log.Debugf("DHT %s dhtRunBootstrap %s -- routing table size: %d", dht.self, msg, dht.routingTable.Size())
	}
	bslog("start")
	defer bslog("end")
	defer log.EventBegin(ctx, "dhtRunBootstrap").Done()
117

Matt Joiner's avatar
Matt Joiner committed
118 119
	doQuery := func(n int, target string, f func(context.Context) error) error {
		log.Debugf("Bootstrapping query (%d/%d) to %s", n, cfg.Queries, target)
120 121
		ctx, cancel := context.WithTimeout(ctx, cfg.Timeout)
		defer cancel()
Matt Joiner's avatar
Matt Joiner committed
122
		return f(ctx)
123 124
	}

Matt Joiner's avatar
Matt Joiner committed
125 126 127 128 129 130
	// Do all but one of the bootstrap queries as random walks.
	for i := 1; i < cfg.Queries; i++ {
		err := doQuery(i, "random ID", dht.randomWalk)
		if err != nil {
			return err
		}
131 132
	}

133
	// Find self to distribute peer info to our neighbors.
Matt Joiner's avatar
Matt Joiner committed
134 135 136 137
	return doQuery(cfg.Queries, fmt.Sprintf("self: %s", dht.self), func(ctx context.Context) error {
		_, err := dht.walk(ctx, dht.self)
		return err
	})
138
}