dht_bootstrap.go 6.45 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
	pstore "github.com/libp2p/go-libp2p-peerstore"
George Antoniadis's avatar
George Antoniadis committed
12
	routing "github.com/libp2p/go-libp2p-routing"
13
	multiaddr "github.com/multiformats/go-multiaddr"
Hector Sanjuan's avatar
Hector Sanjuan committed
14
	_ "github.com/multiformats/go-multiaddr-dns"
15 16
)

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
var DefaultBootstrapPeers []multiaddr.Multiaddr

func init() {
	for _, s := range []string{
		"/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
		"/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
		"/dnsaddr/bootstrap.libp2p.io/ipfs/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
		"/dnsaddr/bootstrap.libp2p.io/ipfs/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
		"/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",            // mars.i.ipfs.io
		"/ip4/104.236.179.241/tcp/4001/ipfs/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM",           // pluto.i.ipfs.io
		"/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu",           // saturn.i.ipfs.io
		"/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64",             // venus.i.ipfs.io
		"/ip4/178.62.158.247/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd",            // earth.i.ipfs.io
		"/ip6/2604:a880:1:20::203:d001/tcp/4001/ipfs/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM",  // pluto.i.ipfs.io
		"/ip6/2400:6180:0:d0::151:6001/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu",  // saturn.i.ipfs.io
		"/ip6/2604:a880:800:10::4a:5001/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64", // venus.i.ipfs.io
		"/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd", // earth.i.ipfs.io
	} {
		ma, err := multiaddr.NewMultiaddr(s)
		if err != nil {
			panic(err)
		}
		DefaultBootstrapPeers = append(DefaultBootstrapPeers, ma)
	}
}

43
// BootstrapConfig specifies parameters used bootstrapping the DHT.
44
//
45 46 47 48 49
// 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
50 51
	Period  time.Duration // how often to run periodic bootstrap.
	Timeout time.Duration // how long to wait for a bootstrap query to run
52
}
53

54 55 56 57 58 59
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,
60

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

66
	Timeout: time.Duration(10 * time.Second),
67 68
}

Matt Joiner's avatar
Matt Joiner committed
69 70
// A method in the IpfsRouting interface. It calls BootstrapWithConfig with
// the default bootstrap config.
71
func (dht *IpfsDHT) Bootstrap(ctx context.Context) error {
Matt Joiner's avatar
Matt Joiner committed
72
	return dht.BootstrapWithConfig(ctx, DefaultBootstrapConfig)
Matt Joiner's avatar
Matt Joiner committed
73
}
74

Matt Joiner's avatar
Matt Joiner committed
75 76 77 78
// Runs cfg.Queries bootstrap queries every cfg.Period.
func (dht *IpfsDHT) BootstrapWithConfig(ctx context.Context, cfg BootstrapConfig) error {
	// Because this method is not synchronous, we have to duplicate sanity
	// checks on the config so that callers aren't oblivious.
Matt Joiner's avatar
Matt Joiner committed
79
	if cfg.Queries <= 0 {
Matt Joiner's avatar
Matt Joiner committed
80
		return fmt.Errorf("invalid number of queries: %d", cfg.Queries)
Matt Joiner's avatar
Matt Joiner committed
81
	}
Matt Joiner's avatar
Matt Joiner committed
82
	go func() {
83
		for {
Matt Joiner's avatar
Matt Joiner committed
84 85
			err := dht.runBootstrap(ctx, cfg)
			if err != nil {
Matt Joiner's avatar
Matt Joiner committed
86
				logger.Warningf("error bootstrapping: %s", err)
Matt Joiner's avatar
Matt Joiner committed
87
			}
88 89
			select {
			case <-time.After(cfg.Period):
Matt Joiner's avatar
Matt Joiner committed
90
			case <-ctx.Done():
91 92 93
				return
			}
		}
Matt Joiner's avatar
Matt Joiner committed
94 95
	}()
	return nil
Matt Joiner's avatar
Matt Joiner committed
96
}
vyzo's avatar
vyzo committed
97

Matt Joiner's avatar
Matt Joiner committed
98 99 100
// 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 {
Matt Joiner's avatar
Matt Joiner committed
101
	if cfg.Queries <= 0 {
Matt Joiner's avatar
Matt Joiner committed
102
		return fmt.Errorf("invalid number of queries: %d", cfg.Queries)
Matt Joiner's avatar
Matt Joiner committed
103
	}
Matt Joiner's avatar
Matt Joiner committed
104 105
	return dht.runBootstrap(ctx, cfg)
}
Matt Joiner's avatar
Matt Joiner committed
106

Matt Joiner's avatar
Matt Joiner committed
107 108 109 110 111
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)
Matt Joiner's avatar
Matt Joiner committed
112
}
113

Matt Joiner's avatar
Matt Joiner committed
114 115 116 117 118 119 120
// Traverse the DHT toward the given ID.
func (dht *IpfsDHT) walk(ctx context.Context, target peer.ID) (pstore.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)
}
Steven Allen's avatar
Steven Allen committed
121

Matt Joiner's avatar
Matt Joiner committed
122 123 124 125 126 127 128 129 130 131
// 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:
		// We found a peer from a randomly generated ID. This should be very
		// unlikely.
Matt Joiner's avatar
Matt Joiner committed
132
		logger.Warningf("random walk toward %s actually found peer: %s", id, p)
Matt Joiner's avatar
Matt Joiner committed
133 134 135
		return nil
	default:
		return err
136
	}
137 138 139
}

// runBootstrap builds up list of peers by requesting random peer IDs
140
func (dht *IpfsDHT) runBootstrap(ctx context.Context, cfg BootstrapConfig) error {
Matt Joiner's avatar
Matt Joiner committed
141
	doQuery := func(n int, target string, f func(context.Context) error) error {
142 143 144 145 146 147
		logger.Infof("starting bootstrap query (%d/%d) to %s (routing table size was %d)",
			n, cfg.Queries, target, dht.routingTable.Size())
		defer func() {
			logger.Infof("finished bootstrap query (%d/%d) to %s (routing table size is now %d)",
				n, cfg.Queries, target, dht.routingTable.Size())
		}()
148
		queryCtx, cancel := context.WithTimeout(ctx, cfg.Timeout)
149
		defer cancel()
150 151 152 153 154
		err := f(queryCtx)
		if err == context.DeadlineExceeded && queryCtx.Err() == context.DeadlineExceeded && ctx.Err() == nil {
			return nil
		}
		return err
155 156
	}

Matt Joiner's avatar
Matt Joiner committed
157
	// Do all but one of the bootstrap queries as random walks.
Steven Allen's avatar
Steven Allen committed
158
	for i := 0; i < cfg.Queries; i++ {
Matt Joiner's avatar
Matt Joiner committed
159 160 161 162
		err := doQuery(i, "random ID", dht.randomWalk)
		if err != nil {
			return err
		}
Steven Allen's avatar
Steven Allen committed
163 164
	}

165
	// Find self to distribute peer info to our neighbors.
Matt Joiner's avatar
Matt Joiner committed
166 167 168 169
	return doQuery(cfg.Queries, fmt.Sprintf("self: %s", dht.self), func(ctx context.Context) error {
		_, err := dht.walk(ctx, dht.self)
		return err
	})
170
}
171 172 173 174 175 176 177 178 179

func (dht *IpfsDHT) BootstrapRandom(ctx context.Context) error {
	return dht.randomWalk(ctx)
}

func (dht *IpfsDHT) BootstrapSelf(ctx context.Context) error {
	_, err := dht.walk(ctx, dht.self)
	return err
}